@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 +1 -1
- package/dist/index.js +1 -1
- package/dist/{src-BTC1k7HX.js → src-CTW5fpJ-.js} +304 -7
- package/package.json +1 -1
package/dist/cli.js
CHANGED
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-
|
|
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
|
|
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 (
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
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);
|