on-zero 0.16.17 → 0.16.19

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 (110) hide show
  1. package/dist/cjs/createZeroClient.cjs +44 -11
  2. package/dist/cjs/createZeroClient.native.js +54 -17
  3. package/dist/cjs/createZeroClient.native.js.map +1 -1
  4. package/dist/cjs/createZeroClient.recovery.test.cjs +49 -0
  5. package/dist/cjs/createZeroClient.recovery.test.native.js +53 -0
  6. package/dist/cjs/createZeroClient.recovery.test.native.js.map +1 -1
  7. package/dist/cjs/generate-helpers.cjs +28 -0
  8. package/dist/cjs/generate-helpers.native.js +36 -0
  9. package/dist/cjs/generate-helpers.native.js.map +1 -1
  10. package/dist/cjs/generate-layout.cjs +62 -53
  11. package/dist/cjs/generate-layout.native.js +62 -54
  12. package/dist/cjs/generate-layout.native.js.map +1 -1
  13. package/dist/cjs/generate-lite.cjs +28 -0
  14. package/dist/cjs/generate-lite.native.js +89 -33
  15. package/dist/cjs/generate-lite.native.js.map +1 -1
  16. package/dist/cjs/generate-lite.test.cjs +46 -0
  17. package/dist/cjs/generate-lite.test.native.js +46 -0
  18. package/dist/cjs/generate-lite.test.native.js.map +1 -1
  19. package/dist/cjs/generate.cjs +237 -243
  20. package/dist/cjs/generate.native.js +352 -353
  21. package/dist/cjs/generate.native.js.map +1 -1
  22. package/dist/cjs/generate.test.cjs +35 -0
  23. package/dist/cjs/generate.test.native.js +35 -0
  24. package/dist/cjs/generate.test.native.js.map +1 -1
  25. package/dist/cjs/helpers/createMutators.cjs +2 -1
  26. package/dist/cjs/helpers/createMutators.native.js +3 -1
  27. package/dist/cjs/helpers/createMutators.native.js.map +1 -1
  28. package/dist/cjs/helpers/recoverZeroClient.cjs +8 -2
  29. package/dist/cjs/helpers/recoverZeroClient.native.js +10 -2
  30. package/dist/cjs/helpers/recoverZeroClient.native.js.map +1 -1
  31. package/dist/cjs/helpers/recoverZeroClient.test.cjs +53 -0
  32. package/dist/cjs/helpers/recoverZeroClient.test.native.js +55 -0
  33. package/dist/cjs/helpers/recoverZeroClient.test.native.js.map +1 -1
  34. package/dist/cjs/native-typescript.cjs +190 -0
  35. package/dist/cjs/native-typescript.native.js +255 -0
  36. package/dist/cjs/native-typescript.native.js.map +1 -0
  37. package/dist/esm/createZeroClient.mjs +44 -11
  38. package/dist/esm/createZeroClient.mjs.map +1 -1
  39. package/dist/esm/createZeroClient.native.js +54 -17
  40. package/dist/esm/createZeroClient.native.js.map +1 -1
  41. package/dist/esm/createZeroClient.recovery.test.mjs +50 -1
  42. package/dist/esm/createZeroClient.recovery.test.mjs.map +1 -1
  43. package/dist/esm/createZeroClient.recovery.test.native.js +54 -1
  44. package/dist/esm/createZeroClient.recovery.test.native.js.map +1 -1
  45. package/dist/esm/generate-helpers.mjs +27 -1
  46. package/dist/esm/generate-helpers.mjs.map +1 -1
  47. package/dist/esm/generate-helpers.native.js +35 -1
  48. package/dist/esm/generate-helpers.native.js.map +1 -1
  49. package/dist/esm/generate-layout.mjs +52 -54
  50. package/dist/esm/generate-layout.mjs.map +1 -1
  51. package/dist/esm/generate-layout.native.js +52 -55
  52. package/dist/esm/generate-layout.native.js.map +1 -1
  53. package/dist/esm/generate-lite.mjs +29 -1
  54. package/dist/esm/generate-lite.mjs.map +1 -1
  55. package/dist/esm/generate-lite.native.js +90 -34
  56. package/dist/esm/generate-lite.native.js.map +1 -1
  57. package/dist/esm/generate-lite.test.mjs +46 -0
  58. package/dist/esm/generate-lite.test.mjs.map +1 -1
  59. package/dist/esm/generate-lite.test.native.js +46 -0
  60. package/dist/esm/generate-lite.test.native.js.map +1 -1
  61. package/dist/esm/generate.mjs +238 -244
  62. package/dist/esm/generate.mjs.map +1 -1
  63. package/dist/esm/generate.native.js +353 -354
  64. package/dist/esm/generate.native.js.map +1 -1
  65. package/dist/esm/generate.test.mjs +35 -0
  66. package/dist/esm/generate.test.mjs.map +1 -1
  67. package/dist/esm/generate.test.native.js +35 -0
  68. package/dist/esm/generate.test.native.js.map +1 -1
  69. package/dist/esm/helpers/createMutators.mjs +2 -1
  70. package/dist/esm/helpers/createMutators.mjs.map +1 -1
  71. package/dist/esm/helpers/createMutators.native.js +3 -1
  72. package/dist/esm/helpers/createMutators.native.js.map +1 -1
  73. package/dist/esm/helpers/recoverZeroClient.mjs +8 -2
  74. package/dist/esm/helpers/recoverZeroClient.mjs.map +1 -1
  75. package/dist/esm/helpers/recoverZeroClient.native.js +10 -2
  76. package/dist/esm/helpers/recoverZeroClient.native.js.map +1 -1
  77. package/dist/esm/helpers/recoverZeroClient.test.mjs +53 -0
  78. package/dist/esm/helpers/recoverZeroClient.test.mjs.map +1 -1
  79. package/dist/esm/helpers/recoverZeroClient.test.native.js +55 -0
  80. package/dist/esm/helpers/recoverZeroClient.test.native.js.map +1 -1
  81. package/dist/esm/index.native.js.map +1 -1
  82. package/dist/esm/native-typescript.mjs +164 -0
  83. package/dist/esm/native-typescript.mjs.map +1 -0
  84. package/dist/esm/native-typescript.native.js +226 -0
  85. package/dist/esm/native-typescript.native.js.map +1 -0
  86. package/package.json +4 -3
  87. package/src/createZeroClient.recovery.test.tsx +56 -1
  88. package/src/createZeroClient.tsx +54 -15
  89. package/src/generate-helpers.ts +79 -0
  90. package/src/generate-layout.ts +64 -108
  91. package/src/generate-lite.test.ts +49 -0
  92. package/src/generate-lite.ts +44 -0
  93. package/src/generate.test.ts +52 -0
  94. package/src/generate.ts +313 -366
  95. package/src/helpers/createMutators.ts +14 -3
  96. package/src/helpers/recoverZeroClient.test.ts +50 -0
  97. package/src/helpers/recoverZeroClient.ts +12 -2
  98. package/src/native-typescript.ts +176 -0
  99. package/types/createZeroClient.d.ts.map +1 -1
  100. package/types/generate-helpers.d.ts +13 -0
  101. package/types/generate-helpers.d.ts.map +1 -1
  102. package/types/generate-layout.d.ts +2 -1
  103. package/types/generate-layout.d.ts.map +1 -1
  104. package/types/generate-lite.d.ts +1 -0
  105. package/types/generate-lite.d.ts.map +1 -1
  106. package/types/generate.d.ts.map +1 -1
  107. package/types/helpers/createMutators.d.ts.map +1 -1
  108. package/types/helpers/recoverZeroClient.d.ts.map +1 -1
  109. package/types/native-typescript.d.ts +11 -0
  110. package/types/native-typescript.d.ts.map +1 -0
@@ -1,10 +1,13 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
3
3
  import { basename, dirname, resolve } from "node:path";
4
- import { CRUD_MUTATION_NAMES, formatObjectKey, generateAggregatesFile, generateGroupedQueriesFile, generateInstancesFile, generateModelsFile, generateReadmeFile, generateSyncedMutationsFile, generateSyncedQueriesFile, generateTablesFile, generateTypesFile, parseColumnType, parseTypeString, shouldSkipObjectKey } from "./generate-helpers.mjs";
4
+ import * as ts from "typescript/unstable/ast";
5
+ import { ObjectFlags, SymbolFlags, TypeFlags } from "typescript/unstable/async";
6
+ import { CRUD_MUTATION_NAMES, formatObjectKey, generateAggregatesFile, generateGroupedQueriesFile, generateInstancesFile, generateModelsFile, generateReadmeFile, generateSyncedMutationsFile, generateSyncedQueriesFile, generateTablesFile, generateTypesFile, parseColumnType, parseTypeString, renderDrizzleZeroSqliteSchemaModule, shouldSkipObjectKey } from "./generate-helpers.mjs";
5
7
  import { discoverDataLayout, namespaceImportPath } from "./generate-layout.mjs";
8
+ import { collectTypeScriptSourcePaths, createNativeTypeScriptProject } from "./native-typescript.mjs";
6
9
  const hash = s => createHash("sha256").update(s).digest("hex");
7
- const GENERATOR_CACHE_VERSION = "6";
10
+ const GENERATOR_CACHE_VERSION = "7";
8
11
  const isGeneratorSourceFile = name => name.endsWith(".ts") && !name.endsWith(".d.ts") && !name.endsWith(".test.ts") && !name.endsWith(".spec.ts");
9
12
  function hashInputTree(sourceRoots, generatedDir) {
10
13
  const parts = [];
@@ -65,72 +68,37 @@ function saveCache() {
65
68
  }
66
69
  function writeFileIfChanged(filePath, content) {
67
70
  const contentHash = hash(content);
68
- const cachedHash = generateCache[filePath];
69
- if (cachedHash === contentHash && existsSync(filePath)) {
71
+ let onDisk = null;
72
+ try {
73
+ onDisk = readFileSync(filePath, "utf-8");
74
+ } catch {}
75
+ if (onDisk !== null && hash(onDisk) === contentHash) {
76
+ generateCache[filePath] = contentHash;
70
77
  return false;
71
78
  }
72
79
  writeFileSync(filePath, content, "utf-8");
73
80
  generateCache[filePath] = contentHash;
74
81
  return true;
75
82
  }
76
- function createTypeResolver(ts, files, dir) {
77
- const configPath = ts.findConfigFile(dir, ts.sys.fileExists, "tsconfig.json");
78
- let compilerOptions = {
79
- target: ts.ScriptTarget.Latest,
80
- module: ts.ModuleKind.ESNext,
81
- moduleResolution: ts.ModuleResolutionKind.Bundler,
82
- strict: false,
83
- skipLibCheck: true,
84
- noEmit: true
85
- };
86
- if (configPath) {
87
- const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
88
- if (configFile.config) {
89
- const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, dirname(configPath));
90
- compilerOptions = {
91
- ...compilerOptions,
92
- ...parsed.options
93
- };
94
- }
95
- }
96
- const fileMap = /* @__PURE__ */new Map();
97
- for (const f of files) {
98
- fileMap.set(f.path, f.content);
99
- }
100
- const host = ts.createCompilerHost(compilerOptions);
101
- const originalGetSourceFile = host.getSourceFile.bind(host);
102
- host.getSourceFile = (fileName, languageVersion, onError) => {
103
- const content = fileMap.get(fileName);
104
- if (content !== void 0) {
105
- return ts.createSourceFile(fileName, content, languageVersion, true);
106
- }
107
- return originalGetSourceFile(fileName, languageVersion, onError);
108
- };
109
- host.fileExists = fileName => fileMap.has(fileName) || ts.sys.fileExists(fileName);
110
- host.readFile = fileName => fileMap.get(fileName) ?? ts.sys.readFile(fileName);
111
- const program = ts.createProgram(files.map(f => f.path), compilerOptions, host);
112
- const checker = program.getTypeChecker();
83
+ function createTypeResolver(project) {
113
84
  return {
114
- program,
115
- checker,
116
- // resolve a type annotation node to a ts.Type
117
- resolveType(node) {
85
+ project,
86
+ async resolveType(node) {
118
87
  try {
119
- return checker.getTypeFromTypeNode(node);
88
+ return (await project.checker.getTypeFromTypeNode(node)) ?? null;
120
89
  } catch {
121
90
  return null;
122
91
  }
123
92
  },
124
- // convert a resolved type to valibot code
125
- typeToValibot(type) {
126
- return tsTypeToValibot(ts, checker, type);
93
+ async typeToValibot(type) {
94
+ return tsTypeToValibot(project, type);
127
95
  }
128
96
  };
129
97
  }
130
- function resolveParamType(ts, resolver, sourceFile, exportName, paramIndex) {
131
- let result = null;
132
- ts.forEachChild(sourceFile, node => {
133
- if (result) return;
98
+ async function resolveParamType(resolver, sourceFile, exportName, paramIndex) {
99
+ let typeNode = null;
100
+ sourceFile.forEachChild(node => {
101
+ if (typeNode) return;
134
102
  if (!ts.isVariableStatement(node)) return;
135
103
  if (!node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) return;
136
104
  const decl = node.declarationList.declarations[0];
@@ -139,13 +107,13 @@ function resolveParamType(ts, resolver, sourceFile, exportName, paramIndex) {
139
107
  if (decl.initializer && ts.isArrowFunction(decl.initializer)) {
140
108
  const param = decl.initializer.parameters[paramIndex];
141
109
  if (param?.type) {
142
- result = resolver.resolveType(param.type);
110
+ typeNode = param.type;
143
111
  }
144
112
  }
145
113
  });
146
- return result;
114
+ return typeNode ? resolver.resolveType(typeNode) : null;
147
115
  }
148
- function readMutationsCall(ts, call) {
116
+ function readMutationsCall(call) {
149
117
  const args = call.arguments;
150
118
  if (args.length < 2) {
151
119
  const only = args[0];
@@ -171,9 +139,9 @@ function readMutationsCall(ts, call) {
171
139
  handlersArg: handlers && ts.isObjectLiteralExpression(handlers) ? handlers : null
172
140
  };
173
141
  }
174
- function resolveMutationParamTypes(ts, resolver, sourceFile) {
175
- const resolved = /* @__PURE__ */new Map();
176
- ts.forEachChild(sourceFile, node => {
142
+ async function resolveMutationParamTypes(resolver, sourceFile) {
143
+ const nodes = /* @__PURE__ */new Map();
144
+ sourceFile.forEachChild(node => {
177
145
  if (!ts.isVariableStatement(node)) return;
178
146
  if (!node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) return;
179
147
  const decl = node.declarationList.declarations[0];
@@ -182,7 +150,7 @@ function resolveMutationParamTypes(ts, resolver, sourceFile) {
182
150
  if (!decl.initializer || !ts.isCallExpression(decl.initializer)) return;
183
151
  const {
184
152
  handlersArg
185
- } = readMutationsCall(ts, decl.initializer);
153
+ } = readMutationsCall(decl.initializer);
186
154
  if (!handlersArg) return;
187
155
  for (const prop of handlersArg.properties) {
188
156
  if (!ts.isPropertyAssignment(prop) && !ts.isMethodDeclaration(prop)) continue;
@@ -200,17 +168,19 @@ function resolveMutationParamTypes(ts, resolver, sourceFile) {
200
168
  if (!params || params.length < 2) continue;
201
169
  const typeNode = params[1].type;
202
170
  if (!typeNode) continue;
203
- const expanded = resolver.resolveType(typeNode);
204
- if (expanded) {
205
- resolved.set(name, expanded);
206
- }
171
+ nodes.set(name, typeNode);
207
172
  }
208
173
  });
174
+ const resolved = /* @__PURE__ */new Map();
175
+ for (const [name, node] of nodes) {
176
+ const expanded = await resolver.resolveType(node);
177
+ if (expanded) resolved.set(name, expanded);
178
+ }
209
179
  return resolved;
210
180
  }
211
- function extractMutationsFromModel(ts, sourceFile, content, fileName, silent, typeToValibot, resolvedTypes, resolvedTypeToValibot) {
181
+ function extractMutationsFromModel(sourceFile, content, fileName, silent, typeToValibot, resolvedValibot) {
212
182
  let mutateNode = null;
213
- ts.forEachChild(sourceFile, node => {
183
+ sourceFile.forEachChild(node => {
214
184
  if (!ts.isVariableStatement(node)) return;
215
185
  if (!node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) return;
216
186
  const decl = node.declarationList.declarations[0];
@@ -234,12 +204,12 @@ function extractMutationsFromModel(ts, sourceFile, content, fileName, silent, ty
234
204
  hasTable,
235
205
  crud,
236
206
  handlersArg
237
- } = readMutationsCall(ts, call);
207
+ } = readMutationsCall(call);
238
208
  const hasCRUD = hasTable && crud;
239
209
  const columns = {};
240
210
  const primaryKeys = [];
241
211
  if (hasCRUD) {
242
- extractSchemaColumns(ts, sourceFile, columns, primaryKeys);
212
+ extractSchemaColumns(sourceFile, columns, primaryKeys);
243
213
  }
244
214
  const custom = [];
245
215
  if (handlersArg) {
@@ -276,11 +246,8 @@ function extractMutationsFromModel(ts, sourceFile, content, fileName, silent, ty
276
246
  continue;
277
247
  }
278
248
  let valibotCode = typeToValibot(paramType);
279
- if (!valibotCode && resolvedTypes && resolvedTypeToValibot) {
280
- const resolvedType = resolvedTypes.get(name);
281
- if (resolvedType) {
282
- valibotCode = resolvedTypeToValibot(resolvedType);
283
- }
249
+ if (!valibotCode && resolvedValibot) {
250
+ valibotCode = resolvedValibot.get(name) ?? null;
284
251
  }
285
252
  custom.push({
286
253
  name,
@@ -297,7 +264,7 @@ function extractMutationsFromModel(ts, sourceFile, content, fileName, silent, ty
297
264
  custom
298
265
  };
299
266
  }
300
- function extractSchemaColumns(ts, sourceFile, columns, primaryKeys) {
267
+ function extractSchemaColumns(sourceFile, columns, primaryKeys) {
301
268
  function visit(node) {
302
269
  if (ts.isCallExpression(node)) {
303
270
  const text = node.expression.getText(sourceFile);
@@ -322,80 +289,75 @@ function extractSchemaColumns(ts, sourceFile, columns, primaryKeys) {
322
289
  }
323
290
  }
324
291
  }
325
- ts.forEachChild(node, visit);
292
+ node.forEachChild(visit);
326
293
  }
327
294
  visit(sourceFile);
328
295
  }
329
- function getInstantiatedPropertyType(checker, parent, name) {
330
- const resolveProperty = Reflect.get(checker, "getTypeOfPropertyOfType");
331
- if (typeof resolveProperty !== "function") return void 0;
332
- return Reflect.apply(resolveProperty, checker, [parent, name]);
333
- }
334
- function tsTypeToValibot(ts, checker, type, seen) {
335
- if (!seen) seen = /* @__PURE__ */new Set();
336
- const flags = type.getFlags();
337
- if (flags & (ts.TypeFlags.Object | ts.TypeFlags.Intersection)) {
338
- if (seen.has(type)) return "v.unknown()";
339
- seen.add(type);
340
- }
341
- const recurse = t => tsTypeToValibot(ts, checker, t, seen);
342
- if (flags & ts.TypeFlags.String) return "v.string()";
343
- if (flags & ts.TypeFlags.Number) return "v.number()";
344
- if (flags & ts.TypeFlags.Boolean) return "v.boolean()";
345
- if (flags & ts.TypeFlags.Void || flags & ts.TypeFlags.Undefined) return "v.void_()";
346
- if (flags & ts.TypeFlags.Null) return "v.null_()";
347
- if (flags & ts.TypeFlags.Any || flags & ts.TypeFlags.Unknown) return "v.unknown()";
348
- if (flags & ts.TypeFlags.Never) return "v.never()";
349
- if (flags & ts.TypeFlags.TemplateLiteral) return "v.string()";
350
- if (flags & ts.TypeFlags.Object && (checker.isArrayType?.(type) || checker.isArrayLikeType?.(type))) {
351
- const typeArgs = checker.getTypeArguments?.(type) ?? [];
352
- const elementType = typeArgs.length === 1 ? typeArgs[0] : type.getNumberIndexType?.();
353
- return `v.array(${elementType ? recurse(elementType) : "v.unknown()"})`;
354
- }
355
- if (flags & ts.TypeFlags.StringLiteral) {
296
+ async function tsTypeToValibot(project, type, seen = /* @__PURE__ */new Set()) {
297
+ const checker = project.checker;
298
+ const flags = type.flags;
299
+ if (flags & (TypeFlags.Object | TypeFlags.Intersection)) {
300
+ if (seen.has(type.id)) return "v.unknown()";
301
+ seen.add(type.id);
302
+ }
303
+ const recurse = value => tsTypeToValibot(project, value, seen);
304
+ if (flags & TypeFlags.String) return "v.string()";
305
+ if (flags & TypeFlags.Number) return "v.number()";
306
+ if (flags & TypeFlags.Boolean) return "v.boolean()";
307
+ if (flags & TypeFlags.Void || flags & TypeFlags.Undefined) return "v.void_()";
308
+ if (flags & TypeFlags.Null) return "v.null_()";
309
+ if (flags & TypeFlags.Any || flags & TypeFlags.Unknown) return "v.unknown()";
310
+ if (flags & TypeFlags.Never) return "v.never()";
311
+ if (flags & TypeFlags.TemplateLiteral) return "v.string()";
312
+ if (flags & TypeFlags.Object && (await checker.isArrayLikeType(type))) {
313
+ const typeArgs = type.isTypeReference() ? await checker.getTypeArguments(type) : [];
314
+ const indexInfo = (await checker.getIndexInfosOfType(type)).find(info => Boolean(info.keyType.flags & TypeFlags.Number));
315
+ const elementType = typeArgs.length === 1 ? typeArgs[0] : indexInfo?.valueType;
316
+ return `v.array(${elementType ? await recurse(elementType) : "v.unknown()"})`;
317
+ }
318
+ if (type.isStringLiteralType()) {
356
319
  return `v.literal(${JSON.stringify(type.value)})`;
357
320
  }
358
- if (flags & ts.TypeFlags.NumberLiteral) {
321
+ if (type.isNumberLiteralType()) {
359
322
  return `v.literal(${type.value})`;
360
323
  }
361
- if (flags & ts.TypeFlags.BooleanLiteral) {
362
- const name = type.intrinsicName;
363
- return `v.literal(${name === "true"})`;
324
+ if (type.isBooleanLiteralType()) {
325
+ return `v.literal(${type.value})`;
364
326
  }
365
- if (type.isUnion()) {
366
- const members = type.types;
367
- const hasNull = members.some(t => t.getFlags() & ts.TypeFlags.Null);
368
- const hasUndefined = members.some(t => t.getFlags() & (ts.TypeFlags.Undefined | ts.TypeFlags.Void));
369
- const rest = members.filter(t => !(t.getFlags() & (ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.Void)));
370
- if (rest.length === 2 && rest.every(t => t.getFlags() & ts.TypeFlags.BooleanLiteral)) {
327
+ if (type.isUnionType()) {
328
+ const members = await type.getTypes();
329
+ const hasNull = members.some(member => member.flags & TypeFlags.Null);
330
+ const hasUndefined = members.some(member => member.flags & (TypeFlags.Undefined | TypeFlags.Void));
331
+ const rest = members.filter(member => !(member.flags & (TypeFlags.Null | TypeFlags.Undefined | TypeFlags.Void)));
332
+ if (rest.length === 2 && rest.every(member => member.flags & TypeFlags.BooleanLiteral)) {
371
333
  let inner2 = "v.boolean()";
372
334
  if (hasNull) inner2 = `v.nullable(${inner2})`;
373
335
  if (hasUndefined) inner2 = `v.optional(${inner2})`;
374
336
  return inner2;
375
337
  }
376
338
  if (rest.length === 0) return "v.unknown()";
377
- let inner = rest.length === 1 ? recurse(rest[0]) : `v.union([${rest.map(t => recurse(t)).join(", ")}])`;
339
+ let inner = rest.length === 1 ? await recurse(rest[0]) : `v.union([${(await Promise.all(rest.map(recurse))).join(", ")}])`;
378
340
  if (hasNull) inner = `v.nullable(${inner})`;
379
341
  if (hasUndefined) inner = `v.optional(${inner})`;
380
342
  return inner;
381
343
  }
382
- const resolveSymbolType = (parent, prop) => {
383
- const instantiated = getInstantiatedPropertyType(checker, parent, prop.getName());
384
- if (instantiated) return instantiated;
385
- if (prop.valueDeclaration) return checker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
386
- if (prop.declarations?.[0]) return checker.getTypeOfSymbolAtLocation(prop, prop.declarations[0]);
387
- return checker.getDeclaredTypeOfSymbol(prop);
344
+ const resolveSymbolType = async property => {
345
+ const symbolType = await checker.getTypeOfSymbol(property);
346
+ if (symbolType && !symbolType.isErrorType()) return symbolType;
347
+ const declaration = property.valueDeclaration ?? property.declarations[0];
348
+ const node = declaration ? await declaration.resolve(project) : void 0;
349
+ if (node) return checker.getTypeOfSymbolAtLocation(property, node);
350
+ return checker.getDeclaredTypeOfSymbol(property);
388
351
  };
389
- if (type.isIntersection()) {
390
- const props2 = type.getProperties();
391
- if (props2.length === 0) return "v.object({})";
352
+ const objectToValibot = async props => {
353
+ if (props.length === 0) return "v.object({})";
392
354
  const entries = [];
393
- for (const prop of props2) {
394
- const name = prop.getName();
355
+ for (const prop of props) {
356
+ const name = prop.name;
395
357
  if (shouldSkipObjectKey(name)) continue;
396
- const propType = resolveSymbolType(type, prop);
397
- const isOptional = !!(prop.getFlags() & ts.SymbolFlags.Optional);
398
- let val = recurse(propType);
358
+ const propType = await resolveSymbolType(prop);
359
+ const isOptional = Boolean(prop.flags & SymbolFlags.Optional);
360
+ let val = await recurse(propType);
399
361
  if (isOptional && !val.startsWith("v.optional(")) {
400
362
  val = `v.optional(${val})`;
401
363
  }
@@ -405,47 +367,32 @@ function tsTypeToValibot(ts, checker, type, seen) {
405
367
  return `v.object({
406
368
  ${entries.join(",\n ")},
407
369
  })`;
408
- }
409
- const props = type.getProperties();
410
- if (props.length > 0 && (type.getFlags() & ts.TypeFlags.Object || type.objectFlags)) {
411
- const objectFlags = type.objectFlags ?? 0;
412
- if (objectFlags & ts.ObjectFlags.Reference) {
413
- const typeRef = type;
414
- const symbol = type.getSymbol();
415
- const name = symbol?.getName();
416
- if ((name === "Array" || name === "ReadonlyArray") && typeRef.typeArguments?.length === 1) {
417
- return `v.array(${recurse(typeRef.typeArguments[0])})`;
370
+ };
371
+ if (type.isIntersectionType()) {
372
+ return objectToValibot(await checker.getPropertiesOfType(type));
373
+ }
374
+ if (type.isTupleType()) {
375
+ const typeArgs = await checker.getTypeArguments(type);
376
+ return `v.tuple([${(await Promise.all(typeArgs.map(recurse))).join(", ")}])`;
377
+ }
378
+ if (type.isObjectType()) {
379
+ if (type.objectFlags & ObjectFlags.Reference && type.isTypeReference()) {
380
+ const symbol = await type.getSymbol();
381
+ const typeArgs = await checker.getTypeArguments(type);
382
+ if ((symbol?.name === "Array" || symbol?.name === "ReadonlyArray") && typeArgs.length === 1) {
383
+ return `v.array(${await recurse(typeArgs[0])})`;
418
384
  }
419
385
  }
420
- if (objectFlags & ts.ObjectFlags.Tuple) {
421
- const typeRef = type;
422
- const typeArgs = typeRef.typeArguments || [];
423
- return `v.tuple([${typeArgs.map(t => recurse(t)).join(", ")}])`;
386
+ const props = await checker.getPropertiesOfType(type);
387
+ if (props.length > 0) return objectToValibot(props);
388
+ }
389
+ for (const indexInfo of await checker.getIndexInfosOfType(type)) {
390
+ if (indexInfo.keyType.flags & TypeFlags.String) {
391
+ return `v.record(v.string(), ${await recurse(indexInfo.valueType)})`;
424
392
  }
425
- const entries = [];
426
- for (const prop of props) {
427
- const name = prop.getName();
428
- if (shouldSkipObjectKey(name)) continue;
429
- const propType = resolveSymbolType(type, prop);
430
- const isOptional = !!(prop.getFlags() & ts.SymbolFlags.Optional);
431
- let val = recurse(propType);
432
- if (isOptional && !val.startsWith("v.optional(")) {
433
- val = `v.optional(${val})`;
434
- }
435
- entries.push(`${formatObjectKey(name)}: ${val}`);
393
+ if (indexInfo.keyType.flags & TypeFlags.Number) {
394
+ return `v.record(v.number(), ${await recurse(indexInfo.valueType)})`;
436
395
  }
437
- if (entries.length === 0) return "v.object({})";
438
- return `v.object({
439
- ${entries.join(",\n ")},
440
- })`;
441
- }
442
- const stringIndex = type.getStringIndexType();
443
- if (stringIndex) {
444
- return `v.record(v.string(), ${recurse(stringIndex)})`;
445
- }
446
- const numberIndex = type.getNumberIndexType();
447
- if (numberIndex) {
448
- return `v.record(v.number(), ${recurse(numberIndex)})`;
449
396
  }
450
397
  return "v.unknown()";
451
398
  }
@@ -536,24 +483,41 @@ function dataMembershipFromLayout(layout) {
536
483
  allTables: [...new Set(layout.instances.flatMap(instance => [...instance.syncTables, ...instance.supportTables]))].sort()
537
484
  };
538
485
  }
486
+ async function loadGeneratorProject(baseDir) {
487
+ const sourcePaths = collectTypeScriptSourcePaths([baseDir, resolve(dirname(baseDir), "database")]);
488
+ return createNativeTypeScriptProject(baseDir, sourcePaths);
489
+ }
539
490
  async function deriveDataMembership(options) {
540
- const ts = await import("typescript");
541
- const layout = discoverDataLayout(ts, resolve(options.dir), options.config ? resolve(options.config) : void 0);
542
- return dataMembershipFromLayout(layout);
491
+ const baseDir = resolve(options.dir);
492
+ const project = await loadGeneratorProject(baseDir);
493
+ try {
494
+ const layout = discoverDataLayout(project, baseDir, options.config ? resolve(options.config) : void 0);
495
+ return dataMembershipFromLayout(layout);
496
+ } finally {
497
+ await project.close();
498
+ }
543
499
  }
544
500
  async function generateDrizzleSchemaInputFile(options) {
545
- const ts = await import("typescript");
546
501
  const baseDir = resolve(options.dir);
547
- const layout = discoverDataLayout(ts, baseDir, options.config ? resolve(options.config) : void 0);
502
+ const project = await loadGeneratorProject(baseDir);
503
+ try {
504
+ return generateDrizzleSchemaInputFileWithProject(options, project);
505
+ } finally {
506
+ await project.close();
507
+ }
508
+ }
509
+ async function generateDrizzleSchemaInputFileWithProject(options, project) {
510
+ const baseDir = resolve(options.dir);
511
+ const layout = discoverDataLayout(project, baseDir, options.config ? resolve(options.config) : void 0);
548
512
  const tableNames = dataMembershipFromLayout(layout).allTables;
549
513
  const relationsPath = layout.metadataPaths.find(path => basename(path) === "relations.ts" && dirname(path) === resolve(dirname(baseDir), "database")) ?? layout.metadataPaths.find(path => basename(path) === "relations.ts");
550
514
  const relationEntries = [];
551
515
  if (relationsPath) {
552
- const source = ts.createSourceFile(relationsPath, readFileSync(relationsPath, "utf8"), ts.ScriptTarget.Latest, true);
516
+ const source = project.sourceFile(relationsPath);
553
517
  const included = new Set(tableNames);
554
518
  const visit = node => {
555
519
  if (!ts.isCallExpression(node) || node.expression.getText(source) !== "defineRelations") {
556
- ts.forEachChild(node, visit);
520
+ node.forEachChild(visit);
557
521
  return;
558
522
  }
559
523
  const factory = node.arguments[1];
@@ -587,6 +551,14 @@ ${relations.map(relation => ` ${relation},`).join("\n")}
587
551
  return ["// auto-generated from the on-zero data layout", `import { defineRelations } from 'drizzle-orm'`, `import * as schema from ${schemaImportPath}`, "", `export { ${tableNames.join(", ")} } from ${schemaImportPath}`, `export const relations = defineRelations(schema, (r) => ({`, ...relationEntries, `}))`, ""].join("\n");
588
552
  }
589
553
  async function generate(options) {
554
+ const project = await loadGeneratorProject(resolve(options.dir));
555
+ try {
556
+ return await generateWithProject(options, project);
557
+ } finally {
558
+ await project.close();
559
+ }
560
+ }
561
+ async function generateWithProject(options, project) {
590
562
  const {
591
563
  dir,
592
564
  after,
@@ -602,11 +574,41 @@ async function generate(options) {
602
574
  });
603
575
  }
604
576
  loadCache();
605
- const ts = await import("typescript");
606
- const layout = discoverDataLayout(ts, baseDir, config ? resolve(config) : void 0);
577
+ const layout = discoverDataLayout(project, baseDir, config ? resolve(config) : void 0);
607
578
  const metadataHash = hash(layout.metadataPaths.map(path => `${path}\0${readFileSync(path, "utf8")}`).join("\0"));
579
+ const databaseSchemaPath = resolve(dirname(baseDir), "database/schema.ts");
580
+ const needsSqliteZeroSchema = existsSync(databaseSchemaPath);
608
581
  const inputHash = hash(`${hashInputTree(layout.sourceRoots, generatedDir)}\0${metadataHash}`);
609
- if (!force && generateCache.__generatorVersion === GENERATOR_CACHE_VERSION && generateCache.__inputHash === inputHash && existsSync(resolve(generatedDir, "models.ts"))) {
582
+ if (!force && generateCache.__generatorVersion === GENERATOR_CACHE_VERSION && generateCache.__inputHash === inputHash &&
583
+ // the input hash deliberately skips the generated directory, so it cannot
584
+ // notice that the outputs themselves moved. existence is not correctness
585
+ // either: a checkout, merge or revert leaves every file present and stale.
586
+ // confirm the bytes on disk still match what the cache says it wrote there
587
+ // before skipping the build, or the fast path serves someone else's output.
588
+ //
589
+ // a consumer that formats generated output after this runs never hits the
590
+ // fast path again, because the cache holds the bytes written here and disk
591
+ // holds the formatted ones. soot does exactly that, `on-zero generate &&
592
+ // generate-instance-tables && oxfmt src/data/generated/`, and the number is
593
+ // already taken: 2.50s median before, 3.69s after, four warm runs each in
594
+ // one checkout with only the pin swapped, so about 1.2 seconds or 47
595
+ // percent on this step. the full three-command script went 2.65s to 3.14s
596
+ // with a spread that swallows most of it.
597
+ //
598
+ // the shape matters more than the number. this is not a cache that hits
599
+ // less often, it is a cache that never hits, so the cost does not degrade
600
+ // gracefully and grows with the generated output rather than staying at
601
+ // 1.2s. correct and slower beats fast and stale, which is why it is written
602
+ // this way, but if generate time ever becomes the complaint, record the
603
+ // hash after the consumer's formatter runs rather than weakening this.
604
+ Object.entries(generateCache).every(([cachedPath, cachedHash]) => {
605
+ if (!cachedPath.startsWith(generatedDir)) return true;
606
+ try {
607
+ return hash(readFileSync(cachedPath, "utf-8")) === cachedHash;
608
+ } catch {
609
+ return false;
610
+ }
611
+ }) && existsSync(resolve(generatedDir, "models.ts")) && (!needsSqliteZeroSchema || existsSync(resolve(generatedDir, "schema.ts")))) {
610
612
  let counts = {};
611
613
  try {
612
614
  counts = JSON.parse(generateCache.__counts || "{}");
@@ -635,6 +637,24 @@ async function generate(options) {
635
637
  // managed externally and should not be overwritten.
636
638
  ...(filesWithSchema.length > 0 ? [writeFileIfChanged(resolve(generatedDir, "types.ts"), generateTypesFile(schemaModules.map(module => module.name))), writeFileIfChanged(resolve(generatedDir, "tables.ts"), generateTablesFile(schemaModules))] : []), writeFileIfChanged(resolve(generatedDir, "README.md"), generateReadmeFile())];
637
639
  let filesChanged = writeResults.filter(Boolean).length;
640
+ if (needsSqliteZeroSchema) {
641
+ const membership = dataMembershipFromLayout(layout);
642
+ const drizzleSchema = await generateDrizzleSchemaInputFileWithProject({
643
+ dir: baseDir,
644
+ schemaImportPath: "../../database/schema",
645
+ config
646
+ }, project);
647
+ const sqliteSchema = renderDrizzleZeroSqliteSchemaModule({
648
+ importPath: "./drizzleSchema",
649
+ tableNames: membership.allTables
650
+ });
651
+ if (writeFileIfChanged(resolve(generatedDir, "drizzleSchema.ts"), drizzleSchema)) {
652
+ filesChanged++;
653
+ }
654
+ if (writeFileIfChanged(resolve(generatedDir, "schema.ts"), sqliteSchema)) {
655
+ filesChanged++;
656
+ }
657
+ }
638
658
  let queryCount = 0;
639
659
  let mutationCount = 0;
640
660
  const typeToValibot = paramType => {
@@ -645,23 +665,12 @@ async function generate(options) {
645
665
  }
646
666
  };
647
667
  const allQueries = [];
648
- let queryResolver = null;
649
- const getQueryResolver = () => {
650
- if (!queryResolver) {
651
- const allFiles = [...new Set(layout.namespaces.flatMap(namespace => namespace.sourcePaths))].map(path => ({
652
- path,
653
- content: readFileSync(path, "utf-8")
654
- }));
655
- queryResolver = createTypeResolver(ts, allFiles, baseDir);
656
- }
657
- return queryResolver;
658
- };
659
668
  for (const namespace of layout.namespaces.filter(namespace2 => namespace2.queryPath !== null)) {
660
669
  const filePath = namespace.queryPath;
661
670
  try {
662
- const content = readFileSync(filePath, "utf-8");
663
- const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
664
- ts.forEachChild(sourceFile, node => {
671
+ const sourceFile = project.sourceFile(filePath);
672
+ const candidates = [];
673
+ sourceFile.forEachChild(node => {
665
674
  if (ts.isVariableStatement(node)) {
666
675
  const exportModifier = node.modifiers?.find(m => m.kind === ts.SyntaxKind.ExportKeyword);
667
676
  if (!exportModifier) return;
@@ -676,31 +685,33 @@ async function generate(options) {
676
685
  const param = params[0];
677
686
  paramType = param.type?.getText(sourceFile) || "unknown";
678
687
  }
679
- let valibotCode = typeToValibot(paramType);
680
- if (!valibotCode && params.length > 0 && params[0].type) {
681
- const resolver = getQueryResolver();
682
- const resolverSourceFile = resolver.program.getSourceFile(filePath);
683
- if (resolverSourceFile) {
684
- const resolvedType = resolveParamType(ts, resolver, resolverSourceFile, name, 0);
685
- if (resolvedType) {
686
- valibotCode = resolver.typeToValibot(resolvedType);
687
- }
688
- }
689
- }
690
- if (valibotCode) {
691
- allQueries.push({
692
- name,
693
- params: paramType,
694
- valibotCode,
695
- sourceFile: namespace.name,
696
- importPath: namespaceImportPath(baseDir, filePath)
697
- });
698
- } else if (!silent && paramType !== "void") {
699
- console.error(`\u2717 ${name}: could not resolve type "${paramType}"`);
700
- }
688
+ candidates.push({
689
+ name,
690
+ paramType,
691
+ typeNode: params[0]?.type ?? null
692
+ });
701
693
  }
702
694
  }
703
695
  });
696
+ const resolver = createTypeResolver(project.projectForFile(filePath));
697
+ for (const candidate of candidates) {
698
+ let valibotCode = typeToValibot(candidate.paramType);
699
+ if (!valibotCode && candidate.typeNode) {
700
+ const resolvedType = await resolveParamType(resolver, sourceFile, candidate.name, 0);
701
+ if (resolvedType) valibotCode = await resolver.typeToValibot(resolvedType);
702
+ }
703
+ if (valibotCode) {
704
+ allQueries.push({
705
+ name: candidate.name,
706
+ params: candidate.paramType,
707
+ valibotCode,
708
+ sourceFile: namespace.name,
709
+ importPath: namespaceImportPath(baseDir, filePath)
710
+ });
711
+ } else if (!silent && candidate.paramType !== "void") {
712
+ console.error(`\u2717 ${candidate.name}: could not resolve type "${candidate.paramType}"`);
713
+ }
714
+ }
704
715
  } catch (err) {
705
716
  if (!silent) console.error(`Error processing ${filePath}:`, err);
706
717
  }
@@ -729,20 +740,14 @@ async function generate(options) {
729
740
  if (aggregatesChanged) filesChanged++;
730
741
  }
731
742
  const allModelMutations = [];
732
- const mutationFiles = [];
733
743
  const unresolvedModels = [];
734
744
  for (const namespace of modelNamespaces) {
735
745
  const filePath = namespace.modelPath;
736
746
  const fileBaseName = namespace.name;
737
747
  try {
738
748
  const content = readFileSync(filePath, "utf-8");
739
- mutationFiles.push({
740
- path: filePath,
741
- content,
742
- baseName: fileBaseName
743
- });
744
- const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
745
- const result = extractMutationsFromModel(ts, sourceFile, content, filePath, !!silent, typeToValibot);
749
+ const sourceFile = project.sourceFile(filePath);
750
+ const result = extractMutationsFromModel(sourceFile, content, filePath, !!silent, typeToValibot);
746
751
  if (result) {
747
752
  result.modelName = fileBaseName;
748
753
  allModelMutations.push(result);
@@ -759,36 +764,20 @@ async function generate(options) {
759
764
  }
760
765
  }
761
766
  if (unresolvedModels.length > 0) {
762
- const collectTsFiles = dir2 => {
763
- const results = [];
764
- for (const entry of readdirSync(dir2, {
765
- withFileTypes: true
766
- })) {
767
- const fullPath = resolve(dir2, entry.name);
768
- if (entry.isDirectory() && entry.name !== "node_modules") {
769
- results.push(...collectTsFiles(fullPath));
770
- } else if (entry.isFile() && isGeneratorSourceFile(entry.name)) {
771
- results.push({
772
- path: fullPath,
773
- content: readFileSync(fullPath, "utf-8")
774
- });
775
- }
776
- }
777
- return results;
778
- };
779
- const allFiles = [...new Map(layout.sourceRoots.flatMap(collectTsFiles).map(file => [file.path, file])).values()];
780
- const modelResolver = createTypeResolver(ts, allFiles, baseDir);
781
767
  for (const {
782
768
  baseName,
783
769
  filePath
784
770
  } of unresolvedModels) {
785
- const resolverSourceFile = modelResolver.program.getSourceFile(filePath);
786
- if (!resolverSourceFile) continue;
787
- const resolvedTypes = resolveMutationParamTypes(ts, modelResolver, resolverSourceFile);
771
+ const sourceFile = project.sourceFile(filePath);
772
+ const modelResolver = createTypeResolver(project.projectForFile(filePath));
773
+ const resolvedTypes = await resolveMutationParamTypes(modelResolver, sourceFile);
788
774
  if (resolvedTypes.size === 0) continue;
775
+ const resolvedValibot = /* @__PURE__ */new Map();
776
+ for (const [name, type] of resolvedTypes) {
777
+ resolvedValibot.set(name, await modelResolver.typeToValibot(type));
778
+ }
789
779
  const content = readFileSync(filePath, "utf-8");
790
- const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
791
- const result = extractMutationsFromModel(ts, sourceFile, content, filePath, !!silent, typeToValibot, resolvedTypes, modelResolver.typeToValibot);
780
+ const result = extractMutationsFromModel(sourceFile, content, filePath, !!silent, typeToValibot, resolvedValibot);
792
781
  if (result) {
793
782
  result.modelName = baseName;
794
783
  const idx = allModelMutations.findIndex(m => m.modelName === baseName);
@@ -866,8 +855,13 @@ ${event} ${path}`);
866
855
  }, debounce);
867
856
  };
868
857
  const databaseDir = resolve(dirname(baseDir), "database");
869
- const ts = await import("typescript");
870
- const layout = discoverDataLayout(ts, baseDir, options.config ? resolve(options.config) : void 0);
858
+ const project = await loadGeneratorProject(baseDir);
859
+ let layout;
860
+ try {
861
+ layout = discoverDataLayout(project, baseDir, options.config ? resolve(options.config) : void 0);
862
+ } finally {
863
+ await project.close();
864
+ }
871
865
  const watcher = chokidar.watch([... /* @__PURE__ */new Set([...layout.sourceRoots, ...(existsSync(databaseDir) ? [databaseDir] : [])])], {
872
866
  persistent: true,
873
867
  ignoreInitial: true,