@supalive/codegen 1.8.0 → 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-BRGy8CeY.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-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-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
  }
@@ -513,12 +602,12 @@ var Extractor = class {
513
602
  }
514
603
  }
515
604
  /**
516
- * Read an `@type` JSDoc tag from a property and parse it into a DartType.
605
+ * Read an `@genType` JSDoc tag from a property and parse it into a DartType.
517
606
  *
518
607
  * Supported formats:
519
- * - `/** @type StoreCardItem *​/` → model with that name
520
- * - `/** @type List<StoreCardItem> *​/` → list with model element
521
- * - `/** @type String *​/`, `/** @type int *​/`, etc. → primitive
608
+ * - `/** @genType StoreCardItem *​/` → model with that name
609
+ * - `/** @genType List<StoreCardItem> *​/` → list with model element
610
+ * - `/** @genType String *​/`, `/** @genType int *​/`, etc. → primitive
522
611
  */
523
612
  readTypeOverride(prop) {
524
613
  try {
@@ -528,9 +617,14 @@ var Extractor = class {
528
617
  if (!Node.isJSDocable(node)) return null;
529
618
  const jsDocs = node.getJsDocs();
530
619
  if (jsDocs.length === 0) return null;
531
- const typeTag = jsDocs[jsDocs.length - 1].getTags().find((t) => t.getTagName() === "type");
620
+ const typeTag = jsDocs[jsDocs.length - 1].getTags().find((t) => t.getTagName() === "genType");
532
621
  if (!typeTag) return null;
533
- const raw = typeTag.compilerNode.typeExpression?.getText?.()?.trim();
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
+ }
534
628
  if (!raw) return null;
535
629
  return parseTypeOverride(raw);
536
630
  } catch {
@@ -569,7 +663,77 @@ var Extractor = class {
569
663
  }
570
664
  const ret = unwrapPromise(sig.getReturnType());
571
665
  const { hasNull, hasUndefined } = splitNullish(ret);
572
- 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");
573
737
  return {
574
738
  name,
575
739
  kind,
@@ -655,7 +819,16 @@ var Extractor = class {
655
819
  const nullable = hasNull;
656
820
  const typeOverride = this.readTypeOverride(prop);
657
821
  let dartType;
658
- if (typeOverride) dartType = typeOverride;
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;
659
832
  else if (core.length === 0) dartType = { kind: "dynamic" };
660
833
  else if (core.length === 1) {
661
834
  const fieldHint = name.replace(/Result$/, "") + pascal(jsonKey);
@@ -979,6 +1152,67 @@ function parseTypeOverride(raw) {
979
1152
  name: raw
980
1153
  };
981
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
+ }
982
1216
  /** The bare candidate name for a structural (non-schema) enum group. */
983
1217
  function fallbackBase(g) {
984
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.8.0",
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",