@tier0/sdk-v2 0.0.0-stage → 0.2.1
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/LICENSE +21 -0
- package/NOTICE.md +7 -0
- package/README.md +201 -2
- package/dist/cjs/app-identity-contract.generated.d.ts +22 -0
- package/dist/cjs/app-identity-contract.generated.js +51 -0
- package/dist/cjs/app-identity-contract.generated.js.map +1 -0
- package/dist/cjs/capabilities.d.ts +54 -0
- package/dist/cjs/capabilities.js +134 -0
- package/dist/cjs/capabilities.js.map +1 -0
- package/dist/cjs/client.d.ts +604 -0
- package/dist/cjs/client.js +35 -0
- package/dist/cjs/client.js.map +1 -0
- package/dist/cjs/cloud-server.d.ts +31 -0
- package/dist/cjs/cloud-server.js +114 -0
- package/dist/cjs/cloud-server.js.map +1 -0
- package/dist/cjs/cloud-transport.d.ts +67 -0
- package/dist/cjs/cloud-transport.js +272 -0
- package/dist/cjs/cloud-transport.js.map +1 -0
- package/dist/cjs/controls.d.ts +93 -0
- package/dist/cjs/controls.js +169 -0
- package/dist/cjs/controls.js.map +1 -0
- package/dist/cjs/data/bootstrap.d.ts +70 -0
- package/dist/cjs/data/bootstrap.js +291 -0
- package/dist/cjs/data/bootstrap.js.map +1 -0
- package/dist/cjs/data/call.d.ts +33 -0
- package/dist/cjs/data/call.js +216 -0
- package/dist/cjs/data/call.js.map +1 -0
- package/dist/cjs/data/client.d.ts +134 -0
- package/dist/cjs/data/client.js +185 -0
- package/dist/cjs/data/client.js.map +1 -0
- package/dist/cjs/data/collection.d.ts +335 -0
- package/dist/cjs/data/collection.js +601 -0
- package/dist/cjs/data/collection.js.map +1 -0
- package/dist/cjs/data/context.d.ts +12 -0
- package/dist/cjs/data/context.js +50 -0
- package/dist/cjs/data/context.js.map +1 -0
- package/dist/cjs/data/declarations.d.ts +11 -0
- package/dist/cjs/data/declarations.js +18 -0
- package/dist/cjs/data/declarations.js.map +1 -0
- package/dist/cjs/data/define.d.ts +77 -0
- package/dist/cjs/data/define.js +31 -0
- package/dist/cjs/data/define.js.map +1 -0
- package/dist/cjs/data/error.d.ts +78 -0
- package/dist/cjs/data/error.js +182 -0
- package/dist/cjs/data/error.js.map +1 -0
- package/dist/cjs/data/index.d.ts +24 -0
- package/dist/cjs/data/index.js +49 -0
- package/dist/cjs/data/index.js.map +1 -0
- package/dist/cjs/data/schema.d.ts +88 -0
- package/dist/cjs/data/schema.js +657 -0
- package/dist/cjs/data/schema.js.map +1 -0
- package/dist/cjs/data/sort.d.ts +8 -0
- package/dist/cjs/data/sort.js +21 -0
- package/dist/cjs/data/sort.js.map +1 -0
- package/dist/cjs/data/subscribe.d.ts +108 -0
- package/dist/cjs/data/subscribe.js +270 -0
- package/dist/cjs/data/subscribe.js.map +1 -0
- package/dist/cjs/files.d.ts +67 -0
- package/dist/cjs/files.js +119 -0
- package/dist/cjs/files.js.map +1 -0
- package/dist/cjs/framework-scope.d.ts +1 -0
- package/dist/cjs/framework-scope.js +14 -0
- package/dist/cjs/framework-scope.js.map +1 -0
- package/dist/cjs/index.d.ts +14 -0
- package/dist/cjs/index.js +33 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/mq/broker.d.ts +65 -0
- package/dist/cjs/mq/broker.js +115 -0
- package/dist/cjs/mq/broker.js.map +1 -0
- package/dist/cjs/mq/client.d.ts +37 -0
- package/dist/cjs/mq/client.js +321 -0
- package/dist/cjs/mq/client.js.map +1 -0
- package/dist/cjs/mq/index.d.ts +5 -0
- package/dist/cjs/mq/index.js +11 -0
- package/dist/cjs/mq/index.js.map +1 -0
- package/dist/cjs/mq/types.d.ts +48 -0
- package/dist/cjs/mq/types.js +3 -0
- package/dist/cjs/mq/types.js.map +1 -0
- package/dist/cjs/openapi/api.generated.d.ts +820 -0
- package/dist/cjs/openapi/api.generated.js +607 -0
- package/dist/cjs/openapi/api.generated.js.map +1 -0
- package/dist/cjs/openapi/client.d.ts +56 -0
- package/dist/cjs/openapi/client.js +219 -0
- package/dist/cjs/openapi/client.js.map +1 -0
- package/dist/cjs/openapi/codecs.generated.d.ts +2234 -0
- package/dist/cjs/openapi/codecs.generated.js +6 -0
- package/dist/cjs/openapi/codecs.generated.js.map +1 -0
- package/dist/cjs/openapi/index.d.ts +5 -0
- package/dist/cjs/openapi/index.js +12 -0
- package/dist/cjs/openapi/index.js.map +1 -0
- package/dist/cjs/openapi/types.generated.d.ts +3227 -0
- package/dist/cjs/openapi/types.generated.js +3 -0
- package/dist/cjs/openapi/types.generated.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/react.d.ts +9 -0
- package/dist/cjs/react.js +16 -0
- package/dist/cjs/react.js.map +1 -0
- package/dist/cjs/runtime-context.d.ts +9 -0
- package/dist/cjs/runtime-context.js +25 -0
- package/dist/cjs/runtime-context.js.map +1 -0
- package/dist/cjs/runtime-env.d.ts +1 -0
- package/dist/cjs/runtime-env.js +18 -0
- package/dist/cjs/runtime-env.js.map +1 -0
- package/dist/cjs/source.d.ts +39 -0
- package/dist/cjs/source.js +29 -0
- package/dist/cjs/source.js.map +1 -0
- package/dist/cjs/stream.d.ts +40 -0
- package/dist/cjs/stream.js +33 -0
- package/dist/cjs/stream.js.map +1 -0
- package/dist/cjs/uns-v2/client.d.ts +132 -0
- package/dist/cjs/uns-v2/client.js +352 -0
- package/dist/cjs/uns-v2/client.js.map +1 -0
- package/dist/cjs/uns-v2/codec.d.ts +11 -0
- package/dist/cjs/uns-v2/codec.js +290 -0
- package/dist/cjs/uns-v2/codec.js.map +1 -0
- package/dist/cjs/uns-v2/data-client.d.ts +10 -0
- package/dist/cjs/uns-v2/data-client.js +18 -0
- package/dist/cjs/uns-v2/data-client.js.map +1 -0
- package/dist/cjs/uns-v2/generated.d.ts +18178 -0
- package/dist/cjs/uns-v2/generated.js +144 -0
- package/dist/cjs/uns-v2/generated.js.map +1 -0
- package/dist/cjs/uns-v2/index.d.ts +5 -0
- package/dist/cjs/uns-v2/index.js +24 -0
- package/dist/cjs/uns-v2/index.js.map +1 -0
- package/dist/cjs/uns-v2/sse.d.ts +20 -0
- package/dist/cjs/uns-v2/sse.js +65 -0
- package/dist/cjs/uns-v2/sse.js.map +1 -0
- package/dist/cjs/vue.d.ts +9 -0
- package/dist/cjs/vue.js +39 -0
- package/dist/cjs/vue.js.map +1 -0
- package/dist/esm/app-identity-contract.generated.d.ts +22 -0
- package/dist/esm/app-identity-contract.generated.js +48 -0
- package/dist/esm/app-identity-contract.generated.js.map +1 -0
- package/dist/esm/capabilities.d.ts +54 -0
- package/dist/esm/capabilities.js +129 -0
- package/dist/esm/capabilities.js.map +1 -0
- package/dist/esm/client.d.ts +604 -0
- package/dist/esm/client.js +32 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/cloud-server.d.ts +31 -0
- package/dist/esm/cloud-server.js +109 -0
- package/dist/esm/cloud-server.js.map +1 -0
- package/dist/esm/cloud-transport.d.ts +67 -0
- package/dist/esm/cloud-transport.js +267 -0
- package/dist/esm/cloud-transport.js.map +1 -0
- package/dist/esm/controls.d.ts +93 -0
- package/dist/esm/controls.js +163 -0
- package/dist/esm/controls.js.map +1 -0
- package/dist/esm/data/bootstrap.d.ts +70 -0
- package/dist/esm/data/bootstrap.js +286 -0
- package/dist/esm/data/bootstrap.js.map +1 -0
- package/dist/esm/data/call.d.ts +33 -0
- package/dist/esm/data/call.js +211 -0
- package/dist/esm/data/call.js.map +1 -0
- package/dist/esm/data/client.d.ts +134 -0
- package/dist/esm/data/client.js +181 -0
- package/dist/esm/data/client.js.map +1 -0
- package/dist/esm/data/collection.d.ts +335 -0
- package/dist/esm/data/collection.js +594 -0
- package/dist/esm/data/collection.js.map +1 -0
- package/dist/esm/data/context.d.ts +12 -0
- package/dist/esm/data/context.js +44 -0
- package/dist/esm/data/context.js.map +1 -0
- package/dist/esm/data/declarations.d.ts +11 -0
- package/dist/esm/data/declarations.js +10 -0
- package/dist/esm/data/declarations.js.map +1 -0
- package/dist/esm/data/define.d.ts +77 -0
- package/dist/esm/data/define.js +27 -0
- package/dist/esm/data/define.js.map +1 -0
- package/dist/esm/data/error.d.ts +78 -0
- package/dist/esm/data/error.js +176 -0
- package/dist/esm/data/error.js.map +1 -0
- package/dist/esm/data/index.d.ts +24 -0
- package/dist/esm/data/index.js +20 -0
- package/dist/esm/data/index.js.map +1 -0
- package/dist/esm/data/schema.d.ts +88 -0
- package/dist/esm/data/schema.js +646 -0
- package/dist/esm/data/schema.js.map +1 -0
- package/dist/esm/data/sort.d.ts +8 -0
- package/dist/esm/data/sort.js +18 -0
- package/dist/esm/data/sort.js.map +1 -0
- package/dist/esm/data/subscribe.d.ts +108 -0
- package/dist/esm/data/subscribe.js +265 -0
- package/dist/esm/data/subscribe.js.map +1 -0
- package/dist/esm/files.d.ts +67 -0
- package/dist/esm/files.js +113 -0
- package/dist/esm/files.js.map +1 -0
- package/dist/esm/framework-scope.d.ts +1 -0
- package/dist/esm/framework-scope.js +11 -0
- package/dist/esm/framework-scope.js.map +1 -0
- package/dist/esm/index.d.ts +14 -0
- package/dist/esm/index.js +10 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/mq/broker.d.ts +65 -0
- package/dist/esm/mq/broker.js +109 -0
- package/dist/esm/mq/broker.js.map +1 -0
- package/dist/esm/mq/client.d.ts +37 -0
- package/dist/esm/mq/client.js +314 -0
- package/dist/esm/mq/client.js.map +1 -0
- package/dist/esm/mq/index.d.ts +5 -0
- package/dist/esm/mq/index.js +3 -0
- package/dist/esm/mq/index.js.map +1 -0
- package/dist/esm/mq/types.d.ts +48 -0
- package/dist/esm/mq/types.js +2 -0
- package/dist/esm/mq/types.js.map +1 -0
- package/dist/esm/openapi/api.generated.d.ts +820 -0
- package/dist/esm/openapi/api.generated.js +603 -0
- package/dist/esm/openapi/api.generated.js.map +1 -0
- package/dist/esm/openapi/client.d.ts +56 -0
- package/dist/esm/openapi/client.js +210 -0
- package/dist/esm/openapi/client.js.map +1 -0
- package/dist/esm/openapi/codecs.generated.d.ts +2234 -0
- package/dist/esm/openapi/codecs.generated.js +3 -0
- package/dist/esm/openapi/codecs.generated.js.map +1 -0
- package/dist/esm/openapi/index.d.ts +5 -0
- package/dist/esm/openapi/index.js +3 -0
- package/dist/esm/openapi/index.js.map +1 -0
- package/dist/esm/openapi/types.generated.d.ts +3227 -0
- package/dist/esm/openapi/types.generated.js +2 -0
- package/dist/esm/openapi/types.generated.js.map +1 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/react.d.ts +9 -0
- package/dist/esm/react.js +12 -0
- package/dist/esm/react.js.map +1 -0
- package/dist/esm/runtime-context.d.ts +9 -0
- package/dist/esm/runtime-context.js +22 -0
- package/dist/esm/runtime-context.js.map +1 -0
- package/dist/esm/runtime-env.d.ts +1 -0
- package/dist/esm/runtime-env.js +15 -0
- package/dist/esm/runtime-env.js.map +1 -0
- package/dist/esm/source.d.ts +39 -0
- package/dist/esm/source.js +25 -0
- package/dist/esm/source.js.map +1 -0
- package/dist/esm/stream.d.ts +40 -0
- package/dist/esm/stream.js +29 -0
- package/dist/esm/stream.js.map +1 -0
- package/dist/esm/uns-v2/client.d.ts +132 -0
- package/dist/esm/uns-v2/client.js +343 -0
- package/dist/esm/uns-v2/client.js.map +1 -0
- package/dist/esm/uns-v2/codec.d.ts +11 -0
- package/dist/esm/uns-v2/codec.js +284 -0
- package/dist/esm/uns-v2/codec.js.map +1 -0
- package/dist/esm/uns-v2/data-client.d.ts +10 -0
- package/dist/esm/uns-v2/data-client.js +14 -0
- package/dist/esm/uns-v2/data-client.js.map +1 -0
- package/dist/esm/uns-v2/generated.d.ts +18178 -0
- package/dist/esm/uns-v2/generated.js +141 -0
- package/dist/esm/uns-v2/generated.js.map +1 -0
- package/dist/esm/uns-v2/index.d.ts +5 -0
- package/dist/esm/uns-v2/index.js +5 -0
- package/dist/esm/uns-v2/index.js.map +1 -0
- package/dist/esm/uns-v2/sse.d.ts +20 -0
- package/dist/esm/uns-v2/sse.js +60 -0
- package/dist/esm/uns-v2/sse.js.map +1 -0
- package/dist/esm/vue.d.ts +9 -0
- package/dist/esm/vue.js +36 -0
- package/dist/esm/vue.js.map +1 -0
- package/docs/acceptance-2026-10-22.md +88 -0
- package/package.json +243 -4
- package/skills/tier0-sdk-v2/SKILL.md +66 -0
- package/skills/tier0-sdk-v2/references/integration.md +72 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-agent/SKILL.md +73 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-agent/references/results.md +45 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-controls/SKILL.md +63 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-data/SKILL.md +58 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/consistency.md +70 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/errors.md +82 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/modeling.md +113 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/read-write.md +168 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/schema-evolution.md +36 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-files/SKILL.md +85 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-files/references/delete.md +22 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-files/references/download.md +49 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-files/references/multipart.md +42 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-files/references/upload.md +27 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-files/references/url.md +22 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-members/SKILL.md +56 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-members/references/app-members.md +41 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-members/references/platform-members.md +56 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-mq/SKILL.md +78 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-mq/references/quickstart.md +69 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-notifications/SKILL.md +42 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-notifications/references/get.md +34 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-notifications/references/send.md +72 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-source/SKILL.md +38 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/changes.md +40 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/create.md +63 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/delete.md +47 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/protocols.md +36 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/read.md +34 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/state.md +37 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/update.md +62 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/SKILL.md +43 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/create.md +56 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/delete.md +35 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/deploy.md +64 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/lifecycle.md +26 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/read.md +35 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/update.md +36 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-system/SKILL.md +64 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-system/references/info.md +23 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-system/references/whoami.md +21 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/SKILL.md +55 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/create.md +56 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/data-workflows.md +69 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/delete.md +53 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/namespace.md +38 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/read.md +34 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/restore.md +39 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/update.md +62 -0
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Collection = exports.MAX_GET_IDS = exports.Cursor = exports.MAX_ATOMIC_ITEMS = exports.MAX_PAGE_SIZE = void 0;
|
|
4
|
+
exports.assertAtomicBatch = assertAtomicBatch;
|
|
5
|
+
exports.toDoc = toDoc;
|
|
6
|
+
const error_js_1 = require("./error.js");
|
|
7
|
+
const schema_js_1 = require("./schema.js");
|
|
8
|
+
const sort_js_1 = require("./sort.js");
|
|
9
|
+
/** UNS page limit for find / aggregate / distinct. */
|
|
10
|
+
exports.MAX_PAGE_SIZE = 200;
|
|
11
|
+
/**
|
|
12
|
+
* UNS commits at most 200 items in one synchronous atomic batch
|
|
13
|
+
* (insertMany, bulkWrite, data.transact).
|
|
14
|
+
*/
|
|
15
|
+
exports.MAX_ATOMIC_ITEMS = 200;
|
|
16
|
+
/** Rejects batches UNS would refuse, before anything is sent. */
|
|
17
|
+
function assertAtomicBatch(operation, count) {
|
|
18
|
+
if (count > exports.MAX_ATOMIC_ITEMS) {
|
|
19
|
+
throw new error_js_1.DataError({
|
|
20
|
+
message: `${operation} commits at most ${exports.MAX_ATOMIC_ITEMS} items atomically; got ${count}. ` +
|
|
21
|
+
`Split the input into chunks of ${exports.MAX_ATOMIC_ITEMS} (each chunk commits on its own; give each chunk a stable ` +
|
|
22
|
+
'idempotencyKey or fixed _ids so a retry is safe), or keep all-or-nothing work within one batch.',
|
|
23
|
+
status: 400,
|
|
24
|
+
code: 'BATCH_LIMIT_EXCEEDED',
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function toDoc(record) {
|
|
29
|
+
return {
|
|
30
|
+
...record.data,
|
|
31
|
+
_id: record.id,
|
|
32
|
+
_meta: { revision: record.revision, createdAt: record.createdAt, updatedAt: record.updatedAt },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Async-iterable result set. Pages hold at most 200 items; the page source
|
|
37
|
+
* decides whether the next page is reached by cursor or by offset page number.
|
|
38
|
+
*/
|
|
39
|
+
class Cursor {
|
|
40
|
+
fetchPage;
|
|
41
|
+
sortSpec;
|
|
42
|
+
limitCount;
|
|
43
|
+
projection;
|
|
44
|
+
started = false;
|
|
45
|
+
constructor(fetchPage, options = {}) {
|
|
46
|
+
this.fetchPage = fetchPage;
|
|
47
|
+
this.sortSpec = options.sort;
|
|
48
|
+
this.limitCount = options.limit;
|
|
49
|
+
this.projection = options.projection;
|
|
50
|
+
}
|
|
51
|
+
assertNotStarted() {
|
|
52
|
+
if (this.started)
|
|
53
|
+
throw new Error('Cursor options must be set before iteration starts.');
|
|
54
|
+
}
|
|
55
|
+
sort(sort) {
|
|
56
|
+
this.assertNotStarted();
|
|
57
|
+
this.sortSpec = sort;
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
/** Total number of documents to return (pages are fetched ≤200 at a time). */
|
|
61
|
+
limit(count) {
|
|
62
|
+
this.assertNotStarted();
|
|
63
|
+
this.limitCount = count;
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
project(projection) {
|
|
67
|
+
this.assertNotStarted();
|
|
68
|
+
this.projection = projection;
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
async *[Symbol.asyncIterator]() {
|
|
72
|
+
this.started = true;
|
|
73
|
+
const sort = (0, sort_js_1.toSortPairs)(this.sortSpec);
|
|
74
|
+
let remaining = this.limitCount ?? Number.POSITIVE_INFINITY;
|
|
75
|
+
// One page size for the whole iteration: offset pages are only stable when it never changes.
|
|
76
|
+
const size = Math.min(exports.MAX_PAGE_SIZE, remaining);
|
|
77
|
+
let token = null;
|
|
78
|
+
while (remaining > 0) {
|
|
79
|
+
const page = await this.fetchPage({ token, size, sort, projection: this.projection });
|
|
80
|
+
for (const item of page.items.slice(0, remaining))
|
|
81
|
+
yield item;
|
|
82
|
+
remaining -= page.items.length;
|
|
83
|
+
if (page.next === null || page.items.length === 0)
|
|
84
|
+
return;
|
|
85
|
+
token = page.next;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async toArray() {
|
|
89
|
+
const out = [];
|
|
90
|
+
for await (const item of this)
|
|
91
|
+
out.push(item);
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
exports.Cursor = Cursor;
|
|
96
|
+
/** Pipelines made only of `$match` / `$sort` page by cursor; UNS requires offset paging for any other stage. */
|
|
97
|
+
function isDirectPipeline(stages) {
|
|
98
|
+
return stages.every((stage) => Object.keys(stage).every((key) => key === '$match' || key === '$sort'));
|
|
99
|
+
}
|
|
100
|
+
/** UNS reads at most 100 records by ID per request. */
|
|
101
|
+
exports.MAX_GET_IDS = 100;
|
|
102
|
+
function compact(value) {
|
|
103
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
104
|
+
}
|
|
105
|
+
function filterOrNothing(filter) {
|
|
106
|
+
return Object.keys(filter).length > 0 ? filter : undefined;
|
|
107
|
+
}
|
|
108
|
+
/** `recordAggregatePipeline` body of one numbered page with its capped total. */
|
|
109
|
+
function pageRequest(filter, options) {
|
|
110
|
+
const page = Math.max(1, Math.trunc(options.page ?? 1));
|
|
111
|
+
const pageSize = Math.min(exports.MAX_PAGE_SIZE, Math.max(1, Math.trunc(options.pageSize ?? 20)));
|
|
112
|
+
const pipeline = [];
|
|
113
|
+
if (Object.keys(filter).length > 0)
|
|
114
|
+
pipeline.push({ $match: filter });
|
|
115
|
+
const sort = (0, sort_js_1.toSortPairs)(options.sort);
|
|
116
|
+
if (sort)
|
|
117
|
+
pipeline.push({ $sort: sort });
|
|
118
|
+
if (options.projection)
|
|
119
|
+
pipeline.push({ $project: options.projection });
|
|
120
|
+
const body = { pipeline, page: { mode: 'offset', page, size: pageSize }, count: options.maxCount === undefined ? {} : { max: options.maxCount } };
|
|
121
|
+
return { body, page, pageSize };
|
|
122
|
+
}
|
|
123
|
+
function countByPipeline(field, filter) {
|
|
124
|
+
const pipeline = [];
|
|
125
|
+
if (Object.keys(filter).length > 0)
|
|
126
|
+
pipeline.push({ $match: filter });
|
|
127
|
+
// `_id` breaks ties, so offset pages of equal counts neither repeat nor skip a group.
|
|
128
|
+
pipeline.push({ $group: { _id: `$${field}`, count: { $sum: 1 } } }, { $sort: { count: -1, _id: 1 } });
|
|
129
|
+
return pipeline;
|
|
130
|
+
}
|
|
131
|
+
const toValueCount = (row) => ({ value: row._id ?? null, count: row.count });
|
|
132
|
+
function idsInOrder(ids) {
|
|
133
|
+
return Object.entries(ids ?? {})
|
|
134
|
+
.sort(([a], [b]) => Number(a) - Number(b))
|
|
135
|
+
.map(([, id]) => id);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Atomic batches pass no `committed` counts: their write errors rolled the
|
|
139
|
+
* whole batch back. Non-atomic runs pass their cumulative counts; `details`
|
|
140
|
+
* then has the shape `partialWrite` uses.
|
|
141
|
+
*/
|
|
142
|
+
function assertNoWriteErrors(result, operation, committed) {
|
|
143
|
+
if (result.writeErrors && result.writeErrors.length > 0) {
|
|
144
|
+
const counts = Object.entries(committed ?? {}).map(([name, value]) => `${name}=${value}`).join(', ');
|
|
145
|
+
const changed = committed !== undefined && Object.values(committed).some((value) => value > 0);
|
|
146
|
+
throw new error_js_1.DataError({
|
|
147
|
+
message: `${operation} stopped on ${result.writeErrors.length} write error(s)${counts ? `; already committed: ${counts}` : ''}.`,
|
|
148
|
+
status: 409,
|
|
149
|
+
code: 'WRITE_ERRORS',
|
|
150
|
+
execution: changed ? 'committed' : 'rolledBack',
|
|
151
|
+
attempts: 1,
|
|
152
|
+
details: committed
|
|
153
|
+
? { ...committed, status: changed ? 'partiallyCommitted' : 'failed', hasMore: Boolean(result.hasMore), resumeAfter: result.resumeAfter ?? null, writeErrors: result.writeErrors }
|
|
154
|
+
: { writeErrors: result.writeErrors },
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* A non-atomic run failed after some writes committed: on an earlier page, or
|
|
160
|
+
* within the failed page (UNS then answers `execution: "committed"` with the
|
|
161
|
+
* page's partial result as `data`). Like UNS, the error keeps its code and is
|
|
162
|
+
* `committed` with `retry.safe: false`; `details` is the cumulative result
|
|
163
|
+
* of the whole call in the shape of a success (`matchedCount` …, `status:
|
|
164
|
+
* "partiallyCommitted"`, `hasMore`, `resumeAfter`). Continue by passing
|
|
165
|
+
* `resumeAfter` back; repeating the call would apply `$inc` / `$push` twice.
|
|
166
|
+
*/
|
|
167
|
+
function partialWrite(operation, totals, resumeAfter, cause) {
|
|
168
|
+
const counts = Object.entries(totals).map(([name, value]) => `${name}=${value}`).join(', ');
|
|
169
|
+
const writeErrors = Array.isArray(cause.details.writeErrors) ? { writeErrors: cause.details.writeErrors } : {};
|
|
170
|
+
return new error_js_1.DataError({
|
|
171
|
+
message: `${operation} committed ${counts} and then failed: ${cause.message} ` +
|
|
172
|
+
(resumeAfter
|
|
173
|
+
? `Continue with { resumeAfter: details.resumeAfter }${cause.outcomeUnknown ? ' after checking the failed page, whose outcome is unknown' : ''}; do not repeat the whole call.`
|
|
174
|
+
: 'Read back what changed before writing again; do not repeat the whole call.'),
|
|
175
|
+
status: cause.status,
|
|
176
|
+
code: cause.code,
|
|
177
|
+
reason: cause.reason,
|
|
178
|
+
upstreamStatus: cause.upstreamStatus,
|
|
179
|
+
requestId: cause.requestId,
|
|
180
|
+
// Earlier writes took effect; the failed page too, perhaps, when its outcome is unknown.
|
|
181
|
+
execution: cause.outcomeUnknown ? 'unknown' : 'committed',
|
|
182
|
+
retry: { safe: false },
|
|
183
|
+
attempts: cause.attempts,
|
|
184
|
+
details: { ...totals, status: 'partiallyCommitted', hasMore: true, resumeAfter: resumeAfter ?? null, ...writeErrors },
|
|
185
|
+
cause,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
class Collection {
|
|
189
|
+
definition;
|
|
190
|
+
runtime;
|
|
191
|
+
constructor(definition, runtime) {
|
|
192
|
+
this.definition = definition;
|
|
193
|
+
this.runtime = runtime;
|
|
194
|
+
}
|
|
195
|
+
get alias() {
|
|
196
|
+
return this.definition.alias;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* One call with stale-ID recovery. A call continuing a cursor or
|
|
200
|
+
* resumeAfter token of the old collection is not retried: the token means
|
|
201
|
+
* nothing to a recreated collection.
|
|
202
|
+
*/
|
|
203
|
+
post(operation, body, options = {}) {
|
|
204
|
+
const page = body.page;
|
|
205
|
+
const continues = typeof body.cursor === 'string' || typeof body.resumeAfter === 'string' || typeof page?.cursor === 'string';
|
|
206
|
+
return this.runtime.recover(() => this.send(operation, body, options), { retry: !continues });
|
|
207
|
+
}
|
|
208
|
+
async send(operation, body, options = {}) {
|
|
209
|
+
const collectionId = await this.runtime.resolve(this.definition);
|
|
210
|
+
return this.runtime.call(operation, { collectionId, ...compact(body) }, {
|
|
211
|
+
dataSubject: this.runtime.subject(),
|
|
212
|
+
signal: this.runtime.signal(),
|
|
213
|
+
...options,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
/* ---------------------------------- reads --------------------------------- */
|
|
217
|
+
find(filter = {}, options = {}) {
|
|
218
|
+
return new Cursor(async ({ token, size, sort, projection }) => {
|
|
219
|
+
const result = await this.post('recordFind', {
|
|
220
|
+
filter: Object.keys(filter).length > 0 ? filter : undefined,
|
|
221
|
+
sort,
|
|
222
|
+
projection,
|
|
223
|
+
limit: size,
|
|
224
|
+
cursor: typeof token === 'string' ? token : undefined,
|
|
225
|
+
});
|
|
226
|
+
return {
|
|
227
|
+
items: result.items.map((record) => toDoc(record)),
|
|
228
|
+
next: result.pageInfo.hasMore && result.pageInfo.nextCursor ? result.pageInfo.nextCursor : null,
|
|
229
|
+
};
|
|
230
|
+
}, options);
|
|
231
|
+
}
|
|
232
|
+
async findOne(filter = {}, options = {}) {
|
|
233
|
+
const [first] = await this.find(filter, { ...options, limit: 1 }).toArray();
|
|
234
|
+
return first ?? null;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Records by ID, in the order asked for (each ID once, at its first
|
|
238
|
+
* position); IDs that do not exist, are deleted or are hidden by employee
|
|
239
|
+
* data rules are listed in `missing` alike. More than 100 IDs are read in
|
|
240
|
+
* chunks of 100 (not one snapshot).
|
|
241
|
+
*/
|
|
242
|
+
async get(ids, options = {}) {
|
|
243
|
+
const unique = [...new Set(ids)];
|
|
244
|
+
const chunks = [];
|
|
245
|
+
for (let start = 0; start < unique.length; start += exports.MAX_GET_IDS)
|
|
246
|
+
chunks.push(unique.slice(start, start + exports.MAX_GET_IDS));
|
|
247
|
+
const results = await Promise.all(chunks.map((chunk) => this.post('recordGet', { ids: chunk, projection: options.projection })));
|
|
248
|
+
return {
|
|
249
|
+
items: results.flatMap((result) => result.items.map((record) => toDoc(record))),
|
|
250
|
+
missing: results.flatMap((result) => result.missing),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* One numbered page plus the total match count, for paged list screens:
|
|
255
|
+
* one `recordAggregatePipeline` offset page whose `count` runs in the same
|
|
256
|
+
* snapshot. Show a capped total as "10000+".
|
|
257
|
+
*/
|
|
258
|
+
async findPage(filter = {}, options = {}) {
|
|
259
|
+
const { body, page, pageSize } = pageRequest(filter, options);
|
|
260
|
+
const result = await this.post('recordAggregatePipeline', body);
|
|
261
|
+
return { items: result.items, total: result.total, page, pageSize };
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Counts matches up to `maxCount` (1–100000, UNS default 100000). A
|
|
265
|
+
* `capped` result means "at least `count`".
|
|
266
|
+
*/
|
|
267
|
+
async countDocuments(filter = {}, options = {}) {
|
|
268
|
+
const result = await this.post('recordCount', { filter: filterOrNothing(filter), maxCount: options.maxCount });
|
|
269
|
+
return { count: result.count, capped: result.capped };
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Match counts per value of `field`, largest first: one `$group` pipeline
|
|
273
|
+
* instead of a count per value. Records without the field count under
|
|
274
|
+
* `value: null`.
|
|
275
|
+
*/
|
|
276
|
+
async countBy(field, filter = {}) {
|
|
277
|
+
const rows = await this.aggregate(countByPipeline(field, filter)).toArray();
|
|
278
|
+
return rows.map(toValueCount);
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Builders for `data.queryBatch`: each describes one read of this
|
|
282
|
+
* collection, run together with the others in one request and one
|
|
283
|
+
* snapshot. Each returns one page (at most 200 rows), unlike the cursor
|
|
284
|
+
* methods; `get` takes at most 100 IDs.
|
|
285
|
+
*/
|
|
286
|
+
get query() {
|
|
287
|
+
const one = (op, build, parse) => ({
|
|
288
|
+
collection: this.definition,
|
|
289
|
+
prepare: async () => ({ op, ...compact(await build()) }),
|
|
290
|
+
parse: parse,
|
|
291
|
+
});
|
|
292
|
+
return {
|
|
293
|
+
find: (filter = {}, options = {}) => one('find', async () => ({
|
|
294
|
+
filter: filterOrNothing(filter),
|
|
295
|
+
sort: (0, sort_js_1.toSortPairs)(options.sort),
|
|
296
|
+
projection: options.projection,
|
|
297
|
+
limit: Math.min(exports.MAX_PAGE_SIZE, options.limit ?? 20),
|
|
298
|
+
count: options.maxCount === undefined ? undefined : { max: options.maxCount },
|
|
299
|
+
}), (data) => ({
|
|
300
|
+
items: data.items.map((record) => toDoc(record)),
|
|
301
|
+
hasMore: data.pageInfo.hasMore,
|
|
302
|
+
...(data.total ? { total: data.total } : {}),
|
|
303
|
+
})),
|
|
304
|
+
findPage: (filter = {}, options = {}) => {
|
|
305
|
+
const { body, page, pageSize } = pageRequest(filter, options);
|
|
306
|
+
return one('aggregate', async () => body, (data) => ({
|
|
307
|
+
items: data.items, total: data.total, page, pageSize,
|
|
308
|
+
}));
|
|
309
|
+
},
|
|
310
|
+
count: (filter = {}, options = {}) => one('count', async () => ({ filter: filterOrNothing(filter), maxCount: options.maxCount }), (data) => ({ count: data.count, capped: data.capped })),
|
|
311
|
+
countBy: (field, filter = {}) => one('aggregate', async () => ({
|
|
312
|
+
pipeline: await this.prepareStages(countByPipeline(field, filter)),
|
|
313
|
+
page: { mode: 'offset', page: 1, size: exports.MAX_PAGE_SIZE },
|
|
314
|
+
}), (data) => data.items.map(toValueCount)),
|
|
315
|
+
distinct: (field, filter = {}) => one('distinct', async () => ({ field, filter: filterOrNothing(filter), page: { mode: 'offset', page: 1, size: exports.MAX_PAGE_SIZE } }), (data) => data.values),
|
|
316
|
+
aggregate: (pipeline) => one('aggregate', async () => {
|
|
317
|
+
const stages = await this.prepareStages(pipeline);
|
|
318
|
+
const last = stages[stages.length - 1];
|
|
319
|
+
// A final $count returns one row and forbids an explicit page.
|
|
320
|
+
return last !== undefined && '$count' in last
|
|
321
|
+
? { pipeline: stages }
|
|
322
|
+
: { pipeline: stages, page: isDirectPipeline(stages) ? { mode: 'cursor', size: exports.MAX_PAGE_SIZE } : { mode: 'offset', page: 1, size: exports.MAX_PAGE_SIZE } };
|
|
323
|
+
}, (data) => data.items),
|
|
324
|
+
get: (ids, options = {}) => {
|
|
325
|
+
const unique = [...new Set(ids)];
|
|
326
|
+
if (unique.length < 1 || unique.length > exports.MAX_GET_IDS) {
|
|
327
|
+
throw new TypeError(`query.get takes 1–${exports.MAX_GET_IDS} distinct IDs; use collection.get for more.`);
|
|
328
|
+
}
|
|
329
|
+
return one('get', async () => ({ ids: unique, projection: options.projection }), (data) => ({ items: data.items.map((record) => toDoc(record)), missing: data.missing }));
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
/** Distinct values of a field (every page). */
|
|
334
|
+
async distinct(field, filter = {}) {
|
|
335
|
+
const values = [];
|
|
336
|
+
for (let page = 1;; page += 1) {
|
|
337
|
+
const result = await this.post('recordDistinct', {
|
|
338
|
+
field,
|
|
339
|
+
filter: Object.keys(filter).length > 0 ? filter : undefined,
|
|
340
|
+
page: { mode: 'offset', page, size: exports.MAX_PAGE_SIZE },
|
|
341
|
+
});
|
|
342
|
+
values.push(...result.values);
|
|
343
|
+
if (!result.pageInfo.hasMore)
|
|
344
|
+
return values;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Runs a pipeline (`$match`, `$sort`, `$group`, `$project`, `$set`,
|
|
349
|
+
* `$lookup`, `$unwind`, `$skip`, `$limit`, `$count`). `$sort` accepts
|
|
350
|
+
* `{ field: 1 | -1 }`; `$lookup` takes `from` (a declared collection or
|
|
351
|
+
* alias of this App) instead of a collection ID. Rows are whatever the
|
|
352
|
+
* pipeline outputs, not wrapped records.
|
|
353
|
+
*
|
|
354
|
+
* Pipelines of only `$match` / `$sort` page by cursor; any other stage pages
|
|
355
|
+
* by offset (UNS rejects cursors for transformed results). The `$exists`,
|
|
356
|
+
* `$distinct` and `$frequencies` stages are listed in the UNS schema but not
|
|
357
|
+
* executed by the record pipeline; use `countDocuments`, `distinct` or `$group`.
|
|
358
|
+
*/
|
|
359
|
+
aggregate(pipeline) {
|
|
360
|
+
// Recovery wraps stage preparation too, so a retry re-resolves `$lookup` IDs.
|
|
361
|
+
return new Cursor(({ token, size }) => this.runtime.recover(() => this.aggregatePage(pipeline, token, size), { retry: typeof token !== 'string' }));
|
|
362
|
+
}
|
|
363
|
+
async aggregatePage(pipeline, token, size) {
|
|
364
|
+
const stages = await this.prepareStages(pipeline);
|
|
365
|
+
const last = stages[stages.length - 1];
|
|
366
|
+
if (last !== undefined && '$count' in last) {
|
|
367
|
+
// A final $count returns one row and forbids an explicit page.
|
|
368
|
+
const result = await this.send('recordAggregatePipeline', { pipeline: stages });
|
|
369
|
+
return { items: result.items, next: null };
|
|
370
|
+
}
|
|
371
|
+
if (isDirectPipeline(stages)) {
|
|
372
|
+
const result = await this.send('recordAggregatePipeline', {
|
|
373
|
+
pipeline: stages,
|
|
374
|
+
page: { mode: 'cursor', size, ...(typeof token === 'string' ? { cursor: token } : {}) },
|
|
375
|
+
});
|
|
376
|
+
return { items: result.items, next: result.pageInfo.hasMore && result.pageInfo.nextCursor ? result.pageInfo.nextCursor : null };
|
|
377
|
+
}
|
|
378
|
+
const page = typeof token === 'number' ? token : 1;
|
|
379
|
+
const result = await this.send('recordAggregatePipeline', {
|
|
380
|
+
pipeline: stages,
|
|
381
|
+
page: { mode: 'offset', page, size },
|
|
382
|
+
});
|
|
383
|
+
return {
|
|
384
|
+
items: result.items,
|
|
385
|
+
next: result.pageInfo.hasMore ? (result.pageInfo.nextPage ?? page + 1) : null,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
/** Converts Mongo-style `$sort` objects and resolves `$lookup.from`, recursively. */
|
|
389
|
+
async prepareStages(pipeline) {
|
|
390
|
+
return Promise.all(pipeline.map(async (stage) => {
|
|
391
|
+
if ('$sort' in stage && !Array.isArray(stage.$sort)) {
|
|
392
|
+
return { ...stage, $sort: (0, sort_js_1.toSortPairs)(stage.$sort) };
|
|
393
|
+
}
|
|
394
|
+
if ('$lookup' in stage && stage.$lookup && typeof stage.$lookup === 'object') {
|
|
395
|
+
const { from, pipeline: inner, ...lookup } = stage.$lookup;
|
|
396
|
+
const resolved = { ...lookup };
|
|
397
|
+
if (from !== undefined)
|
|
398
|
+
resolved.collectionId = await this.runtime.resolve(from);
|
|
399
|
+
if (Array.isArray(inner))
|
|
400
|
+
resolved.pipeline = await this.prepareStages(inner);
|
|
401
|
+
return { ...stage, $lookup: resolved };
|
|
402
|
+
}
|
|
403
|
+
return stage;
|
|
404
|
+
}));
|
|
405
|
+
}
|
|
406
|
+
/* --------------------------------- writes --------------------------------- */
|
|
407
|
+
async insertOne(document, options = {}) {
|
|
408
|
+
const { id, data } = (0, schema_js_1.validateDocument)(this.definition, document);
|
|
409
|
+
const result = await this.post('recordInsertOne', { id, data, idempotencyKey: options.idempotencyKey });
|
|
410
|
+
return { insertedId: result.insertedId };
|
|
411
|
+
}
|
|
412
|
+
/** Inserts up to 200 documents atomically (all or none). */
|
|
413
|
+
async insertMany(documents, options = {}) {
|
|
414
|
+
if (documents.length === 0)
|
|
415
|
+
return { insertedCount: 0, insertedIds: [] };
|
|
416
|
+
assertAtomicBatch('insertMany', documents.length);
|
|
417
|
+
const prepared = documents.map((document) => (0, schema_js_1.validateDocument)(this.definition, document));
|
|
418
|
+
const result = await this.post('recordInsertMany', {
|
|
419
|
+
documents: prepared.map(({ id, data }) => compact({ id, data })),
|
|
420
|
+
ordered: true,
|
|
421
|
+
atomic: true,
|
|
422
|
+
idempotencyKey: options.idempotencyKey,
|
|
423
|
+
});
|
|
424
|
+
assertNoWriteErrors(result, 'insertMany');
|
|
425
|
+
return { insertedCount: result.insertedCount ?? 0, insertedIds: idsInOrder(result.insertedIds) };
|
|
426
|
+
}
|
|
427
|
+
async updateOne(filter, update, options = {}) {
|
|
428
|
+
const result = await this.post('recordUpdateOne', {
|
|
429
|
+
filter,
|
|
430
|
+
update: (0, schema_js_1.validateUpdate)(this.definition, update),
|
|
431
|
+
upsert: options.upsert,
|
|
432
|
+
expectedRevision: options.expectedRevision,
|
|
433
|
+
sort: (0, sort_js_1.toSortPairs)(options.sort),
|
|
434
|
+
arrayFilters: options.arrayFilters,
|
|
435
|
+
idempotencyKey: options.idempotencyKey,
|
|
436
|
+
});
|
|
437
|
+
return { matchedCount: result.matchedCount ?? 0, modifiedCount: result.modifiedCount ?? 0, upsertedId: result.upsertedId ?? null };
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Updates every match like Mongo's updateMany: documents commit one by one
|
|
441
|
+
* (not atomically), following `resumeAfter` until every match is processed.
|
|
442
|
+
* A failure after some documents committed keeps its code but is
|
|
443
|
+
* `execution: "committed"`, with the cumulative counts and
|
|
444
|
+
* `details.resumeAfter`; pass that back as `resumeAfter` to continue
|
|
445
|
+
* instead of repeating the call. For all-or-nothing multi-document changes
|
|
446
|
+
* use `data.transact` (≤200 items).
|
|
447
|
+
*/
|
|
448
|
+
async updateMany(filter, update, options = {}) {
|
|
449
|
+
const validated = (0, schema_js_1.validateUpdate)(this.definition, update);
|
|
450
|
+
let matchedCount = 0;
|
|
451
|
+
let modifiedCount = 0;
|
|
452
|
+
await this.writeEach('updateMany', 'recordUpdateMany', { filter, update: validated, arrayFilters: options.arrayFilters }, options.resumeAfter, (result) => {
|
|
453
|
+
matchedCount += result.matchedCount ?? 0;
|
|
454
|
+
modifiedCount += result.modifiedCount ?? 0;
|
|
455
|
+
return { matchedCount, modifiedCount };
|
|
456
|
+
});
|
|
457
|
+
return { matchedCount, modifiedCount };
|
|
458
|
+
}
|
|
459
|
+
/** Runs a non-atomic many-write page by page; see updateMany for failures. */
|
|
460
|
+
async writeEach(name, operation, body, from, add) {
|
|
461
|
+
let resumeAfter = from;
|
|
462
|
+
let committed;
|
|
463
|
+
do {
|
|
464
|
+
let result;
|
|
465
|
+
try {
|
|
466
|
+
result = await this.post(operation, { ...body, atomic: false, resumeAfter });
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
if (!(error instanceof error_js_1.DataError))
|
|
470
|
+
throw error;
|
|
471
|
+
// UNS reports what the failed page itself committed, with where to continue.
|
|
472
|
+
const page = error.execution === 'committed' ? error.details : undefined;
|
|
473
|
+
const totals = page ? add(page) : committed;
|
|
474
|
+
// Nothing of this call took effect: the page's own error tells the truth.
|
|
475
|
+
if (!totals || !Object.values(totals).some((value) => value > 0))
|
|
476
|
+
throw error;
|
|
477
|
+
throw partialWrite(name, totals, page ? (page.resumeAfter ?? undefined) : resumeAfter, error);
|
|
478
|
+
}
|
|
479
|
+
committed = add(result);
|
|
480
|
+
assertNoWriteErrors(result, name, committed);
|
|
481
|
+
resumeAfter = result.hasMore && result.resumeAfter ? result.resumeAfter : undefined;
|
|
482
|
+
} while (resumeAfter);
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Replaces the whole body. Fields written by another App version and not in
|
|
486
|
+
* this declaration are lost — prefer `updateOne` with `$set`.
|
|
487
|
+
*/
|
|
488
|
+
async replaceOne(filter, replacement, options = {}) {
|
|
489
|
+
const { data } = (0, schema_js_1.validateDocument)(this.definition, replacement);
|
|
490
|
+
const result = await this.post('recordReplaceOne', {
|
|
491
|
+
filter,
|
|
492
|
+
replacement: data,
|
|
493
|
+
upsert: options.upsert,
|
|
494
|
+
expectedRevision: options.expectedRevision,
|
|
495
|
+
sort: (0, sort_js_1.toSortPairs)(options.sort),
|
|
496
|
+
idempotencyKey: options.idempotencyKey,
|
|
497
|
+
});
|
|
498
|
+
return { matchedCount: result.matchedCount ?? 0, modifiedCount: result.modifiedCount ?? 0, upsertedId: result.upsertedId ?? null };
|
|
499
|
+
}
|
|
500
|
+
/** Soft-deletes one record; requires `uns.delete` on the App key. */
|
|
501
|
+
async deleteOne(filter, options = {}) {
|
|
502
|
+
const result = await this.post('recordDeleteOne', { filter, expectedRevision: options.expectedRevision, sort: (0, sort_js_1.toSortPairs)(options.sort), idempotencyKey: options.idempotencyKey });
|
|
503
|
+
return { deletedCount: result.deletedCount ?? 0 };
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Soft-deletes every match, one document at a time like Mongo's deleteMany
|
|
507
|
+
* (not atomic), following `resumeAfter`; requires `uns.delete` on the App
|
|
508
|
+
* key. Failures after committed deletes report them as updateMany does.
|
|
509
|
+
*/
|
|
510
|
+
async deleteMany(filter, options = {}) {
|
|
511
|
+
let deletedCount = 0;
|
|
512
|
+
await this.writeEach('deleteMany', 'recordDeleteMany', { filter }, options.resumeAfter, (result) => {
|
|
513
|
+
deletedCount += result.deletedCount ?? 0;
|
|
514
|
+
return { deletedCount };
|
|
515
|
+
});
|
|
516
|
+
return { deletedCount };
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Atomic read-modify-write. A state transition puts the expected state in
|
|
520
|
+
* the filter: `findOneAndUpdate({ _id, status: "DRAFT" }, { $set: { status: "SUBMITTED" } })`
|
|
521
|
+
* returns null when another request moved the record first.
|
|
522
|
+
*/
|
|
523
|
+
async findOneAndUpdate(filter, update, options = {}) {
|
|
524
|
+
const result = await this.post('recordFindOneAndUpdate', {
|
|
525
|
+
filter,
|
|
526
|
+
update: (0, schema_js_1.validateUpdate)(this.definition, update),
|
|
527
|
+
upsert: options.upsert,
|
|
528
|
+
expectedRevision: options.expectedRevision,
|
|
529
|
+
sort: (0, sort_js_1.toSortPairs)(options.sort),
|
|
530
|
+
arrayFilters: options.arrayFilters,
|
|
531
|
+
returnDocument: options.returnDocument ?? 'after',
|
|
532
|
+
projection: options.projection,
|
|
533
|
+
idempotencyKey: options.idempotencyKey,
|
|
534
|
+
});
|
|
535
|
+
return result.value ? toDoc(result.value) : null;
|
|
536
|
+
}
|
|
537
|
+
async findOneAndReplace(filter, replacement, options = {}) {
|
|
538
|
+
const { data } = (0, schema_js_1.validateDocument)(this.definition, replacement);
|
|
539
|
+
const result = await this.post('recordFindOneAndReplace', {
|
|
540
|
+
filter,
|
|
541
|
+
replacement: data,
|
|
542
|
+
upsert: options.upsert,
|
|
543
|
+
expectedRevision: options.expectedRevision,
|
|
544
|
+
sort: (0, sort_js_1.toSortPairs)(options.sort),
|
|
545
|
+
returnDocument: options.returnDocument ?? 'after',
|
|
546
|
+
projection: options.projection,
|
|
547
|
+
idempotencyKey: options.idempotencyKey,
|
|
548
|
+
});
|
|
549
|
+
return result.value ? toDoc(result.value) : null;
|
|
550
|
+
}
|
|
551
|
+
async findOneAndDelete(filter, options = {}) {
|
|
552
|
+
const result = await this.post('recordFindOneAndDelete', {
|
|
553
|
+
filter,
|
|
554
|
+
expectedRevision: options.expectedRevision,
|
|
555
|
+
sort: (0, sort_js_1.toSortPairs)(options.sort),
|
|
556
|
+
projection: options.projection,
|
|
557
|
+
idempotencyKey: options.idempotencyKey,
|
|
558
|
+
});
|
|
559
|
+
return result.value ? toDoc(result.value) : null;
|
|
560
|
+
}
|
|
561
|
+
/** Up to 200 mixed single-record writes in one atomic call (insertOne / updateOne / replaceOne / deleteOne). */
|
|
562
|
+
async bulkWrite(operations, options = {}) {
|
|
563
|
+
assertAtomicBatch('bulkWrite', operations.length);
|
|
564
|
+
const mapped = operations.map((operation) => {
|
|
565
|
+
if ('insertOne' in operation) {
|
|
566
|
+
const { id, data } = (0, schema_js_1.validateDocument)(this.definition, operation.insertOne.document);
|
|
567
|
+
return { insertOne: compact({ id, data }) };
|
|
568
|
+
}
|
|
569
|
+
if ('updateOne' in operation) {
|
|
570
|
+
const { filter, update, upsert, sort } = operation.updateOne;
|
|
571
|
+
return { updateOne: compact({ filter, update: (0, schema_js_1.validateUpdate)(this.definition, update), upsert, sort: (0, sort_js_1.toSortPairs)(sort) }) };
|
|
572
|
+
}
|
|
573
|
+
if ('replaceOne' in operation) {
|
|
574
|
+
const { filter, replacement, upsert, sort } = operation.replaceOne;
|
|
575
|
+
const { data } = (0, schema_js_1.validateDocument)(this.definition, replacement);
|
|
576
|
+
return { replaceOne: compact({ filter, replacement: data, upsert, sort: (0, sort_js_1.toSortPairs)(sort) }) };
|
|
577
|
+
}
|
|
578
|
+
if ('deleteOne' in operation) {
|
|
579
|
+
return { deleteOne: compact({ filter: operation.deleteOne.filter, sort: (0, sort_js_1.toSortPairs)(operation.deleteOne.sort) }) };
|
|
580
|
+
}
|
|
581
|
+
throw new Error(`Unsupported bulkWrite operation ${Object.keys(operation).join(', ')}; use updateMany() / deleteMany() or data.transact().`);
|
|
582
|
+
});
|
|
583
|
+
if (mapped.length === 0) {
|
|
584
|
+
return { insertedCount: 0, matchedCount: 0, modifiedCount: 0, deletedCount: 0, upsertedCount: 0, insertedIds: {}, upsertedIds: {} };
|
|
585
|
+
}
|
|
586
|
+
const result = await this.post('recordBulkWrite', { operations: mapped, ordered: true, atomic: true, idempotencyKey: options.idempotencyKey });
|
|
587
|
+
assertNoWriteErrors(result, 'bulkWrite');
|
|
588
|
+
const numericKeys = (ids) => Object.fromEntries(Object.entries(ids ?? {}).map(([index, id]) => [Number(index), id]));
|
|
589
|
+
return {
|
|
590
|
+
insertedCount: result.insertedCount ?? 0,
|
|
591
|
+
matchedCount: result.matchedCount ?? 0,
|
|
592
|
+
modifiedCount: result.modifiedCount ?? 0,
|
|
593
|
+
deletedCount: result.deletedCount ?? 0,
|
|
594
|
+
upsertedCount: result.upsertedCount ?? 0,
|
|
595
|
+
insertedIds: numericKeys(result.insertedIds),
|
|
596
|
+
upsertedIds: numericKeys(result.upsertedIds),
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
exports.Collection = Collection;
|
|
601
|
+
//# sourceMappingURL=collection.js.map
|