hadron-document
Advanced tools
Comparing version 0.0.0-next-44318443f2cd1088082181789b25fc26a7a11bb2 to 0.0.0-next-4474f17fe4181829ba5d709778c535a4c343cccf
@@ -7,5 +7,19 @@ import type { Element } from './element'; | ||
import type { HadronEJSONOptions } from './utils'; | ||
/** | ||
* The event constant. | ||
*/ | ||
export declare const Events: { | ||
Cancel: string; | ||
Expanded: string; | ||
Collapsed: string; | ||
VisibleElementsChanged: string; | ||
EditingStarted: string; | ||
EditingFinished: string; | ||
MarkedForDeletion: string; | ||
DeletionFinished: string; | ||
}; | ||
export declare const DEFAULT_VISIBLE_ELEMENTS = 25; | ||
/** | ||
* Represents a document. | ||
*/ | ||
export declare class Document extends EventEmitter { | ||
@@ -19,7 +33,47 @@ uuid: string; | ||
currentType: 'Document'; | ||
size: number | null; | ||
expanded: boolean; | ||
maxVisibleElementsCount: number; | ||
editing: boolean; | ||
markedForDeletion: boolean; | ||
/** | ||
* Send cancel event. | ||
*/ | ||
cancel(): void; | ||
/** | ||
* Create the new document from the provided object. | ||
* | ||
* @param {Object} doc - The document. | ||
* @param {boolean} cloned - If it is a cloned document. | ||
*/ | ||
constructor(doc: BSONObject, cloned?: boolean); | ||
apply(doc: BSONObject | Document): void; | ||
/** | ||
* Generate the javascript object for this document. | ||
* | ||
* @returns {Object} The javascript object. | ||
*/ | ||
generateObject(options?: ObjectGeneratorOptions): BSONObject; | ||
/** | ||
* Generate the javascript object with the original elements in this document. | ||
* | ||
* @returns {Object} The original javascript object. | ||
*/ | ||
generateOriginalObject(options?: ObjectGeneratorOptions): BSONObject; | ||
/** | ||
* Generate the `query` and `updateDoc` to be used in an update operation | ||
* where the update only succeeds when the changed document's elements have | ||
* not been changed in the background. | ||
* | ||
* `query` and `updateDoc` may use $getField and $setField if field names | ||
* contain either `.` or start with `$`. These operators are only available | ||
* on MongoDB 5.0+. (Note that field names starting with `$` are also only | ||
* allowed in MongoDB 5.0+.) | ||
* | ||
* @param keyInclusionOptions Specify which fields to include in the | ||
* originalFields list. | ||
* | ||
* @returns {Object} An object containing the `query` and `updateDoc` to be | ||
* used in an update operation. | ||
*/ | ||
generateUpdateUnlessChangedInBackgroundQuery(opts?: Readonly<KeyInclusionOptions>): { | ||
@@ -32,22 +86,149 @@ query: BSONObject; | ||
}; | ||
/** | ||
* Get an element by its key. | ||
* | ||
* @param {String} key | ||
* | ||
* @returns {Element} The element. | ||
*/ | ||
get(key: string): Element | undefined; | ||
/** | ||
* Get an element by a series of segment names. | ||
* | ||
* @param {Array} path - The series of fieldnames. Cannot be empty. | ||
* | ||
* @returns {Element} The element. | ||
*/ | ||
getChild(path: (string | number)[]): Element | undefined; | ||
/** | ||
* Get the _id value for the document. | ||
* | ||
* @returns {Object} The id. | ||
*/ | ||
getId(): BSONValue; | ||
/** | ||
* Generate the query javascript object reflecting the elements that | ||
* are specified by the keys listed in `keys`. The values of this object are | ||
* the original values, this can be used when querying for an update based | ||
* on multiple criteria. | ||
* | ||
* @param keyInclusionOptions Specify which fields to include in the | ||
* originalFields list. | ||
* | ||
* @returns {Object} The javascript object. | ||
*/ | ||
getQueryForOriginalKeysAndValuesForSpecifiedKeys(opts?: Readonly<KeyInclusionOptions>): BSONObject; | ||
/** | ||
* Get the _id value as a string. Required if _id is not always an ObjectId. | ||
* | ||
* @returns {String} The string id. | ||
*/ | ||
getStringId(): null | string; | ||
/** | ||
* Insert a placeholder element at the end of the document. | ||
* | ||
* @returns {Element} The placeholder element. | ||
*/ | ||
insertPlaceholder(): Element; | ||
/** | ||
* Add a new element to this document. | ||
* | ||
* @param {String} key - The element key. | ||
* @param {Object} value - The value. | ||
* | ||
* @returns {Element} The new element. | ||
*/ | ||
insertBeginning(key: string | number, value: BSONValue): Element; | ||
/** | ||
* Add a new element to this document. | ||
* | ||
* @param {String} key - The element key. | ||
* @param {Object} value - The value. | ||
* | ||
* @returns {Element} The new element. | ||
*/ | ||
insertEnd(key: string | number, value: BSONValue): Element; | ||
/** | ||
* Insert an element after the provided element. | ||
* | ||
* @param {Element} element - The element to insert after. | ||
* @param {String} key - The key. | ||
* @param {Object} value - The value. | ||
* | ||
* @returns {Element} The new element. | ||
*/ | ||
insertAfter(element: Element, key: string | number, value: BSONValue): Element | undefined; | ||
/** | ||
* A document always exists, is never added. | ||
* | ||
* @returns Always false. | ||
*/ | ||
isAdded(): boolean; | ||
/** | ||
* Determine if the element is modified at all. | ||
* | ||
* @returns {Boolean} If the element is modified. | ||
*/ | ||
isModified(): boolean; | ||
/** | ||
* A document is never removed | ||
* | ||
* @returns {false} Always false. | ||
*/ | ||
isRemoved(): boolean; | ||
/** | ||
* The document object is always the root object. | ||
* | ||
* @returns {true} Always true. | ||
*/ | ||
isRoot(): this is Document; | ||
/** | ||
* Generates a sequence of elements. | ||
* | ||
* @returns {Array} The elements. | ||
*/ | ||
_generateElements(): ElementList; | ||
/** | ||
* @deprecated Use DocumentEvents import instead | ||
*/ | ||
static get Events(): typeof Events; | ||
/** | ||
* Parse a new Document from extended JSON input. | ||
*/ | ||
static FromEJSON(input: string): Document; | ||
/** | ||
* Parse multiple Document from extended JSON input. | ||
* If the input consists of only a single document without | ||
* `[array]` brackets, return an array consisting of only | ||
* that document. | ||
*/ | ||
static FromEJSONArray(input: string): Document[]; | ||
/** | ||
* Convert this Document instance into a human-readable EJSON string. | ||
*/ | ||
toEJSON(source?: 'original' | 'current', options?: HadronEJSONOptions): string; | ||
/** | ||
* Expands a document by expanding all of its fields | ||
*/ | ||
expand(): void; | ||
/** | ||
* Collapses a document by collapsing all of its fields | ||
*/ | ||
collapse(): void; | ||
getVisibleElements(): Element[]; | ||
setMaxVisibleElementsCount(newCount: number): void; | ||
getTotalVisibleElementsCount(): number; | ||
startEditing(elementId?: string, field?: 'key' | 'value' | 'type'): void; | ||
finishEditing(): void; | ||
onUpdateStart(): void; | ||
onUpdateSuccess(doc: Record<string, unknown>): void; | ||
onUpdateBlocked(): void; | ||
onUpdateError(error: Error): void; | ||
markForDeletion(): void; | ||
finishDeletion(): void; | ||
onRemoveStart(): void; | ||
onRemoveSuccess(): void; | ||
onRemoveError(error: Error): void; | ||
} | ||
export default Document; | ||
//# sourceMappingURL=document.d.ts.map |
@@ -6,3 +6,3 @@ 'use strict'; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Document = exports.Events = void 0; | ||
exports.Document = exports.DEFAULT_VISIBLE_ELEMENTS = exports.Events = void 0; | ||
const element_1 = require("./element"); | ||
@@ -13,9 +13,49 @@ const eventemitter3_1 = __importDefault(require("eventemitter3")); | ||
const utils_1 = require("./utils"); | ||
const _1 = require("."); | ||
/** | ||
* The event constant. | ||
*/ | ||
exports.Events = { | ||
Cancel: 'Document::Cancel', | ||
Expanded: 'Document::Expanded', | ||
Collapsed: 'Document::Collapsed', | ||
VisibleElementsChanged: 'Document::VisibleElementsChanged', | ||
EditingStarted: 'Document::EditingStarted', | ||
EditingFinished: 'Document::EditingFinished', | ||
MarkedForDeletion: 'Document::MarkedForDeletion', | ||
DeletionFinished: 'Document::DeletionFinished', | ||
}; | ||
/** | ||
* The id field. | ||
*/ | ||
const ID = '_id'; | ||
exports.DEFAULT_VISIBLE_ELEMENTS = 25; | ||
/** | ||
* Represents a document. | ||
*/ | ||
class Document extends eventemitter3_1.default { | ||
/** | ||
* Send cancel event. | ||
*/ | ||
cancel() { | ||
// Cancel will remove elements from iterator, clone it before iterating | ||
// otherwise we will skip items | ||
for (const element of Array.from(this.elements)) { | ||
element.cancel(); | ||
} | ||
this.emit(exports.Events.Cancel); | ||
} | ||
/** | ||
* Create the new document from the provided object. | ||
* | ||
* @param {Object} doc - The document. | ||
* @param {boolean} cloned - If it is a cloned document. | ||
*/ | ||
constructor(doc, cloned = false) { | ||
super(); | ||
this.size = null; | ||
this.expanded = false; | ||
this.maxVisibleElementsCount = exports.DEFAULT_VISIBLE_ELEMENTS; | ||
this.editing = false; | ||
this.markedForDeletion = false; | ||
this.uuid = new bson_1.UUID().toHexString(); | ||
@@ -29,10 +69,4 @@ this.doc = doc; | ||
} | ||
cancel() { | ||
for (const element of Array.from(this.elements)) { | ||
element.cancel(); | ||
} | ||
this.emit(exports.Events.Cancel); | ||
} | ||
apply(doc) { | ||
if (typeof (doc === null || doc === void 0 ? void 0 : doc.generateObject) === 'function') { | ||
if (typeof doc?.generateObject === 'function') { | ||
doc = doc.generateObject(); | ||
@@ -61,9 +95,39 @@ } | ||
} | ||
/** | ||
* Generate the javascript object for this document. | ||
* | ||
* @returns {Object} The javascript object. | ||
*/ | ||
generateObject(options) { | ||
return object_generator_1.default.generate(this.elements, options); | ||
} | ||
/** | ||
* Generate the javascript object with the original elements in this document. | ||
* | ||
* @returns {Object} The original javascript object. | ||
*/ | ||
generateOriginalObject(options) { | ||
return object_generator_1.default.generateOriginal(this.elements, options); | ||
} | ||
/** | ||
* Generate the `query` and `updateDoc` to be used in an update operation | ||
* where the update only succeeds when the changed document's elements have | ||
* not been changed in the background. | ||
* | ||
* `query` and `updateDoc` may use $getField and $setField if field names | ||
* contain either `.` or start with `$`. These operators are only available | ||
* on MongoDB 5.0+. (Note that field names starting with `$` are also only | ||
* allowed in MongoDB 5.0+.) | ||
* | ||
* @param keyInclusionOptions Specify which fields to include in the | ||
* originalFields list. | ||
* | ||
* @returns {Object} An object containing the `query` and `updateDoc` to be | ||
* used in an update operation. | ||
*/ | ||
generateUpdateUnlessChangedInBackgroundQuery(opts = {}) { | ||
// Build a query that will find the document to update only if it has the | ||
// values of elements that were changed with their original value. | ||
// This query won't find the document if an updated element's value isn't | ||
// the same value as it was when it was originally loaded. | ||
const originalFieldsThatWillBeUpdated = object_generator_1.default.getQueryForOriginalKeysAndValuesForSpecifiedFields(this, opts, true); | ||
@@ -80,5 +144,19 @@ const query = { | ||
} | ||
/** | ||
* Get an element by its key. | ||
* | ||
* @param {String} key | ||
* | ||
* @returns {Element} The element. | ||
*/ | ||
get(key) { | ||
return this.elements.get(key); | ||
} | ||
/** | ||
* Get an element by a series of segment names. | ||
* | ||
* @param {Array} path - The series of fieldnames. Cannot be empty. | ||
* | ||
* @returns {Element} The element. | ||
*/ | ||
getChild(path) { | ||
@@ -102,2 +180,7 @@ if (!path) { | ||
} | ||
/** | ||
* Get the _id value for the document. | ||
* | ||
* @returns {Object} The id. | ||
*/ | ||
getId() { | ||
@@ -107,5 +190,21 @@ const element = this.get(ID); | ||
} | ||
/** | ||
* Generate the query javascript object reflecting the elements that | ||
* are specified by the keys listed in `keys`. The values of this object are | ||
* the original values, this can be used when querying for an update based | ||
* on multiple criteria. | ||
* | ||
* @param keyInclusionOptions Specify which fields to include in the | ||
* originalFields list. | ||
* | ||
* @returns {Object} The javascript object. | ||
*/ | ||
getQueryForOriginalKeysAndValuesForSpecifiedKeys(opts = {}) { | ||
return object_generator_1.default.getQueryForOriginalKeysAndValuesForSpecifiedFields(this, opts, false); | ||
} | ||
/** | ||
* Get the _id value as a string. Required if _id is not always an ObjectId. | ||
* | ||
* @returns {String} The string id. | ||
*/ | ||
getStringId() { | ||
@@ -122,23 +221,66 @@ const element = this.get(ID); | ||
} | ||
/** | ||
* Insert a placeholder element at the end of the document. | ||
* | ||
* @returns {Element} The placeholder element. | ||
*/ | ||
insertPlaceholder() { | ||
return this.insertEnd('', ''); | ||
} | ||
/** | ||
* Add a new element to this document. | ||
* | ||
* @param {String} key - The element key. | ||
* @param {Object} value - The value. | ||
* | ||
* @returns {Element} The new element. | ||
*/ | ||
insertBeginning(key, value) { | ||
const newElement = this.elements.insertBeginning(key, value); | ||
newElement._bubbleUp(element_1.Events.Added, newElement, this); | ||
this.emit(exports.Events.VisibleElementsChanged, this); | ||
return newElement; | ||
} | ||
/** | ||
* Add a new element to this document. | ||
* | ||
* @param {String} key - The element key. | ||
* @param {Object} value - The value. | ||
* | ||
* @returns {Element} The new element. | ||
*/ | ||
insertEnd(key, value) { | ||
const newElement = this.elements.insertEnd(key, value); | ||
newElement._bubbleUp(element_1.Events.Added, newElement, this); | ||
this.emit(exports.Events.VisibleElementsChanged, this); | ||
return newElement; | ||
} | ||
/** | ||
* Insert an element after the provided element. | ||
* | ||
* @param {Element} element - The element to insert after. | ||
* @param {String} key - The key. | ||
* @param {Object} value - The value. | ||
* | ||
* @returns {Element} The new element. | ||
*/ | ||
insertAfter(element, key, value) { | ||
const newElement = this.elements.insertAfter(element, key, value); | ||
newElement === null || newElement === void 0 ? void 0 : newElement._bubbleUp(element_1.Events.Added, newElement, this); | ||
newElement?._bubbleUp(element_1.Events.Added, newElement, this); | ||
this.emit(exports.Events.VisibleElementsChanged, this); | ||
return newElement; | ||
} | ||
/** | ||
* A document always exists, is never added. | ||
* | ||
* @returns Always false. | ||
*/ | ||
isAdded() { | ||
return false; | ||
} | ||
/** | ||
* Determine if the element is modified at all. | ||
* | ||
* @returns {Boolean} If the element is modified. | ||
*/ | ||
isModified() { | ||
@@ -152,14 +294,35 @@ for (const element of this.elements) { | ||
} | ||
/** | ||
* A document is never removed | ||
* | ||
* @returns {false} Always false. | ||
*/ | ||
isRemoved() { | ||
return false; | ||
} | ||
/** | ||
* The document object is always the root object. | ||
* | ||
* @returns {true} Always true. | ||
*/ | ||
isRoot() { | ||
return true; | ||
} | ||
/** | ||
* Generates a sequence of elements. | ||
* | ||
* @returns {Array} The elements. | ||
*/ | ||
_generateElements() { | ||
return new element_1.ElementList(this, this.doc); | ||
} | ||
/** | ||
* @deprecated Use DocumentEvents import instead | ||
*/ | ||
static get Events() { | ||
return exports.Events; | ||
} | ||
/** | ||
* Parse a new Document from extended JSON input. | ||
*/ | ||
static FromEJSON(input) { | ||
@@ -169,2 +332,8 @@ const parsed = bson_1.EJSON.parse(input, { relaxed: false }); | ||
} | ||
/** | ||
* Parse multiple Document from extended JSON input. | ||
* If the input consists of only a single document without | ||
* `[array]` brackets, return an array consisting of only | ||
* that document. | ||
*/ | ||
static FromEJSONArray(input) { | ||
@@ -176,2 +345,5 @@ const parsed = bson_1.EJSON.parse(input, { relaxed: false }); | ||
} | ||
/** | ||
* Convert this Document instance into a human-readable EJSON string. | ||
*/ | ||
toEJSON(source = 'current', options = {}) { | ||
@@ -183,2 +355,84 @@ const obj = source === 'original' | ||
} | ||
/** | ||
* Expands a document by expanding all of its fields | ||
*/ | ||
expand() { | ||
this.expanded = true; | ||
for (const element of this.elements) { | ||
element.expand(true); | ||
} | ||
this.emit(exports.Events.Expanded); | ||
this.emit(exports.Events.VisibleElementsChanged, this); | ||
} | ||
/** | ||
* Collapses a document by collapsing all of its fields | ||
*/ | ||
collapse() { | ||
this.expanded = false; | ||
for (const element of this.elements) { | ||
element.collapse(); | ||
} | ||
this.emit(exports.Events.Collapsed); | ||
this.emit(exports.Events.VisibleElementsChanged, this); | ||
} | ||
getVisibleElements() { | ||
return [...this.elements].slice(0, this.maxVisibleElementsCount); | ||
} | ||
setMaxVisibleElementsCount(newCount) { | ||
this.maxVisibleElementsCount = newCount; | ||
this.emit(exports.Events.VisibleElementsChanged, this); | ||
} | ||
getTotalVisibleElementsCount() { | ||
const visibleElements = this.getVisibleElements(); | ||
return visibleElements.reduce((totalVisibleChildElements, element) => { | ||
return (totalVisibleChildElements + 1 + element.getTotalVisibleElementsCount()); | ||
}, 0); | ||
} | ||
startEditing(elementId, field) { | ||
if (!this.editing) { | ||
this.editing = true; | ||
this.emit(_1.DocumentEvents.EditingStarted, elementId, field); | ||
} | ||
} | ||
finishEditing() { | ||
if (this.editing) { | ||
this.editing = false; | ||
this.emit(_1.DocumentEvents.EditingFinished); | ||
} | ||
} | ||
onUpdateStart() { | ||
this.emit('update-start'); | ||
} | ||
onUpdateSuccess(doc) { | ||
this.emit('update-success', doc); | ||
this.finishEditing(); | ||
} | ||
onUpdateBlocked() { | ||
this.emit('update-blocked'); | ||
} | ||
onUpdateError(error) { | ||
this.emit('update-error', error.message); | ||
} | ||
markForDeletion() { | ||
if (!this.markedForDeletion) { | ||
this.markedForDeletion = true; | ||
this.emit(_1.DocumentEvents.MarkedForDeletion); | ||
} | ||
} | ||
finishDeletion() { | ||
if (this.markedForDeletion) { | ||
this.markedForDeletion = false; | ||
this.emit(_1.DocumentEvents.DeletionFinished); | ||
} | ||
} | ||
onRemoveStart() { | ||
this.emit('remove-start'); | ||
} | ||
onRemoveSuccess() { | ||
this.emit('remove-success'); | ||
this.finishDeletion(); | ||
} | ||
onRemoveError(error) { | ||
this.emit('remove-error', error.message); | ||
} | ||
} | ||
@@ -185,0 +439,0 @@ exports.Document = Document; |
import type { BSONValue } from '../utils'; | ||
import StandardEditor from './standard'; | ||
import type Element from '../element'; | ||
/** | ||
* CRUD editor for date values. | ||
*/ | ||
export default class DateEditor extends StandardEditor { | ||
/** | ||
* Create the editor with the element. | ||
* | ||
* @param {Element} element - The hadron document element. | ||
*/ | ||
constructor(element: Element); | ||
/** | ||
* Complete the date edit by converting the valid string to a date | ||
* object or leaving as invalid. | ||
*/ | ||
complete(): void; | ||
/** | ||
* Edit the element with the provided value. | ||
* | ||
* @param {Object} value - The new value. | ||
*/ | ||
edit(value: BSONValue): void; | ||
/** | ||
* Get the number of characters the value should display. | ||
* | ||
* @param {Boolean} editMode - If the element is being edited. | ||
* | ||
* @returns {Number} The number of characters. | ||
*/ | ||
size(editMode?: boolean): number; | ||
/** | ||
* Start the date edit. | ||
* | ||
* @param {Object} value - The value in the field. | ||
*/ | ||
start(): void; | ||
/** | ||
* Get the value being edited. | ||
* | ||
* @param {Boolean} editMode - If the UI is in edit mode. | ||
* | ||
* @returns {String} The value. | ||
*/ | ||
value(): string; | ||
@@ -11,0 +47,0 @@ _formattedValue(): string; |
@@ -10,6 +10,18 @@ "use strict"; | ||
const standard_1 = __importDefault(require("./standard")); | ||
/** | ||
* CRUD editor for date values. | ||
*/ | ||
class DateEditor extends standard_1.default { | ||
/** | ||
* Create the editor with the element. | ||
* | ||
* @param {Element} element - The hadron document element. | ||
*/ | ||
constructor(element) { | ||
super(element); | ||
} | ||
/** | ||
* Complete the date edit by converting the valid string to a date | ||
* object or leaving as invalid. | ||
*/ | ||
complete() { | ||
@@ -21,2 +33,7 @@ super.complete(); | ||
} | ||
/** | ||
* Edit the element with the provided value. | ||
* | ||
* @param {Object} value - The new value. | ||
*/ | ||
edit(value) { | ||
@@ -38,2 +55,9 @@ try { | ||
} | ||
/** | ||
* Get the number of characters the value should display. | ||
* | ||
* @param {Boolean} editMode - If the element is being edited. | ||
* | ||
* @returns {Number} The number of characters. | ||
*/ | ||
size(editMode) { | ||
@@ -48,2 +72,7 @@ const value = this.element.currentValue; | ||
} | ||
/** | ||
* Start the date edit. | ||
* | ||
* @param {Object} value - The value in the field. | ||
*/ | ||
start() { | ||
@@ -55,2 +84,9 @@ super.start(); | ||
} | ||
/** | ||
* Get the value being edited. | ||
* | ||
* @param {Boolean} editMode - If the UI is in edit mode. | ||
* | ||
* @returns {String} The value. | ||
*/ | ||
value() { | ||
@@ -64,5 +100,11 @@ const value = this.element.currentValue; | ||
_formattedValue() { | ||
return new Date(this.element.currentValue) | ||
.toISOString() | ||
.replace('Z', '+00:00'); | ||
// BSON Date are uint64_t ms, JS Date only supports float64 ms, | ||
// so some valid BSON Dates are not representable in JS. | ||
const date = new Date(this.element.currentValue); | ||
try { | ||
return date.toISOString().replace('Z', '+00:00'); | ||
} | ||
catch { | ||
return String(date); | ||
} | ||
} | ||
@@ -69,0 +111,0 @@ } |
import StandardEditor from './standard'; | ||
import type Element from '../element'; | ||
import type { BSONValue } from '../utils'; | ||
/** | ||
* CRUD editor for decimal128 values. | ||
*/ | ||
export default class Decimal128Editor extends StandardEditor { | ||
/** | ||
* Create the editor with the element. | ||
* | ||
* @param {Element} element - The hadron document element. | ||
*/ | ||
constructor(element: Element); | ||
/** | ||
* Complete the decimal128 edit by converting the valid string to a decimal128 | ||
* value or leaving as invalid. | ||
*/ | ||
complete(): void; | ||
/** | ||
* Edit Decimal128 element. Check if the value is a Decimal128 before setting typed | ||
* up value. | ||
* | ||
* @param {Object} value - The new value. | ||
*/ | ||
edit(value: BSONValue): void; | ||
} | ||
//# sourceMappingURL=decimal128.d.ts.map |
@@ -9,6 +9,18 @@ "use strict"; | ||
const standard_1 = __importDefault(require("./standard")); | ||
/** | ||
* CRUD editor for decimal128 values. | ||
*/ | ||
class Decimal128Editor extends standard_1.default { | ||
/** | ||
* Create the editor with the element. | ||
* | ||
* @param {Element} element - The hadron document element. | ||
*/ | ||
constructor(element) { | ||
super(element); | ||
} | ||
/** | ||
* Complete the decimal128 edit by converting the valid string to a decimal128 | ||
* value or leaving as invalid. | ||
*/ | ||
complete() { | ||
@@ -20,2 +32,8 @@ super.complete(); | ||
} | ||
/** | ||
* Edit Decimal128 element. Check if the value is a Decimal128 before setting typed | ||
* up value. | ||
* | ||
* @param {Object} value - The new value. | ||
*/ | ||
edit(value) { | ||
@@ -22,0 +40,0 @@ try { |
import type { BSONValue } from '../utils'; | ||
import StandardEditor from './standard'; | ||
import type Element from '../element'; | ||
/** | ||
* CRUD editor for double values. | ||
*/ | ||
export default class DoubleEditor extends StandardEditor { | ||
/** | ||
* Create the editor with the element. | ||
* | ||
* @param {Element} element - The hadron document element. | ||
*/ | ||
constructor(element: Element); | ||
/** | ||
* Complete the double edit by converting the valid string to a double | ||
* value or leaving as invalid. | ||
*/ | ||
complete(): void; | ||
/** | ||
* Edit Double element. Check if the value is a Double before setting tped | ||
* up value. | ||
* | ||
* @param {Object} value - The new value. | ||
*/ | ||
edit(value: BSONValue): void; | ||
/** | ||
* Get the number of characters the value should display. | ||
* | ||
* @param {Boolean} editMode - If the element is being edited. | ||
* | ||
* @returns {Number} The number of characters. | ||
*/ | ||
size(): number; | ||
} | ||
//# sourceMappingURL=double.d.ts.map |
@@ -10,6 +10,18 @@ "use strict"; | ||
const standard_1 = __importDefault(require("./standard")); | ||
/** | ||
* CRUD editor for double values. | ||
*/ | ||
class DoubleEditor extends standard_1.default { | ||
/** | ||
* Create the editor with the element. | ||
* | ||
* @param {Element} element - The hadron document element. | ||
*/ | ||
constructor(element) { | ||
super(element); | ||
} | ||
/** | ||
* Complete the double edit by converting the valid string to a double | ||
* value or leaving as invalid. | ||
*/ | ||
complete() { | ||
@@ -21,2 +33,8 @@ super.complete(); | ||
} | ||
/** | ||
* Edit Double element. Check if the value is a Double before setting tped | ||
* up value. | ||
* | ||
* @param {Object} value - The new value. | ||
*/ | ||
edit(value) { | ||
@@ -38,5 +56,16 @@ try { | ||
} | ||
/** | ||
* Get the number of characters the value should display. | ||
* | ||
* @param {Boolean} editMode - If the element is being edited. | ||
* | ||
* @returns {Number} The number of characters. | ||
*/ | ||
size() { | ||
const currentValue = this.element.currentValue; | ||
return (0, utils_1.fieldStringLen)(typeof currentValue.valueOf === 'function' | ||
return (0, utils_1.fieldStringLen)( | ||
// Not all values that will be checked here are bson types with a `value` | ||
// property, using valueOf is a more resilient way of getting the "native" | ||
// value from `currentValue` | ||
typeof currentValue.valueOf === 'function' | ||
? currentValue.valueOf() | ||
@@ -43,0 +72,0 @@ : currentValue); |
@@ -36,4 +36,4 @@ import StandardEditor from './standard'; | ||
export default _default; | ||
export declare type Editor = DateEditor | StandardEditor | StringEditor | Decimal128Editor | DoubleEditor | Int32Editor | Int64Editor | NullEditor | UndefinedEditor | ObjectIdEditor; | ||
export type Editor = DateEditor | StandardEditor | StringEditor | Decimal128Editor | DoubleEditor | Int32Editor | Int64Editor | NullEditor | UndefinedEditor | ObjectIdEditor; | ||
export { DateEditor, StandardEditor, StringEditor, Decimal128Editor, DoubleEditor, Int32Editor, Int64Editor, NullEditor, UndefinedEditor, ObjectIdEditor, }; | ||
//# sourceMappingURL=index.d.ts.map |
import StandardEditor from './standard'; | ||
import type Element from '../element'; | ||
/** | ||
* CRUD editor for int32 values. | ||
*/ | ||
export default class Int32Editor extends StandardEditor { | ||
/** | ||
* Create the editor with the element. | ||
* | ||
* @param {Element} element - The hadron document element. | ||
*/ | ||
constructor(element: Element); | ||
/** | ||
* Get the number of characters the value should display. | ||
* | ||
* @param {Boolean} editMode - If the element is being edited. | ||
* | ||
* @returns {Number} The number of characters. | ||
*/ | ||
size(): number; | ||
} | ||
//# sourceMappingURL=int32.d.ts.map |
@@ -8,6 +8,21 @@ "use strict"; | ||
const standard_1 = __importDefault(require("./standard")); | ||
/** | ||
* CRUD editor for int32 values. | ||
*/ | ||
class Int32Editor extends standard_1.default { | ||
/** | ||
* Create the editor with the element. | ||
* | ||
* @param {Element} element - The hadron document element. | ||
*/ | ||
constructor(element) { | ||
super(element); | ||
} | ||
/** | ||
* Get the number of characters the value should display. | ||
* | ||
* @param {Boolean} editMode - If the element is being edited. | ||
* | ||
* @returns {Number} The number of characters. | ||
*/ | ||
size() { | ||
@@ -14,0 +29,0 @@ return (0, utils_1.fieldStringLen)(this.element.currentValue.valueOf()); |
import StandardEditor from './standard'; | ||
import type Element from '../element'; | ||
import type { BSONValue } from '../utils'; | ||
/** | ||
* CRUD editor for int32 values. | ||
*/ | ||
export default class Int64Editor extends StandardEditor { | ||
/** | ||
* Create the editor with the element. | ||
* | ||
* @param {Element} element - The hadron document element. | ||
*/ | ||
constructor(element: Element); | ||
/** | ||
* Complete the int64 edit by converting the valid string to a int64 | ||
* value or leaving as invalid. | ||
*/ | ||
complete(): void; | ||
/** | ||
* Edit Int64 element. Check if the value is a Int64 before setting typed | ||
* up value. | ||
* | ||
* @param {Object} value - The new value. | ||
*/ | ||
edit(value: BSONValue): void; | ||
} | ||
//# sourceMappingURL=int64.d.ts.map |
@@ -9,6 +9,18 @@ "use strict"; | ||
const standard_1 = __importDefault(require("./standard")); | ||
/** | ||
* CRUD editor for int32 values. | ||
*/ | ||
class Int64Editor extends standard_1.default { | ||
/** | ||
* Create the editor with the element. | ||
* | ||
* @param {Element} element - The hadron document element. | ||
*/ | ||
constructor(element) { | ||
super(element); | ||
} | ||
/** | ||
* Complete the int64 edit by converting the valid string to a int64 | ||
* value or leaving as invalid. | ||
*/ | ||
complete() { | ||
@@ -20,2 +32,8 @@ super.complete(); | ||
} | ||
/** | ||
* Edit Int64 element. Check if the value is a Int64 before setting typed | ||
* up value. | ||
* | ||
* @param {Object} value - The new value. | ||
*/ | ||
edit(value) { | ||
@@ -22,0 +40,0 @@ try { |
import StandardEditor from './standard'; | ||
import type Element from '../element'; | ||
/** | ||
* CRUD editor for null values. | ||
*/ | ||
export default class NullEditor extends StandardEditor { | ||
/** | ||
* Create the editor with the element. | ||
* | ||
* @param {Element} element - The hadron document element. | ||
*/ | ||
constructor(element: Element); | ||
/** | ||
* Get the number of characters the value should display. | ||
* | ||
* @param {Boolean} editMode - If the element is being edited. | ||
* | ||
* @returns {Number} The number of characters. | ||
*/ | ||
size(): number; | ||
/** | ||
* Get the value being edited. | ||
* | ||
* @returns {Object} The value. | ||
*/ | ||
value(): 'null'; | ||
} | ||
//# sourceMappingURL=null.d.ts.map |
@@ -7,10 +7,33 @@ "use strict"; | ||
const standard_1 = __importDefault(require("./standard")); | ||
/** | ||
* Null is always 'null' | ||
*/ | ||
const VALUE = 'null'; | ||
/** | ||
* CRUD editor for null values. | ||
*/ | ||
class NullEditor extends standard_1.default { | ||
/** | ||
* Create the editor with the element. | ||
* | ||
* @param {Element} element - The hadron document element. | ||
*/ | ||
constructor(element) { | ||
super(element); | ||
} | ||
/** | ||
* Get the number of characters the value should display. | ||
* | ||
* @param {Boolean} editMode - If the element is being edited. | ||
* | ||
* @returns {Number} The number of characters. | ||
*/ | ||
size() { | ||
return 4; | ||
} | ||
/** | ||
* Get the value being edited. | ||
* | ||
* @returns {Object} The value. | ||
*/ | ||
value() { | ||
@@ -17,0 +40,0 @@ return VALUE; |
import StandardEditor from './standard'; | ||
import type Element from '../element'; | ||
import type { BSONValue } from '../utils'; | ||
/** | ||
* CRUD editor for object id values. | ||
*/ | ||
export default class ObjectIdEditor extends StandardEditor { | ||
/** | ||
* Create the editor with the element. | ||
* | ||
* @param {Element} element - The hadron document element. | ||
*/ | ||
constructor(element: Element); | ||
/** | ||
* Complete the object id edit by converting the valid string to an object id | ||
* object or leaving as invalid. | ||
*/ | ||
complete(): void; | ||
/** | ||
* Edit the element with the provided value. | ||
* | ||
* @param {Object} value - The new value. | ||
*/ | ||
edit(value: BSONValue): void; | ||
/** | ||
* Start the object id edit. | ||
*/ | ||
start(): void; | ||
} | ||
//# sourceMappingURL=objectid.d.ts.map |
@@ -9,6 +9,18 @@ "use strict"; | ||
const standard_1 = __importDefault(require("./standard")); | ||
/** | ||
* CRUD editor for object id values. | ||
*/ | ||
class ObjectIdEditor extends standard_1.default { | ||
/** | ||
* Create the editor with the element. | ||
* | ||
* @param {Element} element - The hadron document element. | ||
*/ | ||
constructor(element) { | ||
super(element); | ||
} | ||
/** | ||
* Complete the object id edit by converting the valid string to an object id | ||
* object or leaving as invalid. | ||
*/ | ||
complete() { | ||
@@ -20,2 +32,7 @@ super.complete(); | ||
} | ||
/** | ||
* Edit the element with the provided value. | ||
* | ||
* @param {Object} value - The new value. | ||
*/ | ||
edit(value) { | ||
@@ -32,2 +49,5 @@ try { | ||
} | ||
/** | ||
* Start the object id edit. | ||
*/ | ||
start() { | ||
@@ -34,0 +54,0 @@ super.start(); |
import type { TypeCastTypes } from 'hadron-type-checker'; | ||
import type { BSONValue } from '../utils'; | ||
import type Element from '../element'; | ||
/** | ||
* CRUD editor for standard values. | ||
*/ | ||
export default class StandardEditor { | ||
@@ -8,6 +11,35 @@ element: Element; | ||
editing: boolean; | ||
/** | ||
* Create the editor with the element. | ||
* | ||
* @param {Element} element - The hadron document element. | ||
*/ | ||
constructor(element: Element); | ||
/** | ||
* Edit the element with the provided value. | ||
* | ||
* @param {Object} value - The new value. | ||
*/ | ||
edit(value: BSONValue): void; | ||
/** | ||
* Edit the element via a paste. | ||
* | ||
* @param {String} value - The value. | ||
*/ | ||
paste(value: string): void; | ||
/** | ||
* Get the number of characters the value should display. | ||
* | ||
* @param {Boolean} editMode - If the element is being edited. | ||
* | ||
* @returns {Number} The number of characters. | ||
*/ | ||
size(): number; | ||
/** | ||
* Get the value being edited. Always returns a string because this value will | ||
* always be used by browser input elements that operate on nothing but | ||
* strings | ||
* | ||
* @returns {string} The value. | ||
*/ | ||
value(): string; | ||
@@ -14,0 +46,0 @@ start(): void; |
@@ -9,4 +9,15 @@ "use strict"; | ||
const utils_1 = require("../utils"); | ||
/** | ||
* Regex to match an array or object string. | ||
*/ | ||
const ARRAY_OR_OBJECT = /^(\[|\{)(.+)(\]|\})$/; | ||
/** | ||
* CRUD editor for standard values. | ||
*/ | ||
class StandardEditor { | ||
/** | ||
* Create the editor with the element. | ||
* | ||
* @param {Element} element - The hadron document element. | ||
*/ | ||
constructor(element) { | ||
@@ -17,2 +28,7 @@ this.element = element; | ||
} | ||
/** | ||
* Edit the element with the provided value. | ||
* | ||
* @param {Object} value - The new value. | ||
*/ | ||
edit(value) { | ||
@@ -28,2 +44,7 @@ const currentType = this.element.currentType; | ||
} | ||
/** | ||
* Edit the element via a paste. | ||
* | ||
* @param {String} value - The value. | ||
*/ | ||
paste(value) { | ||
@@ -38,8 +59,23 @@ if (ARRAY_OR_OBJECT.exec(value)) { | ||
} | ||
/** | ||
* Get the number of characters the value should display. | ||
* | ||
* @param {Boolean} editMode - If the element is being edited. | ||
* | ||
* @returns {Number} The number of characters. | ||
*/ | ||
size() { | ||
return (0, utils_1.fieldStringLen)(this.element.currentValue); | ||
} | ||
/** | ||
* Get the value being edited. Always returns a string because this value will | ||
* always be used by browser input elements that operate on nothing but | ||
* strings | ||
* | ||
* @returns {string} The value. | ||
*/ | ||
value() { | ||
return String(this.element.currentValue); | ||
} | ||
// Standard editing requires no special start/complete behaviour. | ||
start() { | ||
@@ -46,0 +82,0 @@ this.editing = true; |
import StandardEditor from './standard'; | ||
import type Element from '../element'; | ||
export declare const STRING_TYPE = "String"; | ||
/** | ||
* CRUD editor for string values. | ||
*/ | ||
export default class StringEditor extends StandardEditor { | ||
/** | ||
* Create the editor with the element. | ||
* | ||
* @param {Element} element - The hadron document element. | ||
*/ | ||
constructor(element: Element); | ||
} | ||
//# sourceMappingURL=string.d.ts.map |
@@ -9,3 +9,11 @@ "use strict"; | ||
exports.STRING_TYPE = 'String'; | ||
/** | ||
* CRUD editor for string values. | ||
*/ | ||
class StringEditor extends standard_1.default { | ||
/** | ||
* Create the editor with the element. | ||
* | ||
* @param {Element} element - The hadron document element. | ||
*/ | ||
constructor(element) { | ||
@@ -12,0 +20,0 @@ super(element); |
import StandardEditor from './standard'; | ||
import type Element from '../element'; | ||
/** | ||
* CRUD editor for undefined values. | ||
*/ | ||
export default class UndefinedEditor extends StandardEditor { | ||
/** | ||
* Create the editor with the element. | ||
* | ||
* @param {Element} element - The hadron document element. | ||
*/ | ||
constructor(element: Element); | ||
/** | ||
* Get the number of characters the value should display. | ||
* | ||
* @param {Boolean} editMode - If the element is being edited. | ||
* | ||
* @returns {Number} The number of characters. | ||
*/ | ||
size(): number; | ||
/** | ||
* Get the value being edited. | ||
* | ||
* @returns {Object} The value. | ||
*/ | ||
value(): 'undefined'; | ||
} | ||
//# sourceMappingURL=undefined.d.ts.map |
@@ -7,10 +7,33 @@ "use strict"; | ||
const standard_1 = __importDefault(require("./standard")); | ||
/** | ||
* Undefined is always 'undefined' | ||
*/ | ||
const VALUE = 'undefined'; | ||
/** | ||
* CRUD editor for undefined values. | ||
*/ | ||
class UndefinedEditor extends standard_1.default { | ||
/** | ||
* Create the editor with the element. | ||
* | ||
* @param {Element} element - The hadron document element. | ||
*/ | ||
constructor(element) { | ||
super(element); | ||
} | ||
/** | ||
* Get the number of characters the value should display. | ||
* | ||
* @param {Boolean} editMode - If the element is being edited. | ||
* | ||
* @returns {Number} The number of characters. | ||
*/ | ||
size() { | ||
return VALUE.length; | ||
} | ||
/** | ||
* Get the value being edited. | ||
* | ||
* @returns {Object} The value. | ||
*/ | ||
value() { | ||
@@ -17,0 +40,0 @@ return VALUE; |
@@ -0,1 +1,4 @@ | ||
/** | ||
* The event constant. | ||
*/ | ||
declare const _default: { | ||
@@ -9,4 +12,7 @@ readonly Added: "Element::Added"; | ||
readonly Valid: "Element::Valid"; | ||
readonly Expanded: "Element::Expanded"; | ||
readonly Collapsed: "Element::Collapsed"; | ||
readonly VisibleElementsChanged: "Element::VisibleElementsChanged"; | ||
}; | ||
export default _default; | ||
//# sourceMappingURL=element-events.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
* The event constant. | ||
*/ | ||
exports.default = { | ||
@@ -11,3 +14,6 @@ Added: 'Element::Added', | ||
Valid: 'Element::Valid', | ||
Expanded: 'Element::Expanded', | ||
Collapsed: 'Element::Collapsed', | ||
VisibleElementsChanged: 'Element::VisibleElementsChanged', | ||
}; | ||
//# sourceMappingURL=element-events.js.map |
@@ -10,3 +10,12 @@ import EventEmitter from 'eventemitter3'; | ||
export { Events }; | ||
/** | ||
* Is this the path to a field that is used internally by the | ||
* MongoDB driver or server and not for user consumption? | ||
*/ | ||
export declare function isInternalFieldPath(path: string | number): boolean; | ||
export declare const DEFAULT_VISIBLE_ELEMENTS = 25; | ||
export declare function isValueExpandable(value: BSONValue): value is BSONObject | BSONArray; | ||
/** | ||
* Represents an element in a document. | ||
*/ | ||
export declare class Element extends EventEmitter { | ||
@@ -29,3 +38,16 @@ uuid: string; | ||
decrypted: boolean; | ||
expanded: boolean; | ||
maxVisibleElementsCount: number; | ||
/** | ||
* Cancel any modifications to the element. | ||
*/ | ||
cancel(): void; | ||
/** | ||
* Create the element. | ||
* | ||
* @param key - The key. | ||
* @param value - The value. | ||
* @param parent - The parent element. | ||
* @param added - Is the element a new 'addition'? | ||
*/ | ||
constructor(key: string | number, value: BSONValue | number, parent?: Element | Document | null, added?: boolean); | ||
@@ -35,51 +57,315 @@ get nextElement(): Element | undefined; | ||
_getLevel(): number; | ||
/** | ||
* Edit the element. | ||
* | ||
* @param value - The new value. | ||
*/ | ||
edit(value: BSONValue): void; | ||
changeType(newType: TypeCastTypes): void; | ||
getRoot(): Document; | ||
/** | ||
* Get an element by its key. | ||
* | ||
* @param key - The key name. | ||
* | ||
* @returns The element. | ||
*/ | ||
get(key: string | number): Element | undefined; | ||
/** | ||
* Get an element by its index. | ||
* | ||
* @param i - The index. | ||
* | ||
* @returns The element. | ||
*/ | ||
at(i: number): Element | undefined; | ||
/** | ||
* Rename the element. Update the parent's mapping if available. | ||
* | ||
* @param key - The new key. | ||
*/ | ||
rename(key: string | number): void; | ||
/** | ||
* Generate the javascript object for this element. | ||
* | ||
* @returns The javascript object. | ||
*/ | ||
generateObject(options?: ObjectGeneratorOptions): BSONValue; | ||
/** | ||
* Generate the javascript object representing the original values | ||
* for this element (pre-element removal, renaming, editing). | ||
* | ||
* @returns The javascript object. | ||
*/ | ||
generateOriginalObject(options?: ObjectGeneratorOptions): BSONValue; | ||
/** | ||
* Insert an element after the provided element. If this element is an array, | ||
* then ignore the key specified by the caller and use the correct index. | ||
* Update the keys of the rest of the elements in the LinkedList. | ||
* | ||
* @param element - The element to insert after. | ||
* @param key - The key. | ||
* @param value - The value. | ||
* | ||
* @returns The new element. | ||
*/ | ||
insertAfter(element: Element, key: string | number, value: BSONValue): Element; | ||
/** | ||
* Add a new element to this element. | ||
* | ||
* @param| Number} key - The element key. | ||
* @param value - The value. | ||
* | ||
* @returns The new element. | ||
*/ | ||
insertEnd(key: string | number, value: BSONValue): Element; | ||
/** | ||
* Insert a placeholder element at the end of the element. | ||
* | ||
* @returns The placeholder element. | ||
*/ | ||
insertPlaceholder(): Element; | ||
insertSiblingPlaceholder(): Element; | ||
/** | ||
* Is the element a newly added element | ||
* | ||
* @returns If the element is newly added. | ||
*/ | ||
isAdded(): boolean; | ||
/** | ||
* Is the element blank? | ||
* | ||
* @returns If the element is blank. | ||
*/ | ||
isBlank(): boolean; | ||
/** | ||
* Does the element have a valid value for the current type? | ||
* | ||
* @returns If the value is valid. | ||
*/ | ||
isCurrentTypeValid(): boolean; | ||
/** | ||
* Set the element as valid. | ||
*/ | ||
setValid(): void; | ||
/** | ||
* Set the element as invalid. | ||
* | ||
* @param value - The value. | ||
* @param newType - The new type. | ||
* @param message - The error message. | ||
*/ | ||
setInvalid(value: BSONValue, newType: TypeCastTypes, message: string): void; | ||
/** | ||
* Determine if the key is a duplicate. | ||
* | ||
* @param value - The value to check. | ||
* | ||
* @returns If the key is a duplicate. | ||
*/ | ||
isDuplicateKey(value: string | number): boolean; | ||
/** | ||
* Determine if the element is edited - returns true if | ||
* the key or value changed. Does not count array values whose keys have | ||
* changed as edited. | ||
* | ||
* @returns If the element is edited. | ||
*/ | ||
isEdited(): boolean; | ||
/** | ||
* Check for value equality. | ||
* @returns If the value is equal. | ||
*/ | ||
_valuesEqual(): boolean; | ||
_isObjectIdEqual(): boolean; | ||
_isExpandable(): boolean; | ||
/** | ||
* Is the element the last in the elements. | ||
* | ||
* @returns If the element is last. | ||
*/ | ||
isLast(): boolean; | ||
/** | ||
* Determine if the element was explicitly renamed by the user. | ||
* | ||
* @returns If the element was explicitly renamed by the user. | ||
*/ | ||
isRenamed(): boolean; | ||
/** | ||
* Determine if the element was renamed, potentially as part | ||
* of moving array elements. | ||
* | ||
* @returns If the element was renamed, explicitly or implicitly. | ||
*/ | ||
hasChangedKey(): boolean; | ||
/** | ||
* Can changes to the element be reverted? | ||
* | ||
* @returns If the element can be reverted. | ||
*/ | ||
isRevertable(): boolean; | ||
/** | ||
* Can the element be removed? | ||
* | ||
* @returns If the element can be removed. | ||
*/ | ||
isRemovable(): boolean; | ||
/** | ||
* Can no action be taken on the element | ||
* | ||
* @returns If no action can be taken. | ||
*/ | ||
isNotActionable(): boolean; | ||
/** | ||
* Determine if the value has been decrypted via CSFLE. | ||
* | ||
* Warning: This does *not* apply to the children of decrypted elements! | ||
* This only returns true for the exact field that was decrypted. | ||
* | ||
* a: Object | ||
* \-- b: Object <- decrypted | ||
* \-- c: number | ||
* | ||
* a.isValueDecrypted() === false | ||
* a.get('b')?.isValueDecrypted() === true | ||
* a.get('b')?.get('c')?.isValueDecrypted() === true | ||
* | ||
* @returns If the value was encrypted on the server and is now decrypted. | ||
*/ | ||
isValueDecrypted(): boolean; | ||
/** | ||
* Detemine if this value or any of its children were marked | ||
* as having been decrypted with CSFLE. | ||
* | ||
* Warning: This does *not* apply to the children of decrypted elements! | ||
* This only returns true for the exact field that was decrypted | ||
* and its parents. | ||
* | ||
* a: Object | ||
* \-- b: Object <- decrypted | ||
* \-- c: number | ||
* | ||
* a.containsDecryptedChildren() === true | ||
* a.get('b')?.containsDecryptedChildren() === true | ||
* a.get('b')?.get('c')?.containsDecryptedChildren() === false | ||
* | ||
* @returns If any child of this element has been decrypted directly. | ||
*/ | ||
containsDecryptedChildren(): boolean; | ||
/** | ||
* Determine if the value is editable. | ||
* | ||
* @returns If the value is editable. | ||
*/ | ||
isValueEditable(): boolean; | ||
/** | ||
* Determine if the key of the parent element is editable. | ||
* | ||
* @returns If the parent's key is editable. | ||
*/ | ||
isParentEditable(): boolean; | ||
_isKeyLegallyEditable(): boolean; | ||
/** | ||
* Determine if the key is editable. | ||
* | ||
* @returns If the key is editable. | ||
*/ | ||
isKeyEditable(): boolean; | ||
/** | ||
* Is this a field that is used internally by the MongoDB driver or server | ||
* and not for user consumption? | ||
* | ||
* @returns | ||
*/ | ||
isInternalField(): boolean; | ||
/** | ||
* Determine if the element is modified at all. | ||
* | ||
* @returns If the element is modified. | ||
*/ | ||
isModified(): boolean; | ||
/** | ||
* Is the element flagged for removal? | ||
* | ||
* @returns If the element is flagged for removal. | ||
*/ | ||
isRemoved(): boolean; | ||
/** | ||
* Are any immediate children of this element flagged for removal? | ||
* | ||
* @returns If any immediate children of this element are flagged for removal. | ||
*/ | ||
hasAnyRemovedChild(): boolean; | ||
/** | ||
* Elements themselves are not the root. | ||
* | ||
* @returns Always false. | ||
*/ | ||
isRoot(): false; | ||
/** | ||
* Flag the element for removal. | ||
*/ | ||
remove(): void; | ||
/** | ||
* Revert the changes to the element. | ||
*/ | ||
revert(): void; | ||
/** | ||
* Expands the target element and optionally its children as well. | ||
* Document.expand is when we would want to expand the children otherwise we | ||
* will most expand the element itself. | ||
*/ | ||
expand(expandChildren?: boolean): void; | ||
/** | ||
* Collapses only the target element | ||
*/ | ||
collapse(): void; | ||
/** | ||
* Fire and bubble up the event. | ||
* | ||
* @param evt - The event. | ||
* @paramdata - Optional. | ||
*/ | ||
_bubbleUp(evt: typeof Events[keyof typeof Events], ...data: BSONArray): void; | ||
/** | ||
* Convert this element to an empty object. | ||
*/ | ||
_convertToEmptyObject(): void; | ||
/** | ||
* Convert to an empty array. | ||
*/ | ||
_convertToEmptyArray(): void; | ||
/** | ||
* Is the element empty? | ||
* | ||
* @param element - The element to check. | ||
* | ||
* @returns If the element is empty. | ||
*/ | ||
_isElementEmpty(element: Element | undefined | null): boolean; | ||
_isExpandable(value: BSONValue): value is BSONObject | BSONArray; | ||
/** | ||
* Generates a sequence of child elements. | ||
* | ||
* @param object - The object to generate from. | ||
* | ||
* @returns The elements. | ||
*/ | ||
_generateElements(object: BSONObject | BSONArray): ElementList; | ||
/** | ||
* Removes the added elements from the element. | ||
*/ | ||
_removeAddedElements(): void; | ||
getVisibleElements(): Element[]; | ||
setMaxVisibleElementsCount(newCount: number): void; | ||
getTotalVisibleElementsCount(): number; | ||
private emitVisibleElementsChanged; | ||
/** | ||
* @deprecated Use ElementEvents import instead | ||
*/ | ||
static get Events(): typeof ElementEvents; | ||
} | ||
/** | ||
* Represents a doubly linked list. | ||
*/ | ||
export declare class ElementList implements Iterable<Element> { | ||
@@ -93,8 +379,55 @@ private parent; | ||
get(key: string | number): Element | undefined; | ||
some(predicate: (value: Element, index: number, array: Element[]) => unknown): boolean; | ||
every(predicate: (value: Element, index: number, array: Element[]) => unknown): boolean; | ||
get firstElement(): Element | undefined; | ||
get lastElement(): Element | undefined; | ||
/** | ||
* Insert data after the provided element. | ||
* | ||
* @param afterElement - The element to insert after. | ||
* @param key - The element key. | ||
* @param value - The element value. | ||
* @param added - If the element is new. | ||
* | ||
* @returns The inserted element. | ||
*/ | ||
insertAfter(afterElement: Element, key: string | number, value: BSONValue, added?: boolean): Element | undefined; | ||
/** | ||
* Insert data before the provided element. | ||
* | ||
* @param beforeElement - The element to insert before. | ||
* @param key - The element key. | ||
* @param value - The element value. | ||
* @param added - If the element is new. | ||
* | ||
* @returns The inserted element. | ||
*/ | ||
insertBefore(beforeElement: Element, key: string | number, value: BSONValue, added?: boolean): Element | undefined; | ||
/** | ||
* Insert data at the beginning of the list. | ||
* | ||
* @param key - The element key. | ||
* @param value - The element value. | ||
* @param added - If the element is new. | ||
* | ||
* @returns The data element. | ||
*/ | ||
insertBeginning(key: string | number, value: BSONValue, added?: boolean): Element; | ||
/** | ||
* Insert data at the end of the list. | ||
* | ||
* @param key - The element key. | ||
* @param value - The element value. | ||
* @param added - If the element is new. | ||
* | ||
* @returns The data element. | ||
*/ | ||
insertEnd(key: string | number, value: BSONValue, added?: boolean): Element; | ||
/** | ||
* Remove the element from the list. | ||
* | ||
* @param removeElement - The element to remove. | ||
* | ||
* @returns The list with the element removed. | ||
*/ | ||
remove(removeElement: Element): this; | ||
@@ -101,0 +434,0 @@ findNext(el: Element): Element | undefined; |
@@ -6,8 +6,5 @@ 'use strict'; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.ElementList = exports.Element = exports.isInternalFieldPath = exports.Events = exports.DATE_FORMAT = void 0; | ||
exports.ElementList = exports.Element = exports.isValueExpandable = exports.DEFAULT_VISIBLE_ELEMENTS = exports.isInternalFieldPath = exports.Events = exports.DATE_FORMAT = void 0; | ||
const eventemitter3_1 = __importDefault(require("eventemitter3")); | ||
const lodash_isplainobject_1 = __importDefault(require("lodash.isplainobject")); | ||
const lodash_isarray_1 = __importDefault(require("lodash.isarray")); | ||
const lodash_isequal_1 = __importDefault(require("lodash.isequal")); | ||
const lodash_isstring_1 = __importDefault(require("lodash.isstring")); | ||
const lodash_1 = require("lodash"); | ||
const object_generator_1 = __importDefault(require("./object-generator")); | ||
@@ -19,5 +16,13 @@ const hadron_type_checker_1 = __importDefault(require("hadron-type-checker")); | ||
exports.Events = element_events_1.default; | ||
const utils_1 = require("./utils"); | ||
const _1 = require("."); | ||
exports.DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss.SSS'; | ||
/** | ||
* Id field constant. | ||
*/ | ||
const ID = '_id'; | ||
/** | ||
* Is this the path to a field that is used internally by the | ||
* MongoDB driver or server and not for user consumption? | ||
*/ | ||
function isInternalFieldPath(path) { | ||
@@ -27,2 +32,5 @@ return typeof path === 'string' && /^__safeContent__($|\.)/.test(path); | ||
exports.isInternalFieldPath = isInternalFieldPath; | ||
/** | ||
* Types that are not editable. | ||
*/ | ||
const UNEDITABLE_TYPES = [ | ||
@@ -39,5 +47,38 @@ 'Binary', | ||
]; | ||
exports.DEFAULT_VISIBLE_ELEMENTS = 25; | ||
function isValueExpandable(value) { | ||
return (0, lodash_1.isPlainObject)(value) || (0, lodash_1.isArray)(value); | ||
} | ||
exports.isValueExpandable = isValueExpandable; | ||
/** | ||
* Represents an element in a document. | ||
*/ | ||
class Element extends eventemitter3_1.default { | ||
/** | ||
* Cancel any modifications to the element. | ||
*/ | ||
cancel() { | ||
if (this.elements) { | ||
// Cancel will remove elements from iterator, clone it before iterating | ||
// otherwise we will skip items | ||
for (const element of Array.from(this.elements)) { | ||
element.cancel(); | ||
} | ||
} | ||
if (this.isModified()) { | ||
this.revert(); | ||
} | ||
} | ||
/** | ||
* Create the element. | ||
* | ||
* @param key - The key. | ||
* @param value - The value. | ||
* @param parent - The parent element. | ||
* @param added - Is the element a new 'addition'? | ||
*/ | ||
constructor(key, value, parent = null, added = false) { | ||
super(); | ||
this.expanded = false; | ||
this.maxVisibleElementsCount = exports.DEFAULT_VISIBLE_ELEMENTS; | ||
this.uuid = new bson_1.UUID().toHexString(); | ||
@@ -53,6 +94,10 @@ this.key = key; | ||
this.setValid(); | ||
// Make sure that all values that element will hold onto will be explicit | ||
// bson types: convert JavaScript numbers to either Int32 or Double | ||
if (typeof value === 'number') { | ||
value = hadron_type_checker_1.default.cast(value, hadron_type_checker_1.default.type(value)); | ||
} | ||
if (this._isExpandable(value)) { | ||
if (isValueExpandable(value)) { | ||
// NB: Important to set `originalExpandableValue` first as element | ||
// generation will depend on it | ||
this.originalExpandableValue = value; | ||
@@ -65,2 +110,5 @@ this.elements = this._generateElements(value); | ||
} | ||
// The AutoEncrypter marks decrypted entries in a document | ||
// for us with a special symbol. We opt into this behavior | ||
// in the devtools-connect package. | ||
let parentValue; | ||
@@ -77,19 +125,7 @@ if (this.parent) { | ||
} | ||
cancel() { | ||
if (this.elements) { | ||
for (const element of Array.from(this.elements)) { | ||
element.cancel(); | ||
} | ||
} | ||
if (this.isModified()) { | ||
this.revert(); | ||
} | ||
} | ||
get nextElement() { | ||
var _a, _b; | ||
return (_b = (_a = this.parent) === null || _a === void 0 ? void 0 : _a.elements) === null || _b === void 0 ? void 0 : _b.findNext(this); | ||
return this.parent?.elements?.findNext(this); | ||
} | ||
get previousElement() { | ||
var _a, _b; | ||
return (_b = (_a = this.parent) === null || _a === void 0 ? void 0 : _a.elements) === null || _b === void 0 ? void 0 : _b.findPrevious(this); | ||
return this.parent?.elements?.findPrevious(this); | ||
} | ||
@@ -105,9 +141,14 @@ _getLevel() { | ||
} | ||
/** | ||
* Edit the element. | ||
* | ||
* @param value - The new value. | ||
*/ | ||
edit(value) { | ||
this.currentType = hadron_type_checker_1.default.type(value); | ||
if (this._isExpandable(value) && !this._isExpandable(this.currentValue)) { | ||
if (isValueExpandable(value) && !isValueExpandable(this.currentValue)) { | ||
this.currentValue = null; | ||
this.elements = this._generateElements(value); | ||
} | ||
else if (!this._isExpandable(value) && this.elements) { | ||
else if (!isValueExpandable(value) && this.elements) { | ||
this.currentValue = value; | ||
@@ -147,3 +188,3 @@ this.elements = undefined; | ||
let parent = this.parent; | ||
while (parent === null || parent === void 0 ? void 0 : parent.parent) { | ||
while (parent?.parent) { | ||
parent = parent.parent; | ||
@@ -153,10 +194,27 @@ } | ||
} | ||
/** | ||
* Get an element by its key. | ||
* | ||
* @param key - The key name. | ||
* | ||
* @returns The element. | ||
*/ | ||
get(key) { | ||
var _a; | ||
return (_a = this.elements) === null || _a === void 0 ? void 0 : _a.get(key); | ||
return this.elements?.get(key); | ||
} | ||
/** | ||
* Get an element by its index. | ||
* | ||
* @param i - The index. | ||
* | ||
* @returns The element. | ||
*/ | ||
at(i) { | ||
var _a; | ||
return (_a = this.elements) === null || _a === void 0 ? void 0 : _a.at(i); | ||
return this.elements?.at(i); | ||
} | ||
/** | ||
* Rename the element. Update the parent's mapping if available. | ||
* | ||
* @param key - The new key. | ||
*/ | ||
rename(key) { | ||
@@ -166,2 +224,7 @@ this.currentKey = key; | ||
} | ||
/** | ||
* Generate the javascript object for this element. | ||
* | ||
* @returns The javascript object. | ||
*/ | ||
generateObject(options) { | ||
@@ -176,2 +239,8 @@ if (this.currentType === 'Array') { | ||
} | ||
/** | ||
* Generate the javascript object representing the original values | ||
* for this element (pre-element removal, renaming, editing). | ||
* | ||
* @returns The javascript object. | ||
*/ | ||
generateOriginalObject(options) { | ||
@@ -188,2 +257,13 @@ if (this.type === 'Array') { | ||
} | ||
/** | ||
* Insert an element after the provided element. If this element is an array, | ||
* then ignore the key specified by the caller and use the correct index. | ||
* Update the keys of the rest of the elements in the LinkedList. | ||
* | ||
* @param element - The element to insert after. | ||
* @param key - The key. | ||
* @param value - The value. | ||
* | ||
* @returns The new element. | ||
*/ | ||
insertAfter(element, key, value) { | ||
@@ -195,4 +275,13 @@ if (!this.elements) { | ||
newElement._bubbleUp(element_events_1.default.Added, newElement, this); | ||
this.emitVisibleElementsChanged(); | ||
return newElement; | ||
} | ||
/** | ||
* Add a new element to this element. | ||
* | ||
* @param| Number} key - The element key. | ||
* @param value - The value. | ||
* | ||
* @returns The new element. | ||
*/ | ||
insertEnd(key, value) { | ||
@@ -204,20 +293,54 @@ if (!this.elements) { | ||
this._bubbleUp(element_events_1.default.Added, newElement); | ||
this.emitVisibleElementsChanged(); | ||
return newElement; | ||
} | ||
/** | ||
* Insert a placeholder element at the end of the element. | ||
* | ||
* @returns The placeholder element. | ||
*/ | ||
insertPlaceholder() { | ||
return this.insertEnd('', ''); | ||
// When adding a placeholder value to an array we default to the type | ||
// of the last value currently in the array. Otherwise empty string. | ||
const placeholderValue = this.currentType === 'Array' && this.elements?.lastElement | ||
? (0, utils_1.getDefaultValueForType)(this.elements?.lastElement.currentType) | ||
: ''; | ||
return this.insertEnd('', placeholderValue); | ||
} | ||
insertSiblingPlaceholder() { | ||
return this.parent.insertAfter(this, '', ''); | ||
// When adding a sibling placeholder value to an array we default the | ||
// new values' type to the preceding element's type to hopefully make | ||
// it so folks don't have to change the type later. Otherwise empty string. | ||
const placeholderValue = this.parent?.currentType === 'Array' | ||
? (0, utils_1.getDefaultValueForType)(this.currentType) | ||
: ''; | ||
return this.parent.insertAfter(this, '', placeholderValue); | ||
} | ||
/** | ||
* Is the element a newly added element | ||
* | ||
* @returns If the element is newly added. | ||
*/ | ||
isAdded() { | ||
var _a; | ||
return this.added || !!((_a = this.parent) === null || _a === void 0 ? void 0 : _a.isAdded()); | ||
return this.added || !!this.parent?.isAdded(); | ||
} | ||
/** | ||
* Is the element blank? | ||
* | ||
* @returns If the element is blank. | ||
*/ | ||
isBlank() { | ||
return this.currentKey === '' && this.currentValue === ''; | ||
} | ||
/** | ||
* Does the element have a valid value for the current type? | ||
* | ||
* @returns If the value is valid. | ||
*/ | ||
isCurrentTypeValid() { | ||
return !!this.currentTypeValid; | ||
} | ||
/** | ||
* Set the element as valid. | ||
*/ | ||
setValid() { | ||
@@ -228,2 +351,9 @@ this.currentTypeValid = true; | ||
} | ||
/** | ||
* Set the element as invalid. | ||
* | ||
* @param value - The value. | ||
* @param newType - The new type. | ||
* @param message - The error message. | ||
*/ | ||
setInvalid(value, newType, message) { | ||
@@ -236,8 +366,14 @@ this.currentValue = value; | ||
} | ||
/** | ||
* Determine if the key is a duplicate. | ||
* | ||
* @param value - The value to check. | ||
* | ||
* @returns If the key is a duplicate. | ||
*/ | ||
isDuplicateKey(value) { | ||
var _a, _b; | ||
if (value === '') { | ||
return false; | ||
} | ||
for (const element of (_b = (_a = this.parent) === null || _a === void 0 ? void 0 : _a.elements) !== null && _b !== void 0 ? _b : []) { | ||
for (const element of this.parent?.elements ?? []) { | ||
if (element.uuid !== this.uuid && element.currentKey === value) { | ||
@@ -249,2 +385,9 @@ return true; | ||
} | ||
/** | ||
* Determine if the element is edited - returns true if | ||
* the key or value changed. Does not count array values whose keys have | ||
* changed as edited. | ||
* | ||
* @returns If the element is edited. | ||
*/ | ||
isEdited() { | ||
@@ -256,10 +399,15 @@ return ((this.isRenamed() || | ||
} | ||
/** | ||
* Check for value equality. | ||
* @returns If the value is equal. | ||
*/ | ||
_valuesEqual() { | ||
if (this.currentType === 'Date' && (0, lodash_isstring_1.default)(this.currentValue)) { | ||
return (0, lodash_isequal_1.default)(this.value, new Date(this.currentValue)); | ||
if (this.currentType === 'Date' && (0, lodash_1.isString)(this.currentValue)) { | ||
return (0, lodash_1.isEqual)(this.value, new Date(this.currentValue)); | ||
} | ||
else if (this.currentType === 'ObjectId' && (0, lodash_isstring_1.default)(this.currentValue)) { | ||
else if (this.currentType === 'ObjectId' && (0, lodash_1.isString)(this.currentValue)) { | ||
return this._isObjectIdEqual(); | ||
} | ||
return (0, lodash_isequal_1.default)(this.value, this.currentValue); | ||
return (0, lodash_1.isEqual)(this.value, this.currentValue); | ||
} | ||
@@ -274,6 +422,18 @@ _isObjectIdEqual() { | ||
} | ||
_isExpandable() { | ||
return this.currentType === 'Array' || this.currentType === 'Object'; | ||
} | ||
/** | ||
* Is the element the last in the elements. | ||
* | ||
* @returns If the element is last. | ||
*/ | ||
isLast() { | ||
var _a, _b; | ||
return ((_b = (_a = this.parent) === null || _a === void 0 ? void 0 : _a.elements) === null || _b === void 0 ? void 0 : _b.lastElement) === this; | ||
return this.parent?.elements?.lastElement === this; | ||
} | ||
/** | ||
* Determine if the element was explicitly renamed by the user. | ||
* | ||
* @returns If the element was explicitly renamed by the user. | ||
*/ | ||
isRenamed() { | ||
@@ -287,11 +447,32 @@ if (!this.parent || | ||
} | ||
/** | ||
* Determine if the element was renamed, potentially as part | ||
* of moving array elements. | ||
* | ||
* @returns If the element was renamed, explicitly or implicitly. | ||
*/ | ||
hasChangedKey() { | ||
return this.key !== this.currentKey; | ||
} | ||
/** | ||
* Can changes to the element be reverted? | ||
* | ||
* @returns If the element can be reverted. | ||
*/ | ||
isRevertable() { | ||
return this.isEdited() || this.isRemoved(); | ||
} | ||
/** | ||
* Can the element be removed? | ||
* | ||
* @returns If the element can be removed. | ||
*/ | ||
isRemovable() { | ||
return !this.parent.isRemoved(); | ||
} | ||
/** | ||
* Can no action be taken on the element | ||
* | ||
* @returns If no action can be taken. | ||
*/ | ||
isNotActionable() { | ||
@@ -301,5 +482,39 @@ return (((this.key === ID || this.isInternalField()) && !this.isAdded()) || | ||
} | ||
/** | ||
* Determine if the value has been decrypted via CSFLE. | ||
* | ||
* Warning: This does *not* apply to the children of decrypted elements! | ||
* This only returns true for the exact field that was decrypted. | ||
* | ||
* a: Object | ||
* \-- b: Object <- decrypted | ||
* \-- c: number | ||
* | ||
* a.isValueDecrypted() === false | ||
* a.get('b')?.isValueDecrypted() === true | ||
* a.get('b')?.get('c')?.isValueDecrypted() === true | ||
* | ||
* @returns If the value was encrypted on the server and is now decrypted. | ||
*/ | ||
isValueDecrypted() { | ||
return this.decrypted; | ||
} | ||
/** | ||
* Detemine if this value or any of its children were marked | ||
* as having been decrypted with CSFLE. | ||
* | ||
* Warning: This does *not* apply to the children of decrypted elements! | ||
* This only returns true for the exact field that was decrypted | ||
* and its parents. | ||
* | ||
* a: Object | ||
* \-- b: Object <- decrypted | ||
* \-- c: number | ||
* | ||
* a.containsDecryptedChildren() === true | ||
* a.get('b')?.containsDecryptedChildren() === true | ||
* a.get('b')?.get('c')?.containsDecryptedChildren() === false | ||
* | ||
* @returns If any child of this element has been decrypted directly. | ||
*/ | ||
containsDecryptedChildren() { | ||
@@ -316,2 +531,7 @@ if (this.isValueDecrypted()) { | ||
} | ||
/** | ||
* Determine if the value is editable. | ||
* | ||
* @returns If the value is editable. | ||
*/ | ||
isValueEditable() { | ||
@@ -321,2 +541,7 @@ return (this._isKeyLegallyEditable() && | ||
} | ||
/** | ||
* Determine if the key of the parent element is editable. | ||
* | ||
* @returns If the parent's key is editable. | ||
*/ | ||
isParentEditable() { | ||
@@ -330,7 +555,20 @@ if (this.parent && !this.parent.isRoot()) { | ||
return (this.isParentEditable() && | ||
(this.isAdded() || (this.currentKey !== ID && !this.isInternalField()))); | ||
(this.isAdded() || | ||
((this.currentKey !== ID || !this.parent?.isRoot()) && | ||
!this.isInternalField()))); | ||
} | ||
/** | ||
* Determine if the key is editable. | ||
* | ||
* @returns If the key is editable. | ||
*/ | ||
isKeyEditable() { | ||
return this._isKeyLegallyEditable() && !this.containsDecryptedChildren(); | ||
} | ||
/** | ||
* Is this a field that is used internally by the MongoDB driver or server | ||
* and not for user consumption? | ||
* | ||
* @returns | ||
*/ | ||
isInternalField() { | ||
@@ -348,2 +586,7 @@ if (!this.parent) { | ||
} | ||
/** | ||
* Determine if the element is modified at all. | ||
* | ||
* @returns If the element is modified. | ||
*/ | ||
isModified() { | ||
@@ -359,5 +602,15 @@ if (this.elements) { | ||
} | ||
/** | ||
* Is the element flagged for removal? | ||
* | ||
* @returns If the element is flagged for removal. | ||
*/ | ||
isRemoved() { | ||
return this.removed; | ||
} | ||
/** | ||
* Are any immediate children of this element flagged for removal? | ||
* | ||
* @returns If any immediate children of this element are flagged for removal. | ||
*/ | ||
hasAnyRemovedChild() { | ||
@@ -373,5 +626,13 @@ if (this.elements) { | ||
} | ||
/** | ||
* Elements themselves are not the root. | ||
* | ||
* @returns Always false. | ||
*/ | ||
isRoot() { | ||
return false; | ||
} | ||
/** | ||
* Flag the element for removal. | ||
*/ | ||
remove() { | ||
@@ -382,8 +643,15 @@ this.revert(); | ||
this._bubbleUp(element_events_1.default.Removed, this, this.parent); | ||
this.emitVisibleElementsChanged(this.parent); | ||
} | ||
} | ||
/** | ||
* Revert the changes to the element. | ||
*/ | ||
revert() { | ||
if (this.isAdded()) { | ||
this.parent.elements.remove(this); | ||
this.parent?.elements?.remove(this); | ||
this._bubbleUp(element_events_1.default.Removed, this, this.parent); | ||
if (this.parent) { | ||
this.emitVisibleElementsChanged(this.parent); | ||
} | ||
delete this.parent; | ||
@@ -412,2 +680,42 @@ } | ||
} | ||
/** | ||
* Expands the target element and optionally its children as well. | ||
* Document.expand is when we would want to expand the children otherwise we | ||
* will most expand the element itself. | ||
*/ | ||
expand(expandChildren = false) { | ||
if (!this._isExpandable()) { | ||
return; | ||
} | ||
this.expanded = true; | ||
if (expandChildren && this.elements) { | ||
for (const element of this.elements) { | ||
element.expand(expandChildren); | ||
} | ||
} | ||
this.emit(_1.ElementEvents.Expanded, this); | ||
this.emitVisibleElementsChanged(); | ||
} | ||
/** | ||
* Collapses only the target element | ||
*/ | ||
collapse() { | ||
if (!this._isExpandable()) { | ||
return; | ||
} | ||
this.expanded = false; | ||
if (this.elements) { | ||
for (const element of this.elements) { | ||
element.collapse(); | ||
} | ||
} | ||
this.emit(_1.ElementEvents.Collapsed, this); | ||
this.emitVisibleElementsChanged(); | ||
} | ||
/** | ||
* Fire and bubble up the event. | ||
* | ||
* @param evt - The event. | ||
* @paramdata - Optional. | ||
*/ | ||
_bubbleUp(evt, ...data) { | ||
@@ -425,2 +733,5 @@ this.emit(evt, ...data); | ||
} | ||
/** | ||
* Convert this element to an empty object. | ||
*/ | ||
_convertToEmptyObject() { | ||
@@ -430,2 +741,5 @@ this.edit({}); | ||
} | ||
/** | ||
* Convert to an empty array. | ||
*/ | ||
_convertToEmptyArray() { | ||
@@ -435,11 +749,25 @@ this.edit([]); | ||
} | ||
/** | ||
* Is the element empty? | ||
* | ||
* @param element - The element to check. | ||
* | ||
* @returns If the element is empty. | ||
*/ | ||
_isElementEmpty(element) { | ||
return !!element && element.isAdded() && element.isBlank(); | ||
} | ||
_isExpandable(value) { | ||
return (0, lodash_isplainobject_1.default)(value) || (0, lodash_isarray_1.default)(value); | ||
} | ||
/** | ||
* Generates a sequence of child elements. | ||
* | ||
* @param object - The object to generate from. | ||
* | ||
* @returns The elements. | ||
*/ | ||
_generateElements(object) { | ||
return new ElementList(this, object); | ||
} | ||
/** | ||
* Removes the added elements from the element. | ||
*/ | ||
_removeAddedElements() { | ||
@@ -454,2 +782,34 @@ if (this.elements) { | ||
} | ||
getVisibleElements() { | ||
if (!this.elements || !this.expanded) { | ||
return []; | ||
} | ||
return [...this.elements].slice(0, this.maxVisibleElementsCount); | ||
} | ||
setMaxVisibleElementsCount(newCount) { | ||
if (!this._isExpandable()) { | ||
return; | ||
} | ||
this.maxVisibleElementsCount = newCount; | ||
this.emitVisibleElementsChanged(); | ||
} | ||
getTotalVisibleElementsCount() { | ||
if (!this.elements || !this.expanded) { | ||
return 0; | ||
} | ||
return this.getVisibleElements().reduce((totalVisibleChildElements, element) => { | ||
return (totalVisibleChildElements + 1 + element.getTotalVisibleElementsCount()); | ||
}, 0); | ||
} | ||
emitVisibleElementsChanged(targetElement = this) { | ||
if (targetElement.isRoot()) { | ||
targetElement.emit(_1.DocumentEvents.VisibleElementsChanged, targetElement); | ||
} | ||
else if (targetElement.expanded) { | ||
targetElement._bubbleUp(element_events_1.default.VisibleElementsChanged, targetElement, targetElement.getRoot()); | ||
} | ||
} | ||
/** | ||
* @deprecated Use ElementEvents import instead | ||
*/ | ||
static get Events() { | ||
@@ -460,6 +820,9 @@ return _1.ElementEvents; | ||
exports.Element = Element; | ||
/** | ||
* Represents a doubly linked list. | ||
*/ | ||
class ElementList { | ||
constructor(parent, originalDoc) { | ||
this.parent = parent; | ||
this.elements = Object.entries(originalDoc !== null && originalDoc !== void 0 ? originalDoc : {}).map(([k, v]) => { | ||
this.elements = Object.entries(originalDoc ?? {}).map(([k, v]) => { | ||
return new Element(this.isArray() ? parseInt(k, 10) : k, v, parent, parent.isRoot() ? parent.cloned : false); | ||
@@ -482,2 +845,8 @@ }); | ||
} | ||
some(predicate) { | ||
return this.elements.some(predicate); | ||
} | ||
every(predicate) { | ||
return this.elements.every(predicate); | ||
} | ||
get firstElement() { | ||
@@ -489,2 +858,12 @@ return this.elements[0]; | ||
} | ||
/** | ||
* Insert data after the provided element. | ||
* | ||
* @param afterElement - The element to insert after. | ||
* @param key - The element key. | ||
* @param value - The element value. | ||
* @param added - If the element is new. | ||
* | ||
* @returns The inserted element. | ||
*/ | ||
insertAfter(afterElement, key, value, added = true) { | ||
@@ -508,2 +887,12 @@ let newElement; | ||
} | ||
/** | ||
* Insert data before the provided element. | ||
* | ||
* @param beforeElement - The element to insert before. | ||
* @param key - The element key. | ||
* @param value - The element value. | ||
* @param added - If the element is new. | ||
* | ||
* @returns The inserted element. | ||
*/ | ||
insertBefore(beforeElement, key, value, added = true) { | ||
@@ -526,2 +915,11 @@ let newElement; | ||
} | ||
/** | ||
* Insert data at the beginning of the list. | ||
* | ||
* @param key - The element key. | ||
* @param value - The element value. | ||
* @param added - If the element is new. | ||
* | ||
* @returns The data element. | ||
*/ | ||
insertBeginning(key, value, added = true) { | ||
@@ -537,2 +935,11 @@ const newElement = new Element(this.isArray() ? 0 : key, value, this.parent, added); | ||
} | ||
/** | ||
* Insert data at the end of the list. | ||
* | ||
* @param key - The element key. | ||
* @param value - The element value. | ||
* @param added - If the element is new. | ||
* | ||
* @returns The data element. | ||
*/ | ||
insertEnd(key, value, added = true) { | ||
@@ -543,2 +950,9 @@ const newElement = new Element(this.isArray() ? this.elements.length : key, value, this.parent, added); | ||
} | ||
/** | ||
* Remove the element from the list. | ||
* | ||
* @param removeElement - The element to remove. | ||
* | ||
* @returns The list with the element removed. | ||
*/ | ||
remove(removeElement) { | ||
@@ -545,0 +959,0 @@ let removeIdx = -1; |
@@ -1,8 +0,9 @@ | ||
import Document, { Events as DocumentEvents } from './document'; | ||
import Element, { Events as ElementEvents, isInternalFieldPath } from './element'; | ||
import Document, { Events as DocumentEvents, DEFAULT_VISIBLE_ELEMENTS as DEFAULT_VISIBLE_DOCUMENT_ELEMENTS } from './document'; | ||
import Element, { Events as ElementEvents, isInternalFieldPath, DEFAULT_VISIBLE_ELEMENTS } from './element'; | ||
import ElementEditor from './editor'; | ||
import type { Editor } from './editor'; | ||
import { getDefaultValueForType, objectToIdiomaticEJSON } from './utils'; | ||
export default Document; | ||
export type { Editor }; | ||
export { Document, DocumentEvents, Element, ElementEvents, ElementEditor, isInternalFieldPath, }; | ||
export { Document, DocumentEvents, DEFAULT_VISIBLE_DOCUMENT_ELEMENTS, Element, ElementEvents, DEFAULT_VISIBLE_ELEMENTS, ElementEditor, isInternalFieldPath, getDefaultValueForType, objectToIdiomaticEJSON, }; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -29,6 +29,7 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.isInternalFieldPath = exports.ElementEditor = exports.ElementEvents = exports.Element = exports.DocumentEvents = exports.Document = void 0; | ||
exports.objectToIdiomaticEJSON = exports.getDefaultValueForType = exports.isInternalFieldPath = exports.ElementEditor = exports.DEFAULT_VISIBLE_ELEMENTS = exports.ElementEvents = exports.Element = exports.DEFAULT_VISIBLE_DOCUMENT_ELEMENTS = exports.DocumentEvents = exports.Document = void 0; | ||
const document_1 = __importStar(require("./document")); | ||
exports.Document = document_1.default; | ||
Object.defineProperty(exports, "DocumentEvents", { enumerable: true, get: function () { return document_1.Events; } }); | ||
Object.defineProperty(exports, "DEFAULT_VISIBLE_DOCUMENT_ELEMENTS", { enumerable: true, get: function () { return document_1.DEFAULT_VISIBLE_ELEMENTS; } }); | ||
const element_1 = __importStar(require("./element")); | ||
@@ -38,5 +39,9 @@ exports.Element = element_1.default; | ||
Object.defineProperty(exports, "isInternalFieldPath", { enumerable: true, get: function () { return element_1.isInternalFieldPath; } }); | ||
Object.defineProperty(exports, "DEFAULT_VISIBLE_ELEMENTS", { enumerable: true, get: function () { return element_1.DEFAULT_VISIBLE_ELEMENTS; } }); | ||
const editor_1 = __importDefault(require("./editor")); | ||
exports.ElementEditor = editor_1.default; | ||
const utils_1 = require("./utils"); | ||
Object.defineProperty(exports, "getDefaultValueForType", { enumerable: true, get: function () { return utils_1.getDefaultValueForType; } }); | ||
Object.defineProperty(exports, "objectToIdiomaticEJSON", { enumerable: true, get: function () { return utils_1.objectToIdiomaticEJSON; } }); | ||
exports.default = document_1.default; | ||
//# sourceMappingURL=index.js.map |
@@ -8,14 +8,110 @@ import type { Element } from './element'; | ||
export interface KeyInclusionOptions { | ||
/** | ||
* An array whose entries are used as keys that are always included | ||
* in lists for queried keys (e.g. as part of the `find` portion of | ||
* an update). | ||
* | ||
* A nested field for `{ a: { b: 42 } }` would be described by the | ||
* field path `['a', 'b']`. | ||
*/ | ||
alwaysIncludeKeys?: string[][]; | ||
/** | ||
* An array whose entries are used as keys that are included in lists | ||
* for queried keys (e.g. as part of the `find` portion of | ||
* an update), even when the value of that field has originally been | ||
* an encrypted value in the sense of CSFLE/QE. | ||
* | ||
* A nested field for `{ a: { b: 42 } }` would be described by the | ||
* field path `['a', 'b']`. | ||
*/ | ||
includableEncryptedKeys?: string[][]; | ||
} | ||
/** | ||
* Generates javascript objects from elements. | ||
*/ | ||
export declare class ObjectGenerator { | ||
/** | ||
* Generate a javascript object from the elements. | ||
* | ||
* @param {Array} elements - The elements. | ||
* | ||
* @returns {Object} The javascript object. | ||
*/ | ||
static generate(elements: Iterable<Element>, options?: ObjectGeneratorOptions): Record<string, unknown>; | ||
/** | ||
* Generate a javascript object from the elements with their original keys | ||
* and values. This can be used in a query with an update to | ||
* ensure the values on the document to edit are still up to date. | ||
* | ||
* @param {Array} elements - The elements. | ||
* | ||
* @returns {Object} The javascript object. | ||
*/ | ||
static generateOriginal(elements: Iterable<Element>, options?: ObjectGeneratorOptions): Record<string, unknown>; | ||
/** | ||
* Generate an array from the elements. | ||
* | ||
* @param {Array} elements - The elements. | ||
* | ||
* @returns {Array} The array. | ||
*/ | ||
static generateArray(elements: Iterable<Element>, options?: ObjectGeneratorOptions): unknown[]; | ||
/** | ||
* Generate an array from the elements using their original values. | ||
* | ||
* @param {Array} elements - The elements. | ||
* | ||
* @returns {Array} The array. | ||
*/ | ||
static generateOriginalArray(elements: Iterable<Element>, options?: ObjectGeneratorOptions): unknown[]; | ||
/** | ||
* As the first step in generating query and update documents for updated | ||
* fields in a document, gather the original and new paths and values | ||
* for those updated fields. | ||
* | ||
* @param target The target document, or, when recursing, element. | ||
* @param keyInclusionOptions Specify which fields to include in the | ||
* originalFields list. | ||
* @param includeUpdatedFields Whether to include original and new values | ||
* of updated fields. If set to `false`, only fields included in | ||
* @see alwaysIncludeOriginalKeys are included. | ||
* @returns A pair `{ originalFields, newFields }`, each listing the | ||
* original and new paths and values for updated fields, respectively. | ||
*/ | ||
private static recursivelyGatherFieldsAndValuesForUpdate; | ||
private static createGetFieldExpr; | ||
private static createSetFieldExpr; | ||
/** | ||
* Generate the query javascript object reflecting original | ||
* values of specific elements in this documents. This can include | ||
* elements that were updated in this document. In that case, the | ||
* values of this object are the original values, this can be used | ||
* when querying for an update to see if the original document was | ||
* changed in the background while it was being updated elsewhere. | ||
* | ||
* NOTE: `alwaysIncludeKeys` is currently used for sharding, since | ||
* updates on sharded setups need to include the shard key in their | ||
* find part. https://jira.mongodb.org/browse/SPM-1632 will make | ||
* this requirement go away for future MongoDB versions! | ||
* | ||
* @param target The target (sub-)document. | ||
* @param keyInclusionOptions Specify which fields to include in the | ||
* originalFields list. | ||
* @param includeUpdatedFields Whether to include the original values for | ||
* updated fields. | ||
* | ||
* @returns A pair of lists, one containing the original values for updated fields | ||
* or those specified in the always-include list, and one containing new values | ||
* of the updated fields. If includeUpdatedFields is not set, the second | ||
* list will be empty. | ||
*/ | ||
static getQueryForOriginalKeysAndValuesForSpecifiedFields(target: Document | Element, keyInclusionOptions: Readonly<KeyInclusionOptions>, includeUpdatedFields: boolean): BSONObject; | ||
/** | ||
* Generate an update document or pipeline which reflects the updates | ||
* that have taken place for this document. A pipeline will be returned | ||
* if the updates require changes to fields containing dots or prefixed | ||
* with $. | ||
* | ||
* @param target The target (sub-)document. | ||
*/ | ||
static generateUpdateDoc(target: Document | Element): { | ||
@@ -22,0 +118,0 @@ $set?: BSONObject; |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.ObjectGenerator = void 0; | ||
const lodash_isequal_1 = __importDefault(require("lodash.isequal")); | ||
const lodash_1 = require("lodash"); | ||
const DECRYPTED_KEYS = Symbol.for('@@mdb.decryptedKeys'); | ||
@@ -12,2 +9,3 @@ function maybeDecorateWithDecryptedKeys(object, element) { | ||
if (!object[DECRYPTED_KEYS]) { | ||
// non-enumerable object[DECRYPTED_KEYS] = [] | ||
Object.defineProperty(object, DECRYPTED_KEYS, { | ||
@@ -23,4 +21,15 @@ value: [], | ||
} | ||
/** Used to represent missing values, i.e. non-existent fields. */ | ||
const DoesNotExist = Symbol('DoesNotExist'); | ||
/** | ||
* Generates javascript objects from elements. | ||
*/ | ||
class ObjectGenerator { | ||
/** | ||
* Generate a javascript object from the elements. | ||
* | ||
* @param {Array} elements - The elements. | ||
* | ||
* @returns {Object} The javascript object. | ||
*/ | ||
static generate(elements, options = {}) { | ||
@@ -42,2 +51,11 @@ if (elements) { | ||
} | ||
/** | ||
* Generate a javascript object from the elements with their original keys | ||
* and values. This can be used in a query with an update to | ||
* ensure the values on the document to edit are still up to date. | ||
* | ||
* @param {Array} elements - The elements. | ||
* | ||
* @returns {Object} The javascript object. | ||
*/ | ||
static generateOriginal(elements, options = {}) { | ||
@@ -59,2 +77,9 @@ if (elements) { | ||
} | ||
/** | ||
* Generate an array from the elements. | ||
* | ||
* @param {Array} elements - The elements. | ||
* | ||
* @returns {Array} The array. | ||
*/ | ||
static generateArray(elements, options = {}) { | ||
@@ -81,2 +106,9 @@ if (elements) { | ||
} | ||
/** | ||
* Generate an array from the elements using their original values. | ||
* | ||
* @param {Array} elements - The elements. | ||
* | ||
* @returns {Array} The array. | ||
*/ | ||
static generateOriginalArray(elements, options = {}) { | ||
@@ -101,12 +133,31 @@ if (elements) { | ||
} | ||
/** | ||
* As the first step in generating query and update documents for updated | ||
* fields in a document, gather the original and new paths and values | ||
* for those updated fields. | ||
* | ||
* @param target The target document, or, when recursing, element. | ||
* @param keyInclusionOptions Specify which fields to include in the | ||
* originalFields list. | ||
* @param includeUpdatedFields Whether to include original and new values | ||
* of updated fields. If set to `false`, only fields included in | ||
* @see alwaysIncludeOriginalKeys are included. | ||
* @returns A pair `{ originalFields, newFields }`, each listing the | ||
* original and new paths and values for updated fields, respectively. | ||
*/ | ||
static recursivelyGatherFieldsAndValuesForUpdate(target, keyInclusionOptions, includeUpdatedFields) { | ||
var _a, _b, _c, _d, _e; | ||
const originalFields = []; | ||
const newFields = []; | ||
const alwaysIncludeKeys = (_a = keyInclusionOptions.alwaysIncludeKeys) !== null && _a !== void 0 ? _a : []; | ||
const includableEncryptedKeys = (_b = keyInclusionOptions.includableEncryptedKeys) !== null && _b !== void 0 ? _b : []; | ||
for (const element of (_c = target.elements) !== null && _c !== void 0 ? _c : []) { | ||
const alwaysIncludeKeys = keyInclusionOptions.alwaysIncludeKeys ?? []; | ||
const includableEncryptedKeys = keyInclusionOptions.includableEncryptedKeys ?? []; | ||
for (const element of target.elements ?? []) { | ||
const isArrayIndex = target.currentType === 'Array'; | ||
// Do not include encrypted fields in the `originalFields` list | ||
// unless we know that it is okay to include them (i.e. because | ||
// we know that we can perform equality queries on those fields). | ||
const canIncludeOriginalValue = !element.isValueDecrypted() || | ||
includableEncryptedKeys.some((key) => key.length === 1 && key[0] === String(element.key)); | ||
// Recurse into an element if it either has been updated and we are looking | ||
// for updated fields, or it is part of the set of keys that we should always | ||
// include. | ||
if ((includeUpdatedFields && | ||
@@ -117,2 +168,13 @@ element.isModified() && | ||
alwaysIncludeKeys.some((key) => key[0] === String(element.key))) { | ||
// Two possible cases: Either we recurse into this element and change | ||
// nested values, or we replace the element entirely. | ||
// We can only recurse if: | ||
// - This is a nested element with children, i.e. array or document | ||
// - It was not explicitly requested via alwaysIncludeKeys to | ||
// always include it in its entirety | ||
// - Its type has not changed | ||
// - It is not an array with removed elements, since MongoDB has | ||
// no way to remove individual array elements (!!) prior to | ||
// agg-pipeline-style updates added in 4.2, and even then it's complex | ||
// to actually do so | ||
if (element.elements && | ||
@@ -124,2 +186,5 @@ !alwaysIncludeKeys.some((key) => key.length === 1 && key[0] === String(element.key)) && | ||
!element.hasAnyRemovedChild()))) { | ||
// Nested case: Translate keyInclusionOptions to the nested keys, | ||
// get the original keys and values for the nested element, | ||
// then translate the result back to this level. | ||
const filterAndShiftFieldPaths = (paths) => paths | ||
@@ -151,2 +216,4 @@ .filter((key) => key[0] === String(element.key)) | ||
else { | ||
// Using `.key` instead of `.currentKey` to ensure we look at | ||
// the original field's value. | ||
if (canIncludeOriginalValue) { | ||
@@ -172,4 +239,9 @@ originalFields.push({ | ||
element.currentKey !== '') { | ||
// When a new field is added, check if the original value of that | ||
// field (which is typically that it was missing) was changed in | ||
// the background. If there *was* another field in its place, | ||
// that means that it was removed, and is added to `originalValue` | ||
// at another point. | ||
let wasRenamedToKeyOfPreviouslyExistingElement = false; | ||
for (const otherElement of (_d = target.elements) !== null && _d !== void 0 ? _d : []) { | ||
for (const otherElement of target.elements ?? []) { | ||
if (otherElement !== element && | ||
@@ -196,2 +268,3 @@ otherElement.key === element.currentKey) { | ||
element.key !== '') { | ||
// Remove the original field when an element is removed or renamed. | ||
if (canIncludeOriginalValue) { | ||
@@ -204,3 +277,3 @@ originalFields.push({ | ||
let wasRemovedAndLaterReplacedByNewElement = false; | ||
for (const otherElement of (_e = target.elements) !== null && _e !== void 0 ? _e : []) { | ||
for (const otherElement of target.elements ?? []) { | ||
if (otherElement !== element && | ||
@@ -220,6 +293,10 @@ otherElement.currentKey === element.key) { | ||
} | ||
// Sometimes elements are removed or renamed, and then another | ||
// element is added or renamed to take its place. We filter out | ||
// the DoesNotExist entry for that case. | ||
for (let i = 0; i < newFields.length;) { | ||
const entry = newFields[i]; | ||
if (entry.value === DoesNotExist) { | ||
if (newFields.some((otherEntry) => (0, lodash_isequal_1.default)(otherEntry.path, entry.path) && entry !== otherEntry)) { | ||
if (newFields.some((otherEntry) => (0, lodash_1.isEqual)(otherEntry.path, entry.path) && entry !== otherEntry)) { | ||
// Drop `entry`. | ||
newFields.splice(i, 1); | ||
@@ -233,2 +310,4 @@ continue; | ||
} | ||
// Return a $getField expression that evaluates to the current value | ||
// of the document at `path`. | ||
static createGetFieldExpr(path) { | ||
@@ -246,2 +325,4 @@ return path.reduce((input, { key, isArrayIndex }) => isArrayIndex | ||
} | ||
// Return a $setField expression that writes the specified value | ||
// to the document at `path`. | ||
static createSetFieldExpr(path, value) { | ||
@@ -251,2 +332,3 @@ return path.reduceRight((value, { key, isArrayIndex }, idx, array) => { | ||
if (!isArrayIndex) { | ||
// 'Simple' case: Change a property of a document | ||
return { | ||
@@ -260,2 +342,6 @@ $setField: { | ||
} | ||
// Array case: concatenate the prefix of the array before the changed | ||
// index, an array containing the new value at the changed index, | ||
// and the suffix afterwards; use $let to avoid specifying the full | ||
// input value expression multiple times. | ||
return { | ||
@@ -266,4 +352,7 @@ $let: { | ||
$concatArrays: [ | ||
// The third argument to $slice must not be 0 | ||
...(+key > 0 ? [{ $slice: ['$$input', 0, +key] }] : []), | ||
[value], | ||
// The third argument is required; 2^31-1 is the maximum | ||
// accepted value, and well beyond what BSON can represent. | ||
{ $slice: ['$$input', +key + 1, 2 ** 31 - 1] }, | ||
@@ -276,2 +365,26 @@ ], | ||
} | ||
/** | ||
* Generate the query javascript object reflecting original | ||
* values of specific elements in this documents. This can include | ||
* elements that were updated in this document. In that case, the | ||
* values of this object are the original values, this can be used | ||
* when querying for an update to see if the original document was | ||
* changed in the background while it was being updated elsewhere. | ||
* | ||
* NOTE: `alwaysIncludeKeys` is currently used for sharding, since | ||
* updates on sharded setups need to include the shard key in their | ||
* find part. https://jira.mongodb.org/browse/SPM-1632 will make | ||
* this requirement go away for future MongoDB versions! | ||
* | ||
* @param target The target (sub-)document. | ||
* @param keyInclusionOptions Specify which fields to include in the | ||
* originalFields list. | ||
* @param includeUpdatedFields Whether to include the original values for | ||
* updated fields. | ||
* | ||
* @returns A pair of lists, one containing the original values for updated fields | ||
* or those specified in the always-include list, and one containing new values | ||
* of the updated fields. If includeUpdatedFields is not set, the second | ||
* list will be empty. | ||
*/ | ||
static getQueryForOriginalKeysAndValuesForSpecifiedFields(target, keyInclusionOptions, includeUpdatedFields) { | ||
@@ -281,2 +394,4 @@ const { originalFields } = ObjectGenerator.recursivelyGatherFieldsAndValuesForUpdate(target, keyInclusionOptions, includeUpdatedFields); | ||
if (originalFields.some(({ path }) => path.some(({ key }) => key.includes('.') || key.startsWith('$')))) { | ||
// Some of the keys in this query are only accesible via $getField, | ||
// which was introduced in MongoDB 5.0. | ||
const equalityMatches = []; | ||
@@ -304,6 +419,15 @@ for (const { path, value } of originalFields) { | ||
} | ||
/** | ||
* Generate an update document or pipeline which reflects the updates | ||
* that have taken place for this document. A pipeline will be returned | ||
* if the updates require changes to fields containing dots or prefixed | ||
* with $. | ||
* | ||
* @param target The target (sub-)document. | ||
*/ | ||
static generateUpdateDoc(target) { | ||
var _a, _b; | ||
const { newFields } = ObjectGenerator.recursivelyGatherFieldsAndValuesForUpdate(target, {}, true); | ||
if (newFields.some(({ path }) => path.some(({ key }) => key.includes('.') || key.startsWith('$')))) { | ||
// Some of the keys in this query are only writable via $setField/$unsetField, | ||
// which was introduced in MongoDB 5.0. In this case we can use pipeline-style updates. | ||
return newFields.map(({ path, value }) => { | ||
@@ -319,7 +443,7 @@ return { | ||
if (value === DoesNotExist) { | ||
(_a = updateDoc.$unset) !== null && _a !== void 0 ? _a : (updateDoc.$unset = {}); | ||
updateDoc.$unset ??= {}; | ||
updateDoc.$unset[path.map(({ key }) => key).join('.')] = true; | ||
} | ||
else { | ||
(_b = updateDoc.$set) !== null && _b !== void 0 ? _b : (updateDoc.$set = {}); | ||
updateDoc.$set ??= {}; | ||
updateDoc.$set[path.map(({ key }) => key).join('.')] = value; | ||
@@ -326,0 +450,0 @@ } |
@@ -1,11 +0,33 @@ | ||
import { EJSON } from 'bson'; | ||
import type { TypeCastMap, TypeCastTypes } from 'hadron-type-checker'; | ||
export declare function fieldStringLen(value: unknown): number; | ||
export declare type BSONObject = TypeCastMap['Object']; | ||
export declare type BSONArray = TypeCastMap['Array']; | ||
export declare type BSONValue = TypeCastMap[TypeCastTypes]; | ||
export type BSONObject = TypeCastMap['Object']; | ||
export type BSONArray = TypeCastMap['Array']; | ||
export type BSONValue = TypeCastMap[TypeCastTypes]; | ||
export interface HadronEJSONOptions { | ||
indent?: number | string; | ||
} | ||
export declare function objectToIdiomaticEJSON(value: Readonly<EJSON.SerializableTypes>, options?: HadronEJSONOptions): string; | ||
/** | ||
* Turn a BSON value into what we consider idiomatic extended JSON. | ||
* | ||
* This differs from both the relaxed and strict mode of the 'bson' | ||
* package's EJSON class: We preserve the type information for longs | ||
* via $numberLong, but redact it for $numberInt and $numberDouble. | ||
* | ||
* This may seem inconsistent, but considering that the latter two | ||
* types are exactly representable in JS and $numberLong is not, | ||
* in addition to the fact that this has been historic behavior | ||
* in Compass for a long time, this seems like a reasonable choice. | ||
* | ||
* Also turns $date.$numberLong into a date so that it will be | ||
* displayed as an iso date string since this is what Compass did | ||
* historically. Unless it is outside of the safe range. | ||
* | ||
* @param value Any BSON value. | ||
* @returns A serialized, human-readable and human-editable string. | ||
*/ | ||
export declare function objectToIdiomaticEJSON(value: any, options?: HadronEJSONOptions): string; | ||
/** | ||
* Returns a default value for the BSON type passed in. | ||
*/ | ||
export declare function getDefaultValueForType(type: keyof TypeCastMap): string | boolean | Date | Record<string, unknown> | unknown[] | import("bson").Binary | import("bson").Code | import("bson").Decimal128 | import("bson").Double | import("bson").Int32 | import("bson").Long | import("bson").MaxKey | import("bson").MinKey | import("bson").ObjectId | import("bson").BSONRegExp | import("bson").BSONSymbol | import("bson").Timestamp | null | undefined; | ||
//# sourceMappingURL=utils.d.ts.map |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.objectToIdiomaticEJSON = exports.fieldStringLen = void 0; | ||
exports.getDefaultValueForType = exports.objectToIdiomaticEJSON = exports.fieldStringLen = void 0; | ||
const bson_1 = require("bson"); | ||
const hadron_type_checker_1 = __importDefault(require("hadron-type-checker")); | ||
const UNCASTED_EMPTY_TYPE_VALUE = { | ||
Array: [], | ||
Object: {}, | ||
Decimal128: 0, | ||
Int32: 0, | ||
Int64: 0, | ||
Double: 0, | ||
MaxKey: 0, | ||
MinKey: 0, | ||
Timestamp: 0, | ||
Date: 0, | ||
String: '', | ||
Code: '', | ||
Binary: '', | ||
ObjectId: '', | ||
BSONRegExp: '', | ||
BSONSymbol: '', | ||
Boolean: false, | ||
Undefined: undefined, | ||
Null: null, | ||
}; | ||
const maxFourYearDate = new Date('9999-12-31T23:59:59.999Z').valueOf(); | ||
function fieldStringLen(value) { | ||
@@ -10,2 +36,21 @@ const length = String(value).length; | ||
exports.fieldStringLen = fieldStringLen; | ||
/** | ||
* Turn a BSON value into what we consider idiomatic extended JSON. | ||
* | ||
* This differs from both the relaxed and strict mode of the 'bson' | ||
* package's EJSON class: We preserve the type information for longs | ||
* via $numberLong, but redact it for $numberInt and $numberDouble. | ||
* | ||
* This may seem inconsistent, but considering that the latter two | ||
* types are exactly representable in JS and $numberLong is not, | ||
* in addition to the fact that this has been historic behavior | ||
* in Compass for a long time, this seems like a reasonable choice. | ||
* | ||
* Also turns $date.$numberLong into a date so that it will be | ||
* displayed as an iso date string since this is what Compass did | ||
* historically. Unless it is outside of the safe range. | ||
* | ||
* @param value Any BSON value. | ||
* @returns A serialized, human-readable and human-editable string. | ||
*/ | ||
function objectToIdiomaticEJSON(value, options = {}) { | ||
@@ -24,2 +69,3 @@ const serialized = bson_1.EJSON.serialize(value, { | ||
const entry = value[key]; | ||
// We are only interested in object-like values, skip everything else | ||
if (typeof entry !== 'object' || entry === null) { | ||
@@ -35,2 +81,4 @@ continue; | ||
!Object.is(+entry.$numberDouble, -0)) { | ||
// EJSON can represent +/-Infinity or NaN values but JSON can't | ||
// (and -0 can be parsed from JSON but not serialized by JSON.stringify). | ||
value[key] = +entry.$numberDouble; | ||
@@ -40,5 +88,18 @@ } | ||
} | ||
if (entry.$date && entry.$date.$numberLong) { | ||
const number = entry.$date.$numberLong; | ||
if (number >= 0 && number <= maxFourYearDate) { | ||
entry.$date = new Date(+number).toISOString(); | ||
} | ||
} | ||
makeEJSONIdiomatic(entry); | ||
} | ||
} | ||
/** | ||
* Returns a default value for the BSON type passed in. | ||
*/ | ||
function getDefaultValueForType(type) { | ||
return hadron_type_checker_1.default.cast(UNCASTED_EMPTY_TYPE_VALUE[type], type); | ||
} | ||
exports.getDefaultValueForType = getDefaultValueForType; | ||
//# sourceMappingURL=utils.js.map |
@@ -10,3 +10,3 @@ { | ||
"homepage": "https://github.com/mongodb-js/compass", | ||
"version": "0.0.0-next-44318443f2cd1088082181789b25fc26a7a11bb2", | ||
"version": "0.0.0-next-4474f17fe4181829ba5d709778c535a4c343cccf", | ||
"repository": { | ||
@@ -34,6 +34,6 @@ "type": "git", | ||
"prepublishOnly": "npm run compile && compass-scripts check-exports-exist", | ||
"clean": "rimraf dist", | ||
"clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\" || true", | ||
"precompile": "npm run clean", | ||
"compile": "tsc -p tsconfig.json", | ||
"depcheck": "depcheck", | ||
"depcheck": "compass-scripts check-peer-deps && depcheck", | ||
"eslint": "eslint", | ||
@@ -45,27 +45,18 @@ "prettier": "prettier", | ||
"test": "mocha", | ||
"test-cov": "nyc -x \"**/*.spec.*\" --reporter=lcov --reporter=text --reporter=html npm run test", | ||
"test-cov": "nyc --compact=false --produce-source-map=false -x \"**/*.spec.*\" --reporter=lcov --reporter=text --reporter=html npm run test", | ||
"test-watch": "npm run test -- --watch", | ||
"test-ci": "npm run test-cov", | ||
"reformat": "npm run prettier -- --write ." | ||
"reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." | ||
}, | ||
"dependencies": { | ||
"bson": "^4.4.1", | ||
"debug": "^4.2.0", | ||
"bson": "^6.7.0", | ||
"eventemitter3": "^4.0.0", | ||
"hadron-type-checker": "0.0.0-next-44318443f2cd1088082181789b25fc26a7a11bb2", | ||
"lodash.foreach": "^4.5.0", | ||
"lodash.isarray": "^4.0.0", | ||
"lodash.isequal": "^4.5.0", | ||
"lodash.isplainobject": "^4.0.6", | ||
"lodash.isstring": "^4.0.1" | ||
"hadron-type-checker": "0.0.0-next-4474f17fe4181829ba5d709778c535a4c343cccf", | ||
"lodash": "^4.17.21" | ||
}, | ||
"devDependencies": { | ||
"@mongodb-js/eslint-config-compass": "0.0.0-next-44318443f2cd1088082181789b25fc26a7a11bb2", | ||
"@mongodb-js/mocha-config-compass": "0.0.0-next-44318443f2cd1088082181789b25fc26a7a11bb2", | ||
"@mongodb-js/prettier-config-compass": "0.0.0-next-44318443f2cd1088082181789b25fc26a7a11bb2", | ||
"@mongodb-js/tsconfig-compass": "0.0.0-next-44318443f2cd1088082181789b25fc26a7a11bb2", | ||
"@types/lodash.isarray": "^4.0.6", | ||
"@types/lodash.isequal": "^4.5.5", | ||
"@types/lodash.isplainobject": "^4.0.6", | ||
"@types/lodash.isstring": "^4.0.6", | ||
"@mongodb-js/eslint-config-compass": "0.0.0-next-4474f17fe4181829ba5d709778c535a4c343cccf", | ||
"@mongodb-js/mocha-config-compass": "0.0.0-next-4474f17fe4181829ba5d709778c535a4c343cccf", | ||
"@mongodb-js/prettier-config-compass": "0.0.0-next-4474f17fe4181829ba5d709778c535a4c343cccf", | ||
"@mongodb-js/tsconfig-compass": "0.0.0-next-4474f17fe4181829ba5d709778c535a4c343cccf", | ||
"chai": "^4.2.0", | ||
@@ -75,7 +66,8 @@ "depcheck": "^1.4.1", | ||
"eslint-config-mongodb-js": "^5.0.3", | ||
"mocha": "^7.0.1", | ||
"moment": "^2.27.0", | ||
"prettier": "^2.7.1" | ||
"mocha": "^10.2.0", | ||
"moment": "^2.29.4", | ||
"prettier": "^2.7.1", | ||
"sinon": "^17.0.1" | ||
}, | ||
"gitHead": "44318443f2cd1088082181789b25fc26a7a11bb2" | ||
"gitHead": "4474f17fe4181829ba5d709778c535a4c343cccf" | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
231078
4
12
3751
+ Addedlodash@^4.17.21
+ Addedbson@6.10.3(transitive)
+ Addedhadron-type-checker@0.0.0-next-4474f17fe4181829ba5d709778c535a4c343cccf(transitive)
- Removeddebug@^4.2.0
- Removedlodash.foreach@^4.5.0
- Removedlodash.isarray@^4.0.0
- Removedlodash.isequal@^4.5.0
- Removedlodash.isplainobject@^4.0.6
- Removedlodash.isstring@^4.0.1
- Removedbase64-js@1.5.1(transitive)
- Removedbson@4.7.2(transitive)
- Removedbuffer@5.7.1(transitive)
- Removeddebug@4.4.0(transitive)
- Removedhadron-type-checker@0.0.0-next-44318443f2cd1088082181789b25fc26a7a11bb2(transitive)
- Removedieee754@1.2.1(transitive)
- Removedlodash.foreach@4.5.0(transitive)
- Removedlodash.isarray@4.0.0(transitive)
- Removedlodash.isequal@4.5.0(transitive)
- Removedlodash.isplainobject@4.0.6(transitive)
- Removedlodash.isstring@4.0.1(transitive)
- Removedms@2.1.3(transitive)
Updatedbson@^6.7.0
Updatedhadron-type-checker@0.0.0-next-4474f17fe4181829ba5d709778c535a4c343cccf