@squidcloud/client 1.0.195 → 1.0.196

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/index.js CHANGED
@@ -28258,7 +28258,10 @@ function map(project, thisArg) {
28258
28258
  });
28259
28259
  }
28260
28260
  //# sourceMappingURL=map.js.map
28261
+ // EXTERNAL MODULE: ../node_modules/assertic/dist/index.js
28262
+ var dist = __webpack_require__(8975);
28261
28263
  ;// CONCATENATED MODULE: ../internal-common/src/public-utils/id-utils.ts
28264
+
28262
28265
  /** Generates a UUID-style ID. */
28263
28266
  function generateId() {
28264
28267
  let dt = new Date().getTime();
@@ -28270,12 +28273,19 @@ function generateId() {
28270
28273
  }
28271
28274
  /** Default length if ID produced by `generateShortId`. */
28272
28275
  const DEFAULT_SHORT_ID_LENGTH = 18;
28273
- /** Generates an ID of the given `length` using lowercase latin letters and digits.. */
28274
- function generateShortId(length = DEFAULT_SHORT_ID_LENGTH) {
28275
- const characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
28276
+ const SHORT_ID_CHARACTERS = 'abcdefghijklmnopqrstuvwxyz0123456789';
28277
+ /**
28278
+ * Generates an ID of the given `length` using lowercase latin letters and digits.
28279
+ * If the 'prefix' provided replaces the first generated characters with the prefix.
28280
+ */
28281
+ function generateShortId(length = DEFAULT_SHORT_ID_LENGTH, prefix = '') {
28282
+ (0,dist.assertTruthy)(prefix.length < length, 'ID prefix is too long');
28276
28283
  let id = '';
28277
28284
  for (let i = 0; i < length; i++) {
28278
- id += characters.charAt(Math.floor(Math.random() * characters.length));
28285
+ id += SHORT_ID_CHARACTERS.charAt(Math.floor(Math.random() * SHORT_ID_CHARACTERS.length));
28286
+ }
28287
+ if (prefix.length > 0) {
28288
+ id = prefix + id.substring(prefix.length);
28279
28289
  }
28280
28290
  return id;
28281
28291
  }
@@ -28433,7 +28443,7 @@ class AiChatbotProfileReference {
28433
28443
  * Adds a new profile to the chatbot. This will result in an error if a profile already exists with the same id.
28434
28444
  *
28435
28445
  * @param data An object containing options for creating the profile.
28436
- * @param data.modelName - The name of the OpenAI model (`gpt-3.5, `gpt-4` or `claude-2`).
28446
+ * @param data.modelName - The name of the OpenAI model (`gpt-3.5, `gpt-4`, `claude-3-opus-20240229`, `claude-3-sonnet-20240229` or `claude-3-haiku-20240307`).
28437
28447
  * @param data.isPublic - Whether the chat functionality of the profile can be accessed without security rules.
28438
28448
  * @returns A promise that resolves when the profile is successfully created.
28439
28449
  */
@@ -28458,7 +28468,7 @@ class AiChatbotProfileReference {
28458
28468
  * current profile id.
28459
28469
  *
28460
28470
  * @param data An object containing options for updating the profile.
28461
- * @param data.modelName - The name of the OpenAI model (`gpt-3.5, `gpt-4` or `claude-2`).
28471
+ * @param data.modelName - The name of the OpenAI model (`gpt-3.5, `gpt-4`, `claude-3-opus-20240229`, `claude-3-sonnet-20240229` or `claude-3-haiku-20240307`).
28462
28472
  * @param data.isPublic - Whether the chat functionality of the profile can be accessed without security rules.
28463
28473
  * @returns A promise that resolves when the profile is successfully updated.
28464
28474
  */
@@ -28777,8 +28787,6 @@ class ApiClient {
28777
28787
 
28778
28788
  // EXTERNAL MODULE: ../node_modules/lodash/lodash.js
28779
28789
  var lodash = __webpack_require__(8784);
28780
- // EXTERNAL MODULE: ../node_modules/assertic/dist/index.js
28781
- var dist = __webpack_require__(8975);
28782
28790
  // EXTERNAL MODULE: ../node_modules/rfdc/index.js
28783
28791
  var rfdc = __webpack_require__(7795);
28784
28792
  var rfdc_default = /*#__PURE__*/__webpack_require__.n(rfdc);
@@ -29355,7 +29363,9 @@ const AI_MODEL_NAMES = [
29355
29363
  'gpt-3.5-turbo',
29356
29364
  'gpt-3.5-turbo-1106',
29357
29365
  'gpt-4',
29358
- 'claude-2',
29366
+ 'claude-3-opus-20240229',
29367
+ 'claude-3-sonnet-20240229',
29368
+ 'claude-3-haiku-20240307',
29359
29369
  'gpt-4-turbo-preview',
29360
29370
  ];
29361
29371
 
@@ -29612,6 +29622,7 @@ var IntegrationType;
29612
29622
  IntegrationType["cognito"] = "cognito";
29613
29623
  IntegrationType["okta"] = "okta";
29614
29624
  IntegrationType["descope"] = "descope";
29625
+ IntegrationType["firebase_auth"] = "firebase_auth";
29615
29626
  IntegrationType["kafka"] = "kafka";
29616
29627
  IntegrationType["confluent"] = "confluent";
29617
29628
  IntegrationType["built_in_queue"] = "built_in_queue";
@@ -30174,235 +30185,6 @@ var ClientConnectionState;
30174
30185
 
30175
30186
 
30176
30187
 
30177
-
30178
- ;// CONCATENATED MODULE: ../internal-common/src/utils/assert.ts
30179
-
30180
-
30181
-
30182
- /** @internal */
30183
- function assertValidateTruthy(value, message, statusCode = HttpStatus.BAD_REQUEST, details) {
30184
- assertTruthy(value, () => new ValidationError(message, statusCode, details));
30185
- }
30186
- /** @internal */
30187
- function assert_validateTruthy(value, message, statusCode = HttpStatus.BAD_REQUEST, details) {
30188
- return truthy(value, () => new ValidationError(message, statusCode, details));
30189
- }
30190
-
30191
- ;// CONCATENATED MODULE: ../internal-common/src/utils/validation.ts
30192
- /**
30193
- * This file contains general validators for the different objects being received from the client. The parameters are
30194
- * usually of type 'any' to make sure there are no assumptions that the object has the correct type.
30195
- * Also, this file should avoid importing from other files that are not for validation to avoid circular deps.
30196
- */
30197
-
30198
-
30199
-
30200
- class validation_ValidationError extends Error {
30201
- constructor(error, statusCode, details) {
30202
- super(error);
30203
- this.statusCode = statusCode;
30204
- this.details = details;
30205
- }
30206
- }
30207
- function validatePathPart(part) {
30208
- if (!part || !part.match(/^[a-zA-Z][a-zA-Z0-9!@#$%^&*~_]{0,49}$/)) {
30209
- throw new Error('A document id and a collection id can contain only a-z, A-Z, 0-9,!@#$%^&*~_, starting' +
30210
- 'with a letter, at least one character, and up to 50.');
30211
- }
30212
- }
30213
- function validateCollectionName(collectionName) {
30214
- if (typeof collectionName !== 'string' || !collectionName) {
30215
- throw new Error('Collection path has to be a non empty string');
30216
- }
30217
- validatePathPart(collectionName);
30218
- }
30219
- function validateFieldName(fieldName) {
30220
- if (!fieldName || typeof fieldName !== 'string') {
30221
- throw new Error('Field name has to be a non-empty string');
30222
- }
30223
- if (fieldName === '__docId__') {
30224
- // __docId__ is the only valid fieldName that can start with '_'
30225
- return;
30226
- }
30227
- if (!fieldName.match(/^[a-zA-Z_$][a-zA-Z0-9!@#$%^&*~_ ]{0,49}$/)) {
30228
- throw new Error('A Field name can contain only a-z, A-Z, 0-9,!@#$%^&*~_, starting with a letter, at least one character, and up to 50. Field name: ' +
30229
- fieldName);
30230
- }
30231
- }
30232
- function isValidId(id) {
30233
- return typeof id === 'string' && !!id && id.length <= 50 && /^[a-zA-Z0-9_-]+$/.test(id);
30234
- }
30235
- function validateId(id, message) {
30236
- validateTruthy(isValidId(id), message, HttpStatus.BAD_REQUEST);
30237
- }
30238
- function validateFieldSort(fieldSort) {
30239
- if (!(fieldSort instanceof Object)) {
30240
- throw new Error('Field sort has to be an object');
30241
- }
30242
- const safeFieldSort = fieldSort;
30243
- (0,dist.assertTruthy)(hasOnlyKeys(safeFieldSort, ['fieldName', 'asc']), 'Field sort should only contain a fieldName and asc');
30244
- (0,dist.assertTruthy)(isRightType(safeFieldSort.asc, 'boolean'), 'Asc needs to be boolean');
30245
- validateFieldName(safeFieldSort.fieldName);
30246
- }
30247
- /** @internal */
30248
- function validateOpenIdProvider(openIdProvider) {
30249
- assertTruthy(openIdProvider, 'INVALID_PROVIDER');
30250
- validateOpenIdProviderType(openIdProvider.providerType);
30251
- assertTruthy(openIdProvider.providerType, 'INVALID_CLIENT_ID');
30252
- assertTruthy(openIdProvider.clientId, 'INVALID_CLIENT_ID');
30253
- assertTruthy(openIdProvider.domain, 'INVALID_DOMAIN');
30254
- return openIdProvider;
30255
- }
30256
- /** @internal */
30257
- function validateOpenIdProviderType(providerType) {
30258
- const providerArray = ['auth0'];
30259
- assertTruthy(providerArray.includes(providerType), 'INVALID_OPEN_ID_PROVIDER_TYPE');
30260
- }
30261
- function validateDeleteMutation(mutation) {
30262
- if (!mutation || mutation.type !== 'delete') {
30263
- throw new Error('Mutation has to be non empty with type delete.');
30264
- }
30265
- // Not much to validate for delete.
30266
- }
30267
- function validateInsertMutation(mutation) {
30268
- if (!mutation || mutation.type !== 'insert') {
30269
- throw new Error('Mutation has to be non empty with type insert.');
30270
- }
30271
- if (!mutation.properties || typeof mutation.properties !== 'object') {
30272
- throw new Error('The properties in insert mutation need to be a JSON object.');
30273
- }
30274
- for (const [fieldName] of Object.entries(mutation.properties)) {
30275
- validateFieldName(fieldName);
30276
- // TODO - figure out how to validate the value
30277
- }
30278
- }
30279
- function validateUpdatePropertyMutation(propertyMutation) {
30280
- if (!propertyMutation || propertyMutation.type !== 'update') {
30281
- throw new Error('Update value property mutation has to be of type update');
30282
- }
30283
- if (propertyMutation.value === undefined) {
30284
- throw new Error('Value has to exist in an update value property mutation..');
30285
- }
30286
- }
30287
- function validateApplyNumericFnPropertyMutation(propertyMutation) {
30288
- if (!propertyMutation || propertyMutation.type !== 'applyNumericFn') {
30289
- throw new Error('Apply numeric fn mutation has to be of type applyNumericFn');
30290
- }
30291
- if (!['increment'].includes(propertyMutation.fn)) {
30292
- throw new Error('Invalid fn for apply numeric fn.');
30293
- }
30294
- if (typeof propertyMutation.value !== 'number') {
30295
- throw new Error('The value in an apply numeric fn function has to be numeric.');
30296
- }
30297
- }
30298
- function validateApplyStringFnPropertyMutation(propertyMutation) {
30299
- if (!propertyMutation || propertyMutation.type !== 'applyStringFn') {
30300
- throw new Error('Apply string fn mutation has to be of type applyStringFn');
30301
- }
30302
- if (!['trim', 'extendString'].includes(propertyMutation.fn)) {
30303
- throw new Error('Invalid fn for apply string fn.');
30304
- }
30305
- if (typeof propertyMutation.value !== 'string') {
30306
- throw new Error('The value in an apply string fn function has to be a string.');
30307
- }
30308
- }
30309
- function validatePropertyMutation(propertyMutation) {
30310
- if (!propertyMutation || typeof propertyMutation !== 'object') {
30311
- throw new Error('Property mutation need to be a JSON object.');
30312
- }
30313
- if (!['update', 'applyNumericFn', 'applyStringFn'].includes(propertyMutation.type)) {
30314
- throw new Error(`Property mutation can be of type 'update', 'applyNumericFn', 'applyStringFn'`);
30315
- }
30316
- switch (propertyMutation.type) {
30317
- case 'update':
30318
- validateUpdatePropertyMutation(propertyMutation);
30319
- break;
30320
- case 'applyNumericFn':
30321
- validateApplyNumericFnPropertyMutation(propertyMutation);
30322
- break;
30323
- case 'applyStringFn':
30324
- validateApplyStringFnPropertyMutation(propertyMutation);
30325
- break;
30326
- }
30327
- }
30328
- function validateUpdateMutation(mutation) {
30329
- if (!mutation || mutation.type !== 'update') {
30330
- throw new Error('Mutation has to be non empty with type update.');
30331
- }
30332
- if (!mutation.properties || typeof mutation.properties !== 'object') {
30333
- throw new Error('The properties in update mutation need to be a JSON object.');
30334
- }
30335
- const entries = Object.entries(mutation.properties);
30336
- for (const [fieldName, propertyMutations] of entries) {
30337
- validateFieldName(fieldName);
30338
- for (const propertyMutation of propertyMutations) {
30339
- validatePropertyMutation(propertyMutation);
30340
- }
30341
- }
30342
- }
30343
- function validateMutation(mutation) {
30344
- if (!mutation) {
30345
- throw new Error('Mutation cannot be empty');
30346
- }
30347
- if (!['insert', 'delete', 'update'].includes(mutation.type)) {
30348
- throw new Error(`Mutation type has to be one of 'insert', 'delete', or 'update'`);
30349
- }
30350
- validateCollectionName(mutation.squidDocIdObj.collectionName);
30351
- validatePathPart(mutation.squidDocIdObj.docId);
30352
- switch (mutation.type) {
30353
- case 'delete':
30354
- validateDeleteMutation(mutation);
30355
- break;
30356
- case 'insert':
30357
- validateInsertMutation(mutation);
30358
- break;
30359
- case 'update':
30360
- validateUpdateMutation(mutation);
30361
- break;
30362
- }
30363
- }
30364
- function validateMutations(mutations) {
30365
- if (!mutations || !(mutations instanceof Array) || !mutations.length) {
30366
- throw new Error('The list of mutations has to be a non-empty array.');
30367
- }
30368
- for (const mutation of mutations) {
30369
- validateMutation(mutation);
30370
- }
30371
- }
30372
- function validateQueryLimit(limit) {
30373
- (0,dist.assertNumber)(limit, 'Limit needs to be a number');
30374
- if (limit === -1)
30375
- return;
30376
- (0,dist.assertTruthy)(limit > 0, 'query limit has to be greater than 0');
30377
- (0,dist.assertTruthy)(Math.floor(limit) === limit, 'query limit has to be an integer');
30378
- (0,dist.assertTruthy)(limit <= 20000, 'Limit can be maximum 20000');
30379
- }
30380
- /** Returns true if the value is not an empty string (undefined/null are considered empty). */
30381
- function isNotEmpty(value) {
30382
- validateCorrectStringType(value);
30383
- return typeof value === 'string' && !!value;
30384
- }
30385
- /**
30386
- * TODO: deprecated: this method is used in both validate...() and is...() methods.
30387
- * The validate...() must throw an error, while is...() must not.
30388
- */
30389
- function validateCorrectStringType(value) {
30390
- if (value !== null && value !== undefined && typeof value !== 'string') {
30391
- throw new Error(`Unexpected input type ${typeof value}`);
30392
- }
30393
- }
30394
- /** Returns true if 'typeof' of the 'value' is 'type' or 'type[]'. */
30395
- function isRightType(value, type) {
30396
- // TODO: the method is ambiguous when the value is an empty array and will return 'true' for any type.
30397
- if (Array.isArray(value)) {
30398
- return value.every(element => typeof element === type);
30399
- }
30400
- return typeof value === type;
30401
- }
30402
- /** Returns true if 'obj' has only keys listed in the 'keys'. Object can't be an array. */
30403
- function hasOnlyKeys(obj, keys) {
30404
- return !Array.isArray(obj) && [...Object.keys(obj)].every(key => keys.includes(key));
30405
- }
30406
30188
 
30407
30189
  ;// CONCATENATED MODULE: ../internal-common/src/types/document.types.ts
30408
30190
 
@@ -30428,27 +30210,545 @@ function getSquidDocId(...args) {
30428
30210
  return normalizeJsonAsString(squidDocIdObjObj);
30429
30211
  }
30430
30212
 
30431
- ;// CONCATENATED MODULE: ./src/query/pagination.ts
30213
+ ;// CONCATENATED MODULE: ./src/document-reference.ts
30432
30214
 
30433
30215
 
30434
30216
 
30435
- class Pagination {
30436
- /** @internal */
30437
- constructor(snapshotEmitter, options = {}) {
30438
- /* Invariants:
30439
- internalStateObserver.value.data
30440
- - always contains the pageSize elements that are on the page (fewer if we're on the last page)
30441
- - always contains some elements before the first one on the page and some elements after
30442
- the last one on the page, unless at the beginning/end.
30443
- Note: it is possible that all the elements before/after the page get deleted. Our current strategy is to grab
30444
- a full page of them and hope they don't all get deleted. An alternative strategy would be to check in dataReceived
30445
- if numBefore and numAfter are getting low, and rerun the query in that case. However, in order to do that we would need to be
30446
- able to check if we are at the next-to-last page, because numAfter can be as low as 1 in that case, and similarly for numBefore.
30447
-
30448
- firstElement
30449
- - is an extracted document that may or may not be part of the data
30450
- - the page consists of the first pageSize elements of data that are >= firstElement
30451
- - firstElement === null refers to a hypothetical document that is ordered before all other documents.
30217
+
30218
+
30219
+
30220
+
30221
+ /**
30222
+ * Holds a reference to a document. A document reference is a reference to a specific record in a collection. You can
30223
+ * use it to read or write data to the document. A document can refer to a row in a table in a relational database or a
30224
+ * document in a NoSQL database. Additionally, a document reference can refer to a non-existent document, which you can
30225
+ * use to create a new document.
30226
+ *
30227
+ * Read more about document references in the
30228
+ * {@link https://docs.squid.cloud/docs/development-tools/client-sdk/document-reference documentation}.
30229
+ * @typeParam T The type of the document data.
30230
+ */
30231
+ class DocumentReference {
30232
+ /**
30233
+ * @internal
30234
+ */
30235
+ constructor(_squidDocId, dataManager, queryBuilderFactory) {
30236
+ this._squidDocId = _squidDocId;
30237
+ this.dataManager = dataManager;
30238
+ this.queryBuilderFactory = queryBuilderFactory;
30239
+ this.refId = generateId();
30240
+ }
30241
+ /**
30242
+ * @internal
30243
+ */
30244
+ get squidDocId() {
30245
+ return this._squidDocId;
30246
+ }
30247
+ /**
30248
+ * Returns the document data. Throws an error if the document does not exist.
30249
+ *
30250
+ * @returns The document data.
30251
+ * @throws Error if the document does not exist.
30252
+ */
30253
+ get data() {
30254
+ return cloneDeep(this.dataRef);
30255
+ }
30256
+ /**
30257
+ * Returns a read-only internal copy of the document data. This works similar to `this.data`, except it does not
30258
+ * perform a defensive copy. The caller may not modify this object, on penalty of unexpected behavior.
30259
+ *
30260
+ * @returns The document data.
30261
+ * @throws Error if the document does not exist.
30262
+ */
30263
+ get dataRef() {
30264
+ const getError = () => {
30265
+ const { collectionName, integrationId, docId } = parseSquidDocId(this.squidDocId);
30266
+ return `No data found for document reference: ${JSON.stringify({
30267
+ docId,
30268
+ collectionName,
30269
+ integrationId,
30270
+ }, null, 2)}`;
30271
+ };
30272
+ return (0,dist.truthy)(this.dataManager.getProperties(this.squidDocId), getError);
30273
+ }
30274
+ /**
30275
+ * Returns whether data has been populated for this document reference. Data
30276
+ * will not present if a document has not been queried, does not exist, or
30277
+ * has been deleted.
30278
+ *
30279
+ * @returns Whether the document has data.
30280
+ */
30281
+ get hasData() {
30282
+ return !!this.dataManager.getProperties(this.squidDocId);
30283
+ }
30284
+ /**
30285
+ * A promise that resolves with the latest data from the server or undefined if the document does not exist on the
30286
+ * server.
30287
+ *
30288
+ * @returns A promise that resolves with latest data from the server or undefined if the document does not exist on
30289
+ * the server.
30290
+ */
30291
+ async snapshot() {
30292
+ if (this.hasSquidPlaceholderId()) {
30293
+ throw new Error('Cannot invoke snapshot of a document that was created locally without storing it on the server.');
30294
+ }
30295
+ if (this.isTracked() && this.hasData)
30296
+ return this.data;
30297
+ const results = await this.queryBuilderFactory.getForDocument(this.squidDocId).dereference().snapshot();
30298
+ (0,dist.truthy)(results.length <= 1, 'Got more than one doc for the same id:' + this.squidDocId);
30299
+ return results.length ? results[0] : undefined;
30300
+ }
30301
+ /**
30302
+ * Returns an observable that emits the latest data from the server or undefined if the document is deleted or does
30303
+ * not exist on the server.
30304
+ *
30305
+ * @returns An observable that emits the latest data from the server or undefined if the document is deleted or does
30306
+ * not exist on the server.
30307
+ */
30308
+ snapshots() {
30309
+ return this.queryBuilderFactory
30310
+ .getForDocument(this.squidDocId)
30311
+ .dereference()
30312
+ .snapshots()
30313
+ .pipe((0,external_rxjs_.map)(results => {
30314
+ (0,dist.truthy)(results.length <= 1, 'Got more than one doc for the same id:' + this.squidDocId);
30315
+ return results.length ? results[0] : undefined;
30316
+ }));
30317
+ }
30318
+ /**
30319
+ * Returns the data that is currently available on the client or undefined if data has not yet been populated.
30320
+ *
30321
+ * @returns The data that is currently available on the client or undefined if data has not yet been populated.
30322
+ */
30323
+ peek() {
30324
+ return this.isTracked() && this.hasData ? this.data : undefined;
30325
+ }
30326
+ /**
30327
+ * Returns whether the locally available version of the document may not be the latest version on the server.
30328
+ *
30329
+ * @returns Whether the locally available version of the document may not be the latest version on the server.
30330
+ */
30331
+ isDirty() {
30332
+ return this.dataManager.isDirty(this.squidDocId);
30333
+ }
30334
+ isTracked() {
30335
+ return this.dataManager.isTracked(this.squidDocId);
30336
+ }
30337
+ /**
30338
+ * Updates the document with the given data.
30339
+ * The `update` will be reflected optimistically locally and will be applied to the server later.
30340
+ * If a transactionId is provided, the `update` will be applied to the server as an atomic operation together with
30341
+ * the rest of the operations in the transaction and the `update` will not reflect locally until the transaction is
30342
+ * completed locally.
30343
+ *
30344
+ * The returned promise will resolve once the `update` has been applied to the server or immediately if the `update`
30345
+ * is part of a transaction.
30346
+ *
30347
+ * @param data The data to update - can be partial.
30348
+ * @param transactionId The transaction to use for this operation. If not provided, the operation will be applied
30349
+ * immediately.
30350
+ */
30351
+ async update(data, transactionId) {
30352
+ const properties = {};
30353
+ Object.entries(data).forEach(([fieldName, value]) => {
30354
+ const propertyMutation = value !== undefined
30355
+ ? {
30356
+ type: 'update',
30357
+ value: value,
30358
+ }
30359
+ : {
30360
+ type: 'removeProperty',
30361
+ };
30362
+ properties[fieldName] = [propertyMutation];
30363
+ });
30364
+ const mutation = {
30365
+ type: 'update',
30366
+ squidDocIdObj: parseSquidDocId(this.squidDocId),
30367
+ properties,
30368
+ };
30369
+ return this.dataManager.applyOutgoingMutation(mutation, transactionId);
30370
+ }
30371
+ /**
30372
+ * Similar to {@link update}, but only updates the given path.
30373
+ * @param path The path to update.
30374
+ * @param value The value to set at the given path.
30375
+ * @param transactionId The transaction to use for this operation. If not provided, the operation will be applied
30376
+ * immediately.
30377
+ */
30378
+ async setInPath(path, value, transactionId) {
30379
+ // Not sure why TypeScript doesn't understand that `path` below is restricted, but we need the explicit type
30380
+ // assertion to make it compile.
30381
+ return this.update({ [path]: cloneDeep(value) }, transactionId);
30382
+ }
30383
+ /**
30384
+ * Similar to `update`, but only deletes the given path.
30385
+ * @param path The path to delete.
30386
+ * @param transactionId The transaction to use for this operation. If not provided, the operation will be applied
30387
+ * immediately.
30388
+ */
30389
+ async deleteInPath(path, transactionId) {
30390
+ return this.update({ [path]: undefined }, transactionId);
30391
+ }
30392
+ /**
30393
+ * Increments the value at the given path by the given value. The value may be both positive and negative.
30394
+ * @param path The path to the value to increment.
30395
+ * @param value The value to increment by.
30396
+ * @param transactionId The transaction to use for this operation. If not provided, the operation will be applied
30397
+ * immediately.
30398
+ */
30399
+ incrementInPath(path, value, transactionId) {
30400
+ const propertyMutation = {
30401
+ type: 'applyNumericFn',
30402
+ fn: 'increment',
30403
+ value,
30404
+ };
30405
+ const mutation = {
30406
+ type: 'update',
30407
+ squidDocIdObj: parseSquidDocId(this.squidDocId),
30408
+ properties: {
30409
+ [path]: [propertyMutation],
30410
+ },
30411
+ };
30412
+ return this.dataManager.applyOutgoingMutation(mutation, transactionId);
30413
+ }
30414
+ /**
30415
+ * Decrements the value at the given path by the given value. The value may be both positive and negative.
30416
+ * @param path The path to the value to decrement.
30417
+ * @param value The value to decrement by.
30418
+ * @param transactionId The transaction to use for this operation. If not provided, the operation will be applied
30419
+ * immediately.
30420
+ */
30421
+ decrementInPath(path, value, transactionId) {
30422
+ return this.incrementInPath(path, -value, transactionId);
30423
+ }
30424
+ /**
30425
+ * Inserts the document with the given data. If the document already exists, the operation will be treated as
30426
+ * `upsert`. The `insert` will be reflected optimistically locally and will be applied to the server later. If a
30427
+ * transactionId is provided, the `insert` will be applied to the server as an atomic operation together with the
30428
+ * rest
30429
+ * of the operations in the transaction and the `insert` will not reflect locally until the transaction is completed
30430
+ * locally.
30431
+ *
30432
+ * The returned promise will resolve once the `insert` has been applied to the server or immediately if the `insert`
30433
+ * is part of a transaction.
30434
+ *
30435
+ * @param data The data to insert.
30436
+ * @param transactionId The transaction to use for this operation. If not provided, the operation will be applied
30437
+ * immediately.
30438
+ */
30439
+ async insert(data, transactionId) {
30440
+ const squidDocIdObj = parseSquidDocId(this.squidDocId);
30441
+ const integrationId = squidDocIdObj.integrationId;
30442
+ // Exclude the generated client id from the mutation properties.
30443
+ let docIdProps = deserializeObj(squidDocIdObj.docId);
30444
+ if (docIdProps[SquidPlaceholderId])
30445
+ docIdProps = {};
30446
+ // Destructure composite __ids for the built_in_db.
30447
+ if (integrationId === IntegrationType.built_in_db && docIdProps.__id) {
30448
+ try {
30449
+ const idProps = deserializeObj(docIdProps.__id);
30450
+ docIdProps = Object.assign(Object.assign({}, docIdProps), idProps);
30451
+ }
30452
+ catch (_a) { }
30453
+ }
30454
+ const mutation = {
30455
+ type: 'insert',
30456
+ squidDocIdObj,
30457
+ properties: Object.assign(Object.assign(Object.assign({}, data), { __docId__: squidDocIdObj.docId }), docIdProps),
30458
+ };
30459
+ return this.dataManager.applyOutgoingMutation(mutation, transactionId);
30460
+ }
30461
+ /**
30462
+ * Deletes the document.
30463
+ * The `delete` will be reflected optimistically locally and will be applied to the server later.
30464
+ * If a transactionId is provided, the `delete` will be applied to the server as an atomic operation together with
30465
+ * the rest of the operations in the transaction and the `delete` will not reflect locally until the transaction is
30466
+ * completed locally.
30467
+ *
30468
+ * The returned promise will resolve once the `delete` has been applied to the server or immediately if the `delete`
30469
+ * is part of a transaction.
30470
+ *
30471
+ * @param transactionId The transaction to use for this operation. If not provided, the operation will be applied
30472
+ * immediately.
30473
+ */
30474
+ async delete(transactionId) {
30475
+ const mutation = {
30476
+ type: 'delete',
30477
+ squidDocIdObj: parseSquidDocId(this.squidDocId),
30478
+ };
30479
+ return this.dataManager.applyOutgoingMutation(mutation, transactionId);
30480
+ }
30481
+ /**
30482
+ * @internal
30483
+ */
30484
+ migrateDocIds(idResolutionMap) {
30485
+ const newSquidDocId = idResolutionMap[this.squidDocId];
30486
+ if (newSquidDocId) {
30487
+ this._squidDocId = newSquidDocId;
30488
+ }
30489
+ }
30490
+ hasSquidPlaceholderId() {
30491
+ const obj = deserializeObj(this._squidDocId);
30492
+ if (typeof obj === 'object' && !!obj.docId) {
30493
+ const docIdObj = deserializeObj(obj.docId);
30494
+ if (typeof docIdObj === 'object' && Object.keys(docIdObj).includes(SquidPlaceholderId)) {
30495
+ return true;
30496
+ }
30497
+ }
30498
+ return false;
30499
+ }
30500
+ }
30501
+
30502
+ ;// CONCATENATED MODULE: ../internal-common/src/utils/assert.ts
30503
+
30504
+
30505
+
30506
+ /** @internal */
30507
+ function assertValidateTruthy(value, message, statusCode = HttpStatus.BAD_REQUEST, details) {
30508
+ assertTruthy(value, () => new ValidationError(message, statusCode, details));
30509
+ }
30510
+ /** @internal */
30511
+ function assert_validateTruthy(value, message, statusCode = HttpStatus.BAD_REQUEST, details) {
30512
+ return truthy(value, () => new ValidationError(message, statusCode, details));
30513
+ }
30514
+
30515
+ ;// CONCATENATED MODULE: ../internal-common/src/utils/validation.ts
30516
+ /**
30517
+ * This file contains general validators for the different objects being received from the client. The parameters are
30518
+ * usually of type 'any' to make sure there are no assumptions that the object has the correct type.
30519
+ * Also, this file should avoid importing from other files that are not for validation to avoid circular deps.
30520
+ */
30521
+
30522
+
30523
+
30524
+ class validation_ValidationError extends Error {
30525
+ constructor(error, statusCode, details) {
30526
+ super(error);
30527
+ this.statusCode = statusCode;
30528
+ this.details = details;
30529
+ }
30530
+ }
30531
+ function validatePathPart(part) {
30532
+ if (!part || !part.match(/^[a-zA-Z][a-zA-Z0-9!@#$%^&*~_]{0,49}$/)) {
30533
+ throw new Error('A document id and a collection id can contain only a-z, A-Z, 0-9,!@#$%^&*~_, starting' +
30534
+ 'with a letter, at least one character, and up to 50.');
30535
+ }
30536
+ }
30537
+ function validateCollectionName(collectionName) {
30538
+ if (typeof collectionName !== 'string' || !collectionName) {
30539
+ throw new Error('Collection path has to be a non empty string');
30540
+ }
30541
+ validatePathPart(collectionName);
30542
+ }
30543
+ function validateFieldName(fieldName) {
30544
+ if (!fieldName || typeof fieldName !== 'string') {
30545
+ throw new Error('Field name has to be a non-empty string');
30546
+ }
30547
+ if (fieldName === '__docId__') {
30548
+ // __docId__ is the only valid fieldName that can start with '_'
30549
+ return;
30550
+ }
30551
+ if (!fieldName.match(/^[a-zA-Z_$][a-zA-Z0-9!@#$%^&*~_ ]{0,49}$/)) {
30552
+ throw new Error('A Field name can contain only a-z, A-Z, 0-9,!@#$%^&*~_, starting with a letter, at least one character, and up to 50. Field name: ' +
30553
+ fieldName);
30554
+ }
30555
+ }
30556
+ function isValidId(id) {
30557
+ return typeof id === 'string' && !!id && id.length <= 50 && /^[a-zA-Z0-9_-]+$/.test(id);
30558
+ }
30559
+ function validateId(id, message) {
30560
+ validateTruthy(isValidId(id), message, HttpStatus.BAD_REQUEST);
30561
+ }
30562
+ function validateFieldSort(fieldSort) {
30563
+ if (!(fieldSort instanceof Object)) {
30564
+ throw new Error('Field sort has to be an object');
30565
+ }
30566
+ const safeFieldSort = fieldSort;
30567
+ (0,dist.assertTruthy)(hasOnlyKeys(safeFieldSort, ['fieldName', 'asc']), 'Field sort should only contain a fieldName and asc');
30568
+ (0,dist.assertTruthy)(isRightType(safeFieldSort.asc, 'boolean'), 'Asc needs to be boolean');
30569
+ validateFieldName(safeFieldSort.fieldName);
30570
+ }
30571
+ /** @internal */
30572
+ function validateOpenIdProvider(openIdProvider) {
30573
+ assertTruthy(openIdProvider, 'INVALID_PROVIDER');
30574
+ validateOpenIdProviderType(openIdProvider.providerType);
30575
+ assertTruthy(openIdProvider.providerType, 'INVALID_CLIENT_ID');
30576
+ assertTruthy(openIdProvider.clientId, 'INVALID_CLIENT_ID');
30577
+ assertTruthy(openIdProvider.domain, 'INVALID_DOMAIN');
30578
+ return openIdProvider;
30579
+ }
30580
+ /** @internal */
30581
+ function validateOpenIdProviderType(providerType) {
30582
+ const providerArray = ['auth0'];
30583
+ assertTruthy(providerArray.includes(providerType), 'INVALID_OPEN_ID_PROVIDER_TYPE');
30584
+ }
30585
+ function validateDeleteMutation(mutation) {
30586
+ if (!mutation || mutation.type !== 'delete') {
30587
+ throw new Error('Mutation has to be non empty with type delete.');
30588
+ }
30589
+ // Not much to validate for delete.
30590
+ }
30591
+ function validateInsertMutation(mutation) {
30592
+ if (!mutation || mutation.type !== 'insert') {
30593
+ throw new Error('Mutation has to be non empty with type insert.');
30594
+ }
30595
+ if (!mutation.properties || typeof mutation.properties !== 'object') {
30596
+ throw new Error('The properties in insert mutation need to be a JSON object.');
30597
+ }
30598
+ for (const [fieldName] of Object.entries(mutation.properties)) {
30599
+ validateFieldName(fieldName);
30600
+ // TODO - figure out how to validate the value
30601
+ }
30602
+ }
30603
+ function validateUpdatePropertyMutation(propertyMutation) {
30604
+ if (!propertyMutation || propertyMutation.type !== 'update') {
30605
+ throw new Error('Update value property mutation has to be of type update');
30606
+ }
30607
+ if (propertyMutation.value === undefined) {
30608
+ throw new Error('Value has to exist in an update value property mutation..');
30609
+ }
30610
+ }
30611
+ function validateApplyNumericFnPropertyMutation(propertyMutation) {
30612
+ if (!propertyMutation || propertyMutation.type !== 'applyNumericFn') {
30613
+ throw new Error('Apply numeric fn mutation has to be of type applyNumericFn');
30614
+ }
30615
+ if (!['increment'].includes(propertyMutation.fn)) {
30616
+ throw new Error('Invalid fn for apply numeric fn.');
30617
+ }
30618
+ if (typeof propertyMutation.value !== 'number') {
30619
+ throw new Error('The value in an apply numeric fn function has to be numeric.');
30620
+ }
30621
+ }
30622
+ function validateApplyStringFnPropertyMutation(propertyMutation) {
30623
+ if (!propertyMutation || propertyMutation.type !== 'applyStringFn') {
30624
+ throw new Error('Apply string fn mutation has to be of type applyStringFn');
30625
+ }
30626
+ if (!['trim', 'extendString'].includes(propertyMutation.fn)) {
30627
+ throw new Error('Invalid fn for apply string fn.');
30628
+ }
30629
+ if (typeof propertyMutation.value !== 'string') {
30630
+ throw new Error('The value in an apply string fn function has to be a string.');
30631
+ }
30632
+ }
30633
+ function validatePropertyMutation(propertyMutation) {
30634
+ if (!propertyMutation || typeof propertyMutation !== 'object') {
30635
+ throw new Error('Property mutation need to be a JSON object.');
30636
+ }
30637
+ if (!['update', 'applyNumericFn', 'applyStringFn'].includes(propertyMutation.type)) {
30638
+ throw new Error(`Property mutation can be of type 'update', 'applyNumericFn', 'applyStringFn'`);
30639
+ }
30640
+ switch (propertyMutation.type) {
30641
+ case 'update':
30642
+ validateUpdatePropertyMutation(propertyMutation);
30643
+ break;
30644
+ case 'applyNumericFn':
30645
+ validateApplyNumericFnPropertyMutation(propertyMutation);
30646
+ break;
30647
+ case 'applyStringFn':
30648
+ validateApplyStringFnPropertyMutation(propertyMutation);
30649
+ break;
30650
+ }
30651
+ }
30652
+ function validateUpdateMutation(mutation) {
30653
+ if (!mutation || mutation.type !== 'update') {
30654
+ throw new Error('Mutation has to be non empty with type update.');
30655
+ }
30656
+ if (!mutation.properties || typeof mutation.properties !== 'object') {
30657
+ throw new Error('The properties in update mutation need to be a JSON object.');
30658
+ }
30659
+ const entries = Object.entries(mutation.properties);
30660
+ for (const [fieldName, propertyMutations] of entries) {
30661
+ validateFieldName(fieldName);
30662
+ for (const propertyMutation of propertyMutations) {
30663
+ validatePropertyMutation(propertyMutation);
30664
+ }
30665
+ }
30666
+ }
30667
+ function validateMutation(mutation) {
30668
+ if (!mutation) {
30669
+ throw new Error('Mutation cannot be empty');
30670
+ }
30671
+ if (!['insert', 'delete', 'update'].includes(mutation.type)) {
30672
+ throw new Error(`Mutation type has to be one of 'insert', 'delete', or 'update'`);
30673
+ }
30674
+ validateCollectionName(mutation.squidDocIdObj.collectionName);
30675
+ validatePathPart(mutation.squidDocIdObj.docId);
30676
+ switch (mutation.type) {
30677
+ case 'delete':
30678
+ validateDeleteMutation(mutation);
30679
+ break;
30680
+ case 'insert':
30681
+ validateInsertMutation(mutation);
30682
+ break;
30683
+ case 'update':
30684
+ validateUpdateMutation(mutation);
30685
+ break;
30686
+ }
30687
+ }
30688
+ function validateMutations(mutations) {
30689
+ if (!mutations || !(mutations instanceof Array) || !mutations.length) {
30690
+ throw new Error('The list of mutations has to be a non-empty array.');
30691
+ }
30692
+ for (const mutation of mutations) {
30693
+ validateMutation(mutation);
30694
+ }
30695
+ }
30696
+ function validateQueryLimit(limit) {
30697
+ (0,dist.assertNumber)(limit, 'Limit needs to be a number');
30698
+ if (limit === -1)
30699
+ return;
30700
+ (0,dist.assertTruthy)(limit > 0, 'query limit has to be greater than 0');
30701
+ (0,dist.assertTruthy)(Math.floor(limit) === limit, 'query limit has to be an integer');
30702
+ (0,dist.assertTruthy)(limit <= 20000, 'Limit can be maximum 20000');
30703
+ }
30704
+ /** Returns true if the value is not an empty string (undefined/null are considered empty). */
30705
+ function isNotEmpty(value) {
30706
+ validateCorrectStringType(value);
30707
+ return typeof value === 'string' && !!value;
30708
+ }
30709
+ /**
30710
+ * TODO: deprecated: this method is used in both validate...() and is...() methods.
30711
+ * The validate...() must throw an error, while is...() must not.
30712
+ */
30713
+ function validateCorrectStringType(value) {
30714
+ if (value !== null && value !== undefined && typeof value !== 'string') {
30715
+ throw new Error(`Unexpected input type ${typeof value}`);
30716
+ }
30717
+ }
30718
+ /** Returns true if 'typeof' of the 'value' is 'type' or 'type[]'. */
30719
+ function isRightType(value, type) {
30720
+ // TODO: the method is ambiguous when the value is an empty array and will return 'true' for any type.
30721
+ if (Array.isArray(value)) {
30722
+ return value.every(element => typeof element === type);
30723
+ }
30724
+ return typeof value === type;
30725
+ }
30726
+ /** Returns true if 'obj' has only keys listed in the 'keys'. Object can't be an array. */
30727
+ function hasOnlyKeys(obj, keys) {
30728
+ return !Array.isArray(obj) && [...Object.keys(obj)].every(key => keys.includes(key));
30729
+ }
30730
+
30731
+ ;// CONCATENATED MODULE: ./src/query/pagination.ts
30732
+
30733
+
30734
+
30735
+ class Pagination {
30736
+ /** @internal */
30737
+ constructor(snapshotEmitter, options = {}) {
30738
+ /* Invariants:
30739
+ internalStateObserver.value.data
30740
+ - always contains the pageSize elements that are on the page (fewer if we're on the last page)
30741
+ - always contains some elements before the first one on the page and some elements after
30742
+ the last one on the page, unless at the beginning/end.
30743
+ Note: it is possible that all the elements before/after the page get deleted. Our current strategy is to grab
30744
+ a full page of them and hope they don't all get deleted. An alternative strategy would be to check in dataReceived
30745
+ if numBefore and numAfter are getting low, and rerun the query in that case. However, in order to do that we would need to be
30746
+ able to check if we are at the next-to-last page, because numAfter can be as low as 1 in that case, and similarly for numBefore.
30747
+
30748
+ firstElement
30749
+ - is an extracted document that may or may not be part of the data
30750
+ - the page consists of the first pageSize elements of data that are >= firstElement
30751
+ - firstElement === null refers to a hypothetical document that is ordered before all other documents.
30452
30752
  */
30453
30753
  this.internalStateObserver = new external_rxjs_.BehaviorSubject(null);
30454
30754
  this.firstElement = null;
@@ -31526,6 +31826,7 @@ class GroupedJoin {
31526
31826
 
31527
31827
 
31528
31828
 
31829
+
31529
31830
  /**
31530
31831
  * Holds a reference to a data collection. A collection reference is a reference to a collection in a database. You
31531
31832
  * can use it to read or write data to the collection. A collection can refer to a table in a relational database or a
@@ -31539,12 +31840,13 @@ class CollectionReference {
31539
31840
  /**
31540
31841
  * @internal
31541
31842
  */
31542
- constructor(collectionName, integrationId, documentReferenceFactory, queryBuilderFactory, querySubscriptionManager) {
31843
+ constructor(collectionName, integrationId, documentReferenceFactory, queryBuilderFactory, querySubscriptionManager, dataManager) {
31543
31844
  this.collectionName = collectionName;
31544
31845
  this.integrationId = integrationId;
31545
31846
  this.documentReferenceFactory = documentReferenceFactory;
31546
31847
  this.queryBuilderFactory = queryBuilderFactory;
31547
31848
  this.querySubscriptionManager = querySubscriptionManager;
31849
+ this.dataManager = dataManager;
31548
31850
  this.refId = generateId();
31549
31851
  }
31550
31852
  /**
@@ -31602,6 +31904,31 @@ class CollectionReference {
31602
31904
  const squidDocId = getSquidDocId(docIdAsJsonString, this.collectionName, this.integrationId);
31603
31905
  return this.documentReferenceFactory.create(squidDocId, this.queryBuilderFactory);
31604
31906
  }
31907
+ /**
31908
+ * Inserts multiple documents into the collection.
31909
+ */
31910
+ async insertMany(docs, txId) {
31911
+ await this.dataManager.runInTransaction(async (innerTxId) => {
31912
+ for (const doc of docs) {
31913
+ await this.doc(doc.id).insert(doc.data, innerTxId);
31914
+ }
31915
+ }, txId);
31916
+ }
31917
+ /**
31918
+ * Deletes multiple documents from the collection.
31919
+ */
31920
+ async deleteMany(docIdsOrReferences, txId) {
31921
+ await this.dataManager.runInTransaction(async (innerTxId) => {
31922
+ for (const idOrRef of docIdsOrReferences) {
31923
+ if (idOrRef instanceof DocumentReference) {
31924
+ await idOrRef.delete(innerTxId);
31925
+ }
31926
+ else {
31927
+ await this.doc(idOrRef).delete(innerTxId);
31928
+ }
31929
+ }
31930
+ }, txId);
31931
+ }
31605
31932
  /**
31606
31933
  * Creates a `QueryBuilder` that can be used to query the collection.
31607
31934
  *
@@ -31748,10 +32075,11 @@ class MergedQueryBuilder {
31748
32075
 
31749
32076
  /** @internal */
31750
32077
  class CollectionReferenceFactory {
31751
- constructor(documentReferenceFactory, queryBuilderFactory, querySubscriptionManager) {
32078
+ constructor(documentReferenceFactory, queryBuilderFactory, querySubscriptionManager, dataManager) {
31752
32079
  this.documentReferenceFactory = documentReferenceFactory;
31753
32080
  this.queryBuilderFactory = queryBuilderFactory;
31754
32081
  this.querySubscriptionManager = querySubscriptionManager;
32082
+ this.dataManager = dataManager;
31755
32083
  this.collections = new Map();
31756
32084
  }
31757
32085
  get(collectionName, integrationId) {
@@ -31762,7 +32090,7 @@ class CollectionReferenceFactory {
31762
32090
  }
31763
32091
  let collectionRef = integrationCollectionMap.get(collectionName);
31764
32092
  if (!collectionRef) {
31765
- collectionRef = new CollectionReference(collectionName, integrationId, this.documentReferenceFactory, this.queryBuilderFactory, this.querySubscriptionManager);
32093
+ collectionRef = new CollectionReference(collectionName, integrationId, this.documentReferenceFactory, this.queryBuilderFactory, this.querySubscriptionManager, this.dataManager);
31766
32094
  integrationCollectionMap.set(collectionName, collectionRef);
31767
32095
  }
31768
32096
  return collectionRef;
@@ -32565,537 +32893,248 @@ class DataManager {
32565
32893
  }
32566
32894
  return Object.entries(outgoingMutationsByIntegrationId);
32567
32895
  }
32568
- /**
32569
- * Handles the case that due to some change (an incoming or outgoing change to a document), a document becomes orphan.
32570
- * That is, there are no ongoing queries that will keep it up-to-date.
32571
- * An orphan document should not stay locally since it may be stale after some time.
32572
- */
32573
- handleOrphanDocs() {
32574
- this.querySubscriptionManager.onOrphanDocuments.subscribe(orphanDocs => {
32575
- for (const squidDocId of orphanDocs) {
32576
- if (!this.isTracked(squidDocId)) {
32577
- this.forgetDocument(squidDocId);
32578
- }
32579
- }
32580
- });
32581
- }
32582
- acknowledgeDocument(squidDocId, timestamp, expires = false) {
32583
- this.docIdToServerTimestamp.set(squidDocId, { timestamp });
32584
- this.setExpiration(squidDocId, expires);
32585
- }
32586
- setExpiration(squidDocId, expires) {
32587
- const docTimestamp = this.docIdToServerTimestamp.get(squidDocId);
32588
- if (docTimestamp) {
32589
- docTimestamp.expireTimestamp = expires ? Date.now() + 20000 : undefined;
32590
- }
32591
- }
32592
- forgetDocument(squidDocId) {
32593
- this.docIdToLocalTimestamp.delete(squidDocId);
32594
- /**
32595
- * Keep the document timestamp for a bit more just to make sure there is nothing in-flight that will be accepted
32596
- * with the wrong timestamp.
32597
- */
32598
- this.setExpiration(squidDocId, true);
32599
- }
32600
- migrateDocIds(idResolutionMap) {
32601
- this.pendingOutgoingMutations.forEach(outgoingMutations => {
32602
- outgoingMutations.forEach(outgoingMutation => {
32603
- const squidDocId = getSquidDocId(outgoingMutation.mutation.squidDocIdObj);
32604
- const resolvedId = idResolutionMap[squidDocId];
32605
- if (resolvedId) {
32606
- outgoingMutation.mutation.squidDocIdObj = parseSquidDocId(resolvedId);
32607
- }
32608
- });
32609
- });
32610
- Object.entries(idResolutionMap).forEach(([squidDocId, newSquidDocId]) => {
32611
- replaceKeyInMap(this.pendingOutgoingMutations, squidDocId, newSquidDocId);
32612
- replaceKeyInMap(this.docIdToLocalTimestamp, squidDocId, newSquidDocId);
32613
- replaceKeyInMap(this.docIdToServerTimestamp, squidDocId, newSquidDocId);
32614
- });
32615
- }
32616
- hasPendingSentMutations() {
32617
- for (const outgoingMutationsForDoc of this.pendingOutgoingMutations.values()) {
32618
- for (const outgoingMutation of outgoingMutationsForDoc) {
32619
- if (outgoingMutation.sentToServer) {
32620
- return true;
32621
- }
32622
- }
32623
- }
32624
- return false;
32625
- }
32626
- }
32627
- /** A constant used to differentiate undefined values provided by user from the real undefined variable state. */
32628
- const UNSET_VALUE = Symbol('undefined');
32629
-
32630
- ;// CONCATENATED MODULE: ./src/destruct.manager.ts
32631
-
32632
-
32633
- class DestructManager {
32634
- constructor() {
32635
- this.preDestructors = [];
32636
- this.destructors = [];
32637
- this.isDestructedSubject = new external_rxjs_.BehaviorSubject(false);
32638
- }
32639
- get isDestructing() {
32640
- return this.isDestructedSubject.value;
32641
- }
32642
- observeIsDestructing() {
32643
- return this.isDestructedSubject.asObservable().pipe((0,external_rxjs_.filter)(Boolean), map(() => undefined));
32644
- }
32645
- onPreDestruct(fn) {
32646
- this.preDestructors.push(fn);
32647
- }
32648
- onDestruct(fn) {
32649
- this.destructors.push(fn);
32650
- }
32651
- async destruct() {
32652
- this.reportDestructed();
32653
- const fns = this.preDestructors.concat(this.destructors);
32654
- let fn = fns.shift();
32655
- while (fn) {
32656
- try {
32657
- await fn();
32658
- }
32659
- catch (e) {
32660
- console.error('Error while destructing Squid', e);
32661
- }
32662
- fn = fns.shift();
32663
- }
32664
- }
32665
- reportDestructed() {
32666
- if (this.isDestructing) {
32667
- return;
32668
- }
32669
- this.isDestructedSubject.next(true);
32670
- }
32671
- }
32672
-
32673
- ;// CONCATENATED MODULE: ./src/distributed-lock.manager.ts
32674
-
32675
-
32676
-
32677
- /**
32678
- * @internal
32679
- */
32680
- class DistributedLockManager {
32681
- constructor(socketManager, destructManager) {
32682
- this.socketManager = socketManager;
32683
- this.destructManager = destructManager;
32684
- this.ongoingLocks = {};
32685
- this.acquireLockMessagesFromServer = this.socketManager
32686
- .observeNotifications()
32687
- .pipe((0,external_rxjs_.filter)(message => message.type === 'lockAcquired'));
32688
- this.releaseLockMessagesFromServer = this.socketManager
32689
- .observeNotifications()
32690
- .pipe((0,external_rxjs_.filter)(message => message.type === 'lockReleased'));
32691
- // we may override this value in tests
32692
- this.lockWaitForConnectionThreshold = 2000;
32693
- destructManager.onPreDestruct(() => {
32694
- this.releaseAllLocks();
32695
- });
32696
- this.socketManager.observeConnectionReady().subscribe(ready => {
32697
- if (ready)
32698
- return;
32699
- this.releaseAllLocks();
32700
- });
32701
- this.releaseLockMessagesFromServer.subscribe(message => {
32702
- const lock = this.ongoingLocks[message.payload.clientRequestId];
32703
- if (lock === undefined)
32704
- return;
32705
- lock.release();
32706
- });
32707
- }
32708
- async lock(mutex) {
32709
- /**
32710
- * Wait up to 2 seconds for the connection to be ready and if it is not, consider the client to be not connected.
32711
- * This is useful because right after the Squid client is created, the socket is still not connected but the user
32712
- * may want to acquire a lock; it is a valid situation. Also, there may be short disconnect and in this case we
32713
- * still want to wait for the connection to be ready.
32714
- */
32715
- const isConnected = await (0,external_rxjs_.firstValueFrom)((0,external_rxjs_.race)((0,external_rxjs_.timer)(this.lockWaitForConnectionThreshold).pipe(map(() => false)), this.socketManager.observeConnectionReady().pipe((0,external_rxjs_.filter)(Boolean)), this.destructManager.observeIsDestructing()));
32716
- if (!isConnected) {
32717
- return Promise.reject('CLIENT_NOT_CONNECTED');
32718
- }
32719
- const clientRequestId = generateId();
32720
- const acquireLockMessage = {
32721
- type: 'acquireLock',
32722
- payload: {
32723
- mutex,
32724
- clientRequestId,
32725
- },
32726
- };
32727
- this.socketManager.sendMessage(acquireLockMessage);
32728
- const result = await (0,external_rxjs_.firstValueFrom)((0,external_rxjs_.race)((0,external_rxjs_.timer)(5000).pipe((0,external_rxjs_.take)(1), map(() => {
32729
- return {
32730
- payload: {
32731
- error: 'TIMEOUT_GETTING_LOCK',
32732
- lockId: undefined,
32733
- },
32734
- };
32735
- })), this.acquireLockMessagesFromServer.pipe((0,external_rxjs_.filter)(message => message.payload.clientRequestId === clientRequestId))));
32736
- if (this.destructManager.isDestructing) {
32737
- throw new Error('Destructing');
32738
- }
32739
- if (!result.payload.lockId) {
32740
- throw new Error(`Failed to acquire lock: ${result.payload.error}`);
32741
- }
32742
- const lockId = result.payload.lockId;
32743
- const lock = new DistributedLockImpl(lockId, clientRequestId, this.ongoingLocks, this.socketManager);
32744
- this.ongoingLocks[lockId] = lock;
32745
- return lock;
32746
- }
32747
- releaseAllLocks() {
32748
- for (const [lockId, lock] of Object.entries(this.ongoingLocks)) {
32749
- lock.release();
32750
- delete this.ongoingLocks[lockId];
32751
- }
32752
- }
32753
- }
32754
- /**
32755
- * @internal
32756
- */
32757
- class DistributedLockImpl {
32758
- constructor(lockId, clientRequestId, ongoingLocks, socketManager) {
32759
- this.lockId = lockId;
32760
- this.clientRequestId = clientRequestId;
32761
- this.ongoingLocks = ongoingLocks;
32762
- this.socketManager = socketManager;
32763
- this.released = false;
32764
- this.onReleaseSubject = new external_rxjs_.Subject();
32896
+ /**
32897
+ * Handles the case that due to some change (an incoming or outgoing change to a document), a document becomes orphan.
32898
+ * That is, there are no ongoing queries that will keep it up-to-date.
32899
+ * An orphan document should not stay locally since it may be stale after some time.
32900
+ */
32901
+ handleOrphanDocs() {
32902
+ this.querySubscriptionManager.onOrphanDocuments.subscribe(orphanDocs => {
32903
+ for (const squidDocId of orphanDocs) {
32904
+ if (!this.isTracked(squidDocId)) {
32905
+ this.forgetDocument(squidDocId);
32906
+ }
32907
+ }
32908
+ });
32765
32909
  }
32766
- release() {
32767
- if (this.released)
32768
- return;
32769
- this.released = true;
32770
- delete this.ongoingLocks[this.lockId];
32771
- const releaseLockMessage = {
32772
- type: 'releaseLock',
32773
- payload: {
32774
- lockId: this.lockId,
32775
- clientRequestId: this.clientRequestId,
32776
- },
32777
- };
32778
- this.socketManager.sendMessage(releaseLockMessage);
32779
- this.onReleaseSubject.next();
32910
+ acknowledgeDocument(squidDocId, timestamp, expires = false) {
32911
+ this.docIdToServerTimestamp.set(squidDocId, { timestamp });
32912
+ this.setExpiration(squidDocId, expires);
32780
32913
  }
32781
- observeRelease() {
32782
- return this.onReleaseSubject.asObservable();
32914
+ setExpiration(squidDocId, expires) {
32915
+ const docTimestamp = this.docIdToServerTimestamp.get(squidDocId);
32916
+ if (docTimestamp) {
32917
+ docTimestamp.expireTimestamp = expires ? Date.now() + 20000 : undefined;
32918
+ }
32783
32919
  }
32784
- isReleased() {
32785
- return this.released;
32920
+ forgetDocument(squidDocId) {
32921
+ this.docIdToLocalTimestamp.delete(squidDocId);
32922
+ /**
32923
+ * Keep the document timestamp for a bit more just to make sure there is nothing in-flight that will be accepted
32924
+ * with the wrong timestamp.
32925
+ */
32926
+ this.setExpiration(squidDocId, true);
32786
32927
  }
32787
- }
32788
-
32789
- ;// CONCATENATED MODULE: ./src/document-identity.service.ts
32790
-
32791
- /** @internal */
32792
- class DocumentIdentityService {
32793
- constructor(documentStore, destructManager) {
32794
- this.documentStore = documentStore;
32795
- this.destructManager = destructManager;
32796
- this.changeNotifier = new external_rxjs_.BehaviorSubject({});
32797
- this.destructManager.onDestruct(() => {
32798
- this.changeNotifier.complete();
32928
+ migrateDocIds(idResolutionMap) {
32929
+ this.pendingOutgoingMutations.forEach(outgoingMutations => {
32930
+ outgoingMutations.forEach(outgoingMutation => {
32931
+ const squidDocId = getSquidDocId(outgoingMutation.mutation.squidDocIdObj);
32932
+ const resolvedId = idResolutionMap[squidDocId];
32933
+ if (resolvedId) {
32934
+ outgoingMutation.mutation.squidDocIdObj = parseSquidDocId(resolvedId);
32935
+ }
32936
+ });
32799
32937
  });
32800
- }
32801
- migrate(idResolutionMap) {
32802
- // Update the document in the database before notifying any other subscribers.
32803
32938
  Object.entries(idResolutionMap).forEach(([squidDocId, newSquidDocId]) => {
32804
- this.documentStore.migrateDocId(squidDocId, newSquidDocId);
32939
+ replaceKeyInMap(this.pendingOutgoingMutations, squidDocId, newSquidDocId);
32940
+ replaceKeyInMap(this.docIdToLocalTimestamp, squidDocId, newSquidDocId);
32941
+ replaceKeyInMap(this.docIdToServerTimestamp, squidDocId, newSquidDocId);
32805
32942
  });
32806
- this.changeNotifier.next(idResolutionMap);
32807
32943
  }
32808
- observeChanges() {
32809
- return this.changeNotifier.asObservable();
32944
+ hasPendingSentMutations() {
32945
+ for (const outgoingMutationsForDoc of this.pendingOutgoingMutations.values()) {
32946
+ for (const outgoingMutation of outgoingMutationsForDoc) {
32947
+ if (outgoingMutation.sentToServer) {
32948
+ return true;
32949
+ }
32950
+ }
32951
+ }
32952
+ return false;
32810
32953
  }
32811
32954
  }
32955
+ /** A constant used to differentiate undefined values provided by user from the real undefined variable state. */
32956
+ const UNSET_VALUE = Symbol('undefined');
32812
32957
 
32813
- ;// CONCATENATED MODULE: ./src/document-reference.ts
32814
-
32815
-
32816
-
32817
-
32818
-
32958
+ ;// CONCATENATED MODULE: ./src/destruct.manager.ts
32819
32959
 
32820
32960
 
32821
- /**
32822
- * Holds a reference to a document. A document reference is a reference to a specific record in a collection. You can
32823
- * use it to read or write data to the document. A document can refer to a row in a table in a relational database or a
32824
- * document in a NoSQL database. Additionally, a document reference can refer to a non-existent document, which you can
32825
- * use to create a new document.
32826
- *
32827
- * Read more about document references in the
32828
- * {@link https://docs.squid.cloud/docs/development-tools/client-sdk/document-reference documentation}.
32829
- * @typeParam T The type of the document data.
32830
- */
32831
- class DocumentReference {
32832
- /**
32833
- * @internal
32834
- */
32835
- constructor(_squidDocId, dataManager, queryBuilderFactory) {
32836
- this._squidDocId = _squidDocId;
32837
- this.dataManager = dataManager;
32838
- this.queryBuilderFactory = queryBuilderFactory;
32839
- this.refId = generateId();
32961
+ class DestructManager {
32962
+ constructor() {
32963
+ this.preDestructors = [];
32964
+ this.destructors = [];
32965
+ this.isDestructedSubject = new external_rxjs_.BehaviorSubject(false);
32840
32966
  }
32841
- /**
32842
- * @internal
32843
- */
32844
- get squidDocId() {
32845
- return this._squidDocId;
32967
+ get isDestructing() {
32968
+ return this.isDestructedSubject.value;
32846
32969
  }
32847
- /**
32848
- * Returns the document data. Throws an error if the document does not exist.
32849
- *
32850
- * @returns The document data.
32851
- * @throws Error if the document does not exist.
32852
- */
32853
- get data() {
32854
- return cloneDeep(this.dataRef);
32970
+ observeIsDestructing() {
32971
+ return this.isDestructedSubject.asObservable().pipe((0,external_rxjs_.filter)(Boolean), map(() => undefined));
32855
32972
  }
32856
- /**
32857
- * Returns a read-only internal copy of the document data. This works similar to `this.data`, except it does not
32858
- * perform a defensive copy. The caller may not modify this object, on penalty of unexpected behavior.
32859
- *
32860
- * @returns The document data.
32861
- * @throws Error if the document does not exist.
32862
- */
32863
- get dataRef() {
32864
- const getError = () => {
32865
- const { collectionName, integrationId, docId } = parseSquidDocId(this.squidDocId);
32866
- return `No data found for document reference: ${JSON.stringify({
32867
- docId,
32868
- collectionName,
32869
- integrationId,
32870
- }, null, 2)}`;
32871
- };
32872
- return (0,dist.truthy)(this.dataManager.getProperties(this.squidDocId), getError);
32973
+ onPreDestruct(fn) {
32974
+ this.preDestructors.push(fn);
32873
32975
  }
32874
- /**
32875
- * Returns whether data has been populated for this document reference. Data
32876
- * will not present if a document has not been queried, does not exist, or
32877
- * has been deleted.
32878
- *
32879
- * @returns Whether the document has data.
32880
- */
32881
- get hasData() {
32882
- return !!this.dataManager.getProperties(this.squidDocId);
32976
+ onDestruct(fn) {
32977
+ this.destructors.push(fn);
32883
32978
  }
32884
- /**
32885
- * A promise that resolves with the latest data from the server or undefined if the document does not exist on the
32886
- * server.
32887
- *
32888
- * @returns A promise that resolves with latest data from the server or undefined if the document does not exist on
32889
- * the server.
32890
- */
32891
- async snapshot() {
32892
- if (this.hasSquidPlaceholderId()) {
32893
- throw new Error('Cannot invoke snapshot of a document that was created locally without storing it on the server.');
32979
+ async destruct() {
32980
+ this.reportDestructed();
32981
+ const fns = this.preDestructors.concat(this.destructors);
32982
+ let fn = fns.shift();
32983
+ while (fn) {
32984
+ try {
32985
+ await fn();
32986
+ }
32987
+ catch (e) {
32988
+ console.error('Error while destructing Squid', e);
32989
+ }
32990
+ fn = fns.shift();
32894
32991
  }
32895
- if (this.isTracked() && this.hasData)
32896
- return this.data;
32897
- const results = await this.queryBuilderFactory.getForDocument(this.squidDocId).dereference().snapshot();
32898
- (0,dist.truthy)(results.length <= 1, 'Got more than one doc for the same id:' + this.squidDocId);
32899
- return results.length ? results[0] : undefined;
32900
- }
32901
- /**
32902
- * Returns an observable that emits the latest data from the server or undefined if the document is deleted or does
32903
- * not exist on the server.
32904
- *
32905
- * @returns An observable that emits the latest data from the server or undefined if the document is deleted or does
32906
- * not exist on the server.
32907
- */
32908
- snapshots() {
32909
- return this.queryBuilderFactory
32910
- .getForDocument(this.squidDocId)
32911
- .dereference()
32912
- .snapshots()
32913
- .pipe((0,external_rxjs_.map)(results => {
32914
- (0,dist.truthy)(results.length <= 1, 'Got more than one doc for the same id:' + this.squidDocId);
32915
- return results.length ? results[0] : undefined;
32916
- }));
32917
- }
32918
- /**
32919
- * Returns the data that is currently available on the client or undefined if data has not yet been populated.
32920
- *
32921
- * @returns The data that is currently available on the client or undefined if data has not yet been populated.
32922
- */
32923
- peek() {
32924
- return this.isTracked() && this.hasData ? this.data : undefined;
32925
- }
32926
- /**
32927
- * Returns whether the locally available version of the document may not be the latest version on the server.
32928
- *
32929
- * @returns Whether the locally available version of the document may not be the latest version on the server.
32930
- */
32931
- isDirty() {
32932
- return this.dataManager.isDirty(this.squidDocId);
32933
- }
32934
- isTracked() {
32935
- return this.dataManager.isTracked(this.squidDocId);
32936
32992
  }
32937
- /**
32938
- * Updates the document with the given data.
32939
- * The `update` will be reflected optimistically locally and will be applied to the server later.
32940
- * If a transactionId is provided, the `update` will be applied to the server as an atomic operation together with
32941
- * the rest of the operations in the transaction and the `update` will not reflect locally until the transaction is
32942
- * completed locally.
32943
- *
32944
- * The returned promise will resolve once the `update` has been applied to the server or immediately if the `update`
32945
- * is part of a transaction.
32946
- *
32947
- * @param data The data to update - can be partial.
32948
- * @param transactionId The transaction to use for this operation. If not provided, the operation will be applied
32949
- * immediately.
32950
- */
32951
- async update(data, transactionId) {
32952
- const properties = {};
32953
- Object.entries(data).forEach(([fieldName, value]) => {
32954
- const propertyMutation = value !== undefined
32955
- ? {
32956
- type: 'update',
32957
- value: value,
32958
- }
32959
- : {
32960
- type: 'removeProperty',
32961
- };
32962
- properties[fieldName] = [propertyMutation];
32993
+ reportDestructed() {
32994
+ if (this.isDestructing) {
32995
+ return;
32996
+ }
32997
+ this.isDestructedSubject.next(true);
32998
+ }
32999
+ }
33000
+
33001
+ ;// CONCATENATED MODULE: ./src/distributed-lock.manager.ts
33002
+
33003
+
33004
+
33005
+ /**
33006
+ * @internal
33007
+ */
33008
+ class DistributedLockManager {
33009
+ constructor(socketManager, destructManager) {
33010
+ this.socketManager = socketManager;
33011
+ this.destructManager = destructManager;
33012
+ this.ongoingLocks = {};
33013
+ this.acquireLockMessagesFromServer = this.socketManager
33014
+ .observeNotifications()
33015
+ .pipe((0,external_rxjs_.filter)(message => message.type === 'lockAcquired'));
33016
+ this.releaseLockMessagesFromServer = this.socketManager
33017
+ .observeNotifications()
33018
+ .pipe((0,external_rxjs_.filter)(message => message.type === 'lockReleased'));
33019
+ // we may override this value in tests
33020
+ this.lockWaitForConnectionThreshold = 2000;
33021
+ destructManager.onPreDestruct(() => {
33022
+ this.releaseAllLocks();
32963
33023
  });
32964
- const mutation = {
32965
- type: 'update',
32966
- squidDocIdObj: parseSquidDocId(this.squidDocId),
32967
- properties,
33024
+ this.socketManager.observeConnectionReady().subscribe(ready => {
33025
+ if (ready)
33026
+ return;
33027
+ this.releaseAllLocks();
33028
+ });
33029
+ this.releaseLockMessagesFromServer.subscribe(message => {
33030
+ const lock = this.ongoingLocks[message.payload.clientRequestId];
33031
+ if (lock === undefined)
33032
+ return;
33033
+ lock.release();
33034
+ });
33035
+ }
33036
+ async lock(mutex) {
33037
+ /**
33038
+ * Wait up to 2 seconds for the connection to be ready and if it is not, consider the client to be not connected.
33039
+ * This is useful because right after the Squid client is created, the socket is still not connected but the user
33040
+ * may want to acquire a lock; it is a valid situation. Also, there may be short disconnect and in this case we
33041
+ * still want to wait for the connection to be ready.
33042
+ */
33043
+ const isConnected = await (0,external_rxjs_.firstValueFrom)((0,external_rxjs_.race)((0,external_rxjs_.timer)(this.lockWaitForConnectionThreshold).pipe(map(() => false)), this.socketManager.observeConnectionReady().pipe((0,external_rxjs_.filter)(Boolean)), this.destructManager.observeIsDestructing()));
33044
+ if (!isConnected) {
33045
+ return Promise.reject('CLIENT_NOT_CONNECTED');
33046
+ }
33047
+ const clientRequestId = generateId();
33048
+ const acquireLockMessage = {
33049
+ type: 'acquireLock',
33050
+ payload: {
33051
+ mutex,
33052
+ clientRequestId,
33053
+ },
32968
33054
  };
32969
- return this.dataManager.applyOutgoingMutation(mutation, transactionId);
33055
+ this.socketManager.sendMessage(acquireLockMessage);
33056
+ const result = await (0,external_rxjs_.firstValueFrom)((0,external_rxjs_.race)((0,external_rxjs_.timer)(5000).pipe((0,external_rxjs_.take)(1), map(() => {
33057
+ return {
33058
+ payload: {
33059
+ error: 'TIMEOUT_GETTING_LOCK',
33060
+ lockId: undefined,
33061
+ },
33062
+ };
33063
+ })), this.acquireLockMessagesFromServer.pipe((0,external_rxjs_.filter)(message => message.payload.clientRequestId === clientRequestId))));
33064
+ if (this.destructManager.isDestructing) {
33065
+ throw new Error('Destructing');
33066
+ }
33067
+ if (!result.payload.lockId) {
33068
+ throw new Error(`Failed to acquire lock: ${result.payload.error}`);
33069
+ }
33070
+ const lockId = result.payload.lockId;
33071
+ const lock = new DistributedLockImpl(lockId, clientRequestId, this.ongoingLocks, this.socketManager);
33072
+ this.ongoingLocks[lockId] = lock;
33073
+ return lock;
32970
33074
  }
32971
- /**
32972
- * Similar to {@link update}, but only updates the given path.
32973
- * @param path The path to update.
32974
- * @param value The value to set at the given path.
32975
- * @param transactionId The transaction to use for this operation. If not provided, the operation will be applied
32976
- * immediately.
32977
- */
32978
- async setInPath(path, value, transactionId) {
32979
- // Not sure why TypeScript doesn't understand that `path` below is restricted, but we need the explicit type
32980
- // assertion to make it compile.
32981
- return this.update({ [path]: cloneDeep(value) }, transactionId);
33075
+ releaseAllLocks() {
33076
+ for (const [lockId, lock] of Object.entries(this.ongoingLocks)) {
33077
+ lock.release();
33078
+ delete this.ongoingLocks[lockId];
33079
+ }
32982
33080
  }
32983
- /**
32984
- * Similar to `update`, but only deletes the given path.
32985
- * @param path The path to delete.
32986
- * @param transactionId The transaction to use for this operation. If not provided, the operation will be applied
32987
- * immediately.
32988
- */
32989
- async deleteInPath(path, transactionId) {
32990
- return this.update({ [path]: undefined }, transactionId);
33081
+ }
33082
+ /**
33083
+ * @internal
33084
+ */
33085
+ class DistributedLockImpl {
33086
+ constructor(lockId, clientRequestId, ongoingLocks, socketManager) {
33087
+ this.lockId = lockId;
33088
+ this.clientRequestId = clientRequestId;
33089
+ this.ongoingLocks = ongoingLocks;
33090
+ this.socketManager = socketManager;
33091
+ this.released = false;
33092
+ this.onReleaseSubject = new external_rxjs_.Subject();
32991
33093
  }
32992
- /**
32993
- * Increments the value at the given path by the given value. The value may be both positive and negative.
32994
- * @param path The path to the value to increment.
32995
- * @param value The value to increment by.
32996
- * @param transactionId The transaction to use for this operation. If not provided, the operation will be applied
32997
- * immediately.
32998
- */
32999
- incrementInPath(path, value, transactionId) {
33000
- const propertyMutation = {
33001
- type: 'applyNumericFn',
33002
- fn: 'increment',
33003
- value,
33004
- };
33005
- const mutation = {
33006
- type: 'update',
33007
- squidDocIdObj: parseSquidDocId(this.squidDocId),
33008
- properties: {
33009
- [path]: [propertyMutation],
33094
+ release() {
33095
+ if (this.released)
33096
+ return;
33097
+ this.released = true;
33098
+ delete this.ongoingLocks[this.lockId];
33099
+ const releaseLockMessage = {
33100
+ type: 'releaseLock',
33101
+ payload: {
33102
+ lockId: this.lockId,
33103
+ clientRequestId: this.clientRequestId,
33010
33104
  },
33011
33105
  };
33012
- return this.dataManager.applyOutgoingMutation(mutation, transactionId);
33106
+ this.socketManager.sendMessage(releaseLockMessage);
33107
+ this.onReleaseSubject.next();
33013
33108
  }
33014
- /**
33015
- * Decrements the value at the given path by the given value. The value may be both positive and negative.
33016
- * @param path The path to the value to decrement.
33017
- * @param value The value to decrement by.
33018
- * @param transactionId The transaction to use for this operation. If not provided, the operation will be applied
33019
- * immediately.
33020
- */
33021
- decrementInPath(path, value, transactionId) {
33022
- return this.incrementInPath(path, -value, transactionId);
33109
+ observeRelease() {
33110
+ return this.onReleaseSubject.asObservable();
33023
33111
  }
33024
- /**
33025
- * Inserts the document with the given data. If the document already exists, the operation will be treated as
33026
- * `upsert`. The `insert` will be reflected optimistically locally and will be applied to the server later. If a
33027
- * transactionId is provided, the `insert` will be applied to the server as an atomic operation together with the
33028
- * rest
33029
- * of the operations in the transaction and the `insert` will not reflect locally until the transaction is completed
33030
- * locally.
33031
- *
33032
- * The returned promise will resolve once the `insert` has been applied to the server or immediately if the `insert`
33033
- * is part of a transaction.
33034
- *
33035
- * @param data The data to insert.
33036
- * @param transactionId The transaction to use for this operation. If not provided, the operation will be applied
33037
- * immediately.
33038
- */
33039
- async insert(data, transactionId) {
33040
- const squidDocIdObj = parseSquidDocId(this.squidDocId);
33041
- const integrationId = squidDocIdObj.integrationId;
33042
- // Exclude the generated client id from the mutation properties.
33043
- let docIdProps = deserializeObj(squidDocIdObj.docId);
33044
- if (docIdProps[SquidPlaceholderId])
33045
- docIdProps = {};
33046
- // Destructure composite __ids for the built_in_db.
33047
- if (integrationId === IntegrationType.built_in_db && docIdProps.__id) {
33048
- try {
33049
- const idProps = deserializeObj(docIdProps.__id);
33050
- docIdProps = Object.assign(Object.assign({}, docIdProps), idProps);
33051
- }
33052
- catch (_a) { }
33053
- }
33054
- const mutation = {
33055
- type: 'insert',
33056
- squidDocIdObj,
33057
- properties: Object.assign(Object.assign(Object.assign({}, data), { __docId__: squidDocIdObj.docId }), docIdProps),
33058
- };
33059
- return this.dataManager.applyOutgoingMutation(mutation, transactionId);
33112
+ isReleased() {
33113
+ return this.released;
33060
33114
  }
33061
- /**
33062
- * Deletes the document.
33063
- * The `delete` will be reflected optimistically locally and will be applied to the server later.
33064
- * If a transactionId is provided, the `delete` will be applied to the server as an atomic operation together with
33065
- * the rest of the operations in the transaction and the `delete` will not reflect locally until the transaction is
33066
- * completed locally.
33067
- *
33068
- * The returned promise will resolve once the `delete` has been applied to the server or immediately if the `delete`
33069
- * is part of a transaction.
33070
- *
33071
- * @param transactionId The transaction to use for this operation. If not provided, the operation will be applied
33072
- * immediately.
33073
- */
33074
- async delete(transactionId) {
33075
- const mutation = {
33076
- type: 'delete',
33077
- squidDocIdObj: parseSquidDocId(this.squidDocId),
33078
- };
33079
- return this.dataManager.applyOutgoingMutation(mutation, transactionId);
33115
+ }
33116
+
33117
+ ;// CONCATENATED MODULE: ./src/document-identity.service.ts
33118
+
33119
+ /** @internal */
33120
+ class DocumentIdentityService {
33121
+ constructor(documentStore, destructManager) {
33122
+ this.documentStore = documentStore;
33123
+ this.destructManager = destructManager;
33124
+ this.changeNotifier = new external_rxjs_.BehaviorSubject({});
33125
+ this.destructManager.onDestruct(() => {
33126
+ this.changeNotifier.complete();
33127
+ });
33080
33128
  }
33081
- /**
33082
- * @internal
33083
- */
33084
- migrateDocIds(idResolutionMap) {
33085
- const newSquidDocId = idResolutionMap[this.squidDocId];
33086
- if (newSquidDocId) {
33087
- this._squidDocId = newSquidDocId;
33088
- }
33129
+ migrate(idResolutionMap) {
33130
+ // Update the document in the database before notifying any other subscribers.
33131
+ Object.entries(idResolutionMap).forEach(([squidDocId, newSquidDocId]) => {
33132
+ this.documentStore.migrateDocId(squidDocId, newSquidDocId);
33133
+ });
33134
+ this.changeNotifier.next(idResolutionMap);
33089
33135
  }
33090
- hasSquidPlaceholderId() {
33091
- const obj = deserializeObj(this._squidDocId);
33092
- if (typeof obj === 'object' && !!obj.docId) {
33093
- const docIdObj = deserializeObj(obj.docId);
33094
- if (typeof docIdObj === 'object' && Object.keys(docIdObj).includes(SquidPlaceholderId)) {
33095
- return true;
33096
- }
33097
- }
33098
- return false;
33136
+ observeChanges() {
33137
+ return this.changeNotifier.asObservable();
33099
33138
  }
33100
33139
  }
33101
33140
 
@@ -49132,6 +49171,7 @@ const kotlinControllers = [
49132
49171
  'mutation',
49133
49172
  'application',
49134
49173
  'storage',
49174
+ 'internalCodeExecutor',
49135
49175
  ];
49136
49176
  function getApplicationUrl(regionPrefix, appId, path) {
49137
49177
  const baseUrl = 'https://squid.cloud';
@@ -51410,8 +51450,8 @@ class Squid {
51410
51450
  this.localQueryManager = new LocalQueryManager(this.documentStore, this.documentReferenceFactory, this.querySubscriptionManager);
51411
51451
  const mutationSender = new MutationSender(this.rpcManager, this.lockManager, this.querySender);
51412
51452
  this.queryBuilderFactory = new QueryBuilderFactory(this.querySubscriptionManager, this.localQueryManager, this.documentReferenceFactory, this.documentIdentityService);
51413
- this.collectionReferenceFactory = new CollectionReferenceFactory(this.documentReferenceFactory, this.queryBuilderFactory, this.querySubscriptionManager);
51414
51453
  this.dataManager = new DataManager(this.documentStore, mutationSender, this.socketManager, this.querySubscriptionManager, this.queryBuilderFactory, this.lockManager, this.destructManager, this.documentIdentityService, this.querySender);
51454
+ this.collectionReferenceFactory = new CollectionReferenceFactory(this.documentReferenceFactory, this.queryBuilderFactory, this.querySubscriptionManager, this.dataManager);
51415
51455
  this.documentReferenceFactory.setDataManager(this.dataManager);
51416
51456
  this.backendFunctionManager = new BackendFunctionManager(this.clientIdService, this.rpcManager);
51417
51457
  this.nativeQueryManager = new NativeQueryManager(this.rpcManager);