primitive-admin 1.0.54 → 1.0.56
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/assets/skill/skills/primitive-platform/SKILL.md +230 -2
- package/dist/bin/primitive.js +3 -1
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/apps.js +54 -2
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/database-types.js +4 -4
- package/dist/src/commands/database-types.js.map +1 -1
- package/dist/src/commands/databases.js +35 -14
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/documents.js +77 -0
- package/dist/src/commands/documents.js.map +1 -1
- package/dist/src/commands/guides.d.ts +15 -12
- package/dist/src/commands/guides.js +22 -15
- package/dist/src/commands/guides.js.map +1 -1
- package/dist/src/commands/metadata.js +48 -3
- package/dist/src/commands/metadata.js.map +1 -1
- package/dist/src/commands/scripts.d.ts +18 -0
- package/dist/src/commands/scripts.js +59 -5
- package/dist/src/commands/scripts.js.map +1 -1
- package/dist/src/commands/settings.d.ts +15 -0
- package/dist/src/commands/settings.js +102 -0
- package/dist/src/commands/settings.js.map +1 -0
- package/dist/src/commands/sync-app-settings.d.ts +115 -0
- package/dist/src/commands/sync-app-settings.js +379 -0
- package/dist/src/commands/sync-app-settings.js.map +1 -0
- package/dist/src/commands/sync.d.ts +134 -19
- package/dist/src/commands/sync.js +1949 -600
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/workflows.js +54 -15
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/api-client.d.ts +29 -1
- package/dist/src/lib/api-client.js +31 -2
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/app-settings-descriptor.d.ts +108 -0
- package/dist/src/lib/app-settings-descriptor.js +254 -0
- package/dist/src/lib/app-settings-descriptor.js.map +1 -0
- package/dist/src/lib/codegen-shared/generatedFiles.d.ts +36 -0
- package/dist/src/lib/codegen-shared/generatedFiles.js +67 -10
- package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -1
- package/dist/src/lib/codegen-shared/prettierStable.d.ts +262 -0
- package/dist/src/lib/codegen-shared/prettierStable.js +610 -0
- package/dist/src/lib/codegen-shared/prettierStable.js.map +1 -0
- package/dist/src/lib/db-codegen/dbGenerator.d.ts +11 -83
- package/dist/src/lib/db-codegen/dbGenerator.js +96 -556
- package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -1
- package/dist/src/lib/db-codegen/dbNaming.d.ts +20 -7
- package/dist/src/lib/db-codegen/dbNaming.js +31 -13
- package/dist/src/lib/db-codegen/dbNaming.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTemplates.d.ts +70 -4
- package/dist/src/lib/db-codegen/dbTemplates.js +244 -70
- package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTsTypes.d.ts +13 -22
- package/dist/src/lib/db-codegen/dbTsTypes.js +33 -28
- package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTypeIR.d.ts +146 -0
- package/dist/src/lib/db-codegen/dbTypeIR.js +517 -0
- package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -0
- package/dist/src/lib/db-codegen/generated-operation-def-descriptor.d.ts +112 -0
- package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js +211 -0
- package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js.map +1 -0
- package/dist/src/lib/generated-allowlist.js +7 -0
- package/dist/src/lib/generated-allowlist.js.map +1 -1
- package/dist/src/lib/platform-owned.d.ts +37 -0
- package/dist/src/lib/platform-owned.js +46 -0
- package/dist/src/lib/platform-owned.js.map +1 -0
- package/dist/src/lib/swift-codegen/dbGenerator.d.ts +100 -0
- package/dist/src/lib/swift-codegen/dbGenerator.js +682 -0
- package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -0
- package/dist/src/lib/swift-codegen/dbSwiftTypes.d.ts +42 -0
- package/dist/src/lib/swift-codegen/dbSwiftTypes.js +100 -0
- package/dist/src/lib/swift-codegen/dbSwiftTypes.js.map +1 -0
- package/dist/src/lib/swift-codegen/generator.d.ts +85 -0
- package/dist/src/lib/swift-codegen/generator.js +266 -0
- package/dist/src/lib/swift-codegen/generator.js.map +1 -0
- package/dist/src/lib/swift-codegen/schemaToSwift.d.ts +72 -0
- package/dist/src/lib/swift-codegen/schemaToSwift.js +644 -0
- package/dist/src/lib/swift-codegen/schemaToSwift.js.map +1 -0
- package/dist/src/lib/swift-codegen/siblingSymbols.d.ts +90 -0
- package/dist/src/lib/swift-codegen/siblingSymbols.js +152 -0
- package/dist/src/lib/swift-codegen/siblingSymbols.js.map +1 -0
- package/dist/src/lib/swift-codegen/swiftNaming.d.ts +85 -0
- package/dist/src/lib/swift-codegen/swiftNaming.js +198 -0
- package/dist/src/lib/swift-codegen/swiftNaming.js.map +1 -0
- package/dist/src/lib/sync-resource-types.d.ts +437 -0
- package/dist/src/lib/sync-resource-types.js +781 -0
- package/dist/src/lib/sync-resource-types.js.map +1 -0
- package/dist/src/lib/template.d.ts +1 -1
- package/dist/src/lib/template.js +6 -3
- package/dist/src/lib/template.js.map +1 -1
- package/dist/src/lib/toml-metadata-config.js +18 -0
- package/dist/src/lib/toml-metadata-config.js.map +1 -1
- package/dist/src/lib/workflow-apply.js +12 -2
- package/dist/src/lib/workflow-apply.js.map +1 -1
- package/dist/src/lib/workflow-codegen/generator.d.ts +27 -5
- package/dist/src/lib/workflow-codegen/generator.js +214 -68
- package/dist/src/lib/workflow-codegen/generator.js.map +1 -1
- package/dist/src/lib/workflow-codegen/invokerIR.d.ts +83 -0
- package/dist/src/lib/workflow-codegen/invokerIR.js +71 -0
- package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -0
- package/dist/src/lib/workflow-codegen/schemaToTs.d.ts +17 -1
- package/dist/src/lib/workflow-codegen/schemaToTs.js +59 -38
- package/dist/src/lib/workflow-codegen/schemaToTs.js.map +1 -1
- package/dist/src/lib/workflow-payload.d.ts +19 -0
- package/dist/src/lib/workflow-payload.js +21 -0
- package/dist/src/lib/workflow-payload.js.map +1 -1
- package/dist/src/lib/workflow-toml-validator.d.ts +30 -0
- package/dist/src/lib/workflow-toml-validator.js +206 -0
- package/dist/src/lib/workflow-toml-validator.js.map +1 -1
- package/dist/src/types/index.d.ts +1 -0
- package/package.json +5 -2
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `databases codegen --lang swift` orchestrator (issue #1547, Phase 4).
|
|
3
|
+
*
|
|
4
|
+
* The Swift counterpart of `db-codegen/dbGenerator.ts`. Reads the SAME synced
|
|
5
|
+
* `database-types/*.toml` and emits ONE `<type>.generated.swift` per database
|
|
6
|
+
* type. Each file carries the type's record structs (read shape), per-op params
|
|
7
|
+
* structs (write shape), per-op result typealiases, and a typed `<Type>Ops`
|
|
8
|
+
* factory over the additive generic `executeOperation` overload on
|
|
9
|
+
* `DatabasesAPI` (the only published SDK surface for this generator).
|
|
10
|
+
*
|
|
11
|
+
* Design decisions realized (issue #1547):
|
|
12
|
+
* - Operation + record metadata (which ops exist, their params, their result
|
|
13
|
+
* descriptor, and per-model fields) is read from the SHARED `describeDbType`
|
|
14
|
+
* IR — the SAME source the TS renderer (`db-codegen/dbGenerator.ts`) reads.
|
|
15
|
+
* The Swift renderer reconstructs none of it (binding item 5), so the two
|
|
16
|
+
* clients can never disagree on a type's surface.
|
|
17
|
+
* - The generic result envelopes (`DBQueryResult<T>`, `DBMutationResult`, …)
|
|
18
|
+
* live in the JsBaoClient SDK, NOT emitted per file — a Swift package
|
|
19
|
+
* compiles all generated files into one module, so a per-file copy would
|
|
20
|
+
* collide. Generated files reference the SDK envelopes (mirrors how Phase 3
|
|
21
|
+
* put the workflow result envelopes in the SDK).
|
|
22
|
+
* - Swift has no analog of TS `Pick<M, …>` / `Partial<M>`, so a query op with
|
|
23
|
+
* a `pick`/`partial` projection types its rows as the open `JSONValue`
|
|
24
|
+
* rather than a narrowed record (the same Fork-4 "no clean analog →
|
|
25
|
+
* JSONValue" fallback the emitter uses for un-descended schema nodes). A
|
|
26
|
+
* `full` projection over a known record types as `DBQueryResult<Record>`.
|
|
27
|
+
* - Disk lifecycle (write / `--check` / orphan cleanup) delegates to the
|
|
28
|
+
* shared `writeOrCheckGeneratedFiles`, scoped by an ownership BANNER
|
|
29
|
+
* distinct from both the workflow Swift banner and the model codegen's, so
|
|
30
|
+
* a shared Swift output dir stays safe.
|
|
31
|
+
*/
|
|
32
|
+
import * as path from "path";
|
|
33
|
+
import { writeOrCheckGeneratedFiles, } from "../codegen-shared/generatedFiles.js";
|
|
34
|
+
import { describeDbType, } from "../db-codegen/dbTypeIR.js";
|
|
35
|
+
import { resolveRecordInterfaceName, opParamsInterfaceName, opResultAliasName, } from "../db-codegen/dbNaming.js";
|
|
36
|
+
import { GENERATED_SWIFT_FILE_SUFFIX, SWIFT_BANNER_PREFIX } from "./generator.js";
|
|
37
|
+
import { assertNoRetainedSiblingCollision, assertNoForeignGeneratorSymbolCollision, } from "./siblingSymbols.js";
|
|
38
|
+
import { SwiftCodegenError } from "./schemaToSwift.js";
|
|
39
|
+
import { structTypeName, enumTypeName, enumCaseNames, swiftPropertyName, unescapeIdentifier, } from "./swiftNaming.js";
|
|
40
|
+
import { swiftTypeForDbFieldType, swiftTypeForDbParamType, } from "./dbSwiftTypes.js";
|
|
41
|
+
/**
|
|
42
|
+
* Banner prefix stamped on every CLI-emitted database `.generated.swift` file.
|
|
43
|
+
* Distinct from both the workflow Swift banner (`// AUTO-GENERATED FROM
|
|
44
|
+
* workflows/`) and the model codegen's (`// Generated by swift-bao-codegen`),
|
|
45
|
+
* so none of the three generators' stale-sweeps ever deletes another's files
|
|
46
|
+
* when they share an output dir.
|
|
47
|
+
*/
|
|
48
|
+
export const DB_SWIFT_BANNER_PREFIX = "// AUTO-GENERATED FROM database-types/";
|
|
49
|
+
/** Swift module the generated types + factory import. */
|
|
50
|
+
const CLIENT_MODULE = "JsBaoClient";
|
|
51
|
+
const INDENT = " ";
|
|
52
|
+
const CONFORMANCES = "Codable, Equatable, Sendable";
|
|
53
|
+
/** A Swift string literal (runtime call passes the op name verbatim). */
|
|
54
|
+
function swiftStringLiteral(s) {
|
|
55
|
+
const escaped = s
|
|
56
|
+
.replace(/\\/g, "\\\\")
|
|
57
|
+
.replace(/"/g, '\\"')
|
|
58
|
+
.replace(/\n/g, "\\n")
|
|
59
|
+
.replace(/\r/g, "\\r")
|
|
60
|
+
.replace(/\t/g, "\\t");
|
|
61
|
+
return `"${escaped}"`;
|
|
62
|
+
}
|
|
63
|
+
/** Swift record struct type name for a model (shared naming + Swift sanitize). */
|
|
64
|
+
export function dbRecordSwiftTypeName(modelName, className) {
|
|
65
|
+
return structTypeName(resolveRecordInterfaceName(modelName, className));
|
|
66
|
+
}
|
|
67
|
+
/** `<Op>Params` Swift struct name (shared PascalCase + Swift sanitize). */
|
|
68
|
+
function dbParamsSwiftTypeName(opName) {
|
|
69
|
+
return structTypeName(opParamsInterfaceName(opName));
|
|
70
|
+
}
|
|
71
|
+
/** `<Op>Result` Swift typealias name. */
|
|
72
|
+
function dbResultSwiftTypeName(opName) {
|
|
73
|
+
return structTypeName(opResultAliasName(opName));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Namespace `enum` name for a database type (issue #1687). Every symbol a type
|
|
77
|
+
* emits is nested under `public enum <Type>` (`securities-ref` →
|
|
78
|
+
* `SecuritiesRef`), so two DB types can never collide on a shared op symbol.
|
|
79
|
+
* Reuses the same `structTypeName` sanitizer (keyword suffixing + digit-leading
|
|
80
|
+
* `_` guard) the old `<Type>Ops` derivation used, so the namespace name is a
|
|
81
|
+
* legal Swift type identifier.
|
|
82
|
+
*/
|
|
83
|
+
export function dbNamespaceName(databaseType) {
|
|
84
|
+
return structTypeName(databaseType);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Swift standard-library / Foundation / `JsBaoClient` SDK type names that the
|
|
88
|
+
* generated `enum <Type>` body references UNQUALIFIED. If a database type's
|
|
89
|
+
* namespace enum takes one of these names, Swift name lookup inside the enum
|
|
90
|
+
* resolves the nested namespace first and shadows the real type — e.g. a
|
|
91
|
+
* `string` database emits `public enum String { … }`, and every `String`
|
|
92
|
+
* field, `CodingKeys` raw type, and string argument inside now resolves to the
|
|
93
|
+
* empty namespace enum instead of `Swift.String`, breaking `Codable` synthesis
|
|
94
|
+
* and rejecting string values (Codex [P1], #1687). The generated code never
|
|
95
|
+
* qualifies these references (`Swift.String`, `JsBaoClient.SortDirection`), so
|
|
96
|
+
* the only safe fix is to reject the namespace name at codegen time and have
|
|
97
|
+
* the user rename the database type — consistent with #1653's "clear
|
|
98
|
+
* codegen-time error, the user renames" decision.
|
|
99
|
+
*
|
|
100
|
+
* The set is the union of every type the emitter can reference inside the enum:
|
|
101
|
+
* the scalar/collection types from `dbSwiftTypes.ts`, the protocol conformances
|
|
102
|
+
* stamped on emitted structs/enums, and the SDK envelope + factory types.
|
|
103
|
+
*/
|
|
104
|
+
const RESERVED_NAMESPACE_NAMES = new Set([
|
|
105
|
+
// Swift stdlib scalars emitted for record fields / op params.
|
|
106
|
+
"String",
|
|
107
|
+
"Int",
|
|
108
|
+
"Double",
|
|
109
|
+
"Bool",
|
|
110
|
+
// Foundation types a field/param mapping could resolve to (defensive: the
|
|
111
|
+
// current mappings widen date → String, but keep these reserved so a future
|
|
112
|
+
// mapping can't be silently shadowed).
|
|
113
|
+
"Data",
|
|
114
|
+
"Date",
|
|
115
|
+
// Collection types behind the `[…]` / `?` sugar the emitter uses. Sugar isn't
|
|
116
|
+
// shadowed by a nested type, but an explicit reference would be — reserved as
|
|
117
|
+
// defense-in-depth, and a database type normalizing to one of these is a
|
|
118
|
+
// dangerous name regardless.
|
|
119
|
+
"Array",
|
|
120
|
+
"Dictionary",
|
|
121
|
+
"Optional",
|
|
122
|
+
"Set",
|
|
123
|
+
// Protocol conformances stamped on every emitted struct / nested enum.
|
|
124
|
+
"Codable",
|
|
125
|
+
"Decodable",
|
|
126
|
+
"Encodable",
|
|
127
|
+
"Equatable",
|
|
128
|
+
"Hashable",
|
|
129
|
+
"Sendable",
|
|
130
|
+
"CaseIterable",
|
|
131
|
+
"CodingKey",
|
|
132
|
+
// JsBaoClient SDK types the ops factory + result typealiases reference.
|
|
133
|
+
"JsBaoClient",
|
|
134
|
+
"JSONValue",
|
|
135
|
+
"SortDirection",
|
|
136
|
+
"DBQueryResult",
|
|
137
|
+
"DBMutationResult",
|
|
138
|
+
"DBCountResult",
|
|
139
|
+
"DBAggregateResult",
|
|
140
|
+
"DBApplyToQueryResult",
|
|
141
|
+
"DBPipelineResult",
|
|
142
|
+
]);
|
|
143
|
+
/**
|
|
144
|
+
* Validate that a database type's derived namespace enum name is safe to emit,
|
|
145
|
+
* and return it. Throws a named `SwiftCodegenError` (with a rename hint) for
|
|
146
|
+
* the whole class of unsafe names a server-accepted database type can produce
|
|
147
|
+
* (#1687):
|
|
148
|
+
*
|
|
149
|
+
* 1. **Degenerate `_`** — a type with no retained identifier characters
|
|
150
|
+
* (empty after sanitize, or separator/emoji/punctuation-only, e.g. `--`,
|
|
151
|
+
* `🎉`) normalizes to `_` via `structTypeName`. Swift reserves `_` as the
|
|
152
|
+
* wildcard and rejects `public enum _` (Codex [P2]).
|
|
153
|
+
* 2. **Stdlib/SDK shadowing** — a name in `RESERVED_NAMESPACE_NAMES` shadows
|
|
154
|
+
* a type the generated body references unqualified (Codex [P1]).
|
|
155
|
+
*
|
|
156
|
+
* Leading-digit residue (`123` → `_123`) and Swift reserved words (`self` →
|
|
157
|
+
* `Self_`, `any` → `Any_`) are already lifted into legal, non-shadowing
|
|
158
|
+
* identifiers by `structTypeName` (digit-leading `_` guard + keyword suffix),
|
|
159
|
+
* so they need no rejection here — only names that survive that sanitizer as
|
|
160
|
+
* `_` or as a real type identifier remain unsafe. The two in-run / retained-
|
|
161
|
+
* sibling namespace-NAME collision guards (two database types whose names
|
|
162
|
+
* normalize to the SAME enum) live in `generateDbSwiftTypes`; this check
|
|
163
|
+
* covers the single-type unsafe-name class.
|
|
164
|
+
*/
|
|
165
|
+
export function assertSafeNamespaceName(databaseType, sourceFile) {
|
|
166
|
+
const namespaceName = dbNamespaceName(databaseType);
|
|
167
|
+
if (namespaceName === "_") {
|
|
168
|
+
throw new SwiftCodegenError(`swift db codegen: database type "${databaseType}" has no letters or ` +
|
|
169
|
+
`digits to form a Swift type identifier, so it normalizes to the ` +
|
|
170
|
+
`reserved wildcard "_" — the compiler rejects "public enum _". Rename ` +
|
|
171
|
+
`the database type to include at least one letter.`, { sourceFile });
|
|
172
|
+
}
|
|
173
|
+
if (RESERVED_NAMESPACE_NAMES.has(namespaceName)) {
|
|
174
|
+
throw new SwiftCodegenError(`swift db codegen: database type "${databaseType}" normalizes to the ` +
|
|
175
|
+
`Swift namespace "public enum ${namespaceName}", which shadows the ` +
|
|
176
|
+
`standard-library / JsBaoClient SDK type "${namespaceName}" that the ` +
|
|
177
|
+
`generated code references unqualified (record fields, op results, or ` +
|
|
178
|
+
`the ops factory). Inside the enum "${namespaceName}" would resolve to ` +
|
|
179
|
+
`the empty namespace instead of the real type, breaking compilation. ` +
|
|
180
|
+
`Rename the database type so it does not normalize to "${namespaceName}".`, { sourceFile });
|
|
181
|
+
}
|
|
182
|
+
return namespaceName;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Nested factory struct name inside a DB-type namespace: `Orders.Ops` (issue
|
|
186
|
+
* #1687). Fixed leaf name — the enclosing `enum <Type>` supplies the
|
|
187
|
+
* qualification, so it no longer needs the `<Type>` prefix.
|
|
188
|
+
*/
|
|
189
|
+
const DB_OPS_STRUCT_NAME = "Ops";
|
|
190
|
+
/**
|
|
191
|
+
* Nested static factory func name: `Orders.ops(client, databaseId:)` (issue
|
|
192
|
+
* #1687). Replaces the old top-level free `<type>Ops(...)` function.
|
|
193
|
+
*/
|
|
194
|
+
const DB_OPS_FACTORY_NAME = "ops";
|
|
195
|
+
/**
|
|
196
|
+
* Render a nested `String`-raw enum for a field/param declaring `enum = [...]`.
|
|
197
|
+
* Matches the JSON-Schema emitter's + model codegen's nested-enum shape.
|
|
198
|
+
*/
|
|
199
|
+
function renderStringEnum(typeName, values, indent) {
|
|
200
|
+
const pad = INDENT.repeat(indent);
|
|
201
|
+
const inner = INDENT.repeat(indent + 1);
|
|
202
|
+
const cases = enumCaseNames(values);
|
|
203
|
+
const lines = [
|
|
204
|
+
`${pad}public enum ${typeName}: String, Codable, CaseIterable, Sendable {`,
|
|
205
|
+
];
|
|
206
|
+
for (const c of cases)
|
|
207
|
+
lines.push(`${inner}case ${c.name} = ${swiftStringLiteral(c.raw)}`);
|
|
208
|
+
lines.push(`${pad}}`);
|
|
209
|
+
return lines.join("\n");
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Render a Swift `struct` from resolved members + nested-enum declarations. A
|
|
213
|
+
* public memberwise initializer is always emitted (the synthesized one is not
|
|
214
|
+
* public across module boundaries); `CodingKeys` only when a wire key isn't a
|
|
215
|
+
* legal/idiomatic Swift identifier. No custom Codable is needed — DB records /
|
|
216
|
+
* params have no oneOf / open-object / required-nullable cases.
|
|
217
|
+
*/
|
|
218
|
+
function renderStruct(typeName, members, nestedEnumDecls) {
|
|
219
|
+
const b = INDENT;
|
|
220
|
+
const needsCodingKeys = members.some((m) => unescapeIdentifier(m.swiftName) !== m.wireKey);
|
|
221
|
+
const out = [`public struct ${typeName}: ${CONFORMANCES} {`];
|
|
222
|
+
for (const decl of nestedEnumDecls)
|
|
223
|
+
out.push(decl);
|
|
224
|
+
if (nestedEnumDecls.length > 0)
|
|
225
|
+
out.push("");
|
|
226
|
+
for (const m of members) {
|
|
227
|
+
out.push(`${b}public var ${m.swiftName}: ${m.type}${m.optional ? "?" : ""}`);
|
|
228
|
+
}
|
|
229
|
+
if (members.length > 0)
|
|
230
|
+
out.push("");
|
|
231
|
+
// Public memberwise initializer.
|
|
232
|
+
if (members.length === 0) {
|
|
233
|
+
out.push(`${b}public init() {}`);
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
out.push(`${b}public init(`);
|
|
237
|
+
members.forEach((m, i) => {
|
|
238
|
+
const type = `${m.type}${m.optional ? "?" : ""}`;
|
|
239
|
+
const def = m.optional ? " = nil" : "";
|
|
240
|
+
out.push(`${b}${b}${m.swiftName}: ${type}${def}${i < members.length - 1 ? "," : ""}`);
|
|
241
|
+
});
|
|
242
|
+
out.push(`${b}) {`);
|
|
243
|
+
for (const m of members)
|
|
244
|
+
out.push(`${b}${b}self.${m.swiftName} = ${m.swiftName}`);
|
|
245
|
+
out.push(`${b}}`);
|
|
246
|
+
}
|
|
247
|
+
if (needsCodingKeys) {
|
|
248
|
+
out.push("");
|
|
249
|
+
out.push(`${b}public enum CodingKeys: String, CodingKey {`);
|
|
250
|
+
for (const m of members) {
|
|
251
|
+
const bare = unescapeIdentifier(m.swiftName);
|
|
252
|
+
const rhs = bare !== m.wireKey ? ` = ${swiftStringLiteral(m.wireKey)}` : "";
|
|
253
|
+
out.push(`${b}${b}case ${m.swiftName}${rhs}`);
|
|
254
|
+
}
|
|
255
|
+
out.push(`${b}}`);
|
|
256
|
+
}
|
|
257
|
+
out.push(`}`);
|
|
258
|
+
return out.join("\n");
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Render one record model. Empty (no declared fields and no stamped fields) →
|
|
262
|
+
* an open typealias (`[String: JSONValue]`), mirroring the TS empty-record
|
|
263
|
+
* `Record<string, unknown>` — a Database-DO record is schemaless, so the empty
|
|
264
|
+
* shape stays open.
|
|
265
|
+
*/
|
|
266
|
+
function renderRecordStruct(typeName, fields, stampedFields) {
|
|
267
|
+
const members = [];
|
|
268
|
+
const nestedEnums = [];
|
|
269
|
+
const declared = new Set();
|
|
270
|
+
for (const f of fields) {
|
|
271
|
+
declared.add(f.name);
|
|
272
|
+
let type;
|
|
273
|
+
if (f.type === "string" && Array.isArray(f.enum) && f.enum.length > 0) {
|
|
274
|
+
const enumName = enumTypeName(f.name);
|
|
275
|
+
nestedEnums.push(renderStringEnum(enumName, f.enum, 1));
|
|
276
|
+
type = enumName;
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
type = swiftTypeForDbFieldType(f.type);
|
|
280
|
+
}
|
|
281
|
+
members.push({
|
|
282
|
+
wireKey: f.name,
|
|
283
|
+
swiftName: swiftPropertyName(f.name),
|
|
284
|
+
type,
|
|
285
|
+
optional: !f.required,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
// Server-stamped fields → optional String props if not already declared.
|
|
289
|
+
for (const stamped of stampedFields) {
|
|
290
|
+
if (declared.has(stamped))
|
|
291
|
+
continue;
|
|
292
|
+
declared.add(stamped);
|
|
293
|
+
members.push({
|
|
294
|
+
wireKey: stamped,
|
|
295
|
+
swiftName: swiftPropertyName(stamped),
|
|
296
|
+
type: "String",
|
|
297
|
+
optional: true,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
if (members.length === 0) {
|
|
301
|
+
return `public typealias ${typeName} = [String: JSONValue]`;
|
|
302
|
+
}
|
|
303
|
+
return renderStruct(typeName, members, nestedEnums);
|
|
304
|
+
}
|
|
305
|
+
/** Render one op-params struct (write shape). */
|
|
306
|
+
function renderParamsStruct(typeName, op) {
|
|
307
|
+
const members = [];
|
|
308
|
+
const nestedEnums = [];
|
|
309
|
+
for (const p of op.params) {
|
|
310
|
+
let type;
|
|
311
|
+
if (p.type === "string" && Array.isArray(p.enum) && p.enum.length > 0) {
|
|
312
|
+
const enumName = enumTypeName(p.name);
|
|
313
|
+
nestedEnums.push(renderStringEnum(enumName, p.enum, 1));
|
|
314
|
+
type = swiftTypeForDbParamType(p.type, enumName, p.items);
|
|
315
|
+
}
|
|
316
|
+
else if (p.type === "array" &&
|
|
317
|
+
p.items === "string" &&
|
|
318
|
+
Array.isArray(p.enum) &&
|
|
319
|
+
p.enum.length > 0) {
|
|
320
|
+
const enumName = enumTypeName(p.name);
|
|
321
|
+
nestedEnums.push(renderStringEnum(enumName, p.enum, 1));
|
|
322
|
+
type = swiftTypeForDbParamType(p.type, enumName, p.items);
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
type = swiftTypeForDbParamType(p.type, undefined, p.items);
|
|
326
|
+
}
|
|
327
|
+
members.push({
|
|
328
|
+
wireKey: p.name,
|
|
329
|
+
swiftName: swiftPropertyName(p.name),
|
|
330
|
+
type,
|
|
331
|
+
optional: !p.required,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
// A no-param op still gets an empty struct so the factory can construct `()`.
|
|
335
|
+
return renderStruct(typeName, members, nestedEnums);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Swift `DBQueryResult<…>` element for a query op / returning pipeline step.
|
|
339
|
+
* A known record with a `full` projection → `DBQueryResult<Record>`. A
|
|
340
|
+
* `pick`/`partial` projection has no Swift Pick/Partial analog → open rows
|
|
341
|
+
* (`DBQueryResult<JSONValue>`); a null record likewise.
|
|
342
|
+
*/
|
|
343
|
+
function queryResultSwift(recordTypeName, projection) {
|
|
344
|
+
if (recordTypeName === null)
|
|
345
|
+
return "DBQueryResult<JSONValue>";
|
|
346
|
+
const kind = projection?.kind ?? "full";
|
|
347
|
+
return kind === "full"
|
|
348
|
+
? `DBQueryResult<${recordTypeName}>`
|
|
349
|
+
: "DBQueryResult<JSONValue>";
|
|
350
|
+
}
|
|
351
|
+
/** Resolve the Swift result type expression for an op. */
|
|
352
|
+
function resultSwiftType(op, recordTypeByModel) {
|
|
353
|
+
const recordFor = (model) => model ? recordTypeByModel[model] ?? null : null;
|
|
354
|
+
switch (op.opType) {
|
|
355
|
+
case "query":
|
|
356
|
+
return queryResultSwift(recordFor(op.resultModelName), op.projection);
|
|
357
|
+
case "mutation":
|
|
358
|
+
return "DBMutationResult";
|
|
359
|
+
case "count":
|
|
360
|
+
return "DBCountResult";
|
|
361
|
+
case "aggregate":
|
|
362
|
+
return "DBAggregateResult";
|
|
363
|
+
case "applyToQuery":
|
|
364
|
+
return "DBApplyToQueryResult";
|
|
365
|
+
case "pipeline": {
|
|
366
|
+
const d = op.pipelineReturn ?? { kind: "opaque" };
|
|
367
|
+
switch (d.kind) {
|
|
368
|
+
case "query":
|
|
369
|
+
return queryResultSwift(recordFor(d.modelName), d.projection);
|
|
370
|
+
case "count":
|
|
371
|
+
return "DBCountResult";
|
|
372
|
+
case "aggregate":
|
|
373
|
+
return "DBAggregateResult";
|
|
374
|
+
case "opaque":
|
|
375
|
+
default:
|
|
376
|
+
return "DBPipelineResult";
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
default:
|
|
380
|
+
// Unknown op type → the opaque JSONValue (parity with the TS `unknown`).
|
|
381
|
+
return "JSONValue";
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Render the typed `Ops` factory nested under the type's namespace enum: a
|
|
386
|
+
* struct with one `async throws` method per op, each binding `<Op>Params` in
|
|
387
|
+
* and `<Op>Result` out over the generic `DatabasesAPI.executeOperation`
|
|
388
|
+
* overload, plus a static `ops(client, databaseId:)` factory on the namespace
|
|
389
|
+
* (issue #1687). Returns "" for a type with no operations. The emitted block is
|
|
390
|
+
* un-indented here; `renderDbTypeSwiftFile` wraps the whole per-type body in
|
|
391
|
+
* `public enum <Type> { … }` and indents it one level.
|
|
392
|
+
*/
|
|
393
|
+
function renderOpsFactory(databaseType, ops) {
|
|
394
|
+
if (ops.length === 0)
|
|
395
|
+
return "";
|
|
396
|
+
const namespaceName = dbNamespaceName(databaseType);
|
|
397
|
+
const b = INDENT;
|
|
398
|
+
const lines = [
|
|
399
|
+
`/// Typed accessor for \`${databaseType}\` operations. Obtain one with`,
|
|
400
|
+
`/// \`${namespaceName}.${DB_OPS_FACTORY_NAME}(client, databaseId:)\`.`,
|
|
401
|
+
`public struct ${DB_OPS_STRUCT_NAME}: Sendable {`,
|
|
402
|
+
`${b}public let client: JsBaoClient`,
|
|
403
|
+
`${b}public let databaseId: String`,
|
|
404
|
+
``,
|
|
405
|
+
`${b}public init(client: JsBaoClient, databaseId: String) {`,
|
|
406
|
+
`${b}${b}self.client = client`,
|
|
407
|
+
`${b}${b}self.databaseId = databaseId`,
|
|
408
|
+
`${b}}`,
|
|
409
|
+
];
|
|
410
|
+
for (const op of ops) {
|
|
411
|
+
const method = swiftPropertyName(op.opName);
|
|
412
|
+
const paramsType = dbParamsSwiftTypeName(op.opName);
|
|
413
|
+
const resultType = dbResultSwiftTypeName(op.opName);
|
|
414
|
+
const nameLiteral = swiftStringLiteral(op.opName);
|
|
415
|
+
// With params: `method(_ params:, options…)`. Without: options only, and
|
|
416
|
+
// the empty params struct is constructed for the call.
|
|
417
|
+
const paramLine = op.hasParams ? `${b}${b}_ params: ${paramsType},` : null;
|
|
418
|
+
const paramsArg = op.hasParams ? "params" : `${paramsType}()`;
|
|
419
|
+
lines.push(``, `${b}/// Execute the \`${op.opName}\` operation.`, `${b}public func ${method}(`);
|
|
420
|
+
if (paramLine)
|
|
421
|
+
lines.push(paramLine);
|
|
422
|
+
lines.push(`${b}${b}limit: Int? = nil,`, `${b}${b}cursor: String? = nil,`, `${b}${b}direction: SortDirection? = nil,`, `${b}${b}timing: Bool? = nil`, `${b}) async throws -> ${resultType} {`, `${b}${b}try await client.databases.executeOperation(`, `${b}${b}${b}databaseId: databaseId,`, `${b}${b}${b}name: ${nameLiteral},`, `${b}${b}${b}params: ${paramsArg},`, `${b}${b}${b}limit: limit,`, `${b}${b}${b}cursor: cursor,`, `${b}${b}${b}direction: direction,`, `${b}${b}${b}timing: timing`, `${b}${b})`, `${b}}`);
|
|
423
|
+
}
|
|
424
|
+
lines.push(`}`, ``);
|
|
425
|
+
lines.push(`/// Typed operations factory for the \`${databaseType}\` database type.`, `public static func ${DB_OPS_FACTORY_NAME}(_ client: JsBaoClient, databaseId: String) -> ${DB_OPS_STRUCT_NAME} {`, `${b}${DB_OPS_STRUCT_NAME}(client: client, databaseId: databaseId)`, `}`);
|
|
426
|
+
return lines.join("\n");
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Indent every non-empty line of a rendered Swift block by `levels` levels
|
|
430
|
+
* (blank lines stay blank, preserving struct-internal spacing). Used to nest a
|
|
431
|
+
* type's declarations one level inside its `enum <Type>` namespace (#1687).
|
|
432
|
+
*/
|
|
433
|
+
function indentSwiftLines(text, levels) {
|
|
434
|
+
const pad = INDENT.repeat(levels);
|
|
435
|
+
return text
|
|
436
|
+
.split("\n")
|
|
437
|
+
.map((line) => (line.length === 0 ? line : pad + line))
|
|
438
|
+
.join("\n");
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Render the full `<type>.generated.swift` content for one database-type TOML.
|
|
442
|
+
* Pure — exported so tests can assert on the string directly. Throws on a Swift
|
|
443
|
+
* record type-name collision between two models (mirrors the TS collision guard).
|
|
444
|
+
*/
|
|
445
|
+
export function renderDbTypeSwiftFile(input) {
|
|
446
|
+
const ir = describeDbType(input);
|
|
447
|
+
const sourceFile = `database-types/${ir.databaseType}.toml`;
|
|
448
|
+
// Reject an unsafe namespace enum name (empty/separator-only `_`, or a name
|
|
449
|
+
// that shadows a stdlib/SDK type the body references) before any rendering,
|
|
450
|
+
// with a clear rename hint (#1687). `structTypeName` already lifts leading
|
|
451
|
+
// digits and Swift keywords into legal identifiers, so only the degenerate
|
|
452
|
+
// and shadowing cases remain. The validated name is reused at emission below.
|
|
453
|
+
const namespaceName = assertSafeNamespaceName(ir.databaseType, sourceFile);
|
|
454
|
+
// Within-file collision guards (issue #1653). A single
|
|
455
|
+
// `<type>.generated.swift` compiles as one Swift file, so every TOP-LEVEL
|
|
456
|
+
// symbol it declares — record structs, per-op `<Op>Params` structs,
|
|
457
|
+
// `<Op>Result` typealiases, and (for a type with operations) the `<Type>Ops`
|
|
458
|
+
// struct plus its free-function factory — shares ONE namespace and must be
|
|
459
|
+
// unique. The earlier guards kept records and op-params in SEPARATE maps, so a
|
|
460
|
+
// record whose `class_name` resolves to `<Type>Ops` (or to an op's
|
|
461
|
+
// params/result name) slipped through uncaught (Codex [P2]). Track them all in
|
|
462
|
+
// one registry keyed on the emitted symbol.
|
|
463
|
+
//
|
|
464
|
+
// The `<Type>Ops` METHODS live in a SEPARATE (struct-member) namespace, and
|
|
465
|
+
// Swift permits overloading a method name as long as the parameter lists
|
|
466
|
+
// differ. So two ops that normalize to the same method name are tracked apart
|
|
467
|
+
// by their emitted overload SIGNATURE, not the bare method basename: `get.user`
|
|
468
|
+
// and `getuser` produce DISTINCT `<Op>Params` types (`GetUserParams` via
|
|
469
|
+
// `dbNaming.pascalCase`, which splits on `.`, vs `GetuserParams`) but the SAME
|
|
470
|
+
// `getuser` method name (via `swiftPropertyName`, which drops `.`). When both
|
|
471
|
+
// ops take params, each emits `getuser(_ params: <distinct type>, …)` — legal
|
|
472
|
+
// Swift overloads that compile, so the check must ALLOW them (issue #1704). A
|
|
473
|
+
// bare method-name check rejected them as a false positive. Two ops truly
|
|
474
|
+
// redeclare only when the method name AND the parameter signature match — e.g.
|
|
475
|
+
// two PARAM-LESS ops that both emit `getuser(limit:…)` (no `_ params:` at all).
|
|
476
|
+
// The params-type guard alone could never catch that case (Codex [P1]).
|
|
477
|
+
const recordTypeByModel = {};
|
|
478
|
+
const topLevelSymbol = new Map(); // symbol → owner description
|
|
479
|
+
const methodOwner = new Map(); // op method signature → opName
|
|
480
|
+
const registerTopLevel = (symbol, owner, schemaPath) => {
|
|
481
|
+
const prior = topLevelSymbol.get(symbol);
|
|
482
|
+
if (prior !== undefined && prior !== owner) {
|
|
483
|
+
throw new SwiftCodegenError(`swift db codegen: symbol collision in database type "${ir.databaseType}" — ` +
|
|
484
|
+
`${prior} and ${owner} both produce Swift symbol "${symbol}" in the ` +
|
|
485
|
+
`"${dbNamespaceName(ir.databaseType)}" namespace, ` +
|
|
486
|
+
`which redeclares when the file compiles. Rename one, or add a ` +
|
|
487
|
+
`class_name override, so the two differ after Swift normalization.`, { sourceFile, schemaPath });
|
|
488
|
+
}
|
|
489
|
+
topLevelSymbol.set(symbol, owner);
|
|
490
|
+
};
|
|
491
|
+
// Record structs.
|
|
492
|
+
for (const rec of ir.records) {
|
|
493
|
+
const typeName = dbRecordSwiftTypeName(rec.modelName, rec.className);
|
|
494
|
+
registerTopLevel(typeName, `record "${rec.modelName}"`, "models");
|
|
495
|
+
recordTypeByModel[rec.modelName] = typeName;
|
|
496
|
+
}
|
|
497
|
+
// Per-op params structs, result typealiases, and factory method names.
|
|
498
|
+
for (const op of ir.ops) {
|
|
499
|
+
registerTopLevel(dbParamsSwiftTypeName(op.opName), `operation "${op.opName}" params`, "operations");
|
|
500
|
+
registerTopLevel(dbResultSwiftTypeName(op.opName), `operation "${op.opName}" result`, "operations");
|
|
501
|
+
const method = swiftPropertyName(op.opName);
|
|
502
|
+
// Key on the emitted Swift overload signature, not the bare method name. All
|
|
503
|
+
// ops share the same trailing `limit`/`cursor`/`direction`/`timing` labels
|
|
504
|
+
// and types, so the only thing that distinguishes two same-named methods is
|
|
505
|
+
// the leading `_ params:` parameter: a param-taking op emits its distinct
|
|
506
|
+
// `<Op>Params` type, a param-less op emits none. Ops with distinct param
|
|
507
|
+
// structs are therefore legal overloads (issue #1704); only ops that match
|
|
508
|
+
// on BOTH the method name and this signature redeclare.
|
|
509
|
+
const signature = op.hasParams
|
|
510
|
+
? `${method}(${dbParamsSwiftTypeName(op.opName)})`
|
|
511
|
+
: `${method}()`;
|
|
512
|
+
const priorOp = methodOwner.get(signature);
|
|
513
|
+
if (priorOp !== undefined && priorOp !== op.opName) {
|
|
514
|
+
throw new SwiftCodegenError(`swift db codegen: operation method-signature collision in database type ` +
|
|
515
|
+
`"${ir.databaseType}" — operations "${priorOp}" and "${op.opName}" ` +
|
|
516
|
+
`both emit the Swift method "${method}" with the same parameter ` +
|
|
517
|
+
`signature in the "${dbNamespaceName(ir.databaseType)}.${DB_OPS_STRUCT_NAME}" factory, which redeclares when the file ` +
|
|
518
|
+
`compiles. Rename one ` +
|
|
519
|
+
`operation, or give them distinct parameters, so the two differ ` +
|
|
520
|
+
`after Swift normalization.`, { sourceFile, schemaPath: "operations" });
|
|
521
|
+
}
|
|
522
|
+
methodOwner.set(signature, op.opName);
|
|
523
|
+
}
|
|
524
|
+
// The nested `Ops` struct + its static `ops` factory (emitted only when the
|
|
525
|
+
// type has operations — see `renderOpsFactory`). Both live inside the type's
|
|
526
|
+
// `enum <Type>` namespace alongside the records/params/results, so a record
|
|
527
|
+
// whose name resolves to `Ops` collides here instead of at `swift build`.
|
|
528
|
+
if (ir.ops.length > 0) {
|
|
529
|
+
registerTopLevel(DB_OPS_STRUCT_NAME, `the \`${DB_OPS_STRUCT_NAME}\` operations struct`, "operations");
|
|
530
|
+
registerTopLevel(DB_OPS_FACTORY_NAME, `the \`${DB_OPS_FACTORY_NAME}\` operations factory`, "operations");
|
|
531
|
+
}
|
|
532
|
+
const banner = [
|
|
533
|
+
`// AUTO-GENERATED FROM database-types/${ir.databaseType}.toml — DO NOT EDIT.`,
|
|
534
|
+
"// Run `primitive databases codegen --lang swift` to regenerate.",
|
|
535
|
+
`// fingerprint: ${ir.fingerprint}`,
|
|
536
|
+
].join("\n");
|
|
537
|
+
const imports = ["import Foundation", `import ${CLIENT_MODULE}`].join("\n");
|
|
538
|
+
const bodyBlocks = [];
|
|
539
|
+
// Record structs, sorted by Swift type name for deterministic output.
|
|
540
|
+
const sortedRecords = [...ir.records].sort((a, b) => recordTypeByModel[a.modelName].localeCompare(recordTypeByModel[b.modelName]));
|
|
541
|
+
for (const rec of sortedRecords) {
|
|
542
|
+
bodyBlocks.push(renderRecordStruct(recordTypeByModel[rec.modelName], rec.fields, ir.stampedFields));
|
|
543
|
+
}
|
|
544
|
+
// Per-op params structs + result aliases (ops already sorted by op name).
|
|
545
|
+
for (const op of ir.ops) {
|
|
546
|
+
bodyBlocks.push(renderParamsStruct(dbParamsSwiftTypeName(op.opName), op));
|
|
547
|
+
bodyBlocks.push(`public typealias ${dbResultSwiftTypeName(op.opName)} = ${resultSwiftType(op, recordTypeByModel)}`);
|
|
548
|
+
}
|
|
549
|
+
const factory = renderOpsFactory(ir.databaseType, ir.ops);
|
|
550
|
+
if (factory)
|
|
551
|
+
bodyBlocks.push(factory);
|
|
552
|
+
// Nest every symbol under a caseless `public enum <Type>` namespace (#1687),
|
|
553
|
+
// so `Orders.ListParams` and `Invoices.ListParams` can never collide when all
|
|
554
|
+
// `.generated.swift` files compile into one module. An empty database type
|
|
555
|
+
// still emits `public enum <Type> {}` — stable, deterministic type presence
|
|
556
|
+
// (sponsor decision 2026-07-19). `namespaceName` was validated + derived at
|
|
557
|
+
// the top of this function via `assertSafeNamespaceName`.
|
|
558
|
+
const namespaced = bodyBlocks.length === 0
|
|
559
|
+
? `public enum ${namespaceName} {}`
|
|
560
|
+
: `public enum ${namespaceName} {\n${indentSwiftLines(bodyBlocks.join("\n\n"), 1)}\n}`;
|
|
561
|
+
return [banner, imports, namespaced].join("\n\n") + "\n";
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Run Swift database codegen end-to-end. One `<type>.generated.swift` per
|
|
565
|
+
* database type; delegates the disk lifecycle to the shared writer/checker with
|
|
566
|
+
* banner-scoped ownership so a shared Swift output dir stays safe.
|
|
567
|
+
*/
|
|
568
|
+
export async function generateDbSwiftTypes(options) {
|
|
569
|
+
const outAbs = path.resolve(options.outputDir);
|
|
570
|
+
// Run-scoped namespace registry: normalized namespace enum name → the
|
|
571
|
+
// database type that emitted it. Namespacing every symbol under
|
|
572
|
+
// `public enum <Type>` (issue #1687) makes cross-type symbol collisions
|
|
573
|
+
// structurally impossible — `Orders.ListParams` and `Invoices.ListParams`
|
|
574
|
+
// can never redeclare. The ONE collision that survives is two DB types whose
|
|
575
|
+
// NAMES normalize to the SAME namespace enum identifier (e.g. `my-type` and
|
|
576
|
+
// `my_type` both → `MyType`); they emit two `enum MyType` blocks that
|
|
577
|
+
// redeclare when the files compile into one module. Fork 3 (sponsor decision
|
|
578
|
+
// 2026-07-19): scope this guard to DB namespaces within the run, and drop the
|
|
579
|
+
// old cross-type OP-SYMBOL registry (that collision class no longer exists
|
|
580
|
+
// after namespacing).
|
|
581
|
+
//
|
|
582
|
+
// This in-run map only catches the namespace-name clash when BOTH colliding
|
|
583
|
+
// types are in `options.inputs` — i.e. a full run. A filtered / single-type
|
|
584
|
+
// run (`singleType: true`) has just the selected type in `inputs` and keeps
|
|
585
|
+
// its siblings on disk untouched (`fullOwnership: false`), so a retained
|
|
586
|
+
// `my_type.generated.swift` already declaring `public enum MyType` would
|
|
587
|
+
// never enter this map and would still redeclare at `swift build`. The
|
|
588
|
+
// retained-sibling guard below closes that residual for filtered runs — it
|
|
589
|
+
// compares the NAMESPACE NAME (not the old op symbols) against siblings'
|
|
590
|
+
// declared top-level symbols.
|
|
591
|
+
const namespaceOwner = new Map();
|
|
592
|
+
const filesToWrite = new Map();
|
|
593
|
+
for (const input of options.inputs) {
|
|
594
|
+
// renderDbTypeSwiftFile throws on the within-file record/op collisions
|
|
595
|
+
// (classes 2 + record guard) before we reach the namespace registry.
|
|
596
|
+
const content = renderDbTypeSwiftFile(input);
|
|
597
|
+
const namespace = dbNamespaceName(input.databaseType);
|
|
598
|
+
const prior = namespaceOwner.get(namespace);
|
|
599
|
+
if (prior !== undefined && prior !== input.databaseType) {
|
|
600
|
+
throw new SwiftCodegenError(`swift db codegen: namespace collision — database types "${prior}" and ` +
|
|
601
|
+
`"${input.databaseType}" both normalize to the Swift namespace ` +
|
|
602
|
+
`"${namespace}", so each emits a "public enum ${namespace}" that ` +
|
|
603
|
+
`redeclares when all .generated.swift files compile into one module. ` +
|
|
604
|
+
`Rename one database type so the two differ after Swift normalization.`, { sourceFile: `database-types/${input.databaseType}.toml` });
|
|
605
|
+
}
|
|
606
|
+
namespaceOwner.set(namespace, input.databaseType);
|
|
607
|
+
const filePath = path.join(outAbs, `${input.databaseType}${GENERATED_SWIFT_FILE_SUFFIX}`);
|
|
608
|
+
filesToWrite.set(filePath, content);
|
|
609
|
+
}
|
|
610
|
+
// Filtered-run retained-sibling namespace-name collision (issue #1687). A
|
|
611
|
+
// `singleType` run renders only the selected type; its sibling
|
|
612
|
+
// `.generated.swift` files stay on disk but never enter `namespaceOwner`
|
|
613
|
+
// above, so this run's `public enum <Type>` could still redeclare against a
|
|
614
|
+
// retained sibling that declares the SAME enum (e.g. this run emits
|
|
615
|
+
// `enum MyType` for `my-type` while `my_type.generated.swift` on disk already
|
|
616
|
+
// declares `enum MyType`). Each DB file nests everything under one
|
|
617
|
+
// `public enum <Type>`, so a sibling's ONLY top-level symbol IS its namespace
|
|
618
|
+
// name — comparing the run's emitted namespace names against the siblings'
|
|
619
|
+
// parsed top-level symbols is exactly the namespace-name check. `emitted` is
|
|
620
|
+
// the run's namespace-name → owning database type map; the run's own output
|
|
621
|
+
// paths are excluded, so a filtered re-run of the same type never
|
|
622
|
+
// self-collides. (Full runs are already covered by `namespaceOwner` above.)
|
|
623
|
+
if (options.singleType === true) {
|
|
624
|
+
await assertNoRetainedSiblingCollision({
|
|
625
|
+
outputDir: outAbs,
|
|
626
|
+
ownershipBanner: DB_SWIFT_BANNER_PREFIX,
|
|
627
|
+
suffix: GENERATED_SWIFT_FILE_SUFFIX,
|
|
628
|
+
emitted: namespaceOwner,
|
|
629
|
+
ownPaths: filesToWrite.keys(),
|
|
630
|
+
makeError: ({ symbol, owner, siblingStem }) => new SwiftCodegenError(`swift db codegen: namespace collision with a retained sibling — ` +
|
|
631
|
+
`database type "${owner}" (this run) and the existing ` +
|
|
632
|
+
`"${siblingStem}${GENERATED_SWIFT_FILE_SUFFIX}" both declare the ` +
|
|
633
|
+
`Swift namespace "public enum ${symbol}", which redeclares when all ` +
|
|
634
|
+
`.generated.swift files compile into one module. A filtered ` +
|
|
635
|
+
`(single-type) run does not regenerate siblings, so both files ` +
|
|
636
|
+
`coexist. Rename one database type so the two differ after Swift ` +
|
|
637
|
+
`normalization, or regenerate the whole output directory.`, { sourceFile: `database-types/${owner}.toml` }),
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
// Cross-generator namespace collision (issue #1857). A database type's
|
|
641
|
+
// namespace enum name can match a top-level symbol a WORKFLOW-generated file
|
|
642
|
+
// already declares in a shared output dir — e.g. db type `foo-input` emits
|
|
643
|
+
// `public enum FooInput` while workflow `foo` emits a top-level struct
|
|
644
|
+
// `FooInput`. Their filenames differ, so the foreign-banner WRITE guard below
|
|
645
|
+
// (path-only) never trips, and the retained-sibling guard above only scans
|
|
646
|
+
// DB-owned files. This scan reads the workflow-owned siblings' declared
|
|
647
|
+
// top-level symbols and throws if the run's namespace name collides with any.
|
|
648
|
+
// It runs on every db run (not just filtered), because a full db run does not
|
|
649
|
+
// regenerate workflow files — they coexist regardless of ordering.
|
|
650
|
+
await assertNoForeignGeneratorSymbolCollision({
|
|
651
|
+
outputDir: outAbs,
|
|
652
|
+
foreignBanner: SWIFT_BANNER_PREFIX,
|
|
653
|
+
suffix: GENERATED_SWIFT_FILE_SUFFIX,
|
|
654
|
+
emitted: namespaceOwner,
|
|
655
|
+
makeError: ({ symbol, owner, siblingStem }) => new SwiftCodegenError(`swift db codegen: namespace collision with a workflow-generated file — ` +
|
|
656
|
+
`database type "${owner}" emits the Swift namespace ` +
|
|
657
|
+
`"public enum ${symbol}", but the existing ` +
|
|
658
|
+
`"${siblingStem}${GENERATED_SWIFT_FILE_SUFFIX}" (owned by the workflow ` +
|
|
659
|
+
`Swift generator) already declares a top-level "${symbol}" in the same ` +
|
|
660
|
+
`output directory, which redeclares when all .generated.swift files ` +
|
|
661
|
+
`compile into one module. Rename the database type or the "${siblingStem}" ` +
|
|
662
|
+
`workflow so the two differ after Swift normalization, or give one ` +
|
|
663
|
+
`generator a separate --output directory.`, { sourceFile: `database-types/${owner}.toml` }),
|
|
664
|
+
});
|
|
665
|
+
return await writeOrCheckGeneratedFiles({
|
|
666
|
+
outputDir: outAbs,
|
|
667
|
+
filesToWrite,
|
|
668
|
+
check: options.check,
|
|
669
|
+
fullOwnership: options.singleType !== true,
|
|
670
|
+
generatedSuffix: GENERATED_SWIFT_FILE_SUFFIX,
|
|
671
|
+
ownershipBanner: DB_SWIFT_BANNER_PREFIX,
|
|
672
|
+
// Class 3b: never clobber a file owned by the workflow Swift generator.
|
|
673
|
+
foreignOwnershipBanners: [SWIFT_BANNER_PREFIX],
|
|
674
|
+
onForeignBannerCollision: ({ filePath, foreignBanner }) => new SwiftCodegenError(`swift codegen: output collision — this run would overwrite ` +
|
|
675
|
+
`"${path.basename(filePath)}" in the shared output directory, which ` +
|
|
676
|
+
`is already owned by a different generator (banner ` +
|
|
677
|
+
`"${foreignBanner.trim()}"). A database type and a workflow key with ` +
|
|
678
|
+
`the same name cannot share an output directory; give one generator a ` +
|
|
679
|
+
`separate --output directory.`),
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
//# sourceMappingURL=dbGenerator.js.map
|