@tier0/sdk-v2 0.9.3 → 0.9.5
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/README.md +5 -5
- package/dist/cjs/data/bootstrap.js +4 -0
- package/dist/cjs/data/bootstrap.js.map +1 -1
- package/dist/cjs/data/client.d.ts +9 -4
- package/dist/cjs/data/client.js +26 -13
- package/dist/cjs/data/client.js.map +1 -1
- package/dist/cjs/data/collection.d.ts +8 -7
- package/dist/cjs/data/collection.js +4 -3
- package/dist/cjs/data/collection.js.map +1 -1
- package/dist/cjs/data/declarations.d.ts +1 -1
- package/dist/cjs/data/declarations.js.map +1 -1
- package/dist/cjs/data/define.d.ts +7 -0
- package/dist/cjs/data/define.js +4 -0
- package/dist/cjs/data/define.js.map +1 -1
- package/dist/cjs/data/schema.d.ts +4 -4
- package/dist/cjs/data/schema.js +26 -5
- package/dist/cjs/data/schema.js.map +1 -1
- package/dist/cjs/data/subscribe.d.ts +9 -8
- package/dist/cjs/data/subscribe.js +5 -11
- package/dist/cjs/data/subscribe.js.map +1 -1
- package/dist/esm/data/bootstrap.js +4 -0
- package/dist/esm/data/bootstrap.js.map +1 -1
- package/dist/esm/data/client.d.ts +9 -4
- package/dist/esm/data/client.js +26 -13
- package/dist/esm/data/client.js.map +1 -1
- package/dist/esm/data/collection.d.ts +8 -7
- package/dist/esm/data/collection.js +4 -3
- package/dist/esm/data/collection.js.map +1 -1
- package/dist/esm/data/declarations.d.ts +1 -1
- package/dist/esm/data/declarations.js.map +1 -1
- package/dist/esm/data/define.d.ts +7 -0
- package/dist/esm/data/define.js +3 -0
- package/dist/esm/data/define.js.map +1 -1
- package/dist/esm/data/schema.d.ts +4 -4
- package/dist/esm/data/schema.js +27 -6
- package/dist/esm/data/schema.js.map +1 -1
- package/dist/esm/data/subscribe.d.ts +9 -8
- package/dist/esm/data/subscribe.js +5 -11
- package/dist/esm/data/subscribe.js.map +1 -1
- package/package.json +1 -1
- package/skills/tier0-sdk-v2/tier0-sdk-v2-data/SKILL.md +5 -3
- package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/modeling.md +16 -0
- package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/SKILL.md +3 -1
- package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/create.md +5 -2
- package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/namespace.md +8 -3
package/README.md
CHANGED
|
@@ -20,13 +20,13 @@ Collection 声明必须同时提供业务名称 `name`(例如「生产工单
|
|
|
20
20
|
|
|
21
21
|
## 安装与初始化
|
|
22
22
|
|
|
23
|
-
维护者发布前在本仓库运行 `npm ci`、`npm run check`,并验证生成的 `.tgz`。本分支的功能应使用检查通过的 `tier0-sdk-v2-0.9.
|
|
23
|
+
维护者发布前在本仓库运行 `npm ci`、`npm run check`,并验证生成的 `.tgz`。本分支的功能应使用检查通过的 `tier0-sdk-v2-0.9.5.tgz` 在业务项目中验证:
|
|
24
24
|
|
|
25
25
|
```sh
|
|
26
|
-
npm install /path/to/tier0-sdk-v2-0.9.
|
|
26
|
+
npm install /path/to/tier0-sdk-v2-0.9.5.tgz
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
`0.9.
|
|
29
|
+
`0.9.5` 正式发布后再使用 `npm install @tier0/sdk-v2@0.9.5`。发布前,npm 的既有版本和 `next` 标签不代表本分支的完整功能。
|
|
30
30
|
|
|
31
31
|
```ts
|
|
32
32
|
import { createClient } from '@tier0/sdk-v2';
|
|
@@ -65,7 +65,7 @@ const user = await tier0.system.whoami();
|
|
|
65
65
|
| `@tier0/sdk-v2/react` | `useTier0Query()`、`useTier0Mutation()`,需要 React 和 TanStack Query |
|
|
66
66
|
| `@tier0/sdk-v2/vue` | `useTier0Request()`,需要 Vue 3 |
|
|
67
67
|
| `@tier0/sdk-v2/server` | `createCloudAppRequestVerifier()`,仅服务端加载 `node:crypto` |
|
|
68
|
-
| `@tier0/sdk-v2/data` | App 数据层:`defineCollection()` 声明集合(含 TTL)、`createDataClient()` 首次访问时 ensure
|
|
68
|
+
| `@tier0/sdk-v2/data` | App 数据层:`defineCollection()` 声明集合(含 TTL)、`createDataClient()` 首次访问时 ensure,按 collectionId 引用已有集合、Mongo 风格读写、带总数分页与封顶计数、原子批量与 `transact`、`subscribe` 变更订阅、员工数据主体透传;自动幂等键、并发上限与按 `retry.safe` 重试;仅服务端,需要 `zod` 4 |
|
|
69
69
|
| `@tier0/sdk-v2/data/define` | 集合声明与类型:`defineCollection()`、`Doc` / `NewDoc` 等、`compileCollection()`、`validateDocument()`;可进浏览器包,组件和 route 的客户端代码只从这里取声明和类型 |
|
|
70
70
|
|
|
71
71
|
集合声明必须分别提供稳定程序标识 `alias` 和业务名称 `name`,例如 `alias: 'production_orders'`、`name: '生产工单'`。目录、数据和引用直接用 name 展示,不提供 displayName 或名称回退;name 为 1–63 个 Unicode 字符,无首尾空白,不含 `/` 或空字符。ensure 按 alias 关联集合,仅首次创建设置 name,不覆盖用户改名。名称冲突时选择有区分度的业务名称,不拼随机后缀。
|
|
@@ -108,7 +108,7 @@ Source/Stream 仍保留 `{ code, msg, requestId, data }` 响应以及 `UnsV2Erro
|
|
|
108
108
|
|
|
109
109
|
## UNS 数据访问
|
|
110
110
|
|
|
111
|
-
创建本 App 集合使用 `defineCollection` / `appCollectionsEnsure`,`ownerAppId` 表示唯一所属 App。使用已有或其他 App
|
|
111
|
+
创建本 App 集合使用 `defineCollection` / `appCollectionsEnsure`,`ownerAppId` 表示唯一所属 App。使用已有或其他 App 共享的集合,可用 `data.collection({ collectionId })` 获取数据层句柄,也可向 `tier0.uns.recordFind`、`recordInsertOne` 等接口传入实际 `collectionId`;无需新增声明或配置服务端 alias,也不改变集合归属。引用已有集合跳过 ensure、schema 修改和种子写入;可选的 `schema` 仅用于本地写入校验。App Key 仍须已有覆盖该集合及操作的授权,知道 ID 不代表获得权限。
|
|
112
112
|
|
|
113
113
|
新建 Schema 的数值字段使用明确格式:整数为 `type: 'integer', format: 'int32'`,小数为 `type: 'number', format: 'double'`,明确需要单精度时指定 `format: 'float'`。App 数据层编译 Zod 声明时,会递归为未指定格式的 integer/number 补齐 int32/double,并保留显式格式;直接调用 UNS/MCP 时由调用方提供 format。这些是类型注解,不新增数值范围或舍入规则;ensure 不改写已有字段注解,已有 legacy 字段需通过 schema:replace 单独迁移。
|
|
114
114
|
|
|
@@ -242,6 +242,10 @@ function createBootstrap(config) {
|
|
|
242
242
|
* propagate.
|
|
243
243
|
*/
|
|
244
244
|
async function rebuild(before, cause) {
|
|
245
|
+
// Only owned IDs can be recovered by ensure; shared references must keep their original error.
|
|
246
|
+
const failedId = cause.details.collectionId;
|
|
247
|
+
if (typeof failedId === 'string' && ![...before.collectionIds.values()].includes(failedId))
|
|
248
|
+
return false;
|
|
245
249
|
const current = ensure();
|
|
246
250
|
const latest = await current;
|
|
247
251
|
if (latest !== before)
|
|
@@ -1 +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 failed chunk (outcome unknown, or marked safe to\n * resend) is replayed after a short wait instead of duplicated. Seed writes\n * 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 name, `.describe()` or collection description comes back as\n * ANNOTATION_NOT_APPLIED; it is logged as a warning pointing at\n * metadata:updateOne for names, or schema:replace for schema annotations.\n */\nimport { createHash } from 'node:crypto';\nimport type { DataCall } from './call.js';\nimport { assertNoWriteErrors, 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/** Wait before replaying seed chunk attempt n+1: n × this. */\nexport const SEED_RETRY_MS = 1_000;\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 business name belongs to another collection in this folder; pick another name and keep the stable 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 const result = await call<{ writeErrors?: Record<string, unknown>[] }>('recordBulkWrite', {\n collectionId,\n operations: documents.map(document => ({ insertOne: document })),\n ordered: true,\n atomic: true,\n idempotencyKey: seedChunkKey(collectionId, chunk),\n });\n assertNoWriteErrors(result, 'seed');\n return;\n } catch (error) {\n // The chunk key is stable, so replaying is safe even when the outcome is unknown: a chunk\n // that did commit replays its receipt. Wait first, so a write still running can finish.\n if (!(error instanceof DataError) || !(error.outcomeUnknown || error.retry.safe) || attempt >= SEED_ATTEMPTS) throw error;\n await new Promise((resolve) => setTimeout(resolve, SEED_RETRY_MS * attempt));\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 execution: error.execution,\n attempts: error.attempts,\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 names/titles/descriptions; not applied: ${annotations.map((note) => notePath(result.alias, note)).join(', ')}. ` +\n `Rename with metadata:updateOne via the tier0 MCP; apply schema annotations with schema:replace, selecting alias, recordSchema, indexes and description from compileCollection(...).`,\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 // ensure did apply; only the warnings fail the Preview request.\n execution: 'committed',\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, rebuild };\n}\n"]}
|
|
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 failed chunk (outcome unknown, or marked safe to\n * resend) is replayed after a short wait instead of duplicated. Seed writes\n * 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 name, `.describe()` or collection description comes back as\n * ANNOTATION_NOT_APPLIED; it is logged as a warning pointing at\n * metadata:updateOne for names, or schema:replace for schema annotations.\n */\nimport { createHash } from 'node:crypto';\nimport type { DataCall } from './call.js';\nimport { assertNoWriteErrors, 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/** Wait before replaying seed chunk attempt n+1: n × this. */\nexport const SEED_RETRY_MS = 1_000;\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 business name belongs to another collection in this folder; pick another name and keep the stable 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 const result = await call<{ writeErrors?: Record<string, unknown>[] }>('recordBulkWrite', {\n collectionId,\n operations: documents.map(document => ({ insertOne: document })),\n ordered: true,\n atomic: true,\n idempotencyKey: seedChunkKey(collectionId, chunk),\n });\n assertNoWriteErrors(result, 'seed');\n return;\n } catch (error) {\n // The chunk key is stable, so replaying is safe even when the outcome is unknown: a chunk\n // that did commit replays its receipt. Wait first, so a write still running can finish.\n if (!(error instanceof DataError) || !(error.outcomeUnknown || error.retry.safe) || attempt >= SEED_ATTEMPTS) throw error;\n await new Promise((resolve) => setTimeout(resolve, SEED_RETRY_MS * attempt));\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 execution: error.execution,\n attempts: error.attempts,\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 names/titles/descriptions; not applied: ${annotations.map((note) => notePath(result.alias, note)).join(', ')}. ` +\n `Rename with metadata:updateOne via the tier0 MCP; apply schema annotations with schema:replace, selecting alias, recordSchema, indexes and description from compileCollection(...).`,\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 // ensure did apply; only the warnings fail the Preview request.\n execution: 'committed',\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 // Only owned IDs can be recovered by ensure; shared references must keep their original error.\n const failedId = cause.details.collectionId;\n if (typeof failedId === 'string' && ![...before.collectionIds.values()].includes(failedId)) return false;\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, rebuild };\n}\n"]}
|
|
@@ -7,16 +7,16 @@ import type { z } from 'zod';
|
|
|
7
7
|
import type { ClientConfig } from '../openapi/client.js';
|
|
8
8
|
import { type DataEnvironment, type EnsureState, type Logger } from './bootstrap.js';
|
|
9
9
|
import { Collection, type BatchQuery, type WithMeta } from './collection.js';
|
|
10
|
-
import { type CollectionDefinition } from './define.js';
|
|
10
|
+
import { type CollectionDefinition, type CollectionReference, type CollectionSchema, type CollectionTarget } from './define.js';
|
|
11
11
|
import { type SubscribeOptions, type SubscriptionEvent } from './subscribe.js';
|
|
12
12
|
import { DataError } from './error.js';
|
|
13
13
|
import { type Filter, type Sort } from './sort.js';
|
|
14
14
|
export type TransactAction = 'record.create' | 'record.check' | 'record.read' | 'record.update' | 'record.modify' | 'record.replace' | 'record.delete' | 'record.query';
|
|
15
|
-
/** One `recordTransactionExecute` item; `collection` is a declaration
|
|
15
|
+
/** One `recordTransactionExecute` item; `collection` is a declaration, declared alias, or existing-ID reference. */
|
|
16
16
|
export interface TransactStep {
|
|
17
17
|
opId: string;
|
|
18
18
|
action: TransactAction;
|
|
19
|
-
collection:
|
|
19
|
+
collection: CollectionTarget | string;
|
|
20
20
|
id?: string | {
|
|
21
21
|
ref: string;
|
|
22
22
|
};
|
|
@@ -84,7 +84,7 @@ export type SettledBatchResults<Q extends BatchQueries> = {
|
|
|
84
84
|
export declare const MAX_BATCH_QUERIES = 20;
|
|
85
85
|
/** Credentials as for `createClient`; `transportTimeoutMs` (per attempt) defaults to 25 s here, see DEFAULT_DATA_TIMEOUT_MS. */
|
|
86
86
|
export interface DataClientConfig extends ClientConfig {
|
|
87
|
-
collections
|
|
87
|
+
collections?: readonly CollectionDefinition[];
|
|
88
88
|
/** Defaults to "deployed" when NODE_ENV is production, otherwise "preview". */
|
|
89
89
|
environment?: DataEnvironment;
|
|
90
90
|
logger?: Logger;
|
|
@@ -92,6 +92,11 @@ export interface DataClientConfig extends ClientConfig {
|
|
|
92
92
|
export interface DataClient {
|
|
93
93
|
/** Typed handle for a declaration listed in `collections`. */
|
|
94
94
|
collection<C extends CollectionDefinition>(definition: C): Collection<z.output<C['schema']>, z.input<C['schema']>>;
|
|
95
|
+
/** Existing collection, with optional local write validation. Never ensured, seeded, or granted by the SDK. */
|
|
96
|
+
collection<S extends CollectionSchema>(reference: CollectionReference<S> & {
|
|
97
|
+
schema: S;
|
|
98
|
+
}): Collection<z.output<S>, z.input<S>>;
|
|
99
|
+
collection<T extends object = Record<string, unknown>>(reference: CollectionReference): Collection<T>;
|
|
95
100
|
/** Handle by alias, typed by the caller. */
|
|
96
101
|
collection<T extends object = Record<string, unknown>>(alias: string): Collection<T>;
|
|
97
102
|
/**
|
package/dist/cjs/data/client.js
CHANGED
|
@@ -19,7 +19,7 @@ const QUERY_NAME = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
|
19
19
|
function createDataClient(config) {
|
|
20
20
|
const { call, openStream } = (0, call_js_1.createDataTransport)(config);
|
|
21
21
|
const environment = config.environment ?? ((0, runtime_env_js_1.getEnvVar)('NODE_ENV') === 'production' ? 'deployed' : 'preview');
|
|
22
|
-
const declared = [...config.collections];
|
|
22
|
+
const declared = [...(config.collections ?? [])];
|
|
23
23
|
if (declared.length > define_js_1.MAX_COLLECTIONS) {
|
|
24
24
|
throw new Error(`An App declares at most ${define_js_1.MAX_COLLECTIONS} collections; got ${declared.length}.`);
|
|
25
25
|
}
|
|
@@ -33,12 +33,15 @@ function createDataClient(config) {
|
|
|
33
33
|
const bootstrap = (0, bootstrap_js_1.createBootstrap)({ call, collections: declared, environment, logger: config.logger ?? console });
|
|
34
34
|
const runtime = {
|
|
35
35
|
call,
|
|
36
|
-
resolve: (target) =>
|
|
37
|
-
|
|
36
|
+
resolve: async (target) => {
|
|
37
|
+
const definition = lookup(target);
|
|
38
|
+
return 'collectionId' in definition ? definition.collectionId : bootstrap.resolve(definition);
|
|
39
|
+
},
|
|
40
|
+
recover: (fn, options) => options.collections.every(target => 'collectionId' in target) ? fn() : bootstrap.recover(fn, options),
|
|
38
41
|
subject: context_js_1.currentDataSubject,
|
|
39
42
|
signal: context_js_1.currentSignal,
|
|
40
43
|
};
|
|
41
|
-
const handles = new
|
|
44
|
+
const handles = new WeakMap();
|
|
42
45
|
function lookup(target) {
|
|
43
46
|
if (typeof target === 'string') {
|
|
44
47
|
const found = declared.find((definition) => definition.alias.toLowerCase() === target.toLowerCase());
|
|
@@ -46,6 +49,12 @@ function createDataClient(config) {
|
|
|
46
49
|
throw new Error(`Collection "${target}" is not declared in this data client's collections.`);
|
|
47
50
|
return found;
|
|
48
51
|
}
|
|
52
|
+
if ('collectionId' in target) {
|
|
53
|
+
if (typeof target.collectionId !== 'string' || !target.collectionId || target.collectionId.trim() !== target.collectionId) {
|
|
54
|
+
throw new TypeError('An existing collection reference needs a nonempty collectionId without surrounding whitespace.');
|
|
55
|
+
}
|
|
56
|
+
return target;
|
|
57
|
+
}
|
|
49
58
|
if (!declared.includes(target)) {
|
|
50
59
|
throw new Error(`Collection "${target.alias}" is declared but missing from the \`collections\` passed to createDataClient.`);
|
|
51
60
|
}
|
|
@@ -63,7 +72,7 @@ function createDataClient(config) {
|
|
|
63
72
|
async function transact(steps, options = {}) {
|
|
64
73
|
(0, collection_js_1.assertAtomicBatch)('data.transact', steps.length);
|
|
65
74
|
// Steps resolve their collection IDs inside the recovered call, so a retry uses fresh IDs.
|
|
66
|
-
const result = await
|
|
75
|
+
const result = await runtime.recover(() => sendTransaction(steps, options), { collections: steps.map(step => lookup(step.collection)) });
|
|
67
76
|
const results = (result.results ?? []).map(({ document, ...item }) => ({
|
|
68
77
|
...item,
|
|
69
78
|
...(document ? { document: (0, collection_js_1.toDoc)(document) } : {}),
|
|
@@ -87,7 +96,7 @@ function createDataClient(config) {
|
|
|
87
96
|
async function sendTransaction(steps, options) {
|
|
88
97
|
const items = await Promise.all(steps.map(async ({ collection: target, sort, query, ...step }) => {
|
|
89
98
|
const definition = lookup(target);
|
|
90
|
-
const item = { ...step, collectionId: await
|
|
99
|
+
const item = { ...step, collectionId: await runtime.resolve(definition) };
|
|
91
100
|
if (sort)
|
|
92
101
|
item.sort = (0, sort_js_1.toSortPairs)(sort);
|
|
93
102
|
if (query)
|
|
@@ -116,17 +125,16 @@ function createDataClient(config) {
|
|
|
116
125
|
const bad = names.find((name) => !QUERY_NAME.test(name));
|
|
117
126
|
if (bad !== undefined)
|
|
118
127
|
throw new TypeError(`data.queryBatch query name "${bad}" must match ${QUERY_NAME}.`);
|
|
119
|
-
|
|
120
|
-
lookup(queries[name].collection);
|
|
128
|
+
const collections = names.map(name => lookup(queries[name].collection));
|
|
121
129
|
// Items resolve their collection IDs inside the recovered call, so a retry uses fresh IDs.
|
|
122
|
-
const { results } = await
|
|
130
|
+
const { results } = await runtime.recover(async () => {
|
|
123
131
|
const items = await Promise.all(names.map(async (name) => ({
|
|
124
132
|
id: name,
|
|
125
|
-
collectionId: await
|
|
133
|
+
collectionId: await runtime.resolve(queries[name].collection),
|
|
126
134
|
...(await queries[name].prepare()),
|
|
127
135
|
})));
|
|
128
136
|
return call('recordQueryBatch', { queries: items }, { dataSubject: (0, context_js_1.currentDataSubject)(), signal: (0, context_js_1.currentSignal)() });
|
|
129
|
-
});
|
|
137
|
+
}, { collections });
|
|
130
138
|
const settled = Object.fromEntries(names.map((name) => {
|
|
131
139
|
const result = results[name];
|
|
132
140
|
if (result?.ok === true)
|
|
@@ -142,8 +150,13 @@ function createDataClient(config) {
|
|
|
142
150
|
}
|
|
143
151
|
const subscribe = (0, subscribe_js_1.createSubscribe)({
|
|
144
152
|
openStream,
|
|
145
|
-
ensure:
|
|
146
|
-
|
|
153
|
+
ensure: (targets) => targets.some(target => !('collectionId' in target))
|
|
154
|
+
? bootstrap.ensure()
|
|
155
|
+
: Promise.resolve({ results: [], collectionIds: new Map(), conflicts: [] }),
|
|
156
|
+
rebuild: (state, error, targets) => targets.every(target => 'collectionId' in target)
|
|
157
|
+
? Promise.resolve(false)
|
|
158
|
+
: bootstrap.rebuild(state, error),
|
|
159
|
+
resolve: runtime.resolve,
|
|
147
160
|
lookup,
|
|
148
161
|
declared,
|
|
149
162
|
subject: context_js_1.currentDataSubject,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/data/client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Data client factory: collection handles, cross-collection transactions and\n * the shared bootstrap. An App builds one client per process from its\n * collection declarations; tests build their own with a mock `fetch`.\n */\nimport type { z } from 'zod';\nimport type { ClientConfig } from '../openapi/client.js';\nimport { getEnvVar } from '../runtime-env.js';\nimport { createBootstrap, type DataEnvironment, type EnsureState, type Logger } from './bootstrap.js';\nimport { createDataTransport } from './call.js';\nimport { assertAtomicBatch, Collection, toDoc, type BatchQuery, type UnsRecord, type WithMeta } from './collection.js';\nimport { currentDataSubject, currentSignal } from './context.js';\nimport { MAX_COLLECTIONS, type CollectionDefinition } from './define.js';\nimport { createSubscribe, type SubscribeOptions, type SubscriptionEvent } from './subscribe.js';\nimport { parseUnsV2Fault, UnsV2Error } from '../uns-v2/client.js';\nimport { DataError, toDataError } from './error.js';\nimport { validateStepData, validateUpdate } from './schema.js';\nimport { toSortPairs, type Filter, type Sort } from './sort.js';\n\nexport type TransactAction =\n | 'record.create'\n | 'record.check'\n | 'record.read'\n | 'record.update'\n | 'record.modify'\n | 'record.replace'\n | 'record.delete'\n | 'record.query';\n\n/** One `recordTransactionExecute` item; `collection` is a declaration or alias instead of a collection ID. */\nexport interface TransactStep {\n opId: string;\n action: TransactAction;\n collection: CollectionDefinition | string;\n id?: string | { ref: string };\n filter?: Filter;\n data?: Record<string, unknown>;\n update?: Record<string, unknown>;\n upsert?: boolean;\n expectedRevision?: number;\n dependsOn?: string[];\n bindings?: { path: string; ref: string; resultPath: string }[];\n selection?: 'exactOne' | 'first';\n sort?: Sort;\n returnDocument?: 'before' | 'after';\n exists?: boolean;\n operations?: Record<string, unknown>[];\n conditions?: Filter[];\n /** `record.query`: rows to bind from; `expectCount` asserts the match count before `limit`. */\n query?: { filter?: Filter; sort?: Sort; limit?: number; expectCount?: { min?: number; max?: number } };\n arrayFilters?: Record<string, unknown>[];\n}\n\nexport interface TransactResultItem {\n opId: string;\n action: string;\n id: string;\n status: string;\n collectionId?: string;\n revision?: number;\n /** Before/after snapshot when requested, as a flat document. */\n document?: WithMeta<Record<string, unknown>>;\n data?: unknown;\n query?: unknown;\n error?: unknown;\n}\n\nexport interface TransactResult {\n status: string;\n results: TransactResultItem[];\n}\n\n/** Named reads for `data.queryBatch`: 1–20 entries, names matching `[A-Za-z0-9_.-]{1,64}`. */\nexport type BatchQueries = Record<string, BatchQuery<unknown>>;\nexport type BatchResults<Q extends BatchQueries> = { [K in keyof Q]: Q[K] extends BatchQuery<infer R> ? R : never };\nexport type SettledBatchResults<Q extends BatchQueries> = {\n [K in keyof Q]: Q[K] extends BatchQuery<infer R> ? { ok: true; value: R } | { ok: false; error: DataError } : never;\n};\n\n/** Contract limit of `records:queryBatch`. */\nexport const MAX_BATCH_QUERIES = 20;\nconst QUERY_NAME = /^[A-Za-z0-9_.-]{1,64}$/;\n\n/** Credentials as for `createClient`; `transportTimeoutMs` (per attempt) defaults to 25 s here, see DEFAULT_DATA_TIMEOUT_MS. */\nexport interface DataClientConfig extends ClientConfig {\n collections: readonly CollectionDefinition[];\n /** Defaults to \"deployed\" when NODE_ENV is production, otherwise \"preview\". */\n environment?: DataEnvironment;\n logger?: Logger;\n}\n\nexport interface DataClient {\n /** Typed handle for a declaration listed in `collections`. */\n collection<C extends CollectionDefinition>(definition: C): Collection<z.output<C['schema']>, z.input<C['schema']>>;\n /** Handle by alias, typed by the caller. */\n collection<T extends object = Record<string, unknown>>(alias: string): Collection<T>;\n /**\n * Atomic multi-record, multi-collection write (`recordTransactionExecute`, sync),\n * at most 200 steps. Steps can depend on each other (`dependsOn`) and bind\n * earlier results (`bindings`). This is not a Mongo session: everything is\n * sent at once.\n */\n transact(steps: readonly TransactStep[], options?: { dryRun?: boolean; idempotencyKey?: string }): Promise<TransactResult>;\n /**\n * Committed record changes of declared collections (all by default), as\n * one async iterable that reconnects and resumes by itself; see\n * `SubscribeOptions`. Events carry identities only; changes in collections\n * with employee data rules arrive as collection-level notices unless\n * `restricted: \"record\"` is asked for. Open it inside a request to watch as\n * that employee, or in `runAsApp` for one App-wide stream a server fans out.\n */\n subscribe(options?: SubscribeOptions): AsyncIterable<SubscriptionEvent>;\n /**\n * Up to 20 reads of declared collections in one request, answered from one\n * snapshot: what a page shows (a list page with its total, counters, a\n * lookup by ID) in one round trip. Build each read with\n * `collection.query.*`; results come back under the same names.\n *\n * A read UNS refuses on its own (invalid filter, no grant) rejects the\n * whole call with its DataError; with `{ settle: true }` every result is\n * `{ ok, value }` or `{ ok: false, error }` instead. A statement timeout or\n * a batch over 2000 rows / 8 MiB always fails the whole call.\n */\n queryBatch<Q extends BatchQueries>(queries: Q): Promise<BatchResults<Q>>;\n queryBatch<Q extends BatchQueries>(queries: Q, options: { settle: true }): Promise<SettledBatchResults<Q>>;\n /** Ensures every declaration now (normally lazy on first access). */\n ensure(): Promise<EnsureState>;\n}\n\nexport function createDataClient(config: DataClientConfig): DataClient {\n const { call, openStream } = createDataTransport(config);\n const environment = config.environment ?? (getEnvVar('NODE_ENV') === 'production' ? 'deployed' : 'preview');\n const declared = [...config.collections];\n if (declared.length > MAX_COLLECTIONS) {\n throw new Error(`An App declares at most ${MAX_COLLECTIONS} collections; got ${declared.length}.`);\n }\n const seen = new Set<string>();\n for (const definition of declared) {\n const key = definition.alias.toLowerCase();\n if (seen.has(key)) throw new Error(`Collection alias \"${definition.alias}\" is declared twice (aliases are case-insensitive).`);\n seen.add(key);\n }\n\n const bootstrap = createBootstrap({ call, collections: declared, environment, logger: config.logger ?? console });\n const runtime = {\n call,\n resolve: (target: CollectionDefinition | string) => bootstrap.resolve(lookup(target)),\n recover: bootstrap.recover,\n subject: currentDataSubject,\n signal: currentSignal,\n };\n const handles = new Map<CollectionDefinition, Collection<object>>();\n\n function lookup(target: CollectionDefinition | string): CollectionDefinition {\n if (typeof target === 'string') {\n const found = declared.find((definition) => definition.alias.toLowerCase() === target.toLowerCase());\n if (!found) throw new Error(`Collection \"${target}\" is not declared in this data client's collections.`);\n return found;\n }\n if (!declared.includes(target)) {\n throw new Error(`Collection \"${target.alias}\" is declared but missing from the \\`collections\\` passed to createDataClient.`);\n }\n return target;\n }\n\n function collection(target: CollectionDefinition | string) {\n const definition = lookup(target);\n let handle = handles.get(definition);\n if (!handle) {\n handle = new Collection<object>(definition, runtime);\n handles.set(definition, handle);\n }\n return handle;\n }\n\n async function transact(\n steps: readonly TransactStep[],\n options: { dryRun?: boolean; idempotencyKey?: string } = {},\n ): Promise<TransactResult> {\n assertAtomicBatch('data.transact', steps.length);\n // Steps resolve their collection IDs inside the recovered call, so a retry uses fresh IDs.\n const result = await bootstrap.recover(() => sendTransaction(steps, options));\n const results = (result.results ?? []).map(({ document, ...item }) => ({\n ...item,\n ...(document ? { document: toDoc<Record<string, unknown>>(document) } : {}),\n }));\n const expected = options.dryRun ? 'validated' : 'committed';\n if (result.status !== expected) {\n const failed = results.filter((item) => item.error);\n // A sync transaction should only fail whole; map the rest of the Outcome statuses anyway.\n const execution = result.status === 'unknown' ? 'unknown' : result.status === 'partiallyCommitted' ? 'committed' : 'rolledBack';\n throw new DataError({\n message: `Transaction ${result.status}: ${failed.map((item) => `${item.opId} ${JSON.stringify(item.error)}`).join('; ') || 'no step committed'}`,\n status: execution === 'unknown' ? 503 : 409,\n code: 'TRANSACTION_FAILED',\n execution,\n attempts: 1,\n details: { status: result.status, results },\n });\n }\n return { status: result.status, results };\n }\n\n async function sendTransaction(steps: readonly TransactStep[], options: { dryRun?: boolean; idempotencyKey?: string }) {\n const items = await Promise.all(\n steps.map(async ({ collection: target, sort, query, ...step }) => {\n const definition = lookup(target);\n const item: Record<string, unknown> = { ...step, collectionId: await bootstrap.resolve(definition) };\n if (sort) item.sort = toSortPairs(sort);\n if (query) item.query = { ...query, ...(query.sort ? { sort: toSortPairs(query.sort) } : {}) };\n if (step.data) {\n // Fields bound to earlier results are filled by UNS at commit; everything literal validates now.\n const bound = (step.bindings ?? []).flatMap((binding) => (binding.path.startsWith('data.') ? [binding.path.slice(5).split('.')[0]] : []));\n item.data = validateStepData(definition, step.data, bound);\n }\n if (step.update) item.update = validateUpdate(definition, step.update);\n return item;\n }),\n );\n return call<{ status: string; results?: (Omit<TransactResultItem, 'document'> & { document?: UnsRecord })[] }>(\n 'recordTransactionExecute',\n {\n items,\n mode: 'sync',\n ...(options.dryRun ? { dryRun: true } : {}),\n ...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),\n },\n { dataSubject: currentDataSubject(), signal: currentSignal() },\n );\n }\n\n async function queryBatch<Q extends BatchQueries>(queries: Q, options: { settle?: boolean } = {}) {\n const names = Object.keys(queries);\n if (names.length < 1 || names.length > MAX_BATCH_QUERIES) {\n throw new TypeError(`data.queryBatch takes 1–${MAX_BATCH_QUERIES} queries; got ${names.length}.`);\n }\n const bad = names.find((name) => !QUERY_NAME.test(name));\n if (bad !== undefined) throw new TypeError(`data.queryBatch query name \"${bad}\" must match ${QUERY_NAME}.`);\n for (const name of names) lookup(queries[name].collection);\n // Items resolve their collection IDs inside the recovered call, so a retry uses fresh IDs.\n const { results } = await bootstrap.recover(async () => {\n const items = await Promise.all(names.map(async (name) => ({\n id: name,\n collectionId: await bootstrap.resolve(queries[name].collection),\n ...(await queries[name].prepare()),\n })));\n return call<{ results: Record<string, { ok: true; data: unknown } | { ok: false; error: unknown }> }>(\n 'recordQueryBatch', { queries: items }, { dataSubject: currentDataSubject(), signal: currentSignal() });\n });\n const settled = Object.fromEntries(names.map((name) => {\n const result = results[name];\n if (result?.ok === true) return [name, { ok: true, value: queries[name].parse(result.data) }];\n return [name, { ok: false, error: itemError(name, result?.error) }];\n })) as Record<string, { ok: true; value: unknown } | { ok: false; error: DataError }>;\n if (options.settle) return settled;\n const failed = names.find((name) => !settled[name].ok);\n if (failed !== undefined) throw (settled[failed] as { error: DataError }).error;\n return Object.fromEntries(names.map((name) => [name, (settled[name] as { value: unknown }).value]));\n }\n\n const subscribe = createSubscribe({\n openStream,\n ensure: bootstrap.ensure,\n rebuild: bootstrap.rebuild,\n lookup,\n declared,\n subject: currentDataSubject,\n });\n\n return {\n collection: collection as DataClient['collection'],\n transact,\n queryBatch: queryBatch as DataClient['queryBatch'],\n subscribe,\n ensure: bootstrap.ensure,\n };\n}\n\n/** Item errors carry no HTTP status of their own; it follows from the code. */\nconst ITEM_STATUS: Record<string, number> = { FORBIDDEN: 403, NOT_FOUND: 404 };\n\n/** A queryBatch item's error as a DataError; its `upstreamStatus` is the status its code stands for. */\nfunction itemError(name: string, value: unknown): DataError {\n const fault = parseUnsV2Fault(value);\n if (!fault) {\n return new DataError({\n message: `UNS answered query \"${name}\" of recordQueryBatch with an invalid result.`,\n status: 502,\n code: 'DATA_BAD_RESPONSE',\n upstreamStatus: 200,\n execution: 'rolledBack',\n attempts: 1,\n });\n }\n const error = new UnsV2Error(fault.reason ?? fault.code, {\n ...fault,\n // The items are reads: never an unknown outcome.\n execution: fault.execution === 'unknown' ? 'rolledBack' : fault.execution,\n queryId: fault.queryId ?? name,\n status: ITEM_STATUS[fault.code] ?? 400,\n operation: 'recordQueryBatch',\n data: { queryId: fault.queryId ?? name, details: fault.details ?? [] },\n });\n return toDataError(error);\n}\n\n"]}
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/data/client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Data client factory: collection handles, cross-collection transactions and\n * the shared bootstrap. An App builds one client per process from its\n * collection declarations; tests build their own with a mock `fetch`.\n */\nimport type { z } from 'zod';\nimport type { ClientConfig } from '../openapi/client.js';\nimport { getEnvVar } from '../runtime-env.js';\nimport { createBootstrap, type DataEnvironment, type EnsureState, type Logger } from './bootstrap.js';\nimport { createDataTransport } from './call.js';\nimport { assertAtomicBatch, Collection, toDoc, type BatchQuery, type UnsRecord, type WithMeta } from './collection.js';\nimport { currentDataSubject, currentSignal } from './context.js';\nimport { MAX_COLLECTIONS, type CollectionDefinition, type CollectionReference, type CollectionSchema, type CollectionTarget } from './define.js';\nimport { createSubscribe, type SubscribeOptions, type SubscriptionEvent } from './subscribe.js';\nimport { parseUnsV2Fault, UnsV2Error } from '../uns-v2/client.js';\nimport { DataError, toDataError } from './error.js';\nimport { validateStepData, validateUpdate } from './schema.js';\nimport { toSortPairs, type Filter, type Sort } from './sort.js';\n\nexport type TransactAction =\n | 'record.create'\n | 'record.check'\n | 'record.read'\n | 'record.update'\n | 'record.modify'\n | 'record.replace'\n | 'record.delete'\n | 'record.query';\n\n/** One `recordTransactionExecute` item; `collection` is a declaration, declared alias, or existing-ID reference. */\nexport interface TransactStep {\n opId: string;\n action: TransactAction;\n collection: CollectionTarget | string;\n id?: string | { ref: string };\n filter?: Filter;\n data?: Record<string, unknown>;\n update?: Record<string, unknown>;\n upsert?: boolean;\n expectedRevision?: number;\n dependsOn?: string[];\n bindings?: { path: string; ref: string; resultPath: string }[];\n selection?: 'exactOne' | 'first';\n sort?: Sort;\n returnDocument?: 'before' | 'after';\n exists?: boolean;\n operations?: Record<string, unknown>[];\n conditions?: Filter[];\n /** `record.query`: rows to bind from; `expectCount` asserts the match count before `limit`. */\n query?: { filter?: Filter; sort?: Sort; limit?: number; expectCount?: { min?: number; max?: number } };\n arrayFilters?: Record<string, unknown>[];\n}\n\nexport interface TransactResultItem {\n opId: string;\n action: string;\n id: string;\n status: string;\n collectionId?: string;\n revision?: number;\n /** Before/after snapshot when requested, as a flat document. */\n document?: WithMeta<Record<string, unknown>>;\n data?: unknown;\n query?: unknown;\n error?: unknown;\n}\n\nexport interface TransactResult {\n status: string;\n results: TransactResultItem[];\n}\n\n/** Named reads for `data.queryBatch`: 1–20 entries, names matching `[A-Za-z0-9_.-]{1,64}`. */\nexport type BatchQueries = Record<string, BatchQuery<unknown>>;\nexport type BatchResults<Q extends BatchQueries> = { [K in keyof Q]: Q[K] extends BatchQuery<infer R> ? R : never };\nexport type SettledBatchResults<Q extends BatchQueries> = {\n [K in keyof Q]: Q[K] extends BatchQuery<infer R> ? { ok: true; value: R } | { ok: false; error: DataError } : never;\n};\n\n/** Contract limit of `records:queryBatch`. */\nexport const MAX_BATCH_QUERIES = 20;\nconst QUERY_NAME = /^[A-Za-z0-9_.-]{1,64}$/;\n\n/** Credentials as for `createClient`; `transportTimeoutMs` (per attempt) defaults to 25 s here, see DEFAULT_DATA_TIMEOUT_MS. */\nexport interface DataClientConfig extends ClientConfig {\n collections?: readonly CollectionDefinition[];\n /** Defaults to \"deployed\" when NODE_ENV is production, otherwise \"preview\". */\n environment?: DataEnvironment;\n logger?: Logger;\n}\n\nexport interface DataClient {\n /** Typed handle for a declaration listed in `collections`. */\n collection<C extends CollectionDefinition>(definition: C): Collection<z.output<C['schema']>, z.input<C['schema']>>;\n /** Existing collection, with optional local write validation. Never ensured, seeded, or granted by the SDK. */\n collection<S extends CollectionSchema>(reference: CollectionReference<S> & { schema: S }): Collection<z.output<S>, z.input<S>>;\n collection<T extends object = Record<string, unknown>>(reference: CollectionReference): Collection<T>;\n /** Handle by alias, typed by the caller. */\n collection<T extends object = Record<string, unknown>>(alias: string): Collection<T>;\n /**\n * Atomic multi-record, multi-collection write (`recordTransactionExecute`, sync),\n * at most 200 steps. Steps can depend on each other (`dependsOn`) and bind\n * earlier results (`bindings`). This is not a Mongo session: everything is\n * sent at once.\n */\n transact(steps: readonly TransactStep[], options?: { dryRun?: boolean; idempotencyKey?: string }): Promise<TransactResult>;\n /**\n * Committed record changes of declared collections (all by default), as\n * one async iterable that reconnects and resumes by itself; see\n * `SubscribeOptions`. Events carry identities only; changes in collections\n * with employee data rules arrive as collection-level notices unless\n * `restricted: \"record\"` is asked for. Open it inside a request to watch as\n * that employee, or in `runAsApp` for one App-wide stream a server fans out.\n */\n subscribe(options?: SubscribeOptions): AsyncIterable<SubscriptionEvent>;\n /**\n * Up to 20 reads of declared collections in one request, answered from one\n * snapshot: what a page shows (a list page with its total, counters, a\n * lookup by ID) in one round trip. Build each read with\n * `collection.query.*`; results come back under the same names.\n *\n * A read UNS refuses on its own (invalid filter, no grant) rejects the\n * whole call with its DataError; with `{ settle: true }` every result is\n * `{ ok, value }` or `{ ok: false, error }` instead. A statement timeout or\n * a batch over 2000 rows / 8 MiB always fails the whole call.\n */\n queryBatch<Q extends BatchQueries>(queries: Q): Promise<BatchResults<Q>>;\n queryBatch<Q extends BatchQueries>(queries: Q, options: { settle: true }): Promise<SettledBatchResults<Q>>;\n /** Ensures every declaration now (normally lazy on first access). */\n ensure(): Promise<EnsureState>;\n}\n\nexport function createDataClient(config: DataClientConfig): DataClient {\n const { call, openStream } = createDataTransport(config);\n const environment = config.environment ?? (getEnvVar('NODE_ENV') === 'production' ? 'deployed' : 'preview');\n const declared = [...(config.collections ?? [])];\n if (declared.length > MAX_COLLECTIONS) {\n throw new Error(`An App declares at most ${MAX_COLLECTIONS} collections; got ${declared.length}.`);\n }\n const seen = new Set<string>();\n for (const definition of declared) {\n const key = definition.alias.toLowerCase();\n if (seen.has(key)) throw new Error(`Collection alias \"${definition.alias}\" is declared twice (aliases are case-insensitive).`);\n seen.add(key);\n }\n\n const bootstrap = createBootstrap({ call, collections: declared, environment, logger: config.logger ?? console });\n const runtime = {\n call,\n resolve: async (target: CollectionTarget | string) => {\n const definition = lookup(target);\n return 'collectionId' in definition ? definition.collectionId : bootstrap.resolve(definition);\n },\n recover: <R>(fn: () => Promise<R>, options: { collections: readonly CollectionTarget[]; retry?: boolean }) =>\n options.collections.every(target => 'collectionId' in target) ? fn() : bootstrap.recover(fn, options),\n subject: currentDataSubject,\n signal: currentSignal,\n };\n const handles = new WeakMap<CollectionTarget, Collection<object>>();\n\n function lookup(target: CollectionTarget | string): CollectionTarget {\n if (typeof target === 'string') {\n const found = declared.find((definition) => definition.alias.toLowerCase() === target.toLowerCase());\n if (!found) throw new Error(`Collection \"${target}\" is not declared in this data client's collections.`);\n return found;\n }\n if ('collectionId' in target) {\n if (typeof target.collectionId !== 'string' || !target.collectionId || target.collectionId.trim() !== target.collectionId) {\n throw new TypeError('An existing collection reference needs a nonempty collectionId without surrounding whitespace.');\n }\n return target;\n }\n if (!declared.includes(target)) {\n throw new Error(`Collection \"${target.alias}\" is declared but missing from the \\`collections\\` passed to createDataClient.`);\n }\n return target;\n }\n\n function collection(target: CollectionTarget | string) {\n const definition = lookup(target);\n let handle = handles.get(definition);\n if (!handle) {\n handle = new Collection<object>(definition, runtime);\n handles.set(definition, handle);\n }\n return handle;\n }\n\n async function transact(\n steps: readonly TransactStep[],\n options: { dryRun?: boolean; idempotencyKey?: string } = {},\n ): Promise<TransactResult> {\n assertAtomicBatch('data.transact', steps.length);\n // Steps resolve their collection IDs inside the recovered call, so a retry uses fresh IDs.\n const result = await runtime.recover(() => sendTransaction(steps, options), { collections: steps.map(step => lookup(step.collection)) });\n const results = (result.results ?? []).map(({ document, ...item }) => ({\n ...item,\n ...(document ? { document: toDoc<Record<string, unknown>>(document) } : {}),\n }));\n const expected = options.dryRun ? 'validated' : 'committed';\n if (result.status !== expected) {\n const failed = results.filter((item) => item.error);\n // A sync transaction should only fail whole; map the rest of the Outcome statuses anyway.\n const execution = result.status === 'unknown' ? 'unknown' : result.status === 'partiallyCommitted' ? 'committed' : 'rolledBack';\n throw new DataError({\n message: `Transaction ${result.status}: ${failed.map((item) => `${item.opId} ${JSON.stringify(item.error)}`).join('; ') || 'no step committed'}`,\n status: execution === 'unknown' ? 503 : 409,\n code: 'TRANSACTION_FAILED',\n execution,\n attempts: 1,\n details: { status: result.status, results },\n });\n }\n return { status: result.status, results };\n }\n\n async function sendTransaction(steps: readonly TransactStep[], options: { dryRun?: boolean; idempotencyKey?: string }) {\n const items = await Promise.all(\n steps.map(async ({ collection: target, sort, query, ...step }) => {\n const definition = lookup(target);\n const item: Record<string, unknown> = { ...step, collectionId: await runtime.resolve(definition) };\n if (sort) item.sort = toSortPairs(sort);\n if (query) item.query = { ...query, ...(query.sort ? { sort: toSortPairs(query.sort) } : {}) };\n if (step.data) {\n // Fields bound to earlier results are filled by UNS at commit; everything literal validates now.\n const bound = (step.bindings ?? []).flatMap((binding) => (binding.path.startsWith('data.') ? [binding.path.slice(5).split('.')[0]] : []));\n item.data = validateStepData(definition, step.data, bound);\n }\n if (step.update) item.update = validateUpdate(definition, step.update);\n return item;\n }),\n );\n return call<{ status: string; results?: (Omit<TransactResultItem, 'document'> & { document?: UnsRecord })[] }>(\n 'recordTransactionExecute',\n {\n items,\n mode: 'sync',\n ...(options.dryRun ? { dryRun: true } : {}),\n ...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),\n },\n { dataSubject: currentDataSubject(), signal: currentSignal() },\n );\n }\n\n async function queryBatch<Q extends BatchQueries>(queries: Q, options: { settle?: boolean } = {}) {\n const names = Object.keys(queries);\n if (names.length < 1 || names.length > MAX_BATCH_QUERIES) {\n throw new TypeError(`data.queryBatch takes 1–${MAX_BATCH_QUERIES} queries; got ${names.length}.`);\n }\n const bad = names.find((name) => !QUERY_NAME.test(name));\n if (bad !== undefined) throw new TypeError(`data.queryBatch query name \"${bad}\" must match ${QUERY_NAME}.`);\n const collections = names.map(name => lookup(queries[name].collection));\n // Items resolve their collection IDs inside the recovered call, so a retry uses fresh IDs.\n const { results } = await runtime.recover(async () => {\n const items = await Promise.all(names.map(async (name) => ({\n id: name,\n collectionId: await runtime.resolve(queries[name].collection),\n ...(await queries[name].prepare()),\n })));\n return call<{ results: Record<string, { ok: true; data: unknown } | { ok: false; error: unknown }> }>(\n 'recordQueryBatch', { queries: items }, { dataSubject: currentDataSubject(), signal: currentSignal() });\n }, { collections });\n const settled = Object.fromEntries(names.map((name) => {\n const result = results[name];\n if (result?.ok === true) return [name, { ok: true, value: queries[name].parse(result.data) }];\n return [name, { ok: false, error: itemError(name, result?.error) }];\n })) as Record<string, { ok: true; value: unknown } | { ok: false; error: DataError }>;\n if (options.settle) return settled;\n const failed = names.find((name) => !settled[name].ok);\n if (failed !== undefined) throw (settled[failed] as { error: DataError }).error;\n return Object.fromEntries(names.map((name) => [name, (settled[name] as { value: unknown }).value]));\n }\n\n const subscribe = createSubscribe({\n openStream,\n ensure: (targets) => targets.some(target => !('collectionId' in target))\n ? bootstrap.ensure()\n : Promise.resolve({ results: [], collectionIds: new Map<string, string>(), conflicts: [] }),\n rebuild: (state, error, targets) => targets.every(target => 'collectionId' in target)\n ? Promise.resolve(false)\n : bootstrap.rebuild(state, error),\n resolve: runtime.resolve,\n lookup,\n declared,\n subject: currentDataSubject,\n });\n\n return {\n collection: collection as DataClient['collection'],\n transact,\n queryBatch: queryBatch as DataClient['queryBatch'],\n subscribe,\n ensure: bootstrap.ensure,\n };\n}\n\n/** Item errors carry no HTTP status of their own; it follows from the code. */\nconst ITEM_STATUS: Record<string, number> = { FORBIDDEN: 403, NOT_FOUND: 404 };\n\n/** A queryBatch item's error as a DataError; its `upstreamStatus` is the status its code stands for. */\nfunction itemError(name: string, value: unknown): DataError {\n const fault = parseUnsV2Fault(value);\n if (!fault) {\n return new DataError({\n message: `UNS answered query \"${name}\" of recordQueryBatch with an invalid result.`,\n status: 502,\n code: 'DATA_BAD_RESPONSE',\n upstreamStatus: 200,\n execution: 'rolledBack',\n attempts: 1,\n });\n }\n const error = new UnsV2Error(fault.reason ?? fault.code, {\n ...fault,\n // The items are reads: never an unknown outcome.\n execution: fault.execution === 'unknown' ? 'rolledBack' : fault.execution,\n queryId: fault.queryId ?? name,\n status: ITEM_STATUS[fault.code] ?? 400,\n operation: 'recordQueryBatch',\n data: { queryId: fault.queryId ?? name, details: fault.details ?? [] },\n });\n return toDataError(error);\n}\n\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DataCall } from './call.js';
|
|
2
|
-
import type
|
|
2
|
+
import { type CollectionTarget, type RecordMeta } from './define.js';
|
|
3
3
|
import { type Filter, type Sort, type SortPairs } from './sort.js';
|
|
4
4
|
/** UNS page limit for find / aggregate / distinct. */
|
|
5
5
|
export declare const MAX_PAGE_SIZE = 200;
|
|
@@ -53,10 +53,11 @@ interface ManyPage extends WriteResult {
|
|
|
53
53
|
/** Runtime services a collection handle needs from its client. */
|
|
54
54
|
export interface CollectionRuntime {
|
|
55
55
|
call: DataCall;
|
|
56
|
-
/**
|
|
57
|
-
resolve(target:
|
|
56
|
+
/** Resolve an existing ID directly, or ensure a declared collection. */
|
|
57
|
+
resolve(target: CollectionTarget | string): Promise<string>;
|
|
58
58
|
/** Runs a call, re-ensuring and retrying once when its collection ID went stale (see bootstrap). */
|
|
59
|
-
recover<R>(fn: () => Promise<R>, options
|
|
59
|
+
recover<R>(fn: () => Promise<R>, options: {
|
|
60
|
+
collections: readonly CollectionTarget[];
|
|
60
61
|
retry?: boolean;
|
|
61
62
|
}): Promise<R>;
|
|
62
63
|
/** Data subject of the request being served. */
|
|
@@ -129,7 +130,7 @@ export interface ValueCount {
|
|
|
129
130
|
}
|
|
130
131
|
/** One read of `data.queryBatch`, built by `collection.query.*`. */
|
|
131
132
|
export interface BatchQuery<R> {
|
|
132
|
-
readonly collection:
|
|
133
|
+
readonly collection: CollectionTarget;
|
|
133
134
|
/** The batch item without `id` and `collectionId`; resolves `$lookup` targets. */
|
|
134
135
|
prepare(): Promise<Record<string, unknown>>;
|
|
135
136
|
/** The item's result as the builder's caller sees it. */
|
|
@@ -218,9 +219,9 @@ export type BulkOperation = {
|
|
|
218
219
|
*/
|
|
219
220
|
export declare function assertNoWriteErrors(result: ManyPage, operation: string, committed?: Record<string, number>): void;
|
|
220
221
|
export declare class Collection<T extends object = Record<string, unknown>, TInput extends object = T> {
|
|
221
|
-
readonly definition:
|
|
222
|
+
readonly definition: CollectionTarget;
|
|
222
223
|
private readonly runtime;
|
|
223
|
-
constructor(definition:
|
|
224
|
+
constructor(definition: CollectionTarget, runtime: CollectionRuntime);
|
|
224
225
|
get alias(): string;
|
|
225
226
|
/**
|
|
226
227
|
* One call with stale-ID recovery. A call continuing a cursor or
|
|
@@ -4,6 +4,7 @@ exports.Collection = exports.MAX_GET_IDS = exports.Cursor = exports.MAX_ATOMIC_I
|
|
|
4
4
|
exports.assertAtomicBatch = assertAtomicBatch;
|
|
5
5
|
exports.toDoc = toDoc;
|
|
6
6
|
exports.assertNoWriteErrors = assertNoWriteErrors;
|
|
7
|
+
const define_js_1 = require("./define.js");
|
|
7
8
|
const error_js_1 = require("./error.js");
|
|
8
9
|
const schema_js_1 = require("./schema.js");
|
|
9
10
|
const sort_js_1 = require("./sort.js");
|
|
@@ -194,7 +195,7 @@ class Collection {
|
|
|
194
195
|
this.runtime = runtime;
|
|
195
196
|
}
|
|
196
197
|
get alias() {
|
|
197
|
-
return this.definition
|
|
198
|
+
return (0, define_js_1.collectionLabel)(this.definition);
|
|
198
199
|
}
|
|
199
200
|
/**
|
|
200
201
|
* One call with stale-ID recovery. A call continuing a cursor or
|
|
@@ -204,7 +205,7 @@ class Collection {
|
|
|
204
205
|
post(operation, body, options = {}) {
|
|
205
206
|
const page = body.page;
|
|
206
207
|
const continues = typeof body.cursor === 'string' || typeof body.resumeAfter === 'string' || typeof page?.cursor === 'string';
|
|
207
|
-
return this.runtime.recover(() => this.send(operation, body, options), { retry: !continues });
|
|
208
|
+
return this.runtime.recover(() => this.send(operation, body, options), { collections: [this.definition], retry: !continues });
|
|
208
209
|
}
|
|
209
210
|
async send(operation, body, options = {}) {
|
|
210
211
|
const collectionId = await this.runtime.resolve(this.definition);
|
|
@@ -358,7 +359,7 @@ class Collection {
|
|
|
358
359
|
*/
|
|
359
360
|
aggregate(pipeline) {
|
|
360
361
|
// Recovery wraps stage preparation too, so a retry re-resolves `$lookup` IDs.
|
|
361
|
-
return new Cursor(({ token, size }) => this.runtime.recover(() => this.aggregatePage(pipeline, token, size), { retry: typeof token !== 'string' }));
|
|
362
|
+
return new Cursor(({ token, size }) => this.runtime.recover(() => this.aggregatePage(pipeline, token, size), { collections: [this.definition], retry: typeof token !== 'string' }));
|
|
362
363
|
}
|
|
363
364
|
async aggregatePage(pipeline, token, size) {
|
|
364
365
|
const stages = await this.prepareStages(pipeline);
|