@llui/vite-plugin 0.0.31 → 0.0.34

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.
@@ -1,29 +1,50 @@
1
1
  import ts from 'typescript';
2
- export function extractMsgSchema(source) {
3
- return extractDiscriminatedUnionSchema(source, 'Msg');
2
+ /** True when `f` is a rich descriptor (object with `type` key). */
3
+ export function isRichField(f) {
4
+ return typeof f === 'object' && f !== null && !Array.isArray(f) && 'type' in f;
4
5
  }
5
- export function extractEffectSchema(source) {
6
- return extractDiscriminatedUnionSchema(source, 'Effect');
6
+ /** Extracts the bare type from either descriptor form. */
7
+ export function fieldType(f) {
8
+ return isRichField(f) ? f.type : f;
9
+ }
10
+ export function extractMsgSchema(source, typeName = 'Msg') {
11
+ return extractDiscriminatedUnionSchema(source, typeName);
12
+ }
13
+ export function extractEffectSchema(source, typeName = 'Effect') {
14
+ return extractDiscriminatedUnionSchema(source, typeName);
7
15
  }
8
16
  function extractDiscriminatedUnionSchema(source, typeName) {
9
17
  const sf = ts.createSourceFile('input.ts', source, ts.ScriptTarget.Latest, true);
18
+ const typeIndex = buildTypeIndex(sf);
10
19
  for (const stmt of sf.statements) {
11
20
  if (!ts.isTypeAliasDeclaration(stmt))
12
21
  continue;
13
22
  if (stmt.name.text !== typeName)
14
23
  continue;
15
24
  const variants = {};
16
- collectVariants(stmt.type, variants);
25
+ collectVariants(stmt.type, variants, source, typeIndex);
17
26
  if (Object.keys(variants).length === 0)
18
27
  return null;
19
28
  return { discriminant: 'type', variants };
20
29
  }
21
30
  return null;
22
31
  }
23
- function collectVariants(type, variants) {
32
+ function buildTypeIndex(sf) {
33
+ const index = new Map();
34
+ for (const stmt of sf.statements) {
35
+ if (ts.isTypeAliasDeclaration(stmt)) {
36
+ index.set(stmt.name.text, stmt.type);
37
+ }
38
+ else if (ts.isInterfaceDeclaration(stmt)) {
39
+ index.set(stmt.name.text, stmt);
40
+ }
41
+ }
42
+ return index;
43
+ }
44
+ function collectVariants(type, variants, source, typeIndex) {
24
45
  if (ts.isUnionTypeNode(type)) {
25
46
  for (const member of type.types) {
26
- collectVariants(member, variants);
47
+ collectVariants(member, variants, source, typeIndex);
27
48
  }
28
49
  return;
29
50
  }
@@ -42,18 +63,57 @@ function collectVariants(type, variants) {
42
63
  }
43
64
  continue;
44
65
  }
45
- if (!memberType) {
46
- fields[name] = 'unknown';
47
- continue;
48
- }
49
- fields[name] = resolveFieldType(memberType);
66
+ fields[name] = buildFieldDescriptor(member, source, typeIndex);
50
67
  }
51
68
  if (discriminantValue) {
52
69
  variants[discriminantValue] = fields;
53
70
  }
54
71
  }
55
72
  }
56
- function resolveFieldType(type) {
73
+ /**
74
+ * Build a single field descriptor from a property signature: type,
75
+ * optionality, and any `@should("…")` JSDoc hint. Emits the compact
76
+ * bare form when there's nothing extra to communicate; otherwise the
77
+ * rich `{type, optional?, priority?, hint?}` shape.
78
+ *
79
+ * Exported so the cross-file resolver (which walks the same property
80
+ * signatures when the Msg type lives in a different file from the
81
+ * `component()` call) can produce identical descriptors. Without
82
+ * sharing this helper, JSDoc hints would silently disappear whenever
83
+ * a Msg union got resolved across module boundaries.
84
+ */
85
+ export function buildFieldDescriptor(member, source, typeIndex = new Map()) {
86
+ const baseType = member.type
87
+ ? resolveFieldType(member.type, typeIndex, MAX_FIELD_DEPTH)
88
+ : 'unknown';
89
+ const optional = member.questionToken !== undefined;
90
+ const jsdoc = readMemberJSDoc(source, member);
91
+ const hint = readShouldHint(jsdoc);
92
+ if (!optional && hint === null) {
93
+ return baseType;
94
+ }
95
+ const rich = { type: baseType };
96
+ if (optional)
97
+ rich.optional = true;
98
+ if (hint !== null) {
99
+ rich.priority = 'should';
100
+ rich.hint = hint;
101
+ }
102
+ return rich;
103
+ }
104
+ /**
105
+ * Recursion bound for nested type resolution. Stops the extractor
106
+ * before it spirals on self-referential or mutually-recursive types
107
+ * (`type Tree = { children: Tree[] }`). At depth 0 every reference
108
+ * collapses to `'unknown'`; the synthesizer emits `null` and the
109
+ * agent falls back to free-form filling.
110
+ *
111
+ * 3 covers the common cases (Msg payload → Criterion → ValueMeta),
112
+ * keeps the bundle bounded, and is well under the Tarjan-style
113
+ * depths needed for actual recursive types.
114
+ */
115
+ const MAX_FIELD_DEPTH = 3;
116
+ export function resolveFieldType(type, typeIndex = new Map(), depth = MAX_FIELD_DEPTH) {
57
117
  // Primitive keywords
58
118
  if (type.kind === ts.SyntaxKind.StringKeyword)
59
119
  return 'string';
@@ -78,6 +138,134 @@ function resolveFieldType(type) {
78
138
  return { enum: literals };
79
139
  }
80
140
  }
141
+ // Below this point, all branches need depth budget. Bail out cheaply.
142
+ if (depth <= 0)
143
+ return 'unknown';
144
+ // Inline object literal — `{a: number; b: string}` directly.
145
+ if (ts.isTypeLiteralNode(type)) {
146
+ return { kind: 'object', shape: collectInlineShape(type, typeIndex, depth - 1) };
147
+ }
148
+ // Array type — `T[]` and `readonly T[]`.
149
+ if (ts.isArrayTypeNode(type)) {
150
+ return { kind: 'array', element: resolveFieldType(type.elementType, typeIndex, depth - 1) };
151
+ }
152
+ // Generic Array<T> (less common in app code but compiler may produce it).
153
+ if (ts.isTypeReferenceNode(type) &&
154
+ ts.isIdentifier(type.typeName) &&
155
+ type.typeName.text === 'Array' &&
156
+ type.typeArguments?.length === 1 &&
157
+ type.typeArguments[0]) {
158
+ return {
159
+ kind: 'array',
160
+ element: resolveFieldType(type.typeArguments[0], typeIndex, depth - 1),
161
+ };
162
+ }
163
+ // ReadonlyArray<T> → same shape; the readonly modifier is purely a
164
+ // TypeScript-side concern that the agent never observes at runtime.
165
+ if (ts.isTypeReferenceNode(type) &&
166
+ ts.isIdentifier(type.typeName) &&
167
+ type.typeName.text === 'ReadonlyArray' &&
168
+ type.typeArguments?.length === 1 &&
169
+ type.typeArguments[0]) {
170
+ return {
171
+ kind: 'array',
172
+ element: resolveFieldType(type.typeArguments[0], typeIndex, depth - 1),
173
+ };
174
+ }
175
+ // `readonly T[]` parses as TypeOperator(readonly) wrapping ArrayType.
176
+ if (ts.isTypeOperatorNode(type) && type.operator === ts.SyntaxKind.ReadonlyKeyword) {
177
+ return resolveFieldType(type.type, typeIndex, depth);
178
+ }
179
+ // Named type reference — chase it through the local index.
180
+ if (ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName)) {
181
+ const target = typeIndex.get(type.typeName.text);
182
+ if (target) {
183
+ if (ts.isInterfaceDeclaration(target)) {
184
+ return {
185
+ kind: 'object',
186
+ shape: collectInterfaceShape(target, typeIndex, depth - 1),
187
+ };
188
+ }
189
+ // Type alias — recurse on its body. `type Foo = …` could resolve
190
+ // to anything (object literal, array, union, primitive); each
191
+ // already has its own branch above.
192
+ return resolveFieldType(target, typeIndex, depth - 1);
193
+ }
194
+ // Reference to a type the index doesn't know about — typically
195
+ // imported from another module. Cross-file resolution is the
196
+ // separate cross-file-resolver pipeline's job; leave this as
197
+ // unknown rather than fabricating a misleading shape.
198
+ return 'unknown';
199
+ }
81
200
  return 'unknown';
82
201
  }
202
+ function collectInlineShape(lit, typeIndex, depth) {
203
+ const shape = {};
204
+ for (const member of lit.members) {
205
+ if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name))
206
+ continue;
207
+ const name = member.name.text;
208
+ const baseType = member.type
209
+ ? resolveFieldType(member.type, typeIndex, depth)
210
+ : 'unknown';
211
+ const optional = member.questionToken !== undefined;
212
+ if (!optional) {
213
+ shape[name] = baseType;
214
+ }
215
+ else {
216
+ shape[name] = { type: baseType, optional: true };
217
+ }
218
+ }
219
+ return shape;
220
+ }
221
+ function collectInterfaceShape(iface, typeIndex, depth) {
222
+ const shape = {};
223
+ for (const member of iface.members) {
224
+ if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name))
225
+ continue;
226
+ const name = member.name.text;
227
+ const baseType = member.type
228
+ ? resolveFieldType(member.type, typeIndex, depth)
229
+ : 'unknown';
230
+ const optional = member.questionToken !== undefined;
231
+ if (!optional) {
232
+ shape[name] = baseType;
233
+ }
234
+ else {
235
+ shape[name] = { type: baseType, optional: true };
236
+ }
237
+ }
238
+ return shape;
239
+ }
240
+ /**
241
+ * Read the leading JSDoc block immediately above `member`. The
242
+ * TypeScript parser doesn't attach JSDoc to interior property
243
+ * signatures, so we re-scan the source between the previous member's
244
+ * end (or the type-literal's `{`) and this member's start, and return
245
+ * the last `/** … *\/` block found there. Returns `''` when none.
246
+ */
247
+ function readMemberJSDoc(source, member) {
248
+ const ranges = ts.getLeadingCommentRanges(source, member.pos) ?? [];
249
+ // Walk in order, keeping only `/** */` blocks. Multiple back-to-back
250
+ // JSDocs concatenate (matches msg-annotations.ts's existing behavior).
251
+ const docs = ranges
252
+ .filter((r) => r.kind === ts.SyntaxKind.MultiLineCommentTrivia)
253
+ .map((r) => source.slice(r.pos, r.end))
254
+ .filter((txt) => txt.startsWith('/**'));
255
+ return docs.join('\n');
256
+ }
257
+ /**
258
+ * Match `@should("…")` (and curly-quote variant) anywhere in the
259
+ * JSDoc. Mirrors msg-annotations.ts's `@intent` parser — same grammar,
260
+ * same tolerance for either ASCII or curly quotes.
261
+ *
262
+ * Returns the unescaped string content, or null when the tag is
263
+ * absent or malformed.
264
+ */
265
+ function readShouldHint(comment) {
266
+ if (!comment)
267
+ return null;
268
+ const match = comment.match(/@should\s*\(\s*["“]([^"”]*)["”]\s*\)/);
269
+ return match?.[1] ?? null;
270
+ }
83
271
  //# sourceMappingURL=msg-schema.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"msg-schema.js","sourceRoot":"","sources":["../src/msg-schema.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript'\n\nexport interface MsgSchema {\n discriminant: string\n variants: Record<string, Record<string, string | { enum: string[] }>>\n}\n\nexport function extractMsgSchema(source: string): MsgSchema | null {\n return extractDiscriminatedUnionSchema(source, 'Msg')\n}\n\nexport function extractEffectSchema(source: string): MsgSchema | null {\n return extractDiscriminatedUnionSchema(source, 'Effect')\n}\n\nfunction extractDiscriminatedUnionSchema(source: string, typeName: string): MsgSchema | null {\n const sf = ts.createSourceFile('input.ts', source, ts.ScriptTarget.Latest, true)\n\n for (const stmt of sf.statements) {\n if (!ts.isTypeAliasDeclaration(stmt)) continue\n if (stmt.name.text !== typeName) continue\n\n const variants: MsgSchema['variants'] = {}\n collectVariants(stmt.type, variants)\n\n if (Object.keys(variants).length === 0) return null\n\n return { discriminant: 'type', variants }\n }\n\n return null\n}\n\nfunction collectVariants(type: ts.TypeNode, variants: MsgSchema['variants']): void {\n if (ts.isUnionTypeNode(type)) {\n for (const member of type.types) {\n collectVariants(member, variants)\n }\n return\n }\n\n if (ts.isTypeLiteralNode(type)) {\n let discriminantValue: string | null = null\n const fields: Record<string, string | { enum: string[] }> = {}\n\n for (const member of type.members) {\n if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name)) continue\n\n const name = member.name.text\n const memberType = member.type\n\n if (name === 'type' && memberType) {\n // Extract the discriminant value\n if (ts.isLiteralTypeNode(memberType) && ts.isStringLiteral(memberType.literal)) {\n discriminantValue = memberType.literal.text\n }\n continue\n }\n\n if (!memberType) {\n fields[name] = 'unknown'\n continue\n }\n\n fields[name] = resolveFieldType(memberType)\n }\n\n if (discriminantValue) {\n variants[discriminantValue] = fields\n }\n }\n}\n\nfunction resolveFieldType(type: ts.TypeNode): string | { enum: string[] } {\n // Primitive keywords\n if (type.kind === ts.SyntaxKind.StringKeyword) return 'string'\n if (type.kind === ts.SyntaxKind.NumberKeyword) return 'number'\n if (type.kind === ts.SyntaxKind.BooleanKeyword) return 'boolean'\n\n // String literal union: 'a' | 'b' | 'c'\n if (ts.isUnionTypeNode(type)) {\n const literals: string[] = []\n let allLiterals = true\n for (const member of type.types) {\n if (ts.isLiteralTypeNode(member) && ts.isStringLiteral(member.literal)) {\n literals.push(member.literal.text)\n } else {\n allLiterals = false\n break\n }\n }\n if (allLiterals && literals.length > 0) {\n return { enum: literals }\n }\n }\n\n return 'unknown'\n}\n"]}
1
+ {"version":3,"file":"msg-schema.js","sourceRoot":"","sources":["../src/msg-schema.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript'\n\n/**\n * The \"bare type\" of a field. Covers four cases:\n * - primitive keyword as a string: `'string'`, `'number'`, `'boolean'`, `'unknown'`\n * - string-literal union: `{enum: ['a', 'b']}`\n * - nested object shape: `{kind: 'object', shape: {...}}` — emitted when\n * a field's type is a local interface/type alias the extractor could\n * follow (depth-limited; cross-file references stay `'unknown'`).\n * - array of element type: `{kind: 'array', element: <bare type>}`.\n *\n * The synthesizer in `@llui/agent`'s `list_actions` walks these to build\n * copy-paste-ready payload examples; the validator in `send_message`\n * walks them too (treating object/array as \"any\" since deep validation\n * is the reducer's job).\n */\nexport type MsgFieldType =\n | string\n | { enum: string[] }\n | { kind: 'object'; shape: Record<string, MsgField> }\n | { kind: 'array'; element: MsgFieldType }\n\n/**\n * Rich per-field descriptor. Emitted only when there's something\n * beyond the bare type to communicate — optionality, an explicit\n * priority hint, or a freeform agent hint. When everything but `type`\n * is unset, the producer emits the bare `MsgFieldType` instead so\n * variants without annotations stay byte-cheap in the bundle.\n */\nexport interface MsgFieldRich {\n type: MsgFieldType\n /** Mirrors TypeScript's `?:` optional marker. Required fields omit this. */\n optional?: boolean\n /**\n * Strength signal for optional fields. Borrows RFC 2119's `SHOULD`:\n * the LLM ought to fill it in unless it has a specific reason not\n * to. Required fields don't carry a priority — TS already conveys\n * \"must\" via the type system. Currently the only level; future\n * extensions could add `'recommended'` or similar.\n */\n priority?: 'should'\n /** Freeform consequence-shaped explanation. Surfaced verbatim to\n * the LLM at affordance time. */\n hint?: string\n}\n\nexport type MsgField = MsgFieldType | MsgFieldRich\n\nexport interface MsgSchema {\n discriminant: string\n variants: Record<string, Record<string, MsgField>>\n}\n\n/** True when `f` is a rich descriptor (object with `type` key). */\nexport function isRichField(f: MsgField): f is MsgFieldRich {\n return typeof f === 'object' && f !== null && !Array.isArray(f) && 'type' in f\n}\n\n/** Extracts the bare type from either descriptor form. */\nexport function fieldType(f: MsgField): MsgFieldType {\n return isRichField(f) ? f.type : f\n}\n\nexport function extractMsgSchema(source: string, typeName: string = 'Msg'): MsgSchema | null {\n return extractDiscriminatedUnionSchema(source, typeName)\n}\n\nexport function extractEffectSchema(source: string, typeName: string = 'Effect'): MsgSchema | null {\n return extractDiscriminatedUnionSchema(source, typeName)\n}\n\nfunction extractDiscriminatedUnionSchema(source: string, typeName: string): MsgSchema | null {\n const sf = ts.createSourceFile('input.ts', source, ts.ScriptTarget.Latest, true)\n const typeIndex = buildTypeIndex(sf)\n\n for (const stmt of sf.statements) {\n if (!ts.isTypeAliasDeclaration(stmt)) continue\n if (stmt.name.text !== typeName) continue\n\n const variants: MsgSchema['variants'] = {}\n collectVariants(stmt.type, variants, source, typeIndex)\n\n if (Object.keys(variants).length === 0) return null\n\n return { discriminant: 'type', variants }\n }\n\n return null\n}\n\n/**\n * Index of type aliases and interfaces visible from a source file,\n * keyed by name. Lets the field-type resolver follow `Criterion[]` →\n * `interface Criterion { … }` and emit a nested object shape rather\n * than `'unknown'`.\n *\n * The cross-file resolver pipeline (`cross-file-resolver.ts`) builds\n * an enriched index that includes types imported from sibling files —\n * follow `GridSorting` → `'rank' | 'crit-X' | 'crit-Y'` → `{enum: […]}`\n * even when the alias lives in `./state.ts` not the Msg-defining file.\n */\nexport type TypeIndex = Map<string, ts.TypeNode | ts.InterfaceDeclaration>\n\nfunction buildTypeIndex(sf: ts.SourceFile): TypeIndex {\n const index: TypeIndex = new Map()\n for (const stmt of sf.statements) {\n if (ts.isTypeAliasDeclaration(stmt)) {\n index.set(stmt.name.text, stmt.type)\n } else if (ts.isInterfaceDeclaration(stmt)) {\n index.set(stmt.name.text, stmt)\n }\n }\n return index\n}\n\nfunction collectVariants(\n type: ts.TypeNode,\n variants: MsgSchema['variants'],\n source: string,\n typeIndex: TypeIndex,\n): void {\n if (ts.isUnionTypeNode(type)) {\n for (const member of type.types) {\n collectVariants(member, variants, source, typeIndex)\n }\n return\n }\n\n if (ts.isTypeLiteralNode(type)) {\n let discriminantValue: string | null = null\n const fields: Record<string, MsgField> = {}\n\n for (const member of type.members) {\n if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name)) continue\n\n const name = member.name.text\n const memberType = member.type\n\n if (name === 'type' && memberType) {\n // Extract the discriminant value\n if (ts.isLiteralTypeNode(memberType) && ts.isStringLiteral(memberType.literal)) {\n discriminantValue = memberType.literal.text\n }\n continue\n }\n\n fields[name] = buildFieldDescriptor(member, source, typeIndex)\n }\n\n if (discriminantValue) {\n variants[discriminantValue] = fields\n }\n }\n}\n\n/**\n * Build a single field descriptor from a property signature: type,\n * optionality, and any `@should(\"…\")` JSDoc hint. Emits the compact\n * bare form when there's nothing extra to communicate; otherwise the\n * rich `{type, optional?, priority?, hint?}` shape.\n *\n * Exported so the cross-file resolver (which walks the same property\n * signatures when the Msg type lives in a different file from the\n * `component()` call) can produce identical descriptors. Without\n * sharing this helper, JSDoc hints would silently disappear whenever\n * a Msg union got resolved across module boundaries.\n */\nexport function buildFieldDescriptor(\n member: ts.PropertySignature,\n source: string,\n typeIndex: TypeIndex = new Map(),\n): MsgField {\n const baseType: MsgFieldType = member.type\n ? resolveFieldType(member.type, typeIndex, MAX_FIELD_DEPTH)\n : 'unknown'\n const optional = member.questionToken !== undefined\n const jsdoc = readMemberJSDoc(source, member)\n const hint = readShouldHint(jsdoc)\n\n if (!optional && hint === null) {\n return baseType\n }\n const rich: MsgFieldRich = { type: baseType }\n if (optional) rich.optional = true\n if (hint !== null) {\n rich.priority = 'should'\n rich.hint = hint\n }\n return rich\n}\n\n/**\n * Recursion bound for nested type resolution. Stops the extractor\n * before it spirals on self-referential or mutually-recursive types\n * (`type Tree = { children: Tree[] }`). At depth 0 every reference\n * collapses to `'unknown'`; the synthesizer emits `null` and the\n * agent falls back to free-form filling.\n *\n * 3 covers the common cases (Msg payload → Criterion → ValueMeta),\n * keeps the bundle bounded, and is well under the Tarjan-style\n * depths needed for actual recursive types.\n */\nconst MAX_FIELD_DEPTH = 3\n\nexport function resolveFieldType(\n type: ts.TypeNode,\n typeIndex: TypeIndex = new Map(),\n depth = MAX_FIELD_DEPTH,\n): MsgFieldType {\n // Primitive keywords\n if (type.kind === ts.SyntaxKind.StringKeyword) return 'string'\n if (type.kind === ts.SyntaxKind.NumberKeyword) return 'number'\n if (type.kind === ts.SyntaxKind.BooleanKeyword) return 'boolean'\n\n // String literal union: 'a' | 'b' | 'c'\n if (ts.isUnionTypeNode(type)) {\n const literals: string[] = []\n let allLiterals = true\n for (const member of type.types) {\n if (ts.isLiteralTypeNode(member) && ts.isStringLiteral(member.literal)) {\n literals.push(member.literal.text)\n } else {\n allLiterals = false\n break\n }\n }\n if (allLiterals && literals.length > 0) {\n return { enum: literals }\n }\n }\n\n // Below this point, all branches need depth budget. Bail out cheaply.\n if (depth <= 0) return 'unknown'\n\n // Inline object literal — `{a: number; b: string}` directly.\n if (ts.isTypeLiteralNode(type)) {\n return { kind: 'object', shape: collectInlineShape(type, typeIndex, depth - 1) }\n }\n\n // Array type — `T[]` and `readonly T[]`.\n if (ts.isArrayTypeNode(type)) {\n return { kind: 'array', element: resolveFieldType(type.elementType, typeIndex, depth - 1) }\n }\n // Generic Array<T> (less common in app code but compiler may produce it).\n if (\n ts.isTypeReferenceNode(type) &&\n ts.isIdentifier(type.typeName) &&\n type.typeName.text === 'Array' &&\n type.typeArguments?.length === 1 &&\n type.typeArguments[0]\n ) {\n return {\n kind: 'array',\n element: resolveFieldType(type.typeArguments[0], typeIndex, depth - 1),\n }\n }\n // ReadonlyArray<T> → same shape; the readonly modifier is purely a\n // TypeScript-side concern that the agent never observes at runtime.\n if (\n ts.isTypeReferenceNode(type) &&\n ts.isIdentifier(type.typeName) &&\n type.typeName.text === 'ReadonlyArray' &&\n type.typeArguments?.length === 1 &&\n type.typeArguments[0]\n ) {\n return {\n kind: 'array',\n element: resolveFieldType(type.typeArguments[0], typeIndex, depth - 1),\n }\n }\n // `readonly T[]` parses as TypeOperator(readonly) wrapping ArrayType.\n if (ts.isTypeOperatorNode(type) && type.operator === ts.SyntaxKind.ReadonlyKeyword) {\n return resolveFieldType(type.type, typeIndex, depth)\n }\n\n // Named type reference — chase it through the local index.\n if (ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName)) {\n const target = typeIndex.get(type.typeName.text)\n if (target) {\n if (ts.isInterfaceDeclaration(target)) {\n return {\n kind: 'object',\n shape: collectInterfaceShape(target, typeIndex, depth - 1),\n }\n }\n // Type alias — recurse on its body. `type Foo = …` could resolve\n // to anything (object literal, array, union, primitive); each\n // already has its own branch above.\n return resolveFieldType(target, typeIndex, depth - 1)\n }\n // Reference to a type the index doesn't know about — typically\n // imported from another module. Cross-file resolution is the\n // separate cross-file-resolver pipeline's job; leave this as\n // unknown rather than fabricating a misleading shape.\n return 'unknown'\n }\n\n return 'unknown'\n}\n\nfunction collectInlineShape(\n lit: ts.TypeLiteralNode,\n typeIndex: TypeIndex,\n depth: number,\n): Record<string, MsgField> {\n const shape: Record<string, MsgField> = {}\n for (const member of lit.members) {\n if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name)) continue\n const name = member.name.text\n const baseType: MsgFieldType = member.type\n ? resolveFieldType(member.type, typeIndex, depth)\n : 'unknown'\n const optional = member.questionToken !== undefined\n if (!optional) {\n shape[name] = baseType\n } else {\n shape[name] = { type: baseType, optional: true }\n }\n }\n return shape\n}\n\nfunction collectInterfaceShape(\n iface: ts.InterfaceDeclaration,\n typeIndex: TypeIndex,\n depth: number,\n): Record<string, MsgField> {\n const shape: Record<string, MsgField> = {}\n for (const member of iface.members) {\n if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name)) continue\n const name = member.name.text\n const baseType: MsgFieldType = member.type\n ? resolveFieldType(member.type, typeIndex, depth)\n : 'unknown'\n const optional = member.questionToken !== undefined\n if (!optional) {\n shape[name] = baseType\n } else {\n shape[name] = { type: baseType, optional: true }\n }\n }\n return shape\n}\n\n/**\n * Read the leading JSDoc block immediately above `member`. The\n * TypeScript parser doesn't attach JSDoc to interior property\n * signatures, so we re-scan the source between the previous member's\n * end (or the type-literal's `{`) and this member's start, and return\n * the last `/** … *\\/` block found there. Returns `''` when none.\n */\nfunction readMemberJSDoc(source: string, member: ts.PropertySignature): string {\n const ranges = ts.getLeadingCommentRanges(source, member.pos) ?? []\n // Walk in order, keeping only `/** */` blocks. Multiple back-to-back\n // JSDocs concatenate (matches msg-annotations.ts's existing behavior).\n const docs = ranges\n .filter((r) => r.kind === ts.SyntaxKind.MultiLineCommentTrivia)\n .map((r) => source.slice(r.pos, r.end))\n .filter((txt) => txt.startsWith('/**'))\n return docs.join('\\n')\n}\n\n/**\n * Match `@should(\"…\")` (and curly-quote variant) anywhere in the\n * JSDoc. Mirrors msg-annotations.ts's `@intent` parser — same grammar,\n * same tolerance for either ASCII or curly quotes.\n *\n * Returns the unescaped string content, or null when the tag is\n * absent or malformed.\n */\nfunction readShouldHint(comment: string): string | null {\n if (!comment) return null\n const match = comment.match(/@should\\s*\\(\\s*[\"“]([^\"”]*)[\"”]\\s*\\)/)\n return match?.[1] ?? null\n}\n"]}
@@ -1,4 +1,7 @@
1
1
  import ts from 'typescript';
2
+ import { extractMsgSchema, extractEffectSchema } from './msg-schema.js';
3
+ import { extractMsgAnnotations } from './msg-annotations.js';
4
+ import { extractStateSchema } from './state-schema.js';
2
5
  /**
3
6
  * Transform a source file containing @llui/dom imports.
4
7
  * Returns the transformed source or null if no transformation needed.
@@ -8,7 +11,50 @@ export interface TransformEdit {
8
11
  end: number;
9
12
  replacement: string;
10
13
  }
11
- export declare function transformLlui(source: string, _filename: string, devMode?: boolean, emitAgentMetadata?: boolean, mcpPort?: number | null, verbose?: boolean): {
14
+ /**
15
+ * Pre-resolved external type sources from the cross-file resolver.
16
+ * When the plugin's vite hook detects that `State` / `Msg` / `Effect`
17
+ * for a `component<...>()` call are imported (not declared in the
18
+ * current file), it walks the imports and re-exports to find the
19
+ * declaring file, then passes the source + local name here. Each
20
+ * extractor below uses the resolved source instead of falling back to
21
+ * the file-local search (which would miss the type entirely).
22
+ */
23
+ export interface ExternalTypeSources {
24
+ state?: {
25
+ source: string;
26
+ typeName: string;
27
+ };
28
+ msg?: {
29
+ source: string;
30
+ typeName: string;
31
+ };
32
+ effect?: {
33
+ source: string;
34
+ typeName: string;
35
+ };
36
+ }
37
+ /**
38
+ * Schemas already extracted by the plugin's async hook before invoking
39
+ * the sync transform. Used for cases the file-local sync extractors
40
+ * can't handle on their own:
41
+ * - The Msg/Effect/State alias lives in another file (cross-file
42
+ * resolution, see `cross-file-resolver.ts`).
43
+ * - The Msg/Effect alias is a *composition* — a union mixing inline
44
+ * `{ type: 'literal' }` members with TypeReferences pointing at
45
+ * other (often imported) Msg unions.
46
+ *
47
+ * When provided, transformLlui uses these instead of running its own
48
+ * file-local extractors. When omitted (the test path that constructs
49
+ * a single-source string), the file-local extractors run as before.
50
+ */
51
+ export interface PreExtractedSchemas {
52
+ msgSchema?: ReturnType<typeof extractMsgSchema>;
53
+ msgAnnotations?: ReturnType<typeof extractMsgAnnotations>;
54
+ stateSchema?: ReturnType<typeof extractStateSchema>;
55
+ effectSchema?: ReturnType<typeof extractEffectSchema>;
56
+ }
57
+ export declare function transformLlui(source: string, _filename: string, devMode?: boolean, emitAgentMetadata?: boolean, mcpPort?: number | null, verbose?: boolean, typeSources?: ExternalTypeSources, preExtracted?: PreExtractedSchemas): {
12
58
  output: string;
13
59
  edits: TransformEdit[];
14
60
  } | null;
@@ -1 +1 @@
1
- {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;AAiL3B;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,UAAQ,EACf,iBAAiB,UAAQ,EACzB,OAAO,GAAE,MAAM,GAAG,IAAW,EAC7B,OAAO,UAAQ,GACd;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,aAAa,EAAE,CAAA;CAAE,GAAG,IAAI,CA8TnD;AAwoID,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,wBAAwB,GAAG,IAAI,CAoGjC;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CA4B7D;AAID;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI3D;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,GAAG,MAAM,GAAG,IAAI,CAcrF;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CASzF;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC,GAChE,MAAM,CAYR"}
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;AAE3B,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EAIpB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,qBAAqB,EAA2B,MAAM,sBAAsB,CAAA;AACrF,OAAO,EAAE,kBAAkB,EAAkB,MAAM,mBAAmB,CAAA;AA6KtE;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;CACpB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5C,GAAG,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1C,MAAM,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;CAC9C;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAA;IAC/C,cAAc,CAAC,EAAE,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAA;IACzD,WAAW,CAAC,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAA;IACnD,YAAY,CAAC,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAA;CACtD;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,UAAQ,EACf,iBAAiB,UAAQ,EACzB,OAAO,GAAE,MAAM,GAAG,IAAW,EAC7B,OAAO,UAAQ,EACf,WAAW,CAAC,EAAE,mBAAmB,EACjC,YAAY,CAAC,EAAE,mBAAmB,GACjC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,aAAa,EAAE,CAAA;CAAE,GAAG,IAAI,CAqYnD;AAgqID,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,wBAAwB,GAAG,IAAI,CAoGjC;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CA4B7D;AAID;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI3D;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,GAAG,MAAM,GAAG,IAAI,CAcrF;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CASzF;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC,GAChE,MAAM,CAYR"}