@llui/vite-plugin 0.0.31 → 0.0.33
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/README.md +8 -9
- package/dist/binding-descriptors.d.ts +98 -16
- package/dist/binding-descriptors.d.ts.map +1 -1
- package/dist/binding-descriptors.js +319 -61
- package/dist/binding-descriptors.js.map +1 -1
- package/dist/cross-file-resolver.d.ts +109 -0
- package/dist/cross-file-resolver.d.ts.map +1 -0
- package/dist/cross-file-resolver.js +457 -0
- package/dist/cross-file-resolver.js.map +1 -0
- package/dist/index.d.ts +0 -24
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +241 -33
- package/dist/index.js.map +1 -1
- package/dist/msg-annotations.d.ts +47 -5
- package/dist/msg-annotations.d.ts.map +1 -1
- package/dist/msg-annotations.js +85 -9
- package/dist/msg-annotations.js.map +1 -1
- package/dist/msg-schema.d.ts +81 -5
- package/dist/msg-schema.d.ts.map +1 -1
- package/dist/msg-schema.js +201 -13
- package/dist/msg-schema.js.map +1 -1
- package/dist/transform.d.ts +47 -1
- package/dist/transform.d.ts.map +1 -1
- package/dist/transform.js +155 -61
- package/dist/transform.js.map +1 -1
- package/package.json +1 -1
- package/dist/diagnostics.d.ts +0 -14
- package/dist/diagnostics.js +0 -846
- package/dist/diagnostics.js.map +0 -1
package/dist/msg-schema.js.map
CHANGED
|
@@ -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"]}
|
package/dist/transform.d.ts
CHANGED
|
@@ -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
|
-
|
|
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;
|
package/dist/transform.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;
|
|
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"}
|
package/dist/transform.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
import { collectDeps } from './collect-deps.js';
|
|
3
|
-
import { extractMsgSchema, extractEffectSchema } from './msg-schema.js';
|
|
3
|
+
import { extractMsgSchema, extractEffectSchema, isRichField, } from './msg-schema.js';
|
|
4
4
|
import { extractMsgAnnotations } from './msg-annotations.js';
|
|
5
5
|
import { extractStateSchema } from './state-schema.js';
|
|
6
6
|
import { computeSchemaHash } from './schema-hash.js';
|
|
7
|
-
import {
|
|
7
|
+
import { tagDispatchHandlers, injectScopeVariantRegistrations } from './binding-descriptors.js';
|
|
8
8
|
import { compilerCache } from './compiler-cache.js';
|
|
9
9
|
function createMaskLiteral(f, mask) {
|
|
10
10
|
if (mask >= 0)
|
|
@@ -176,8 +176,8 @@ function resolveKey(key, kind) {
|
|
|
176
176
|
return 'class';
|
|
177
177
|
return key;
|
|
178
178
|
}
|
|
179
|
-
export function transformLlui(source, _filename, devMode = false, emitAgentMetadata = false, mcpPort = 5200, verbose = false) {
|
|
180
|
-
|
|
179
|
+
export function transformLlui(source, _filename, devMode = false, emitAgentMetadata = false, mcpPort = 5200, verbose = false, typeSources, preExtracted) {
|
|
180
|
+
let sourceFile = ts.createSourceFile('input.ts', source, ts.ScriptTarget.Latest, true);
|
|
181
181
|
// Find the @llui/dom import
|
|
182
182
|
const imp = findLluiImport(sourceFile);
|
|
183
183
|
if (!imp)
|
|
@@ -187,6 +187,38 @@ export function transformLlui(source, _filename, devMode = false, emitAgentMetad
|
|
|
187
187
|
const importedHelpers = getImportedHelpers(lluiImport);
|
|
188
188
|
if (importedHelpers.size === 0 && !hasReactiveAccessors(sourceFile))
|
|
189
189
|
return null;
|
|
190
|
+
// Connect-pattern pass: detects `*.connect(get, sendFn, …)` call
|
|
191
|
+
// sites and inserts a runtime `__registerScopeVariants([...])`
|
|
192
|
+
// adjacent to each, with the variants statically extracted from the
|
|
193
|
+
// sendFn's body. Handles the dispatch-translation case at the
|
|
194
|
+
// syntactic level — handler propagation via `tagSend` covers the
|
|
195
|
+
// rest. Runs FIRST so its `collectLocalFns` resolver still sees raw
|
|
196
|
+
// arrow initializers in const declarations (the universal tagger
|
|
197
|
+
// below replaces those initializers with `Object.assign(...)`
|
|
198
|
+
// wrappers).
|
|
199
|
+
//
|
|
200
|
+
// Universal handler-tagger pass: walks every arrow/function
|
|
201
|
+
// expression and wraps any whose body contains literal
|
|
202
|
+
// `send({type:'X'})` / `dispatch({type:'X'})` calls with
|
|
203
|
+
// `Object.assign(arrow, {__lluiVariants: ['X']})`. The runtime
|
|
204
|
+
// (`@llui/dom` `elements.ts` / `el-split.ts`) reads the tag from
|
|
205
|
+
// event-handler bindings only — so tags placed on functions in
|
|
206
|
+
// non-handler positions are runtime-inert. This deliberately covers
|
|
207
|
+
// three patterns at once:
|
|
208
|
+
// • Inline event handlers (`onClick: () => send(...)`)
|
|
209
|
+
// • Const-bound translators (`const sendMenu = (m) => dispatch(...)`)
|
|
210
|
+
// • Positional-arg helpers (`navButton(label, () => dispatch(...))`)
|
|
211
|
+
//
|
|
212
|
+
// Both passes gated on dev/agent-metadata so production bundles
|
|
213
|
+
// without agent integration don't pay the per-handler `Object.assign`
|
|
214
|
+
// cost.
|
|
215
|
+
let scopeRegistrationsInjected = false;
|
|
216
|
+
if (devMode || emitAgentMetadata) {
|
|
217
|
+
const injection = injectScopeVariantRegistrations(sourceFile, ts.factory);
|
|
218
|
+
sourceFile = injection.sf;
|
|
219
|
+
scopeRegistrationsInjected = injection.injected;
|
|
220
|
+
sourceFile = tagDispatchHandlers(sourceFile, ts.factory);
|
|
221
|
+
}
|
|
190
222
|
// Pass 2 pre-scan: collect all state access paths
|
|
191
223
|
// Only use precise masks in files that define a component() — the __dirty
|
|
192
224
|
// function is generated per-component, so bit assignments in other files
|
|
@@ -302,10 +334,27 @@ export function transformLlui(source, _filename, devMode = false, emitAgentMetad
|
|
|
302
334
|
usesApplyBinding = true;
|
|
303
335
|
// Extract schema data once — used both for devMode injections and the
|
|
304
336
|
// unconditional __schemaHash (spec §7.4: hash ships in prod too).
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
337
|
+
//
|
|
338
|
+
// Resolution priority for each schema:
|
|
339
|
+
// 1. preExtracted.* — used when the plugin's async hook has already
|
|
340
|
+
// done cross-file + composition extraction (the production path).
|
|
341
|
+
// 2. typeSources.* — file-local extraction against an alternate
|
|
342
|
+
// source file (legacy path; covers cross-file but not composition).
|
|
343
|
+
// 3. file-local — the test path: extract from `source` itself.
|
|
344
|
+
//
|
|
345
|
+
// When `preExtracted` is provided, treat it as authoritative even
|
|
346
|
+
// when the value is `null` (the resolver was run and found
|
|
347
|
+
// nothing) — falling back to local extraction would mask the
|
|
348
|
+
// resolver's "not extractable" verdict.
|
|
349
|
+
const msgSchema = preExtracted?.msgSchema !== undefined
|
|
350
|
+
? preExtracted.msgSchema
|
|
351
|
+
: extractMsgSchema(typeSources?.msg?.source ?? source, typeSources?.msg?.typeName ?? 'Msg');
|
|
352
|
+
const msgAnnotations = preExtracted?.msgAnnotations !== undefined
|
|
353
|
+
? preExtracted.msgAnnotations
|
|
354
|
+
: extractMsgAnnotations(typeSources?.msg?.source ?? source, typeSources?.msg?.typeName ?? 'Msg');
|
|
355
|
+
const stateSchema = preExtracted?.stateSchema !== undefined
|
|
356
|
+
? preExtracted.stateSchema
|
|
357
|
+
: extractStateSchema(typeSources?.state?.source ?? source, typeSources?.state?.typeName ?? 'State');
|
|
309
358
|
const shouldEmitAgentMetadata = devMode || emitAgentMetadata;
|
|
310
359
|
if (shouldEmitAgentMetadata) {
|
|
311
360
|
if (msgSchema) {
|
|
@@ -317,13 +366,18 @@ export function transformLlui(source, _filename, devMode = false, emitAgentMetad
|
|
|
317
366
|
if (stateSchema) {
|
|
318
367
|
result = injectStateSchema(result ?? node, stateSchema.fields, f);
|
|
319
368
|
}
|
|
320
|
-
const effectSchema =
|
|
369
|
+
const effectSchema = preExtracted?.effectSchema !== undefined
|
|
370
|
+
? preExtracted.effectSchema
|
|
371
|
+
: extractEffectSchema(typeSources?.effect?.source ?? source, typeSources?.effect?.typeName ?? 'Effect');
|
|
321
372
|
if (effectSchema) {
|
|
322
373
|
result = injectEffectSchema(result ?? node, effectSchema, f);
|
|
323
374
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
375
|
+
// Note: binding descriptors are no longer emitted on the
|
|
376
|
+
// component def. They're now collected at runtime by walking
|
|
377
|
+
// event-handler arrows that the `tagEventHandlerSends` pass
|
|
378
|
+
// wrapped with `__lluiVariants` metadata. See
|
|
379
|
+
// `binding-descriptors.ts` (compiler) and the matching
|
|
380
|
+
// `@llui/dom binding-descriptors.ts` (runtime registry).
|
|
327
381
|
// Populate compiler cache — preSource and msgMaskMap are known now;
|
|
328
382
|
// postSource is filled in after the full output is assembled.
|
|
329
383
|
const cachedComponentName = extractComponentNameFromConfig(node);
|
|
@@ -364,7 +418,7 @@ export function transformLlui(source, _filename, devMode = false, emitAgentMetad
|
|
|
364
418
|
// Pass 3: Clean up imports — use the old cleanupImports approach
|
|
365
419
|
// which operates on the transformed SourceFile safely
|
|
366
420
|
const safeToRemove = new Set([...compiledHelpers].filter((h) => !bailedHelpers.has(h)));
|
|
367
|
-
transformed = cleanupImports(transformed, lluiImport, importedHelpers, safeToRemove, usesElSplit, usesElTemplate, usesMemo, usesApplyBinding, usesCloneStaticTemplate, f);
|
|
421
|
+
transformed = cleanupImports(transformed, lluiImport, importedHelpers, safeToRemove, usesElSplit, usesElTemplate, usesMemo, usesApplyBinding, usesCloneStaticTemplate, scopeRegistrationsInjected, f);
|
|
368
422
|
if (edits.length === 0)
|
|
369
423
|
return null;
|
|
370
424
|
// Find component declarations for HMR and agent metadata
|
|
@@ -1034,7 +1088,10 @@ function tryInjectDirty(node, fieldBits, f) {
|
|
|
1034
1088
|
// it changes. Lets introspection tools decode runtime dirty masks to field names.
|
|
1035
1089
|
const legendProps = [];
|
|
1036
1090
|
for (const [field, bit] of topLevelBits) {
|
|
1037
|
-
|
|
1091
|
+
// Use string literal — state field names declared via string keys
|
|
1092
|
+
// (e.g. `{ "weird-key": ... }`) would otherwise emit as bare
|
|
1093
|
+
// identifiers and break the printed output.
|
|
1094
|
+
legendProps.push(f.createPropertyAssignment(f.createStringLiteral(field), createMaskLiteral(f, bit)));
|
|
1038
1095
|
}
|
|
1039
1096
|
const legendProp = f.createPropertyAssignment('__maskLegend', f.createObjectLiteralExpression(legendProps, false));
|
|
1040
1097
|
// Structural mask — used by both __update and __handlers
|
|
@@ -2001,13 +2058,14 @@ function buildAccess(f, root, parts) {
|
|
|
2001
2058
|
return expr;
|
|
2002
2059
|
}
|
|
2003
2060
|
// ── Pass 3: Import cleanup ───────────────────────────────────────
|
|
2004
|
-
function cleanupImports(sf, lluiImport, _helpers, compiled, usesElSplit, usesElTemplate, usesMemo, usesApplyBinding, usesCloneStaticTemplate, f) {
|
|
2061
|
+
function cleanupImports(sf, lluiImport, _helpers, compiled, usesElSplit, usesElTemplate, usesMemo, usesApplyBinding, usesCloneStaticTemplate, usesRegisterScopeVariants, f) {
|
|
2005
2062
|
if (compiled.size === 0 &&
|
|
2006
2063
|
!usesElTemplate &&
|
|
2007
2064
|
!usesElSplit &&
|
|
2008
2065
|
!usesMemo &&
|
|
2009
2066
|
!usesApplyBinding &&
|
|
2010
|
-
!usesCloneStaticTemplate
|
|
2067
|
+
!usesCloneStaticTemplate &&
|
|
2068
|
+
!usesRegisterScopeVariants)
|
|
2011
2069
|
return sf;
|
|
2012
2070
|
const clause = lluiImport.importClause;
|
|
2013
2071
|
if (!clause?.namedBindings || !ts.isNamedImports(clause.namedBindings))
|
|
@@ -2037,6 +2095,13 @@ function cleanupImports(sf, lluiImport, _helpers, compiled, usesElSplit, usesElT
|
|
|
2037
2095
|
remaining.push(f.createImportSpecifier(false, undefined, f.createIdentifier('__handleMsg')));
|
|
2038
2096
|
}
|
|
2039
2097
|
}
|
|
2098
|
+
// The connect-pattern injector (binding-descriptors.ts) emits
|
|
2099
|
+
// `__registerScopeVariants([...])` calls; ensure the runtime
|
|
2100
|
+
// helper is imported when at least one was inserted.
|
|
2101
|
+
const hasRegisterScopeVariants = clause.namedBindings.elements.some((s) => s.name.text === '__registerScopeVariants');
|
|
2102
|
+
if (!hasRegisterScopeVariants && usesRegisterScopeVariants) {
|
|
2103
|
+
remaining.push(f.createImportSpecifier(false, undefined, f.createIdentifier('__registerScopeVariants')));
|
|
2104
|
+
}
|
|
2040
2105
|
const newBindings = f.createNamedImports(remaining);
|
|
2041
2106
|
const newClause = f.createImportClause(false, undefined, newBindings);
|
|
2042
2107
|
const newImportDecl = f.createImportDeclaration(undefined, newClause, lluiImport.moduleSpecifier);
|
|
@@ -2103,7 +2168,7 @@ function stateTypeToLiteral(t, f) {
|
|
|
2103
2168
|
// object
|
|
2104
2169
|
const fieldProps = [];
|
|
2105
2170
|
for (const [k, v] of Object.entries(t.fields)) {
|
|
2106
|
-
fieldProps.push(f.createPropertyAssignment(k, stateTypeToLiteral(v, f)));
|
|
2171
|
+
fieldProps.push(f.createPropertyAssignment(f.createStringLiteral(k), stateTypeToLiteral(v, f)));
|
|
2107
2172
|
}
|
|
2108
2173
|
return f.createObjectLiteralExpression([
|
|
2109
2174
|
f.createPropertyAssignment('kind', f.createStringLiteral('object')),
|
|
@@ -2152,15 +2217,11 @@ function injectMsgSchema(node, schema, f) {
|
|
|
2152
2217
|
const variantProps = [];
|
|
2153
2218
|
for (const [variant, fields] of Object.entries(schema.variants)) {
|
|
2154
2219
|
const fieldProps = [];
|
|
2155
|
-
for (const [field,
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
fieldProps.push(f.createPropertyAssignment(field, f.createObjectLiteralExpression([
|
|
2161
|
-
f.createPropertyAssignment('enum', f.createArrayLiteralExpression(type.enum.map((v) => f.createStringLiteral(v)))),
|
|
2162
|
-
])));
|
|
2163
|
-
}
|
|
2220
|
+
for (const [field, descriptor] of Object.entries(fields)) {
|
|
2221
|
+
// Always wrap user-derived keys with createStringLiteral — bare
|
|
2222
|
+
// strings get printed as identifiers, which breaks fields named
|
|
2223
|
+
// with reserved words ('delete'), hyphens, or other non-id chars.
|
|
2224
|
+
fieldProps.push(f.createPropertyAssignment(f.createStringLiteral(field), buildFieldDescriptorExpr(descriptor, f)));
|
|
2164
2225
|
}
|
|
2165
2226
|
variantProps.push(f.createPropertyAssignment(f.createStringLiteral(variant), f.createObjectLiteralExpression(fieldProps)));
|
|
2166
2227
|
}
|
|
@@ -2175,6 +2236,60 @@ function injectMsgSchema(node, schema, f) {
|
|
|
2175
2236
|
...node.arguments.slice(1),
|
|
2176
2237
|
]);
|
|
2177
2238
|
}
|
|
2239
|
+
/**
|
|
2240
|
+
* Emit the AST for a single field descriptor. Bare forms (`'string'`,
|
|
2241
|
+
* `{enum: [...]}`) print as their compact form; rich descriptors emit
|
|
2242
|
+
* an object literal carrying `type`, `optional`, `priority`, and `hint`
|
|
2243
|
+
* properties as set.
|
|
2244
|
+
*/
|
|
2245
|
+
function buildFieldDescriptorExpr(descriptor, f) {
|
|
2246
|
+
if (typeof descriptor === 'string') {
|
|
2247
|
+
return f.createStringLiteral(descriptor);
|
|
2248
|
+
}
|
|
2249
|
+
if (isRichField(descriptor)) {
|
|
2250
|
+
const props = [];
|
|
2251
|
+
// `type` is the bare type; recurse to emit either a string or the
|
|
2252
|
+
// enum object.
|
|
2253
|
+
props.push(f.createPropertyAssignment('type', buildFieldDescriptorExpr(descriptor.type, f)));
|
|
2254
|
+
if (descriptor.optional) {
|
|
2255
|
+
props.push(f.createPropertyAssignment('optional', f.createTrue()));
|
|
2256
|
+
}
|
|
2257
|
+
if (descriptor.priority) {
|
|
2258
|
+
props.push(f.createPropertyAssignment('priority', f.createStringLiteral(descriptor.priority)));
|
|
2259
|
+
}
|
|
2260
|
+
if (descriptor.hint !== undefined) {
|
|
2261
|
+
props.push(f.createPropertyAssignment('hint', f.createStringLiteral(descriptor.hint)));
|
|
2262
|
+
}
|
|
2263
|
+
return f.createObjectLiteralExpression(props);
|
|
2264
|
+
}
|
|
2265
|
+
// The remaining cases are bare type-shape variants emitted by
|
|
2266
|
+
// `resolveFieldType`: enum, object, array. Discriminate by which
|
|
2267
|
+
// key is present so we never confuse an inline object literal
|
|
2268
|
+
// ({enum: [...]}) with a deeply-nested shape descriptor.
|
|
2269
|
+
if ('enum' in descriptor) {
|
|
2270
|
+
return f.createObjectLiteralExpression([
|
|
2271
|
+
f.createPropertyAssignment('enum', f.createArrayLiteralExpression(descriptor.enum.map((v) => f.createStringLiteral(v)))),
|
|
2272
|
+
]);
|
|
2273
|
+
}
|
|
2274
|
+
if ('kind' in descriptor && descriptor.kind === 'object') {
|
|
2275
|
+
// Nested object shape — recurse per field. Fields may themselves
|
|
2276
|
+
// be rich descriptors (optional, etc.), so route each through the
|
|
2277
|
+
// same builder.
|
|
2278
|
+
const shapeProps = [];
|
|
2279
|
+
for (const [k, v] of Object.entries(descriptor.shape)) {
|
|
2280
|
+
shapeProps.push(f.createPropertyAssignment(f.createStringLiteral(k), buildFieldDescriptorExpr(v, f)));
|
|
2281
|
+
}
|
|
2282
|
+
return f.createObjectLiteralExpression([
|
|
2283
|
+
f.createPropertyAssignment('kind', f.createStringLiteral('object')),
|
|
2284
|
+
f.createPropertyAssignment('shape', f.createObjectLiteralExpression(shapeProps)),
|
|
2285
|
+
]);
|
|
2286
|
+
}
|
|
2287
|
+
// Array — `{kind: 'array', element: <bare type>}`.
|
|
2288
|
+
return f.createObjectLiteralExpression([
|
|
2289
|
+
f.createPropertyAssignment('kind', f.createStringLiteral('array')),
|
|
2290
|
+
f.createPropertyAssignment('element', buildFieldDescriptorExpr(descriptor.element, f)),
|
|
2291
|
+
]);
|
|
2292
|
+
}
|
|
2178
2293
|
function hasNonDefaultAnnotation(a) {
|
|
2179
2294
|
for (const v of Object.values(a)) {
|
|
2180
2295
|
if (v.intent !== null)
|
|
@@ -2183,7 +2298,7 @@ function hasNonDefaultAnnotation(a) {
|
|
|
2183
2298
|
return true;
|
|
2184
2299
|
if (v.requiresConfirm)
|
|
2185
2300
|
return true;
|
|
2186
|
-
if (v.
|
|
2301
|
+
if (v.dispatchMode !== 'shared')
|
|
2187
2302
|
return true;
|
|
2188
2303
|
}
|
|
2189
2304
|
return false;
|
|
@@ -2191,13 +2306,18 @@ function hasNonDefaultAnnotation(a) {
|
|
|
2191
2306
|
function annotationsToObjectLiteral(a) {
|
|
2192
2307
|
const props = [];
|
|
2193
2308
|
for (const [variant, ann] of Object.entries(a)) {
|
|
2194
|
-
props.push(ts.factory.createPropertyAssignment(
|
|
2309
|
+
props.push(ts.factory.createPropertyAssignment(
|
|
2310
|
+
// Wrap with createStringLiteral — the printer treats bare strings
|
|
2311
|
+
// as identifiers, which produces invalid JS for discriminants
|
|
2312
|
+
// containing characters like '/' (e.g. 'Router/RouteChanged'),
|
|
2313
|
+
// reserved words ('delete'), or hyphens.
|
|
2314
|
+
ts.factory.createStringLiteral(variant), ts.factory.createObjectLiteralExpression([
|
|
2195
2315
|
ts.factory.createPropertyAssignment('intent', ann.intent === null
|
|
2196
2316
|
? ts.factory.createNull()
|
|
2197
2317
|
: ts.factory.createStringLiteral(ann.intent)),
|
|
2198
2318
|
ts.factory.createPropertyAssignment('alwaysAffordable', ann.alwaysAffordable ? ts.factory.createTrue() : ts.factory.createFalse()),
|
|
2199
2319
|
ts.factory.createPropertyAssignment('requiresConfirm', ann.requiresConfirm ? ts.factory.createTrue() : ts.factory.createFalse()),
|
|
2200
|
-
ts.factory.createPropertyAssignment('
|
|
2320
|
+
ts.factory.createPropertyAssignment('dispatchMode', ts.factory.createStringLiteral(ann.dispatchMode)),
|
|
2201
2321
|
], true)));
|
|
2202
2322
|
}
|
|
2203
2323
|
return ts.factory.createObjectLiteralExpression(props, true);
|
|
@@ -2254,15 +2374,12 @@ function injectEffectSchema(node, schema, f) {
|
|
|
2254
2374
|
const variantProps = [];
|
|
2255
2375
|
for (const [variant, fields] of Object.entries(schema.variants)) {
|
|
2256
2376
|
const fieldProps = [];
|
|
2257
|
-
for (const [field,
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
f.createPropertyAssignment('enum', f.createArrayLiteralExpression(type.enum.map((v) => f.createStringLiteral(v)))),
|
|
2264
|
-
])));
|
|
2265
|
-
}
|
|
2377
|
+
for (const [field, descriptor] of Object.entries(fields)) {
|
|
2378
|
+
// Effects share the same descriptor pipeline as messages — they
|
|
2379
|
+
// can carry @should/optional annotations too, even though typical
|
|
2380
|
+
// Effect unions don't need them. Reusing the helper keeps both
|
|
2381
|
+
// schemas's wire formats consistent.
|
|
2382
|
+
fieldProps.push(f.createPropertyAssignment(f.createStringLiteral(field), buildFieldDescriptorExpr(descriptor, f)));
|
|
2266
2383
|
}
|
|
2267
2384
|
variantProps.push(f.createPropertyAssignment(f.createStringLiteral(variant), f.createObjectLiteralExpression(fieldProps)));
|
|
2268
2385
|
}
|
|
@@ -2277,29 +2394,6 @@ function injectEffectSchema(node, schema, f) {
|
|
|
2277
2394
|
...node.arguments.slice(1),
|
|
2278
2395
|
]);
|
|
2279
2396
|
}
|
|
2280
|
-
function injectBindingDescriptors(node, descs, f) {
|
|
2281
|
-
const configArg = node.arguments[0];
|
|
2282
|
-
if (!configArg || !ts.isObjectLiteralExpression(configArg))
|
|
2283
|
-
return node;
|
|
2284
|
-
// Don't inject if already present
|
|
2285
|
-
for (const prop of configArg.properties) {
|
|
2286
|
-
if (ts.isPropertyAssignment(prop) &&
|
|
2287
|
-
ts.isIdentifier(prop.name) &&
|
|
2288
|
-
prop.name.text === '__bindingDescriptors') {
|
|
2289
|
-
return node;
|
|
2290
|
-
}
|
|
2291
|
-
}
|
|
2292
|
-
const descsProp = f.createPropertyAssignment('__bindingDescriptors', bindingDescriptorsToArrayLiteral(descs));
|
|
2293
|
-
const newConfig = f.createObjectLiteralExpression([...configArg.properties, descsProp], true);
|
|
2294
|
-
return f.createCallExpression(node.expression, node.typeArguments, [
|
|
2295
|
-
newConfig,
|
|
2296
|
-
...node.arguments.slice(1),
|
|
2297
|
-
]);
|
|
2298
|
-
}
|
|
2299
|
-
function bindingDescriptorsToArrayLiteral(descs) {
|
|
2300
|
-
const entries = descs.map((d) => ts.factory.createObjectLiteralExpression([ts.factory.createPropertyAssignment('variant', ts.factory.createStringLiteral(d.variant))], false));
|
|
2301
|
-
return ts.factory.createArrayLiteralExpression(entries, true);
|
|
2302
|
-
}
|
|
2303
2397
|
// ── Per-item accessor detection ──────────────────────────────────
|
|
2304
2398
|
// ── Item selector deduplication ──────────────────────────────────
|
|
2305
2399
|
/**
|