@llui/vite-plugin 0.0.28 → 0.0.29

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.
@@ -0,0 +1,91 @@
1
+ import ts from 'typescript';
2
+ const DEFAULT = {
3
+ intent: null,
4
+ alwaysAffordable: false,
5
+ requiresConfirm: false,
6
+ humanOnly: false,
7
+ };
8
+ /**
9
+ * Walk a Msg-like discriminated-union type alias and extract JSDoc
10
+ * annotations attached to each union member. Returns null if no
11
+ * recognizable union is found so callers can skip emission cleanly.
12
+ *
13
+ * Expected JSDoc grammar (order-independent):
14
+ * @intent("human readable")
15
+ * @alwaysAffordable
16
+ * @requiresConfirm
17
+ * @humanOnly
18
+ *
19
+ * Unknown tags are ignored; malformed @intent (no quoted string) is
20
+ * treated as "no intent". The four flags are booleans; any occurrence
21
+ * of the tag sets it true.
22
+ */
23
+ export function extractMsgAnnotations(source) {
24
+ const sf = ts.createSourceFile('msg.ts', source, ts.ScriptTarget.Latest, true);
25
+ const aliases = [];
26
+ sf.forEachChild((n) => {
27
+ if (ts.isTypeAliasDeclaration(n))
28
+ aliases.push(n);
29
+ });
30
+ const named = aliases.find((a) => a.name.text === 'Msg');
31
+ const alias = named ?? aliases.find((a) => ts.isUnionTypeNode(a.type));
32
+ if (!alias || !ts.isUnionTypeNode(alias.type))
33
+ return null;
34
+ const result = {};
35
+ const types = alias.type.types;
36
+ for (let i = 0; i < types.length; i++) {
37
+ const member = types[i];
38
+ if (member === undefined || !ts.isTypeLiteralNode(member))
39
+ continue;
40
+ const variant = readDiscriminantLiteral(member);
41
+ if (!variant)
42
+ continue;
43
+ // Leading JSDoc for union member i is scanned from the end of the
44
+ // previous element (or union.pos for the first member), because
45
+ // TypeScript's parser places comment ranges relative to the token
46
+ // that follows them — and the | bar is not part of the TypeLiteralNode.
47
+ const prev = types[i - 1];
48
+ const scanPos = i === 0 || prev === undefined ? alias.type.pos : prev.end;
49
+ const comment = readLeadingJSDoc(source, scanPos);
50
+ result[variant] = parseAnnotations(comment);
51
+ }
52
+ return Object.keys(result).length === 0 ? null : result;
53
+ }
54
+ function readDiscriminantLiteral(lit) {
55
+ for (const m of lit.members) {
56
+ if (!ts.isPropertySignature(m))
57
+ continue;
58
+ if (!m.name || !ts.isIdentifier(m.name) || m.name.text !== 'type')
59
+ continue;
60
+ if (!m.type || !ts.isLiteralTypeNode(m.type))
61
+ continue;
62
+ const literal = m.type.literal;
63
+ if (ts.isStringLiteral(literal))
64
+ return literal.text;
65
+ }
66
+ return null;
67
+ }
68
+ function readLeadingJSDoc(source, scanPos) {
69
+ const ranges = ts.getLeadingCommentRanges(source, scanPos) ?? [];
70
+ const docs = ranges
71
+ .filter((r) => r.kind === ts.SyntaxKind.MultiLineCommentTrivia)
72
+ .map((r) => source.slice(r.pos, r.end))
73
+ .filter((txt) => txt.startsWith('/**'));
74
+ return docs.join('\n');
75
+ }
76
+ function parseAnnotations(comment) {
77
+ if (!comment)
78
+ return { ...DEFAULT };
79
+ const intent = readIntent(comment);
80
+ return {
81
+ intent,
82
+ alwaysAffordable: /@alwaysAffordable\b/.test(comment),
83
+ requiresConfirm: /@requiresConfirm\b/.test(comment),
84
+ humanOnly: /@humanOnly\b/.test(comment),
85
+ };
86
+ }
87
+ function readIntent(comment) {
88
+ const match = comment.match(/@intent\s*\(\s*["\u201c]([^"\u201d]*)["\u201d]\s*\)/);
89
+ return match?.[1] ?? null;
90
+ }
91
+ //# sourceMappingURL=msg-annotations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"msg-annotations.js","sourceRoot":"","sources":["../src/msg-annotations.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript'\n\nexport type MessageAnnotations = {\n intent: string | null\n alwaysAffordable: boolean\n requiresConfirm: boolean\n humanOnly: boolean\n}\n\nconst DEFAULT: MessageAnnotations = {\n intent: null,\n alwaysAffordable: false,\n requiresConfirm: false,\n humanOnly: false,\n}\n\n/**\n * Walk a Msg-like discriminated-union type alias and extract JSDoc\n * annotations attached to each union member. Returns null if no\n * recognizable union is found so callers can skip emission cleanly.\n *\n * Expected JSDoc grammar (order-independent):\n * @intent(\"human readable\")\n * @alwaysAffordable\n * @requiresConfirm\n * @humanOnly\n *\n * Unknown tags are ignored; malformed @intent (no quoted string) is\n * treated as \"no intent\". The four flags are booleans; any occurrence\n * of the tag sets it true.\n */\nexport function extractMsgAnnotations(source: string): Record<string, MessageAnnotations> | null {\n const sf = ts.createSourceFile('msg.ts', source, ts.ScriptTarget.Latest, true)\n const aliases: ts.TypeAliasDeclaration[] = []\n sf.forEachChild((n) => {\n if (ts.isTypeAliasDeclaration(n)) aliases.push(n)\n })\n const named = aliases.find((a) => a.name.text === 'Msg')\n const alias = named ?? aliases.find((a) => ts.isUnionTypeNode(a.type))\n if (!alias || !ts.isUnionTypeNode(alias.type)) return null\n\n const result: Record<string, MessageAnnotations> = {}\n const types = alias.type.types\n for (let i = 0; i < types.length; i++) {\n const member = types[i]\n if (member === undefined || !ts.isTypeLiteralNode(member)) continue\n const variant = readDiscriminantLiteral(member)\n if (!variant) continue\n // Leading JSDoc for union member i is scanned from the end of the\n // previous element (or union.pos for the first member), because\n // TypeScript's parser places comment ranges relative to the token\n // that follows them — and the | bar is not part of the TypeLiteralNode.\n const prev = types[i - 1]\n const scanPos = i === 0 || prev === undefined ? alias.type.pos : prev.end\n const comment = readLeadingJSDoc(source, scanPos)\n result[variant] = parseAnnotations(comment)\n }\n return Object.keys(result).length === 0 ? null : result\n}\n\nfunction readDiscriminantLiteral(lit: ts.TypeLiteralNode): string | null {\n for (const m of lit.members) {\n if (!ts.isPropertySignature(m)) continue\n if (!m.name || !ts.isIdentifier(m.name) || m.name.text !== 'type') continue\n if (!m.type || !ts.isLiteralTypeNode(m.type)) continue\n const literal = m.type.literal\n if (ts.isStringLiteral(literal)) return literal.text\n }\n return null\n}\n\nfunction readLeadingJSDoc(source: string, scanPos: number): string {\n const ranges = ts.getLeadingCommentRanges(source, scanPos) ?? []\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\nfunction parseAnnotations(comment: string): MessageAnnotations {\n if (!comment) return { ...DEFAULT }\n const intent = readIntent(comment)\n return {\n intent,\n alwaysAffordable: /@alwaysAffordable\\b/.test(comment),\n requiresConfirm: /@requiresConfirm\\b/.test(comment),\n humanOnly: /@humanOnly\\b/.test(comment),\n }\n}\n\nfunction readIntent(comment: string): string | null {\n const match = comment.match(/@intent\\s*\\(\\s*[\"\\u201c]([^\"\\u201d]*)[\"\\u201d]\\s*\\)/)\n return match?.[1] ?? null\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import type { MessageAnnotations } from './msg-annotations.js';
2
+ export type SchemaHashInput = {
3
+ msgSchema: unknown;
4
+ stateSchema: unknown;
5
+ msgAnnotations: Record<string, MessageAnnotations> | null | undefined;
6
+ };
7
+ /**
8
+ * Stable hex SHA-256 (first 32 chars) over a normalized JSON serialization
9
+ * of msgSchema + stateSchema + msgAnnotations. Object key order is
10
+ * normalized so equivalent inputs always produce equal hashes.
11
+ *
12
+ * Used by the runtime to detect when the browser-to-server `hello` frame
13
+ * needs to re-send its schema payload (dev hot-reload).
14
+ */
15
+ export declare function computeSchemaHash(input: SchemaHashInput): string;
16
+ //# sourceMappingURL=schema-hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-hash.d.ts","sourceRoot":"","sources":["../src/schema-hash.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAE9D,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,OAAO,CAAA;IAClB,WAAW,EAAE,OAAO,CAAA;IACpB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,GAAG,SAAS,CAAA;CACtE,CAAA;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAQhE"}
@@ -0,0 +1,31 @@
1
+ import { createHash } from 'node:crypto';
2
+ /**
3
+ * Stable hex SHA-256 (first 32 chars) over a normalized JSON serialization
4
+ * of msgSchema + stateSchema + msgAnnotations. Object key order is
5
+ * normalized so equivalent inputs always produce equal hashes.
6
+ *
7
+ * Used by the runtime to detect when the browser-to-server `hello` frame
8
+ * needs to re-send its schema payload (dev hot-reload).
9
+ */
10
+ export function computeSchemaHash(input) {
11
+ const normalized = {
12
+ msgSchema: sortDeep(input.msgSchema),
13
+ stateSchema: sortDeep(input.stateSchema),
14
+ msgAnnotations: sortDeep(input.msgAnnotations ?? null),
15
+ };
16
+ const json = JSON.stringify(normalized);
17
+ return createHash('sha256').update(json).digest('hex').slice(0, 32);
18
+ }
19
+ function sortDeep(value) {
20
+ if (value === null || typeof value !== 'object')
21
+ return value;
22
+ if (Array.isArray(value))
23
+ return value.map(sortDeep);
24
+ const obj = value;
25
+ const out = {};
26
+ for (const k of Object.keys(obj).sort()) {
27
+ out[k] = sortDeep(obj[k]);
28
+ }
29
+ return out;
30
+ }
31
+ //# sourceMappingURL=schema-hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-hash.js","sourceRoot":"","sources":["../src/schema-hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AASxC;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAsB;IACtD,MAAM,UAAU,GAAG;QACjB,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC;QACpC,WAAW,EAAE,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC;QACxC,cAAc,EAAE,QAAQ,CAAC,KAAK,CAAC,cAAc,IAAI,IAAI,CAAC;KACvD,CAAA;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA;IACvC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AACrE,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAC7D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IACpD,MAAM,GAAG,GAAG,KAAgC,CAAA;IAC5C,MAAM,GAAG,GAA4B,EAAE,CAAA;IACvC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QACxC,GAAG,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IAC3B,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC","sourcesContent":["import { createHash } from 'node:crypto'\nimport type { MessageAnnotations } from './msg-annotations.js'\n\nexport type SchemaHashInput = {\n msgSchema: unknown\n stateSchema: unknown\n msgAnnotations: Record<string, MessageAnnotations> | null | undefined\n}\n\n/**\n * Stable hex SHA-256 (first 32 chars) over a normalized JSON serialization\n * of msgSchema + stateSchema + msgAnnotations. Object key order is\n * normalized so equivalent inputs always produce equal hashes.\n *\n * Used by the runtime to detect when the browser-to-server `hello` frame\n * needs to re-send its schema payload (dev hot-reload).\n */\nexport function computeSchemaHash(input: SchemaHashInput): string {\n const normalized = {\n msgSchema: sortDeep(input.msgSchema),\n stateSchema: sortDeep(input.stateSchema),\n msgAnnotations: sortDeep(input.msgAnnotations ?? null),\n }\n const json = JSON.stringify(normalized)\n return createHash('sha256').update(json).digest('hex').slice(0, 32)\n}\n\nfunction sortDeep(value: unknown): unknown {\n if (value === null || typeof value !== 'object') return value\n if (Array.isArray(value)) return value.map(sortDeep)\n const obj = value as Record<string, unknown>\n const out: Record<string, unknown> = {}\n for (const k of Object.keys(obj).sort()) {\n out[k] = sortDeep(obj[k])\n }\n return out\n}\n"]}
@@ -7,7 +7,7 @@ export interface TransformEdit {
7
7
  end: number;
8
8
  replacement: string;
9
9
  }
10
- export declare function transformLlui(source: string, _filename: string, devMode?: boolean, mcpPort?: number | null, verbose?: boolean): {
10
+ export declare function transformLlui(source: string, _filename: string, devMode?: boolean, emitAgentMetadata?: boolean, mcpPort?: number | null, verbose?: boolean): {
11
11
  output: string;
12
12
  edits: TransformEdit[];
13
13
  } | null;
@@ -1 +1 @@
1
- {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AA6KA;;;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,OAAO,GAAE,MAAM,GAAG,IAAW,EAC7B,OAAO,UAAQ,GACd;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,aAAa,EAAE,CAAA;CAAE,GAAG,IAAI,CAuQnD;AA++HD,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"}
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AAgLA;;;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,CAiSnD;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"}
package/dist/transform.js CHANGED
@@ -1,7 +1,10 @@
1
1
  import ts from 'typescript';
2
2
  import { collectDeps } from './collect-deps.js';
3
3
  import { extractMsgSchema, extractEffectSchema } from './msg-schema.js';
4
+ import { extractMsgAnnotations } from './msg-annotations.js';
4
5
  import { extractStateSchema } from './state-schema.js';
6
+ import { computeSchemaHash } from './schema-hash.js';
7
+ import { extractBindingDescriptors } from './binding-descriptors.js';
5
8
  function createMaskLiteral(f, mask) {
6
9
  if (mask >= 0)
7
10
  return f.createNumericLiteral(mask);
@@ -172,7 +175,7 @@ function resolveKey(key, kind) {
172
175
  return 'class';
173
176
  return key;
174
177
  }
175
- export function transformLlui(source, _filename, devMode = false, mcpPort = 5200, verbose = false) {
178
+ export function transformLlui(source, _filename, devMode = false, emitAgentMetadata = false, mcpPort = 5200, verbose = false) {
176
179
  const sourceFile = ts.createSourceFile('input.ts', source, ts.ScriptTarget.Latest, true);
177
180
  // Find the @llui/dom import
178
181
  const imp = findLluiImport(sourceFile);
@@ -296,12 +299,20 @@ export function transformLlui(source, _filename, devMode = false, mcpPort = 5200
296
299
  let result = tryInjectDirty(node, fieldBits, f);
297
300
  if (result)
298
301
  usesApplyBinding = true;
299
- if (devMode) {
300
- const schema = extractMsgSchema(source);
301
- if (schema) {
302
- result = injectMsgSchema(result ?? node, schema, f);
302
+ // Extract schema data once — used both for devMode injections and the
303
+ // unconditional __schemaHash (spec §7.4: hash ships in prod too).
304
+ const msgSchema = extractMsgSchema(source);
305
+ const msgAnnotations = extractMsgAnnotations(source);
306
+ const stateSchema = extractStateSchema(source);
307
+ const bindingDescriptors = extractBindingDescriptors(source);
308
+ const shouldEmitAgentMetadata = devMode || emitAgentMetadata;
309
+ if (shouldEmitAgentMetadata) {
310
+ if (msgSchema) {
311
+ result = injectMsgSchema(result ?? node, msgSchema, f);
312
+ }
313
+ if (msgAnnotations && hasNonDefaultAnnotation(msgAnnotations)) {
314
+ result = injectMsgAnnotations(result ?? node, msgAnnotations, f);
303
315
  }
304
- const stateSchema = extractStateSchema(source);
305
316
  if (stateSchema) {
306
317
  result = injectStateSchema(result ?? node, stateSchema.fields, f);
307
318
  }
@@ -309,8 +320,21 @@ export function transformLlui(source, _filename, devMode = false, mcpPort = 5200
309
320
  if (effectSchema) {
310
321
  result = injectEffectSchema(result ?? node, effectSchema, f);
311
322
  }
323
+ if (bindingDescriptors.length > 0) {
324
+ result = injectBindingDescriptors(result ?? node, bindingDescriptors, f);
325
+ }
326
+ }
327
+ if (devMode) {
312
328
  result = injectComponentMeta(result ?? node, node, sourceFile, _filename, f);
313
329
  }
330
+ // __schemaHash is always emitted (not dev-gated) — runtime uses it to
331
+ // gate hello-frame re-sends during hot-reload in prod builds too.
332
+ const schemaHash = computeSchemaHash({
333
+ msgSchema: msgSchema ?? null,
334
+ stateSchema: stateSchema ?? null,
335
+ msgAnnotations,
336
+ });
337
+ result = injectSchemaHash(result ?? node, schemaHash, f);
314
338
  if (result) {
315
339
  if (hasPos)
316
340
  edits.push({ start: origStart, end: origEnd, replacement: '' });
@@ -2124,6 +2148,71 @@ function injectMsgSchema(node, schema, f) {
2124
2148
  ...node.arguments.slice(1),
2125
2149
  ]);
2126
2150
  }
2151
+ function hasNonDefaultAnnotation(a) {
2152
+ for (const v of Object.values(a)) {
2153
+ if (v.intent !== null)
2154
+ return true;
2155
+ if (v.alwaysAffordable)
2156
+ return true;
2157
+ if (v.requiresConfirm)
2158
+ return true;
2159
+ if (v.humanOnly)
2160
+ return true;
2161
+ }
2162
+ return false;
2163
+ }
2164
+ function annotationsToObjectLiteral(a) {
2165
+ const props = [];
2166
+ for (const [variant, ann] of Object.entries(a)) {
2167
+ props.push(ts.factory.createPropertyAssignment(variant, ts.factory.createObjectLiteralExpression([
2168
+ ts.factory.createPropertyAssignment('intent', ann.intent === null
2169
+ ? ts.factory.createNull()
2170
+ : ts.factory.createStringLiteral(ann.intent)),
2171
+ ts.factory.createPropertyAssignment('alwaysAffordable', ann.alwaysAffordable ? ts.factory.createTrue() : ts.factory.createFalse()),
2172
+ ts.factory.createPropertyAssignment('requiresConfirm', ann.requiresConfirm ? ts.factory.createTrue() : ts.factory.createFalse()),
2173
+ ts.factory.createPropertyAssignment('humanOnly', ann.humanOnly ? ts.factory.createTrue() : ts.factory.createFalse()),
2174
+ ], true)));
2175
+ }
2176
+ return ts.factory.createObjectLiteralExpression(props, true);
2177
+ }
2178
+ function injectMsgAnnotations(node, annotations, f) {
2179
+ const configArg = node.arguments[0];
2180
+ if (!configArg || !ts.isObjectLiteralExpression(configArg))
2181
+ return node;
2182
+ // Don't inject if already present
2183
+ for (const prop of configArg.properties) {
2184
+ if (ts.isPropertyAssignment(prop) &&
2185
+ ts.isIdentifier(prop.name) &&
2186
+ prop.name.text === '__msgAnnotations') {
2187
+ return node;
2188
+ }
2189
+ }
2190
+ const annotationsProp = f.createPropertyAssignment('__msgAnnotations', annotationsToObjectLiteral(annotations));
2191
+ const newConfig = f.createObjectLiteralExpression([...configArg.properties, annotationsProp], true);
2192
+ return f.createCallExpression(node.expression, node.typeArguments, [
2193
+ newConfig,
2194
+ ...node.arguments.slice(1),
2195
+ ]);
2196
+ }
2197
+ function injectSchemaHash(node, hash, f) {
2198
+ const configArg = node.arguments[0];
2199
+ if (!configArg || !ts.isObjectLiteralExpression(configArg))
2200
+ return node;
2201
+ // Don't inject if already present
2202
+ for (const prop of configArg.properties) {
2203
+ if (ts.isPropertyAssignment(prop) &&
2204
+ ts.isIdentifier(prop.name) &&
2205
+ prop.name.text === '__schemaHash') {
2206
+ return node;
2207
+ }
2208
+ }
2209
+ const hashProp = f.createPropertyAssignment('__schemaHash', f.createStringLiteral(hash));
2210
+ const newConfig = f.createObjectLiteralExpression([...configArg.properties, hashProp], true);
2211
+ return f.createCallExpression(node.expression, node.typeArguments, [
2212
+ newConfig,
2213
+ ...node.arguments.slice(1),
2214
+ ]);
2215
+ }
2127
2216
  function injectEffectSchema(node, schema, f) {
2128
2217
  const configArg = node.arguments[0];
2129
2218
  if (!configArg || !ts.isObjectLiteralExpression(configArg))
@@ -2161,6 +2250,29 @@ function injectEffectSchema(node, schema, f) {
2161
2250
  ...node.arguments.slice(1),
2162
2251
  ]);
2163
2252
  }
2253
+ function injectBindingDescriptors(node, descs, f) {
2254
+ const configArg = node.arguments[0];
2255
+ if (!configArg || !ts.isObjectLiteralExpression(configArg))
2256
+ return node;
2257
+ // Don't inject if already present
2258
+ for (const prop of configArg.properties) {
2259
+ if (ts.isPropertyAssignment(prop) &&
2260
+ ts.isIdentifier(prop.name) &&
2261
+ prop.name.text === '__bindingDescriptors') {
2262
+ return node;
2263
+ }
2264
+ }
2265
+ const descsProp = f.createPropertyAssignment('__bindingDescriptors', bindingDescriptorsToArrayLiteral(descs));
2266
+ const newConfig = f.createObjectLiteralExpression([...configArg.properties, descsProp], true);
2267
+ return f.createCallExpression(node.expression, node.typeArguments, [
2268
+ newConfig,
2269
+ ...node.arguments.slice(1),
2270
+ ]);
2271
+ }
2272
+ function bindingDescriptorsToArrayLiteral(descs) {
2273
+ const entries = descs.map((d) => ts.factory.createObjectLiteralExpression([ts.factory.createPropertyAssignment('variant', ts.factory.createStringLiteral(d.variant))], false));
2274
+ return ts.factory.createArrayLiteralExpression(entries, true);
2275
+ }
2164
2276
  // ── Per-item accessor detection ──────────────────────────────────
2165
2277
  // ── Item selector deduplication ──────────────────────────────────
2166
2278
  /**