primitive-admin 1.0.53 → 1.0.55

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 (173) hide show
  1. package/README.md +38 -10
  2. package/dist/bin/primitive.js +8 -0
  3. package/dist/bin/primitive.js.map +1 -1
  4. package/dist/src/commands/apps.js +54 -2
  5. package/dist/src/commands/apps.js.map +1 -1
  6. package/dist/src/commands/auth.js +16 -6
  7. package/dist/src/commands/auth.js.map +1 -1
  8. package/dist/src/commands/blob-buckets.js +84 -7
  9. package/dist/src/commands/blob-buckets.js.map +1 -1
  10. package/dist/src/commands/catalog.js +3 -3
  11. package/dist/src/commands/catalog.js.map +1 -1
  12. package/dist/src/commands/collections.js +12 -9
  13. package/dist/src/commands/collections.js.map +1 -1
  14. package/dist/src/commands/databases.js +220 -82
  15. package/dist/src/commands/databases.js.map +1 -1
  16. package/dist/src/commands/documents.js +77 -0
  17. package/dist/src/commands/documents.js.map +1 -1
  18. package/dist/src/commands/group-type-configs.js +1 -9
  19. package/dist/src/commands/group-type-configs.js.map +1 -1
  20. package/dist/src/commands/groups.js +12 -6
  21. package/dist/src/commands/groups.js.map +1 -1
  22. package/dist/src/commands/guides.d.ts +161 -0
  23. package/dist/src/commands/guides.js +502 -84
  24. package/dist/src/commands/guides.js.map +1 -1
  25. package/dist/src/commands/integrations.js +2 -2
  26. package/dist/src/commands/integrations.js.map +1 -1
  27. package/dist/src/commands/metadata.d.ts +2 -0
  28. package/dist/src/commands/metadata.js +344 -0
  29. package/dist/src/commands/metadata.js.map +1 -0
  30. package/dist/src/commands/prompts.js +2 -2
  31. package/dist/src/commands/prompts.js.map +1 -1
  32. package/dist/src/commands/rule-sets.d.ts +1 -0
  33. package/dist/src/commands/rule-sets.js +25 -3
  34. package/dist/src/commands/rule-sets.js.map +1 -1
  35. package/dist/src/commands/scripts.d.ts +2 -0
  36. package/dist/src/commands/scripts.js +667 -0
  37. package/dist/src/commands/scripts.js.map +1 -0
  38. package/dist/src/commands/settings.d.ts +15 -0
  39. package/dist/src/commands/settings.js +102 -0
  40. package/dist/src/commands/settings.js.map +1 -0
  41. package/dist/src/commands/sync-app-settings.d.ts +105 -0
  42. package/dist/src/commands/sync-app-settings.js +339 -0
  43. package/dist/src/commands/sync-app-settings.js.map +1 -0
  44. package/dist/src/commands/sync.d.ts +204 -9
  45. package/dist/src/commands/sync.js +1706 -371
  46. package/dist/src/commands/sync.js.map +1 -1
  47. package/dist/src/commands/vars.d.ts +8 -0
  48. package/dist/src/commands/vars.js +110 -0
  49. package/dist/src/commands/vars.js.map +1 -0
  50. package/dist/src/commands/webhooks.js +17 -3
  51. package/dist/src/commands/webhooks.js.map +1 -1
  52. package/dist/src/commands/workflows.d.ts +30 -0
  53. package/dist/src/commands/workflows.js +667 -59
  54. package/dist/src/commands/workflows.js.map +1 -1
  55. package/dist/src/lib/api-client.d.ts +122 -18
  56. package/dist/src/lib/api-client.js +131 -2
  57. package/dist/src/lib/api-client.js.map +1 -1
  58. package/dist/src/lib/app-settings-descriptor.d.ts +108 -0
  59. package/dist/src/lib/app-settings-descriptor.js +250 -0
  60. package/dist/src/lib/app-settings-descriptor.js.map +1 -0
  61. package/dist/src/lib/batch.d.ts +26 -0
  62. package/dist/src/lib/batch.js +32 -0
  63. package/dist/src/lib/batch.js.map +1 -0
  64. package/dist/src/lib/block-layout.d.ts +160 -0
  65. package/dist/src/lib/block-layout.js +451 -0
  66. package/dist/src/lib/block-layout.js.map +1 -0
  67. package/dist/src/lib/codegen-shared/generatedFiles.d.ts +76 -0
  68. package/dist/src/lib/codegen-shared/generatedFiles.js +144 -0
  69. package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -0
  70. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +68 -0
  71. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +168 -0
  72. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js.map +1 -0
  73. package/dist/src/lib/config-toml.d.ts +10 -0
  74. package/dist/src/lib/config-toml.js +42 -0
  75. package/dist/src/lib/config-toml.js.map +1 -0
  76. package/dist/src/lib/config.d.ts +16 -0
  77. package/dist/src/lib/config.js +41 -0
  78. package/dist/src/lib/config.js.map +1 -1
  79. package/dist/src/lib/constants.d.ts +9 -0
  80. package/dist/src/lib/constants.js +9 -0
  81. package/dist/src/lib/constants.js.map +1 -1
  82. package/dist/src/lib/csv.d.ts +1 -2
  83. package/dist/src/lib/csv.js +3 -8
  84. package/dist/src/lib/csv.js.map +1 -1
  85. package/dist/src/lib/db-codegen/dbGenerator.d.ts +21 -65
  86. package/dist/src/lib/db-codegen/dbGenerator.js +117 -321
  87. package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -1
  88. package/dist/src/lib/db-codegen/dbNaming.d.ts +37 -0
  89. package/dist/src/lib/db-codegen/dbNaming.js +70 -0
  90. package/dist/src/lib/db-codegen/dbNaming.js.map +1 -1
  91. package/dist/src/lib/db-codegen/dbTemplates.d.ts +165 -5
  92. package/dist/src/lib/db-codegen/dbTemplates.js +292 -16
  93. package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
  94. package/dist/src/lib/db-codegen/dbTsTypes.d.ts +7 -3
  95. package/dist/src/lib/db-codegen/dbTsTypes.js +23 -1
  96. package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -1
  97. package/dist/src/lib/db-codegen/dbTypeIR.d.ts +146 -0
  98. package/dist/src/lib/db-codegen/dbTypeIR.js +517 -0
  99. package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -0
  100. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.d.ts +101 -0
  101. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js +200 -0
  102. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js.map +1 -0
  103. package/dist/src/lib/generated-allowlist.js +43 -0
  104. package/dist/src/lib/generated-allowlist.js.map +1 -1
  105. package/dist/src/lib/init-config.js +2 -2
  106. package/dist/src/lib/init-config.js.map +1 -1
  107. package/dist/src/lib/output.d.ts +25 -5
  108. package/dist/src/lib/output.js +32 -4
  109. package/dist/src/lib/output.js.map +1 -1
  110. package/dist/src/lib/query-operators.d.ts +43 -0
  111. package/dist/src/lib/query-operators.js +80 -0
  112. package/dist/src/lib/query-operators.js.map +1 -0
  113. package/dist/src/lib/swift-codegen/dbGenerator.d.ts +68 -0
  114. package/dist/src/lib/swift-codegen/dbGenerator.js +380 -0
  115. package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -0
  116. package/dist/src/lib/swift-codegen/dbSwiftTypes.d.ts +42 -0
  117. package/dist/src/lib/swift-codegen/dbSwiftTypes.js +100 -0
  118. package/dist/src/lib/swift-codegen/dbSwiftTypes.js.map +1 -0
  119. package/dist/src/lib/swift-codegen/generator.d.ts +84 -0
  120. package/dist/src/lib/swift-codegen/generator.js +178 -0
  121. package/dist/src/lib/swift-codegen/generator.js.map +1 -0
  122. package/dist/src/lib/swift-codegen/schemaToSwift.d.ts +72 -0
  123. package/dist/src/lib/swift-codegen/schemaToSwift.js +644 -0
  124. package/dist/src/lib/swift-codegen/schemaToSwift.js.map +1 -0
  125. package/dist/src/lib/swift-codegen/swiftNaming.d.ts +85 -0
  126. package/dist/src/lib/swift-codegen/swiftNaming.js +198 -0
  127. package/dist/src/lib/swift-codegen/swiftNaming.js.map +1 -0
  128. package/dist/src/lib/sync-resource-types.d.ts +225 -0
  129. package/dist/src/lib/sync-resource-types.js +394 -0
  130. package/dist/src/lib/sync-resource-types.js.map +1 -0
  131. package/dist/src/lib/toml-database-config.d.ts +10 -19
  132. package/dist/src/lib/toml-database-config.js +28 -59
  133. package/dist/src/lib/toml-database-config.js.map +1 -1
  134. package/dist/src/lib/toml-metadata-config.d.ts +147 -0
  135. package/dist/src/lib/toml-metadata-config.js +463 -0
  136. package/dist/src/lib/toml-metadata-config.js.map +1 -0
  137. package/dist/src/lib/toml-native-form.d.ts +46 -0
  138. package/dist/src/lib/toml-native-form.js +78 -0
  139. package/dist/src/lib/toml-native-form.js.map +1 -0
  140. package/dist/src/lib/toml-params-validator.d.ts +34 -0
  141. package/dist/src/lib/toml-params-validator.js +118 -3
  142. package/dist/src/lib/toml-params-validator.js.map +1 -1
  143. package/dist/src/lib/workflow-apply.d.ts +86 -0
  144. package/dist/src/lib/workflow-apply.js +127 -0
  145. package/dist/src/lib/workflow-apply.js.map +1 -0
  146. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.d.ts +129 -0
  147. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.js +269 -0
  148. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.js.map +1 -0
  149. package/dist/src/lib/workflow-codegen/generator.d.ts +87 -0
  150. package/dist/src/lib/workflow-codegen/generator.js +290 -0
  151. package/dist/src/lib/workflow-codegen/generator.js.map +1 -0
  152. package/dist/src/lib/workflow-codegen/invokerIR.d.ts +85 -0
  153. package/dist/src/lib/workflow-codegen/invokerIR.js +71 -0
  154. package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -0
  155. package/dist/src/lib/workflow-codegen/naming.d.ts +33 -0
  156. package/dist/src/lib/workflow-codegen/naming.js +81 -0
  157. package/dist/src/lib/workflow-codegen/naming.js.map +1 -0
  158. package/dist/src/lib/workflow-codegen/schemaToTs.d.ts +64 -0
  159. package/dist/src/lib/workflow-codegen/schemaToTs.js +282 -0
  160. package/dist/src/lib/workflow-codegen/schemaToTs.js.map +1 -0
  161. package/dist/src/lib/workflow-fragments.js +3 -3
  162. package/dist/src/lib/workflow-fragments.js.map +1 -1
  163. package/dist/src/lib/workflow-payload.d.ts +68 -0
  164. package/dist/src/lib/workflow-payload.js +150 -0
  165. package/dist/src/lib/workflow-payload.js.map +1 -0
  166. package/dist/src/lib/workflow-toml-validator.d.ts +1 -1
  167. package/dist/src/lib/workflow-toml-validator.js +37 -1
  168. package/dist/src/lib/workflow-toml-validator.js.map +1 -1
  169. package/dist/src/types/index.d.ts +11 -0
  170. package/dist/src/validators.d.ts +4 -3
  171. package/dist/src/validators.js +6 -5
  172. package/dist/src/validators.js.map +1 -1
  173. package/package.json +7 -3
@@ -0,0 +1,644 @@
1
+ /**
2
+ * JSON-Schema → Swift `Codable` converter for `swift-codegen` (issue #1547,
3
+ * Phase 1). The Swift counterpart of `workflow-codegen/schemaToTs.ts`.
4
+ *
5
+ * Both emitters read the SAME drift-guarded interpretation of the JSON-Schema
6
+ * subset the workflow platform supports — the vendored `SchemaDescriptor`
7
+ * (`workflow-codegen/generated-schema-descriptor.ts`, a byte copy of the server
8
+ * validator). Only the emitted syntax differs, so a generated Swift type can
9
+ * never promise a shape the runtime validator rejects. See the design and
10
+ * maintainer decision on issue #1547.
11
+ *
12
+ * Where Swift diverges from TypeScript:
13
+ *
14
+ * - **Named types, not anonymous.** TS renders nested object/enum/`oneOf`
15
+ * types inline; Swift `Codable` needs a NAMED `struct`/`enum` for each, so
16
+ * this emitter declares nested types inside their enclosing type.
17
+ * - **Scalars** (Fork 4, maintainer-confirmed): `string`→`String`,
18
+ * `number`→`Double`, `integer`→`Int` (ergonomic divergence from TS
19
+ * `number`), `boolean`→`Bool`.
20
+ * - **Un-descended nodes** (`$ref`, `allOf`, `format`, tuple `items`,
21
+ * `patternProperties`, numeric constraints, multi-scalar non-null unions,
22
+ * mixed-type enums, open-object extra values) → `JSONValue`, the Swift
23
+ * analog of TS `unknown`.
24
+ * - **`never`** (an uninhabitable enum after the type gate) → a strict codegen
25
+ * error, since the field can never be constructed (Fork 4).
26
+ * - **Scalar `enum`, all strings** → a nested `enum … : String, Codable,
27
+ * CaseIterable, Sendable`, the same form the Swift model codegen emits.
28
+ * - **`oneOf` discriminated union** → an `enum` with associated values plus a
29
+ * generated `init(from:)`/`encode(to:)` keyed on the discriminator.
30
+ *
31
+ * Codable semantics adopted from the maintainer decision (binding item 4):
32
+ *
33
+ * - Every generated `struct` gets a generated **public memberwise
34
+ * initializer** (Swift's synthesized memberwise init is not `public` across
35
+ * module boundaries).
36
+ * - A **required-nullable** property (required, but its value may be `null`)
37
+ * is encoded with an EXPLICIT null (not omitted) so server-side
38
+ * required-key validation still sees the key; decode is lenient (a missing
39
+ * key decodes to `nil`).
40
+ * - An **open object** stores unknown keys in an `extra: [String: JSONValue]`
41
+ * catch-all via custom Codable that separates known keys from extras; a key
42
+ * that collides with a known property is owned by the known property (the
43
+ * extra copy is dropped on encode, and never captured on decode).
44
+ * - A `oneOf` branch `struct` CARRIES the discriminator as a single-case
45
+ * `String` enum property, so decoding a branch rejects a conflicting
46
+ * discriminator value, and encoding re-emits the tag from the branch.
47
+ *
48
+ * Errors are named (`SwiftCodegenError`) and carry the source file and schema
49
+ * path for diagnostics (binding item 6).
50
+ */
51
+ import { describeSchema, isType, } from "../workflow-codegen/generated-schema-descriptor.js";
52
+ import { schemaRejectsEmptyObject } from "../workflow-codegen/schemaToTs.js";
53
+ import { enumCaseIdentifier, enumCaseNames, enumTypeName, escapeIfReserved, structTypeName, swiftPropertyName, unescapeIdentifier, } from "./swiftNaming.js";
54
+ /** Re-exported so the invoker phases share ONE required-vs-optional decision. */
55
+ export { schemaRejectsEmptyObject };
56
+ /** Conformances every generated struct/enum promises (all satisfied by `JSONValue`). */
57
+ const CONFORMANCES = "Codable, Equatable, Sendable";
58
+ const ACCESS = "public";
59
+ const INDENT_UNIT = " ";
60
+ /** A named codegen error carrying the source file + schema path (binding 6). */
61
+ export class SwiftCodegenError extends Error {
62
+ sourceFile;
63
+ schemaPath;
64
+ constructor(message, opts) {
65
+ const where = [opts?.sourceFile, opts?.schemaPath]
66
+ .filter((s) => s)
67
+ .join(" ");
68
+ super(where ? `${message} (at ${where})` : message);
69
+ this.name = "SwiftCodegenError";
70
+ this.sourceFile = opts?.sourceFile;
71
+ this.schemaPath = opts?.schemaPath;
72
+ }
73
+ }
74
+ const SCALAR_SWIFT = {
75
+ string: "String",
76
+ number: "Double",
77
+ integer: "Int",
78
+ boolean: "Bool",
79
+ };
80
+ /** A Swift string literal with the escapes Swift needs. */
81
+ function swiftString(value) {
82
+ const escaped = value
83
+ .replace(/\\/g, "\\\\")
84
+ .replace(/"/g, '\\"')
85
+ .replace(/\n/g, "\\n")
86
+ .replace(/\r/g, "\\r")
87
+ .replace(/\t/g, "\\t")
88
+ .replace(/\0/g, "\\0");
89
+ return `"${escaped}"`;
90
+ }
91
+ /**
92
+ * Render a full Swift type declaration for a ROOT schema (e.g. a workflow's
93
+ * `<Key>Input`/`<Key>Output`), including every nested type it needs. Returns the
94
+ * Swift source for the type only — the CLI adds the import/banner (Phase 2).
95
+ */
96
+ export function renderSwiftRootType(rootTypeName, schema, ctx) {
97
+ const state = {
98
+ sourceFile: ctx?.sourceFile,
99
+ path: rootTypeName,
100
+ fail(message) {
101
+ throw new SwiftCodegenError(message, {
102
+ sourceFile: this.sourceFile,
103
+ schemaPath: this.path,
104
+ });
105
+ },
106
+ };
107
+ const d = describeSchema(schema);
108
+ // A qualifying `oneOf` becomes a root enum with associated values.
109
+ if (d.oneOf) {
110
+ if (d.oneOf.kind === "invalid") {
111
+ state.fail(`swift codegen: ${d.oneOf.reason}`);
112
+ }
113
+ return renderOneOfEnum(rootTypeName, d.oneOf, 0, state);
114
+ }
115
+ // A root scalar `enum` (all strings) becomes a top-level String enum.
116
+ const rootEnum = renderStringEnumIfApplicable(d, rootTypeName, 0, state);
117
+ if (rootEnum)
118
+ return rootEnum;
119
+ // A root object becomes a struct.
120
+ if (d.singularType === "object" && d.object) {
121
+ return renderStruct(rootTypeName, d.object, 0, state);
122
+ }
123
+ // Anything else (scalar, array, union, un-descended) is a typealias to the
124
+ // reference type. Nested type declarations (e.g. an array's element struct)
125
+ // are emitted before the alias.
126
+ const ref = renderTypeRef(schema, rootTypeName, 0, state);
127
+ const alias = `${ACCESS} typealias ${rootTypeName} = ${optionalize(ref)}`;
128
+ return ref.decls.length > 0 ? `${ref.decls.join("\n\n")}\n\n${alias}` : alias;
129
+ }
130
+ /** Apply `?` to a reference type when the node is nullable. */
131
+ function optionalize(ref) {
132
+ return ref.nullable ? `${ref.expr}?` : ref.expr;
133
+ }
134
+ /**
135
+ * Render a schema node as a Swift type reference, accumulating any nested type
136
+ * declarations. `nameHint` is the base name for a nested named type (usually the
137
+ * property name). `indent` is where the nested declarations are placed.
138
+ */
139
+ function renderTypeRef(schema, nameHint, indent, state) {
140
+ const d = describeSchema(schema);
141
+ // Discriminated union → a named enum type.
142
+ if (d.oneOf) {
143
+ if (d.oneOf.kind === "invalid") {
144
+ state.fail(`swift codegen: ${d.oneOf.reason}`);
145
+ }
146
+ const typeName = structTypeName(nameHint);
147
+ const decl = renderOneOfEnum(typeName, d.oneOf, indent, {
148
+ ...state,
149
+ path: `${state.path}.${nameHint}`,
150
+ });
151
+ return { expr: typeName, nullable: false, decls: [decl] };
152
+ }
153
+ // Scalar `enum`.
154
+ if (d.enumValues) {
155
+ return renderEnumRef(d, nameHint, indent, state);
156
+ }
157
+ // Type union (e.g. `["string","null"]`).
158
+ if (d.isUnion) {
159
+ return renderUnionRef(d);
160
+ }
161
+ // No usable single type, not an enum/union/oneOf → un-descended → JSONValue.
162
+ if (d.singularType === null) {
163
+ return { expr: "JSONValue", nullable: false, decls: [] };
164
+ }
165
+ if (d.singularType === "object") {
166
+ if (d.object) {
167
+ const typeName = structTypeName(nameHint);
168
+ const decl = renderStruct(typeName, d.object, indent, {
169
+ ...state,
170
+ path: `${state.path}.${nameHint}`,
171
+ });
172
+ return { expr: typeName, nullable: false, decls: [decl] };
173
+ }
174
+ // Open object with no declared properties → an untyped dictionary.
175
+ return { expr: "[String: JSONValue]", nullable: false, decls: [] };
176
+ }
177
+ if (d.singularType === "array") {
178
+ const items = d.array ? d.array.items : undefined;
179
+ // Missing or tuple `items` (array of schemas) is not descended → [JSONValue].
180
+ if (!items || typeof items !== "object" || Array.isArray(items)) {
181
+ return { expr: "[JSONValue]", nullable: false, decls: [] };
182
+ }
183
+ const elem = renderTypeRef(items, `${nameHint}Item`, indent, {
184
+ ...state,
185
+ path: `${state.path}.${nameHint}[]`,
186
+ });
187
+ return {
188
+ expr: `[${optionalize(elem)}]`,
189
+ nullable: false,
190
+ decls: elem.decls,
191
+ };
192
+ }
193
+ // `null` singular type, or an unknown/unmodeled type name → JSONValue.
194
+ const scalar = SCALAR_SWIFT[d.singularType];
195
+ if (scalar)
196
+ return { expr: scalar, nullable: false, decls: [] };
197
+ return {
198
+ expr: "JSONValue",
199
+ nullable: d.singularType === "null",
200
+ decls: [],
201
+ };
202
+ }
203
+ /**
204
+ * Render a scalar `enum` node. All-string members → a nested `String` enum;
205
+ * an empty reachable set → `never` (codegen error); anything else (mixed-type
206
+ * or non-string scalars) → `JSONValue` (Fork 4).
207
+ */
208
+ function renderEnumRef(d, nameHint, indent, state) {
209
+ const decl = renderStringEnumIfApplicable(d, enumTypeName(nameHint), indent, state);
210
+ if (decl) {
211
+ return { expr: enumTypeName(nameHint), nullable: false, decls: [decl] };
212
+ }
213
+ // Not an all-string enum: mixed-type or non-string scalar values fall back to
214
+ // JSONValue (faithful to TS `unknown`). A `null` member makes the node
215
+ // nullable, which JSONValue already covers.
216
+ const hasNull = (d.enumValues ?? []).includes(null);
217
+ return { expr: "JSONValue", nullable: hasNull, decls: [] };
218
+ }
219
+ /**
220
+ * If `d` is a scalar `enum` whose reachable members (after the type gate) are
221
+ * ALL strings, return a nested `String` enum declaration named `typeName`.
222
+ * Returns `null` when it is not an all-string enum. Throws on an uninhabitable
223
+ * (`never`) enum.
224
+ */
225
+ function renderStringEnumIfApplicable(d, typeName, indent, state) {
226
+ if (!d.enumValues)
227
+ return null;
228
+ const allowedTypes = d.types;
229
+ const candidates = allowedTypes.length > 0
230
+ ? d.enumValues.filter((v) => allowedTypes.some((t) => isType(v, t)))
231
+ : d.enumValues;
232
+ if (candidates.length === 0) {
233
+ // No enum member satisfies the declared type gate: the runtime rejects
234
+ // every value. This is `never` on the TS side; a Swift field that can never
235
+ // be constructed is a strict codegen error (Fork 4).
236
+ state.fail("swift codegen: enum has no value satisfying its declared type (uninhabitable / never)");
237
+ }
238
+ const allStrings = candidates.every((v) => typeof v === "string");
239
+ if (!allStrings)
240
+ return null;
241
+ const cases = enumCaseNames(candidates);
242
+ const pad = INDENT_UNIT.repeat(indent);
243
+ const inner = INDENT_UNIT.repeat(indent + 1);
244
+ const lines = [];
245
+ // Conformance list matches the Swift model codegen's nested enums
246
+ // (`String, Codable, CaseIterable, Sendable`). A String-raw enum is implicitly
247
+ // Equatable, so an enclosing struct's synthesized Equatable still works.
248
+ lines.push(`${pad}${ACCESS} enum ${typeName}: String, Codable, CaseIterable, Sendable {`);
249
+ for (const c of cases) {
250
+ lines.push(`${inner}case ${c.name} = ${swiftString(c.raw)}`);
251
+ }
252
+ lines.push(`${pad}}`);
253
+ return lines.join("\n");
254
+ }
255
+ /**
256
+ * Render a type union (`type: [...]`). A single non-null scalar (optionally with
257
+ * `null`) → that scalar (nullable if `null` present). Multiple distinct scalars,
258
+ * or any object/array member → `JSONValue` (no untagged union in Swift).
259
+ */
260
+ function renderUnionRef(d) {
261
+ const hasNull = d.types.includes("null");
262
+ const nonNull = d.types.filter((t) => t !== "null");
263
+ const mapped = [];
264
+ for (const t of nonNull) {
265
+ const s = SCALAR_SWIFT[t];
266
+ if (!s) {
267
+ // object / array / unknown member → not representable as a Swift union.
268
+ return { expr: "JSONValue", nullable: hasNull, decls: [] };
269
+ }
270
+ if (!mapped.includes(s))
271
+ mapped.push(s);
272
+ }
273
+ if (mapped.length === 0) {
274
+ // `["null"]` only → JSONValue (which includes null).
275
+ return { expr: "JSONValue", nullable: true, decls: [] };
276
+ }
277
+ if (mapped.length === 1) {
278
+ return { expr: mapped[0], nullable: hasNull, decls: [] };
279
+ }
280
+ // Multi-scalar non-null union (e.g. ["string","number"]) → JSONValue (Fork 4).
281
+ return { expr: "JSONValue", nullable: hasNull, decls: [] };
282
+ }
283
+ /**
284
+ * Render an object descriptor as a Swift `struct`. `required[]` drives
285
+ * optionality; an open object gains an `extra: [String: JSONValue]` catch-all.
286
+ * Emits a public memberwise initializer always, `CodingKeys` when a wire key
287
+ * isn't its Swift name (or when custom Codable is needed), and custom
288
+ * `init(from:)`/`encode(to:)` when required-nullable properties or the
289
+ * open-object catch-all require it.
290
+ */
291
+ function renderStruct(typeName, objectDesc, indent, state, forcedRequired = []) {
292
+ const { properties, required, additionalPropertiesClosed } = objectDesc;
293
+ const requiredSet = new Set([
294
+ ...(Array.isArray(required)
295
+ ? required.filter((k) => typeof k === "string")
296
+ : []),
297
+ ...forcedRequired,
298
+ ]);
299
+ const isOpen = !additionalPropertiesClosed;
300
+ const nestedDecls = [];
301
+ const usedTypeNames = new Set();
302
+ const props = [];
303
+ const usedSwiftNames = new Set();
304
+ const recordName = (name) => {
305
+ const bare = unescapeIdentifier(name);
306
+ if (usedSwiftNames.has(bare)) {
307
+ state.fail(`swift codegen: two properties map to the same Swift name \`${bare}\``);
308
+ }
309
+ usedSwiftNames.add(bare);
310
+ };
311
+ for (const wireKey of Object.keys(properties)) {
312
+ const ref = renderTypeRef(properties[wireKey], wireKey, indent + 1, {
313
+ ...state,
314
+ path: `${state.path}.${wireKey}`,
315
+ });
316
+ for (const decl of ref.decls) {
317
+ // Nested type names live in the struct body; a collision is a codegen error.
318
+ const declName = declTypeName(decl);
319
+ if (declName) {
320
+ if (usedTypeNames.has(declName)) {
321
+ state.fail(`swift codegen: two nested types resolve to the same name \`${declName}\``);
322
+ }
323
+ usedTypeNames.add(declName);
324
+ }
325
+ nestedDecls.push(decl);
326
+ }
327
+ const swiftName = swiftPropertyName(wireKey);
328
+ recordName(swiftName);
329
+ const isRequired = requiredSet.has(wireKey);
330
+ props.push({
331
+ wireKey,
332
+ swiftName,
333
+ type: ref.expr,
334
+ optionalType: !isRequired || ref.nullable,
335
+ requiredNullable: isRequired && ref.nullable,
336
+ requiredNonNull: isRequired && !ref.nullable,
337
+ });
338
+ }
339
+ // A key listed in `required` but absent from `properties` has no child schema,
340
+ // yet the validator still rejects an object missing it. Emit it as a required
341
+ // `JSONValue` member so the generated type demands the key too.
342
+ for (const wireKey of requiredSet) {
343
+ if (Object.prototype.hasOwnProperty.call(properties, wireKey))
344
+ continue;
345
+ const swiftName = swiftPropertyName(wireKey);
346
+ recordName(swiftName);
347
+ props.push({
348
+ wireKey,
349
+ swiftName,
350
+ type: "JSONValue",
351
+ optionalType: false,
352
+ requiredNullable: false,
353
+ requiredNonNull: true,
354
+ });
355
+ }
356
+ // An open object gains a catch-all stored property. Its Swift name must not
357
+ // collide with a modelled property (a wire key literally named `extra` is
358
+ // plausible), so pick the first free candidate and reserve it. If every
359
+ // candidate is taken, fail with a named error rather than emit a struct with
360
+ // two `var extra` declarations that will not compile.
361
+ let catchAllName = "";
362
+ if (isOpen) {
363
+ const candidates = ["extra", "additionalProperties", "extraProperties"];
364
+ catchAllName = candidates.find((c) => !usedSwiftNames.has(c)) ?? "";
365
+ if (catchAllName === "") {
366
+ state.fail(`swift codegen: cannot name the open-object catch-all for \`${typeName}\` — ` +
367
+ `properties already use every candidate name (${candidates.join(", ")})`);
368
+ }
369
+ usedSwiftNames.add(catchAllName);
370
+ }
371
+ const needsCustomCodable = isOpen || props.some((p) => p.requiredNullable);
372
+ const needsCodingKeys = needsCustomCodable ||
373
+ props.some((p) => unescapeIdentifier(p.swiftName) !== p.wireKey);
374
+ const pad = INDENT_UNIT.repeat(indent);
375
+ const b = INDENT_UNIT.repeat(indent + 1);
376
+ const out = [];
377
+ out.push(`${pad}${ACCESS} struct ${typeName}: ${CONFORMANCES} {`);
378
+ // Nested type declarations first (already rendered at indent + 1).
379
+ for (const decl of nestedDecls) {
380
+ out.push(decl);
381
+ }
382
+ if (nestedDecls.length > 0)
383
+ out.push("");
384
+ // Stored properties.
385
+ for (const p of props) {
386
+ out.push(`${b}${ACCESS} var ${p.swiftName}: ${p.type}${p.optionalType ? "?" : ""}`);
387
+ }
388
+ if (isOpen) {
389
+ out.push(`${b}/// Extra keys the schema does not model, preserved round-trip.`);
390
+ out.push(`${b}${ACCESS} var ${catchAllName}: [String: JSONValue]`);
391
+ }
392
+ out.push("");
393
+ // Public memberwise initializer (synthesized memberwise init is not public
394
+ // across module boundaries — binding item 4).
395
+ out.push(renderMemberwiseInit(props, isOpen, catchAllName, indent + 1));
396
+ // CodingKeys.
397
+ if (needsCodingKeys) {
398
+ out.push("");
399
+ out.push(renderCodingKeys(props, indent + 1));
400
+ }
401
+ // Custom Codable when required-nullable or open-object catch-all needs it.
402
+ if (needsCustomCodable) {
403
+ out.push("");
404
+ out.push(renderCustomDecodeEncode(props, isOpen, catchAllName, indent + 1));
405
+ }
406
+ out.push(`${pad}}`);
407
+ return out.join("\n");
408
+ }
409
+ function renderMemberwiseInit(props, isOpen, catchAllName, indent) {
410
+ const pad = INDENT_UNIT.repeat(indent);
411
+ const body = INDENT_UNIT.repeat(indent + 1);
412
+ const params = [];
413
+ for (const p of props) {
414
+ const type = `${p.type}${p.optionalType ? "?" : ""}`;
415
+ // Optional-typed params default to nil for ergonomics.
416
+ params.push(`${p.swiftName}: ${type}${p.optionalType ? " = nil" : ""}`);
417
+ }
418
+ if (isOpen)
419
+ params.push(`${catchAllName}: [String: JSONValue] = [:]`);
420
+ const lines = [];
421
+ if (params.length === 0) {
422
+ lines.push(`${pad}${ACCESS} init() {}`);
423
+ return lines.join("\n");
424
+ }
425
+ lines.push(`${pad}${ACCESS} init(`);
426
+ params.forEach((param, i) => {
427
+ lines.push(`${body}${param}${i < params.length - 1 ? "," : ""}`);
428
+ });
429
+ lines.push(`${pad}) {`);
430
+ for (const p of props) {
431
+ lines.push(`${body}self.${p.swiftName} = ${p.swiftName}`);
432
+ }
433
+ if (isOpen)
434
+ lines.push(`${body}self.${catchAllName} = ${catchAllName}`);
435
+ lines.push(`${pad}}`);
436
+ return lines.join("\n");
437
+ }
438
+ function renderCodingKeys(props, indent) {
439
+ const pad = INDENT_UNIT.repeat(indent);
440
+ const body = INDENT_UNIT.repeat(indent + 1);
441
+ const lines = [];
442
+ lines.push(`${pad}${ACCESS} enum CodingKeys: String, CodingKey {`);
443
+ for (const p of props) {
444
+ const bare = unescapeIdentifier(p.swiftName);
445
+ const rhs = bare !== p.wireKey ? ` = ${swiftString(p.wireKey)}` : "";
446
+ lines.push(`${body}case ${p.swiftName}${rhs}`);
447
+ }
448
+ lines.push(`${pad}}`);
449
+ return lines.join("\n");
450
+ }
451
+ /**
452
+ * Custom `init(from:)`/`encode(to:)` for a struct that needs explicit-null
453
+ * encoding (required-nullable) or an open-object `extra` catch-all.
454
+ */
455
+ function renderCustomDecodeEncode(props, isOpen, catchAllName, indent) {
456
+ const pad = INDENT_UNIT.repeat(indent);
457
+ const b = INDENT_UNIT.repeat(indent + 1);
458
+ const b2 = INDENT_UNIT.repeat(indent + 2);
459
+ const lines = [];
460
+ // Decode.
461
+ lines.push(`${pad}${ACCESS} init(from decoder: Decoder) throws {`);
462
+ lines.push(`${b}let container = try decoder.container(keyedBy: CodingKeys.self)`);
463
+ for (const p of props) {
464
+ if (p.requiredNonNull) {
465
+ lines.push(`${b}self.${p.swiftName} = try container.decode(${p.type}.self, forKey: .${p.swiftName})`);
466
+ }
467
+ else {
468
+ // Optional or required-nullable: lenient decode (a missing key → nil).
469
+ lines.push(`${b}self.${p.swiftName} = try container.decodeIfPresent(${p.type}.self, forKey: .${p.swiftName})`);
470
+ }
471
+ }
472
+ if (isOpen) {
473
+ lines.push(`${b}let dynamic = try decoder.container(keyedBy: DynamicCodingKey.self)`);
474
+ lines.push(`${b}var collected: [String: JSONValue] = [:]`);
475
+ lines.push(`${b}for key in dynamic.allKeys where !Self.knownKeys.contains(key.stringValue) {`);
476
+ lines.push(`${b2}collected[key.stringValue] = try dynamic.decode(JSONValue.self, forKey: key)`);
477
+ lines.push(`${b}}`);
478
+ lines.push(`${b}self.${catchAllName} = collected`);
479
+ }
480
+ lines.push(`${pad}}`);
481
+ lines.push("");
482
+ // Encode.
483
+ lines.push(`${pad}${ACCESS} func encode(to encoder: Encoder) throws {`);
484
+ lines.push(`${b}var container = encoder.container(keyedBy: CodingKeys.self)`);
485
+ for (const p of props) {
486
+ if (p.requiredNonNull || p.requiredNullable) {
487
+ // Required: always encode the key. For required-nullable this writes an
488
+ // EXPLICIT null when the value is nil (so server required-key validation
489
+ // still sees the key).
490
+ lines.push(`${b}try container.encode(${p.swiftName}, forKey: .${p.swiftName})`);
491
+ }
492
+ else {
493
+ // Pure optional: omit the key when nil.
494
+ lines.push(`${b}try container.encodeIfPresent(${p.swiftName}, forKey: .${p.swiftName})`);
495
+ }
496
+ }
497
+ if (isOpen) {
498
+ lines.push(`${b}var dynamic = encoder.container(keyedBy: DynamicCodingKey.self)`);
499
+ lines.push(`${b}for (key, value) in ${catchAllName} where !Self.knownKeys.contains(key) {`);
500
+ lines.push(`${b2}try dynamic.encode(value, forKey: DynamicCodingKey(stringValue: key))`);
501
+ lines.push(`${b}}`);
502
+ }
503
+ lines.push(`${pad}}`);
504
+ if (isOpen) {
505
+ const wireKeys = props.map((p) => swiftString(p.wireKey)).join(", ");
506
+ lines.push("");
507
+ lines.push(`${pad}private static let knownKeys: Set<String> = [${wireKeys}]`);
508
+ lines.push("");
509
+ lines.push(`${pad}private struct DynamicCodingKey: CodingKey {`);
510
+ lines.push(`${b}var stringValue: String`);
511
+ lines.push(`${b}var intValue: Int? { nil }`);
512
+ lines.push(`${b}init(stringValue: String) { self.stringValue = stringValue }`);
513
+ lines.push(`${b}init?(intValue: Int) { nil }`);
514
+ lines.push(`${pad}}`);
515
+ }
516
+ return lines.join("\n");
517
+ }
518
+ /**
519
+ * Render a valid `oneOf` discriminated union as a Swift `enum` with associated
520
+ * values plus a generated `init(from:)`/`encode(to:)`. Each branch `struct`
521
+ * carries the discriminator (forced required) as a single-case `String` enum, so
522
+ * decoding a branch rejects a conflicting discriminator value and encoding
523
+ * re-emits the tag (binding item 4).
524
+ */
525
+ function renderOneOfEnum(typeName, oneOf, indent, state) {
526
+ const pad = INDENT_UNIT.repeat(indent);
527
+ const b = INDENT_UNIT.repeat(indent + 1);
528
+ const b2 = INDENT_UNIT.repeat(indent + 2);
529
+ const b3 = INDENT_UNIT.repeat(indent + 3);
530
+ const { discriminant, members } = oneOf;
531
+ // Assign each branch a case name (deduped) and a branch struct type name.
532
+ const usedCaseNames = new Map();
533
+ const usedBranchTypes = new Set();
534
+ const branches = members.map((m) => {
535
+ const valueStr = String(m.discriminantValue);
536
+ // Case name derived from the discriminator value, deduped. Dedup on the
537
+ // UN-escaped base and re-escape once, so a reserved-word discriminator
538
+ // (`default`) that collides gets `default_2`, not the uncompilable
539
+ // `` `default`_2 `` (suffix outside the backticks).
540
+ const bareCase = unescapeIdentifier(enumCaseIdentifier(valueStr));
541
+ const count = usedCaseNames.get(bareCase) ?? 0;
542
+ usedCaseNames.set(bareCase, count + 1);
543
+ const caseName = escapeIfReserved(count === 0 ? bareCase : `${bareCase}_${count + 1}`);
544
+ let branchType = structTypeName(`${valueStr}Branch`);
545
+ while (usedBranchTypes.has(branchType))
546
+ branchType = `${branchType}_`;
547
+ usedBranchTypes.add(branchType);
548
+ return { caseName, branchType, member: m };
549
+ });
550
+ const lines = [];
551
+ lines.push(`${pad}${ACCESS} enum ${typeName}: ${CONFORMANCES} {`);
552
+ for (const br of branches) {
553
+ lines.push(`${b}case ${br.caseName}(${br.branchType})`);
554
+ }
555
+ lines.push("");
556
+ // Nested branch structs. Each renders from its member object descriptor with
557
+ // the discriminant forced required.
558
+ for (const br of branches) {
559
+ const md = describeSchema(br.member.schema);
560
+ if (!md.object) {
561
+ // Guaranteed by the descriptor (every member is `type: "object"`), but be
562
+ // defensive rather than emit broken Swift.
563
+ state.fail(`swift codegen: oneOf branch for ${discriminant}=${String(br.member.discriminantValue)} is not an object`);
564
+ }
565
+ const decl = renderStruct(br.branchType, md.object, indent + 1, { ...state, path: `${state.path}.${br.branchType}` }, [discriminant]);
566
+ lines.push(decl);
567
+ lines.push("");
568
+ }
569
+ // Discriminator coding key.
570
+ const tagKind = discriminatorTagKind(members.map((m) => m.discriminantValue));
571
+ lines.push(`${b}private enum DiscriminatorKey: String, CodingKey {`);
572
+ lines.push(`${b2}case tag = ${swiftString(discriminant)}`);
573
+ lines.push(`${b}}`);
574
+ lines.push("");
575
+ // init(from:) — read the tag, select the branch (which re-validates the tag).
576
+ lines.push(`${b}${ACCESS} init(from decoder: Decoder) throws {`);
577
+ lines.push(`${b2}let container = try decoder.container(keyedBy: DiscriminatorKey.self)`);
578
+ if (tagKind.kind === "scalar") {
579
+ lines.push(`${b2}let tag = try container.decode(${tagKind.swiftType}.self, forKey: .tag)`);
580
+ lines.push(`${b2}switch tag {`);
581
+ for (const br of branches) {
582
+ lines.push(`${b2}case ${tagKind.literal(br.member.discriminantValue)}:`);
583
+ lines.push(`${b3}self = .${br.caseName}(try ${br.branchType}(from: decoder))`);
584
+ }
585
+ lines.push(`${b2}default:`);
586
+ lines.push(`${b3}throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: ${swiftString(`Unknown ${discriminant} discriminator for ${typeName}`)}))`);
587
+ lines.push(`${b2}}`);
588
+ }
589
+ else {
590
+ // Mixed-type discriminator values → decode as JSONValue and compare.
591
+ lines.push(`${b2}let tag = try container.decode(JSONValue.self, forKey: .tag)`);
592
+ branches.forEach((br, i) => {
593
+ const kw = i === 0 ? "if" : "} else if";
594
+ lines.push(`${b2}${kw} tag == ${jsonValueLiteral(br.member.discriminantValue)} {`);
595
+ lines.push(`${b3}self = .${br.caseName}(try ${br.branchType}(from: decoder))`);
596
+ });
597
+ lines.push(`${b2}} else {`);
598
+ lines.push(`${b3}throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: ${swiftString(`Unknown ${discriminant} discriminator for ${typeName}`)}))`);
599
+ lines.push(`${b2}}`);
600
+ }
601
+ lines.push(`${b}}`);
602
+ lines.push("");
603
+ // encode(to:) — delegate to the branch (which re-emits the tag).
604
+ lines.push(`${b}${ACCESS} func encode(to encoder: Encoder) throws {`);
605
+ lines.push(`${b2}switch self {`);
606
+ for (const br of branches) {
607
+ lines.push(`${b2}case .${br.caseName}(let value):`);
608
+ lines.push(`${b3}try value.encode(to: encoder)`);
609
+ }
610
+ lines.push(`${b2}}`);
611
+ lines.push(`${b}}`);
612
+ lines.push(`${pad}}`);
613
+ return lines.join("\n");
614
+ }
615
+ /** Determine how to decode + match a set of discriminator literals. */
616
+ function discriminatorTagKind(values) {
617
+ if (values.every((v) => typeof v === "string")) {
618
+ return { kind: "scalar", swiftType: "String", literal: (v) => swiftString(v) };
619
+ }
620
+ if (values.every((v) => typeof v === "number")) {
621
+ return { kind: "scalar", swiftType: "Double", literal: (v) => `${v}` };
622
+ }
623
+ if (values.every((v) => typeof v === "boolean")) {
624
+ return { kind: "scalar", swiftType: "Bool", literal: (v) => `${v}` };
625
+ }
626
+ return { kind: "mixed" };
627
+ }
628
+ /** A `JSONValue` literal for a scalar discriminator (mixed-type branch). */
629
+ function jsonValueLiteral(v) {
630
+ if (typeof v === "string")
631
+ return `JSONValue.string(${swiftString(v)})`;
632
+ if (typeof v === "number")
633
+ return `JSONValue.number(${v})`;
634
+ if (typeof v === "boolean")
635
+ return `JSONValue.bool(${v})`;
636
+ return "JSONValue.null";
637
+ }
638
+ /** Extract the declared type name from a `struct`/`enum` declaration's first line. */
639
+ function declTypeName(decl) {
640
+ const firstLine = decl.split("\n").find((l) => l.trim().length > 0) ?? "";
641
+ const m = firstLine.match(/\b(?:struct|enum)\s+([A-Za-z_][A-Za-z0-9_]*)/);
642
+ return m ? m[1] : null;
643
+ }
644
+ //# sourceMappingURL=schemaToSwift.js.map