@supalive/codegen 1.7.1 → 1.8.1

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/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as generateToDisk, r as readEntryConfig } from "./src-BTC1k7HX.js";
2
+ import { n as generateToDisk, r as readEntryConfig } from "./src-CTW5fpJ-.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-BTC1k7HX.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-CTW5fpJ-.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. */
@@ -271,6 +285,79 @@ function findZodObjectLiteral(node) {
271
285
  }
272
286
  return cur && Node.isObjectLiteralExpression(cur) ? cur : void 0;
273
287
  }
288
+ /**
289
+ * Read `@genType` JSDoc tags from procedure declarations for return type overrides.
290
+ *
291
+ * Syntax:
292
+ * - Object shape: `/** @genType {{value: List<StoreCardItem>}} *​/` — overrides specific fields
293
+ * - Direct type: `/** @genType List<StoreCardItem> *​/` — overrides the whole return type
294
+ *
295
+ * Returns a map of procedure name → raw `@genType` value string.
296
+ */
297
+ function scanProcedureTypeOverrides(routerDecl) {
298
+ const result = /* @__PURE__ */ new Map();
299
+ try {
300
+ const proceduresObj = getProceduresObjectLiteral(routerDecl);
301
+ if (!proceduresObj) return result;
302
+ for (const prop of proceduresObj.getProperties()) {
303
+ const key = getPropertyKey(prop);
304
+ if (!key) continue;
305
+ const typeValue = procedureTypeTag(prop);
306
+ if (typeValue) result.set(key, typeValue);
307
+ }
308
+ } catch {}
309
+ return result;
310
+ }
311
+ /** Read the `@genType` tag from a procedure declaration's JSDoc. */
312
+ function procedureTypeTag(prop) {
313
+ const tag = findTypeTagOnNode(prop);
314
+ if (tag) return tag;
315
+ let idNode;
316
+ if (Node.isShorthandPropertyAssignment(prop)) idNode = prop.getNameNode();
317
+ else if (Node.isPropertyAssignment(prop)) idNode = prop.getInitializer();
318
+ if (idNode && Node.isIdentifier(idNode)) {
319
+ for (const def of idNode.getDefinitionNodes()) if (Node.isVariableDeclaration(def)) {
320
+ const stmt = def.getVariableStatement();
321
+ if (stmt) {
322
+ const tag = findTypeTagOnNode(stmt);
323
+ if (tag) return tag;
324
+ }
325
+ }
326
+ for (const def of idNode.getDefinitionNodes()) if (Node.isImportSpecifier(def)) {
327
+ const sourceFile = def.getImportDeclaration().getModuleSpecifierSourceFile();
328
+ if (sourceFile) {
329
+ const exported = sourceFile.getExportedDeclarations().get(def.getName());
330
+ if (exported && exported.length > 0) {
331
+ const decl = exported[0];
332
+ if (Node.isVariableDeclaration(decl)) {
333
+ const stmt = decl.getVariableStatement();
334
+ if (stmt) {
335
+ const tag = findTypeTagOnNode(stmt);
336
+ if (tag) return tag;
337
+ }
338
+ }
339
+ }
340
+ }
341
+ }
342
+ }
343
+ }
344
+ /** Extract the `@genType` tag value from a JSDocable node, if present. */
345
+ function findTypeTagOnNode(node) {
346
+ if (!Node.isJSDocable(node)) return void 0;
347
+ const docs = node.getJsDocs();
348
+ if (docs.length === 0) return void 0;
349
+ const jd = docs[docs.length - 1];
350
+ for (const tag of jd.getTags()) if (tag.getTagName() === "genType") {
351
+ const text = tag.compilerNode.typeExpression?.getText?.()?.trim();
352
+ if (text) return text;
353
+ const comment = tag.getComment();
354
+ if (typeof comment === "string") return comment.trim();
355
+ if (Array.isArray(comment)) {
356
+ const joined = comment.map((c) => c.getText?.() ?? "").join("").trim();
357
+ if (joined) return joined;
358
+ }
359
+ }
360
+ }
274
361
  //#endregion
275
362
  //#region src/names.ts
276
363
  /** PascalCase an arbitrary string (splitting on any non-alphanumeric run). */
@@ -462,7 +549,7 @@ function extractClient(config, schemaEnums = SchemaEnumRegistry.empty()) {
462
549
  const proceduresType = proceduresSym.getTypeAtLocation(routerDecl);
463
550
  const argMeta = scanZodArgMeta(routerDecl);
464
551
  const procDocs = scanProcedureDocs(routerDecl);
465
- const extractor = new Extractor(routerDecl, config, argMeta, project.getTypeChecker(), schemaEnums);
552
+ const extractor = new Extractor(routerDecl, config, argMeta, scanProcedureTypeOverrides(routerDecl), project.getTypeChecker(), schemaEnums);
466
553
  const procedures = [];
467
554
  for (const procSym of proceduresType.getProperties()) {
468
555
  const name = procSym.getName();
@@ -486,6 +573,7 @@ var Extractor = class {
486
573
  locationNode;
487
574
  config;
488
575
  argMeta;
576
+ procTypeOverrides;
489
577
  tc;
490
578
  schemaEnums;
491
579
  modelRegistry = /* @__PURE__ */ new Map();
@@ -495,10 +583,11 @@ var Extractor = class {
495
583
  enumsFinalized = false;
496
584
  nameCounts = /* @__PURE__ */ new Map();
497
585
  visiting = /* @__PURE__ */ new Set();
498
- constructor(locationNode, config, argMeta, tc, schemaEnums) {
586
+ constructor(locationNode, config, argMeta, procTypeOverrides, tc, schemaEnums) {
499
587
  this.locationNode = locationNode;
500
588
  this.config = config;
501
589
  this.argMeta = argMeta;
590
+ this.procTypeOverrides = procTypeOverrides;
502
591
  this.tc = tc;
503
592
  this.schemaEnums = schemaEnums;
504
593
  }
@@ -512,6 +601,36 @@ var Extractor = class {
512
601
  return;
513
602
  }
514
603
  }
604
+ /**
605
+ * Read an `@genType` JSDoc tag from a property and parse it into a DartType.
606
+ *
607
+ * Supported formats:
608
+ * - `/** @genType StoreCardItem *​/` → model with that name
609
+ * - `/** @genType List<StoreCardItem> *​/` → list with model element
610
+ * - `/** @genType String *​/`, `/** @genType int *​/`, etc. → primitive
611
+ */
612
+ readTypeOverride(prop) {
613
+ try {
614
+ const decls = prop.getDeclarations();
615
+ if (!decls || decls.length === 0) return null;
616
+ const node = decls[0];
617
+ if (!Node.isJSDocable(node)) return null;
618
+ const jsDocs = node.getJsDocs();
619
+ if (jsDocs.length === 0) return null;
620
+ const typeTag = jsDocs[jsDocs.length - 1].getTags().find((t) => t.getTagName() === "genType");
621
+ if (!typeTag) return null;
622
+ let raw = typeTag.compilerNode.typeExpression?.getText?.()?.trim();
623
+ if (!raw) {
624
+ const comment = typeTag.getComment();
625
+ if (typeof comment === "string") raw = comment.trim();
626
+ else if (Array.isArray(comment)) raw = comment.map((c) => c.getText?.() ?? "").join("").trim();
627
+ }
628
+ if (!raw) return null;
629
+ return parseTypeOverride(raw);
630
+ } catch {
631
+ return null;
632
+ }
633
+ }
515
634
  models() {
516
635
  this.finalizeEnums();
517
636
  return [...this.modelRegistry.values()];
@@ -544,7 +663,77 @@ var Extractor = class {
544
663
  }
545
664
  const ret = unwrapPromise(sig.getReturnType());
546
665
  const { hasNull, hasUndefined } = splitNullish(ret);
547
- const resultType = this.walk(ret, pascal(name) + "Result");
666
+ const procOverrideRaw = this.procTypeOverrides.get(name);
667
+ let resultType;
668
+ if (procOverrideRaw) {
669
+ const parsed = parseProcTypeOverride(procOverrideRaw);
670
+ if (parsed) if (parsed.kind === "direct") {
671
+ const core = splitNullish(ret).core;
672
+ if (core.length > 0) {
673
+ if (parsed.dartType.kind === "model") this.walkSingle(core[0], parsed.dartType.name);
674
+ else if (parsed.dartType.kind === "list" && parsed.dartType.element.kind === "model") {
675
+ const actualEl = core[0].isArray() ? core[0].getArrayElementTypeOrThrow() : core[0];
676
+ this.walkSingle(actualEl, parsed.dartType.element.name);
677
+ }
678
+ }
679
+ resultType = parsed.dartType;
680
+ } else {
681
+ const core = splitNullish(ret).core;
682
+ const actualType = core.length > 0 ? core[0] : void 0;
683
+ const modelName = pascal(name) + "Result";
684
+ const fields = [];
685
+ const overrideKeys = new Set(parsed.fields.keys());
686
+ for (const [fieldName, fieldType] of parsed.fields) {
687
+ if (actualType && actualType.isObject()) {
688
+ const actualProp = actualType.getProperty(fieldName);
689
+ if (actualProp) {
690
+ const actualPropType = actualProp.getTypeAtLocation(this.locationNode);
691
+ if (fieldType.kind === "model") this.walkSingle(actualPropType, fieldType.name);
692
+ else if (fieldType.kind === "list" && fieldType.element.kind === "model") {
693
+ const core2 = splitNullish(actualPropType).core;
694
+ if (core2.length > 0) {
695
+ const el = core2[0].isArray() ? core2[0].getArrayElementTypeOrThrow() : core2[0];
696
+ this.walkSingle(el, fieldType.element.name);
697
+ }
698
+ }
699
+ }
700
+ }
701
+ fields.push({
702
+ name: camel(fieldName),
703
+ jsonKey: fieldName,
704
+ type: fieldType,
705
+ optional: false,
706
+ nullable: false
707
+ });
708
+ }
709
+ if (actualType && actualType.isObject()) for (const prop of actualType.getProperties()) {
710
+ const jsonKey = prop.getName();
711
+ if (overrideKeys.has(jsonKey)) continue;
712
+ if (prop.getFlags() & ts.SymbolFlags.Method) continue;
713
+ const { core: propCore, hasNull, hasUndefined } = splitNullish(prop.getTypeAtLocation(this.locationNode));
714
+ const optional = hasUndefined;
715
+ const nullable = hasNull;
716
+ const fieldHint = modelName.replace(/Result$/, "") + pascal(jsonKey);
717
+ const dartType = propCore.length > 0 ? propCore.length === 1 ? this.walkSingle(propCore[0], fieldHint) : this.walkUnion(propCore, { hint: fieldHint }) : { kind: "dynamic" };
718
+ fields.push({
719
+ name: camel(jsonKey),
720
+ jsonKey,
721
+ type: dartType,
722
+ optional,
723
+ nullable
724
+ });
725
+ }
726
+ this.modelRegistry.set(modelName, {
727
+ name: modelName,
728
+ fields
729
+ });
730
+ resultType = {
731
+ kind: "model",
732
+ name: modelName
733
+ };
734
+ }
735
+ else resultType = this.walk(ret, pascal(name) + "Result");
736
+ } else resultType = this.walk(ret, pascal(name) + "Result");
548
737
  return {
549
738
  name,
550
739
  kind,
@@ -628,11 +817,24 @@ var Extractor = class {
628
817
  const { core, hasNull, hasUndefined } = splitNullish(propType);
629
818
  const optional = declaredOptional || hasUndefined;
630
819
  const nullable = hasNull;
820
+ const typeOverride = this.readTypeOverride(prop);
631
821
  let dartType;
632
- if (core.length === 0) dartType = { kind: "dynamic" };
633
- else if (core.length === 1) dartType = this.walkSingle(core[0], name + pascal(jsonKey));
634
- else dartType = this.walkUnion(core, {
635
- hint: name + pascal(jsonKey),
822
+ if (typeOverride) if (typeOverride.kind === "list" && typeOverride.element.kind === "model") if (core.length > 0) {
823
+ const actualEl = core[0].isArray() ? core[0].getArrayElementTypeOrThrow() : core[0];
824
+ dartType = {
825
+ kind: "list",
826
+ element: this.walkSingle(actualEl, typeOverride.element.name)
827
+ };
828
+ } else dartType = typeOverride;
829
+ else if (typeOverride.kind === "model") if (core.length > 0) dartType = this.walkSingle(core[0], typeOverride.name);
830
+ else dartType = typeOverride;
831
+ else dartType = typeOverride;
832
+ else if (core.length === 0) dartType = { kind: "dynamic" };
833
+ else if (core.length === 1) {
834
+ const fieldHint = name.replace(/Result$/, "") + pascal(jsonKey);
835
+ dartType = this.walkSingle(core[0], fieldHint);
836
+ } else dartType = this.walkUnion(core, {
837
+ hint: name.replace(/Result$/, "") + pascal(jsonKey),
636
838
  stem: cleanStem(name),
637
839
  field: jsonKey
638
840
  });
@@ -916,6 +1118,101 @@ function cleanStem(modelName) {
916
1118
  }
917
1119
  return singularizeWord(s);
918
1120
  }
1121
+ const PRIMITIVE_MAP = {
1122
+ string: "string",
1123
+ int: "int",
1124
+ double: "double",
1125
+ bool: "bool",
1126
+ bigint: "bigint",
1127
+ bytes: "bytes",
1128
+ datetime: "datetime",
1129
+ dynamic: "dynamic"
1130
+ };
1131
+ /**
1132
+ * Parse an `@type` override value into a DartType.
1133
+ *
1134
+ * - `"StoreCardItem"` → `{ kind: "model", name: "StoreCardItem" }`
1135
+ * - `"List<StoreCardItem>"` → `{ kind: "list", element: { kind: "model", name: "StoreCardItem" } }`
1136
+ * - `"String"` → `{ kind: "string" }`
1137
+ * - `"int"` → `{ kind: "int" }`
1138
+ */
1139
+ function parseTypeOverride(raw) {
1140
+ const listMatch = raw.match(/^List<(.+)>$/);
1141
+ if (listMatch) {
1142
+ const innerType = parseTypeOverride(listMatch[1].trim());
1143
+ return innerType ? {
1144
+ kind: "list",
1145
+ element: innerType
1146
+ } : null;
1147
+ }
1148
+ const prim = PRIMITIVE_MAP[raw.toLowerCase()];
1149
+ if (prim) return { kind: prim };
1150
+ return {
1151
+ kind: "model",
1152
+ name: raw
1153
+ };
1154
+ }
1155
+ /**
1156
+ * Parse a procedure-level `@genType` value.
1157
+ *
1158
+ * - `{{value: List<StoreCardItem>}}` → object shape with field overrides
1159
+ * - `List<StoreCardItem>` → direct type override
1160
+ * - `StoreCardResult` → direct model name override
1161
+ */
1162
+ function parseProcTypeOverride(raw) {
1163
+ const objectMatch = raw.match(/^\{([\s\S]+)\}$/);
1164
+ if (objectMatch) {
1165
+ let inner = objectMatch[1];
1166
+ if (inner.startsWith("{")) inner = inner.slice(1);
1167
+ if (inner.endsWith("}")) inner = inner.slice(0, -1);
1168
+ const fields = parseFieldOverrides(inner);
1169
+ return fields.size > 0 ? {
1170
+ kind: "object",
1171
+ fields
1172
+ } : null;
1173
+ }
1174
+ const dartType = parseTypeOverride(raw);
1175
+ return dartType ? {
1176
+ kind: "direct",
1177
+ dartType
1178
+ } : null;
1179
+ }
1180
+ /**
1181
+ * Parse comma-separated `field: Type` entries inside an object shape override.
1182
+ *
1183
+ * Handles nested `List<T>` types (the colon inside `List<...>` must not split
1184
+ * the field name). Examples:
1185
+ * - `"value: List<StoreCardItem>"` → `{ value: { kind: "list", ... } }`
1186
+ * - `"name: String, count: int"` → two entries
1187
+ */
1188
+ function parseFieldOverrides(raw) {
1189
+ const fields = /* @__PURE__ */ new Map();
1190
+ const parts = splitOutsideAngles(raw);
1191
+ for (const part of parts) {
1192
+ const colonIdx = part.indexOf(":");
1193
+ if (colonIdx === -1) continue;
1194
+ const fieldName = part.slice(0, colonIdx).trim();
1195
+ const typeStr = part.slice(colonIdx + 1).trim();
1196
+ if (!fieldName || !typeStr) continue;
1197
+ const dartType = parseTypeOverride(typeStr);
1198
+ if (dartType) fields.set(fieldName, dartType);
1199
+ }
1200
+ return fields;
1201
+ }
1202
+ /** Split a string by commas, ignoring commas inside `<...>` angle brackets. */
1203
+ function splitOutsideAngles(raw) {
1204
+ const parts = [];
1205
+ let depth = 0;
1206
+ let start = 0;
1207
+ for (let i = 0; i < raw.length; i++) if (raw[i] === "<") depth++;
1208
+ else if (raw[i] === ">") depth = Math.max(0, depth - 1);
1209
+ else if (raw[i] === "," && depth === 0) {
1210
+ parts.push(raw.slice(start, i));
1211
+ start = i + 1;
1212
+ }
1213
+ parts.push(raw.slice(start));
1214
+ return parts;
1215
+ }
919
1216
  /** The bare candidate name for a structural (non-schema) enum group. */
920
1217
  function fallbackBase(g) {
921
1218
  return pascal(g.field ?? g.hint);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supalive/codegen",
3
- "version": "1.7.1",
3
+ "version": "1.8.1",
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",