zero-swift 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +61 -0
- package/dist/bin.js +649 -0
- package/dist/drift-S756Q2ZA.js +2230 -0
- package/package.json +46 -4
package/dist/bin.js
ADDED
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { relative, resolve as resolve2 } from "node:path";
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
7
|
+
import { parseArgs } from "node:util";
|
|
8
|
+
|
|
9
|
+
// src/generate.ts
|
|
10
|
+
var reservedTypeNames = /* @__PURE__ */ new Set([
|
|
11
|
+
"AnyQuery",
|
|
12
|
+
"Cardinality",
|
|
13
|
+
"ColumnSchema",
|
|
14
|
+
"Column",
|
|
15
|
+
"Condition",
|
|
16
|
+
"Format",
|
|
17
|
+
"Indirect",
|
|
18
|
+
"JSONValue",
|
|
19
|
+
"KeyPathNames",
|
|
20
|
+
"Query",
|
|
21
|
+
"QueryResult",
|
|
22
|
+
"RelationshipHop",
|
|
23
|
+
"Row",
|
|
24
|
+
"RowExpression",
|
|
25
|
+
"TableSchema",
|
|
26
|
+
"ViewEntry",
|
|
27
|
+
"ViewValue",
|
|
28
|
+
"Zero",
|
|
29
|
+
"ZeroEnum",
|
|
30
|
+
"ZeroObjectReader",
|
|
31
|
+
"ZeroObjectWriter",
|
|
32
|
+
"ZeroRow",
|
|
33
|
+
"ZeroSchema",
|
|
34
|
+
"ZeroValue",
|
|
35
|
+
"ZeroValueError",
|
|
36
|
+
"Array",
|
|
37
|
+
"Bool",
|
|
38
|
+
"Dictionary",
|
|
39
|
+
"Double",
|
|
40
|
+
"Int",
|
|
41
|
+
"Optional",
|
|
42
|
+
"Set",
|
|
43
|
+
"String"
|
|
44
|
+
]);
|
|
45
|
+
var swiftKeywords = new Set(
|
|
46
|
+
"associatedtype borrowing class consuming deinit enum extension fileprivate func import init inout internal let nonisolated open operator package private precedencegroup protocol public rethrows static struct subscript typealias var break case catch continue default defer do else fallthrough for guard if in repeat return throw switch where while Any as await false is nil self Self super throws true try".split(" ")
|
|
47
|
+
);
|
|
48
|
+
var nonIdentifierChars = /[^A-Za-z0-9_]/g;
|
|
49
|
+
var leadingDigit = /^[0-9]/;
|
|
50
|
+
var wordBoundary = /([a-z0-9])([A-Z])/g;
|
|
51
|
+
var wordSeparators = /[^A-Za-z0-9]+/;
|
|
52
|
+
var identifierStart = /^[A-Za-z_]/;
|
|
53
|
+
function identifier(name) {
|
|
54
|
+
let id = name.replace(nonIdentifierChars, "_");
|
|
55
|
+
if (leadingDigit.test(id) || id === "") {
|
|
56
|
+
id = "_" + id;
|
|
57
|
+
}
|
|
58
|
+
return swiftKeywords.has(id) ? "`" + id + "`" : id;
|
|
59
|
+
}
|
|
60
|
+
function words(name) {
|
|
61
|
+
return name.replace(wordBoundary, "$1 $2").split(wordSeparators).filter((w) => w !== "");
|
|
62
|
+
}
|
|
63
|
+
function pascalCase(name) {
|
|
64
|
+
const out = words(name).map((w) => w[0].toUpperCase() + w.slice(1)).join("");
|
|
65
|
+
return identifierStart.test(out) ? out : "_" + out;
|
|
66
|
+
}
|
|
67
|
+
function camelCase(name) {
|
|
68
|
+
const p = pascalCase(name);
|
|
69
|
+
return p[0] === "_" ? p : p[0].toLowerCase() + p.slice(1);
|
|
70
|
+
}
|
|
71
|
+
function str(s) {
|
|
72
|
+
return JSON.stringify(s);
|
|
73
|
+
}
|
|
74
|
+
function strings(a) {
|
|
75
|
+
return `[${a.map(str).join(", ")}]`;
|
|
76
|
+
}
|
|
77
|
+
var Emitter = class {
|
|
78
|
+
#access;
|
|
79
|
+
#named = [];
|
|
80
|
+
#names = /* @__PURE__ */ new Set();
|
|
81
|
+
/** Declared types by alias and shape, so a type used twice is one type. */
|
|
82
|
+
#byAlias = /* @__PURE__ */ new Map();
|
|
83
|
+
#prefix;
|
|
84
|
+
constructor(access, prefix, rowNames) {
|
|
85
|
+
this.#access = access === "internal" ? "" : "public ";
|
|
86
|
+
this.#prefix = prefix;
|
|
87
|
+
for (const n of rowNames) {
|
|
88
|
+
this.#names.add(n);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
get access() {
|
|
92
|
+
return this.#access;
|
|
93
|
+
}
|
|
94
|
+
bare(name) {
|
|
95
|
+
return this.#prefix && name.startsWith(this.#prefix) ? name.slice(this.#prefix.length) : name;
|
|
96
|
+
}
|
|
97
|
+
get named() {
|
|
98
|
+
return this.#named;
|
|
99
|
+
}
|
|
100
|
+
/** A free type name, without the prefix: the alias, else `fallback`. */
|
|
101
|
+
#claimBare(preferred, fallback) {
|
|
102
|
+
const free = (name) => !this.#names.has(this.#prefix + name) && !reservedTypeNames.has(this.#prefix + name);
|
|
103
|
+
for (const name of [preferred, fallback]) {
|
|
104
|
+
if (name && free(name)) {
|
|
105
|
+
this.#names.add(this.#prefix + name);
|
|
106
|
+
return name;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
for (let i = 2; ; i++) {
|
|
110
|
+
const name = `${fallback}${i}`;
|
|
111
|
+
if (free(name)) {
|
|
112
|
+
this.#names.add(this.#prefix + name);
|
|
113
|
+
return name;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** The Swift type of a model, naming its enums and objects after `path`. */
|
|
118
|
+
swiftType(model2, path) {
|
|
119
|
+
switch (model2.kind) {
|
|
120
|
+
case "string":
|
|
121
|
+
return "String";
|
|
122
|
+
case "number":
|
|
123
|
+
return "Double";
|
|
124
|
+
case "boolean":
|
|
125
|
+
return "Bool";
|
|
126
|
+
case "json":
|
|
127
|
+
return "JSONValue";
|
|
128
|
+
case "nullable":
|
|
129
|
+
return `${this.swiftType(model2.wrapped, path)}?`;
|
|
130
|
+
case "array":
|
|
131
|
+
return `[${this.swiftType(model2.element, path)}]`;
|
|
132
|
+
case "enum":
|
|
133
|
+
case "object": {
|
|
134
|
+
const key = model2.alias && `${model2.alias}:${JSON.stringify(model2)}`;
|
|
135
|
+
const existing = key && this.#byAlias.get(key);
|
|
136
|
+
if (existing) {
|
|
137
|
+
return existing;
|
|
138
|
+
}
|
|
139
|
+
const name = this.#prefix + this.#claimBare(model2.alias, path);
|
|
140
|
+
if (key) {
|
|
141
|
+
this.#byAlias.set(key, name);
|
|
142
|
+
}
|
|
143
|
+
this.#named.push(
|
|
144
|
+
model2.kind === "enum" ? { kind: "enum", name, cases: model2.cases } : { kind: "object", name, fields: model2.fields }
|
|
145
|
+
);
|
|
146
|
+
return name;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
function generate(schema, customTypes, config, source) {
|
|
152
|
+
const tableNames = Object.keys(schema.tables);
|
|
153
|
+
const prefix = config.typePrefix ?? "";
|
|
154
|
+
const rowNames = /* @__PURE__ */ new Map();
|
|
155
|
+
const used = /* @__PURE__ */ new Set();
|
|
156
|
+
for (const t of tableNames) {
|
|
157
|
+
let name = config.tables?.[t]?.type ?? prefix + pascalCase(t);
|
|
158
|
+
if (reservedTypeNames.has(name) || used.has(name)) {
|
|
159
|
+
name += "Row";
|
|
160
|
+
}
|
|
161
|
+
used.add(name);
|
|
162
|
+
rowNames.set(t, name);
|
|
163
|
+
}
|
|
164
|
+
const e = new Emitter(config.access ?? "public", prefix, used);
|
|
165
|
+
const bare = (name) => prefix && name.startsWith(prefix) ? name.slice(prefix.length) : name;
|
|
166
|
+
const pub = e.access;
|
|
167
|
+
const schemaName = config.schemaName ?? "app";
|
|
168
|
+
const zqlName = config.zql ?? "zql";
|
|
169
|
+
const zqlType = zqlName === "zql" ? "ZQL" : pascalCase(zqlName);
|
|
170
|
+
const out = [
|
|
171
|
+
`// Generated by \`zero-swift codegen\` from ${source}. Do not edit.`,
|
|
172
|
+
"// swift-format-ignore-file",
|
|
173
|
+
"",
|
|
174
|
+
"import Zero",
|
|
175
|
+
""
|
|
176
|
+
];
|
|
177
|
+
const typeDecls = [];
|
|
178
|
+
for (const t of tableNames) {
|
|
179
|
+
const table = schema.tables[t];
|
|
180
|
+
const row = rowNames.get(t);
|
|
181
|
+
const columns = Object.entries(table.columns).map(([name, c]) => {
|
|
182
|
+
const custom = customTypes.get(t)?.get(name);
|
|
183
|
+
const override = config.tables?.[t]?.columns?.[name]?.type;
|
|
184
|
+
let base;
|
|
185
|
+
if (override === "int") {
|
|
186
|
+
base = "Int";
|
|
187
|
+
} else if (c.type === "number" || override === "double") {
|
|
188
|
+
base = "Double";
|
|
189
|
+
} else if (c.type === "boolean") {
|
|
190
|
+
base = "Bool";
|
|
191
|
+
} else if (c.type === "string") {
|
|
192
|
+
base = custom?.kind === "enum" ? e.swiftType(custom, bare(row) + pascalCase(name)) : "String";
|
|
193
|
+
} else if (c.type === "json") {
|
|
194
|
+
base = custom ? e.swiftType(custom, bare(row) + pascalCase(name)) : "JSONValue";
|
|
195
|
+
} else {
|
|
196
|
+
base = "JSONValue";
|
|
197
|
+
}
|
|
198
|
+
const optional = c.optional === true;
|
|
199
|
+
const swiftType = optional && !base.endsWith("?") ? base + "?" : base;
|
|
200
|
+
return { name, property: identifier(name), swiftType, optional };
|
|
201
|
+
});
|
|
202
|
+
const relationships = Object.entries(schema.relationships[t] ?? {}).map(
|
|
203
|
+
([name, hops]) => {
|
|
204
|
+
const last = hops.at(-1);
|
|
205
|
+
const dest = rowNames.get(last.destSchema);
|
|
206
|
+
return {
|
|
207
|
+
name,
|
|
208
|
+
property: identifier(name),
|
|
209
|
+
dest,
|
|
210
|
+
one: last.cardinality === "one"
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
const identifiable = table.primaryKey.length === 1 && table.primaryKey[0] === "id" && columns.some((c) => c.name === "id" && !c.optional);
|
|
215
|
+
const conformances = ["ZeroRow", "Codable", "Hashable"];
|
|
216
|
+
if (identifiable) {
|
|
217
|
+
conformances.push("Identifiable");
|
|
218
|
+
}
|
|
219
|
+
const lines = [];
|
|
220
|
+
lines.push(`${pub}struct ${row}: ${conformances.join(", ")} {`);
|
|
221
|
+
for (const c of columns) {
|
|
222
|
+
lines.push(` ${pub}var ${c.property}: ${c.swiftType}`);
|
|
223
|
+
}
|
|
224
|
+
if (relationships.length) {
|
|
225
|
+
lines.push("");
|
|
226
|
+
lines.push(
|
|
227
|
+
" // Relationships, filled when a query asks for them with `related`."
|
|
228
|
+
);
|
|
229
|
+
for (const r of relationships) {
|
|
230
|
+
lines.push(
|
|
231
|
+
r.one ? ` @Indirect ${pub}var ${r.property}: ${r.dest}?` : ` ${pub}var ${r.property}: [${r.dest}]?`
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const params = [
|
|
236
|
+
...columns.map(
|
|
237
|
+
(c) => `${c.property}: ${c.swiftType}${c.optional ? " = nil" : ""}`
|
|
238
|
+
),
|
|
239
|
+
...relationships.map(
|
|
240
|
+
(r) => `${r.property}: ${r.one ? `${r.dest}?` : `[${r.dest}]?`} = nil`
|
|
241
|
+
)
|
|
242
|
+
];
|
|
243
|
+
lines.push("");
|
|
244
|
+
lines.push(` ${pub}init(`);
|
|
245
|
+
lines.push(params.map((p) => ` ${p}`).join(",\n"));
|
|
246
|
+
lines.push(" ) {");
|
|
247
|
+
for (const c of [...columns, ...relationships]) {
|
|
248
|
+
lines.push(` self.${c.property} = ${c.property}`);
|
|
249
|
+
}
|
|
250
|
+
lines.push(" }");
|
|
251
|
+
lines.push("");
|
|
252
|
+
lines.push(` ${pub}static let tableName = ${str(t)}`);
|
|
253
|
+
lines.push("");
|
|
254
|
+
lines.push(` ${pub}static let columns: KeyPathNames<${row}> = [`);
|
|
255
|
+
for (const c of columns) {
|
|
256
|
+
lines.push(` \\${row}.${c.property}: ${str(c.name)},`);
|
|
257
|
+
}
|
|
258
|
+
lines.push(" ]");
|
|
259
|
+
lines.push("");
|
|
260
|
+
lines.push(
|
|
261
|
+
` ${pub}static let relationships: KeyPathNames<${row}> = ${relationships.length ? "[" : "[:]"}`
|
|
262
|
+
);
|
|
263
|
+
if (relationships.length) {
|
|
264
|
+
for (const r of relationships) {
|
|
265
|
+
lines.push(` \\${row}.${r.property}: ${str(r.name)},`);
|
|
266
|
+
}
|
|
267
|
+
lines.push(" ]");
|
|
268
|
+
}
|
|
269
|
+
lines.push("");
|
|
270
|
+
lines.push(` ${pub}init(entry: ViewEntry) throws(ZeroValueError) {`);
|
|
271
|
+
for (const [i, c] of columns.entries()) {
|
|
272
|
+
lines.push(` ${c.property} = try entry.column(${str(c.name)}, ${i})`);
|
|
273
|
+
}
|
|
274
|
+
for (const r of relationships) {
|
|
275
|
+
lines.push(` ${r.property} = try entry.relationship(${str(r.name)})`);
|
|
276
|
+
}
|
|
277
|
+
lines.push(" }");
|
|
278
|
+
lines.push("");
|
|
279
|
+
lines.push(` ${pub}var columnValues: Row {`);
|
|
280
|
+
lines.push(" [");
|
|
281
|
+
for (const c of columns) {
|
|
282
|
+
lines.push(` ${str(c.name)}: ${c.property}.zeroValue,`);
|
|
283
|
+
}
|
|
284
|
+
lines.push(" ]");
|
|
285
|
+
lines.push(" }");
|
|
286
|
+
lines.push("}");
|
|
287
|
+
typeDecls.push(lines.join("\n"));
|
|
288
|
+
}
|
|
289
|
+
const namedDecls = [];
|
|
290
|
+
for (let i = 0; i < e.named.length; i++) {
|
|
291
|
+
const n = e.named[i];
|
|
292
|
+
namedDecls.push(
|
|
293
|
+
n.kind === "enum" ? enumDecl(pub, n) : objectDecl(e, pub, n)
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
out.push(...typeDecls.flatMap((d) => [d, ""]));
|
|
297
|
+
out.push(...namedDecls.flatMap((d) => [d, ""]));
|
|
298
|
+
out.push(schemaDecl(pub, schema, schemaName));
|
|
299
|
+
out.push("");
|
|
300
|
+
out.push(
|
|
301
|
+
`/// The queries of \`ZeroSchema.${schemaName}\`, one per table: \`${zqlName}.${tableNames[0] ?? "table"}.where(\u2026)\`.`
|
|
302
|
+
);
|
|
303
|
+
out.push(`${pub}struct ${zqlType}: Sendable {`);
|
|
304
|
+
out.push(` ${pub}init() {}`);
|
|
305
|
+
for (const t of tableNames) {
|
|
306
|
+
const row = rowNames.get(t);
|
|
307
|
+
out.push("");
|
|
308
|
+
out.push(
|
|
309
|
+
` ${pub}var ${identifier(t)}: Query<${row}, [${row}]> { Query(AnyQuery(.${schemaName}, ${str(t)})) }`
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
out.push("}");
|
|
313
|
+
out.push("");
|
|
314
|
+
out.push(`${pub}let ${identifier(zqlName)} = ${zqlType}()`);
|
|
315
|
+
out.push("");
|
|
316
|
+
return out.join("\n");
|
|
317
|
+
}
|
|
318
|
+
function enumDecl(pub, n) {
|
|
319
|
+
const used = /* @__PURE__ */ new Set();
|
|
320
|
+
const lines = [
|
|
321
|
+
`${pub}enum ${n.name}: String, ZeroEnum, Codable, CaseIterable {`
|
|
322
|
+
];
|
|
323
|
+
for (const value of n.cases) {
|
|
324
|
+
let c = camelCase(value);
|
|
325
|
+
while (used.has(c)) {
|
|
326
|
+
c += "_";
|
|
327
|
+
}
|
|
328
|
+
used.add(c);
|
|
329
|
+
const id = identifier(c);
|
|
330
|
+
lines.push(c === value ? ` case ${id}` : ` case ${id} = ${str(value)}`);
|
|
331
|
+
}
|
|
332
|
+
lines.push("}");
|
|
333
|
+
return lines.join("\n");
|
|
334
|
+
}
|
|
335
|
+
function objectDecl(e, pub, n) {
|
|
336
|
+
const fields = n.fields.map((f) => {
|
|
337
|
+
const base = e.swiftType(f.type, e.bare(n.name) + pascalCase(f.name));
|
|
338
|
+
const type = f.optional && !base.endsWith("?") ? base + "?" : base;
|
|
339
|
+
return { ...f, property: identifier(f.name), swiftType: type };
|
|
340
|
+
});
|
|
341
|
+
const lines = [`${pub}struct ${n.name}: ZeroValue, Codable, Hashable {`];
|
|
342
|
+
for (const f of fields) {
|
|
343
|
+
lines.push(` ${pub}var ${f.property}: ${f.swiftType}`);
|
|
344
|
+
}
|
|
345
|
+
lines.push("");
|
|
346
|
+
lines.push(` ${pub}init(`);
|
|
347
|
+
lines.push(
|
|
348
|
+
fields.map(
|
|
349
|
+
(f) => ` ${f.property}: ${f.swiftType}${f.swiftType.endsWith("?") ? " = nil" : ""}`
|
|
350
|
+
).join(",\n")
|
|
351
|
+
);
|
|
352
|
+
lines.push(" ) {");
|
|
353
|
+
for (const f of fields) {
|
|
354
|
+
lines.push(` self.${f.property} = ${f.property}`);
|
|
355
|
+
}
|
|
356
|
+
lines.push(" }");
|
|
357
|
+
lines.push("");
|
|
358
|
+
lines.push(` ${pub}init(zeroValue: JSONValue) throws(ZeroValueError) {`);
|
|
359
|
+
lines.push(` let o = try ZeroObjectReader(zeroValue, ${str(n.name)})`);
|
|
360
|
+
for (const f of fields) {
|
|
361
|
+
lines.push(` ${f.property} = try o.field(${str(f.name)})`);
|
|
362
|
+
}
|
|
363
|
+
lines.push(" }");
|
|
364
|
+
lines.push("");
|
|
365
|
+
lines.push(` ${pub}var zeroValue: JSONValue {`);
|
|
366
|
+
lines.push(" var o = ZeroObjectWriter()");
|
|
367
|
+
for (const f of fields) {
|
|
368
|
+
lines.push(
|
|
369
|
+
f.optional ? ` o.setIfPresent(${str(f.name)}, ${f.property})` : ` o.set(${str(f.name)}, ${f.property})`
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
lines.push(" return o.value");
|
|
373
|
+
lines.push(" }");
|
|
374
|
+
lines.push("}");
|
|
375
|
+
return lines.join("\n");
|
|
376
|
+
}
|
|
377
|
+
function schemaDecl(pub, schema, name) {
|
|
378
|
+
const lines = ["extension ZeroSchema {"];
|
|
379
|
+
lines.push(` ${pub}static let ${identifier(name)} = ZeroSchema(`);
|
|
380
|
+
lines.push(" tables: [");
|
|
381
|
+
for (const [t, table] of Object.entries(schema.tables)) {
|
|
382
|
+
lines.push(" TableSchema(");
|
|
383
|
+
lines.push(
|
|
384
|
+
` name: ${str(t)},${table.serverName ? ` serverName: ${str(table.serverName)},` : ""}`
|
|
385
|
+
);
|
|
386
|
+
lines.push(" columns: [");
|
|
387
|
+
for (const [c, col] of Object.entries(table.columns)) {
|
|
388
|
+
const args = [`.${col.type}`];
|
|
389
|
+
if (col.optional) args.push("optional: true");
|
|
390
|
+
if (col.serverName) args.push(`serverName: ${str(col.serverName)}`);
|
|
391
|
+
lines.push(` (${str(c)}, ColumnSchema(${args.join(", ")})),`);
|
|
392
|
+
}
|
|
393
|
+
lines.push(" ],");
|
|
394
|
+
const keys = table.uniqueKeys?.length ? `,
|
|
395
|
+
uniqueKeys: [${table.uniqueKeys.map(strings).join(", ")}]` : "";
|
|
396
|
+
lines.push(` primaryKey: ${strings(table.primaryKey)}${keys}),`);
|
|
397
|
+
}
|
|
398
|
+
lines.push(" ],");
|
|
399
|
+
const rels = Object.entries(schema.relationships).filter(
|
|
400
|
+
([, r]) => Object.keys(r).length > 0
|
|
401
|
+
);
|
|
402
|
+
if (rels.length === 0) {
|
|
403
|
+
lines.push(" relationships: [:])");
|
|
404
|
+
} else {
|
|
405
|
+
lines.push(" relationships: [");
|
|
406
|
+
for (const [t, byName] of rels) {
|
|
407
|
+
lines.push(` ${str(t)}: [`);
|
|
408
|
+
for (const [r, hops] of Object.entries(byName)) {
|
|
409
|
+
lines.push(` ${str(r)}: [`);
|
|
410
|
+
for (const h of hops) {
|
|
411
|
+
lines.push(
|
|
412
|
+
` RelationshipHop(sourceField: ${strings(h.sourceField)}, destField: ${strings(h.destField)}, destSchema: ${str(h.destSchema)}, cardinality: .${h.cardinality}),`
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
lines.push(" ],");
|
|
416
|
+
}
|
|
417
|
+
lines.push(" ],");
|
|
418
|
+
}
|
|
419
|
+
lines.push(" ])");
|
|
420
|
+
}
|
|
421
|
+
lines.push("}");
|
|
422
|
+
return lines.join("\n");
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// src/types.ts
|
|
426
|
+
import { dirname, resolve } from "node:path";
|
|
427
|
+
import ts from "typescript";
|
|
428
|
+
var defaultOptions = {
|
|
429
|
+
target: ts.ScriptTarget.ES2022,
|
|
430
|
+
module: ts.ModuleKind.NodeNext,
|
|
431
|
+
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
432
|
+
allowImportingTsExtensions: true,
|
|
433
|
+
strict: true,
|
|
434
|
+
noEmit: true,
|
|
435
|
+
skipLibCheck: true
|
|
436
|
+
};
|
|
437
|
+
function readCustomTypes(schemaPath, exportName) {
|
|
438
|
+
const file = resolve(schemaPath);
|
|
439
|
+
const program = ts.createProgram([file], compilerOptions(file));
|
|
440
|
+
const checker = program.getTypeChecker();
|
|
441
|
+
const source = program.getSourceFile(file);
|
|
442
|
+
const module = source && checker.getSymbolAtLocation(source);
|
|
443
|
+
if (!module) {
|
|
444
|
+
throw new Error(`Cannot read the types of ${schemaPath}`);
|
|
445
|
+
}
|
|
446
|
+
let symbol = checker.getExportsOfModule(module).find((s) => s.name === exportName);
|
|
447
|
+
if (!symbol) {
|
|
448
|
+
throw new Error(`${schemaPath} does not export "${exportName}"`);
|
|
449
|
+
}
|
|
450
|
+
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
451
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
452
|
+
}
|
|
453
|
+
const schemaType = checker.getTypeOfSymbol(symbol);
|
|
454
|
+
const out = /* @__PURE__ */ new Map();
|
|
455
|
+
const tables = property(checker, schemaType, "tables");
|
|
456
|
+
for (const table of tables ? checker.getPropertiesOfType(tables) : []) {
|
|
457
|
+
const columns = property(
|
|
458
|
+
checker,
|
|
459
|
+
checker.getTypeOfSymbol(table),
|
|
460
|
+
"columns"
|
|
461
|
+
);
|
|
462
|
+
const byColumn = /* @__PURE__ */ new Map();
|
|
463
|
+
for (const column of columns ? checker.getPropertiesOfType(columns) : []) {
|
|
464
|
+
const custom = property(
|
|
465
|
+
checker,
|
|
466
|
+
checker.getTypeOfSymbol(column),
|
|
467
|
+
"customType"
|
|
468
|
+
);
|
|
469
|
+
if (custom) {
|
|
470
|
+
byColumn.set(column.name, model(checker, custom));
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
out.set(table.name, byColumn);
|
|
474
|
+
}
|
|
475
|
+
return out;
|
|
476
|
+
}
|
|
477
|
+
function compilerOptions(file) {
|
|
478
|
+
const configPath = ts.findConfigFile(dirname(file), ts.sys.fileExists);
|
|
479
|
+
if (!configPath) {
|
|
480
|
+
return defaultOptions;
|
|
481
|
+
}
|
|
482
|
+
const parsed = ts.getParsedCommandLineOfConfigFile(configPath, void 0, {
|
|
483
|
+
...ts.sys,
|
|
484
|
+
onUnRecoverableConfigFileDiagnostic: () => {
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
return parsed ? { ...parsed.options, noEmit: true, skipLibCheck: true } : defaultOptions;
|
|
488
|
+
}
|
|
489
|
+
function property(checker, type, name) {
|
|
490
|
+
const p = checker.getPropertyOfType(type, name);
|
|
491
|
+
return p && checker.getTypeOfSymbol(p);
|
|
492
|
+
}
|
|
493
|
+
var nullish = ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.Void;
|
|
494
|
+
function model(checker, type) {
|
|
495
|
+
const alias = aliasName(type);
|
|
496
|
+
if (type.isUnion()) {
|
|
497
|
+
const parts = type.types.filter((t) => !(t.flags & nullish));
|
|
498
|
+
const nullable = parts.length !== type.types.length;
|
|
499
|
+
const inner = unionModel(checker, parts, alias);
|
|
500
|
+
return nullable ? { kind: "nullable", wrapped: inner } : inner;
|
|
501
|
+
}
|
|
502
|
+
if (type.isIntersection()) {
|
|
503
|
+
for (const t of type.types) {
|
|
504
|
+
if (t.flags & ts.TypeFlags.StringLike) return { kind: "string" };
|
|
505
|
+
if (t.flags & ts.TypeFlags.NumberLike) return { kind: "number" };
|
|
506
|
+
}
|
|
507
|
+
return { kind: "json" };
|
|
508
|
+
}
|
|
509
|
+
if (type.isStringLiteral()) {
|
|
510
|
+
return enumModel([type.value], alias);
|
|
511
|
+
}
|
|
512
|
+
if (type.flags & ts.TypeFlags.StringLike) return { kind: "string" };
|
|
513
|
+
if (type.flags & ts.TypeFlags.NumberLike) return { kind: "number" };
|
|
514
|
+
if (type.flags & ts.TypeFlags.BooleanLike) return { kind: "boolean" };
|
|
515
|
+
if (checker.isArrayType(type)) {
|
|
516
|
+
const [element] = checker.getTypeArguments(type);
|
|
517
|
+
return {
|
|
518
|
+
kind: "array",
|
|
519
|
+
element: element ? model(checker, element) : { kind: "json" }
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
if (type.flags & ts.TypeFlags.Object && isPlainObject(checker, type)) {
|
|
523
|
+
const fields = checker.getPropertiesOfType(type).map((p) => {
|
|
524
|
+
const optional = (p.flags & ts.SymbolFlags.Optional) !== 0;
|
|
525
|
+
let t = checker.getTypeOfSymbol(p);
|
|
526
|
+
if (optional) {
|
|
527
|
+
t = checker.getNonNullableType(t);
|
|
528
|
+
}
|
|
529
|
+
return { name: p.name, type: model(checker, t), optional };
|
|
530
|
+
});
|
|
531
|
+
return { kind: "object", ...alias ? { alias } : {}, fields };
|
|
532
|
+
}
|
|
533
|
+
return { kind: "json" };
|
|
534
|
+
}
|
|
535
|
+
function unionModel(checker, parts, alias) {
|
|
536
|
+
if (parts.length === 0) return { kind: "json" };
|
|
537
|
+
if (parts.every((t) => t.isStringLiteral())) {
|
|
538
|
+
return enumModel(
|
|
539
|
+
parts.map((t) => t.value),
|
|
540
|
+
alias
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
if (parts.every((t) => t.flags & ts.TypeFlags.BooleanLike)) {
|
|
544
|
+
return { kind: "boolean" };
|
|
545
|
+
}
|
|
546
|
+
if (parts.every((t) => t.flags & ts.TypeFlags.NumberLike)) {
|
|
547
|
+
return { kind: "number" };
|
|
548
|
+
}
|
|
549
|
+
if (parts.length === 1) return model(checker, parts[0]);
|
|
550
|
+
return { kind: "json" };
|
|
551
|
+
}
|
|
552
|
+
function enumModel(cases, alias) {
|
|
553
|
+
return { kind: "enum", ...alias ? { alias } : {}, cases };
|
|
554
|
+
}
|
|
555
|
+
function isPlainObject(checker, type) {
|
|
556
|
+
return checker.getPropertiesOfType(type).length > 0 && type.getCallSignatures().length === 0 && type.getConstructSignatures().length === 0 && checker.getIndexInfosOfType(type).length === 0;
|
|
557
|
+
}
|
|
558
|
+
function aliasName(type) {
|
|
559
|
+
if (type.aliasSymbol && !type.aliasTypeArguments?.length) {
|
|
560
|
+
return type.aliasSymbol.name;
|
|
561
|
+
}
|
|
562
|
+
const name = type.getSymbol()?.name;
|
|
563
|
+
if (name && !name.startsWith("__") && type.flags & ts.TypeFlags.Object && (type.getSymbol().flags & ts.SymbolFlags.Interface) !== 0) {
|
|
564
|
+
return name;
|
|
565
|
+
}
|
|
566
|
+
return void 0;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// src/cli.ts
|
|
570
|
+
async function codegenFile(config, cwd) {
|
|
571
|
+
const schemaPath = resolve2(cwd, config.schema ?? "schema.ts");
|
|
572
|
+
const exportName = config.export ?? "schema";
|
|
573
|
+
const module = await import(pathToFileURL(schemaPath).href);
|
|
574
|
+
const schema = module[exportName];
|
|
575
|
+
if (!schema || typeof schema !== "object" || !("tables" in schema)) {
|
|
576
|
+
throw new Error(
|
|
577
|
+
`${schemaPath} does not export a schema as "${exportName}"`
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
const customTypes = readCustomTypes(schemaPath, exportName);
|
|
581
|
+
return generate(
|
|
582
|
+
schema,
|
|
583
|
+
customTypes,
|
|
584
|
+
config,
|
|
585
|
+
relative(cwd, schemaPath).replaceAll("\\", "/")
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
async function main(argv) {
|
|
589
|
+
const { values, positionals } = parseArgs({
|
|
590
|
+
args: argv,
|
|
591
|
+
allowPositionals: true,
|
|
592
|
+
options: {
|
|
593
|
+
schema: { type: "string" },
|
|
594
|
+
out: { type: "string" },
|
|
595
|
+
config: { type: "string" },
|
|
596
|
+
samples: { type: "string" },
|
|
597
|
+
check: { type: "boolean" }
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
const [command = "codegen", subcommand] = positionals;
|
|
601
|
+
const cwd = process.cwd();
|
|
602
|
+
if (command === "drift" && subcommand === "record") {
|
|
603
|
+
const { recordDriftFile } = await import("./drift-S756Q2ZA.js");
|
|
604
|
+
const vectors = await recordDriftFile(
|
|
605
|
+
resolve2(cwd, values.samples ?? "drift-samples.ts")
|
|
606
|
+
);
|
|
607
|
+
return writeOutput(
|
|
608
|
+
resolve2(cwd, values.out ?? "drift-vectors.json"),
|
|
609
|
+
vectors,
|
|
610
|
+
values.check ?? false,
|
|
611
|
+
cwd
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
if (command !== "codegen") {
|
|
615
|
+
process.stderr.write(`Unknown command "${positionals.join(" ")}"
|
|
616
|
+
`);
|
|
617
|
+
return 2;
|
|
618
|
+
}
|
|
619
|
+
const configPath = values.config ?? (existsSync(resolve2(cwd, "zero-swift.json")) ? "zero-swift.json" : void 0);
|
|
620
|
+
const fileConfig = configPath ? JSON.parse(readFileSync(resolve2(cwd, configPath), "utf8")) : {};
|
|
621
|
+
const config = {
|
|
622
|
+
...fileConfig,
|
|
623
|
+
...values.schema ? { schema: values.schema } : {},
|
|
624
|
+
...values.out ? { output: values.out } : {}
|
|
625
|
+
};
|
|
626
|
+
const output = resolve2(cwd, config.output ?? "Schema.swift");
|
|
627
|
+
const swift = await codegenFile(config, cwd);
|
|
628
|
+
return writeOutput(output, swift, values.check ?? false, cwd);
|
|
629
|
+
}
|
|
630
|
+
function writeOutput(output, contents, check, cwd) {
|
|
631
|
+
const current = existsSync(output) ? readFileSync(output, "utf8") : void 0;
|
|
632
|
+
if (check) {
|
|
633
|
+
if (current !== contents) {
|
|
634
|
+
process.stderr.write(`${relative(cwd, output)} is out of date
|
|
635
|
+
`);
|
|
636
|
+
return 1;
|
|
637
|
+
}
|
|
638
|
+
return 0;
|
|
639
|
+
}
|
|
640
|
+
if (current !== contents) {
|
|
641
|
+
writeFileSync(output, contents);
|
|
642
|
+
}
|
|
643
|
+
process.stdout.write(`Wrote ${relative(cwd, output)}
|
|
644
|
+
`);
|
|
645
|
+
return 0;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// src/bin.ts
|
|
649
|
+
process.exitCode = await main(process.argv.slice(2));
|