@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,607 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SCHEMA_LIMITS = exports.CollectionSchemaError = void 0;
|
|
4
|
+
exports.zodJsonSchema = zodJsonSchema;
|
|
5
|
+
exports.toRecordSchema = toRecordSchema;
|
|
6
|
+
exports.recordSchemaAt = recordSchemaAt;
|
|
7
|
+
exports.compileCollection = compileCollection;
|
|
8
|
+
exports.validateDocument = validateDocument;
|
|
9
|
+
exports.validateStepData = validateStepData;
|
|
10
|
+
exports.validateUpdate = validateUpdate;
|
|
11
|
+
/**
|
|
12
|
+
* Zod 4 → UNS record schema normalizer, plus write-time validation.
|
|
13
|
+
*
|
|
14
|
+
* UNS stores one closed JSON-schema subset per collection: `type`,
|
|
15
|
+
* `properties`, `required`, `additionalProperties: false`, `items`, `enum`,
|
|
16
|
+
* `minimum`, `maximum`, `title`, `description`. `toRecordSchema` converts the
|
|
17
|
+
* `z.toJSONSchema` output into exactly that subset and rejects every other
|
|
18
|
+
* keyword with a concrete replacement, so a declaration that cannot be stored
|
|
19
|
+
* fails when it is compiled (an App can compile its declarations at build
|
|
20
|
+
* time) instead of on the first request.
|
|
21
|
+
*/
|
|
22
|
+
const zod_1 = require("zod");
|
|
23
|
+
const define_js_1 = require("./define.js");
|
|
24
|
+
class CollectionSchemaError extends Error {
|
|
25
|
+
alias;
|
|
26
|
+
issues;
|
|
27
|
+
constructor(alias, issues) {
|
|
28
|
+
super([
|
|
29
|
+
`Collection "${alias}" cannot be stored in UNS:`,
|
|
30
|
+
...issues.map((issue) => ` - ${issue.path || '(root)'}: ${issue.message}`),
|
|
31
|
+
].join('\n'));
|
|
32
|
+
this.name = 'CollectionSchemaError';
|
|
33
|
+
this.alias = alias;
|
|
34
|
+
this.issues = issues;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.CollectionSchemaError = CollectionSchemaError;
|
|
38
|
+
/** UNS limits (unsmeta Schema.validate and collection indexes); title/description are UTF-8 bytes. */
|
|
39
|
+
exports.SCHEMA_LIMITS = {
|
|
40
|
+
depth: 8,
|
|
41
|
+
properties: 128,
|
|
42
|
+
enumValues: 100,
|
|
43
|
+
title: 512,
|
|
44
|
+
description: 2048,
|
|
45
|
+
indexes: 16,
|
|
46
|
+
indexPaths: 3,
|
|
47
|
+
};
|
|
48
|
+
const SAFE_INTEGER_BOUND = Number.MAX_SAFE_INTEGER;
|
|
49
|
+
const SCHEMA_TYPES = new Set(['object', 'array', 'string', 'number', 'integer', 'boolean']);
|
|
50
|
+
const KEPT_KEYWORDS = new Set([
|
|
51
|
+
'type',
|
|
52
|
+
'title',
|
|
53
|
+
'description',
|
|
54
|
+
'properties',
|
|
55
|
+
'required',
|
|
56
|
+
'additionalProperties',
|
|
57
|
+
'items',
|
|
58
|
+
'enum',
|
|
59
|
+
'minimum',
|
|
60
|
+
'maximum',
|
|
61
|
+
]);
|
|
62
|
+
const RESERVED_FIELDS = new Set(['_id', '_meta']);
|
|
63
|
+
const NULLABLE_HINT = '`.nullable()`, `.nullish()` and `z.union()` are not storable. Make the field `.optional()` and leave it out instead of storing null; model alternatives as one type or as separate optional fields.';
|
|
64
|
+
const API_INPUT_HINT = 'Keep the collection schema plain and enforce this rule in the API input schema.';
|
|
65
|
+
const OPEN_OBJECT_HINT = '`z.record()`, `z.looseObject()` and `.catchall()` are not storable: every stored object is closed. Declare fixed properties, or store an array of `{ key, value }` objects.';
|
|
66
|
+
const KEYWORD_HINTS = {
|
|
67
|
+
anyOf: NULLABLE_HINT,
|
|
68
|
+
oneOf: NULLABLE_HINT,
|
|
69
|
+
allOf: '`z.intersection()` / `.and()` is not storable. Merge the objects with `.extend()`.',
|
|
70
|
+
not: 'Negated schemas are not storable.',
|
|
71
|
+
default: '`.default()` / `.prefault()` / `.catch()` is not storable. Apply defaults in the service when building the document.',
|
|
72
|
+
format: `String formats (\`z.email()\`, \`z.uuid()\`, \`z.iso.datetime()\`...) are not storable. Use \`z.string()\`: store instants as ISO-8601 UTC strings from \`toISOString()\` and dates as "YYYY-MM-DD". ${API_INPUT_HINT}`,
|
|
73
|
+
pattern: `\`.regex()\`, \`.startsWith()\` and similar patterns are not storable. Use \`z.string()\`. ${API_INPUT_HINT}`,
|
|
74
|
+
minLength: `String length limits are not storable. Use \`z.string()\`. ${API_INPUT_HINT}`,
|
|
75
|
+
maxLength: `String length limits are not storable. Use \`z.string()\`. ${API_INPUT_HINT}`,
|
|
76
|
+
contentEncoding: 'Encoded string content is not storable. Use `z.string()`.',
|
|
77
|
+
const: '`z.literal()` is not storable. Use `z.enum([...])`.',
|
|
78
|
+
exclusiveMinimum: '`.gt()` / `.positive()` (exclusive bounds) are not storable. Use inclusive `.min()` (`.min(1)` for a positive integer).',
|
|
79
|
+
exclusiveMaximum: '`.lt()` / `.negative()` are not storable. Use inclusive `.max()`.',
|
|
80
|
+
multipleOf: `\`.multipleOf()\` / \`.step()\` is not storable. ${API_INPUT_HINT}`,
|
|
81
|
+
minItems: `Array length limits are not storable. ${API_INPUT_HINT}`,
|
|
82
|
+
maxItems: `Array length limits are not storable. ${API_INPUT_HINT}`,
|
|
83
|
+
uniqueItems: `Array uniqueness is not storable. ${API_INPUT_HINT}`,
|
|
84
|
+
prefixItems: '`z.tuple()` is not storable. Use an object with named fields.',
|
|
85
|
+
propertyNames: OPEN_OBJECT_HINT,
|
|
86
|
+
patternProperties: OPEN_OBJECT_HINT,
|
|
87
|
+
minProperties: OPEN_OBJECT_HINT,
|
|
88
|
+
maxProperties: OPEN_OBJECT_HINT,
|
|
89
|
+
$ref: 'Recursive or shared `$ref` schemas are not storable. Inline the structure or keep child records in their own collection.',
|
|
90
|
+
$defs: 'Recursive or shared `$ref` schemas are not storable. Inline the structure or keep child records in their own collection.',
|
|
91
|
+
readOnly: '`.readonly()` is not storable; remove it from the collection schema.',
|
|
92
|
+
};
|
|
93
|
+
const encoder = new TextEncoder();
|
|
94
|
+
function utf8Bytes(value) {
|
|
95
|
+
return encoder.encode(value).length;
|
|
96
|
+
}
|
|
97
|
+
function isPlainObject(value) {
|
|
98
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
99
|
+
}
|
|
100
|
+
function joinPath(base, key) {
|
|
101
|
+
if (typeof key === 'number')
|
|
102
|
+
return `${base}[${key}]`;
|
|
103
|
+
return base ? `${base}.${key}` : key;
|
|
104
|
+
}
|
|
105
|
+
function validFieldName(key) {
|
|
106
|
+
if (RESERVED_FIELDS.has(key))
|
|
107
|
+
return `"${key}" is a system field and cannot be declared.`;
|
|
108
|
+
if (key.length === 0 || key === '[]')
|
|
109
|
+
return `Field name "${key}" is reserved.`;
|
|
110
|
+
if (/[.$\\]/.test(key))
|
|
111
|
+
return `Field name "${key}" must not contain ".", "$" or "\\".`;
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
function inferEnumType(values) {
|
|
115
|
+
if (values.length === 0)
|
|
116
|
+
return null;
|
|
117
|
+
if (values.every((value) => typeof value === 'string'))
|
|
118
|
+
return 'string';
|
|
119
|
+
if (values.every((value) => typeof value === 'boolean'))
|
|
120
|
+
return 'boolean';
|
|
121
|
+
if (values.every((value) => typeof value === 'number')) {
|
|
122
|
+
return values.every((value) => Number.isInteger(value)) ? 'integer' : 'number';
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
function normalizeNode(node, path, depth, issues) {
|
|
127
|
+
if (!isPlainObject(node)) {
|
|
128
|
+
issues.push({ path, message: 'Expected a schema object.' });
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
if (depth > exports.SCHEMA_LIMITS.depth) {
|
|
132
|
+
issues.push({ path, message: `Nesting deeper than ${exports.SCHEMA_LIMITS.depth} levels is not storable. Flatten the structure.` });
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
let valid = true;
|
|
136
|
+
for (const key of Object.keys(node)) {
|
|
137
|
+
if (KEPT_KEYWORDS.has(key) || (depth === 0 && key === '$schema'))
|
|
138
|
+
continue;
|
|
139
|
+
valid = false;
|
|
140
|
+
issues.push({
|
|
141
|
+
path,
|
|
142
|
+
message: `\`${key}\` is not supported. ${KEYWORD_HINTS[key] ?? 'Only type, properties, items, enum, minimum, maximum, title and description are storable; remove this constraint from the collection schema.'}`,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
const rawEnum = Array.isArray(node.enum) ? node.enum : undefined;
|
|
146
|
+
let type = node.type;
|
|
147
|
+
if (type === undefined && rawEnum)
|
|
148
|
+
type = inferEnumType(rawEnum) ?? undefined;
|
|
149
|
+
if (Array.isArray(type)) {
|
|
150
|
+
issues.push({ path, message: `A field must have one type, got [${type.join(', ')}]. ${NULLABLE_HINT}` });
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
if (type === 'null') {
|
|
154
|
+
issues.push({ path, message: '`z.null()` is not storable. Make the field optional and leave it out.' });
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
if (typeof type !== 'string' || !SCHEMA_TYPES.has(type)) {
|
|
158
|
+
if (type === undefined && !('anyOf' in node) && !('oneOf' in node) && !('allOf' in node)) {
|
|
159
|
+
issues.push({
|
|
160
|
+
path,
|
|
161
|
+
message: 'Every field needs a concrete type; `z.any()`, `z.unknown()` and `z.json()` are not storable. Declare the actual shape.',
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const out = { type: type };
|
|
167
|
+
for (const key of ['title', 'description']) {
|
|
168
|
+
const value = node[key];
|
|
169
|
+
if (value === undefined)
|
|
170
|
+
continue;
|
|
171
|
+
const limit = key === 'title' ? exports.SCHEMA_LIMITS.title : exports.SCHEMA_LIMITS.description;
|
|
172
|
+
// UNS measures these with Go len(): UTF-8 bytes, so CJK text uses 3 per character.
|
|
173
|
+
if (typeof value !== 'string' || utf8Bytes(value) > limit) {
|
|
174
|
+
issues.push({ path, message: `\`${key}\` must be a string of at most ${limit} UTF-8 bytes.` });
|
|
175
|
+
valid = false;
|
|
176
|
+
}
|
|
177
|
+
else if (value) {
|
|
178
|
+
out[key] = value;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (type === 'object') {
|
|
182
|
+
if (node.additionalProperties !== undefined && node.additionalProperties !== false) {
|
|
183
|
+
issues.push({ path, message: OPEN_OBJECT_HINT });
|
|
184
|
+
valid = false;
|
|
185
|
+
}
|
|
186
|
+
const rawProperties = isPlainObject(node.properties) ? node.properties : {};
|
|
187
|
+
const names = Object.keys(rawProperties);
|
|
188
|
+
if (names.length > exports.SCHEMA_LIMITS.properties) {
|
|
189
|
+
issues.push({ path, message: `More than ${exports.SCHEMA_LIMITS.properties} properties is not storable. Split the object.` });
|
|
190
|
+
valid = false;
|
|
191
|
+
}
|
|
192
|
+
const properties = {};
|
|
193
|
+
for (const name of names) {
|
|
194
|
+
const fieldPath = joinPath(path, name);
|
|
195
|
+
const nameProblem = validFieldName(name);
|
|
196
|
+
if (nameProblem) {
|
|
197
|
+
issues.push({ path: fieldPath, message: nameProblem });
|
|
198
|
+
valid = false;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const child = normalizeNode(rawProperties[name], fieldPath, depth + 1, issues);
|
|
202
|
+
if (child)
|
|
203
|
+
properties[name] = child;
|
|
204
|
+
else
|
|
205
|
+
valid = false;
|
|
206
|
+
}
|
|
207
|
+
out.properties = properties;
|
|
208
|
+
const required = Array.isArray(node.required)
|
|
209
|
+
? [...new Set(node.required.filter((name) => typeof name === 'string' && name in rawProperties))].sort()
|
|
210
|
+
: [];
|
|
211
|
+
if (required.length > 0)
|
|
212
|
+
out.required = required;
|
|
213
|
+
out.additionalProperties = false;
|
|
214
|
+
}
|
|
215
|
+
else if (node.properties !== undefined || node.required !== undefined || node.additionalProperties !== undefined) {
|
|
216
|
+
issues.push({ path, message: 'Only objects may declare properties.' });
|
|
217
|
+
valid = false;
|
|
218
|
+
}
|
|
219
|
+
if (type === 'array') {
|
|
220
|
+
const items = node.items;
|
|
221
|
+
if (!isPlainObject(items)) {
|
|
222
|
+
issues.push({ path, message: 'Arrays need one item schema (`z.array(item)`).' });
|
|
223
|
+
valid = false;
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
const child = normalizeNode(items, `${path}[]`, depth + 1, issues);
|
|
227
|
+
if (child)
|
|
228
|
+
out.items = child;
|
|
229
|
+
else
|
|
230
|
+
valid = false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (rawEnum) {
|
|
234
|
+
const values = [];
|
|
235
|
+
for (const value of rawEnum) {
|
|
236
|
+
const scalar = typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
|
|
237
|
+
const fits = scalar &&
|
|
238
|
+
(type === 'string'
|
|
239
|
+
? typeof value === 'string'
|
|
240
|
+
: type === 'boolean'
|
|
241
|
+
? typeof value === 'boolean'
|
|
242
|
+
: type === 'integer'
|
|
243
|
+
? Number.isInteger(value)
|
|
244
|
+
: type === 'number'
|
|
245
|
+
? typeof value === 'number'
|
|
246
|
+
: false);
|
|
247
|
+
if (!fits) {
|
|
248
|
+
issues.push({ path, message: `Enum value ${JSON.stringify(value)} does not match type ${type}.` });
|
|
249
|
+
valid = false;
|
|
250
|
+
}
|
|
251
|
+
else if (!values.some((existing) => existing === value)) {
|
|
252
|
+
values.push(value);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (values.length > exports.SCHEMA_LIMITS.enumValues) {
|
|
256
|
+
issues.push({ path, message: `More than ${exports.SCHEMA_LIMITS.enumValues} enum values is not storable. Use a reference collection.` });
|
|
257
|
+
valid = false;
|
|
258
|
+
}
|
|
259
|
+
if (values.length > 0)
|
|
260
|
+
out.enum = values;
|
|
261
|
+
}
|
|
262
|
+
for (const key of ['minimum', 'maximum']) {
|
|
263
|
+
const value = node[key];
|
|
264
|
+
if (value === undefined)
|
|
265
|
+
continue;
|
|
266
|
+
if (type !== 'number' && type !== 'integer') {
|
|
267
|
+
issues.push({ path, message: `\`${key}\` is only valid on numbers.` });
|
|
268
|
+
valid = false;
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
272
|
+
issues.push({ path, message: `\`${key}\` must be a finite number.` });
|
|
273
|
+
valid = false;
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
// z.number().int() always emits the safe-integer range; it is not a business bound.
|
|
277
|
+
if (type === 'integer' && Math.abs(value) === SAFE_INTEGER_BOUND)
|
|
278
|
+
continue;
|
|
279
|
+
out[key] = value;
|
|
280
|
+
}
|
|
281
|
+
if (out.minimum !== undefined && out.maximum !== undefined && out.minimum > out.maximum) {
|
|
282
|
+
issues.push({ path, message: '`minimum` is greater than `maximum`.' });
|
|
283
|
+
valid = false;
|
|
284
|
+
}
|
|
285
|
+
return valid ? out : null;
|
|
286
|
+
}
|
|
287
|
+
/** Raw `z.toJSONSchema` output; exported for the snapshot tests. */
|
|
288
|
+
function zodJsonSchema(schema) {
|
|
289
|
+
return zod_1.z.toJSONSchema(schema, { io: 'output', unrepresentable: 'throw', cycles: 'throw' });
|
|
290
|
+
}
|
|
291
|
+
/** Converts a Zod object schema to the UNS record-schema subset or throws `CollectionSchemaError`. */
|
|
292
|
+
function toRecordSchema(alias, schema) {
|
|
293
|
+
let raw;
|
|
294
|
+
try {
|
|
295
|
+
raw = zodJsonSchema(schema);
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
throw new CollectionSchemaError(alias, [
|
|
299
|
+
{
|
|
300
|
+
path: '',
|
|
301
|
+
message: `${error instanceof Error ? error.message : String(error)}. \`z.date()\`, \`z.bigint()\`, \`.transform()\`, \`z.map()\`, \`z.set()\`, \`z.custom()\` and recursive schemas are not storable; store instants as ISO-8601 UTC strings from \`toISOString()\` and dates as "YYYY-MM-DD" strings, and keep transforms in the API input schema.`,
|
|
302
|
+
},
|
|
303
|
+
]);
|
|
304
|
+
}
|
|
305
|
+
const issues = [];
|
|
306
|
+
const normalized = normalizeNode(raw, '', 0, issues);
|
|
307
|
+
if (normalized && normalized.type !== 'object') {
|
|
308
|
+
issues.push({ path: '', message: 'A collection schema must be `z.object({...})`.' });
|
|
309
|
+
}
|
|
310
|
+
if (issues.length > 0 || !normalized)
|
|
311
|
+
throw new CollectionSchemaError(alias, issues);
|
|
312
|
+
return normalized;
|
|
313
|
+
}
|
|
314
|
+
/** Resolves a dotted business path in a record schema; array levels accept index segments. */
|
|
315
|
+
function recordSchemaAt(schema, path) {
|
|
316
|
+
let current = schema;
|
|
317
|
+
for (const segment of path.split('.')) {
|
|
318
|
+
if (!current)
|
|
319
|
+
return undefined;
|
|
320
|
+
if (current.type === 'array' && isArraySegment(segment)) {
|
|
321
|
+
current = current.items;
|
|
322
|
+
}
|
|
323
|
+
else if (current.type === 'object') {
|
|
324
|
+
current = current.properties?.[segment];
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
return undefined;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return current;
|
|
331
|
+
}
|
|
332
|
+
function isArraySegment(segment) {
|
|
333
|
+
return /^(?:\d+|\$|\$\[[A-Za-z0-9_]*\])$/.test(segment);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Index paths follow object properties only: a path that continues past an
|
|
337
|
+
* array (`items.sku`) would index values inside array items, which UNS
|
|
338
|
+
* expression indexes cannot do.
|
|
339
|
+
*/
|
|
340
|
+
function indexPathProblem(alias, schema, path, kind) {
|
|
341
|
+
const segments = path.split('.');
|
|
342
|
+
let current = schema;
|
|
343
|
+
for (const [position, segment] of segments.entries()) {
|
|
344
|
+
if (current?.type === 'array') {
|
|
345
|
+
const arrayPath = segments.slice(0, position).join('.');
|
|
346
|
+
return `"${path}" reaches into the items of array "${arrayPath}"; index the array itself with kind: "gin", or copy the value to a top-level field.`;
|
|
347
|
+
}
|
|
348
|
+
current = current?.type === 'object' ? current.properties?.[segment] : undefined;
|
|
349
|
+
if (!current)
|
|
350
|
+
return `"${path}" is not a declared field of "${alias}".`;
|
|
351
|
+
}
|
|
352
|
+
const target = current;
|
|
353
|
+
if (kind === 'btree' && (target.type === 'object' || target.type === 'array')) {
|
|
354
|
+
return `"${path}" is an ${target.type}; index a scalar field or use kind: "gin".`;
|
|
355
|
+
}
|
|
356
|
+
if (kind === 'gin' && target.type !== 'object' && target.type !== 'array') {
|
|
357
|
+
return `"${path}" is a ${target.type}; a GIN index covers an object or array field.`;
|
|
358
|
+
}
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
function normalizeIndexes(alias, indexes, schema, issues) {
|
|
362
|
+
const out = [];
|
|
363
|
+
const seen = new Set();
|
|
364
|
+
if (indexes.length > exports.SCHEMA_LIMITS.indexes) {
|
|
365
|
+
issues.push({ path: 'indexes', message: `At most ${exports.SCHEMA_LIMITS.indexes} indexes per collection.` });
|
|
366
|
+
}
|
|
367
|
+
indexes.forEach((index, position) => {
|
|
368
|
+
const at = `indexes[${position}]`;
|
|
369
|
+
const paths = index.paths ?? (index.path !== undefined ? [index.path] : []);
|
|
370
|
+
if ((index.path !== undefined) === (index.paths !== undefined)) {
|
|
371
|
+
issues.push({ path: at, message: 'Set exactly one of `path` or `paths`.' });
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (paths.length === 0 || paths.length > exports.SCHEMA_LIMITS.indexPaths) {
|
|
375
|
+
issues.push({ path: at, message: `A compound index has 1–${exports.SCHEMA_LIMITS.indexPaths} paths.` });
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const kind = index.kind ?? 'btree';
|
|
379
|
+
if (kind !== 'btree' && kind !== 'gin') {
|
|
380
|
+
issues.push({ path: at, message: '`kind` is "btree" or "gin".' });
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
if (kind === 'gin' && (paths.length !== 1 || index.unique)) {
|
|
384
|
+
issues.push({ path: at, message: 'A GIN index covers one object/array path and cannot be unique.' });
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (new Set(paths).size !== paths.length) {
|
|
388
|
+
issues.push({ path: at, message: 'A compound index lists each path once.' });
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
for (const fieldPath of paths) {
|
|
392
|
+
const problem = indexPathProblem(alias, schema, fieldPath, kind);
|
|
393
|
+
if (problem) {
|
|
394
|
+
issues.push({ path: at, message: problem });
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
const normalized = index.paths ? { paths: [...paths] } : { path: paths[0] };
|
|
399
|
+
if (index.unique)
|
|
400
|
+
normalized.unique = true;
|
|
401
|
+
if (kind === 'gin')
|
|
402
|
+
normalized.kind = 'gin';
|
|
403
|
+
const key = JSON.stringify(normalized);
|
|
404
|
+
if (!seen.has(key)) {
|
|
405
|
+
seen.add(key);
|
|
406
|
+
out.push(normalized);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
return out;
|
|
410
|
+
}
|
|
411
|
+
const compiled = new WeakMap();
|
|
412
|
+
/** Ensure payload for one declaration; memoized per definition object. */
|
|
413
|
+
function compileCollection(definition) {
|
|
414
|
+
const cached = compiled.get(definition);
|
|
415
|
+
if (cached)
|
|
416
|
+
return cached;
|
|
417
|
+
if (!define_js_1.ALIAS_PATTERN.test(definition.alias)) {
|
|
418
|
+
throw new CollectionSchemaError(definition.alias, [{ path: 'alias', message: `Alias must match ${define_js_1.ALIAS_PATTERN}.` }]);
|
|
419
|
+
}
|
|
420
|
+
const recordSchema = toRecordSchema(definition.alias, definition.schema);
|
|
421
|
+
const issues = [];
|
|
422
|
+
const indexes = normalizeIndexes(definition.alias, definition.indexes, recordSchema, issues);
|
|
423
|
+
if (issues.length > 0)
|
|
424
|
+
throw new CollectionSchemaError(definition.alias, issues);
|
|
425
|
+
const result = {
|
|
426
|
+
alias: definition.alias,
|
|
427
|
+
recordSchema,
|
|
428
|
+
indexes,
|
|
429
|
+
...(definition.description ? { description: definition.description } : {}),
|
|
430
|
+
};
|
|
431
|
+
compiled.set(definition, result);
|
|
432
|
+
return result;
|
|
433
|
+
}
|
|
434
|
+
function unknownKeyIssues(schema, value, path, out) {
|
|
435
|
+
if (!schema)
|
|
436
|
+
return;
|
|
437
|
+
if (schema.type === 'object' && isPlainObject(value)) {
|
|
438
|
+
const properties = schema.properties ?? {};
|
|
439
|
+
const unknown = Object.keys(value).filter((key) => !(key in properties));
|
|
440
|
+
if (unknown.length > 0) {
|
|
441
|
+
out.push({
|
|
442
|
+
code: 'unrecognized_keys',
|
|
443
|
+
keys: unknown,
|
|
444
|
+
path,
|
|
445
|
+
message: `Unknown field${unknown.length > 1 ? 's' : ''} ${unknown.map((key) => `"${key}"`).join(', ')}: declare ${unknown.length > 1 ? 'them' : 'it'} in the collection schema first.`,
|
|
446
|
+
input: value,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
for (const [key, child] of Object.entries(value)) {
|
|
450
|
+
if (key in properties)
|
|
451
|
+
unknownKeyIssues(properties[key], child, [...path, key], out);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
else if (schema.type === 'array' && Array.isArray(value)) {
|
|
455
|
+
value.forEach((item, index) => unknownKeyIssues(schema.items, item, [...path, index], out));
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
function strictParse(zodSchema, recordSchema, value, path) {
|
|
459
|
+
const issues = [];
|
|
460
|
+
unknownKeyIssues(recordSchema, value, path, issues);
|
|
461
|
+
const result = zodSchema.safeParse(value);
|
|
462
|
+
if (!result.success) {
|
|
463
|
+
for (const issue of result.error.issues)
|
|
464
|
+
issues.push({ ...issue, path: [...path, ...issue.path] });
|
|
465
|
+
}
|
|
466
|
+
if (issues.length > 0)
|
|
467
|
+
throw new zod_1.z.ZodError(issues);
|
|
468
|
+
return result.data;
|
|
469
|
+
}
|
|
470
|
+
function zodAt(schema, path) {
|
|
471
|
+
let current = schema;
|
|
472
|
+
for (const segment of path.split('.')) {
|
|
473
|
+
while (current instanceof zod_1.z.ZodOptional)
|
|
474
|
+
current = current.unwrap();
|
|
475
|
+
if (current instanceof zod_1.z.ZodObject) {
|
|
476
|
+
current = current.shape[segment];
|
|
477
|
+
}
|
|
478
|
+
else if (current instanceof zod_1.z.ZodArray && isArraySegment(segment)) {
|
|
479
|
+
current = current.element;
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
return undefined;
|
|
483
|
+
}
|
|
484
|
+
if (!current)
|
|
485
|
+
return undefined;
|
|
486
|
+
}
|
|
487
|
+
return current;
|
|
488
|
+
}
|
|
489
|
+
function validationError(path, message, input) {
|
|
490
|
+
return new zod_1.z.ZodError([{ code: 'custom', path, message, input }]);
|
|
491
|
+
}
|
|
492
|
+
/** Splits `_id` / `_meta` off a document and strictly validates the body. */
|
|
493
|
+
function validateDocument(definition, document) {
|
|
494
|
+
if (!isPlainObject(document))
|
|
495
|
+
throw validationError([], 'Expected a document object.', document);
|
|
496
|
+
const { _id, _meta, ...body } = document;
|
|
497
|
+
void _meta;
|
|
498
|
+
if (_id !== undefined && (typeof _id !== 'string' || _id.length === 0 || _id.length > 128)) {
|
|
499
|
+
throw validationError(['_id'], '`_id` must be a string of 1–128 characters.', _id);
|
|
500
|
+
}
|
|
501
|
+
const { recordSchema } = compileCollection(definition);
|
|
502
|
+
const data = strictParse(definition.schema, recordSchema, body, []);
|
|
503
|
+
return { ...(typeof _id === 'string' ? { id: _id } : {}), data };
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Validates `data` of a transaction step. Fields filled by `bindings`
|
|
507
|
+
* (`data.<field>...`) are checked only for being declared; every literal
|
|
508
|
+
* field is strictly validated, and other required fields must be present.
|
|
509
|
+
*/
|
|
510
|
+
function validateStepData(definition, data, boundFields) {
|
|
511
|
+
if (!isPlainObject(data))
|
|
512
|
+
throw validationError(['data'], 'Expected a document object.', data);
|
|
513
|
+
if ('_id' in data || '_meta' in data) {
|
|
514
|
+
throw validationError(['data', '_id' in data ? '_id' : '_meta'], 'A transaction step sets the record ID with `id`, not `data._id`; `_meta` is read-only.', data);
|
|
515
|
+
}
|
|
516
|
+
const { recordSchema } = compileCollection(definition);
|
|
517
|
+
const properties = recordSchema.properties ?? {};
|
|
518
|
+
const bound = new Set(boundFields.filter((field) => field in properties));
|
|
519
|
+
const unknownBound = boundFields.filter((field) => !(field in properties));
|
|
520
|
+
if (unknownBound.length > 0) {
|
|
521
|
+
throw validationError(['bindings'], `Bound field${unknownBound.length > 1 ? 's' : ''} ${unknownBound.join(', ')} not declared in "${definition.alias}".`, boundFields);
|
|
522
|
+
}
|
|
523
|
+
if (bound.size === 0)
|
|
524
|
+
return strictParse(definition.schema, recordSchema, data, ['data']);
|
|
525
|
+
const literal = Object.fromEntries(Object.entries(data).filter(([key]) => !bound.has(key)));
|
|
526
|
+
const omit = Object.fromEntries([...bound].map((field) => [field, true]));
|
|
527
|
+
const literalSchema = {
|
|
528
|
+
...recordSchema,
|
|
529
|
+
properties: Object.fromEntries(Object.entries(properties).filter(([key]) => !bound.has(key))),
|
|
530
|
+
required: recordSchema.required?.filter((key) => !bound.has(key)),
|
|
531
|
+
};
|
|
532
|
+
const parsed = strictParse(definition.schema.omit(omit), literalSchema, literal, ['data']);
|
|
533
|
+
const boundValues = Object.fromEntries(Object.entries(data).filter(([key]) => bound.has(key)));
|
|
534
|
+
return { ...parsed, ...boundValues };
|
|
535
|
+
}
|
|
536
|
+
const VALUE_OPERATORS = new Set(['$set', '$setOnInsert']);
|
|
537
|
+
const ELEMENT_OPERATORS = new Set(['$push', '$addToSet']);
|
|
538
|
+
const NUMERIC_OPERATORS = new Set(['$inc', '$mul']);
|
|
539
|
+
/** Validates update-operator documents against the declaration (paths and values). */
|
|
540
|
+
function validateUpdate(definition, update) {
|
|
541
|
+
if (!isPlainObject(update) || Object.keys(update).length === 0) {
|
|
542
|
+
throw validationError([], 'An update needs at least one operator such as `$set`.', update);
|
|
543
|
+
}
|
|
544
|
+
const { recordSchema } = compileCollection(definition);
|
|
545
|
+
const out = {};
|
|
546
|
+
for (const [operator, fields] of Object.entries(update)) {
|
|
547
|
+
if (!operator.startsWith('$')) {
|
|
548
|
+
throw validationError([operator], "Updates use operators (`{ $set: { status: \"DONE\" } }`); use replaceOne() to replace a whole document.", update);
|
|
549
|
+
}
|
|
550
|
+
if (!isPlainObject(fields))
|
|
551
|
+
throw validationError([operator], `\`${operator}\` takes an object of field paths.`, fields);
|
|
552
|
+
const next = {};
|
|
553
|
+
for (const [path, value] of Object.entries(fields)) {
|
|
554
|
+
const issuePath = [operator, path];
|
|
555
|
+
if (path === '_id' || path === '_meta' || path.startsWith('_meta.')) {
|
|
556
|
+
throw validationError(issuePath, '`_id` and `_meta` are read-only.', value);
|
|
557
|
+
}
|
|
558
|
+
const target = recordSchemaAt(recordSchema, path);
|
|
559
|
+
const zodTarget = zodAt(definition.schema, path);
|
|
560
|
+
if (!target || !zodTarget) {
|
|
561
|
+
throw validationError(issuePath, `"${path}" is not a declared field of "${definition.alias}".`, value);
|
|
562
|
+
}
|
|
563
|
+
if (VALUE_OPERATORS.has(operator)) {
|
|
564
|
+
if (value === undefined)
|
|
565
|
+
continue;
|
|
566
|
+
next[path] = strictParse(zodTarget, target, value, issuePath);
|
|
567
|
+
}
|
|
568
|
+
else if (ELEMENT_OPERATORS.has(operator)) {
|
|
569
|
+
const element = zodTarget instanceof zod_1.z.ZodOptional ? zodTarget.unwrap() : zodTarget;
|
|
570
|
+
if (target.type !== 'array' || !(element instanceof zod_1.z.ZodArray)) {
|
|
571
|
+
throw validationError(issuePath, `\`${operator}\` needs an array field; "${path}" is ${target.type}.`, value);
|
|
572
|
+
}
|
|
573
|
+
const each = isPlainObject(value) && Array.isArray(value.$each);
|
|
574
|
+
const items = each ? (value.$each) : [value];
|
|
575
|
+
const parsed = items.map((item, index) => strictParse(element.element, target.items, item, each ? [...issuePath, '$each', index] : issuePath));
|
|
576
|
+
next[path] = each ? { ...value, $each: parsed } : parsed[0];
|
|
577
|
+
}
|
|
578
|
+
else if (NUMERIC_OPERATORS.has(operator)) {
|
|
579
|
+
if (target.type !== 'number' && target.type !== 'integer') {
|
|
580
|
+
throw validationError(issuePath, `\`${operator}\` needs a numeric field; "${path}" is ${target.type}.`, value);
|
|
581
|
+
}
|
|
582
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
583
|
+
throw validationError(issuePath, `\`${operator}\` needs a finite number.`, value);
|
|
584
|
+
}
|
|
585
|
+
next[path] = value;
|
|
586
|
+
}
|
|
587
|
+
else if (operator === '$rename') {
|
|
588
|
+
if (typeof value !== 'string' || !recordSchemaAt(recordSchema, value)) {
|
|
589
|
+
throw validationError(issuePath, `\`$rename\` target "${String(value)}" is not a declared field.`, value);
|
|
590
|
+
}
|
|
591
|
+
next[path] = value;
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
// $unset, $min, $max, $currentDate, $pop, $pull, $pullAll: the path is
|
|
595
|
+
// declared; UNS validates the resulting document.
|
|
596
|
+
next[path] = value;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
if (Object.keys(next).length > 0)
|
|
600
|
+
out[operator] = next;
|
|
601
|
+
}
|
|
602
|
+
if (Object.keys(out).length === 0) {
|
|
603
|
+
throw validationError([], 'Nothing to update: undefined values are dropped; clear a field with `$unset`.', update);
|
|
604
|
+
}
|
|
605
|
+
return out;
|
|
606
|
+
}
|
|
607
|
+
//# sourceMappingURL=schema.js.map
|