velocious 1.0.716 → 1.0.718

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.
Files changed (55) hide show
  1. package/README.md +1 -0
  2. package/build/configuration.js +2 -3
  3. package/build/database/query/model-class-query.js +49 -14
  4. package/build/database/record/attachments/store.js +13 -2
  5. package/build/database/record/index.js +3 -2
  6. package/build/environment-handlers/base.js +11 -0
  7. package/build/environment-handlers/node.js +10 -0
  8. package/build/src/configuration.d.ts.map +1 -1
  9. package/build/src/configuration.js +3 -4
  10. package/build/src/database/query/model-class-query.d.ts +13 -3
  11. package/build/src/database/query/model-class-query.d.ts.map +1 -1
  12. package/build/src/database/query/model-class-query.js +45 -15
  13. package/build/src/database/record/attachments/store.d.ts.map +1 -1
  14. package/build/src/database/record/attachments/store.js +12 -3
  15. package/build/src/database/record/index.d.ts +4 -2
  16. package/build/src/database/record/index.d.ts.map +1 -1
  17. package/build/src/database/record/index.js +4 -3
  18. package/build/src/environment-handlers/base.d.ts +8 -0
  19. package/build/src/environment-handlers/base.d.ts.map +1 -1
  20. package/build/src/environment-handlers/base.js +11 -1
  21. package/build/src/environment-handlers/node.d.ts +7 -0
  22. package/build/src/environment-handlers/node.d.ts.map +1 -1
  23. package/build/src/environment-handlers/node.js +10 -1
  24. package/build/src/sync/sync-api-client-types.d.ts +10 -0
  25. package/build/src/sync/sync-api-client-types.d.ts.map +1 -1
  26. package/build/src/sync/sync-api-client-types.js +3 -1
  27. package/build/src/sync/sync-api-client.d.ts +6 -0
  28. package/build/src/sync/sync-api-client.d.ts.map +1 -1
  29. package/build/src/sync/sync-api-client.js +17 -2
  30. package/build/src/sync/sync-applied-sequence-store.d.ts +138 -0
  31. package/build/src/sync/sync-applied-sequence-store.d.ts.map +1 -0
  32. package/build/src/sync/sync-applied-sequence-store.js +267 -0
  33. package/build/src/sync/sync-client.d.ts +15 -0
  34. package/build/src/sync/sync-client.d.ts.map +1 -1
  35. package/build/src/sync/sync-client.js +43 -3
  36. package/build/src/tenants/tenant-model-scope.d.ts +2 -1
  37. package/build/src/tenants/tenant-model-scope.d.ts.map +1 -1
  38. package/build/src/tenants/tenant-model-scope.js +4 -3
  39. package/build/sync/sync-api-client-types.js +2 -0
  40. package/build/sync/sync-api-client.js +18 -1
  41. package/build/sync/sync-applied-sequence-store.js +298 -0
  42. package/build/sync/sync-client.js +51 -2
  43. package/build/tenants/tenant-model-scope.js +3 -2
  44. package/package.json +1 -1
  45. package/src/configuration.js +2 -3
  46. package/src/database/query/model-class-query.js +49 -14
  47. package/src/database/record/attachments/store.js +13 -2
  48. package/src/database/record/index.js +3 -2
  49. package/src/environment-handlers/base.js +11 -0
  50. package/src/environment-handlers/node.js +10 -0
  51. package/src/sync/sync-api-client-types.js +2 -0
  52. package/src/sync/sync-api-client.js +18 -1
  53. package/src/sync/sync-applied-sequence-store.js +298 -0
  54. package/src/sync/sync-client.js +51 -2
  55. package/src/tenants/tenant-model-scope.js +3 -2
@@ -43,9 +43,10 @@ export default class TenantModelScope<MC extends typeof import("../database/reco
43
43
  create(attributes?: Record<string, ReturnType<typeof JSON.parse>>): Promise<InstanceType<MC>>;
44
44
  /**
45
45
  * Counts records on the captured tenant.
46
+ * @param {string | null} [column] - Attribute or column to count. Defaults to the primary key.
46
47
  * @returns {Promise<number>} - Count on the captured tenant.
47
48
  */
48
- count(): Promise<number>;
49
+ count(column?: string | null): Promise<number>;
49
50
  /**
50
51
  * Finds a record by primary key on the captured tenant.
51
52
  * @param {string | number} recordId - Record identifier.
@@ -1 +1 @@
1
- {"version":3,"file":"tenant-model-scope.d.ts","sourceRoot":"","sources":["../../../src/tenants/tenant-model-scope.js"],"names":[],"mappings":"AAEA,OAAO,YAAY,MAAM,oBAAoB,CAAA;AAE7C;;;GAGG;AACH,MAAM,CAAC,OAAO,OAAO,gBAAgB,CAF+B,EAAE,SAAxD,cAAc,6BAA6B,EAAE,OAAW;IAW7D,OAAO;IACP,WAAW;IACX,mBAAmB;IAV1B;;;;;;OAMG;IACH,YAAY,EAAC,aAAa,EAAE,UAAU,EAAE,MAAM,EAAC,EAJ5C;QAAoD,aAAa,EAAzD,OAAO,qBAAqB,EAAE,OAAO,CAC7C;QAAiB,UAAU,EAAnB,EAAE,CACV;QAAqB,MAAM,EAAnB,MAAM,CAChB;KAC8C,EAM9C;IAED;;;;;;;OAOG;IACG,iBAAiB,CAJV,CAAC,EAIU,QAAQ,EAHrB,CAAC,KAAK,EAAE,OAAO,wCAAwC,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,OAAO,0BAA0B,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC,CAGpH,GAFnB,OAAO,CAAC,CAAC,CAAC,CAWtB;IAED;;;;;OAKG;IACG,WAAW,CAJJ,CAAC,EAII,QAAQ,EAHf,CAAC,KAAK,EAAE,OAAO,wCAAwC,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,OAAO,0BAA0B,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC,CAG1H,GAFb,OAAO,CAAC,CAAC,CAAC,CAWtB;IAED;;;;OAIG;IACG,MAAM,CAAC,UAAU,AAHpB,CACA,EADQ,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAG3B,GAFf,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAQrC;IAED;;;OAGG;IACG,KAAK,IAFE,OAAO,CAAC,MAAM,CAAC,CAI3B;IAED;;;;OAIG;IACG,IAAI,CAAC,QAAQ,EAHR,MAAM,GAAG,MAGD,GAFN,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAQ5C;IAED;;;;OAIG;IACG,MAAM,CAAC,UAAU,EAHZ,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGlB,GAFV,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAQ5C;IAED;;;;OAIG;IACG,YAAY,CAAC,UAAU,EAHlB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGZ,GAFhB,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAQrC;IAED;;;OAGG;IACG,OAAO,IAFA,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC,EAAE,CAAC,CAQvC;CACF"}
1
+ {"version":3,"file":"tenant-model-scope.d.ts","sourceRoot":"","sources":["../../../src/tenants/tenant-model-scope.js"],"names":[],"mappings":"AAEA,OAAO,YAAY,MAAM,oBAAoB,CAAA;AAE7C;;;GAGG;AACH,MAAM,CAAC,OAAO,OAAO,gBAAgB,CAF+B,EAAE,SAAxD,cAAc,6BAA6B,EAAE,OAAW;IAW7D,OAAO;IACP,WAAW;IACX,mBAAmB;IAV1B;;;;;;OAMG;IACH,YAAY,EAAC,aAAa,EAAE,UAAU,EAAE,MAAM,EAAC,EAJ5C;QAAoD,aAAa,EAAzD,OAAO,qBAAqB,EAAE,OAAO,CAC7C;QAAiB,UAAU,EAAnB,EAAE,CACV;QAAqB,MAAM,EAAnB,MAAM,CAChB;KAC8C,EAM9C;IAED;;;;;;;OAOG;IACG,iBAAiB,CAJV,CAAC,EAIU,QAAQ,EAHrB,CAAC,KAAK,EAAE,OAAO,wCAAwC,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,OAAO,0BAA0B,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC,CAGpH,GAFnB,OAAO,CAAC,CAAC,CAAC,CAWtB;IAED;;;;;OAKG;IACG,WAAW,CAJJ,CAAC,EAII,QAAQ,EAHf,CAAC,KAAK,EAAE,OAAO,wCAAwC,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,OAAO,0BAA0B,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC,CAG1H,GAFb,OAAO,CAAC,CAAC,CAAC,CAWtB;IAED;;;;OAIG;IACG,MAAM,CAAC,UAAU,AAHpB,CACA,EADQ,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAG3B,GAFf,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAQrC;IAED;;;;OAIG;IACG,KAAK,CAAC,MAAM,AAHf,CACA,EADQ,MAAM,GAAG,IAGK,GAFZ,OAAO,CAAC,MAAM,CAAC,CAI3B;IAED;;;;OAIG;IACG,IAAI,CAAC,QAAQ,EAHR,MAAM,GAAG,MAGD,GAFN,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAQ5C;IAED;;;;OAIG;IACG,MAAM,CAAC,UAAU,EAHZ,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGlB,GAFV,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAQ5C;IAED;;;;OAIG;IACG,YAAY,CAAC,UAAU,EAHlB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGZ,GAFhB,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAQrC;IAED;;;OAGG;IACG,OAAO,IAFA,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC,EAAE,CAAC,CAQvC;CACF"}
@@ -63,10 +63,11 @@ export default class TenantModelScope {
63
63
  }
64
64
  /**
65
65
  * Counts records on the captured tenant.
66
+ * @param {string | null} [column] - Attribute or column to count. Defaults to the primary key.
66
67
  * @returns {Promise<number>} - Count on the captured tenant.
67
68
  */
68
- async count() {
69
- return await this.databaseOperation(async (query) => await query.count());
69
+ async count(column = null) {
70
+ return await this.databaseOperation(async (query) => await query.count(column));
70
71
  }
71
72
  /**
72
73
  * Finds a record by primary key on the captured tenant.
@@ -112,4 +113,4 @@ export default class TenantModelScope {
112
113
  });
113
114
  }
114
115
  }
115
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVuYW50LW1vZGVsLXNjb3BlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3RlbmFudHMvdGVuYW50LW1vZGVsLXNjb3BlLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLFlBQVk7QUFFWixPQUFPLFlBQVksTUFBTSxvQkFBb0IsQ0FBQTtBQUU3Qzs7O0dBR0c7QUFDSCxNQUFNLENBQUMsT0FBTyxPQUFPLGdCQUFnQjtJQUNuQzs7Ozs7O09BTUc7SUFDSCxZQUFZLEVBQUMsYUFBYSxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUM7UUFDN0MsSUFBSSxDQUFDLE9BQU8sR0FBRyxJQUFJLFlBQVksQ0FBQyxFQUFDLGFBQWEsRUFBRSxNQUFNLEVBQUMsQ0FBQyxDQUFBO1FBQ3hELElBQUksQ0FBQyxXQUFXLEdBQUcsVUFBVSxDQUFBO1FBQzdCLElBQUksQ0FBQyxtQkFBbUIsR0FBRyxVQUFVLENBQUMscUJBQXFCLENBQUMsRUFBQyxNQUFNLEVBQUMsQ0FBQyxDQUFBO1FBRXJFLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUE7SUFDckIsQ0FBQztJQUVEOzs7Ozs7O09BT0c7SUFDSCxLQUFLLENBQUMsaUJBQWlCLENBQUMsUUFBUTtRQUM5QixPQUFPLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxpQkFBaUIsQ0FBQztZQUMxQyxrQkFBa0IsRUFBRSxJQUFJLENBQUMsbUJBQW1CO1lBQzVDLElBQUksRUFBRSxnQkFBZ0IsSUFBSSxDQUFDLFdBQVcsQ0FBQyxZQUFZLEVBQUUsRUFBRTtTQUN4RCxFQUFFLEtBQUssRUFBRSxTQUFTLEVBQUUsRUFBRTtZQUNyQixNQUFNLFNBQVMsQ0FBQyxzQkFBc0IsQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLENBQUE7WUFFeEQsT0FBTyxNQUFNLFFBQVEsQ0FBQyxTQUFTLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsRUFBRSxTQUFTLENBQUMsQ0FBQTtRQUN4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R0FBRyxNQUFNLEtBQUssQ0FBQyxPQUFPLEVBQUUsQ0FBQTtZQUVyQyxPQUFPLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLE1BQU0sQ0FBQyx3QkFBd0IsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFBO1FBQzVFLENBQUMsQ0FBQyxDQUFBO0lBQ0osQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQHRzLWNoZWNrXG5cbmltcG9ydCBUZW5hbnRIYW5kbGUgZnJvbSBcIi4vdGVuYW50LWhhbmRsZS5qc1wiXG5cbi8qKlxuICogTW9kZWwgcXVlcnkvY3JlYXRlIHNjb3BlIGJvdW5kIHRvIG9uZSBpbW11dGFibGUgdGVuYW50IGhhbmRsZS5cbiAqIEB0ZW1wbGF0ZSB7dHlwZW9mIGltcG9ydChcIi4uL2RhdGFiYXNlL3JlY29yZC9pbmRleC5qc1wiKS5kZWZhdWx0fSBNQ1xuICovXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBUZW5hbnRNb2RlbFNjb3BlIHtcbiAgLyoqXG4gICAqIFJ1bnMgY29uc3RydWN0b3IuXG4gICAqIEBwYXJhbSB7b2JqZWN0fSBhcmdzIC0gU2NvcGUgYXJndW1lbnRzLlxuICAgKiBAcGFyYW0ge2ltcG9ydChcIi4uL2NvbmZpZ3VyYXRpb24uanNcIikuZGVmYXVsdH0gYXJncy5jb25maWd1cmF0aW9uIC0gT3duaW5nIGNvbmZpZ3VyYXRpb24uXG4gICAqIEBwYXJhbSB7TUN9IGFyZ3MubW9kZWxDbGFzcyAtIE1vZGVsIGNsYXNzIHRvIGJpbmQuXG4gICAqIEBwYXJhbSB7b2JqZWN0fSBhcmdzLnRlbmFudCAtIE9yZGluYXJ5IG9yIG51bGwtcHJvdG90eXBlIEpTT04tY29tcGF0aWJsZSB0ZW5hbnQgZGVzY3JpcHRvci5cbiAgICovXG4gIGNvbnN0cnVjdG9yKHtjb25maWd1cmF0aW9uLCBtb2RlbENsYXNzLCB0ZW5hbnR9KSB7XG4gICAgdGhpcy5faGFuZGxlID0gbmV3IFRlbmFudEhhbmRsZSh7Y29uZmlndXJhdGlvbiwgdGVuYW50fSlcbiAgICB0aGlzLl9tb2RlbENsYXNzID0gbW9kZWxDbGFzc1xuICAgIHRoaXMuX2RhdGFiYXNlSWRlbnRpZmllciA9IG1vZGVsQ2xhc3MuZ2V0RGF0YWJhc2VJZGVudGlmaWVyKHt0ZW5hbnR9KVxuXG4gICAgT2JqZWN0LmZyZWV6ZSh0aGlzKVxuICB9XG5cbiAgLyoqXG4gICAqIFJ1bnMgYSBnZW5lcmFsIG1vZGVsIHF1ZXJ5L3dyaXRlIGNhbGxiYWNrIG9uIHRoZSBjYXB0dXJlZCBwaHlzaWNhbCB0ZW5hbnQuXG4gICAqIFRoZSBxdWVyeSBhbmQgZXZlcnkgcmVjb3JkL2Fzc29jaWF0aW9uIGl0IGxvYWRzIGFyZSBvd25lZCBieSB0aGUgb3BlcmF0aW9uXG4gICAqIGFuZCBtdXN0IGJlIHVzZWQgaW5zaWRlIHRoZSBjYWxsYmFjay5cbiAgICogQHRlbXBsYXRlIFRcbiAgICogQHBhcmFtIHsocXVlcnk6IGltcG9ydChcIi4uL2RhdGFiYXNlL3F1ZXJ5L21vZGVsLWNsYXNzLXF1ZXJ5LmpzXCIpLmRlZmF1bHQ8TUM+LCBvcGVyYXRpb246IGltcG9ydChcIi4uL2RhdGFiYXNlL29wZXJhdGlvbi5qc1wiKS5kZWZhdWx0KSA9PiBQcm9taXNlPFQ+fSBjYWxsYmFjayAtIEJvdW5kIG1vZGVsIGNhbGxiYWNrLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxUPn0gLSBDYWxsYmFjayByZXN1bHQuXG4gICAqL1xuICBhc3luYyBkYXRhYmFzZU9wZXJhdGlvbihjYWxsYmFjaykge1xuICAgIHJldHVybiBhd2FpdCB0aGlzLl9oYW5kbGUuZGF0YWJhc2VPcGVyYXRpb24oe1xuICAgICAgZGF0YWJhc2VJZGVudGlmaWVyOiB0aGlzLl9kYXRhYmFzZUlkZW50aWZpZXIsXG4gICAgICBuYW1lOiBgdXNpbmdUZW5hbnQ6ICR7dGhpcy5fbW9kZWxDbGFzcy5nZXRNb2RlbE5hbWUoKX1gXG4gICAgfSwgYXN5bmMgKG9wZXJhdGlvbikgPT4ge1xuICAgICAgYXdhaXQgb3BlcmF0aW9uLmVuc3VyZU1vZGVsSW5pdGlhbGl6ZWQodGhpcy5fbW9kZWxDbGFzcylcblxuICAgICAgcmV0dXJuIGF3YWl0IGNhbGxiYWNrKG9wZXJhdGlvbi5mb3JNb2RlbCh0aGlzLl9tb2RlbENsYXNzKSwgb3BlcmF0aW9uKVxuICAgIH0pXG4gIH1cblxuICAvKipcbiAgICogUnVucyBhIG1vZGVsIGNhbGxiYWNrIGluIGEgdHJhbnNhY3Rpb24gb24gdGhlIGNhcHR1cmVkIHBoeXNpY2FsIHRlbmFudC5cbiAgICogQHRlbXBsYXRlIFRcbiAgICogQHBhcmFtIHsocXVlcnk6IGltcG9ydChcIi4uL2RhdGFiYXNlL3F1ZXJ5L21vZGVsLWNsYXNzLXF1ZXJ5LmpzXCIpLmRlZmF1bHQ8TUM+LCBvcGVyYXRpb246IGltcG9ydChcIi4uL2RhdGFiYXNlL29wZXJhdGlvbi5qc1wiKS5kZWZhdWx0KSA9PiBQcm9taXNlPFQ+fSBjYWxsYmFjayAtIFRyYW5zYWN0aW9uIGNhbGxiYWNrLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxUPn0gLSBDYWxsYmFjayByZXN1bHQuXG4gICAqL1xuICBhc3luYyB0cmFuc2FjdGlvbihjYWxsYmFjaykge1xuICAgIHJldHVybiBhd2FpdCB0aGlzLl9oYW5kbGUudHJhbnNhY3Rpb24oe1xuICAgICAgZGF0YWJhc2VJZGVudGlmaWVyOiB0aGlzLl9kYXRhYmFzZUlkZW50aWZpZXIsXG4gICAgICBuYW1lOiBgdXNpbmdUZW5hbnQgdHJhbnNhY3Rpb246ICR7dGhpcy5fbW9kZWxDbGFzcy5nZXRNb2RlbE5hbWUoKX1gXG4gICAgfSwgYXN5bmMgKG9wZXJhdGlvbikgPT4ge1xuICAgICAgYXdhaXQgb3BlcmF0aW9uLmVuc3VyZU1vZGVsSW5pdGlhbGl6ZWQodGhpcy5fbW9kZWxDbGFzcylcblxuICAgICAgcmV0dXJuIGF3YWl0IGNhbGxiYWNrKG9wZXJhdGlvbi5mb3JNb2RlbCh0aGlzLl9tb2RlbENsYXNzKSwgb3BlcmF0aW9uKVxuICAgIH0pXG4gIH1cblxuICAvKipcbiAgICogQ3JlYXRlcyBhIHJlY29yZCBvbiB0aGUgY2FwdHVyZWQgdGVuYW50LlxuICAgKiBAcGFyYW0ge1JlY29yZDxzdHJpbmcsIFJldHVyblR5cGU8dHlwZW9mIEpTT04ucGFyc2U+Pn0gW2F0dHJpYnV0ZXNdIC0gQ3JlYXRlIGF0dHJpYnV0ZXMuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPEluc3RhbmNlVHlwZTxNQz4+fSAtIENyZWF0ZWQgcmVjb3JkLlxuICAgKi9cbiAgYXN5bmMgY3JlYXRlKGF0dHJpYnV0ZXMgPSB7fSkge1xuICAgIHJldHVybiBhd2FpdCB0aGlzLmRhdGFiYXNlT3BlcmF0aW9uKGFzeW5jIChxdWVyeSwgb3BlcmF0aW9uKSA9PiB7XG4gICAgICBjb25zdCByZWNvcmQgPSBhd2FpdCBxdWVyeS5jcmVhdGUoYXR0cmlidXRlcylcblxuICAgICAgcmV0dXJuIHJlY29yZC5yZWxlYXNlRGF0YWJhc2VPcGVyYXRpb24ob3BlcmF0aW9uKVxuICAgIH0pXG4gIH1cblxuICAvKipcbiAgICogQ291bnRzIHJlY29yZHMgb24gdGhlIGNhcHR1cmVkIHRlbmFudC5cbiAgICogQHJldHVybnMge1Byb21pc2U8bnVtYmVyPn0gLSBDb3VudCBvbiB0aGUgY2FwdHVyZWQgdGVuYW50LlxuICAgKi9cbiAgYXN5bmMgY291bnQoKSB7XG4gICAgcmV0dXJuIGF3YWl0IHRoaXMuZGF0YWJhc2VPcGVyYXRpb24oYXN5bmMgKHF1ZXJ5KSA9PiBhd2FpdCBxdWVyeS5jb3VudCgpKVxuICB9XG5cbiAgLyoqXG4gICAqIEZpbmRzIGEgcmVjb3JkIGJ5IHByaW1hcnkga2V5IG9uIHRoZSBjYXB0dXJlZCB0ZW5hbnQuXG4gICAqIEBwYXJhbSB7c3RyaW5nIHwgbnVtYmVyfSByZWNvcmRJZCAtIFJlY29yZCBpZGVudGlmaWVyLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxJbnN0YW5jZVR5cGU8TUM+IHwgbnVsbD59IC0gRm91bmQgcmVjb3JkLlxuICAgKi9cbiAgYXN5bmMgZmluZChyZWNvcmRJZCkge1xuICAgIHJldHVybiBhd2FpdCB0aGlzLmRhdGFiYXNlT3BlcmF0aW9uKGFzeW5jIChxdWVyeSwgb3BlcmF0aW9uKSA9PiB7XG4gICAgICBjb25zdCByZWNvcmQgPSBhd2FpdCBxdWVyeS5maW5kKHJlY29yZElkKVxuXG4gICAgICByZXR1cm4gcmVjb3JkID8gcmVjb3JkLnJlbGVhc2VEYXRhYmFzZU9wZXJhdGlvbihvcGVyYXRpb24pIDogbnVsbFxuICAgIH0pXG4gIH1cblxuICAvKipcbiAgICogRmluZHMgYSByZWNvcmQgYnkgY29uZGl0aW9ucyBvbiB0aGUgY2FwdHVyZWQgdGVuYW50LlxuICAgKiBAcGFyYW0ge1JlY29yZDxzdHJpbmcsIHN0cmluZyB8IG51bWJlcj59IGNvbmRpdGlvbnMgLSBGaW5kZXIgY29uZGl0aW9ucy5cbiAgICogQHJldHVybnMge1Byb21pc2U8SW5zdGFuY2VUeXBlPE1DPiB8IG51bGw+fSAtIEZvdW5kIHJlY29yZC5cbiAgICovXG4gIGFzeW5jIGZpbmRCeShjb25kaXRpb25zKSB7XG4gICAgcmV0dXJuIGF3YWl0IHRoaXMuZGF0YWJhc2VPcGVyYXRpb24oYXN5bmMgKHF1ZXJ5LCBvcGVyYXRpb24pID0+IHtcbiAgICAgIGNvbnN0IHJlY29yZCA9IGF3YWl0IHF1ZXJ5LmZpbmRCeShjb25kaXRpb25zKVxuXG4gICAgICByZXR1cm4gcmVjb3JkID8gcmVjb3JkLnJlbGVhc2VEYXRhYmFzZU9wZXJhdGlvbihvcGVyYXRpb24pIDogbnVsbFxuICAgIH0pXG4gIH1cblxuICAvKipcbiAgICogRmluZHMgYSByZWNvcmQgYnkgY29uZGl0aW9ucyBvciByYWlzZXMgb24gdGhlIGNhcHR1cmVkIHRlbmFudC5cbiAgICogQHBhcmFtIHtSZWNvcmQ8c3RyaW5nLCBzdHJpbmcgfCBudW1iZXI+fSBjb25kaXRpb25zIC0gRmluZGVyIGNvbmRpdGlvbnMuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPEluc3RhbmNlVHlwZTxNQz4+fSAtIEZvdW5kIHJlY29yZC5cbiAgICovXG4gIGFzeW5jIGZpbmRCeU9yRmFpbChjb25kaXRpb25zKSB7XG4gICAgcmV0dXJuIGF3YWl0IHRoaXMuZGF0YWJhc2VPcGVyYXRpb24oYXN5bmMgKHF1ZXJ5LCBvcGVyYXRpb24pID0+IHtcbiAgICAgIGNvbnN0IHJlY29yZCA9IGF3YWl0IHF1ZXJ5LmZpbmRCeU9yRmFpbChjb25kaXRpb25zKVxuXG4gICAgICByZXR1cm4gcmVjb3JkLnJlbGVhc2VEYXRhYmFzZU9wZXJhdGlvbihvcGVyYXRpb24pXG4gICAgfSlcbiAgfVxuXG4gIC8qKlxuICAgKiBMb2FkcyBhbGwgcmVjb3JkcyBvbiB0aGUgY2FwdHVyZWQgdGVuYW50LlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxJbnN0YW5jZVR5cGU8TUM+W10+fSAtIEFsbCByZWNvcmRzIG9uIHRoZSBjYXB0dXJlZCB0ZW5hbnQuXG4gICAqL1xuICBhc3luYyB0b0FycmF5KCkge1xuICAgIHJldHVybiBhd2FpdCB0aGlzLmRhdGFiYXNlT3BlcmF0aW9uKGFzeW5jIChxdWVyeSwgb3BlcmF0aW9uKSA9PiB7XG4gICAgICBjb25zdCByZWNvcmRzID0gYXdhaXQgcXVlcnkudG9BcnJheSgpXG5cbiAgICAgIHJldHVybiByZWNvcmRzLm1hcCgocmVjb3JkKSA9PiByZWNvcmQucmVsZWFzZURhdGFiYXNlT3BlcmF0aW9uKG9wZXJhdGlvbikpXG4gICAgfSlcbiAgfVxufVxuIl19
116
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVuYW50LW1vZGVsLXNjb3BlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3RlbmFudHMvdGVuYW50LW1vZGVsLXNjb3BlLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLFlBQVk7QUFFWixPQUFPLFlBQVksTUFBTSxvQkFBb0IsQ0FBQTtBQUU3Qzs7O0dBR0c7QUFDSCxNQUFNLENBQUMsT0FBTyxPQUFPLGdCQUFnQjtJQUNuQzs7Ozs7O09BTUc7SUFDSCxZQUFZLEVBQUMsYUFBYSxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUM7UUFDN0MsSUFBSSxDQUFDLE9BQU8sR0FBRyxJQUFJLFlBQVksQ0FBQyxFQUFDLGFBQWEsRUFBRSxNQUFNLEVBQUMsQ0FBQyxDQUFBO1FBQ3hELElBQUksQ0FBQyxXQUFXLEdBQUcsVUFBVSxDQUFBO1FBQzdCLElBQUksQ0FBQyxtQkFBbUIsR0FBRyxVQUFVLENBQUMscUJBQXFCLENBQUMsRUFBQyxNQUFNLEVBQUMsQ0FBQyxDQUFBO1FBRXJFLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUE7SUFDckIsQ0FBQztJQUVEOzs7Ozs7O09BT0c7SUFDSCxLQUFLLENBQUMsaUJBQWlCLENBQUMsUUFBUTtRQUM5QixPQUFPLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxpQkFBaUIsQ0FBQztZQUMxQyxrQkFBa0IsRUFBRSxJQUFJLENBQUMsbUJBQW1CO1lBQzVDLElBQUksRUFBRSxnQkFBZ0IsSUFBSSxDQUFDLFdBQVcsQ0FBQyxZQUFZLEVBQUUsRUFBRTtTQUN4RCxFQUFFLEtBQUssRUFBRSxTQUFTLEVBQUUsRUFBRTtZQUNyQixNQUFNLFNBQVMsQ0FBQyxzQkFBc0IsQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLENBQUE7WUFFeEQsT0FBTyxNQUFNLFFBQVEsQ0FBQyxTQUFTLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsRUFBRSxTQUFTLENBQUMsQ0FBQTtRQUN4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LFNBQVMsRUFBRSxFQUFFO1lBQzdELE1BQU0sT0FBTyxHQUFHLE1BQU0sS0FBSyxDQUFDLE9BQU8sRUFBRSxDQUFBO1lBRXJDLE9BQU8sT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDLE1BQU0sRUFBRSxFQUFFLENBQUMsTUFBTSxDQUFDLHdCQUF3QixDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUE7UUFDNUUsQ0FBQyxDQUFDLENBQUE7SUFDSixDQUFDO0NBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBAdHMtY2hlY2tcblxuaW1wb3J0IFRlbmFudEhhbmRsZSBmcm9tIFwiLi90ZW5hbnQtaGFuZGxlLmpzXCJcblxuLyoqXG4gKiBNb2RlbCBxdWVyeS9jcmVhdGUgc2NvcGUgYm91bmQgdG8gb25lIGltbXV0YWJsZSB0ZW5hbnQgaGFuZGxlLlxuICogQHRlbXBsYXRlIHt0eXBlb2YgaW1wb3J0KFwiLi4vZGF0YWJhc2UvcmVjb3JkL2luZGV4LmpzXCIpLmRlZmF1bHR9IE1DXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIFRlbmFudE1vZGVsU2NvcGUge1xuICAvKipcbiAgICogUnVucyBjb25zdHJ1Y3Rvci5cbiAgICogQHBhcmFtIHtvYmplY3R9IGFyZ3MgLSBTY29wZSBhcmd1bWVudHMuXG4gICAqIEBwYXJhbSB7aW1wb3J0KFwiLi4vY29uZmlndXJhdGlvbi5qc1wiKS5kZWZhdWx0fSBhcmdzLmNvbmZpZ3VyYXRpb24gLSBPd25pbmcgY29uZmlndXJhdGlvbi5cbiAgICogQHBhcmFtIHtNQ30gYXJncy5tb2RlbENsYXNzIC0gTW9kZWwgY2xhc3MgdG8gYmluZC5cbiAgICogQHBhcmFtIHtvYmplY3R9IGFyZ3MudGVuYW50IC0gT3JkaW5hcnkgb3IgbnVsbC1wcm90b3R5cGUgSlNPTi1jb21wYXRpYmxlIHRlbmFudCBkZXNjcmlwdG9yLlxuICAgKi9cbiAgY29uc3RydWN0b3Ioe2NvbmZpZ3VyYXRpb24sIG1vZGVsQ2xhc3MsIHRlbmFudH0pIHtcbiAgICB0aGlzLl9oYW5kbGUgPSBuZXcgVGVuYW50SGFuZGxlKHtjb25maWd1cmF0aW9uLCB0ZW5hbnR9KVxuICAgIHRoaXMuX21vZGVsQ2xhc3MgPSBtb2RlbENsYXNzXG4gICAgdGhpcy5fZGF0YWJhc2VJZGVudGlmaWVyID0gbW9kZWxDbGFzcy5nZXREYXRhYmFzZUlkZW50aWZpZXIoe3RlbmFudH0pXG5cbiAgICBPYmplY3QuZnJlZXplKHRoaXMpXG4gIH1cblxuICAvKipcbiAgICogUnVucyBhIGdlbmVyYWwgbW9kZWwgcXVlcnkvd3JpdGUgY2FsbGJhY2sgb24gdGhlIGNhcHR1cmVkIHBoeXNpY2FsIHRlbmFudC5cbiAgICogVGhlIHF1ZXJ5IGFuZCBldmVyeSByZWNvcmQvYXNzb2NpYXRpb24gaXQgbG9hZHMgYXJlIG93bmVkIGJ5IHRoZSBvcGVyYXRpb25cbiAgICogYW5kIG11c3QgYmUgdXNlZCBpbnNpZGUgdGhlIGNhbGxiYWNrLlxuICAgKiBAdGVtcGxhdGUgVFxuICAgKiBAcGFyYW0geyhxdWVyeTogaW1wb3J0KFwiLi4vZGF0YWJhc2UvcXVlcnkvbW9kZWwtY2xhc3MtcXVlcnkuanNcIikuZGVmYXVsdDxNQz4sIG9wZXJhdGlvbjogaW1wb3J0KFwiLi4vZGF0YWJhc2Uvb3BlcmF0aW9uLmpzXCIpLmRlZmF1bHQpID0+IFByb21pc2U8VD59IGNhbGxiYWNrIC0gQm91bmQgbW9kZWwgY2FsbGJhY2suXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPFQ+fSAtIENhbGxiYWNrIHJlc3VsdC5cbiAgICovXG4gIGFzeW5jIGRhdGFiYXNlT3BlcmF0aW9uKGNhbGxiYWNrKSB7XG4gICAgcmV0dXJuIGF3YWl0IHRoaXMuX2hhbmRsZS5kYXRhYmFzZU9wZXJhdGlvbih7XG4gICAgICBkYXRhYmFzZUlkZW50aWZpZXI6IHRoaXMuX2RhdGFiYXNlSWRlbnRpZmllcixcbiAgICAgIG5hbWU6IGB1c2luZ1RlbmFudDogJHt0aGlzLl9tb2RlbENsYXNzLmdldE1vZGVsTmFtZSgpfWBcbiAgICB9LCBhc3luYyAob3BlcmF0aW9uKSA9PiB7XG4gICAgICBhd2FpdCBvcGVyYXRpb24uZW5zdXJlTW9kZWxJbml0aWFsaXplZCh0aGlzLl9tb2RlbENsYXNzKVxuXG4gICAgICByZXR1cm4gYXdhaXQgY2FsbGJhY2sob3BlcmF0aW9uLmZvck1vZGVsKHRoaXMuX21vZGVsQ2xhc3MpLCBvcGVyYXRpb24pXG4gICAgfSlcbiAgfVxuXG4gIC8qKlxuICAgKiBSdW5zIGEgbW9kZWwgY2FsbGJhY2sgaW4gYSB0cmFuc2FjdGlvbiBvbiB0aGUgY2FwdHVyZWQgcGh5c2ljYWwgdGVuYW50LlxuICAgKiBAdGVtcGxhdGUgVFxuICAgKiBAcGFyYW0geyhxdWVyeTogaW1wb3J0KFwiLi4vZGF0YWJhc2UvcXVlcnkvbW9kZWwtY2xhc3MtcXVlcnkuanNcIikuZGVmYXVsdDxNQz4sIG9wZXJhdGlvbjogaW1wb3J0KFwiLi4vZGF0YWJhc2Uvb3BlcmF0aW9uLmpzXCIpLmRlZmF1bHQpID0+IFByb21pc2U8VD59IGNhbGxiYWNrIC0gVHJhbnNhY3Rpb24gY2FsbGJhY2suXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPFQ+fSAtIENhbGxiYWNrIHJlc3VsdC5cbiAgICovXG4gIGFzeW5jIHRyYW5zYWN0aW9uKGNhbGxiYWNrKSB7XG4gICAgcmV0dXJuIGF3YWl0IHRoaXMuX2hhbmRsZS50cmFuc2FjdGlvbih7XG4gICAgICBkYXRhYmFzZUlkZW50aWZpZXI6IHRoaXMuX2RhdGFiYXNlSWRlbnRpZmllcixcbiAgICAgIG5hbWU6IGB1c2luZ1RlbmFudCB0cmFuc2FjdGlvbjogJHt0aGlzLl9tb2RlbENsYXNzLmdldE1vZGVsTmFtZSgpfWBcbiAgICB9LCBhc3luYyAob3BlcmF0aW9uKSA9PiB7XG4gICAgICBhd2FpdCBvcGVyYXRpb24uZW5zdXJlTW9kZWxJbml0aWFsaXplZCh0aGlzLl9tb2RlbENsYXNzKVxuXG4gICAgICByZXR1cm4gYXdhaXQgY2FsbGJhY2sob3BlcmF0aW9uLmZvck1vZGVsKHRoaXMuX21vZGVsQ2xhc3MpLCBvcGVyYXRpb24pXG4gICAgfSlcbiAgfVxuXG4gIC8qKlxuICAgKiBDcmVhdGVzIGEgcmVjb3JkIG9uIHRoZSBjYXB0dXJlZCB0ZW5hbnQuXG4gICAqIEBwYXJhbSB7UmVjb3JkPHN0cmluZywgUmV0dXJuVHlwZTx0eXBlb2YgSlNPTi5wYXJzZT4+fSBbYXR0cmlidXRlc10gLSBDcmVhdGUgYXR0cmlidXRlcy5cbiAgICogQHJldHVybnMge1Byb21pc2U8SW5zdGFuY2VUeXBlPE1DPj59IC0gQ3JlYXRlZCByZWNvcmQuXG4gICAqL1xuICBhc3luYyBjcmVhdGUoYXR0cmlidXRlcyA9IHt9KSB7XG4gICAgcmV0dXJuIGF3YWl0IHRoaXMuZGF0YWJhc2VPcGVyYXRpb24oYXN5bmMgKHF1ZXJ5LCBvcGVyYXRpb24pID0+IHtcbiAgICAgIGNvbnN0IHJlY29yZCA9IGF3YWl0IHF1ZXJ5LmNyZWF0ZShhdHRyaWJ1dGVzKVxuXG4gICAgICByZXR1cm4gcmVjb3JkLnJlbGVhc2VEYXRhYmFzZU9wZXJhdGlvbihvcGVyYXRpb24pXG4gICAgfSlcbiAgfVxuXG4gIC8qKlxuICAgKiBDb3VudHMgcmVjb3JkcyBvbiB0aGUgY2FwdHVyZWQgdGVuYW50LlxuICAgKiBAcGFyYW0ge3N0cmluZyB8IG51bGx9IFtjb2x1bW5dIC0gQXR0cmlidXRlIG9yIGNvbHVtbiB0byBjb3VudC4gRGVmYXVsdHMgdG8gdGhlIHByaW1hcnkga2V5LlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxudW1iZXI+fSAtIENvdW50IG9uIHRoZSBjYXB0dXJlZCB0ZW5hbnQuXG4gICAqL1xuICBhc3luYyBjb3VudChjb2x1bW4gPSBudWxsKSB7XG4gICAgcmV0dXJuIGF3YWl0IHRoaXMuZGF0YWJhc2VPcGVyYXRpb24oYXN5bmMgKHF1ZXJ5KSA9PiBhd2FpdCBxdWVyeS5jb3VudChjb2x1bW4pKVxuICB9XG5cbiAgLyoqXG4gICAqIEZpbmRzIGEgcmVjb3JkIGJ5IHByaW1hcnkga2V5IG9uIHRoZSBjYXB0dXJlZCB0ZW5hbnQuXG4gICAqIEBwYXJhbSB7c3RyaW5nIHwgbnVtYmVyfSByZWNvcmRJZCAtIFJlY29yZCBpZGVudGlmaWVyLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxJbnN0YW5jZVR5cGU8TUM+IHwgbnVsbD59IC0gRm91bmQgcmVjb3JkLlxuICAgKi9cbiAgYXN5bmMgZmluZChyZWNvcmRJZCkge1xuICAgIHJldHVybiBhd2FpdCB0aGlzLmRhdGFiYXNlT3BlcmF0aW9uKGFzeW5jIChxdWVyeSwgb3BlcmF0aW9uKSA9PiB7XG4gICAgICBjb25zdCByZWNvcmQgPSBhd2FpdCBxdWVyeS5maW5kKHJlY29yZElkKVxuXG4gICAgICByZXR1cm4gcmVjb3JkID8gcmVjb3JkLnJlbGVhc2VEYXRhYmFzZU9wZXJhdGlvbihvcGVyYXRpb24pIDogbnVsbFxuICAgIH0pXG4gIH1cblxuICAvKipcbiAgICogRmluZHMgYSByZWNvcmQgYnkgY29uZGl0aW9ucyBvbiB0aGUgY2FwdHVyZWQgdGVuYW50LlxuICAgKiBAcGFyYW0ge1JlY29yZDxzdHJpbmcsIHN0cmluZyB8IG51bWJlcj59IGNvbmRpdGlvbnMgLSBGaW5kZXIgY29uZGl0aW9ucy5cbiAgICogQHJldHVybnMge1Byb21pc2U8SW5zdGFuY2VUeXBlPE1DPiB8IG51bGw+fSAtIEZvdW5kIHJlY29yZC5cbiAgICovXG4gIGFzeW5jIGZpbmRCeShjb25kaXRpb25zKSB7XG4gICAgcmV0dXJuIGF3YWl0IHRoaXMuZGF0YWJhc2VPcGVyYXRpb24oYXN5bmMgKHF1ZXJ5LCBvcGVyYXRpb24pID0+IHtcbiAgICAgIGNvbnN0IHJlY29yZCA9IGF3YWl0IHF1ZXJ5LmZpbmRCeShjb25kaXRpb25zKVxuXG4gICAgICByZXR1cm4gcmVjb3JkID8gcmVjb3JkLnJlbGVhc2VEYXRhYmFzZU9wZXJhdGlvbihvcGVyYXRpb24pIDogbnVsbFxuICAgIH0pXG4gIH1cblxuICAvKipcbiAgICogRmluZHMgYSByZWNvcmQgYnkgY29uZGl0aW9ucyBvciByYWlzZXMgb24gdGhlIGNhcHR1cmVkIHRlbmFudC5cbiAgICogQHBhcmFtIHtSZWNvcmQ8c3RyaW5nLCBzdHJpbmcgfCBudW1iZXI+fSBjb25kaXRpb25zIC0gRmluZGVyIGNvbmRpdGlvbnMuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPEluc3RhbmNlVHlwZTxNQz4+fSAtIEZvdW5kIHJlY29yZC5cbiAgICovXG4gIGFzeW5jIGZpbmRCeU9yRmFpbChjb25kaXRpb25zKSB7XG4gICAgcmV0dXJuIGF3YWl0IHRoaXMuZGF0YWJhc2VPcGVyYXRpb24oYXN5bmMgKHF1ZXJ5LCBvcGVyYXRpb24pID0+IHtcbiAgICAgIGNvbnN0IHJlY29yZCA9IGF3YWl0IHF1ZXJ5LmZpbmRCeU9yRmFpbChjb25kaXRpb25zKVxuXG4gICAgICByZXR1cm4gcmVjb3JkLnJlbGVhc2VEYXRhYmFzZU9wZXJhdGlvbihvcGVyYXRpb24pXG4gICAgfSlcbiAgfVxuXG4gIC8qKlxuICAgKiBMb2FkcyBhbGwgcmVjb3JkcyBvbiB0aGUgY2FwdHVyZWQgdGVuYW50LlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxJbnN0YW5jZVR5cGU8TUM+W10+fSAtIEFsbCByZWNvcmRzIG9uIHRoZSBjYXB0dXJlZCB0ZW5hbnQuXG4gICAqL1xuICBhc3luYyB0b0FycmF5KCkge1xuICAgIHJldHVybiBhd2FpdCB0aGlzLmRhdGFiYXNlT3BlcmF0aW9uKGFzeW5jIChxdWVyeSwgb3BlcmF0aW9uKSA9PiB7XG4gICAgICBjb25zdCByZWNvcmRzID0gYXdhaXQgcXVlcnkudG9BcnJheSgpXG5cbiAgICAgIHJldHVybiByZWNvcmRzLm1hcCgocmVjb3JkKSA9PiByZWNvcmQucmVsZWFzZURhdGFiYXNlT3BlcmF0aW9uKG9wZXJhdGlvbikpXG4gICAgfSlcbiAgfVxufVxuIl19
@@ -12,7 +12,9 @@
12
12
  * @property {() => unknown} id - Sync row identifier.
13
13
  * @property {() => unknown} resourceId - Resource identifier.
14
14
  * @property {() => string | null} resourceType - Resource type name.
15
+ * @property {() => number | null} serverSequence - Monotonic server change sequence, when the source provided one.
15
16
  * @property {() => string} syncType - Sync operation type.
17
+ * @property {() => string | null} updatedAt - Server change timestamp, when the source provided one.
16
18
  */
17
19
 
18
20
  /**
@@ -527,10 +527,27 @@ export default class SyncApiClient {
527
527
  id: () => syncPayload.id,
528
528
  resourceId: () => syncPayload.resourceId,
529
529
  resourceType: () => syncPayload.resourceType === null || syncPayload.resourceType === undefined ? null : String(syncPayload.resourceType),
530
- syncType: () => syncPayload.syncType === null || syncPayload.syncType === undefined ? "" : String(syncPayload.syncType)
530
+ serverSequence: () => this.normalizedServerSequence(syncPayload.serverSequence),
531
+ syncType: () => syncPayload.syncType === null || syncPayload.syncType === undefined ? "" : String(syncPayload.syncType),
532
+ updatedAt: () => syncPayload.updatedAt === null || syncPayload.updatedAt === undefined ? null : String(syncPayload.updatedAt)
531
533
  }
532
534
  }
533
535
 
536
+ /**
537
+ * Normalizes a raw server sequence into a positive safe integer.
538
+ * @param {unknown} value - Raw sequence.
539
+ * @returns {number | null} Normalized sequence, or null when absent/invalid.
540
+ */
541
+ static normalizedServerSequence(value) {
542
+ if (value === null || value === undefined || value === "") return null
543
+
544
+ const sequence = Number(value)
545
+
546
+ if (!Number.isSafeInteger(sequence) || sequence <= 0) return null
547
+
548
+ return sequence
549
+ }
550
+
534
551
  /**
535
552
  * Builds an app-configured resource applier for pulled sync rows. The sync
536
553
  * mechanics stay here; apps only declare which models/attributes/hooks are
@@ -0,0 +1,298 @@
1
+ // @ts-check
2
+
3
+ import TableData from "../database/table-data/index.js"
4
+ import TableIndex from "../database/table-data/table-index.js"
5
+ import UUID from "pure-uuid"
6
+
7
+ /**
8
+ * @typedef {object} SyncAppliedSequenceRow
9
+ * @property {string} resourceId - Resource identifier the change applied to.
10
+ * @property {string} resourceType - Resource/model name the change applied to.
11
+ * @property {number} serverSequence - Highest applied server sequence for the resource.
12
+ * @property {string} storeIdentity - Physical store identity owning this row.
13
+ */
14
+ const TABLE_NAME = "velocious_sync_applied_sequences"
15
+ const IDENTITY_INDEX_NAME = "index_velocious_sync_applied_sequences_on_identity"
16
+
17
+ /**
18
+ * Builds the stable in-memory key for one resource identity.
19
+ * @param {{resourceType: string, resourceId: string}} args - Resource identity.
20
+ * @returns {string} Memory key.
21
+ */
22
+ function memoryKey({resourceId, resourceType}) {
23
+ return `${resourceType}:${resourceId}`
24
+ }
25
+
26
+ /**
27
+ * Framework-owned local bookkeeping of the newest server change sequence
28
+ * already applied to each record.
29
+ *
30
+ * The server change feed is at-least-once: a realtime push does not advance a
31
+ * pull cursor, so the next pull re-delivers the same `serverSequence`, and a
32
+ * resumed page can repeat a row. Applying such a duplicate after a newer local
33
+ * provisional write used to let the older server payload win. Tracking the
34
+ * highest applied sequence per record makes the applier idempotent: a change at
35
+ * or below the recorded sequence is skipped, while any higher sequence still
36
+ * applies.
37
+ *
38
+ * Backed by an auto-created `velocious_sync_applied_sequences` table on the
39
+ * configured database, with a process-local memory fallback when no database is
40
+ * configured (mirroring the scope store).
41
+ */
42
+ export default class SyncAppliedSequenceStore {
43
+ /**
44
+ * Creates an applied-sequence store.
45
+ * @param {object} args - Options.
46
+ * @param {import("../configuration.js").default} args.configuration - Configuration owning the database.
47
+ * @param {string} [args.databaseIdentifier] - Database identifier.
48
+ * @param {import("../tenants/tenant-handle.js").default} [args.tenantHandle] - Immutable tenant handle owning the physical store.
49
+ */
50
+ constructor({configuration, databaseIdentifier = "default", tenantHandle}) {
51
+ if (tenantHandle) tenantHandle.assertConfiguration(configuration)
52
+
53
+ this.configuration = configuration
54
+ this.databaseIdentifier = databaseIdentifier
55
+ this.tenantHandle = tenantHandle
56
+ this.storeIdentity = tenantHandle
57
+ ? tenantHandle.databaseIdentity(databaseIdentifier)
58
+ : `configuration:${databaseIdentifier}`
59
+ /** @type {Map<string, number>} */
60
+ this._memorySequences = new Map()
61
+ /** @type {Map<string, Promise<void>>} */
62
+ this._writeQueues = new Map()
63
+ this._isReady = false
64
+ /** @type {Promise<void> | null} */
65
+ this._readyPromise = null
66
+ /** @type {WeakMap<import("../database/drivers/base.js").default, {completion: Promise<void>, promise: Promise<void>}>} */
67
+ this._transactionReadyPromises = new WeakMap()
68
+ }
69
+
70
+ /**
71
+ * Ensures the backing table exists.
72
+ * @returns {Promise<void>} Resolves when ready.
73
+ */
74
+ async ensureReady() {
75
+ if (this._isReady) return
76
+
77
+ if (this._usesMemoryStorage()) {
78
+ this._isReady = true
79
+ return
80
+ }
81
+
82
+ await this._withDb(async (db) => await this._ensureReadyWithDb(db))
83
+ }
84
+
85
+ /**
86
+ * Returns the highest server sequence already applied to a record.
87
+ * @param {{resourceType: string, resourceId: string}} identity - Resource identity.
88
+ * @returns {Promise<number | null>} Applied sequence, or null when none is recorded.
89
+ */
90
+ async lastAppliedSequence(identity) {
91
+ await this.ensureReady()
92
+
93
+ if (this._usesMemoryStorage()) {
94
+ return this._memorySequences.get(memoryKey(identity)) ?? null
95
+ }
96
+
97
+ return await this._withDb(async (db) => {
98
+ const row = await this._rowByResource(db, identity)
99
+
100
+ return row ? row.serverSequence : null
101
+ })
102
+ }
103
+
104
+ /**
105
+ * Records a successfully applied server sequence for a record, keeping only
106
+ * the highest value. Writes for the same record are serialized in-process so
107
+ * concurrent pull/realtime applies cannot move the watermark backwards.
108
+ * @param {{resourceType: string, resourceId: string, serverSequence: number}} args - Applied change identity and sequence.
109
+ * @returns {Promise<void>} Resolves when recorded.
110
+ */
111
+ async recordAppliedSequence({resourceId, resourceType, serverSequence}) {
112
+ await this.ensureReady()
113
+
114
+ const identity = {resourceId, resourceType}
115
+ const key = memoryKey(identity)
116
+ const previousWrite = this._writeQueues.get(key) || Promise.resolve()
117
+ const write = previousWrite
118
+ .then(async () => await this._recordSequential({identity, key, serverSequence}))
119
+ .finally(() => {
120
+ if (this._writeQueues.get(key) === write) this._writeQueues.delete(key)
121
+ })
122
+
123
+ this._writeQueues.set(key, write)
124
+ await write
125
+ }
126
+
127
+ /**
128
+ * Applies one serialized watermark write.
129
+ * @param {{identity: {resourceType: string, resourceId: string}, key: string, serverSequence: number}} args - Write args.
130
+ * @returns {Promise<void>} Resolves when written.
131
+ */
132
+ async _recordSequential({identity, key, serverSequence}) {
133
+ const currentSequence = await this.lastAppliedSequence(identity)
134
+
135
+ if (currentSequence !== null && currentSequence >= serverSequence) return
136
+
137
+ if (this._usesMemoryStorage()) {
138
+ this._memorySequences.set(key, serverSequence)
139
+ return
140
+ }
141
+
142
+ await this._withDb(async (db) => {
143
+ await db.upsert({
144
+ conflictColumns: ["resource_type", "resource_id"],
145
+ data: {
146
+ created_at: new Date(),
147
+ id: new UUID(4).format(),
148
+ resource_id: identity.resourceId,
149
+ resource_type: identity.resourceType,
150
+ server_sequence: serverSequence,
151
+ updated_at: new Date()
152
+ },
153
+ tableName: TABLE_NAME,
154
+ updateColumns: ["server_sequence", "updated_at"]
155
+ })
156
+ })
157
+ }
158
+
159
+ /**
160
+ * Coordinates durable and transaction-local readiness on one connection.
161
+ * @param {import("../database/drivers/base.js").default} db - Database connection.
162
+ * @returns {Promise<void>} Resolves when this caller can use the table.
163
+ */
164
+ async _ensureReadyWithDb(db) {
165
+ if (this._isReady) return
166
+
167
+ const transactionCompletion = db.insideTransaction() ? db.transactionCompletion() : null
168
+ const transactionReady = this._transactionReadyPromises.get(db)
169
+
170
+ if (transactionCompletion && transactionReady?.completion === transactionCompletion) {
171
+ await transactionReady.promise
172
+ return
173
+ }
174
+
175
+ if (this._readyPromise) {
176
+ const readyPromise = this._readyPromise
177
+
178
+ await readyPromise
179
+ if (this._readyPromise === readyPromise) this._readyPromise = null
180
+ if (this._isReady) return
181
+
182
+ await this.ensureReady()
183
+ return
184
+ }
185
+
186
+ if (transactionCompletion) {
187
+ const tableReadyPromise = this._ensureTable(db)
188
+ const transactionReadyPromise = tableReadyPromise.then(() => undefined)
189
+
190
+ const durableReadyPromise = tableReadyPromise.then(async (created) => {
191
+ if (!created) {
192
+ this._isReady = true
193
+ return
194
+ }
195
+
196
+ await transactionCompletion
197
+ })
198
+
199
+ this._transactionReadyPromises.set(db, {completion: transactionCompletion, promise: transactionReadyPromise})
200
+ this._readyPromise = durableReadyPromise
201
+ await transactionReadyPromise
202
+ return
203
+ }
204
+
205
+ this._readyPromise = this._ensureTable(db).then(() => {
206
+ this._isReady = true
207
+ })
208
+
209
+ try {
210
+ await this._readyPromise
211
+ } finally {
212
+ if (!this._isReady) this._readyPromise = null
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Whether the store runs without a configured database.
218
+ * @returns {boolean} Whether memory storage is used.
219
+ */
220
+ _usesMemoryStorage() {
221
+ try {
222
+ return !this.configuration.getDatabaseConfiguration()[this.databaseIdentifier]
223
+ } catch {
224
+ return true
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Runs a callback with a database connection.
230
+ * @template Result
231
+ * @param {(db: import("../database/drivers/base.js").default) => Promise<Result>} callback - Database callback.
232
+ * @returns {Promise<Result>} Callback result.
233
+ */
234
+ async _withDb(callback) {
235
+ if (this.tenantHandle) {
236
+ return await this.tenantHandle.databaseOperation({databaseIdentifier: this.databaseIdentifier, name: "Tenant sync applied-sequence store"}, async (operation) => {
237
+ return await callback(operation.connection())
238
+ })
239
+ }
240
+
241
+ return await this.configuration.ensureConnections({databaseIdentifiers: [this.databaseIdentifier], name: "Sync applied-sequence store"}, async (dbs) => {
242
+ const db = dbs[this.databaseIdentifier]
243
+
244
+ if (!db) throw new Error(`No database connection available for identifier: ${this.databaseIdentifier}`)
245
+
246
+ return await callback(db)
247
+ })
248
+ }
249
+
250
+ /**
251
+ * Ensures the applied-sequences table exists.
252
+ * @param {import("../database/drivers/base.js").default} db - Database connection.
253
+ * @returns {Promise<boolean>} Whether the table had to be created.
254
+ */
255
+ async _ensureTable(db) {
256
+ if (await db.tableExists(TABLE_NAME)) return false
257
+
258
+ const table = new TableData(TABLE_NAME, {ifNotExists: true})
259
+
260
+ table.string("id", {null: false, primaryKey: true})
261
+ table.string("resource_type", {null: false})
262
+ table.string("resource_id", {null: false})
263
+ table.bigint("server_sequence", {null: false})
264
+ table.datetime("created_at", {null: false})
265
+ table.datetime("updated_at", {null: false})
266
+ table.addIndex(new TableIndex(["resource_type", "resource_id"], {name: IDENTITY_INDEX_NAME, unique: true}))
267
+
268
+ await db.createTable(table)
269
+
270
+ return true
271
+ }
272
+
273
+ /**
274
+ * Resolves the stored row for one resource identity.
275
+ * @param {import("../database/drivers/base.js").default} db - Database connection.
276
+ * @param {{resourceType: string, resourceId: string}} identity - Resource identity.
277
+ * @returns {Promise<SyncAppliedSequenceRow | null>} Stored row, or null.
278
+ */
279
+ async _rowByResource(db, identity) {
280
+ const rows = /** @type {Array<Record<string, ReturnType<typeof JSON.parse>>>} */ (await db
281
+ .newQuery()
282
+ .from(TABLE_NAME)
283
+ .where({resource_id: identity.resourceId, resource_type: identity.resourceType})
284
+ .limit(1)
285
+ .results())
286
+
287
+ const row = rows[0]
288
+
289
+ if (!row) return null
290
+
291
+ return {
292
+ resourceId: String(row.resource_id),
293
+ resourceType: String(row.resource_type),
294
+ serverSequence: Number(row.server_sequence),
295
+ storeIdentity: this.storeIdentity
296
+ }
297
+ }
298
+ }
@@ -10,6 +10,7 @@ import VelociousWebsocketClient from "../http-client/websocket-client.js"
10
10
 
11
11
  import {serializedScopeFromQuery} from "./query-scope.js"
12
12
  import SyncApiClient from "./sync-api-client.js"
13
+ import SyncAppliedSequenceStore from "./sync-applied-sequence-store.js"
13
14
  import SyncOfflineSession from "./sync-offline-session.js"
14
15
  import SyncRealtimeBridge from "./sync-realtime-bridge.js"
15
16
  import SyncScopeStore from "./sync-scope-store.js"
@@ -183,6 +184,8 @@ export default class SyncClient {
183
184
  this._userScopeState = "unsubscribed"
184
185
  /** @type {import("./sync-scope-store.js").default | null} */
185
186
  this._scopeStore = scopeStore || null
187
+ /** @type {import("./sync-applied-sequence-store.js").default | null} */
188
+ this._appliedSequenceStore = null
186
189
  /** @type {Promise<void> | null} */
187
190
  this._scheduledReplay = null
188
191
  /** @type {((reason: "mutation" | "realtime") => Promise<void>) | null} */
@@ -832,6 +835,13 @@ export default class SyncClient {
832
835
  * applies through the declared resource configs, registers each written record
833
836
  * for echo suppression (tracked resources do not re-queue applied changes), and
834
837
  * fails loudly instead of silently skipping unconfigured resources.
838
+ *
839
+ * Applies are idempotent per record: the feed is at-least-once (a realtime push
840
+ * does not advance the pull cursor, so the same `serverSequence` arrives again
841
+ * on the next pull). A change whose sequence is not greater than the newest one
842
+ * already applied to that record is skipped, so a duplicate cannot overwrite
843
+ * newer local state. Sources without a sequence (e.g. legacy broadcasts) keep
844
+ * applying unchanged.
835
845
  * @param {{source?: string}} [args] - Error context describing where the change came from.
836
846
  * @returns {(sync: import("./sync-api-client-types.js").SyncChangeEnvelope) => Promise<import("./sync-api-client-types.js").SyncChangeApplyResult>} Loud remote-change applier.
837
847
  */
@@ -846,6 +856,18 @@ export default class SyncClient {
846
856
 
847
857
  return await this.withTenantOperation(async (operation) => {
848
858
  const data = sync.data()
859
+ const resourceId = String(sync.resourceId())
860
+ const serverSequence = sync.serverSequence()
861
+ const appliedSequences = this.appliedSequenceStore()
862
+
863
+ if (serverSequence !== null) {
864
+ const lastAppliedSequence = await appliedSequences.lastAppliedSequence({resourceId, resourceType})
865
+
866
+ if (lastAppliedSequence !== null && serverSequence <= lastAppliedSequence) {
867
+ return {changed: false, resourceType}
868
+ }
869
+ }
870
+
849
871
  const versionAttribute = this.config.resources[resourceType].conflictTracking?.versionAttribute
850
872
 
851
873
  if (versionAttribute) {
@@ -853,7 +875,7 @@ export default class SyncClient {
853
875
  ? /** @type {Record<string, ReturnType<typeof JSON.parse>>} */ (data)
854
876
  : {}
855
877
 
856
- this.noteRemoteVersion({resourceId: String(sync.resourceId()), resourceType, version: dataAttributes[versionAttribute]})
878
+ this.noteRemoteVersion({resourceId, resourceType, version: dataAttributes[versionAttribute]})
857
879
  }
858
880
 
859
881
  const pullResourceConfigs = this.pullResourceConfigs(operation)
@@ -863,7 +885,13 @@ export default class SyncClient {
863
885
  return this.markRemoteApply(record)
864
886
  })
865
887
 
866
- return await applier(sync)
888
+ const result = await applier(sync)
889
+
890
+ if (serverSequence !== null) {
891
+ await appliedSequences.recordAppliedSequence({resourceId, resourceType, serverSequence})
892
+ }
893
+
894
+ return result
867
895
  })
868
896
  }
869
897
  }
@@ -1620,6 +1648,27 @@ export default class SyncClient {
1620
1648
  return this._scopeStore
1621
1649
  }
1622
1650
 
1651
+ /**
1652
+ * Returns the per-record applied-sequence store that keeps remote applies
1653
+ * idempotent across pull/realtime duplication.
1654
+ * @returns {import("./sync-applied-sequence-store.js").default} Applied-sequence store.
1655
+ */
1656
+ appliedSequenceStore() {
1657
+ this.assertTenantReady()
1658
+
1659
+ if (this._appliedSequenceStore && this._databaseIdentity && this._appliedSequenceStore.storeIdentity !== this._databaseIdentity) {
1660
+ throw new Error("SyncClient applied-sequence store belongs to another or unresolved physical tenant database")
1661
+ }
1662
+
1663
+ this._appliedSequenceStore ||= new SyncAppliedSequenceStore({
1664
+ configuration: this.config.configuration,
1665
+ databaseIdentifier: this.config.databaseIdentifier,
1666
+ tenantHandle: this.config.tenantHandle
1667
+ })
1668
+
1669
+ return this._appliedSequenceStore
1670
+ }
1671
+
1623
1672
  /**
1624
1673
  * Resolves the declared resource config for a local record.
1625
1674
  * @param {ReturnType<typeof JSON.parse>} resource - Local model record.
@@ -73,10 +73,11 @@ export default class TenantModelScope {
73
73
 
74
74
  /**
75
75
  * Counts records on the captured tenant.
76
+ * @param {string | null} [column] - Attribute or column to count. Defaults to the primary key.
76
77
  * @returns {Promise<number>} - Count on the captured tenant.
77
78
  */
78
- async count() {
79
- return await this.databaseOperation(async (query) => await query.count())
79
+ async count(column = null) {
80
+ return await this.databaseOperation(async (query) => await query.count(column))
80
81
  }
81
82
 
82
83
  /**
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "velocious": "build/bin/velocious.js"
4
4
  },
5
5
  "name": "velocious",
6
- "version": "1.0.716",
6
+ "version": "1.0.718",
7
7
  "main": "build/index.js",
8
8
  "types": "build/index.d.ts",
9
9
  "files": [
@@ -4324,12 +4324,11 @@ export default class VelociousConfiguration {
4324
4324
  if (!config.backends.lockServer.enabled) return undefined
4325
4325
  if (this._advisoryLockLockServerBackend) return this._advisoryLockLockServerBackend
4326
4326
 
4327
- const {default: AdvisoryLockLockServerBackend} = await import("./database/advisory-lock-lock-server-backend.js")
4328
- const backend = new AdvisoryLockLockServerBackend({
4327
+ const backend = await this.getEnvironmentHandler().createAdvisoryLockLockServerBackend({
4329
4328
  authToken: config.backends.lockServer.authToken,
4330
4329
  connectTimeoutMs: config.backends.lockServer.connectTimeoutMs,
4331
4330
  leaseMs: config.backends.lockServer.leaseMs,
4332
- onError: (error) => {
4331
+ onError: (/** @type {Error} */ error) => {
4333
4332
  this.getErrorEvents().emit("framework-error", {context: "advisory-lock-lock-server", error})
4334
4333
  this.getErrorEvents().emit("all-error", {context: "advisory-lock-lock-server", error, errorType: "framework-error"})
4335
4334
  },