primitive-admin 1.1.0-alpha.47 → 1.1.0-alpha.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/bin/primitive.js +2 -0
  2. package/dist/bin/primitive.js.map +1 -1
  3. package/dist/src/commands/apps.js +54 -2
  4. package/dist/src/commands/apps.js.map +1 -1
  5. package/dist/src/commands/databases.js +31 -10
  6. package/dist/src/commands/databases.js.map +1 -1
  7. package/dist/src/commands/documents.js +77 -0
  8. package/dist/src/commands/documents.js.map +1 -1
  9. package/dist/src/commands/guides.d.ts +15 -12
  10. package/dist/src/commands/guides.js +22 -15
  11. package/dist/src/commands/guides.js.map +1 -1
  12. package/dist/src/commands/settings.d.ts +15 -0
  13. package/dist/src/commands/settings.js +102 -0
  14. package/dist/src/commands/settings.js.map +1 -0
  15. package/dist/src/commands/sync-app-settings.d.ts +105 -0
  16. package/dist/src/commands/sync-app-settings.js +339 -0
  17. package/dist/src/commands/sync-app-settings.js.map +1 -0
  18. package/dist/src/commands/sync.d.ts +34 -19
  19. package/dist/src/commands/sync.js +373 -200
  20. package/dist/src/commands/sync.js.map +1 -1
  21. package/dist/src/commands/workflows.js +32 -10
  22. package/dist/src/commands/workflows.js.map +1 -1
  23. package/dist/src/lib/api-client.d.ts +2 -0
  24. package/dist/src/lib/api-client.js +6 -0
  25. package/dist/src/lib/api-client.js.map +1 -1
  26. package/dist/src/lib/app-settings-descriptor.d.ts +108 -0
  27. package/dist/src/lib/app-settings-descriptor.js +250 -0
  28. package/dist/src/lib/app-settings-descriptor.js.map +1 -0
  29. package/dist/src/lib/codegen-shared/generatedFiles.d.ts +15 -0
  30. package/dist/src/lib/codegen-shared/generatedFiles.js +27 -10
  31. package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -1
  32. package/dist/src/lib/db-codegen/dbGenerator.d.ts +11 -83
  33. package/dist/src/lib/db-codegen/dbGenerator.js +93 -556
  34. package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -1
  35. package/dist/src/lib/db-codegen/dbNaming.d.ts +15 -7
  36. package/dist/src/lib/db-codegen/dbNaming.js +25 -13
  37. package/dist/src/lib/db-codegen/dbNaming.js.map +1 -1
  38. package/dist/src/lib/db-codegen/dbTemplates.d.ts +55 -4
  39. package/dist/src/lib/db-codegen/dbTemplates.js +169 -40
  40. package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
  41. package/dist/src/lib/db-codegen/dbTypeIR.d.ts +146 -0
  42. package/dist/src/lib/db-codegen/dbTypeIR.js +517 -0
  43. package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -0
  44. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.d.ts +101 -0
  45. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js +200 -0
  46. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js.map +1 -0
  47. package/dist/src/lib/swift-codegen/dbGenerator.d.ts +68 -0
  48. package/dist/src/lib/swift-codegen/dbGenerator.js +380 -0
  49. package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -0
  50. package/dist/src/lib/swift-codegen/dbSwiftTypes.d.ts +42 -0
  51. package/dist/src/lib/swift-codegen/dbSwiftTypes.js +100 -0
  52. package/dist/src/lib/swift-codegen/dbSwiftTypes.js.map +1 -0
  53. package/dist/src/lib/swift-codegen/generator.d.ts +84 -0
  54. package/dist/src/lib/swift-codegen/generator.js +178 -0
  55. package/dist/src/lib/swift-codegen/generator.js.map +1 -0
  56. package/dist/src/lib/swift-codegen/schemaToSwift.d.ts +72 -0
  57. package/dist/src/lib/swift-codegen/schemaToSwift.js +644 -0
  58. package/dist/src/lib/swift-codegen/schemaToSwift.js.map +1 -0
  59. package/dist/src/lib/swift-codegen/swiftNaming.d.ts +85 -0
  60. package/dist/src/lib/swift-codegen/swiftNaming.js +198 -0
  61. package/dist/src/lib/swift-codegen/swiftNaming.js.map +1 -0
  62. package/dist/src/lib/sync-resource-types.d.ts +225 -0
  63. package/dist/src/lib/sync-resource-types.js +394 -0
  64. package/dist/src/lib/sync-resource-types.js.map +1 -0
  65. package/dist/src/lib/template.d.ts +1 -1
  66. package/dist/src/lib/template.js +6 -3
  67. package/dist/src/lib/template.js.map +1 -1
  68. package/dist/src/lib/workflow-codegen/generator.d.ts +18 -5
  69. package/dist/src/lib/workflow-codegen/generator.js +116 -41
  70. package/dist/src/lib/workflow-codegen/generator.js.map +1 -1
  71. package/dist/src/lib/workflow-codegen/invokerIR.d.ts +85 -0
  72. package/dist/src/lib/workflow-codegen/invokerIR.js +71 -0
  73. package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -0
  74. package/package.json +4 -2
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Shared database-type operation IR (issue #1547, Phase 4).
3
+ *
4
+ * `describeDbType` resolves ONE database-type TOML into a naming-neutral
5
+ * descriptor of its record models and operations — the record fields (with
6
+ * inferred required-ness), the per-op input params, and the per-op result
7
+ * descriptor (query projection #1439, mutation, count, aggregate,
8
+ * applyToQuery, pipeline return #1437). It performs NO language-specific
9
+ * naming: the TS renderer (`dbGenerator.ts`) and the Swift renderer
10
+ * (`swift-codegen/dbGenerator.ts`) both consume this SAME IR and each apply
11
+ * their own identifier rules. Neither renderer re-walks the op definitions, so
12
+ * the two clients can never disagree on which operations a database type
13
+ * exposes, what params they take, or what shape they return (issue #1547
14
+ * binding item 5 — the same "shared operation IR, no metadata reconstruction"
15
+ * contract Phase 3's `describeWorkflowInvoker` gives the workflow invoker).
16
+ *
17
+ * The resolution logic here was lifted verbatim from `renderDbTypeFile`'s
18
+ * former inline body; the only change is that result descriptors reference a
19
+ * MODEL NAME (naming-neutral) rather than a resolved TS interface name, so the
20
+ * Swift renderer can map the same model to its own type name.
21
+ */
22
+ import type { RecordField, ParamField, ProjectionClassification } from "./dbTemplates.js";
23
+ export interface DbCodegenInput {
24
+ /** Database type name (drives the output filename). */
25
+ databaseType: string;
26
+ /** Path to the source `database-types/<type>.toml` file. */
27
+ tomlPath: string;
28
+ /** Raw TOML content (already read from disk). */
29
+ tomlContent: string;
30
+ }
31
+ /**
32
+ * Naming-neutral form of a pipeline op's resolved `return` step (#1437). Same
33
+ * shape as `dbTemplates.PipelineReturnDescriptor` but keyed on the returning
34
+ * step's MODEL NAME instead of a resolved interface name, so each renderer maps
35
+ * it to its own record type. `opaque` → the generic pipeline-result envelope.
36
+ */
37
+ export type PipelineReturnIR = {
38
+ kind: "query";
39
+ modelName: string | null;
40
+ projection: ProjectionClassification;
41
+ } | {
42
+ kind: "count";
43
+ } | {
44
+ kind: "aggregate";
45
+ } | {
46
+ kind: "opaque";
47
+ };
48
+ /** One record model on a database type (read shape), naming-neutral. */
49
+ export interface DbRecordIR {
50
+ /** TOML model name (e.g. `accounts`). */
51
+ modelName: string;
52
+ /** Optional `class_name` override from the schema (naming input). */
53
+ className?: string;
54
+ /**
55
+ * Record fields with required-ness already resolved (schema `required`,
56
+ * `id` auto-assign, and op-params inference #842 folded in). Rendered into a
57
+ * struct/interface member per field.
58
+ */
59
+ fields: RecordField[];
60
+ }
61
+ /** One operation on a database type, naming-neutral. */
62
+ export interface DbOpIR {
63
+ /** Exact operation name (the `executeOperation` string argument). */
64
+ opName: string;
65
+ /** Operation type (`query|mutation|count|aggregate|pipeline|applyToQuery`). */
66
+ opType: string;
67
+ /** Declared input params (sorted by name), write shape. */
68
+ params: ParamField[];
69
+ /** Whether the op declares any params (a param-less op is callable bare). */
70
+ hasParams: boolean;
71
+ /**
72
+ * For a `query` op, the model its result rows are typed over — or null when
73
+ * the op has no model or the model has no record schema (the result falls
74
+ * back to an open/untyped row).
75
+ */
76
+ resultModelName: string | null;
77
+ /** For a `query` op, how its static `projection` narrows the record (#1439). */
78
+ projection?: ProjectionClassification;
79
+ /** For a `pipeline` op, the resolved `return`-step shape (#1437). */
80
+ pipelineReturn?: PipelineReturnIR;
81
+ }
82
+ /**
83
+ * One realtime subscription on a database type (#1544 Phase 2), naming-neutral.
84
+ *
85
+ * `modelName` is guaranteed to name a model that has a record schema on this
86
+ * type (`describeDbType` throws otherwise — a subscription can't be typed
87
+ * against a model the type doesn't declare; matches the server's
88
+ * `modelName is required` create validation). `select` fields, when present,
89
+ * are each guaranteed to be a known member of that record (declared field or
90
+ * stamped field) unless the record is the open `Record<string, unknown>` form,
91
+ * so the renderer can emit a valid `Pick<Record, …>` without a further guard.
92
+ */
93
+ export interface DbSubscriptionIR {
94
+ /** Subscription key (the `subscribe()` string argument). */
95
+ subscriptionKey: string;
96
+ /** Model whose record type binds the delivered change payload. */
97
+ modelName: string;
98
+ /**
99
+ * Projected field names when the subscription declares `select` (→ the event
100
+ * record narrows to `Pick<Record, …fields>`), else null (→ the full record).
101
+ * Sorted + de-duplicated for deterministic output.
102
+ */
103
+ select: string[] | null;
104
+ }
105
+ /** The shared, naming-neutral descriptor of one database type. */
106
+ export interface DbTypeIR {
107
+ /** Database type name (drives the output filename). */
108
+ databaseType: string;
109
+ /** Content fingerprint stamped in the generated header. */
110
+ fingerprint: string;
111
+ /** Server-stamped field names appended as OPTIONAL props on every record. */
112
+ stampedFields: string[];
113
+ /** Record models (schema order; renderers sort by their own type name). */
114
+ records: DbRecordIR[];
115
+ /** Operations, sorted by op name for deterministic output. */
116
+ ops: DbOpIR[];
117
+ /** Subscriptions, sorted by subscription key for deterministic output. */
118
+ subscriptions: DbSubscriptionIR[];
119
+ }
120
+ /**
121
+ * Resolve a database-type TOML into the shared, naming-neutral IR. Pure: no
122
+ * I/O beyond parsing the passed content. Both the TS and Swift renderers call
123
+ * this so their op/record metadata can never diverge.
124
+ */
125
+ export declare function describeDbType(input: DbCodegenInput): DbTypeIR;
126
+ /**
127
+ * Infer the scalar element type for op params consumed by a `$in`/`$nin`
128
+ * filter operator (#1488). See the original doc in `dbGenerator.ts` — moved
129
+ * here unchanged. Returns a map of param name → element TS type
130
+ * (`"unknown"` sentinel → widen to an untyped element).
131
+ */
132
+ export declare function inferArrayParamElementTypes(op: any, fieldTypesByModel: Map<string, Map<string, string>>): Map<string, string>;
133
+ /**
134
+ * Per-model result of {@link inferRequiredRecordFields}. Shaped as an object so
135
+ * it can grow additional inferred metadata later without changing callers.
136
+ */
137
+ export interface InferredRecordModelInfo {
138
+ /** Record field names promoted to non-optional by op-params inference. */
139
+ fields: Set<string>;
140
+ }
141
+ /**
142
+ * Infer which record fields are always supplied at create time from `save` op
143
+ * params (#842). See the original doc in `dbGenerator.ts` — moved here
144
+ * unchanged.
145
+ */
146
+ export declare function inferRequiredRecordFields(operations: any[]): Map<string, InferredRecordModelInfo>;
@@ -0,0 +1,517 @@
1
+ /**
2
+ * Shared database-type operation IR (issue #1547, Phase 4).
3
+ *
4
+ * `describeDbType` resolves ONE database-type TOML into a naming-neutral
5
+ * descriptor of its record models and operations — the record fields (with
6
+ * inferred required-ness), the per-op input params, and the per-op result
7
+ * descriptor (query projection #1439, mutation, count, aggregate,
8
+ * applyToQuery, pipeline return #1437). It performs NO language-specific
9
+ * naming: the TS renderer (`dbGenerator.ts`) and the Swift renderer
10
+ * (`swift-codegen/dbGenerator.ts`) both consume this SAME IR and each apply
11
+ * their own identifier rules. Neither renderer re-walks the op definitions, so
12
+ * the two clients can never disagree on which operations a database type
13
+ * exposes, what params they take, or what shape they return (issue #1547
14
+ * binding item 5 — the same "shared operation IR, no metadata reconstruction"
15
+ * contract Phase 3's `describeWorkflowInvoker` gives the workflow invoker).
16
+ *
17
+ * The resolution logic here was lifted verbatim from `renderDbTypeFile`'s
18
+ * former inline body; the only change is that result descriptors reference a
19
+ * MODEL NAME (naming-neutral) rather than a resolved TS interface name, so the
20
+ * Swift renderer can map the same model to its own type name.
21
+ */
22
+ import { parseConfigToml, stringifyConfigToml } from "../config-toml.js";
23
+ import { loadSchemaFromTomlString } from "js-bao";
24
+ import { parseDatabaseTypeToml } from "../../commands/sync.js";
25
+ import { fingerprintToml } from "./dbFingerprint.js";
26
+ import { tsTypeForDbFieldType } from "./dbTsTypes.js";
27
+ /**
28
+ * Resolve a database-type TOML into the shared, naming-neutral IR. Pure: no
29
+ * I/O beyond parsing the passed content. Both the TS and Swift renderers call
30
+ * this so their op/record metadata can never diverge.
31
+ */
32
+ export function describeDbType(input) {
33
+ const { tomlContent } = input;
34
+ const fingerprint = fingerprintToml(tomlContent);
35
+ // Parse the raw TOML once for the [type] / [[operations]] data, and route
36
+ // the [models.*] subtree through the shared js-bao loader (same path the
37
+ // server uses to validate the schema).
38
+ const tomlData = parseConfigToml(tomlContent);
39
+ const { typeConfig, operations, subscriptions } = parseDatabaseTypeToml(tomlData);
40
+ const schemas = tomlData.models
41
+ ? loadSchemaFromTomlString(stringifyConfigToml({ models: tomlData.models }), {
42
+ strict: false,
43
+ })
44
+ : [];
45
+ const stampedFields = collectStampedFields(typeConfig);
46
+ // model → record fields that op params prove are always supplied at create
47
+ // time; OR'd with the schema-level `required` flag below (#842).
48
+ const inferredRequired = inferRequiredRecordFields(operations);
49
+ // model → the field names on its record (declared + stamped), used to guard
50
+ // projection `pick` against unknown keys (#1439).
51
+ const recordFieldsByModel = new Map();
52
+ // model → declared field name → field type, feeding array-param element-type
53
+ // inference (#1488).
54
+ const fieldTypesByModel = new Map();
55
+ for (const schema of schemas) {
56
+ const ftypes = new Map();
57
+ for (const [fname, fopts] of Object.entries(schema.fields)) {
58
+ const t = fopts?.type;
59
+ if (typeof t === "string")
60
+ ftypes.set(fname, t);
61
+ }
62
+ fieldTypesByModel.set(schema.name, ftypes);
63
+ const memberNames = new Set(Object.keys(schema.fields));
64
+ for (const stamped of stampedFields)
65
+ memberNames.add(stamped);
66
+ recordFieldsByModel.set(schema.name, memberNames);
67
+ }
68
+ const modelsWithRecord = new Set(schemas.map((s) => s.name));
69
+ // Record IRs (schema order — renderers sort by their own type name).
70
+ const records = schemas.map((schema) => {
71
+ const inferredForModel = inferredRequired.get(schema.name)?.fields;
72
+ const fields = Object.entries(schema.fields).map(([name, opts]) => ({
73
+ name,
74
+ type: opts.type,
75
+ required: opts.required === true ||
76
+ (opts.autoAssign === true && name === "id") ||
77
+ inferredForModel?.has(name) === true,
78
+ enum: opts.enum,
79
+ }));
80
+ return {
81
+ modelName: schema.name,
82
+ className: schema.options?.className,
83
+ fields,
84
+ };
85
+ });
86
+ // Operation IRs, sorted by op name for deterministic output.
87
+ const sortedOps = [...operations]
88
+ .filter((op) => op && typeof op.name === "string")
89
+ .sort((a, b) => a.name.localeCompare(b.name));
90
+ const ops = sortedOps.map((op) => {
91
+ const inferredArrays = inferArrayParamElementTypes(op, fieldTypesByModel);
92
+ const params = paramsToFields(op.params, inferredArrays);
93
+ const resultModelName = op.type === "query" && op.modelName && modelsWithRecord.has(op.modelName)
94
+ ? op.modelName
95
+ : null;
96
+ const projection = op.type === "query"
97
+ ? classifyProjection(op.definition?.projection, (op.modelName && recordFieldsByModel.get(op.modelName)) ||
98
+ new Set())
99
+ : undefined;
100
+ const pipelineReturn = op.type === "pipeline"
101
+ ? resolvePipelineReturn(op.definition, recordFieldsByModel, modelsWithRecord)
102
+ : undefined;
103
+ return {
104
+ opName: op.name,
105
+ opType: op.type,
106
+ params,
107
+ hasParams: params.length > 0,
108
+ resultModelName,
109
+ projection,
110
+ pipelineReturn,
111
+ };
112
+ });
113
+ // Subscription IRs (#1544 Phase 2), sorted by subscription key for
114
+ // deterministic output. Each is bound to its declared `modelName`'s record
115
+ // type; `select` narrows that to a projection.
116
+ const subscriptionIRs = describeSubscriptions(subscriptions, input.databaseType, modelsWithRecord, recordFieldsByModel);
117
+ return {
118
+ databaseType: input.databaseType,
119
+ fingerprint,
120
+ stampedFields,
121
+ records,
122
+ ops,
123
+ subscriptions: subscriptionIRs,
124
+ };
125
+ }
126
+ /**
127
+ * Resolve the parsed `[[subscriptions]]` rows into naming-neutral IR
128
+ * (#1544 Phase 2). Sorted by subscription key.
129
+ *
130
+ * Precise per-subscription typing (maintainer decision, Fork 2 amended): each
131
+ * subscription is bound to its declared `modelName`. A subscription whose
132
+ * `modelName` is missing or names a model this type declares no record schema
133
+ * for is an authoring error the generator surfaces by THROWING — there is no
134
+ * silent `Record<string, unknown>` fallback and no discriminated-union fallback
135
+ * (the server has required `modelName` on every subscription since the feature
136
+ * shipped in #740, so no legacy no-`modelName` rows exist).
137
+ *
138
+ * `select` narrows the event record to a `Pick`; each selected field must be a
139
+ * known member of the record (or the record must be the open
140
+ * `Record<string, unknown>` form), else the generator throws rather than emit
141
+ * an invalid `Pick`.
142
+ */
143
+ function describeSubscriptions(subscriptions, databaseType, modelsWithRecord, recordFieldsByModel) {
144
+ const result = (subscriptions ?? [])
145
+ .filter((sub) => sub && typeof sub.subscriptionKey === "string")
146
+ .map((sub) => {
147
+ const subscriptionKey = sub.subscriptionKey;
148
+ const modelName = sub.modelName;
149
+ if (typeof modelName !== "string" || modelName === "") {
150
+ throw new Error(`Subscription "${subscriptionKey}" on database type ` +
151
+ `"${databaseType}" has no modelName. Every subscription must ` +
152
+ `declare the model whose changes it delivers.`);
153
+ }
154
+ if (!modelsWithRecord.has(modelName)) {
155
+ throw new Error(`Subscription "${subscriptionKey}" on database type ` +
156
+ `"${databaseType}" names model "${modelName}", which has no ` +
157
+ `[models.${modelName}] schema block. Declare the model's schema ` +
158
+ `so its change payload can be typed.`);
159
+ }
160
+ // `select` present (non-empty array) → Pick over those fields. Guard each
161
+ // field against the record's known members unless the record is the open
162
+ // `Record<string, unknown>` form (no declared/stamped members), where any
163
+ // key is valid.
164
+ let select = null;
165
+ if (Array.isArray(sub.select) && sub.select.length > 0) {
166
+ const members = recordFieldsByModel.get(modelName) ?? new Set();
167
+ const recordIsOpen = members.size === 0;
168
+ for (const field of sub.select) {
169
+ if (typeof field !== "string" || field === "") {
170
+ throw new Error(`Subscription "${subscriptionKey}" on database type ` +
171
+ `"${databaseType}" has a non-string select entry.`);
172
+ }
173
+ if (!recordIsOpen && !members.has(field)) {
174
+ throw new Error(`Subscription "${subscriptionKey}" on database type ` +
175
+ `"${databaseType}" selects field "${field}", which is not ` +
176
+ `declared on model "${modelName}". Add it to ` +
177
+ `[models.${modelName}] or remove it from select.`);
178
+ }
179
+ }
180
+ select = [...new Set(sub.select)].sort();
181
+ }
182
+ return { subscriptionKey, modelName, select };
183
+ });
184
+ result.sort((a, b) => a.subscriptionKey.localeCompare(b.subscriptionKey));
185
+ return result;
186
+ }
187
+ /**
188
+ * Classify a query op's static `projection` into how it narrows the record type
189
+ * (#1439). Kept private to the generator: the returned `{ kind, fields }` shape
190
+ * is what the template needs, and the field-guard / `id`-folding / sorting live
191
+ * here so the template stays a pure string-emitter.
192
+ *
193
+ * `recordFieldNames` is the set of members on the record interface (declared +
194
+ * stamped). A projected key that is not a member (a nested `"a.b"` path, or a
195
+ * field absent from the schema) makes the whole projection fall back to
196
+ * `Partial<M>` rather than emit an invalid `Pick`. Reused for a pipeline
197
+ * op's returning `query` step (#1437).
198
+ *
199
+ * Rules (projection values are validated server-side to be `1` or `0`, mixing
200
+ * rejected — codegen runs pre-push, so it still guards):
201
+ * - absent / not an object / empty `{}` → `full` (whole record `M`).
202
+ * - all `1` (inclusion), keys all known → `pick` (with `id` folded in,
203
+ * since inclusion always retains
204
+ * `id` at runtime).
205
+ * - all `0` (exclusion), keys all known → `omit` (over the excluded set
206
+ * minus `id`/`type`). The DO strips
207
+ * excluded fields server-side
208
+ * (#1493 / issue #1456), so the
209
+ * rows genuinely omit them and
210
+ * `Omit<M, …>` is truthful. Falls
211
+ * back to `partial` when the set is
212
+ * empty after dropping `id`/`type`.
213
+ * - mixed / dynamic / non-`1|0` / unknown / → `partial` (honest fallback).
214
+ * nested-key exclusion
215
+ */
216
+ function classifyProjection(projection, recordFieldNames) {
217
+ if (!projection ||
218
+ typeof projection !== "object" ||
219
+ Array.isArray(projection)) {
220
+ return { kind: "full" };
221
+ }
222
+ const entries = Object.entries(projection);
223
+ if (entries.length === 0)
224
+ return { kind: "full" };
225
+ let allInclude = true;
226
+ let allExclude = true;
227
+ const included = [];
228
+ const excluded = [];
229
+ for (const [field, val] of entries) {
230
+ if (val === 1) {
231
+ included.push(field);
232
+ allExclude = false;
233
+ }
234
+ else if (val === 0) {
235
+ excluded.push(field);
236
+ allInclude = false;
237
+ }
238
+ else {
239
+ // A dynamic (`$params.x`) or otherwise non-`1|0` value — can't resolve
240
+ // statically. Fall back rather than emit garbage.
241
+ allInclude = false;
242
+ allExclude = false;
243
+ }
244
+ }
245
+ if (allInclude && included.length > 0) {
246
+ if (!included.every((f) => recordFieldNames.has(f)))
247
+ return { kind: "partial" };
248
+ // Inclusion always retains `id` at runtime (the DO unshifts it), so fold it
249
+ // into the union when the model has an `id` field.
250
+ const fields = new Set(included);
251
+ if (recordFieldNames.has("id"))
252
+ fields.add("id");
253
+ return { kind: "pick", fields: [...fields].sort() };
254
+ }
255
+ if (allExclude && excluded.length > 0) {
256
+ // Static all-exclusion projection (e.g. `{ secret: 0 }`). Since #1493 (issue
257
+ // #1456) the DO's `_applyProjection` deletes excluded keys server-side, so
258
+ // the returned rows genuinely omit them and `Omit<M, …>` is the truthful,
259
+ // tighter type — reading an excluded field off the result is now a compile
260
+ // error rather than a value that silently existed.
261
+ //
262
+ // `id`/`type` are always returned (the validator 400s on excluding them and
263
+ // the DO force-keeps them), so they can never be in the Omit set — drop them
264
+ // first. Dropping them is defensive handling of invalid local/unpushed TOML,
265
+ // not a supported projection shape.
266
+ const omitKeys = excluded.filter((f) => f !== "id" && f !== "type");
267
+ // Every remaining key must be a known member so `Omit<M, union>` is valid TS.
268
+ // A nested `"a.b"` path or an off-schema key falls back to the honest
269
+ // `Partial`, and an exclusion set that is only `id`/`type` reduces to empty
270
+ // after filtering (op rejected server-side) → also `Partial`.
271
+ if (omitKeys.length > 0 && omitKeys.every((f) => recordFieldNames.has(f))) {
272
+ return { kind: "omit", fields: [...new Set(omitKeys)].sort() };
273
+ }
274
+ return { kind: "partial" };
275
+ }
276
+ return { kind: "partial" };
277
+ }
278
+ /**
279
+ * Resolve a pipeline op's `return`-step shape (#1437), naming-neutral (keyed on
280
+ * model name). Never throws — one odd op must not break codegen for the file.
281
+ */
282
+ function resolvePipelineReturn(definition, recordFieldsByModel, modelsWithRecord) {
283
+ if (!definition || typeof definition !== "object")
284
+ return { kind: "opaque" };
285
+ const def = definition;
286
+ const ret = def.return ?? "all";
287
+ if (ret === "all" || typeof ret !== "string")
288
+ return { kind: "opaque" };
289
+ if (!Array.isArray(def.steps))
290
+ return { kind: "opaque" };
291
+ const step = def.steps.find((s) => s && typeof s === "object" && s.name === ret);
292
+ if (!step)
293
+ return { kind: "opaque" };
294
+ if (step.type === "query") {
295
+ const modelName = typeof step.modelName === "string" ? step.modelName : undefined;
296
+ const resultModelName = modelName && modelsWithRecord.has(modelName) ? modelName : null;
297
+ const fieldNames = (modelName && recordFieldsByModel.get(modelName)) || new Set();
298
+ const projection = classifyProjection(step.projection, fieldNames);
299
+ return { kind: "query", modelName: resultModelName, projection };
300
+ }
301
+ if (step.type === "count")
302
+ return { kind: "count" };
303
+ if (step.type === "aggregate")
304
+ return { kind: "aggregate" };
305
+ return { kind: "opaque" };
306
+ }
307
+ /**
308
+ * Collect server-stamped field names from the parsed [type] config: timestamp
309
+ * field names + auto-populated field names. These render as OPTIONAL record
310
+ * props.
311
+ */
312
+ function collectStampedFields(typeConfig) {
313
+ const names = [];
314
+ const ts = typeConfig?.timestamps;
315
+ if (ts && typeof ts === "object") {
316
+ if (typeof ts.create === "string")
317
+ names.push(ts.create);
318
+ if (typeof ts.update === "string")
319
+ names.push(ts.update);
320
+ }
321
+ const apf = typeConfig?.autoPopulatedFields;
322
+ if (apf && typeof apf === "object") {
323
+ for (const key of Object.keys(apf))
324
+ names.push(key);
325
+ }
326
+ return [...new Set(names)];
327
+ }
328
+ /**
329
+ * Convert the normalized op `params` map into a sorted `ParamField[]`. See the
330
+ * original doc in `dbGenerator.ts`. `inferredArrayElements` (#1488) narrows a
331
+ * `$in`/`$nin`-bound untyped/object param to its array element type.
332
+ */
333
+ function paramsToFields(params, inferredArrayElements) {
334
+ if (!params || typeof params !== "object")
335
+ return [];
336
+ return Object.entries(params)
337
+ .map(([name, def]) => {
338
+ const rawType = def?.type;
339
+ const declaredType = rawType ?? "object";
340
+ const declaredItems = def?.items;
341
+ let type = declaredType;
342
+ let items = declaredItems;
343
+ const inferred = inferredArrayElements?.get(name);
344
+ if (declaredType === "object" && inferred !== undefined) {
345
+ type = "array";
346
+ items = inferred === "unknown" ? undefined : inferred;
347
+ }
348
+ return {
349
+ name,
350
+ type,
351
+ required: def?.required === true,
352
+ enum: def?.enum,
353
+ items,
354
+ };
355
+ })
356
+ .sort((a, b) => a.name.localeCompare(b.name));
357
+ }
358
+ /**
359
+ * Infer the scalar element type for op params consumed by a `$in`/`$nin`
360
+ * filter operator (#1488). See the original doc in `dbGenerator.ts` — moved
361
+ * here unchanged. Returns a map of param name → element TS type
362
+ * (`"unknown"` sentinel → widen to an untyped element).
363
+ */
364
+ export function inferArrayParamElementTypes(op, fieldTypesByModel) {
365
+ const result = new Map();
366
+ if (!op || typeof op !== "object")
367
+ return result;
368
+ const def = op.definition;
369
+ if (!def || typeof def !== "object")
370
+ return result;
371
+ const arrayElems = new Map();
372
+ const scalarRefs = new Set();
373
+ const addArrayElem = (param, elem) => {
374
+ let s = arrayElems.get(param);
375
+ if (!s) {
376
+ s = new Set();
377
+ arrayElems.set(param, s);
378
+ }
379
+ s.add(elem);
380
+ };
381
+ const paramRef = (v) => {
382
+ if (typeof v !== "string")
383
+ return null;
384
+ const m = v.match(/^\$params\.([A-Za-z0-9_]+)$/);
385
+ return m ? m[1] : null;
386
+ };
387
+ const elemTypeForField = (field, fields) => {
388
+ const ftype = fields?.get(field);
389
+ if (ftype === undefined)
390
+ return "unknown";
391
+ if (ftype === "stringset")
392
+ return "string";
393
+ const ts = tsTypeForDbFieldType(ftype);
394
+ return ts === "string" || ts === "number" || ts === "boolean"
395
+ ? ts
396
+ : "unknown";
397
+ };
398
+ const modelFields = (name) => typeof name === "string" ? fieldTypesByModel.get(name) : undefined;
399
+ const walkFilter = (filter, fields) => {
400
+ if (!filter || typeof filter !== "object" || Array.isArray(filter))
401
+ return;
402
+ for (const [key, value] of Object.entries(filter)) {
403
+ if (key === "$and" || key === "$or") {
404
+ if (Array.isArray(value)) {
405
+ for (const sub of value)
406
+ walkFilter(sub, fields);
407
+ }
408
+ continue;
409
+ }
410
+ if (key === "$not") {
411
+ walkFilter(value, fields);
412
+ continue;
413
+ }
414
+ if (value && typeof value === "object" && !Array.isArray(value)) {
415
+ for (const [operator, opVal] of Object.entries(value)) {
416
+ const p = paramRef(opVal);
417
+ if (!p)
418
+ continue;
419
+ if (operator === "$in" || operator === "$nin") {
420
+ addArrayElem(p, elemTypeForField(key, fields));
421
+ }
422
+ else {
423
+ scalarRefs.add(p);
424
+ }
425
+ }
426
+ }
427
+ else {
428
+ const p = paramRef(value);
429
+ if (p)
430
+ scalarRefs.add(p);
431
+ }
432
+ }
433
+ };
434
+ walkFilter(def.filter, modelFields(op.modelName));
435
+ if (def.source && typeof def.source === "object") {
436
+ walkFilter(def.source.filter, modelFields(def.source.modelName ?? op.modelName));
437
+ }
438
+ if (Array.isArray(def.operations)) {
439
+ for (const inner of def.operations) {
440
+ if (inner && typeof inner === "object") {
441
+ walkFilter(inner.filter, modelFields(inner.modelName ?? op.modelName));
442
+ }
443
+ }
444
+ }
445
+ if (Array.isArray(def.steps)) {
446
+ for (const step of def.steps) {
447
+ if (step && typeof step === "object") {
448
+ walkFilter(step.filter, modelFields(step.modelName ?? op.modelName));
449
+ }
450
+ }
451
+ }
452
+ for (const [param, elems] of arrayElems) {
453
+ if (scalarRefs.has(param) || elems.size !== 1) {
454
+ result.set(param, "unknown");
455
+ }
456
+ else {
457
+ result.set(param, [...elems][0]);
458
+ }
459
+ }
460
+ return result;
461
+ }
462
+ /**
463
+ * Infer which record fields are always supplied at create time from `save` op
464
+ * params (#842). See the original doc in `dbGenerator.ts` — moved here
465
+ * unchanged.
466
+ */
467
+ export function inferRequiredRecordFields(operations) {
468
+ const verdicts = new Map();
469
+ for (const op of operations ?? []) {
470
+ if (!op || op.type !== "mutation")
471
+ continue;
472
+ const innerOps = op.definition?.operations;
473
+ if (!Array.isArray(innerOps))
474
+ continue;
475
+ const params = op.params && typeof op.params === "object" ? op.params : {};
476
+ for (const inner of innerOps) {
477
+ if (!inner || inner.op !== "save")
478
+ continue;
479
+ const data = inner.data;
480
+ if (!data || typeof data !== "object" || Array.isArray(data))
481
+ continue;
482
+ const modelName = inner.modelName ?? op.modelName;
483
+ if (typeof modelName !== "string" || modelName === "")
484
+ continue;
485
+ let fieldVerdicts = verdicts.get(modelName);
486
+ if (!fieldVerdicts) {
487
+ fieldVerdicts = new Map();
488
+ verdicts.set(modelName, fieldVerdicts);
489
+ }
490
+ for (const [field, value] of Object.entries(data)) {
491
+ const match = typeof value === "string"
492
+ ? value.match(/^\$params\.([A-Za-z0-9_]+)$/)
493
+ : null;
494
+ if (!match) {
495
+ fieldVerdicts.set(field, false);
496
+ continue;
497
+ }
498
+ const paramName = match[1];
499
+ const required = params[paramName]?.required === true;
500
+ const prior = fieldVerdicts.get(field);
501
+ fieldVerdicts.set(field, prior === false ? false : required);
502
+ }
503
+ }
504
+ }
505
+ const result = new Map();
506
+ for (const [modelName, fieldVerdicts] of verdicts) {
507
+ const fields = new Set();
508
+ for (const [field, verdict] of fieldVerdicts) {
509
+ if (verdict)
510
+ fields.add(field);
511
+ }
512
+ if (fields.size > 0)
513
+ result.set(modelName, { fields });
514
+ }
515
+ return result;
516
+ }
517
+ //# sourceMappingURL=dbTypeIR.js.map