@supalive/codegen 1.8.0 → 1.9.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/README.md CHANGED
@@ -85,6 +85,154 @@ const { files } = generate({ entry: "src/server/procedures.ts" });
85
85
  `extractClient` (build the intermediate representation) and `emit` (turn the IR
86
86
  into files) are also exported for advanced use.
87
87
 
88
+ ## Return type overrides
89
+
90
+ By default the codegen infers everything from TypeScript types: `number` →
91
+ `double`, `bigint` → `BigInt`, `Date` → `DateTime`, etc. The `returns` property
92
+ on procedure configs lets you override specific fields for the generated client
93
+ without changing your handler's return type.
94
+
95
+ Model naming uses the `.modelName()` Zod extension method — no extra imports
96
+ needed (it patches all Zod types when you import from `@supalive/core/procedure`).
97
+
98
+ ### Default (no `returns`)
99
+
100
+ Everything inferred from the handler's TS return type:
101
+
102
+ ```ts
103
+ const listCards = query({
104
+ args: z.object({ limit: z.number().int().default(20) }),
105
+ handler: async (_ctx, _input): Promise<{ items: { id: string; name: string }[] }> => ({
106
+ items: [],
107
+ }),
108
+ });
109
+ // → ListCardsResult { items: List<ListCardsItem> }
110
+ ```
111
+
112
+ ### `.modelName("Name")` — rename a model
113
+
114
+ Give a generated model a custom name instead of the auto-derived one:
115
+
116
+ ```ts
117
+ const listCards = query({
118
+ args: z.object({ limit: z.number().int().default(20) }),
119
+ returns: z.array(
120
+ z.object({ id: z.string(), name: z.string() }).modelName("CardItem")
121
+ ),
122
+ handler: async (_ctx, _input) => [] as { id: string; name: string }[],
123
+ });
124
+ // → result type: List<CardItem> (not List<ListCardsResultItem>)
125
+ ```
126
+
127
+ `.modelName("Foo")` on any Zod schema tells the codegen to rename the
128
+ model to `Foo`. Combine with field-level overrides like `z.number().int()` to
129
+ also patch the TS-inferred types.
130
+
131
+ ### Partial field overrides (implicit)
132
+
133
+ Only the fields you list in the `returns` schema are overridden; the rest stay
134
+ inferred from TS. No `partial()` wrapper needed:
135
+
136
+ ```ts
137
+ const storeCards = query({
138
+ args: z.object({}),
139
+ returns: z.object({
140
+ total: z.number().int(), // override: number → int
141
+ // 'cards' is NOT listed → stays inferred from TS
142
+ }),
143
+ handler: async (_ctx, _input): Promise<{
144
+ cards: { id: string; name: string; balance: number }[];
145
+ total: number;
146
+ }> => ({ cards: [], total: 0 }),
147
+ });
148
+ // → StoreCardsResult { cards: List<...>, total: int }
149
+ ```
150
+
151
+ ### Combining `.modelName()` + `.int()`
152
+
153
+ The most common pattern: rename nested models and patch numeric types while
154
+ keeping the parent result inferred from TS:
155
+
156
+ ```ts
157
+ const storeCards = query({
158
+ args: z.object({}),
159
+ returns: z.object({
160
+ value: z.array(
161
+ z.object({
162
+ balanceCents: z.number().int(), // double → int
163
+ }).modelName("StoreCardItem")
164
+ ),
165
+ }),
166
+ handler: async (ctx, input) => {
167
+ const cards = await ctx.db.query(GiftCardSchema).select().get();
168
+ return {
169
+ cursor: "...",
170
+ value: cards,
171
+ };
172
+ },
173
+ });
174
+ // → StoreCardsResult { cursor: String, value: List<StoreCardItem> }
175
+ // StoreCardItem { id, balanceCents: int, ... }
176
+ ```
177
+
178
+ ### Procedure-level list return with model naming
179
+
180
+ When a procedure returns a bare list (not wrapped in an object), use
181
+ `z.array()` at the top level of `returns`:
182
+
183
+ ```ts
184
+ const cardLedger = query({
185
+ args: z.object({ cardId: z.string() }),
186
+ returns: z.array(
187
+ z.object({
188
+ amountCents: z.number().int(),
189
+ balanceAfterCents: z.number().int(),
190
+ }).modelName("CardLedgerItem")
191
+ ),
192
+ handler: async (ctx, { cardId }) =>
193
+ ctx.db.query(CardTransactionSchema).select()
194
+ .where((f) => f.eq("cardId", cardId)).get(),
195
+ });
196
+ // → result type: List<CardLedgerItem>
197
+ // CardLedgerItem { ..., amountCents: int, balanceAfterCents: int, ... }
198
+ ```
199
+
200
+ ### Nested model overrides
201
+
202
+ Override fields inside a list element's model by nesting `.modelName()` +
203
+ field overrides:
204
+
205
+ ```ts
206
+ const nestedOverride = query({
207
+ args: z.object({}),
208
+ returns: z.object({
209
+ items: z.array(
210
+ z.object({
211
+ id: z.string(),
212
+ balance: z.number().int(), // override int
213
+ name: z.string(),
214
+ }).modelName("NestedItem")
215
+ ),
216
+ }),
217
+ handler: async (_ctx, _input) => ({
218
+ items: [] as { id: string; balance: number; name: string }[],
219
+ }),
220
+ });
221
+ // → NestedItem { id: String, balance: int, name: String }
222
+ ```
223
+
224
+ ### Summary of helpers
225
+
226
+ | Helper | Purpose | Example |
227
+ | --- | --- | --- |
228
+ | `.modelName("Name")` | Rename a model in generated output | `z.object({ ... }).modelName("CardItem")` |
229
+ | `z.number().int()` | Patch a field's type from `double` to `int` | `z.object({ balance: z.number().int() })` |
230
+ | `z.bigint()` | Patch a field's type to `BigInt` | `z.object({ revision: z.bigint() })` |
231
+
232
+ All `returns` helpers are purely for codegen — they have no effect at runtime.
233
+ The Zod schemas are not used for runtime validation of the handler's return
234
+ value.
235
+
88
236
  ## License
89
237
 
90
238
  MIT
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as generateToDisk, r as readEntryConfig } from "./src-BRGy8CeY.js";
2
+ import { n as generateToDisk, r as readEntryConfig } from "./src-C8EGEwt7.js";
3
3
  import path from "node:path";
4
4
  //#region src/cli.ts
5
5
  function parseArgs(argv) {
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as emit, c as loadSchemaEnums, i as resolveConfig, n as generateToDisk, o as extractClient, r as readEntryConfig, s as SchemaEnumRegistry, t as generate } from "./src-BRGy8CeY.js";
1
+ import { a as emit, c as loadSchemaEnums, i as resolveConfig, n as generateToDisk, o as extractClient, r as readEntryConfig, s as SchemaEnumRegistry, t as generate } from "./src-C8EGEwt7.js";
2
2
  export { SchemaEnumRegistry, emit, extractClient, generate, generateToDisk, loadSchemaEnums, readEntryConfig, resolveConfig };
@@ -39,6 +39,20 @@ function procedureDocText(prop) {
39
39
  const doc = stmt ? jsDocText(stmt) : void 0;
40
40
  if (doc) return doc;
41
41
  }
42
+ for (const def of idNode.getDefinitionNodes()) if (Node.isImportSpecifier(def)) {
43
+ const sourceFile = def.getImportDeclaration().getModuleSpecifierSourceFile();
44
+ if (sourceFile) {
45
+ const exported = sourceFile.getExportedDeclarations().get(def.getName());
46
+ if (exported && exported.length > 0) {
47
+ const decl = exported[0];
48
+ if (Node.isVariableDeclaration(decl)) {
49
+ const stmt = decl.getVariableStatement();
50
+ const doc = stmt ? jsDocText(stmt) : void 0;
51
+ if (doc) return doc;
52
+ }
53
+ }
54
+ }
55
+ }
42
56
  }
43
57
  }
44
58
  /** The trimmed description of a node's last JSDoc block, if any. */
@@ -66,6 +80,139 @@ function scanZodArgMeta(routerDecl) {
66
80
  } catch {}
67
81
  return result;
68
82
  }
83
+ /** From a `query({ returns: ..., handler })` call, get the output schema. */
84
+ function getOutputSchema(call) {
85
+ if (!Node.isCallExpression(call)) return void 0;
86
+ const arg = call.getArguments()[0];
87
+ if (!arg || !Node.isObjectLiteralExpression(arg)) return void 0;
88
+ const returnsProp = arg.getProperty("returns");
89
+ if (!returnsProp || !Node.isPropertyAssignment(returnsProp)) return void 0;
90
+ return returnsProp.getInitializer();
91
+ }
92
+ /**
93
+ * Scan each procedure's `returns` Zod schema into a structured metadata tree.
94
+ * Returns `Map<procName, OutputSchemaMeta>`.
95
+ */
96
+ function scanZodOutputMeta(routerDecl) {
97
+ const result = /* @__PURE__ */ new Map();
98
+ try {
99
+ const proceduresObj = getProceduresObjectLiteral(routerDecl);
100
+ if (!proceduresObj) return result;
101
+ for (const prop of proceduresObj.getProperties()) {
102
+ const key = getPropertyKey(prop);
103
+ if (!key) continue;
104
+ const call = resolveProcedureCall(prop);
105
+ if (!call) continue;
106
+ const outputSchema = getOutputSchema(call);
107
+ if (!outputSchema) continue;
108
+ const meta = scanOutputSchemaNode(outputSchema);
109
+ if (meta) result.set(key, meta);
110
+ }
111
+ } catch {}
112
+ return result;
113
+ }
114
+ /**
115
+ * Recursively scan a Zod schema expression, extracting field metadata and
116
+ * `__genType` model names. Returns `null` if no metadata found.
117
+ */
118
+ function scanOutputSchemaNode(node) {
119
+ if (!node) return null;
120
+ let resolved = node;
121
+ if (Node.isIdentifier(resolved)) {
122
+ const init = resolveIdentifierInit(resolved);
123
+ if (init) resolved = init;
124
+ }
125
+ let modelName;
126
+ let isObject = false;
127
+ let baseCallNode;
128
+ let arrayElementNode;
129
+ let cur = resolved;
130
+ while (cur && Node.isCallExpression(cur)) {
131
+ const expr = cur.getExpression();
132
+ if (Node.isPropertyAccessExpression(expr)) {
133
+ const method = expr.getName();
134
+ if (method === "meta") {
135
+ const metaArg = cur.getArguments()[0];
136
+ if (metaArg && Node.isObjectLiteralExpression(metaArg)) {
137
+ const genTypeProp = metaArg.getProperty("__genType");
138
+ if (genTypeProp && Node.isPropertyAssignment(genTypeProp)) {
139
+ const init = genTypeProp.getInitializer();
140
+ if (init && Node.isStringLiteral(init)) modelName = init.getLiteralValue();
141
+ }
142
+ }
143
+ cur = expr.getExpression();
144
+ } else if (method === "modelName") {
145
+ const nameArg = cur.getArguments()[0];
146
+ if (nameArg && Node.isStringLiteral(nameArg)) modelName = nameArg.getLiteralValue();
147
+ cur = expr.getExpression();
148
+ } else if (method === "array") {
149
+ arrayElementNode = cur.getArguments()[0];
150
+ cur = expr.getExpression();
151
+ } else if (/^(strict|loose)?[oO]bject$/.test(method)) {
152
+ isObject = true;
153
+ baseCallNode = cur;
154
+ break;
155
+ } else cur = expr.getExpression();
156
+ } else break;
157
+ }
158
+ if (!isObject && cur && Node.isCallExpression(cur)) {
159
+ const expr = cur.getExpression();
160
+ if (Node.isIdentifier(expr) || Node.isPropertyAccessExpression(expr)) {
161
+ const exprText = expr.getText();
162
+ if (/(^|\.)(strict|loose)?[oO]bject$/.test(exprText)) {
163
+ isObject = true;
164
+ baseCallNode = cur;
165
+ }
166
+ }
167
+ }
168
+ if (isObject && baseCallNode && Node.isCallExpression(baseCallNode)) {
169
+ const arg = baseCallNode.getArguments()[0];
170
+ if (!arg || !Node.isObjectLiteralExpression(arg)) return modelName ? {
171
+ fieldMeta: /* @__PURE__ */ new Map(),
172
+ modelName,
173
+ nested: /* @__PURE__ */ new Map()
174
+ } : null;
175
+ const fieldMeta = /* @__PURE__ */ new Map();
176
+ const nested = /* @__PURE__ */ new Map();
177
+ for (const prop of arg.getProperties()) {
178
+ if (!Node.isPropertyAssignment(prop)) continue;
179
+ const key = prop.getName();
180
+ const init = prop.getInitializer();
181
+ if (!init) continue;
182
+ const methods = topLevelZodMethods(init);
183
+ const meta = {};
184
+ if (methods.has("bigint")) meta.numeric = "bigint";
185
+ else if (methods.has("int")) meta.numeric = "int";
186
+ if (methods.has("default") || methods.has("catch") || methods.has("prefault")) meta.optional = true;
187
+ const description = topLevelZodDescription(init);
188
+ if (description) meta.description = description;
189
+ if (meta.numeric || meta.optional || meta.description) fieldMeta.set(key, meta);
190
+ const nestedMeta = scanOutputSchemaNode(init);
191
+ if (nestedMeta) nested.set(key, nestedMeta);
192
+ }
193
+ return {
194
+ fieldMeta,
195
+ modelName,
196
+ nested
197
+ };
198
+ }
199
+ if (arrayElementNode) {
200
+ const elementMeta = scanOutputSchemaNode(arrayElementNode);
201
+ if (elementMeta || modelName) return {
202
+ fieldMeta: /* @__PURE__ */ new Map(),
203
+ modelName,
204
+ nested: /* @__PURE__ */ new Map([["__element", elementMeta ?? {
205
+ fieldMeta: /* @__PURE__ */ new Map(),
206
+ nested: /* @__PURE__ */ new Map()
207
+ }]])
208
+ };
209
+ }
210
+ return modelName ? {
211
+ fieldMeta: /* @__PURE__ */ new Map(),
212
+ modelName,
213
+ nested: /* @__PURE__ */ new Map()
214
+ } : null;
215
+ }
69
216
  function getProceduresObjectLiteral(routerDecl) {
70
217
  let init;
71
218
  if (Node.isVariableDeclaration(routerDecl)) init = routerDecl.getInitializer();
@@ -109,9 +256,6 @@ function followToCall(node, depth) {
109
256
  /**
110
257
  * Given an Identifier that may be an import binding, follow through the import
111
258
  * chain to the actual exported declaration's initializer node.
112
- *
113
- * Handles: `import { listItems } from "./procs.js"` → the exported VariableDeclaration
114
- * `import { x } from "./reexport.js"` where that file re-exports → recursive follow
115
259
  */
116
260
  function followImport(id) {
117
261
  const sourceFile = id.getSourceFile();
@@ -153,9 +297,6 @@ function followImport(id) {
153
297
  }
154
298
  }
155
299
  }
156
- /** Extract the initializer expression from a definition node, unwrapping any
157
- * container nodes (VariableStatement, ExportNamedDeclaration, etc.) that
158
- * ts-morph may return for cross-file imports. */
159
300
  function definitionInitializer(def) {
160
301
  if (Node.isVariableDeclaration(def)) return def.getInitializer();
161
302
  if (Node.isVariableStatement(def)) for (const d of def.getDeclarationList().getDeclarations()) {
@@ -167,7 +308,6 @@ function definitionInitializer(def) {
167
308
  if (init) return init;
168
309
  }
169
310
  }
170
- /** Resolve an Identifier to its initializer, following definitions across files. */
171
311
  function resolveIdentifierInit(id) {
172
312
  if (!Node.isIdentifier(id)) return void 0;
173
313
  for (const def of id.getDefinitionNodes()) {
@@ -212,11 +352,6 @@ function scanObjectSchema(schema) {
212
352
  }
213
353
  return map;
214
354
  }
215
- /**
216
- * Names of the methods called at the top level of a Zod field expression.
217
- * For `z.number().int().default(0)` → `{ number, int, default }`. Nested
218
- * schemas (inside an object argument) are not descended into.
219
- */
220
355
  function topLevelZodMethods(node) {
221
356
  const methods = /* @__PURE__ */ new Set();
222
357
  let cur = node;
@@ -229,11 +364,6 @@ function topLevelZodMethods(node) {
229
364
  }
230
365
  return methods;
231
366
  }
232
- /**
233
- * The description string from a field's top-level `.describe("...")` or
234
- * `.meta({ description: "..." })`, walking the method chain (outermost first,
235
- * so the outermost description wins).
236
- */
237
367
  function topLevelZodDescription(node) {
238
368
  let cur = node;
239
369
  while (cur && Node.isCallExpression(cur)) {
@@ -461,8 +591,9 @@ function extractClient(config, schemaEnums = SchemaEnumRegistry.empty()) {
461
591
  if (!proceduresSym) throw new Error(`Export "${config.router}" does not look like a Supalive router (no "procedures" property).`);
462
592
  const proceduresType = proceduresSym.getTypeAtLocation(routerDecl);
463
593
  const argMeta = scanZodArgMeta(routerDecl);
594
+ const outputMeta = scanZodOutputMeta(routerDecl);
464
595
  const procDocs = scanProcedureDocs(routerDecl);
465
- const extractor = new Extractor(routerDecl, config, argMeta, project.getTypeChecker(), schemaEnums);
596
+ const extractor = new Extractor(routerDecl, config, argMeta, outputMeta, project.getTypeChecker(), schemaEnums);
466
597
  const procedures = [];
467
598
  for (const procSym of proceduresType.getProperties()) {
468
599
  const name = procSym.getName();
@@ -486,6 +617,7 @@ var Extractor = class {
486
617
  locationNode;
487
618
  config;
488
619
  argMeta;
620
+ outputMeta;
489
621
  tc;
490
622
  schemaEnums;
491
623
  modelRegistry = /* @__PURE__ */ new Map();
@@ -495,10 +627,11 @@ var Extractor = class {
495
627
  enumsFinalized = false;
496
628
  nameCounts = /* @__PURE__ */ new Map();
497
629
  visiting = /* @__PURE__ */ new Set();
498
- constructor(locationNode, config, argMeta, tc, schemaEnums) {
630
+ constructor(locationNode, config, argMeta, outputMeta, tc, schemaEnums) {
499
631
  this.locationNode = locationNode;
500
632
  this.config = config;
501
633
  this.argMeta = argMeta;
634
+ this.outputMeta = outputMeta;
502
635
  this.tc = tc;
503
636
  this.schemaEnums = schemaEnums;
504
637
  }
@@ -512,31 +645,6 @@ var Extractor = class {
512
645
  return;
513
646
  }
514
647
  }
515
- /**
516
- * Read an `@type` JSDoc tag from a property and parse it into a DartType.
517
- *
518
- * Supported formats:
519
- * - `/** @type StoreCardItem *​/` → model with that name
520
- * - `/** @type List<StoreCardItem> *​/` → list with model element
521
- * - `/** @type String *​/`, `/** @type int *​/`, etc. → primitive
522
- */
523
- readTypeOverride(prop) {
524
- try {
525
- const decls = prop.getDeclarations();
526
- if (!decls || decls.length === 0) return null;
527
- const node = decls[0];
528
- if (!Node.isJSDocable(node)) return null;
529
- const jsDocs = node.getJsDocs();
530
- if (jsDocs.length === 0) return null;
531
- const typeTag = jsDocs[jsDocs.length - 1].getTags().find((t) => t.getTagName() === "type");
532
- if (!typeTag) return null;
533
- const raw = typeTag.compilerNode.typeExpression?.getText?.()?.trim();
534
- if (!raw) return null;
535
- return parseTypeOverride(raw);
536
- } catch {
537
- return null;
538
- }
539
- }
540
648
  models() {
541
649
  this.finalizeEnums();
542
650
  return [...this.modelRegistry.values()];
@@ -570,6 +678,8 @@ var Extractor = class {
570
678
  const ret = unwrapPromise(sig.getReturnType());
571
679
  const { hasNull, hasUndefined } = splitNullish(ret);
572
680
  const resultType = this.walk(ret, pascal(name) + "Result");
681
+ const outputSchemaMeta = this.outputMeta.get(name);
682
+ if (outputSchemaMeta) this.applyOutputMeta(resultType, outputSchemaMeta);
573
683
  return {
574
684
  name,
575
685
  kind,
@@ -580,6 +690,41 @@ var Extractor = class {
580
690
  resultNullable: hasNull || hasUndefined
581
691
  };
582
692
  }
693
+ /**
694
+ * Recursively apply output schema metadata to a DartType tree.
695
+ * Patches numeric types (int/bigint), renames models via __genType,
696
+ * and recurses into nested objects and lists.
697
+ */
698
+ applyOutputMeta(dt, meta) {
699
+ if (meta.modelName && dt.kind === "model") {
700
+ const existing = this.modelRegistry.get(dt.name);
701
+ if (existing && !this.modelRegistry.has(meta.modelName)) {
702
+ const renamed = {
703
+ ...existing,
704
+ name: meta.modelName
705
+ };
706
+ this.modelRegistry.set(meta.modelName, renamed);
707
+ this.modelRegistry.delete(dt.name);
708
+ }
709
+ dt.name = meta.modelName;
710
+ }
711
+ if (dt.kind === "model") {
712
+ const model = this.modelRegistry.get(dt.name);
713
+ if (model) for (const f of model.fields) {
714
+ const fMeta = meta.fieldMeta.get(f.jsonKey);
715
+ if (fMeta) {
716
+ if (fMeta.numeric && (f.type.kind === "double" || f.type.kind === "int")) f.type = { kind: fMeta.numeric };
717
+ if (fMeta.optional) f.optional = true;
718
+ if (fMeta.description && !f.doc) f.doc = fMeta.description;
719
+ }
720
+ const nestedMeta = meta.nested.get(f.jsonKey);
721
+ if (nestedMeta) this.applyOutputMeta(f.type, nestedMeta);
722
+ }
723
+ } else if (dt.kind === "list") {
724
+ const elMeta = meta.nested.get("__element");
725
+ if (elMeta) this.applyOutputMeta(dt.element, elMeta);
726
+ }
727
+ }
583
728
  walk(type, hint) {
584
729
  const { core } = splitNullish(type);
585
730
  if (core.length === 0) return { kind: "dynamic" };
@@ -653,10 +798,8 @@ var Extractor = class {
653
798
  const { core, hasNull, hasUndefined } = splitNullish(propType);
654
799
  const optional = declaredOptional || hasUndefined;
655
800
  const nullable = hasNull;
656
- const typeOverride = this.readTypeOverride(prop);
657
801
  let dartType;
658
- if (typeOverride) dartType = typeOverride;
659
- else if (core.length === 0) dartType = { kind: "dynamic" };
802
+ if (core.length === 0) dartType = { kind: "dynamic" };
660
803
  else if (core.length === 1) {
661
804
  const fieldHint = name.replace(/Result$/, "") + pascal(jsonKey);
662
805
  dartType = this.walkSingle(core[0], fieldHint);
@@ -945,40 +1088,6 @@ function cleanStem(modelName) {
945
1088
  }
946
1089
  return singularizeWord(s);
947
1090
  }
948
- const PRIMITIVE_MAP = {
949
- string: "string",
950
- int: "int",
951
- double: "double",
952
- bool: "bool",
953
- bigint: "bigint",
954
- bytes: "bytes",
955
- datetime: "datetime",
956
- dynamic: "dynamic"
957
- };
958
- /**
959
- * Parse an `@type` override value into a DartType.
960
- *
961
- * - `"StoreCardItem"` → `{ kind: "model", name: "StoreCardItem" }`
962
- * - `"List<StoreCardItem>"` → `{ kind: "list", element: { kind: "model", name: "StoreCardItem" } }`
963
- * - `"String"` → `{ kind: "string" }`
964
- * - `"int"` → `{ kind: "int" }`
965
- */
966
- function parseTypeOverride(raw) {
967
- const listMatch = raw.match(/^List<(.+)>$/);
968
- if (listMatch) {
969
- const innerType = parseTypeOverride(listMatch[1].trim());
970
- return innerType ? {
971
- kind: "list",
972
- element: innerType
973
- } : null;
974
- }
975
- const prim = PRIMITIVE_MAP[raw.toLowerCase()];
976
- if (prim) return { kind: prim };
977
- return {
978
- kind: "model",
979
- name: raw
980
- };
981
- }
982
1091
  /** The bare candidate name for a structural (non-schema) enum group. */
983
1092
  function fallbackBase(g) {
984
1093
  return pascal(g.field ?? g.hint);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supalive/codegen",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Generate type-safe client code (Dart, and more) from a Supalive TypeScript router.",
5
5
  "author": "Rebaz Raouf",
6
6
  "license": "MIT",