@tier0/sdk-v2 0.0.0-stage → 0.1.0
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 +46 -0
- package/dist/cjs/cloud-transport.js +192 -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 +66 -0
- package/dist/cjs/data/bootstrap.js +282 -0
- package/dist/cjs/data/bootstrap.js.map +1 -0
- package/dist/cjs/data/call.d.ts +16 -0
- package/dist/cjs/data/call.js +51 -0
- package/dist/cjs/data/call.js.map +1 -0
- package/dist/cjs/data/client.d.ts +92 -0
- package/dist/cjs/data/client.js +102 -0
- package/dist/cjs/data/client.js.map +1 -0
- package/dist/cjs/data/collection.d.ts +238 -0
- package/dist/cjs/data/collection.js +441 -0
- package/dist/cjs/data/collection.js.map +1 -0
- package/dist/cjs/data/context.d.ts +10 -0
- package/dist/cjs/data/context.js +39 -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 +69 -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 +60 -0
- package/dist/cjs/data/error.js +184 -0
- package/dist/cjs/data/error.js.map +1 -0
- package/dist/cjs/data/index.d.ts +21 -0
- package/dist/cjs/data/index.js +43 -0
- package/dist/cjs/data/index.js.map +1 -0
- package/dist/cjs/data/schema.d.ts +82 -0
- package/dist/cjs/data/schema.js +607 -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/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 +54 -0
- package/dist/cjs/uns-v2/client.js +177 -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 +15551 -0
- package/dist/cjs/uns-v2/generated.js +139 -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/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 +46 -0
- package/dist/esm/cloud-transport.js +187 -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 +66 -0
- package/dist/esm/data/bootstrap.js +277 -0
- package/dist/esm/data/bootstrap.js.map +1 -0
- package/dist/esm/data/call.d.ts +16 -0
- package/dist/esm/data/call.js +47 -0
- package/dist/esm/data/call.js.map +1 -0
- package/dist/esm/data/client.d.ts +92 -0
- package/dist/esm/data/client.js +99 -0
- package/dist/esm/data/client.js.map +1 -0
- package/dist/esm/data/collection.d.ts +238 -0
- package/dist/esm/data/collection.js +434 -0
- package/dist/esm/data/collection.js.map +1 -0
- package/dist/esm/data/context.d.ts +10 -0
- package/dist/esm/data/context.js +34 -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 +69 -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 +60 -0
- package/dist/esm/data/error.js +178 -0
- package/dist/esm/data/error.js.map +1 -0
- package/dist/esm/data/index.d.ts +21 -0
- package/dist/esm/data/index.js +18 -0
- package/dist/esm/data/index.js.map +1 -0
- package/dist/esm/data/schema.d.ts +82 -0
- package/dist/esm/data/schema.js +596 -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/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 +54 -0
- package/dist/esm/uns-v2/client.js +170 -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 +15551 -0
- package/dist/esm/uns-v2/generated.js +136 -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/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 +242 -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-agent/SKILL.md +73 -0
- package/skills/tier0-sdk-v2-agent/references/results.md +45 -0
- package/skills/tier0-sdk-v2-controls/SKILL.md +63 -0
- package/skills/tier0-sdk-v2-data/SKILL.md +57 -0
- package/skills/tier0-sdk-v2-data/references/consistency.md +70 -0
- package/skills/tier0-sdk-v2-data/references/errors.md +69 -0
- package/skills/tier0-sdk-v2-data/references/modeling.md +77 -0
- package/skills/tier0-sdk-v2-data/references/read-write.md +72 -0
- package/skills/tier0-sdk-v2-data/references/schema-evolution.md +36 -0
- package/skills/tier0-sdk-v2-files/SKILL.md +85 -0
- package/skills/tier0-sdk-v2-files/references/delete.md +22 -0
- package/skills/tier0-sdk-v2-files/references/download.md +49 -0
- package/skills/tier0-sdk-v2-files/references/multipart.md +42 -0
- package/skills/tier0-sdk-v2-files/references/upload.md +27 -0
- package/skills/tier0-sdk-v2-files/references/url.md +22 -0
- package/skills/tier0-sdk-v2-members/SKILL.md +56 -0
- package/skills/tier0-sdk-v2-members/references/app-members.md +41 -0
- package/skills/tier0-sdk-v2-members/references/platform-members.md +56 -0
- package/skills/tier0-sdk-v2-mq/SKILL.md +78 -0
- package/skills/tier0-sdk-v2-mq/references/quickstart.md +69 -0
- package/skills/tier0-sdk-v2-notifications/SKILL.md +42 -0
- package/skills/tier0-sdk-v2-notifications/references/get.md +34 -0
- package/skills/tier0-sdk-v2-notifications/references/send.md +72 -0
- package/skills/tier0-sdk-v2-source/SKILL.md +38 -0
- package/skills/tier0-sdk-v2-source/references/changes.md +40 -0
- package/skills/tier0-sdk-v2-source/references/create.md +63 -0
- package/skills/tier0-sdk-v2-source/references/delete.md +47 -0
- package/skills/tier0-sdk-v2-source/references/protocols.md +36 -0
- package/skills/tier0-sdk-v2-source/references/read.md +34 -0
- package/skills/tier0-sdk-v2-source/references/state.md +37 -0
- package/skills/tier0-sdk-v2-source/references/update.md +61 -0
- package/skills/tier0-sdk-v2-stream/SKILL.md +43 -0
- package/skills/tier0-sdk-v2-stream/references/create.md +55 -0
- package/skills/tier0-sdk-v2-stream/references/delete.md +35 -0
- package/skills/tier0-sdk-v2-stream/references/deploy.md +63 -0
- package/skills/tier0-sdk-v2-stream/references/lifecycle.md +25 -0
- package/skills/tier0-sdk-v2-stream/references/read.md +35 -0
- package/skills/tier0-sdk-v2-stream/references/update.md +35 -0
- package/skills/tier0-sdk-v2-system/SKILL.md +64 -0
- package/skills/tier0-sdk-v2-system/references/info.md +23 -0
- package/skills/tier0-sdk-v2-system/references/whoami.md +21 -0
- package/skills/tier0-sdk-v2-uns/SKILL.md +53 -0
- package/skills/tier0-sdk-v2-uns/references/create.md +85 -0
- package/skills/tier0-sdk-v2-uns/references/data-workflows.md +73 -0
- package/skills/tier0-sdk-v2-uns/references/delete.md +69 -0
- package/skills/tier0-sdk-v2-uns/references/namespace.md +75 -0
- package/skills/tier0-sdk-v2-uns/references/read.md +32 -0
- package/skills/tier0-sdk-v2-uns/references/restore.md +41 -0
- package/skills/tier0-sdk-v2-uns/references/update.md +86 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.controls = exports.ControlsClient = exports.ControlResultUnknownError = exports.ControlError = exports.DEFAULT_CONTROL_TRANSPORT_TIMEOUT_MS = void 0;
|
|
4
|
+
const client_js_1 = require("./openapi/client.js");
|
|
5
|
+
const cloud_transport_js_1 = require("./cloud-transport.js");
|
|
6
|
+
exports.DEFAULT_CONTROL_TRANSPORT_TIMEOUT_MS = cloud_transport_js_1.DEFAULT_CLOUD_TRANSPORT_TIMEOUT_MS;
|
|
7
|
+
/** No Control error is automatically retried, including authentication failures. */
|
|
8
|
+
class ControlError extends Error {
|
|
9
|
+
status;
|
|
10
|
+
errorCode;
|
|
11
|
+
runID;
|
|
12
|
+
contractVersion;
|
|
13
|
+
runStatus;
|
|
14
|
+
replayed;
|
|
15
|
+
retryable = false;
|
|
16
|
+
constructor(message, info) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = 'ControlError';
|
|
19
|
+
this.status = info.status;
|
|
20
|
+
this.errorCode = info.errorCode;
|
|
21
|
+
this.runID = info.runID;
|
|
22
|
+
this.contractVersion = info.contractVersion;
|
|
23
|
+
this.runStatus = info.runStatus;
|
|
24
|
+
this.replayed = info.replayed;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.ControlError = ControlError;
|
|
28
|
+
/** Execution may have happened. Reconcile run information; any deliberate replay must reuse the same key. */
|
|
29
|
+
class ControlResultUnknownError extends ControlError {
|
|
30
|
+
outcomeUnknown = true;
|
|
31
|
+
constructor(info) {
|
|
32
|
+
super('Control result is unknown; execution may have occurred. Do not automatically retry.', info);
|
|
33
|
+
this.name = 'ControlResultUnknownError';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.ControlResultUnknownError = ControlResultUnknownError;
|
|
37
|
+
function record(value) {
|
|
38
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
39
|
+
}
|
|
40
|
+
function keys(value, allowed) {
|
|
41
|
+
if (!record(value) || Reflect.ownKeys(value).some(key => typeof key !== 'string' || !allowed.includes(key))) {
|
|
42
|
+
throw new TypeError('Tier0 SDK: unsupported Control option.');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function json(value, seen = new Set()) {
|
|
46
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
47
|
+
return true;
|
|
48
|
+
if (typeof value === 'number')
|
|
49
|
+
return Number.isFinite(value);
|
|
50
|
+
if (typeof value !== 'object' || seen.has(value))
|
|
51
|
+
return false;
|
|
52
|
+
if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
seen.add(value);
|
|
56
|
+
const valid = (Array.isArray(value) ? Array.from(value) : Object.values(value)).every(item => json(item, seen));
|
|
57
|
+
seen.delete(value);
|
|
58
|
+
return valid;
|
|
59
|
+
}
|
|
60
|
+
function definition(value) {
|
|
61
|
+
if (!record(value))
|
|
62
|
+
return false;
|
|
63
|
+
return ['code', 'name', 'description', 'contractVersion', 'status'].every(key => typeof value[key] === 'string') &&
|
|
64
|
+
value.code !== '' && value.contractVersion !== '' && record(value.inputSchema) && record(value.outputSchema) &&
|
|
65
|
+
['read', 'write'].includes(value.sideEffect) &&
|
|
66
|
+
['none', 'optional', 'required'].includes(value.idempotency) &&
|
|
67
|
+
typeof value.timeoutMs === 'number' && Number.isSafeInteger(value.timeoutMs) && value.timeoutMs > 0;
|
|
68
|
+
}
|
|
69
|
+
function runInfo(value) {
|
|
70
|
+
if (!record(value))
|
|
71
|
+
return {};
|
|
72
|
+
return {
|
|
73
|
+
runID: typeof value.runID === 'string' ? value.runID : undefined,
|
|
74
|
+
contractVersion: typeof value.contractVersion === 'string' ? value.contractVersion : undefined,
|
|
75
|
+
runStatus: typeof value.status === 'string' ? value.status : undefined,
|
|
76
|
+
replayed: typeof value.replayed === 'boolean' ? value.replayed : undefined,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
class ControlsClient {
|
|
80
|
+
transport;
|
|
81
|
+
constructor(resolveClient = client_js_1.getClient, config = {}) {
|
|
82
|
+
this.transport = new cloud_transport_js_1.CloudTransport(resolveClient, config);
|
|
83
|
+
}
|
|
84
|
+
async post(path, body, invoked) {
|
|
85
|
+
let response;
|
|
86
|
+
try {
|
|
87
|
+
response = await this.transport.post(path, body);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (!(error instanceof cloud_transport_js_1.CloudTransportError))
|
|
91
|
+
throw error;
|
|
92
|
+
const info = { status: error.status, errorCode: `CONTROL_${error.kind}` };
|
|
93
|
+
if (invoked)
|
|
94
|
+
throw new ControlResultUnknownError(info);
|
|
95
|
+
const message = error.kind === 'TRANSPORT_TIMEOUT' ? 'Control HTTP transport deadline exceeded.' :
|
|
96
|
+
error.kind === 'PROTOCOL_ERROR' ? 'Invalid Control response.' : 'Control transport failed.';
|
|
97
|
+
throw new ControlError(message, info);
|
|
98
|
+
}
|
|
99
|
+
const { value, status, ok } = response;
|
|
100
|
+
if (!ok) {
|
|
101
|
+
const error = record(value) ? value : {};
|
|
102
|
+
const valid = typeof error.errorCode === 'string' && typeof error.message === 'string';
|
|
103
|
+
const info = {
|
|
104
|
+
...runInfo(value), status,
|
|
105
|
+
errorCode: valid ? error.errorCode : 'CONTROL_PROTOCOL_ERROR',
|
|
106
|
+
};
|
|
107
|
+
if (info.errorCode === 'CONTROL_RESULT_UNKNOWN' || (invoked && !valid)) {
|
|
108
|
+
throw new ControlResultUnknownError(info);
|
|
109
|
+
}
|
|
110
|
+
throw new ControlError(valid ? error.message : 'Invalid Control error response.', info);
|
|
111
|
+
}
|
|
112
|
+
return { value, status };
|
|
113
|
+
}
|
|
114
|
+
/** Discover visible contracts; defaults and authorization are resolved by the server. */
|
|
115
|
+
async discover(options = {}) {
|
|
116
|
+
keys(options, ['page', 'size', 'search']);
|
|
117
|
+
if ((options.page !== undefined && (!Number.isSafeInteger(options.page) || options.page < 1)) ||
|
|
118
|
+
(options.size !== undefined && (!Number.isSafeInteger(options.size) || options.size < 1 || options.size > 200)) ||
|
|
119
|
+
(options.search !== undefined && typeof options.search !== 'string')) {
|
|
120
|
+
throw new TypeError('Tier0 SDK: invalid Control discovery options.');
|
|
121
|
+
}
|
|
122
|
+
const { value, status } = await this.post('/openapi/v1/controls/discover', options, false);
|
|
123
|
+
if (!record(value) || !Number.isSafeInteger(value.total) || value.total < 0 ||
|
|
124
|
+
!Array.isArray(value.list) || !value.list.every(definition)) {
|
|
125
|
+
throw new ControlError('Invalid Control discovery response.', { status, errorCode: 'CONTROL_PROTOCOL_ERROR' });
|
|
126
|
+
}
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
use(selector) {
|
|
130
|
+
keys(selector, ['code']);
|
|
131
|
+
const code = selector.code;
|
|
132
|
+
if (typeof code !== 'string' || code.length > 128 || !/^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/.test(code)) {
|
|
133
|
+
throw new TypeError('Tier0 SDK: invalid Control code.');
|
|
134
|
+
}
|
|
135
|
+
const path = `/openapi/v1/controls/${encodeURIComponent(code)}`;
|
|
136
|
+
return {
|
|
137
|
+
describe: async () => {
|
|
138
|
+
const { value, status } = await this.post(`${path}/describe`, {}, false);
|
|
139
|
+
if (!definition(value) || value.code !== code) {
|
|
140
|
+
throw new ControlError('Invalid Control description.', { status, errorCode: 'CONTROL_PROTOCOL_ERROR' });
|
|
141
|
+
}
|
|
142
|
+
return value;
|
|
143
|
+
},
|
|
144
|
+
invoke: async (input, options = {}) => {
|
|
145
|
+
keys(options, ['idempotencyKey']);
|
|
146
|
+
const key = options.idempotencyKey;
|
|
147
|
+
if (key !== undefined && (typeof key !== 'string' || !key || key.trim() !== key || new TextEncoder().encode(key).length > 128)) {
|
|
148
|
+
throw new TypeError('Tier0 SDK: invalid Control idempotency key.');
|
|
149
|
+
}
|
|
150
|
+
if (!json(input))
|
|
151
|
+
throw new TypeError('Tier0 SDK: Control input must be JSON.');
|
|
152
|
+
const { value, status } = await this.post(`${path}/invoke`, {
|
|
153
|
+
input, ...(key === undefined ? {} : { idempotencyKey: key }),
|
|
154
|
+
}, true);
|
|
155
|
+
if (!record(value) || value.code !== code || typeof value.contractVersion !== 'string' || !value.contractVersion ||
|
|
156
|
+
!Object.prototype.hasOwnProperty.call(value, 'result') || typeof value.runID !== 'string' || !value.runID ||
|
|
157
|
+
typeof value.status !== 'string' || !value.status || ['accepted', 'queued', 'pending', 'running'].includes(value.status) ||
|
|
158
|
+
typeof value.replayed !== 'boolean') {
|
|
159
|
+
throw new ControlResultUnknownError({ ...runInfo(value), status, errorCode: 'CONTROL_PROTOCOL_ERROR' });
|
|
160
|
+
}
|
|
161
|
+
return value;
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
exports.ControlsClient = ControlsClient;
|
|
167
|
+
/** Uses configureClient()/TIER0_API_HOST/TIER0_API_KEY, resolved for every call. */
|
|
168
|
+
exports.controls = new ControlsClient();
|
|
169
|
+
//# sourceMappingURL=controls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controls.js","sourceRoot":"","sources":["../../src/controls.ts"],"names":[],"mappings":"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","sourcesContent":["import { getClient, type HttpClient, type ClientConfig } from './openapi/client.js';\r\nimport { CloudTransport, CloudTransportError, DEFAULT_CLOUD_TRANSPORT_TIMEOUT_MS, type CloudTransportConfig } from './cloud-transport.js';\r\n\r\n/** Cloud Control contracts are independent of generated OpenAPI / Enterprise flow types. */\r\nexport type ControlJSON = null | boolean | number | string | ControlJSON[] | { [key: string]: ControlJSON };\r\n\r\nexport interface PublicControl {\r\n code: string;\r\n name: string;\r\n description: string;\r\n contractVersion: string;\r\n inputSchema: Record<string, ControlJSON>;\r\n outputSchema: Record<string, ControlJSON>;\r\n sideEffect: 'read' | 'write';\r\n idempotency: 'none' | 'optional' | 'required';\r\n timeoutMs: number;\r\n status: string;\r\n}\r\n\r\nexport interface ControlDiscoverOptions {\r\n page?: number;\r\n size?: number;\r\n search?: string;\r\n}\r\n\r\nexport interface ControlDiscoverResponse {\r\n total: number;\r\n list: PublicControl[];\r\n}\r\n\r\n/** Shared HTTP settings; UNS v2 options never affect Control execution or headers. */\r\nexport interface ControlTransportConfig extends CloudTransportConfig {}\r\n\r\nexport interface CloudClientConfig extends ClientConfig, ControlTransportConfig {}\r\n\r\nexport const DEFAULT_CONTROL_TRANSPORT_TIMEOUT_MS = DEFAULT_CLOUD_TRANSPORT_TIMEOUT_MS;\r\n\r\nexport interface ControlInvokeOptions {\r\n /** Stable caller-owned key for one business intent, at most 128 UTF-8 bytes. Never generated or retried by the SDK. */\r\n idempotencyKey?: string;\r\n}\r\n\r\nexport interface ControlInvokeResponse<Result = ControlJSON> {\r\n code: string;\r\n contractVersion: string;\r\n result: Result;\r\n runID: string;\r\n status: string;\r\n /** True only when the server explicitly reports an idempotent replay. */\r\n replayed: boolean;\r\n}\r\n\r\nexport interface ControlErrorInfo {\r\n /** HTTP status, absent when no HTTP response was received. */\r\n status?: number;\r\n /** Gateway string errorCode, or an SDK-local CONTROL_* transport/protocol code. */\r\n errorCode: string;\r\n runID?: string;\r\n contractVersion?: string;\r\n runStatus?: string;\r\n replayed?: boolean;\r\n}\r\n\r\n/** No Control error is automatically retried, including authentication failures. */\r\nexport class ControlError extends Error {\r\n readonly status?: number;\r\n readonly errorCode: string;\r\n readonly runID?: string;\r\n readonly contractVersion?: string;\r\n readonly runStatus?: string;\r\n readonly replayed?: boolean;\r\n readonly retryable = false;\r\n\r\n constructor(message: string, info: ControlErrorInfo) {\r\n super(message);\r\n this.name = 'ControlError';\r\n this.status = info.status;\r\n this.errorCode = info.errorCode;\r\n this.runID = info.runID;\r\n this.contractVersion = info.contractVersion;\r\n this.runStatus = info.runStatus;\r\n this.replayed = info.replayed;\r\n }\r\n}\r\n\r\n/** Execution may have happened. Reconcile run information; any deliberate replay must reuse the same key. */\r\nexport class ControlResultUnknownError extends ControlError {\r\n readonly outcomeUnknown = true;\r\n\r\n constructor(info: ControlErrorInfo) {\r\n super('Control result is unknown; execution may have occurred. Do not automatically retry.', info);\r\n this.name = 'ControlResultUnknownError';\r\n }\r\n}\r\n\r\nfunction record(value: unknown): value is Record<string, unknown> {\r\n return value !== null && typeof value === 'object' && !Array.isArray(value);\r\n}\r\n\r\nfunction keys(value: unknown, allowed: string[]): void {\r\n if (!record(value) || Reflect.ownKeys(value).some(key => typeof key !== 'string' || !allowed.includes(key))) {\r\n throw new TypeError('Tier0 SDK: unsupported Control option.');\r\n }\r\n}\r\n\r\nfunction json(value: unknown, seen = new Set<unknown>()): boolean {\r\n if (value === null || typeof value === 'string' || typeof value === 'boolean') return true;\r\n if (typeof value === 'number') return Number.isFinite(value);\r\n if (typeof value !== 'object' || seen.has(value)) return false;\r\n if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {\r\n return false;\r\n }\r\n seen.add(value);\r\n const valid = (Array.isArray(value) ? Array.from(value) : Object.values(value)).every(item => json(item, seen));\r\n seen.delete(value);\r\n return valid;\r\n}\r\n\r\nfunction definition(value: unknown): value is PublicControl {\r\n if (!record(value)) return false;\r\n return ['code', 'name', 'description', 'contractVersion', 'status'].every(key => typeof value[key] === 'string') &&\r\n value.code !== '' && value.contractVersion !== '' && record(value.inputSchema) && record(value.outputSchema) &&\r\n ['read', 'write'].includes(value.sideEffect as string) &&\r\n ['none', 'optional', 'required'].includes(value.idempotency as string) &&\r\n typeof value.timeoutMs === 'number' && Number.isSafeInteger(value.timeoutMs) && value.timeoutMs > 0;\r\n}\r\n\r\nfunction runInfo(value: unknown): Partial<ControlErrorInfo> {\r\n if (!record(value)) return {};\r\n return {\r\n runID: typeof value.runID === 'string' ? value.runID : undefined,\r\n contractVersion: typeof value.contractVersion === 'string' ? value.contractVersion : undefined,\r\n runStatus: typeof value.status === 'string' ? value.status : undefined,\r\n replayed: typeof value.replayed === 'boolean' ? value.replayed : undefined,\r\n };\r\n}\r\n\r\nexport interface ControlHandle<Input = ControlJSON, Result = ControlJSON> {\r\n describe(): Promise<PublicControl>;\r\n /**\r\n * One POST only. Input is opaque business JSON, including fields named streamId,\r\n * entryId, deploymentVersion or timeoutMs. Only the server chooses binding/version/timeout.\r\n * Generic types are caller assertions; the server validates the contract schemas.\r\n */\r\n invoke(input: Input, options?: ControlInvokeOptions): Promise<ControlInvokeResponse<Result>>;\r\n}\r\n\r\nexport class ControlsClient {\r\n private readonly transport: CloudTransport;\r\n\r\n constructor(resolveClient: () => HttpClient = getClient, config: ControlTransportConfig = {}) {\r\n this.transport = new CloudTransport(resolveClient, config);\r\n }\r\n\r\n private async post(path: string, body: unknown, invoked: boolean): Promise<{ value: unknown; status: number }> {\r\n let response: Awaited<ReturnType<CloudTransport['post']>>;\r\n try {\r\n response = await this.transport.post(path, body);\r\n } catch (error) {\r\n if (!(error instanceof CloudTransportError)) throw error;\r\n const info = { status: error.status, errorCode: `CONTROL_${error.kind}` };\r\n if (invoked) throw new ControlResultUnknownError(info);\r\n const message = error.kind === 'TRANSPORT_TIMEOUT' ? 'Control HTTP transport deadline exceeded.' :\r\n error.kind === 'PROTOCOL_ERROR' ? 'Invalid Control response.' : 'Control transport failed.';\r\n throw new ControlError(message, info);\r\n }\r\n const { value, status, ok } = response;\r\n if (!ok) {\r\n const error = record(value) ? value : {};\r\n const valid = typeof error.errorCode === 'string' && typeof error.message === 'string';\r\n const info: ControlErrorInfo = {\r\n ...runInfo(value), status,\r\n errorCode: valid ? error.errorCode as string : 'CONTROL_PROTOCOL_ERROR',\r\n };\r\n if (info.errorCode === 'CONTROL_RESULT_UNKNOWN' || (invoked && !valid)) {\r\n throw new ControlResultUnknownError(info);\r\n }\r\n throw new ControlError(valid ? error.message as string : 'Invalid Control error response.', info);\r\n }\r\n return { value, status };\r\n }\r\n\r\n /** Discover visible contracts; defaults and authorization are resolved by the server. */\r\n async discover(options: ControlDiscoverOptions = {}): Promise<ControlDiscoverResponse> {\r\n keys(options, ['page', 'size', 'search']);\r\n if ((options.page !== undefined && (!Number.isSafeInteger(options.page) || options.page < 1)) ||\r\n (options.size !== undefined && (!Number.isSafeInteger(options.size) || options.size < 1 || options.size > 200)) ||\r\n (options.search !== undefined && typeof options.search !== 'string')) {\r\n throw new TypeError('Tier0 SDK: invalid Control discovery options.');\r\n }\r\n const { value, status } = await this.post('/openapi/v1/controls/discover', options, false);\r\n if (!record(value) || !Number.isSafeInteger(value.total) || (value.total as number) < 0 ||\r\n !Array.isArray(value.list) || !value.list.every(definition)) {\r\n throw new ControlError('Invalid Control discovery response.', { status, errorCode: 'CONTROL_PROTOCOL_ERROR' });\r\n }\r\n return value as unknown as ControlDiscoverResponse;\r\n }\r\n\r\n use<Input = ControlJSON, Result = ControlJSON>(selector: { code: string }): ControlHandle<Input, Result> {\r\n keys(selector, ['code']);\r\n const code = selector.code;\r\n if (typeof code !== 'string' || code.length > 128 || !/^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/.test(code)) {\r\n throw new TypeError('Tier0 SDK: invalid Control code.');\r\n }\r\n const path = `/openapi/v1/controls/${encodeURIComponent(code)}`;\r\n return {\r\n describe: async () => {\r\n const { value, status } = await this.post(`${path}/describe`, {}, false);\r\n if (!definition(value) || value.code !== code) {\r\n throw new ControlError('Invalid Control description.', { status, errorCode: 'CONTROL_PROTOCOL_ERROR' });\r\n }\r\n return value;\r\n },\r\n invoke: async (input, options = {}) => {\r\n keys(options, ['idempotencyKey']);\r\n const key = options.idempotencyKey;\r\n if (key !== undefined && (typeof key !== 'string' || !key || key.trim() !== key || new TextEncoder().encode(key).length > 128)) {\r\n throw new TypeError('Tier0 SDK: invalid Control idempotency key.');\r\n }\r\n if (!json(input)) throw new TypeError('Tier0 SDK: Control input must be JSON.');\r\n const { value, status } = await this.post(`${path}/invoke`, {\r\n input, ...(key === undefined ? {} : { idempotencyKey: key }),\r\n }, true);\r\n if (!record(value) || value.code !== code || typeof value.contractVersion !== 'string' || !value.contractVersion ||\r\n !Object.prototype.hasOwnProperty.call(value, 'result') || typeof value.runID !== 'string' || !value.runID ||\r\n typeof value.status !== 'string' || !value.status || ['accepted', 'queued', 'pending', 'running'].includes(value.status) ||\r\n typeof value.replayed !== 'boolean') {\r\n throw new ControlResultUnknownError({ ...runInfo(value), status, errorCode: 'CONTROL_PROTOCOL_ERROR' });\r\n }\r\n return value as unknown as ControlInvokeResponse<Result>;\r\n },\r\n };\r\n }\r\n}\r\n\r\n/** Uses configureClient()/TIER0_API_HOST/TIER0_API_KEY, resolved for every call. */\r\nexport const controls = new ControlsClient();\r\n"]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { DataCall } from './call.js';
|
|
2
|
+
import type { CollectionDefinition } from './define.js';
|
|
3
|
+
export type DataEnvironment = 'preview' | 'deployed';
|
|
4
|
+
export interface EnsureWarning {
|
|
5
|
+
code: string;
|
|
6
|
+
path?: string;
|
|
7
|
+
message?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface EnsureNote {
|
|
10
|
+
path: string;
|
|
11
|
+
reason?: string;
|
|
12
|
+
message?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface EnsureResult {
|
|
15
|
+
alias: string;
|
|
16
|
+
collectionId: string;
|
|
17
|
+
revision: number;
|
|
18
|
+
status: 'created' | 'unchanged' | 'widened';
|
|
19
|
+
ignored?: EnsureNote[];
|
|
20
|
+
warnings?: EnsureWarning[];
|
|
21
|
+
indexStatus?: unknown[];
|
|
22
|
+
}
|
|
23
|
+
export interface EnsureConflict {
|
|
24
|
+
alias: string;
|
|
25
|
+
collectionId?: string;
|
|
26
|
+
path?: string;
|
|
27
|
+
reason?: string;
|
|
28
|
+
current?: string;
|
|
29
|
+
declared?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface EnsureState {
|
|
32
|
+
appId?: string;
|
|
33
|
+
results: EnsureResult[];
|
|
34
|
+
/** Lower-cased alias → collection ID. */
|
|
35
|
+
collectionIds: Map<string, string>;
|
|
36
|
+
/** Declarations that conflicted in a deployment (served with their existing ID, if any). */
|
|
37
|
+
conflicts: EnsureConflict[];
|
|
38
|
+
}
|
|
39
|
+
export interface Logger {
|
|
40
|
+
info(...args: unknown[]): void;
|
|
41
|
+
warn(...args: unknown[]): void;
|
|
42
|
+
error(...args: unknown[]): void;
|
|
43
|
+
}
|
|
44
|
+
export interface BootstrapConfig {
|
|
45
|
+
call: DataCall;
|
|
46
|
+
collections: readonly CollectionDefinition[];
|
|
47
|
+
environment: DataEnvironment;
|
|
48
|
+
logger: Logger;
|
|
49
|
+
}
|
|
50
|
+
/** At most one self-healing rebuild per client in this window. */
|
|
51
|
+
export declare const REBUILD_INTERVAL_MS = 30000;
|
|
52
|
+
/**
|
|
53
|
+
* Whether a record call failed because the collection ID it used is gone or
|
|
54
|
+
* no longer granted, by UNS's stable `data.reason` code. Archive and Preview
|
|
55
|
+
* purge also remove the App grant, so most stale IDs answer
|
|
56
|
+
* COLLECTION_NOT_GRANTED or COLLECTION_NOT_FOUND. APP_PERMISSION_MISSING (the
|
|
57
|
+
* key lacks e.g. `uns.delete`) and APP_GRANT_REVOKED are not stale IDs.
|
|
58
|
+
*/
|
|
59
|
+
export declare function isStaleCollectionError(error: unknown): boolean;
|
|
60
|
+
export declare function createBootstrap(config: BootstrapConfig): {
|
|
61
|
+
ensure: () => Promise<EnsureState>;
|
|
62
|
+
resolve: (definition: CollectionDefinition) => Promise<string>;
|
|
63
|
+
recover: <R>(fn: () => Promise<R>, options?: {
|
|
64
|
+
retry?: boolean;
|
|
65
|
+
}) => Promise<R>;
|
|
66
|
+
};
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.REBUILD_INTERVAL_MS = void 0;
|
|
4
|
+
exports.isStaleCollectionError = isStaleCollectionError;
|
|
5
|
+
exports.createBootstrap = createBootstrap;
|
|
6
|
+
/**
|
|
7
|
+
* First-access bootstrap: one batched `ensure` of every declared collection.
|
|
8
|
+
*
|
|
9
|
+
* - ensure only creates or widens; storage never narrows from here, so old
|
|
10
|
+
* and new code may cold-start in any order.
|
|
11
|
+
* - The result is memoized per client, i.e. per server process; a client
|
|
12
|
+
* built from edited declarations ensures again.
|
|
13
|
+
* Failures are not memoized: a later call retries (an imported App may
|
|
14
|
+
* answer "hosted App is not ready").
|
|
15
|
+
* - Seed documents are inserted only for collections ensure reports as
|
|
16
|
+
* `created`: fixed `_id`s, atomic chunks of 200 with a stable idempotency
|
|
17
|
+
* key per chunk so a retried chunk replays instead of duplicating. Seed
|
|
18
|
+
* writes never carry a data subject.
|
|
19
|
+
* - Preview throws on SCHEMA_CONFLICT and warnings so the worker sees them;
|
|
20
|
+
* a deployment logs them and keeps serving with the IDs it has.
|
|
21
|
+
* - Self-healing: a Preview reset hard-deletes collections, and a worker or
|
|
22
|
+
* admin may archive one, while this process still holds the old IDs. A
|
|
23
|
+
* record call that fails with `data.reason` COLLECTION_NOT_FOUND or
|
|
24
|
+
* COLLECTION_NOT_GRANTED drops the memo, ensures again (seeding what is created)
|
|
25
|
+
* and retries the call once, but only when a collection ID actually
|
|
26
|
+
* changed. Rebuilds are limited to one per REBUILD_INTERVAL_MS per client;
|
|
27
|
+
* APP_GRANT_REVOKED is never retried (ensure itself reports it).
|
|
28
|
+
* - `ignored` notes never fail a request. ensure never writes annotations, so
|
|
29
|
+
* an edited `.describe()` or collection description comes back as
|
|
30
|
+
* ANNOTATION_NOT_APPLIED; it is logged as a warning pointing at
|
|
31
|
+
* schema:replace, which is the only way to change stored annotations.
|
|
32
|
+
*/
|
|
33
|
+
const node_crypto_1 = require("node:crypto");
|
|
34
|
+
const collection_js_1 = require("./collection.js");
|
|
35
|
+
const error_js_1 = require("./error.js");
|
|
36
|
+
const schema_js_1 = require("./schema.js");
|
|
37
|
+
const SEED_ATTEMPTS = 3;
|
|
38
|
+
/** At most one self-healing rebuild per client in this window. */
|
|
39
|
+
exports.REBUILD_INTERVAL_MS = 30_000;
|
|
40
|
+
/**
|
|
41
|
+
* Whether a record call failed because the collection ID it used is gone or
|
|
42
|
+
* no longer granted, by UNS's stable `data.reason` code. Archive and Preview
|
|
43
|
+
* purge also remove the App grant, so most stale IDs answer
|
|
44
|
+
* COLLECTION_NOT_GRANTED or COLLECTION_NOT_FOUND. APP_PERMISSION_MISSING (the
|
|
45
|
+
* key lacks e.g. `uns.delete`) and APP_GRANT_REVOKED are not stale IDs.
|
|
46
|
+
*/
|
|
47
|
+
function isStaleCollectionError(error) {
|
|
48
|
+
return (error instanceof error_js_1.DataError &&
|
|
49
|
+
(error.reason === error_js_1.ACCESS_REASONS.notGranted || error.reason === error_js_1.ACCESS_REASONS.notFound));
|
|
50
|
+
}
|
|
51
|
+
function sameCollectionIds(a, b) {
|
|
52
|
+
if (a.collectionIds.size !== b.collectionIds.size)
|
|
53
|
+
return false;
|
|
54
|
+
for (const [alias, id] of a.collectionIds) {
|
|
55
|
+
if (b.collectionIds.get(alias) !== id)
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
/** Idempotency key of one seed chunk (UNS keys are at most 96 characters). */
|
|
61
|
+
function seedChunkKey(collectionId, chunk) {
|
|
62
|
+
return `seed-${(0, node_crypto_1.createHash)('sha256').update(collectionId).digest('hex').slice(0, 40)}-${chunk}`;
|
|
63
|
+
}
|
|
64
|
+
/** What to do about each SCHEMA_CONFLICT reason (CloudAppCollectionConflict.reason). */
|
|
65
|
+
const CONFLICT_HINTS = {
|
|
66
|
+
TYPE_CONFLICT: "a field's type cannot change; use a new field name and backfill with updateMany",
|
|
67
|
+
LIMIT_EXCEEDED: 'the joined schema exceeds a UNS limit (depth, properties, enum values or indexes); split the structure',
|
|
68
|
+
OPEN_OBJECT: 'every object must be closed; declare it with z.object',
|
|
69
|
+
ALIAS_IN_USE: 'the alias belongs to another collection; pick another alias',
|
|
70
|
+
NAME_IN_USE: 'the name belongs to another collection; pick another alias',
|
|
71
|
+
INVALID_INDEX: 'fix the index declaration',
|
|
72
|
+
JOIN_UNSUPPORTED: 'the stored schema holds a node the declaration cannot widen (e.g. enum values outside its range); repair it with schema:replace via the tier0 MCP',
|
|
73
|
+
DATA_RULE_FIELD: 'an employee data rule (ownerField / stateField) uses this field; keep it required with the same type, or ask a workspace admin to change the rule',
|
|
74
|
+
};
|
|
75
|
+
function describeConflicts(conflicts) {
|
|
76
|
+
return conflicts
|
|
77
|
+
.map((conflict) => {
|
|
78
|
+
const where = `${conflict.alias}${conflict.path ? `.${conflict.path}` : ''}`;
|
|
79
|
+
const types = conflict.current || conflict.declared ? ` (stored ${conflict.current ?? '?'}, declared ${conflict.declared ?? '?'})` : '';
|
|
80
|
+
const hint = conflict.reason && CONFLICT_HINTS[conflict.reason] ? ` — ${CONFLICT_HINTS[conflict.reason]}` : '';
|
|
81
|
+
return ` - ${where}: ${conflict.reason ?? 'conflict'}${types}${hint}`;
|
|
82
|
+
})
|
|
83
|
+
.join('\n');
|
|
84
|
+
}
|
|
85
|
+
function notePath(alias, note) {
|
|
86
|
+
return note.path ? `${alias}.${note.path}` : alias;
|
|
87
|
+
}
|
|
88
|
+
function describeWarnings(results) {
|
|
89
|
+
return results
|
|
90
|
+
.flatMap((result) => (result.warnings ?? []).map((warning) => ` - ${result.alias}${warning.path ? `.${warning.path}` : ''}: ${warning.code}${warning.message ? ` — ${warning.message}` : ''}`))
|
|
91
|
+
.join('\n');
|
|
92
|
+
}
|
|
93
|
+
function createBootstrap(config) {
|
|
94
|
+
const { call, collections, environment, logger } = config;
|
|
95
|
+
let pending = null;
|
|
96
|
+
let lastRebuild = Number.NEGATIVE_INFINITY;
|
|
97
|
+
async function requestEnsure(items) {
|
|
98
|
+
// ensure is App-level work: it never carries the employee data subject.
|
|
99
|
+
return call('appCollectionsEnsure', { collections: items });
|
|
100
|
+
}
|
|
101
|
+
async function insertSeedChunk(collectionId, chunk, documents) {
|
|
102
|
+
for (let attempt = 1;; attempt += 1) {
|
|
103
|
+
try {
|
|
104
|
+
await call('recordInsertMany', {
|
|
105
|
+
collectionId,
|
|
106
|
+
documents,
|
|
107
|
+
ordered: true,
|
|
108
|
+
atomic: true,
|
|
109
|
+
idempotencyKey: seedChunkKey(collectionId, chunk),
|
|
110
|
+
});
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
// Same key on retry: a chunk that did commit replays instead of failing on duplicate IDs.
|
|
115
|
+
if (!(error instanceof error_js_1.DataError) || !error.retryable || attempt >= SEED_ATTEMPTS)
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function writeSeeds(results) {
|
|
121
|
+
for (const result of results) {
|
|
122
|
+
if (result.status !== 'created')
|
|
123
|
+
continue;
|
|
124
|
+
const definition = collections.find((candidate) => candidate.alias.toLowerCase() === result.alias.toLowerCase());
|
|
125
|
+
const seed = definition?.seed;
|
|
126
|
+
if (!definition || !seed || seed.length === 0)
|
|
127
|
+
continue;
|
|
128
|
+
try {
|
|
129
|
+
const documents = seed.map((document) => {
|
|
130
|
+
const { id, data } = (0, schema_js_1.validateDocument)(definition, document);
|
|
131
|
+
return { id, data };
|
|
132
|
+
});
|
|
133
|
+
for (let start = 0; start < documents.length; start += collection_js_1.MAX_ATOMIC_ITEMS) {
|
|
134
|
+
await insertSeedChunk(result.collectionId, start / collection_js_1.MAX_ATOMIC_ITEMS, documents.slice(start, start + collection_js_1.MAX_ATOMIC_ITEMS));
|
|
135
|
+
}
|
|
136
|
+
logger.info(`[data] seeded ${documents.length} record(s) into "${result.alias}"`);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
// The collection exists now; a later ensure reports "unchanged" and
|
|
140
|
+
// never re-seeds, so the failure must be visible in the server log.
|
|
141
|
+
logger.error(`[data] seed for new collection "${result.alias}" failed; it keeps the chunks written so far:`, error);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function run(payload) {
|
|
146
|
+
const conflicts = [];
|
|
147
|
+
let remaining = payload;
|
|
148
|
+
let response = { results: [] };
|
|
149
|
+
while (remaining.length > 0) {
|
|
150
|
+
try {
|
|
151
|
+
response = await requestEnsure(remaining);
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
if (!(error instanceof error_js_1.DataError) || error.code !== 'SCHEMA_CONFLICT')
|
|
156
|
+
throw error;
|
|
157
|
+
const reported = Array.isArray(error.details.conflicts) ? error.details.conflicts : [];
|
|
158
|
+
const summary = describeConflicts(reported);
|
|
159
|
+
if (environment === 'preview') {
|
|
160
|
+
throw new error_js_1.DataError({
|
|
161
|
+
message: `The collection declarations conflict with the stored collection schemas; nothing was changed:\n${summary}`,
|
|
162
|
+
status: 409,
|
|
163
|
+
code: 'SCHEMA_CONFLICT',
|
|
164
|
+
upstreamStatus: error.upstreamStatus,
|
|
165
|
+
requestId: error.requestId,
|
|
166
|
+
details: error.details,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
// A deployment keeps serving: drop the conflicting declarations (the
|
|
170
|
+
// whole batch was rejected) and ensure the rest again.
|
|
171
|
+
const conflicting = new Set(reported.map((conflict) => conflict.alias.toLowerCase()));
|
|
172
|
+
const next = remaining.filter((item) => !conflicting.has(item.alias.toLowerCase()));
|
|
173
|
+
if (next.length === remaining.length)
|
|
174
|
+
throw error;
|
|
175
|
+
logger.error(`[data] ensure: declarations conflict with stored schemas; serving them unchanged:\n${summary}`);
|
|
176
|
+
conflicts.push(...reported);
|
|
177
|
+
remaining = next;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const results = response.results ?? [];
|
|
181
|
+
const collectionIds = new Map();
|
|
182
|
+
for (const conflict of conflicts) {
|
|
183
|
+
if (conflict.collectionId)
|
|
184
|
+
collectionIds.set(conflict.alias.toLowerCase(), conflict.collectionId);
|
|
185
|
+
}
|
|
186
|
+
for (const result of results) {
|
|
187
|
+
collectionIds.set(result.alias.toLowerCase(), result.collectionId);
|
|
188
|
+
const notes = result.ignored ?? [];
|
|
189
|
+
const annotations = notes.filter((note) => note.reason === 'ANNOTATION_NOT_APPLIED');
|
|
190
|
+
const wider = notes.filter((note) => note.reason !== 'ANNOTATION_NOT_APPLIED');
|
|
191
|
+
if (wider.length > 0) {
|
|
192
|
+
logger.info(`[data] "${result.alias}" storage is wider than this declaration (or has retired parts); ignored: ` +
|
|
193
|
+
wider.map((note) => `${note.path || '(root)'} ${note.reason ?? ''}`.trim()).join(', '));
|
|
194
|
+
}
|
|
195
|
+
if (annotations.length > 0) {
|
|
196
|
+
// A hint, not an error: storage keeps its annotations and serves as before.
|
|
197
|
+
logger.warn(`[data] ensure does not change stored titles/descriptions; not applied: ${annotations.map((note) => notePath(result.alias, note)).join(', ')}. ` +
|
|
198
|
+
`Apply them with schema:replace via the tier0 MCP, using compileCollection(...) of the declaration as the body.`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
await writeSeeds(results);
|
|
202
|
+
const warnings = describeWarnings(results);
|
|
203
|
+
if (warnings) {
|
|
204
|
+
if (environment === 'preview') {
|
|
205
|
+
throw new error_js_1.DataError({
|
|
206
|
+
message: `Collections were ensured with warnings:\n${warnings}\n` +
|
|
207
|
+
'REQUIRED_NOT_ENFORCED: make the field optional again or narrow explicitly with schema:replace via the tier0 MCP. ' +
|
|
208
|
+
'INDEX_BUILD_FAILED: fix the duplicate or invalid data, then the next ensure retries the index.',
|
|
209
|
+
status: 500,
|
|
210
|
+
code: 'SCHEMA_WARNINGS',
|
|
211
|
+
details: { results },
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
logger.warn(`[data] ensure warnings:\n${warnings}`);
|
|
215
|
+
}
|
|
216
|
+
return { appId: response.appId, results, collectionIds, conflicts };
|
|
217
|
+
}
|
|
218
|
+
/** Ensures every declaration once per client; a failure is forgotten so the next call retries. */
|
|
219
|
+
function ensure() {
|
|
220
|
+
pending ??= Promise.resolve()
|
|
221
|
+
.then(() => run(collections.map(schema_js_1.compileCollection)))
|
|
222
|
+
.catch((error) => {
|
|
223
|
+
pending = null;
|
|
224
|
+
throw error;
|
|
225
|
+
});
|
|
226
|
+
return pending;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Re-ensures after `before` turned out stale. Joins a rebuild already in
|
|
230
|
+
* flight; otherwise rebuilds at most once per REBUILD_INTERVAL_MS. Resolves
|
|
231
|
+
* whether any collection ID changed; ensure errors (APP_GRANT_REVOKED, …)
|
|
232
|
+
* propagate.
|
|
233
|
+
*/
|
|
234
|
+
async function rebuild(before, cause) {
|
|
235
|
+
const current = ensure();
|
|
236
|
+
const latest = await current;
|
|
237
|
+
if (latest !== before)
|
|
238
|
+
return !sameCollectionIds(before, latest);
|
|
239
|
+
if (pending !== current)
|
|
240
|
+
return !sameCollectionIds(before, await ensure());
|
|
241
|
+
const now = Date.now();
|
|
242
|
+
if (now - lastRebuild < exports.REBUILD_INTERVAL_MS)
|
|
243
|
+
return false;
|
|
244
|
+
lastRebuild = now;
|
|
245
|
+
logger.warn(`[data] a collection ID is no longer usable (${cause.code}: ${cause.message}); ensuring collections again`);
|
|
246
|
+
pending = null;
|
|
247
|
+
return !sameCollectionIds(before, await ensure());
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Runs one data call; when it fails on a stale collection ID, rebuilds and
|
|
251
|
+
* runs it once more if an ID changed. `retry: false` (calls continuing a
|
|
252
|
+
* cursor or resumeAfter token of the old collection) only rebuilds.
|
|
253
|
+
*/
|
|
254
|
+
async function recover(fn, options = {}) {
|
|
255
|
+
const before = await ensure();
|
|
256
|
+
try {
|
|
257
|
+
return await fn();
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
if (!isStaleCollectionError(error))
|
|
261
|
+
throw error;
|
|
262
|
+
const changed = await rebuild(before, error);
|
|
263
|
+
if (!changed || options.retry === false)
|
|
264
|
+
throw error;
|
|
265
|
+
return fn();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
async function resolve(definition) {
|
|
269
|
+
const state = await ensure();
|
|
270
|
+
const id = state.collectionIds.get(definition.alias.toLowerCase());
|
|
271
|
+
if (!id) {
|
|
272
|
+
throw new error_js_1.DataError({
|
|
273
|
+
message: `Collection "${definition.alias}" is unavailable: ensure did not return it (see the server log).`,
|
|
274
|
+
status: 503,
|
|
275
|
+
code: 'COLLECTION_UNAVAILABLE',
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
return id;
|
|
279
|
+
}
|
|
280
|
+
return { ensure, resolve, recover };
|
|
281
|
+
}
|
|
282
|
+
//# sourceMappingURL=bootstrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../../../src/data/bootstrap.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * First-access bootstrap: one batched `ensure` of every declared collection.\n *\n * - ensure only creates or widens; storage never narrows from here, so old\n * and new code may cold-start in any order.\n * - The result is memoized per client, i.e. per server process; a client\n * built from edited declarations ensures again.\n * Failures are not memoized: a later call retries (an imported App may\n * answer \"hosted App is not ready\").\n * - Seed documents are inserted only for collections ensure reports as\n * `created`: fixed `_id`s, atomic chunks of 200 with a stable idempotency\n * key per chunk so a retried chunk replays instead of duplicating. Seed\n * writes never carry a data subject.\n * - Preview throws on SCHEMA_CONFLICT and warnings so the worker sees them;\n * a deployment logs them and keeps serving with the IDs it has.\n * - Self-healing: a Preview reset hard-deletes collections, and a worker or\n * admin may archive one, while this process still holds the old IDs. A\n * record call that fails with `data.reason` COLLECTION_NOT_FOUND or\n * COLLECTION_NOT_GRANTED drops the memo, ensures again (seeding what is created)\n * and retries the call once, but only when a collection ID actually\n * changed. Rebuilds are limited to one per REBUILD_INTERVAL_MS per client;\n * APP_GRANT_REVOKED is never retried (ensure itself reports it).\n * - `ignored` notes never fail a request. ensure never writes annotations, so\n * an edited `.describe()` or collection description comes back as\n * ANNOTATION_NOT_APPLIED; it is logged as a warning pointing at\n * schema:replace, which is the only way to change stored annotations.\n */\nimport { createHash } from 'node:crypto';\nimport type { DataCall } from './call.js';\nimport { MAX_ATOMIC_ITEMS } from './collection.js';\nimport type { CollectionDefinition } from './define.js';\nimport { ACCESS_REASONS, DataError } from './error.js';\nimport { compileCollection, validateDocument, type CompiledCollection } from './schema.js';\n\nexport type DataEnvironment = 'preview' | 'deployed';\n\nexport interface EnsureWarning {\n code: string;\n path?: string;\n message?: string;\n}\n\nexport interface EnsureNote {\n path: string;\n reason?: string;\n message?: string;\n}\n\nexport interface EnsureResult {\n alias: string;\n collectionId: string;\n revision: number;\n status: 'created' | 'unchanged' | 'widened';\n ignored?: EnsureNote[];\n warnings?: EnsureWarning[];\n indexStatus?: unknown[];\n}\n\nexport interface EnsureConflict {\n alias: string;\n collectionId?: string;\n path?: string;\n reason?: string;\n current?: string;\n declared?: string;\n}\n\nexport interface EnsureState {\n appId?: string;\n results: EnsureResult[];\n /** Lower-cased alias → collection ID. */\n collectionIds: Map<string, string>;\n /** Declarations that conflicted in a deployment (served with their existing ID, if any). */\n conflicts: EnsureConflict[];\n}\n\nexport interface Logger {\n info(...args: unknown[]): void;\n warn(...args: unknown[]): void;\n error(...args: unknown[]): void;\n}\n\nexport interface BootstrapConfig {\n call: DataCall;\n collections: readonly CollectionDefinition[];\n environment: DataEnvironment;\n logger: Logger;\n}\n\nconst SEED_ATTEMPTS = 3;\n\n/** At most one self-healing rebuild per client in this window. */\nexport const REBUILD_INTERVAL_MS = 30_000;\n\n/**\n * Whether a record call failed because the collection ID it used is gone or\n * no longer granted, by UNS's stable `data.reason` code. Archive and Preview\n * purge also remove the App grant, so most stale IDs answer\n * COLLECTION_NOT_GRANTED or COLLECTION_NOT_FOUND. APP_PERMISSION_MISSING (the\n * key lacks e.g. `uns.delete`) and APP_GRANT_REVOKED are not stale IDs.\n */\nexport function isStaleCollectionError(error: unknown): boolean {\n return (\n error instanceof DataError &&\n (error.reason === ACCESS_REASONS.notGranted || error.reason === ACCESS_REASONS.notFound)\n );\n}\n\nfunction sameCollectionIds(a: EnsureState, b: EnsureState): boolean {\n if (a.collectionIds.size !== b.collectionIds.size) return false;\n for (const [alias, id] of a.collectionIds) {\n if (b.collectionIds.get(alias) !== id) return false;\n }\n return true;\n}\n\n/** Idempotency key of one seed chunk (UNS keys are at most 96 characters). */\nfunction seedChunkKey(collectionId: string, chunk: number): string {\n return `seed-${createHash('sha256').update(collectionId).digest('hex').slice(0, 40)}-${chunk}`;\n}\n\n/** What to do about each SCHEMA_CONFLICT reason (CloudAppCollectionConflict.reason). */\nconst CONFLICT_HINTS: Record<string, string> = {\n TYPE_CONFLICT: \"a field's type cannot change; use a new field name and backfill with updateMany\",\n LIMIT_EXCEEDED: 'the joined schema exceeds a UNS limit (depth, properties, enum values or indexes); split the structure',\n OPEN_OBJECT: 'every object must be closed; declare it with z.object',\n ALIAS_IN_USE: 'the alias belongs to another collection; pick another alias',\n NAME_IN_USE: 'the name belongs to another collection; pick another alias',\n INVALID_INDEX: 'fix the index declaration',\n JOIN_UNSUPPORTED:\n 'the stored schema holds a node the declaration cannot widen (e.g. enum values outside its range); repair it with schema:replace via the tier0 MCP',\n DATA_RULE_FIELD:\n 'an employee data rule (ownerField / stateField) uses this field; keep it required with the same type, or ask a workspace admin to change the rule',\n};\n\nfunction describeConflicts(conflicts: EnsureConflict[]): string {\n return conflicts\n .map((conflict) => {\n const where = `${conflict.alias}${conflict.path ? `.${conflict.path}` : ''}`;\n const types = conflict.current || conflict.declared ? ` (stored ${conflict.current ?? '?'}, declared ${conflict.declared ?? '?'})` : '';\n const hint = conflict.reason && CONFLICT_HINTS[conflict.reason] ? ` — ${CONFLICT_HINTS[conflict.reason]}` : '';\n return ` - ${where}: ${conflict.reason ?? 'conflict'}${types}${hint}`;\n })\n .join('\\n');\n}\n\nfunction notePath(alias: string, note: EnsureNote): string {\n return note.path ? `${alias}.${note.path}` : alias;\n}\n\nfunction describeWarnings(results: EnsureResult[]): string {\n return results\n .flatMap((result) =>\n (result.warnings ?? []).map(\n (warning) => ` - ${result.alias}${warning.path ? `.${warning.path}` : ''}: ${warning.code}${warning.message ? ` — ${warning.message}` : ''}`,\n ),\n )\n .join('\\n');\n}\n\nexport function createBootstrap(config: BootstrapConfig) {\n const { call, collections, environment, logger } = config;\n let pending: Promise<EnsureState> | null = null;\n let lastRebuild = Number.NEGATIVE_INFINITY;\n\n async function requestEnsure(items: CompiledCollection[]) {\n // ensure is App-level work: it never carries the employee data subject.\n return call<{ appId?: string; results: EnsureResult[] }>('appCollectionsEnsure', { collections: items });\n }\n\n async function insertSeedChunk(collectionId: string, chunk: number, documents: { id?: string; data: Record<string, unknown> }[]) {\n for (let attempt = 1; ; attempt += 1) {\n try {\n await call('recordInsertMany', {\n collectionId,\n documents,\n ordered: true,\n atomic: true,\n idempotencyKey: seedChunkKey(collectionId, chunk),\n });\n return;\n } catch (error) {\n // Same key on retry: a chunk that did commit replays instead of failing on duplicate IDs.\n if (!(error instanceof DataError) || !error.retryable || attempt >= SEED_ATTEMPTS) throw error;\n }\n }\n }\n\n async function writeSeeds(results: EnsureResult[]) {\n for (const result of results) {\n if (result.status !== 'created') continue;\n const definition = collections.find((candidate) => candidate.alias.toLowerCase() === result.alias.toLowerCase());\n const seed = definition?.seed;\n if (!definition || !seed || seed.length === 0) continue;\n try {\n const documents = seed.map((document) => {\n const { id, data } = validateDocument(definition, document as Record<string, unknown>);\n return { id, data };\n });\n for (let start = 0; start < documents.length; start += MAX_ATOMIC_ITEMS) {\n await insertSeedChunk(result.collectionId, start / MAX_ATOMIC_ITEMS, documents.slice(start, start + MAX_ATOMIC_ITEMS));\n }\n logger.info(`[data] seeded ${documents.length} record(s) into \"${result.alias}\"`);\n } catch (error) {\n // The collection exists now; a later ensure reports \"unchanged\" and\n // never re-seeds, so the failure must be visible in the server log.\n logger.error(`[data] seed for new collection \"${result.alias}\" failed; it keeps the chunks written so far:`, error);\n }\n }\n }\n\n async function run(payload: CompiledCollection[]): Promise<EnsureState> {\n const conflicts: EnsureConflict[] = [];\n let remaining = payload;\n let response: { appId?: string; results: EnsureResult[] } = { results: [] };\n while (remaining.length > 0) {\n try {\n response = await requestEnsure(remaining);\n break;\n } catch (error) {\n if (!(error instanceof DataError) || error.code !== 'SCHEMA_CONFLICT') throw error;\n const reported = Array.isArray(error.details.conflicts) ? (error.details.conflicts as EnsureConflict[]) : [];\n const summary = describeConflicts(reported);\n if (environment === 'preview') {\n throw new DataError({\n message:\n `The collection declarations conflict with the stored collection schemas; nothing was changed:\\n${summary}`,\n status: 409,\n code: 'SCHEMA_CONFLICT',\n upstreamStatus: error.upstreamStatus,\n requestId: error.requestId,\n details: error.details,\n });\n }\n // A deployment keeps serving: drop the conflicting declarations (the\n // whole batch was rejected) and ensure the rest again.\n const conflicting = new Set(reported.map((conflict) => conflict.alias.toLowerCase()));\n const next = remaining.filter((item) => !conflicting.has(item.alias.toLowerCase()));\n if (next.length === remaining.length) throw error;\n logger.error(`[data] ensure: declarations conflict with stored schemas; serving them unchanged:\\n${summary}`);\n conflicts.push(...reported);\n remaining = next;\n }\n }\n\n const results = response.results ?? [];\n const collectionIds = new Map<string, string>();\n for (const conflict of conflicts) {\n if (conflict.collectionId) collectionIds.set(conflict.alias.toLowerCase(), conflict.collectionId);\n }\n for (const result of results) {\n collectionIds.set(result.alias.toLowerCase(), result.collectionId);\n const notes = result.ignored ?? [];\n const annotations = notes.filter((note) => note.reason === 'ANNOTATION_NOT_APPLIED');\n const wider = notes.filter((note) => note.reason !== 'ANNOTATION_NOT_APPLIED');\n if (wider.length > 0) {\n logger.info(\n `[data] \"${result.alias}\" storage is wider than this declaration (or has retired parts); ignored: ` +\n wider.map((note) => `${note.path || '(root)'} ${note.reason ?? ''}`.trim()).join(', '),\n );\n }\n if (annotations.length > 0) {\n // A hint, not an error: storage keeps its annotations and serves as before.\n logger.warn(\n `[data] ensure does not change stored titles/descriptions; not applied: ${annotations.map((note) => notePath(result.alias, note)).join(', ')}. ` +\n `Apply them with schema:replace via the tier0 MCP, using compileCollection(...) of the declaration as the body.`,\n );\n }\n }\n\n await writeSeeds(results);\n\n const warnings = describeWarnings(results);\n if (warnings) {\n if (environment === 'preview') {\n throw new DataError({\n message:\n `Collections were ensured with warnings:\\n${warnings}\\n` +\n 'REQUIRED_NOT_ENFORCED: make the field optional again or narrow explicitly with schema:replace via the tier0 MCP. ' +\n 'INDEX_BUILD_FAILED: fix the duplicate or invalid data, then the next ensure retries the index.',\n status: 500,\n code: 'SCHEMA_WARNINGS',\n details: { results },\n });\n }\n logger.warn(`[data] ensure warnings:\\n${warnings}`);\n }\n\n return { appId: response.appId, results, collectionIds, conflicts };\n }\n\n /** Ensures every declaration once per client; a failure is forgotten so the next call retries. */\n function ensure(): Promise<EnsureState> {\n pending ??= Promise.resolve()\n .then(() => run(collections.map(compileCollection)))\n .catch((error) => {\n pending = null;\n throw error;\n });\n return pending;\n }\n\n /**\n * Re-ensures after `before` turned out stale. Joins a rebuild already in\n * flight; otherwise rebuilds at most once per REBUILD_INTERVAL_MS. Resolves\n * whether any collection ID changed; ensure errors (APP_GRANT_REVOKED, …)\n * propagate.\n */\n async function rebuild(before: EnsureState, cause: DataError): Promise<boolean> {\n const current = ensure();\n const latest = await current;\n if (latest !== before) return !sameCollectionIds(before, latest);\n if (pending !== current) return !sameCollectionIds(before, await ensure());\n const now = Date.now();\n if (now - lastRebuild < REBUILD_INTERVAL_MS) return false;\n lastRebuild = now;\n logger.warn(`[data] a collection ID is no longer usable (${cause.code}: ${cause.message}); ensuring collections again`);\n pending = null;\n return !sameCollectionIds(before, await ensure());\n }\n\n /**\n * Runs one data call; when it fails on a stale collection ID, rebuilds and\n * runs it once more if an ID changed. `retry: false` (calls continuing a\n * cursor or resumeAfter token of the old collection) only rebuilds.\n */\n async function recover<R>(fn: () => Promise<R>, options: { retry?: boolean } = {}): Promise<R> {\n const before = await ensure();\n try {\n return await fn();\n } catch (error) {\n if (!isStaleCollectionError(error)) throw error;\n const changed = await rebuild(before, error as DataError);\n if (!changed || options.retry === false) throw error;\n return fn();\n }\n }\n\n async function resolve(definition: CollectionDefinition): Promise<string> {\n const state = await ensure();\n const id = state.collectionIds.get(definition.alias.toLowerCase());\n if (!id) {\n throw new DataError({\n message: `Collection \"${definition.alias}\" is unavailable: ensure did not return it (see the server log).`,\n status: 503,\n code: 'COLLECTION_UNAVAILABLE',\n });\n }\n return id;\n }\n\n return { ensure, resolve, recover };\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One UNS call of the data layer. The SDK's UNS client resolves host and key,
|
|
3
|
+
* sends the request and checks the envelope; this binds the employee data
|
|
4
|
+
* subject of the current call and turns every failure into a `DataError`.
|
|
5
|
+
*/
|
|
6
|
+
import { type ClientConfig } from '../openapi/client.js';
|
|
7
|
+
import type { UnsV2OperationName } from '../uns-v2/generated.js';
|
|
8
|
+
/** UNS statements time out at 15 s, so a data call waits no longer by default. */
|
|
9
|
+
export declare const DEFAULT_DATA_TIMEOUT_MS = 15000;
|
|
10
|
+
export interface CallOptions {
|
|
11
|
+
/** Employee assertion forwarded from the current business request. */
|
|
12
|
+
dataSubject?: string;
|
|
13
|
+
}
|
|
14
|
+
/** Sends one operation and resolves with the envelope's `data`. */
|
|
15
|
+
export type DataCall = <T>(operation: UnsV2OperationName, request: Record<string, unknown>, options?: CallOptions) => Promise<T>;
|
|
16
|
+
export declare function createDataCall(config: ClientConfig): DataCall;
|