@jay-framework/compiler-jay-html 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { JayType, WithValidations, PluginComponentResolution, JayEnumType, RefsTree, CompilerSourceFile, SourceFileFormat, JayImportLink, MainRuntimeModes, GenerateTarget, Imports } from '@jay-framework/compiler-shared';
1
+ import { JayType, WithValidations, PluginComponentResolution, PluginManifest, JayEnumType, RefsTree, CompilerSourceFile, SourceFileFormat, JayImportLink, MainRuntimeModes, GenerateTarget, Imports, JayObjectType } from '@jay-framework/compiler-shared';
2
2
  import { HTMLElement } from 'node-html-parser';
3
3
  import { ResolveTsConfigOptions } from '@jay-framework/compiler-analyze-exported-types';
4
4
  import { Coordinate } from '@jay-framework/runtime';
@@ -89,6 +89,7 @@ interface JayImportResolver {
89
89
  contractPath: string;
90
90
  metadata?: Record<string, unknown>;
91
91
  }>;
92
+ resolvePluginManifest(pluginName: string, projectRoot: string): WithValidations<PluginManifest>;
92
93
  }
93
94
  declare const JAY_IMPORT_RESOLVER: JayImportResolver;
94
95
 
@@ -211,6 +212,58 @@ declare function renderRefsType(refs: RefsTree, refsType: string, generateTarget
211
212
 
212
213
  declare function generateTypes(types: JayType): string;
213
214
 
215
+ /**
216
+ * Parser for .jay-action files (compact jay-type notation).
217
+ *
218
+ * Produces JayType from the compact format used by jay-html data scripts:
219
+ * - Primitives: string, number, boolean → JayAtomicType
220
+ * - Enums: enum(a | b | c) → JayEnumType
221
+ * - Arrays: YAML list / type[] shorthand → JayArrayType
222
+ * - Objects: nested YAML maps → JayObjectType (with optionalProps tracking)
223
+ * - Optional: ? suffix on property keys → tracked in JayObjectType.optionalProps
224
+ * - Contract imports: import: block → JayImportedType
225
+ */
226
+
227
+ /**
228
+ * Parsed action definition from a .jay-action file.
229
+ * Uses JayType for the type representation.
230
+ */
231
+ interface ActionDefinition {
232
+ name: string;
233
+ description: string;
234
+ /** Import aliases: alias → contract subpath (e.g., productCard → product-card.jay-contract) */
235
+ imports: Record<string, string>;
236
+ /** Input schema as a JayObjectType. Optional props tracked in optionalProps. */
237
+ inputType: JayObjectType;
238
+ /** Output type (any JayType shape, or undefined if no output). */
239
+ outputType?: JayType;
240
+ }
241
+ /**
242
+ * Parses a .jay-action YAML string into an ActionDefinition using JayType.
243
+ */
244
+ declare function parseAction(actionYaml: string, fileName: string): WithValidations<ActionDefinition>;
245
+
246
+ /**
247
+ * Compiler for .jay-action files → .jay-action.d.ts
248
+ *
249
+ * Generates TypeScript Input/Output interfaces from JayType trees,
250
+ * including import statements for contract references (JayImportedType).
251
+ */
252
+
253
+ interface ContractImportInfo {
254
+ importPath: string;
255
+ viewStateName: string;
256
+ }
257
+ type ContractResolver = (contractSubpath: string) => ContractImportInfo | null;
258
+ /**
259
+ * Default contract resolver that derives names from the subpath.
260
+ */
261
+ declare function defaultContractResolver(contractSubpath: string): ContractImportInfo;
262
+ /**
263
+ * Compiles an ActionDefinition (with JayType) into a TypeScript .d.ts string.
264
+ */
265
+ declare function compileAction(actionWithValidations: WithValidations<ActionDefinition>, contractResolver?: ContractResolver): WithValidations<string>;
266
+
214
267
  /**
215
268
  * Headless contract with its key (used for property path prefix)
216
269
  */
@@ -302,8 +355,26 @@ interface HeadlessInstanceResolvedData {
302
355
  * a coordinateCounters map must be provided to auto-assign an index.
303
356
  */
304
357
  declare function buildInstanceCoordinateKey(element: HTMLElement, contractName: string, coordinateCounters?: Map<string, number>): string;
358
+ /**
359
+ * A headless instance found inside a preserved forEach (fast phase).
360
+ * These instances have unresolved prop bindings and need per-item rendering
361
+ * at fast phase time. They are only allowed if the component has no slow phase.
362
+ */
363
+ interface ForEachHeadlessInstance {
364
+ contractName: string;
365
+ /** The forEach attribute path on the ancestor element (e.g., "allProducts.items") */
366
+ forEachPath: string;
367
+ /** TrackBy key for the forEach (e.g., "_id") */
368
+ trackBy: string;
369
+ /** Prop bindings referencing forEach item fields (e.g., { productId: "{_id}" }) */
370
+ propBindings: Record<string, string>;
371
+ /** Coordinate suffix after trackBy values (e.g., "product-card:0") */
372
+ coordinateSuffix: string;
373
+ }
305
374
  interface HeadlessInstanceDiscoveryResult {
306
375
  instances: DiscoveredHeadlessInstance[];
376
+ /** Headless instances inside preserved forEach elements (need server-time validation) */
377
+ forEachInstances: ForEachHeadlessInstance[];
307
378
  /** HTML with auto-generated ref attributes embedded on <jay:xxx> elements */
308
379
  preRenderedJayHtml: string;
309
380
  }
@@ -314,4 +385,4 @@ declare function resolveHeadlessInstances(preRenderedJayHtml: string, instanceDa
314
385
  */
315
386
  declare function hasSlowPhaseProperties(contract: Contract | undefined): boolean;
316
387
 
317
- export { type Contract, type ContractParam, type ContractProp, type ContractTag, ContractTagType, type DiscoveredHeadlessInstance, type EnumToImport, type HeadlessContractInfo, type HeadlessInstanceDiscoveryResult, type HeadlessInstanceResolvedData, JAY_IMPORT_RESOLVER, type JayContractImportLink, type JayHtmlSourceFile, type JayImportResolver, type PhaseViewStates, type RenderingPhase, type SlowRenderInput, type SlowRenderOutput, buildInstanceCoordinateKey, compileContract, contractToAllPhaseViewStates, contractToImportsViewStateAndRefs, contractToPhaseViewState, createPhaseContract, discoverHeadlessInstances, filterTagsByPhase, generateElementBridgeFile, generateElementDefinitionFile, generateElementFile, generateElementFileReactTarget, generateSandboxRootFile, generateTypes, getEffectivePhase, getJayHtmlImports, getLinkedContractDir, hasSlowPhaseProperties, isTagInPhase, loadLinkedContract, parseContract, parseEnumValues, parseIsEnum, parseJayFile, renderRefsType, resolveHeadlessInstances, slowRenderTransform, validateContractPhases };
388
+ export { type ActionDefinition, type Contract, type ContractImportInfo, type ContractParam, type ContractProp, type ContractResolver, type ContractTag, ContractTagType, type DiscoveredHeadlessInstance, type EnumToImport, type ForEachHeadlessInstance, type HeadlessContractInfo, type HeadlessInstanceDiscoveryResult, type HeadlessInstanceResolvedData, JAY_IMPORT_RESOLVER, type JayContractImportLink, type JayHtmlSourceFile, type JayImportResolver, type PhaseViewStates, type RenderingPhase, type SlowRenderInput, type SlowRenderOutput, buildInstanceCoordinateKey, compileAction, compileContract, contractToAllPhaseViewStates, contractToImportsViewStateAndRefs, contractToPhaseViewState, createPhaseContract, defaultContractResolver, discoverHeadlessInstances, filterTagsByPhase, generateElementBridgeFile, generateElementDefinitionFile, generateElementFile, generateElementFileReactTarget, generateSandboxRootFile, generateTypes, getEffectivePhase, getJayHtmlImports, getLinkedContractDir, hasSlowPhaseProperties, isTagInPhase, loadLinkedContract, parseAction, parseContract, parseEnumValues, parseIsEnum, parseJayFile, renderRefsType, resolveHeadlessInstances, slowRenderTransform, validateContractPhases };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { RenderFragment, Imports, Import, JayImportedType, isObjectType as isObjectType$1, isImportedType, isRecursiveType, JayUnknown, WithValidations, isArrayType as isArrayType$1, isAtomicType, isPromiseType, isEnumType, hasRefs, GenerateTarget, JayComponentType, JayTypeAlias, mkRefsTree, mkRef, equalJayTypes, JayUnionType, JayHTMLType, getModeFileExtension, RuntimeMode, ImportsFor, nestRefs, JayErrorType, mergeRefsTrees, JayObjectType, JayArrayType, JayPromiseType, JAY_CONTRACT_EXTENSION as JAY_CONTRACT_EXTENSION$1, JayRecursiveType, JAY_FULLSTACK_COMPONENTS, JayEnumType, resolvePrimitiveType, SourceFileFormat, resolvePluginComponent } from "@jay-framework/compiler-shared";
1
+ import { RenderFragment, Imports, Import, JayImportedType, isObjectType as isObjectType$1, isImportedType, isRecursiveType, JayUnknown, WithValidations, isArrayType as isArrayType$1, isAtomicType, isPromiseType, isEnumType, hasRefs, GenerateTarget, JayComponentType, JayTypeAlias, mkRefsTree, mkRef, equalJayTypes, JayUnionType, JayHTMLType, getModeFileExtension, RuntimeMode, ImportsFor, nestRefs, JayErrorType, mergeRefsTrees, JayObjectType, JayArrayType, JayPromiseType, JAY_CONTRACT_EXTENSION as JAY_CONTRACT_EXTENSION$1, JayRecursiveType, JAY_FULLSTACK_COMPONENTS, JayEnumType, resolvePrimitiveType, SourceFileFormat, JayOptionalType, isOptionalType, resolvePluginComponent, resolvePluginManifest } from "@jay-framework/compiler-shared";
2
2
  import path from "path";
3
3
  import { getLogger } from "@jay-framework/logger";
4
4
  import fs from "fs/promises";
@@ -13345,7 +13345,7 @@ function getComponentName(tagName, importedSymbols, headlessContractNames) {
13345
13345
  }
13346
13346
  return null;
13347
13347
  }
13348
- function renderInterface(aType) {
13348
+ function renderInterface$1(aType) {
13349
13349
  let childInterfaces = [];
13350
13350
  let genInterface = "";
13351
13351
  if (isObjectType$1(aType)) {
@@ -13361,12 +13361,12 @@ function renderInterface(aType) {
13361
13361
  const optional = childType.isOptional ? "?" : "";
13362
13362
  return ` ${prop}${optional}: ${childType.name}`;
13363
13363
  } else if (isObjectType$1(childType)) {
13364
- childInterfaces.push(renderInterface(childType));
13364
+ childInterfaces.push(renderInterface$1(childType));
13365
13365
  return ` ${prop}: ${childType.name}`;
13366
13366
  } else if (isArrayType$1(childType)) {
13367
13367
  let arrayItemType = childType.itemType;
13368
13368
  if (isObjectType$1(arrayItemType)) {
13369
- childInterfaces.push(renderInterface(arrayItemType));
13369
+ childInterfaces.push(renderInterface$1(arrayItemType));
13370
13370
  return ` ${prop}: Array<${arrayItemType.name}>`;
13371
13371
  }
13372
13372
  if (arrayItemType instanceof JayImportedType) {
@@ -13388,10 +13388,10 @@ function renderInterface(aType) {
13388
13388
  if (isAtomicType(promiseItemType))
13389
13389
  return ` ${prop}: Promise<${promiseItemType.name}>`;
13390
13390
  else if (isObjectType$1(promiseItemType)) {
13391
- childInterfaces.push(renderInterface(promiseItemType));
13391
+ childInterfaces.push(renderInterface$1(promiseItemType));
13392
13392
  return ` ${prop}: Promise<${promiseItemType.name}>`;
13393
13393
  } else if (isArrayType$1(promiseItemType) && isObjectType$1(promiseItemType.itemType)) {
13394
- childInterfaces.push(renderInterface(promiseItemType.itemType));
13394
+ childInterfaces.push(renderInterface$1(promiseItemType.itemType));
13395
13395
  return ` ${prop}: Promise<Array<${promiseItemType.itemType.name}>>`;
13396
13396
  }
13397
13397
  } else if (isEnumType(childType)) {
@@ -13417,7 +13417,7 @@ ${childType.values.map((_) => " " + _).join(",\n")}
13417
13417
  return [...childInterfaces, genInterface].join("\n\n");
13418
13418
  }
13419
13419
  function generateTypes(types2) {
13420
- return renderInterface(types2);
13420
+ return renderInterface$1(types2);
13421
13421
  }
13422
13422
  class Indent {
13423
13423
  base;
@@ -14526,16 +14526,6 @@ ${indent.curr}return ${childElement.rendered}}, '${trackBy}')`,
14526
14526
  );
14527
14527
  }
14528
14528
  function renderHeadlessInstance(htmlElement, newContext, contractName) {
14529
- if (newContext.insideFastForEach) {
14530
- return new RenderFragment(
14531
- "",
14532
- Imports.none(),
14533
- [
14534
- `<jay:${contractName}> cannot be used inside a fast-phase forEach. Headless component instances require server-side rendering which is not available for dynamically-iterated arrays. Change the array's phase to "slow" in the contract to use slowForEach, or use a key-based headless component instead.`
14535
- ],
14536
- mkRefsTree([], {})
14537
- );
14538
- }
14539
14529
  const headlessImport = newContext.headlessImports.find(
14540
14530
  (h) => h.contractName === contractName
14541
14531
  );
@@ -14555,7 +14545,7 @@ ${indent.curr}return ${childElement.rendered}}, '${trackBy}')`,
14555
14545
  const interactiveViewStateType = `${pascal}InteractiveViewState`;
14556
14546
  const refsTypeName = `${pascal}Refs`;
14557
14547
  const elementType = `_Headless${pascal}${idx}Element`;
14558
- const renderType = `_Headless${pascal}${idx}ElementRender`;
14548
+ const renderType2 = `_Headless${pascal}${idx}ElementRender`;
14559
14549
  const preRenderType = `_Headless${pascal}${idx}ElementPreRender`;
14560
14550
  newContext.usedComponentImports.add(interactiveViewStateType);
14561
14551
  for (const link of headlessImport.contractLinks) {
@@ -14629,15 +14619,14 @@ ${indent.curr}return ${childElement.rendered}}, '${trackBy}')`,
14629
14619
  newContext.coordinateCounters.set(counterKey, localIndex + 1);
14630
14620
  coordinateRef = String(localIndex);
14631
14621
  }
14632
- const coordinateKey2 = [
14633
- ...newContext.coordinatePrefix,
14634
- `${contractName}:${coordinateRef}`
14635
- ].join("/");
14622
+ const isInsideForEach = newContext.insideFastForEach;
14623
+ const coordinateSuffix = `${contractName}:${coordinateRef}`;
14624
+ const coordinateKey2 = isInsideForEach ? void 0 : [...newContext.coordinatePrefix, coordinateSuffix].join("/");
14636
14625
  const renderFnCode = `
14637
14626
  // Inline template for headless component: ${contractName} #${idx}
14638
14627
  type ${elementType} = JayElement<${interactiveViewStateType}, ${refsTypeName}>;
14639
- type ${renderType} = RenderElement<${interactiveViewStateType}, ${refsTypeName}, ${elementType}>;
14640
- type ${preRenderType} = [${refsTypeName}, ${renderType}];
14628
+ type ${renderType2} = RenderElement<${interactiveViewStateType}, ${refsTypeName}, ${elementType}>;
14629
+ type ${preRenderType} = [${refsTypeName}, ${renderType2}];
14641
14630
 
14642
14631
  function ${renderFnName}(options?: RenderElementOptions): ${preRenderType} {
14643
14632
  ${renderedRefsManager}
@@ -14651,7 +14640,7 @@ ${inlineBody.rendered}
14651
14640
  const ${componentSymbol} = makeHeadlessInstanceComponent(
14652
14641
  ${renderFnName},
14653
14642
  ${pluginComponentName}.comp,
14654
- '${coordinateKey2}',
14643
+ ${isInsideForEach ? `(dataIds) => [...dataIds, '${coordinateSuffix}'].toString()` : `'${coordinateKey2}'`},
14655
14644
  ${pluginComponentName}.contexts,
14656
14645
  );`;
14657
14646
  newContext.headlessInstanceDefs.push({
@@ -14999,7 +14988,7 @@ ${renderedRoot.rendered}
14999
14988
  const elementType = baseElementName + "Element";
15000
14989
  const refsType = baseElementName + "ElementRefs";
15001
14990
  const viewStateType = types2.name;
15002
- const renderType = `${elementType}Render`;
14991
+ const renderType2 = `${elementType}Render`;
15003
14992
  const preRenderType = `${elementType}PreRender`;
15004
14993
  const contractType = `${baseElementName}Contract`;
15005
14994
  let imports = renderedRoot.imports.plus(Import.ConstructContext).plus(Import.RenderElementOptions).plus(Import.RenderElement).plus(Import.ReferencesManager).plus(Import.jayContract);
@@ -15009,8 +14998,8 @@ ${renderedRoot.rendered}
15009
14998
  const { imports: refImports, renderedRefs } = renderRefsType(renderedRoot.refs, refsType);
15010
14999
  imports = imports.plus(refImports);
15011
15000
  let renderedElement = `export type ${elementType} = JayElement<${viewStateType}, ${refsType}>
15012
- export type ${renderType} = RenderElement<${viewStateType}, ${refsType}, ${elementType}>
15013
- export type ${preRenderType} = [${refsType}, ${renderType}]
15001
+ export type ${renderType2} = RenderElement<${viewStateType}, ${refsType}, ${elementType}>
15002
+ export type ${preRenderType} = [${refsType}, ${renderType2}]
15014
15003
  export type ${contractType} = JayContract<${viewStateType}, ${refsType}>;
15015
15004
  `;
15016
15005
  if (importedSandboxedSymbols.size > 0) {
@@ -20427,9 +20416,252 @@ async function parseJayFile(html2, filename, filePath, options, linkedContractRe
20427
20416
  }
20428
20417
  function getJayHtmlImports(html2) {
20429
20418
  const root = parse_2(html2);
20430
- return root.querySelectorAll(
20431
- 'script[type="application/jay-headfull"], script[type="application/jay-headless"]'
20432
- ).map((script) => script.getAttribute("src")).filter((src) => src !== null);
20419
+ return root.querySelectorAll('script[type="application/jay-headfull"]').map((script) => script.getAttribute("src")).filter((src) => src !== null);
20420
+ }
20421
+ function parseArrayShorthand(value) {
20422
+ if (value.endsWith("[]")) {
20423
+ return value.slice(0, -2);
20424
+ }
20425
+ return null;
20426
+ }
20427
+ function resolveStringType(value, importAliases, path2) {
20428
+ const arrayBase = parseArrayShorthand(value);
20429
+ if (arrayBase) {
20430
+ const itemType = resolveStringType(arrayBase, importAliases, path2);
20431
+ if (itemType)
20432
+ return new JayArrayType(itemType);
20433
+ return null;
20434
+ }
20435
+ if (value.endsWith("?")) {
20436
+ const alias = value.slice(0, -1);
20437
+ if (importAliases.has(alias)) {
20438
+ return new JayImportedType(alias, JayUnknown, true);
20439
+ }
20440
+ return null;
20441
+ }
20442
+ const primitive = resolvePrimitiveType(value);
20443
+ if (primitive !== JayUnknown) {
20444
+ return primitive;
20445
+ }
20446
+ if (parseIsEnum(value)) {
20447
+ const name = path2.length > 0 ? path2[path2.length - 1] : "value";
20448
+ return new JayEnumType(name, parseEnumValues(value));
20449
+ }
20450
+ if (importAliases.has(value)) {
20451
+ return new JayImportedType(value, JayUnknown, false);
20452
+ }
20453
+ return null;
20454
+ }
20455
+ function resolveActionType(value, importAliases, path2) {
20456
+ if (typeof value === "string") {
20457
+ return resolveStringType(value, importAliases, path2);
20458
+ }
20459
+ if (Array.isArray(value)) {
20460
+ if (value.length === 0)
20461
+ return null;
20462
+ const firstItem = value[0];
20463
+ if (typeof firstItem === "string") {
20464
+ const itemType = resolveStringType(firstItem, importAliases, [...path2, "item"]);
20465
+ if (itemType)
20466
+ return new JayArrayType(itemType);
20467
+ } else if (typeof firstItem === "object" && firstItem !== null) {
20468
+ const itemType = parseObjectType(firstItem, importAliases, [
20469
+ ...path2,
20470
+ "item"
20471
+ ]);
20472
+ return new JayArrayType(itemType);
20473
+ }
20474
+ return null;
20475
+ }
20476
+ if (typeof value === "object" && value !== null) {
20477
+ if (Object.keys(value).length === 0) {
20478
+ return new JayObjectType(path2.join("."), {});
20479
+ }
20480
+ return parseObjectType(value, importAliases, path2);
20481
+ }
20482
+ return null;
20483
+ }
20484
+ function parseObjectType(obj, importAliases, path2) {
20485
+ const props = {};
20486
+ for (const [rawKey, value] of Object.entries(obj)) {
20487
+ const isOptional = rawKey.endsWith("?");
20488
+ const name = isOptional ? rawKey.slice(0, -1) : rawKey;
20489
+ const type2 = resolveActionType(value, importAliases, [...path2, name]);
20490
+ if (type2) {
20491
+ props[name] = isOptional ? new JayOptionalType(type2) : type2;
20492
+ }
20493
+ }
20494
+ const typeName = path2.join(".");
20495
+ return new JayObjectType(typeName, props);
20496
+ }
20497
+ function parseAction(actionYaml, fileName) {
20498
+ try {
20499
+ const parsed = jsYaml.load(actionYaml);
20500
+ const validations = [];
20501
+ if (!parsed || typeof parsed !== "object") {
20502
+ return new WithValidations(void 0, [
20503
+ "Action file is empty or not a valid YAML object"
20504
+ ]);
20505
+ }
20506
+ if (!parsed.name || typeof parsed.name !== "string") {
20507
+ validations.push(`Action must have a 'name' field (string)`);
20508
+ }
20509
+ if (!parsed.description || typeof parsed.description !== "string") {
20510
+ validations.push(`Action must have a 'description' field (string)`);
20511
+ }
20512
+ if (parsed.inputSchema === void 0 || parsed.inputSchema === null) {
20513
+ validations.push(`Action must have an 'inputSchema' field`);
20514
+ }
20515
+ if (validations.length > 0) {
20516
+ return new WithValidations(void 0, validations);
20517
+ }
20518
+ const imports = {};
20519
+ if (parsed.import && typeof parsed.import === "object") {
20520
+ for (const [alias, contractPath] of Object.entries(
20521
+ parsed.import
20522
+ )) {
20523
+ if (typeof contractPath === "string") {
20524
+ imports[alias] = contractPath;
20525
+ }
20526
+ }
20527
+ }
20528
+ const importAliases = new Set(Object.keys(imports));
20529
+ let inputType;
20530
+ if (typeof parsed.inputSchema === "object" && parsed.inputSchema !== null && Object.keys(parsed.inputSchema).length > 0) {
20531
+ inputType = parseObjectType(
20532
+ parsed.inputSchema,
20533
+ importAliases,
20534
+ ["input"]
20535
+ );
20536
+ } else {
20537
+ inputType = new JayObjectType("input", {});
20538
+ }
20539
+ let outputType;
20540
+ if (parsed.outputSchema !== void 0 && parsed.outputSchema !== null) {
20541
+ outputType = resolveActionType(parsed.outputSchema, importAliases, ["output"]) ?? void 0;
20542
+ }
20543
+ const definition = {
20544
+ name: parsed.name,
20545
+ description: parsed.description,
20546
+ imports,
20547
+ inputType,
20548
+ outputType
20549
+ };
20550
+ return new WithValidations(definition, []);
20551
+ } catch (e2) {
20552
+ throw new Error(`Failed to parse action YAML for ${fileName}: ${e2.message}`);
20553
+ }
20554
+ }
20555
+ function defaultContractResolver(contractSubpath) {
20556
+ const baseName = contractSubpath.replace(".jay-contract", "");
20557
+ const viewStateName = pascalCase(baseName) + "ViewState";
20558
+ return { importPath: `./${contractSubpath}`, viewStateName };
20559
+ }
20560
+ function collectImportedAliases(type2, aliases2) {
20561
+ if (isImportedType(type2)) {
20562
+ aliases2.add(type2.name);
20563
+ } else if (isOptionalType(type2)) {
20564
+ collectImportedAliases(type2.innerType, aliases2);
20565
+ } else if (isObjectType$1(type2)) {
20566
+ for (const prop of Object.values(type2.props)) {
20567
+ collectImportedAliases(prop, aliases2);
20568
+ }
20569
+ } else if (isArrayType$1(type2)) {
20570
+ collectImportedAliases(type2.itemType, aliases2);
20571
+ }
20572
+ }
20573
+ function renderType(type2, aliasToViewState, indent) {
20574
+ if (isOptionalType(type2)) {
20575
+ return renderType(type2.innerType, aliasToViewState, indent);
20576
+ }
20577
+ if (isAtomicType(type2)) {
20578
+ return type2.name;
20579
+ }
20580
+ if (isEnumType(type2)) {
20581
+ return type2.values.map((v) => `'${v}'`).join(" | ");
20582
+ }
20583
+ if (isImportedType(type2)) {
20584
+ const viewStateName = aliasToViewState.get(type2.name) || type2.name;
20585
+ return type2.isOptional ? `${viewStateName} | null` : viewStateName;
20586
+ }
20587
+ if (isArrayType$1(type2)) {
20588
+ const itemStr = renderType(type2.itemType, aliasToViewState, indent + " ");
20589
+ return `Array<${itemStr}>`;
20590
+ }
20591
+ if (isObjectType$1(type2)) {
20592
+ if (Object.keys(type2.props).length === 0) {
20593
+ return "Record<string, unknown>";
20594
+ }
20595
+ return renderInlineObject(type2, aliasToViewState, indent);
20596
+ }
20597
+ return "unknown";
20598
+ }
20599
+ function renderInlineObject(type2, aliasToViewState, indent) {
20600
+ const childIndent = indent + " ";
20601
+ const lines = Object.entries(type2.props).map(([prop, propType]) => {
20602
+ const optional = isOptionalType(propType) ? "?" : "";
20603
+ const tsType = renderType(propType, aliasToViewState, childIndent);
20604
+ return `${childIndent}${prop}${optional}: ${tsType};`;
20605
+ });
20606
+ return `{
20607
+ ${lines.join("\n")}
20608
+ ${indent}}`;
20609
+ }
20610
+ function renderInterface(interfaceName, type2, aliasToViewState) {
20611
+ const propKeys = Object.keys(type2.props);
20612
+ if (propKeys.length === 0) {
20613
+ return `export interface ${interfaceName} {}`;
20614
+ }
20615
+ const lines = propKeys.map((prop) => {
20616
+ const optional = isOptionalType(type2.props[prop]) ? "?" : "";
20617
+ const tsType = renderType(type2.props[prop], aliasToViewState, " ");
20618
+ return ` ${prop}${optional}: ${tsType};`;
20619
+ });
20620
+ return `export interface ${interfaceName} {
20621
+ ${lines.join("\n")}
20622
+ }`;
20623
+ }
20624
+ function renderOutputType(typeName, outputType, aliasToViewState) {
20625
+ if (isObjectType$1(outputType) && Object.keys(outputType.props).length > 0) {
20626
+ return renderInterface(typeName, outputType, aliasToViewState);
20627
+ }
20628
+ const tsType = renderType(outputType, aliasToViewState, "");
20629
+ return `export type ${typeName} = ${tsType};`;
20630
+ }
20631
+ function compileAction(actionWithValidations, contractResolver = defaultContractResolver) {
20632
+ return actionWithValidations.map((action) => {
20633
+ const baseName = pascalCase(action.name);
20634
+ const sections = [];
20635
+ const usedAliases = /* @__PURE__ */ new Set();
20636
+ collectImportedAliases(action.inputType, usedAliases);
20637
+ if (action.outputType) {
20638
+ collectImportedAliases(action.outputType, usedAliases);
20639
+ }
20640
+ const aliasToViewState = /* @__PURE__ */ new Map();
20641
+ const importStatements = [];
20642
+ for (const alias of usedAliases) {
20643
+ const contractSubpath = action.imports[alias];
20644
+ if (!contractSubpath)
20645
+ continue;
20646
+ const resolved = contractResolver(contractSubpath);
20647
+ if (!resolved)
20648
+ continue;
20649
+ aliasToViewState.set(alias, resolved.viewStateName);
20650
+ importStatements.push(
20651
+ `import { ${resolved.viewStateName} } from '${resolved.importPath}';`
20652
+ );
20653
+ }
20654
+ if (importStatements.length > 0) {
20655
+ sections.push(importStatements.join("\n"));
20656
+ }
20657
+ sections.push(renderInterface(`${baseName}Input`, action.inputType, aliasToViewState));
20658
+ if (action.outputType) {
20659
+ sections.push(
20660
+ renderOutputType(`${baseName}Output`, action.outputType, aliasToViewState)
20661
+ );
20662
+ }
20663
+ return sections.join("\n\n");
20664
+ });
20433
20665
  }
20434
20666
  const ALIAS = Symbol.for("yaml.alias");
20435
20667
  const DOC = Symbol.for("yaml.document");
@@ -26890,6 +27122,9 @@ const JAY_IMPORT_RESOLVER = {
26890
27122
  return new WithValidations(null, [
26891
27123
  `Contract "${contractName}" not found for plugin "${pluginName}". For dynamic contracts, run 'jay-stack agent-kit' to materialize them first.`
26892
27124
  ]);
27125
+ },
27126
+ resolvePluginManifest(pluginName, projectRoot) {
27127
+ return resolvePluginManifest(projectRoot, pluginName);
26893
27128
  }
26894
27129
  };
26895
27130
  function buildPhaseMap(contract, headlessContracts, importResolver) {
@@ -27208,20 +27443,23 @@ function discoverHeadlessInstances(preRenderedJayHtml) {
27208
27443
  }
27209
27444
  });
27210
27445
  const instances = [];
27446
+ const forEachInstances = [];
27211
27447
  const coordinateCounters = /* @__PURE__ */ new Map();
27212
- function walk(element, insidePreservedForEach) {
27448
+ function walk(element, insidePreservedForEach, forEachContexts) {
27213
27449
  const tagName = element.tagName?.toLowerCase();
27214
- const hasForEach = element.getAttribute("forEach") != null;
27450
+ const forEachAttr = element.getAttribute("forEach");
27451
+ const hasForEach = forEachAttr != null;
27452
+ const trackByAttr = element.getAttribute("trackBy");
27215
27453
  if (tagName?.startsWith("jay:")) {
27216
- if (!insidePreservedForEach) {
27217
- const contractName = tagName.substring(4);
27218
- const props = {};
27219
- for (const [key, value] of Object.entries(element.attributes)) {
27220
- const lowerKey = key.toLowerCase();
27221
- if (lowerKey !== "ref" && !lowerKey.startsWith("jay")) {
27222
- props[toCamelCase(key)] = value;
27223
- }
27454
+ const contractName = tagName.substring(4);
27455
+ const props = {};
27456
+ for (const [key, value] of Object.entries(element.attributes)) {
27457
+ const lowerKey = key.toLowerCase();
27458
+ if (lowerKey !== "ref" && !lowerKey.startsWith("jay")) {
27459
+ props[toCamelCase(key)] = value;
27224
27460
  }
27461
+ }
27462
+ if (!insidePreservedForEach) {
27225
27463
  const hasUnresolvedProps = Object.values(props).some((v) => hasBindings(v));
27226
27464
  if (!hasUnresolvedProps) {
27227
27465
  const prefix = buildCoordinatePrefix(element);
@@ -27240,20 +27478,43 @@ function discoverHeadlessInstances(preRenderedJayHtml) {
27240
27478
  coordinate
27241
27479
  });
27242
27480
  }
27481
+ } else {
27482
+ const innerForEach = forEachContexts[forEachContexts.length - 1];
27483
+ if (innerForEach) {
27484
+ let ref = element.getAttribute("ref");
27485
+ if (!ref) {
27486
+ const counterKey = ["forEach", contractName].join("/");
27487
+ const localIndex = coordinateCounters.get(counterKey) ?? 0;
27488
+ coordinateCounters.set(counterKey, localIndex + 1);
27489
+ ref = String(localIndex);
27490
+ }
27491
+ forEachInstances.push({
27492
+ contractName,
27493
+ forEachPath: innerForEach.forEachPath,
27494
+ trackBy: innerForEach.trackBy,
27495
+ propBindings: props,
27496
+ coordinateSuffix: `${contractName}:${ref}`
27497
+ });
27498
+ }
27243
27499
  }
27244
27500
  return;
27245
27501
  }
27502
+ const updatedForEachContexts = hasForEach && forEachAttr && trackByAttr ? [...forEachContexts, { forEachPath: forEachAttr, trackBy: trackByAttr }] : forEachContexts;
27246
27503
  for (const child of element.childNodes) {
27247
27504
  if (child.nodeType === NodeType.ELEMENT_NODE) {
27248
- walk(child, insidePreservedForEach || hasForEach);
27505
+ walk(
27506
+ child,
27507
+ insidePreservedForEach || hasForEach,
27508
+ updatedForEachContexts
27509
+ );
27249
27510
  }
27250
27511
  }
27251
27512
  }
27252
27513
  const body = root.querySelector("body");
27253
27514
  if (body) {
27254
- walk(body, false);
27515
+ walk(body, false, []);
27255
27516
  }
27256
- return { instances, preRenderedJayHtml: root.toString() };
27517
+ return { instances, forEachInstances, preRenderedJayHtml: root.toString() };
27257
27518
  }
27258
27519
  function coordinateKey(coord) {
27259
27520
  return coord.join("/");
@@ -27344,11 +27605,13 @@ export {
27344
27605
  ContractTagType,
27345
27606
  JAY_IMPORT_RESOLVER,
27346
27607
  buildInstanceCoordinateKey,
27608
+ compileAction,
27347
27609
  compileContract,
27348
27610
  contractToAllPhaseViewStates,
27349
27611
  contractToImportsViewStateAndRefs,
27350
27612
  contractToPhaseViewState,
27351
27613
  createPhaseContract,
27614
+ defaultContractResolver,
27352
27615
  discoverHeadlessInstances,
27353
27616
  filterTagsByPhase,
27354
27617
  generateElementBridgeFile,
@@ -27363,6 +27626,7 @@ export {
27363
27626
  hasSlowPhaseProperties,
27364
27627
  isTagInPhase,
27365
27628
  loadLinkedContract,
27629
+ parseAction,
27366
27630
  parseContract,
27367
27631
  parseEnumValues,
27368
27632
  parseIsEnum,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/compiler-jay-html",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.js",
@@ -34,12 +34,12 @@
34
34
  },
35
35
  "author": "",
36
36
  "dependencies": {
37
- "@jay-framework/compiler-analyze-exported-types": "^0.12.0",
38
- "@jay-framework/compiler-shared": "^0.12.0",
39
- "@jay-framework/component": "^0.12.0",
40
- "@jay-framework/logger": "^0.12.0",
41
- "@jay-framework/runtime": "^0.12.0",
42
- "@jay-framework/secure": "^0.12.0",
37
+ "@jay-framework/compiler-analyze-exported-types": "^0.13.0",
38
+ "@jay-framework/compiler-shared": "^0.13.0",
39
+ "@jay-framework/component": "^0.13.0",
40
+ "@jay-framework/logger": "^0.13.0",
41
+ "@jay-framework/runtime": "^0.13.0",
42
+ "@jay-framework/secure": "^0.13.0",
43
43
  "@types/js-yaml": "^4.0.9",
44
44
  "change-case": "^4.1.2",
45
45
  "js-yaml": "^4.1.0",
@@ -51,8 +51,8 @@
51
51
  },
52
52
  "devDependencies": {
53
53
  "@caiogondim/strip-margin": "^1.0.0",
54
- "@jay-framework/4-react": "^0.12.0",
55
- "@jay-framework/dev-environment": "^0.12.0",
54
+ "@jay-framework/4-react": "^0.13.0",
55
+ "@jay-framework/dev-environment": "^0.13.0",
56
56
  "@testing-library/jest-dom": "^6.2.0",
57
57
  "@types/js-beautify": "^1",
58
58
  "@types/node": "^20.11.5",