@polyprism/core 0.1.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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +32 -0
  3. package/dist/annotations/enrich.d.ts +5 -0
  4. package/dist/annotations/enrich.js +40 -0
  5. package/dist/annotations/enrich.js.map +1 -0
  6. package/dist/annotations/index.d.ts +3 -0
  7. package/dist/annotations/index.js +3 -0
  8. package/dist/annotations/index.js.map +1 -0
  9. package/dist/annotations/parser.d.ts +9 -0
  10. package/dist/annotations/parser.js +207 -0
  11. package/dist/annotations/parser.js.map +1 -0
  12. package/dist/emitter/emit-enums.d.ts +9 -0
  13. package/dist/emitter/emit-enums.js +19 -0
  14. package/dist/emitter/emit-enums.js.map +1 -0
  15. package/dist/emitter/emit-json-types.d.ts +9 -0
  16. package/dist/emitter/emit-json-types.js +28 -0
  17. package/dist/emitter/emit-json-types.js.map +1 -0
  18. package/dist/emitter/file-writer.d.ts +12 -0
  19. package/dist/emitter/file-writer.js +25 -0
  20. package/dist/emitter/file-writer.js.map +1 -0
  21. package/dist/emitter/format-type.d.ts +3 -0
  22. package/dist/emitter/format-type.js +162 -0
  23. package/dist/emitter/format-type.js.map +1 -0
  24. package/dist/emitter/index.d.ts +10 -0
  25. package/dist/emitter/index.js +7 -0
  26. package/dist/emitter/index.js.map +1 -0
  27. package/dist/emitter/render-enum.d.ts +6 -0
  28. package/dist/emitter/render-enum.js +56 -0
  29. package/dist/emitter/render-enum.js.map +1 -0
  30. package/dist/emitter/render-json-type.d.ts +3 -0
  31. package/dist/emitter/render-json-type.js +10 -0
  32. package/dist/emitter/render-json-type.js.map +1 -0
  33. package/dist/generator/config.d.ts +10 -0
  34. package/dist/generator/config.js +45 -0
  35. package/dist/generator/config.js.map +1 -0
  36. package/dist/generator/context.d.ts +18 -0
  37. package/dist/generator/context.js +1 -0
  38. package/dist/generator/context.js.map +1 -0
  39. package/dist/generator/define.d.ts +24 -0
  40. package/dist/generator/define.js +23 -0
  41. package/dist/generator/define.js.map +1 -0
  42. package/dist/generator/index.d.ts +6 -0
  43. package/dist/generator/index.js +4 -0
  44. package/dist/generator/index.js.map +1 -0
  45. package/dist/generator/json-rpc.d.ts +7 -0
  46. package/dist/generator/json-rpc.js +59 -0
  47. package/dist/generator/json-rpc.js.map +1 -0
  48. package/dist/index.d.ts +15 -0
  49. package/dist/index.js +6 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/ir/index.d.ts +1 -0
  52. package/dist/ir/index.js +2 -0
  53. package/dist/ir/index.js.map +1 -0
  54. package/dist/ir/types.d.ts +133 -0
  55. package/dist/ir/types.js +17 -0
  56. package/dist/ir/types.js.map +1 -0
  57. package/dist/naming/casing.d.ts +7 -0
  58. package/dist/naming/casing.js +34 -0
  59. package/dist/naming/casing.js.map +1 -0
  60. package/dist/naming/index.d.ts +3 -0
  61. package/dist/naming/index.js +4 -0
  62. package/dist/naming/index.js.map +1 -0
  63. package/dist/naming/resolver.d.ts +26 -0
  64. package/dist/naming/resolver.js +59 -0
  65. package/dist/naming/resolver.js.map +1 -0
  66. package/dist/naming/types.d.ts +11 -0
  67. package/dist/naming/types.js +9 -0
  68. package/dist/naming/types.js.map +1 -0
  69. package/dist/reader/dmmf-reader.d.ts +12 -0
  70. package/dist/reader/dmmf-reader.js +135 -0
  71. package/dist/reader/dmmf-reader.js.map +1 -0
  72. package/dist/reader/index.d.ts +3 -0
  73. package/dist/reader/index.js +2 -0
  74. package/dist/reader/index.js.map +1 -0
  75. package/package.json +52 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Travis Fitzgerald
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # @polyprism/core
2
+
3
+ Internal package — the language-agnostic IR, DMMF reader, annotation parser,
4
+ naming resolver, and generator runtime that powers every `@polyprism/*`
5
+ pattern package.
6
+
7
+ You probably don't want to install this directly. Pick a pattern package:
8
+
9
+ - [`@polyprism/ts-interface`](../ts-interface) — `export interface User { ... }`
10
+ - [`@polyprism/ts-type`](../ts-type) — `export type User = { ... };`
11
+ - [`@polyprism/ts-class`](../ts-class) — `export class User { ... }`
12
+
13
+ See the [root README](../../README.md) for the project overview, annotation
14
+ reference, and full feature list.
15
+
16
+ ## Public API
17
+
18
+ This package exports:
19
+
20
+ - IR types (`ModelDef`, `FieldDef`, `EnumDef`, `AnnotationSet`, etc.)
21
+ - Annotation parser (`parseAnnotations`)
22
+ - Naming resolver (`resolveTypeIdent`, `resolveFieldIdent`, `resolveTypeFilename`)
23
+ - Generator runtime (`defineGenerator`, `GeneratorContext`)
24
+ - Shared emitter helpers (`emitEnums`, `emitJsonTypes`, `prettyFormatType`,
25
+ `createInMemoryFileWriter`)
26
+
27
+ The DMMF reader is internal — it is **never** exposed publicly. This is what
28
+ lets pattern packages have zero third-party runtime dependencies.
29
+
30
+ ## License
31
+
32
+ [MIT](../../LICENSE) © Travis Fitzgerald
@@ -0,0 +1,5 @@
1
+ import { PolyPrismIR } from '../ir/types.js';
2
+
3
+ declare function enrichAnnotations(ir: PolyPrismIR): PolyPrismIR;
4
+
5
+ export { enrichAnnotations };
@@ -0,0 +1,40 @@
1
+ import { parseAnnotations } from "./parser.js";
2
+ function enrichAnnotations(ir) {
3
+ return {
4
+ models: ir.models.map(enrichModel),
5
+ enums: ir.enums.map(enrichEnum)
6
+ };
7
+ }
8
+ function enrichModel(model) {
9
+ return {
10
+ ...model,
11
+ annotations: mergeRawDoc(parseAnnotations(model.documentation), model.documentation),
12
+ fields: model.fields.map(enrichField)
13
+ };
14
+ }
15
+ function enrichField(field) {
16
+ return {
17
+ ...field,
18
+ annotations: mergeRawDoc(parseAnnotations(field.documentation), field.documentation)
19
+ };
20
+ }
21
+ function enrichEnum(enumDef) {
22
+ return {
23
+ ...enumDef,
24
+ annotations: mergeRawDoc(parseAnnotations(enumDef.documentation), enumDef.documentation),
25
+ values: enumDef.values.map(enrichEnumValue)
26
+ };
27
+ }
28
+ function enrichEnumValue(value) {
29
+ return {
30
+ ...value,
31
+ annotations: mergeRawDoc(parseAnnotations(value.documentation), value.documentation)
32
+ };
33
+ }
34
+ function mergeRawDoc(parsed, _original) {
35
+ return parsed;
36
+ }
37
+ export {
38
+ enrichAnnotations
39
+ };
40
+ //# sourceMappingURL=enrich.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/annotations/enrich.ts"],"sourcesContent":["// Walk an PolyPrism IR and enrich every node's annotations by parsing\n// its `documentation` string. Returns a new IR — the input is not mutated.\n\nimport type {\n AnnotationSet,\n EnumDef,\n EnumValueDef,\n FieldDef,\n ModelDef,\n PolyPrismIR,\n} from \"../ir/types.js\";\nimport { parseAnnotations } from \"./parser.js\";\n\nexport function enrichAnnotations(ir: PolyPrismIR): PolyPrismIR {\n return {\n models: ir.models.map(enrichModel),\n enums: ir.enums.map(enrichEnum),\n };\n}\n\nfunction enrichModel(model: ModelDef): ModelDef {\n return {\n ...model,\n annotations: mergeRawDoc(parseAnnotations(model.documentation), model.documentation),\n fields: model.fields.map(enrichField),\n };\n}\n\nfunction enrichField(field: FieldDef): FieldDef {\n return {\n ...field,\n annotations: mergeRawDoc(parseAnnotations(field.documentation), field.documentation),\n };\n}\n\nfunction enrichEnum(enumDef: EnumDef): EnumDef {\n return {\n ...enumDef,\n annotations: mergeRawDoc(parseAnnotations(enumDef.documentation), enumDef.documentation),\n values: enumDef.values.map(enrichEnumValue),\n };\n}\n\nfunction enrichEnumValue(value: EnumValueDef): EnumValueDef {\n return {\n ...value,\n annotations: mergeRawDoc(parseAnnotations(value.documentation), value.documentation),\n };\n}\n\n/**\n * The parser's `documentation` field has annotation lines stripped. For nodes\n * that never had documentation, ensure that stays null rather than empty string.\n */\nfunction mergeRawDoc(parsed: AnnotationSet, _original: string | null): AnnotationSet {\n return parsed;\n}\n"],"mappings":"AAWA,SAAS,wBAAwB;AAE1B,SAAS,kBAAkB,IAA8B;AAC9D,SAAO;AAAA,IACL,QAAQ,GAAG,OAAO,IAAI,WAAW;AAAA,IACjC,OAAO,GAAG,MAAM,IAAI,UAAU;AAAA,EAChC;AACF;AAEA,SAAS,YAAY,OAA2B;AAC9C,SAAO;AAAA,IACL,GAAG;AAAA,IACH,aAAa,YAAY,iBAAiB,MAAM,aAAa,GAAG,MAAM,aAAa;AAAA,IACnF,QAAQ,MAAM,OAAO,IAAI,WAAW;AAAA,EACtC;AACF;AAEA,SAAS,YAAY,OAA2B;AAC9C,SAAO;AAAA,IACL,GAAG;AAAA,IACH,aAAa,YAAY,iBAAiB,MAAM,aAAa,GAAG,MAAM,aAAa;AAAA,EACrF;AACF;AAEA,SAAS,WAAW,SAA2B;AAC7C,SAAO;AAAA,IACL,GAAG;AAAA,IACH,aAAa,YAAY,iBAAiB,QAAQ,aAAa,GAAG,QAAQ,aAAa;AAAA,IACvF,QAAQ,QAAQ,OAAO,IAAI,eAAe;AAAA,EAC5C;AACF;AAEA,SAAS,gBAAgB,OAAmC;AAC1D,SAAO;AAAA,IACL,GAAG;AAAA,IACH,aAAa,YAAY,iBAAiB,MAAM,aAAa,GAAG,MAAM,aAAa;AAAA,EACrF;AACF;AAMA,SAAS,YAAY,QAAuB,WAAyC;AACnF,SAAO;AACT;","names":[]}
@@ -0,0 +1,3 @@
1
+ export { enrichAnnotations } from './enrich.js';
2
+ export { parseAnnotations } from './parser.js';
3
+ import '../ir/types.js';
@@ -0,0 +1,3 @@
1
+ export * from "./enrich.js";
2
+ export * from "./parser.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/annotations/index.ts"],"sourcesContent":["export * from \"./enrich.js\";\nexport * from \"./parser.js\";\n"],"mappings":"AAAA,cAAc;AACd,cAAc;","names":[]}
@@ -0,0 +1,9 @@
1
+ import { AnnotationSet } from '../ir/types.js';
2
+
3
+ /**
4
+ * Parse a documentation string into an AnnotationSet.
5
+ * Lines that aren't annotations are preserved in `documentation`.
6
+ */
7
+ declare function parseAnnotations(doc: string | null): AnnotationSet;
8
+
9
+ export { parseAnnotations };
@@ -0,0 +1,207 @@
1
+ const VALID_NORMALISE_OPS = /* @__PURE__ */ new Set([
2
+ "trim",
3
+ "lowercase",
4
+ "uppercase",
5
+ "nullEmptyToNull"
6
+ ]);
7
+ const VALID_COERCE_TARGETS = /* @__PURE__ */ new Set(["date", "int", "float", "decimal", "string"]);
8
+ function parseAnnotations(doc) {
9
+ const result = {
10
+ hide: false,
11
+ deprecated: null,
12
+ json: null,
13
+ type: null,
14
+ name: null,
15
+ normalise: null,
16
+ coerce: null,
17
+ documentation: null,
18
+ rawAnnotations: []
19
+ };
20
+ if (!doc) return result;
21
+ const logicalLines = splitLogicalLines(doc);
22
+ const docLines = [];
23
+ const rawAnnotations = [];
24
+ for (const line of logicalLines) {
25
+ const trimmed = line.trim();
26
+ const bracketMatch = /^\[(\w+)\]$/.exec(trimmed);
27
+ if (bracketMatch) {
28
+ result.json = { kind: "bare", typeName: bracketMatch[1] };
29
+ rawAnnotations.push(trimmed);
30
+ continue;
31
+ }
32
+ const annotationMatch = /^@(\w+)(?:\s*\(([\s\S]*)\))?$/.exec(trimmed);
33
+ if (annotationMatch) {
34
+ applyAnnotation(annotationMatch[1], annotationMatch[2] ?? null, result);
35
+ rawAnnotations.push(trimmed);
36
+ continue;
37
+ }
38
+ docLines.push(line);
39
+ }
40
+ const joinedDoc = docLines.join("\n").trim();
41
+ result.documentation = joinedDoc.length > 0 ? joinedDoc : null;
42
+ result.rawAnnotations = rawAnnotations;
43
+ return result;
44
+ }
45
+ function splitLogicalLines(doc) {
46
+ const physical = doc.split("\n");
47
+ const logical = [];
48
+ let buffer = "";
49
+ let parenDepth = 0;
50
+ let braceDepth = 0;
51
+ let bracketDepth = 0;
52
+ let inString = null;
53
+ let isEscaped = false;
54
+ for (const line of physical) {
55
+ for (const ch of line) {
56
+ if (isEscaped) {
57
+ isEscaped = false;
58
+ continue;
59
+ }
60
+ if (ch === "\\") {
61
+ isEscaped = true;
62
+ continue;
63
+ }
64
+ if (inString) {
65
+ if (ch === inString) inString = null;
66
+ continue;
67
+ }
68
+ if (ch === '"' || ch === "'" || ch === "`") {
69
+ inString = ch;
70
+ continue;
71
+ }
72
+ if (ch === "(") parenDepth++;
73
+ else if (ch === ")") parenDepth--;
74
+ else if (ch === "{") braceDepth++;
75
+ else if (ch === "}") braceDepth--;
76
+ else if (ch === "[") bracketDepth++;
77
+ else if (ch === "]") bracketDepth--;
78
+ }
79
+ buffer = buffer.length === 0 ? line : `${buffer} ${line.trim()}`;
80
+ if (parenDepth === 0 && braceDepth === 0 && bracketDepth === 0 && inString === null) {
81
+ logical.push(buffer);
82
+ buffer = "";
83
+ }
84
+ }
85
+ if (buffer.length > 0) logical.push(buffer);
86
+ return logical;
87
+ }
88
+ function applyAnnotation(name, args, set) {
89
+ switch (name) {
90
+ case "hide":
91
+ set.hide = true;
92
+ return;
93
+ case "deprecated":
94
+ set.deprecated = { reason: args ? extractStringArg(args) : null };
95
+ return;
96
+ case "json":
97
+ set.json = parseJsonArgs(args);
98
+ return;
99
+ case "type":
100
+ set.type = parseTypeArgs(args);
101
+ return;
102
+ case "name":
103
+ set.name = args ? args.trim() : null;
104
+ return;
105
+ case "normalise":
106
+ case "normalize":
107
+ set.normalise = parseNormaliseArgs(args);
108
+ return;
109
+ case "coerce":
110
+ set.coerce = parseCoerceArg(args);
111
+ return;
112
+ default:
113
+ return;
114
+ }
115
+ }
116
+ function parseJsonArgs(args) {
117
+ if (!args) return null;
118
+ const trimmed = args.trim();
119
+ if (trimmed.length === 0) return null;
120
+ const pathMatch = /^(\w+)\s+from\s+["'](.+)["']$/.exec(trimmed);
121
+ if (pathMatch) {
122
+ return { kind: "with-path", typeName: pathMatch[1], importPath: pathMatch[2] };
123
+ }
124
+ const eqIndex = findTopLevelAssignment(trimmed);
125
+ if (eqIndex > 0) {
126
+ const namePart = trimmed.slice(0, eqIndex).trim();
127
+ const exprPart = trimmed.slice(eqIndex + 1).trim();
128
+ if (/^\w+$/.test(namePart) && exprPart.length > 0) {
129
+ return { kind: "inline-named", typeName: namePart, typeExpression: exprPart };
130
+ }
131
+ }
132
+ if (/^\w+$/.test(trimmed)) {
133
+ return { kind: "bare", typeName: trimmed };
134
+ }
135
+ return { kind: "inline-anonymous", typeExpression: trimmed };
136
+ }
137
+ function findTopLevelAssignment(s) {
138
+ let depth = 0;
139
+ let inString = null;
140
+ let isEscaped = false;
141
+ for (let i = 0; i < s.length; i++) {
142
+ const ch = s[i];
143
+ if (isEscaped) {
144
+ isEscaped = false;
145
+ continue;
146
+ }
147
+ if (ch === "\\") {
148
+ isEscaped = true;
149
+ continue;
150
+ }
151
+ if (inString) {
152
+ if (ch === inString) inString = null;
153
+ continue;
154
+ }
155
+ if (ch === '"' || ch === "'" || ch === "`") {
156
+ inString = ch;
157
+ continue;
158
+ }
159
+ if (ch === "{" || ch === "(" || ch === "[" || ch === "<") {
160
+ depth++;
161
+ continue;
162
+ }
163
+ if (ch === "}" || ch === ")" || ch === "]" || ch === ">") {
164
+ depth--;
165
+ continue;
166
+ }
167
+ if (ch === "=" && depth === 0) {
168
+ const prev = i > 0 ? s[i - 1] : "";
169
+ const next = i + 1 < s.length ? s[i + 1] : "";
170
+ if (next === "=" || next === ">") continue;
171
+ if (prev === "=" || prev === "!" || prev === "<" || prev === ">") continue;
172
+ return i;
173
+ }
174
+ }
175
+ return -1;
176
+ }
177
+ function parseTypeArgs(args) {
178
+ if (!args) return null;
179
+ const trimmed = args.trim();
180
+ const pathMatch = /^(\w+)\s+from\s+["'](.+)["']$/.exec(trimmed);
181
+ if (pathMatch) {
182
+ return { typeName: pathMatch[1], importPath: pathMatch[2] };
183
+ }
184
+ if (/^\w+$/.test(trimmed)) {
185
+ return { typeName: trimmed, importPath: null };
186
+ }
187
+ return null;
188
+ }
189
+ function parseNormaliseArgs(args) {
190
+ if (!args) return null;
191
+ const ops = args.split(",").map((s) => s.trim()).filter((s) => s.length > 0).filter((s) => VALID_NORMALISE_OPS.has(s));
192
+ return ops.length > 0 ? ops : null;
193
+ }
194
+ function parseCoerceArg(args) {
195
+ if (!args) return null;
196
+ const trimmed = args.trim();
197
+ return VALID_COERCE_TARGETS.has(trimmed) ? trimmed : null;
198
+ }
199
+ function extractStringArg(args) {
200
+ const trimmed = args.trim();
201
+ const match = /^["'](.*)["']$/s.exec(trimmed);
202
+ return match ? match[1] : trimmed;
203
+ }
204
+ export {
205
+ parseAnnotations
206
+ };
207
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/annotations/parser.ts"],"sourcesContent":["// Annotation parser for PolyPrism doc-comment annotations.\n//\n// Grammar (v0.1):\n// line = '@' ident // @hide\n// | '@' ident '(' args ')' // @deprecated(\"…\"), @json(…), …\n// | '[' ident ']' // prisma-json-types-generator compat alias\n// | <free text> // preserved as documentation\n//\n// Multi-line annotations are supported: if a line opens '(' or '{' and\n// doesn't close it, subsequent lines are concatenated until parens balance.\n// String literals are respected during the depth tracking so a brace inside\n// a string doesn't unbalance the depth.\n\nimport type {\n AnnotationSet,\n CoerceTo,\n JsonAnnotation,\n NormaliseOp,\n TypeOverride,\n} from \"../ir/types.js\";\n\nconst VALID_NORMALISE_OPS = new Set<NormaliseOp>([\n \"trim\",\n \"lowercase\",\n \"uppercase\",\n \"nullEmptyToNull\",\n]);\n\nconst VALID_COERCE_TARGETS = new Set<CoerceTo>([\"date\", \"int\", \"float\", \"decimal\", \"string\"]);\n\n/**\n * Parse a documentation string into an AnnotationSet.\n * Lines that aren't annotations are preserved in `documentation`.\n */\nexport function parseAnnotations(doc: string | null): AnnotationSet {\n const result: AnnotationSet = {\n hide: false,\n deprecated: null,\n json: null,\n type: null,\n name: null,\n normalise: null,\n coerce: null,\n documentation: null,\n rawAnnotations: [],\n };\n\n if (!doc) return result;\n\n const logicalLines = splitLogicalLines(doc);\n const docLines: string[] = [];\n const rawAnnotations: string[] = [];\n\n for (const line of logicalLines) {\n const trimmed = line.trim();\n\n // Shorthand: [TypeName] → @json(TypeName)\n const bracketMatch = /^\\[(\\w+)\\]$/.exec(trimmed);\n if (bracketMatch) {\n result.json = { kind: \"bare\", typeName: bracketMatch[1]! };\n rawAnnotations.push(trimmed);\n continue;\n }\n\n // @ident or @ident(args)\n const annotationMatch = /^@(\\w+)(?:\\s*\\(([\\s\\S]*)\\))?$/.exec(trimmed);\n if (annotationMatch) {\n applyAnnotation(annotationMatch[1]!, annotationMatch[2] ?? null, result);\n rawAnnotations.push(trimmed);\n continue;\n }\n\n // Not an annotation — preserve as documentation\n docLines.push(line);\n }\n\n const joinedDoc = docLines.join(\"\\n\").trim();\n result.documentation = joinedDoc.length > 0 ? joinedDoc : null;\n result.rawAnnotations = rawAnnotations;\n return result;\n}\n\n/**\n * Split a multi-line doc string into logical lines, joining continuations\n * where parens or braces remain open. Respects string literals.\n */\nfunction splitLogicalLines(doc: string): string[] {\n const physical = doc.split(\"\\n\");\n const logical: string[] = [];\n let buffer = \"\";\n let parenDepth = 0;\n let braceDepth = 0;\n let bracketDepth = 0;\n let inString: '\"' | \"'\" | \"`\" | null = null;\n let isEscaped = false;\n\n for (const line of physical) {\n for (const ch of line) {\n if (isEscaped) {\n isEscaped = false;\n continue;\n }\n if (ch === \"\\\\\") {\n isEscaped = true;\n continue;\n }\n if (inString) {\n if (ch === inString) inString = null;\n continue;\n }\n if (ch === '\"' || ch === \"'\" || ch === \"`\") {\n inString = ch;\n continue;\n }\n if (ch === \"(\") parenDepth++;\n else if (ch === \")\") parenDepth--;\n else if (ch === \"{\") braceDepth++;\n else if (ch === \"}\") braceDepth--;\n else if (ch === \"[\") bracketDepth++;\n else if (ch === \"]\") bracketDepth--;\n }\n\n buffer = buffer.length === 0 ? line : `${buffer} ${line.trim()}`;\n\n if (parenDepth === 0 && braceDepth === 0 && bracketDepth === 0 && inString === null) {\n logical.push(buffer);\n buffer = \"\";\n }\n }\n\n if (buffer.length > 0) logical.push(buffer);\n return logical;\n}\n\nfunction applyAnnotation(name: string, args: string | null, set: AnnotationSet): void {\n switch (name) {\n case \"hide\":\n set.hide = true;\n return;\n case \"deprecated\":\n set.deprecated = { reason: args ? extractStringArg(args) : null };\n return;\n case \"json\":\n set.json = parseJsonArgs(args);\n return;\n case \"type\":\n set.type = parseTypeArgs(args);\n return;\n case \"name\":\n set.name = args ? args.trim() : null;\n return;\n case \"normalise\":\n case \"normalize\":\n set.normalise = parseNormaliseArgs(args);\n return;\n case \"coerce\":\n set.coerce = parseCoerceArg(args);\n return;\n default:\n // Unknown annotation — already captured in rawAnnotations; ignored otherwise.\n return;\n }\n}\n\nfunction parseJsonArgs(args: string | null): JsonAnnotation | null {\n if (!args) return null;\n const trimmed = args.trim();\n if (trimmed.length === 0) return null;\n\n // Form 2: with-path — `TypeName from \"./path\"`\n // Check first so the assignment scan below doesn't get confused by paths\n // containing characters that look operator-like.\n const pathMatch = /^(\\w+)\\s+from\\s+[\"'](.+)[\"']$/.exec(trimmed);\n if (pathMatch) {\n return { kind: \"with-path\", typeName: pathMatch[1]!, importPath: pathMatch[2]! };\n }\n\n // Form 4: inline named — `Name = expression`\n // Locate a top-level `=` that's NOT inside braces/parens/brackets/angles,\n // NOT inside a string/template-literal, and NOT part of a compound operator\n // (`==`, `=>`, `!=`, `<=`, `>=`).\n const eqIndex = findTopLevelAssignment(trimmed);\n if (eqIndex > 0) {\n const namePart = trimmed.slice(0, eqIndex).trim();\n const exprPart = trimmed.slice(eqIndex + 1).trim();\n if (/^\\w+$/.test(namePart) && exprPart.length > 0) {\n return { kind: \"inline-named\", typeName: namePart, typeExpression: exprPart };\n }\n }\n\n // Form 1: bare — a single identifier and nothing else.\n if (/^\\w+$/.test(trimmed)) {\n return { kind: \"bare\", typeName: trimmed };\n }\n\n // Form 3: inline anonymous — anything else with structure.\n // We don't validate TS syntax; the user's tsc will catch invalid expressions.\n return { kind: \"inline-anonymous\", typeExpression: trimmed };\n}\n\n/**\n * Find the index of the first top-level `=` character in a TS-type-like\n * expression, or -1 if none exists. Respects:\n * - depth tracking for `{}`, `()`, `[]`, `<>` (angles treated as generic brackets)\n * - string and template-literal regions (`\"...\"`, `'...'`, `` `...` ``)\n * - isEscaped sequences (`\\` skips the next char inside strings)\n * - compound operators (`==`, `=>`, `!=`, `<=`, `>=`, `===`, `!==`)\n */\nfunction findTopLevelAssignment(s: string): number {\n let depth = 0;\n let inString: '\"' | \"'\" | \"`\" | null = null;\n let isEscaped = false;\n\n for (let i = 0; i < s.length; i++) {\n const ch = s[i]!;\n\n if (isEscaped) {\n isEscaped = false;\n continue;\n }\n if (ch === \"\\\\\") {\n isEscaped = true;\n continue;\n }\n if (inString) {\n if (ch === inString) inString = null;\n continue;\n }\n if (ch === '\"' || ch === \"'\" || ch === \"`\") {\n inString = ch;\n continue;\n }\n if (ch === \"{\" || ch === \"(\" || ch === \"[\" || ch === \"<\") {\n depth++;\n continue;\n }\n if (ch === \"}\" || ch === \")\" || ch === \"]\" || ch === \">\") {\n depth--;\n continue;\n }\n if (ch === \"=\" && depth === 0) {\n const prev = i > 0 ? s[i - 1] : \"\";\n const next = i + 1 < s.length ? s[i + 1] : \"\";\n // Compound operators we should NOT treat as the assignment `=`:\n // `==` `===` `=>` `!=` `!==` `<=` `>=`\n if (next === \"=\" || next === \">\") continue;\n if (prev === \"=\" || prev === \"!\" || prev === \"<\" || prev === \">\") continue;\n return i;\n }\n }\n return -1;\n}\n\nfunction parseTypeArgs(args: string | null): TypeOverride | null {\n if (!args) return null;\n const trimmed = args.trim();\n\n const pathMatch = /^(\\w+)\\s+from\\s+[\"'](.+)[\"']$/.exec(trimmed);\n if (pathMatch) {\n return { typeName: pathMatch[1]!, importPath: pathMatch[2]! };\n }\n\n if (/^\\w+$/.test(trimmed)) {\n return { typeName: trimmed, importPath: null };\n }\n\n return null;\n}\n\nfunction parseNormaliseArgs(args: string | null): readonly NormaliseOp[] | null {\n if (!args) return null;\n const ops = args\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => s.length > 0)\n .filter((s): s is NormaliseOp => VALID_NORMALISE_OPS.has(s as NormaliseOp));\n return ops.length > 0 ? ops : null;\n}\n\nfunction parseCoerceArg(args: string | null): CoerceTo | null {\n if (!args) return null;\n const trimmed = args.trim();\n return VALID_COERCE_TARGETS.has(trimmed as CoerceTo) ? (trimmed as CoerceTo) : null;\n}\n\nfunction extractStringArg(args: string): string | null {\n const trimmed = args.trim();\n const match = /^[\"'](.*)[\"']$/s.exec(trimmed);\n return match ? match[1]! : trimmed;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,9 @@
1
+ import { GeneratorContext } from '../generator/context.js';
2
+ import './file-writer.js';
3
+ import '../ir/types.js';
4
+ import '../generator/config.js';
5
+ import '../naming/types.js';
6
+
7
+ declare function emitEnums(ctx: GeneratorContext): Promise<void>;
8
+
9
+ export { emitEnums };
@@ -0,0 +1,19 @@
1
+ import { resolveTypeFilename, resolveTypeIdent } from "../naming/resolver.js";
2
+ import { renderEnum } from "./render-enum.js";
3
+ async function emitEnums(ctx) {
4
+ for (const enumDef of ctx.ir.enums) {
5
+ if (enumDef.annotations.hide) continue;
6
+ const ident = resolveTypeIdent({
7
+ schemaName: enumDef.name,
8
+ override: enumDef.annotations.name,
9
+ convention: ctx.config.naming.typeNaming
10
+ });
11
+ const filename = resolveTypeFilename(ident, ctx.config.naming.fileNaming);
12
+ const content = renderEnum(enumDef, ctx.config.naming);
13
+ await ctx.writer.write(`enums/${filename}.ts`, content);
14
+ }
15
+ }
16
+ export {
17
+ emitEnums
18
+ };
19
+ //# sourceMappingURL=emit-enums.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/emitter/emit-enums.ts"],"sourcesContent":["// High-level emitter: walks the IR's enums and writes one file per enum.\n// Each pattern's emitter calls `await emitEnums(ctx)` for free standalone\n// enum emission. The directory is fixed at `enums/` for consistency.\n\nimport type { GeneratorContext } from \"../generator/context.js\";\nimport { resolveTypeFilename, resolveTypeIdent } from \"../naming/resolver.js\";\nimport { renderEnum } from \"./render-enum.js\";\n\nexport async function emitEnums(ctx: GeneratorContext): Promise<void> {\n for (const enumDef of ctx.ir.enums) {\n if (enumDef.annotations.hide) continue;\n\n const ident = resolveTypeIdent({\n schemaName: enumDef.name,\n override: enumDef.annotations.name,\n convention: ctx.config.naming.typeNaming,\n });\n const filename = resolveTypeFilename(ident, ctx.config.naming.fileNaming);\n\n const content = renderEnum(enumDef, ctx.config.naming);\n await ctx.writer.write(`enums/${filename}.ts`, content);\n }\n}\n"],"mappings":"AAKA,SAAS,qBAAqB,wBAAwB;AACtD,SAAS,kBAAkB;AAE3B,eAAsB,UAAU,KAAsC;AACpE,aAAW,WAAW,IAAI,GAAG,OAAO;AAClC,QAAI,QAAQ,YAAY,KAAM;AAE9B,UAAM,QAAQ,iBAAiB;AAAA,MAC7B,YAAY,QAAQ;AAAA,MACpB,UAAU,QAAQ,YAAY;AAAA,MAC9B,YAAY,IAAI,OAAO,OAAO;AAAA,IAChC,CAAC;AACD,UAAM,WAAW,oBAAoB,OAAO,IAAI,OAAO,OAAO,UAAU;AAExE,UAAM,UAAU,WAAW,SAAS,IAAI,OAAO,MAAM;AACrD,UAAM,IAAI,OAAO,MAAM,SAAS,QAAQ,OAAO,OAAO;AAAA,EACxD;AACF;","names":[]}
@@ -0,0 +1,9 @@
1
+ import { GeneratorContext } from '../generator/context.js';
2
+ import './file-writer.js';
3
+ import '../ir/types.js';
4
+ import '../generator/config.js';
5
+ import '../naming/types.js';
6
+
7
+ declare function emitJsonTypes(ctx: GeneratorContext): Promise<void>;
8
+
9
+ export { emitJsonTypes };
@@ -0,0 +1,28 @@
1
+ import { autoNameInlineJson, resolveTypeFilename } from "../naming/resolver.js";
2
+ import { renderJsonType } from "./render-json-type.js";
3
+ async function emitJsonTypes(ctx) {
4
+ const collected = /* @__PURE__ */ new Map();
5
+ for (const model of ctx.ir.models) {
6
+ if (model.annotations.hide) continue;
7
+ for (const field of model.fields) {
8
+ if (field.annotations.hide) continue;
9
+ const json = field.annotations.json;
10
+ if (!json) continue;
11
+ if (json.kind === "inline-anonymous") {
12
+ const name = autoNameInlineJson(model.name, field.name);
13
+ collected.set(name, json.typeExpression);
14
+ } else if (json.kind === "inline-named") {
15
+ collected.set(json.typeName, json.typeExpression);
16
+ }
17
+ }
18
+ }
19
+ for (const [typeName, expr] of collected) {
20
+ const filename = resolveTypeFilename(typeName, ctx.config.naming.fileNaming);
21
+ const content = renderJsonType(typeName, expr);
22
+ await ctx.writer.write(`json-types/${filename}.ts`, content);
23
+ }
24
+ }
25
+ export {
26
+ emitJsonTypes
27
+ };
28
+ //# sourceMappingURL=emit-json-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/emitter/emit-json-types.ts"],"sourcesContent":["// High-level emitter: walks the IR for inline `@json(...)` annotations\n// (forms 3 + 4) and writes one file per generated JSON type.\n//\n// Bare and with-path `@json` forms reference an existing user type — no\n// file emission for those. Inline forms (anonymous and named) generate\n// a type alias file under `json-types/`.\n\nimport type { GeneratorContext } from \"../generator/context.js\";\nimport { autoNameInlineJson, resolveTypeFilename } from \"../naming/resolver.js\";\nimport { renderJsonType } from \"./render-json-type.js\";\n\nexport async function emitJsonTypes(ctx: GeneratorContext): Promise<void> {\n const collected = new Map<string, string>();\n\n for (const model of ctx.ir.models) {\n if (model.annotations.hide) continue;\n for (const field of model.fields) {\n if (field.annotations.hide) continue;\n const json = field.annotations.json;\n if (!json) continue;\n\n if (json.kind === \"inline-anonymous\") {\n const name = autoNameInlineJson(model.name, field.name);\n collected.set(name, json.typeExpression);\n } else if (json.kind === \"inline-named\") {\n collected.set(json.typeName, json.typeExpression);\n }\n // bare + with-path reference existing user types — no file to emit\n }\n }\n\n for (const [typeName, expr] of collected) {\n const filename = resolveTypeFilename(typeName, ctx.config.naming.fileNaming);\n const content = renderJsonType(typeName, expr);\n await ctx.writer.write(`json-types/${filename}.ts`, content);\n }\n}\n"],"mappings":"AAQA,SAAS,oBAAoB,2BAA2B;AACxD,SAAS,sBAAsB;AAE/B,eAAsB,cAAc,KAAsC;AACxE,QAAM,YAAY,oBAAI,IAAoB;AAE1C,aAAW,SAAS,IAAI,GAAG,QAAQ;AACjC,QAAI,MAAM,YAAY,KAAM;AAC5B,eAAW,SAAS,MAAM,QAAQ;AAChC,UAAI,MAAM,YAAY,KAAM;AAC5B,YAAM,OAAO,MAAM,YAAY;AAC/B,UAAI,CAAC,KAAM;AAEX,UAAI,KAAK,SAAS,oBAAoB;AACpC,cAAM,OAAO,mBAAmB,MAAM,MAAM,MAAM,IAAI;AACtD,kBAAU,IAAI,MAAM,KAAK,cAAc;AAAA,MACzC,WAAW,KAAK,SAAS,gBAAgB;AACvC,kBAAU,IAAI,KAAK,UAAU,KAAK,cAAc;AAAA,MAClD;AAAA,IAEF;AAAA,EACF;AAEA,aAAW,CAAC,UAAU,IAAI,KAAK,WAAW;AACxC,UAAM,WAAW,oBAAoB,UAAU,IAAI,OAAO,OAAO,UAAU;AAC3E,UAAM,UAAU,eAAe,UAAU,IAAI;AAC7C,UAAM,IAAI,OAAO,MAAM,cAAc,QAAQ,OAAO,OAAO;AAAA,EAC7D;AACF;","names":[]}
@@ -0,0 +1,12 @@
1
+ interface FileWriter {
2
+ /** Write `content` to `relativePath` (relative to the writer's output dir). */
3
+ write(relativePath: string, content: string): Promise<void>;
4
+ }
5
+ declare function createFileWriter(outputDir: string): FileWriter;
6
+ /** In-memory writer for tests. Captures file paths and contents. */
7
+ interface InMemoryFileWriter extends FileWriter {
8
+ readonly files: ReadonlyMap<string, string>;
9
+ }
10
+ declare function createInMemoryFileWriter(): InMemoryFileWriter;
11
+
12
+ export { type FileWriter, type InMemoryFileWriter, createFileWriter, createInMemoryFileWriter };
@@ -0,0 +1,25 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { dirname, resolve } from "node:path";
3
+ function createFileWriter(outputDir) {
4
+ return {
5
+ async write(relativePath, content) {
6
+ const fullPath = resolve(outputDir, relativePath);
7
+ await mkdir(dirname(fullPath), { recursive: true });
8
+ await writeFile(fullPath, content, "utf8");
9
+ }
10
+ };
11
+ }
12
+ function createInMemoryFileWriter() {
13
+ const files = /* @__PURE__ */ new Map();
14
+ return {
15
+ files,
16
+ async write(relativePath, content) {
17
+ files.set(relativePath, content);
18
+ }
19
+ };
20
+ }
21
+ export {
22
+ createFileWriter,
23
+ createInMemoryFileWriter
24
+ };
25
+ //# sourceMappingURL=file-writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/emitter/file-writer.ts"],"sourcesContent":["// File output abstraction used by all pattern emitters.\n// The actual implementation writes to disk; tests can substitute an\n// in-memory writer that captures emitted files for snapshotting.\n\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\n\nexport interface FileWriter {\n /** Write `content` to `relativePath` (relative to the writer's output dir). */\n write(relativePath: string, content: string): Promise<void>;\n}\n\nexport function createFileWriter(outputDir: string): FileWriter {\n return {\n async write(relativePath: string, content: string): Promise<void> {\n const fullPath = resolve(outputDir, relativePath);\n await mkdir(dirname(fullPath), { recursive: true });\n await writeFile(fullPath, content, \"utf8\");\n },\n };\n}\n\n/** In-memory writer for tests. Captures file paths and contents. */\nexport interface InMemoryFileWriter extends FileWriter {\n readonly files: ReadonlyMap<string, string>;\n}\n\nexport function createInMemoryFileWriter(): InMemoryFileWriter {\n const files = new Map<string, string>();\n return {\n files,\n async write(relativePath: string, content: string): Promise<void> {\n files.set(relativePath, content);\n },\n };\n}\n"],"mappings":"AAIA,SAAS,OAAO,iBAAiB;AACjC,SAAS,SAAS,eAAe;AAO1B,SAAS,iBAAiB,WAA+B;AAC9D,SAAO;AAAA,IACL,MAAM,MAAM,cAAsB,SAAgC;AAChE,YAAM,WAAW,QAAQ,WAAW,YAAY;AAChD,YAAM,MAAM,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAClD,YAAM,UAAU,UAAU,SAAS,MAAM;AAAA,IAC3C;AAAA,EACF;AACF;AAOO,SAAS,2BAA+C;AAC7D,QAAM,QAAQ,oBAAI,IAAoB;AACtC,SAAO;AAAA,IACL;AAAA,IACA,MAAM,MAAM,cAAsB,SAAgC;AAChE,YAAM,IAAI,cAAc,OAAO;AAAA,IACjC;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,3 @@
1
+ declare function prettyFormatType(expr: string): string;
2
+
3
+ export { prettyFormatType };