@jay-framework/stack-client-runtime 0.15.6 → 0.16.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.cjs CHANGED
@@ -130,13 +130,20 @@ function makeHeadlessInstanceComponent(preRender, componentDef, coordinateKey) {
130
130
  const clientVS = { ...resolvedFastVS, ...originalRender() };
131
131
  if (!done) {
132
132
  setHydrationDone(true);
133
+ if (instanceData)
134
+ instanceData.viewStates[resolvedKey] = component.materializeViewState(resolvedFastVS);
133
135
  return resolvedFastVS;
134
136
  }
137
+ if (instanceData)
138
+ instanceData.viewStates[resolvedKey] = component.materializeViewState(clientVS);
135
139
  return clientVS;
136
140
  };
137
141
  } else {
138
142
  compCore.render = () => {
139
- return { ...resolvedFastVS, ...originalRender() };
143
+ const vs = { ...resolvedFastVS, ...originalRender() };
144
+ if (instanceData)
145
+ instanceData.viewStates[resolvedKey] = component.materializeViewState(vs);
146
+ return vs;
140
147
  };
141
148
  }
142
149
  return compCore;
@@ -204,6 +211,10 @@ function makeCompositeJayComponent(preRender, defaultViewState, fastCarryForward
204
211
  );
205
212
  }
206
213
  });
214
+ const ivs = instancesData.viewStates;
215
+ if (Object.keys(ivs).length > 0) {
216
+ viewState.__headlessInstances = ivs;
217
+ }
207
218
  return viewState;
208
219
  }
209
220
  };
@@ -278,6 +289,10 @@ function hydrateCompositeJayComponent(hydratePreRender, defaultViewState, fastCa
278
289
  );
279
290
  }
280
291
  });
292
+ const ivs = instancesData.viewStates;
293
+ if (Object.keys(ivs).length > 0) {
294
+ viewState.__headlessInstances = ivs;
295
+ }
281
296
  return viewState;
282
297
  }
283
298
  };
@@ -387,6 +402,112 @@ function buildActionUrl(baseUrl, actionName, method, input) {
387
402
  }
388
403
  return fullUrl;
389
404
  }
405
+ function createStreamCaller(actionName) {
406
+ return (input) => {
407
+ return {
408
+ [Symbol.asyncIterator]() {
409
+ const baseUrl = globalOptions.baseUrl ?? "";
410
+ const url = `${baseUrl}${ACTION_ENDPOINT_BASE}/${actionName}`;
411
+ let reader = null;
412
+ let buffer = "";
413
+ let done = false;
414
+ let error = null;
415
+ const chunks = [];
416
+ let resolveNext = null;
417
+ const fetchPromise = fetch(url, {
418
+ method: "POST",
419
+ headers: {
420
+ "Content-Type": "application/json",
421
+ ...globalOptions.headers
422
+ },
423
+ body: JSON.stringify(input)
424
+ }).then((response) => {
425
+ if (!response.ok) {
426
+ throw new ActionError(
427
+ "STREAM_ERROR",
428
+ `Stream '${actionName}' failed with status ${response.status}`
429
+ );
430
+ }
431
+ reader = response.body.getReader();
432
+ pump();
433
+ }).catch((err) => {
434
+ error = err instanceof ActionError ? err : new ActionError(
435
+ "NETWORK_ERROR",
436
+ `Network error streaming '${actionName}': ${err.message}`
437
+ );
438
+ if (resolveNext)
439
+ resolveNext();
440
+ });
441
+ const decoder = new TextDecoder();
442
+ function pump() {
443
+ reader.read().then(({ done: readerDone, value }) => {
444
+ if (value) {
445
+ buffer += decoder.decode(value, { stream: true });
446
+ const lines = buffer.split("\n");
447
+ buffer = lines.pop();
448
+ for (const line of lines) {
449
+ if (!line.trim())
450
+ continue;
451
+ const parsed = JSON.parse(line);
452
+ if (parsed.error) {
453
+ error = new ActionError("STREAM_ERROR", parsed.error);
454
+ if (resolveNext)
455
+ resolveNext();
456
+ return;
457
+ }
458
+ if (parsed.done) {
459
+ done = true;
460
+ if (resolveNext)
461
+ resolveNext();
462
+ return;
463
+ }
464
+ if ("chunk" in parsed) {
465
+ chunks.push(parsed.chunk);
466
+ if (resolveNext)
467
+ resolveNext();
468
+ }
469
+ }
470
+ }
471
+ if (readerDone) {
472
+ done = true;
473
+ if (resolveNext)
474
+ resolveNext();
475
+ return;
476
+ }
477
+ pump();
478
+ }).catch((err) => {
479
+ error = new ActionError("STREAM_ERROR", err.message);
480
+ if (resolveNext)
481
+ resolveNext();
482
+ });
483
+ }
484
+ return {
485
+ async next() {
486
+ await fetchPromise;
487
+ while (true) {
488
+ if (chunks.length > 0) {
489
+ return { value: chunks.shift(), done: false };
490
+ }
491
+ if (error) {
492
+ throw error;
493
+ }
494
+ if (done) {
495
+ return { value: void 0, done: true };
496
+ }
497
+ await new Promise((resolve) => {
498
+ resolveNext = resolve;
499
+ });
500
+ resolveNext = null;
501
+ }
502
+ },
503
+ [Symbol.asyncIterator]() {
504
+ return this;
505
+ }
506
+ };
507
+ }
508
+ };
509
+ };
510
+ }
390
511
  function isSimpleObject(obj) {
391
512
  if (typeof obj !== "object" || obj === null) {
392
513
  return false;
@@ -591,11 +712,6 @@ class AutomationAgent {
591
712
  if (options) {
592
713
  this.initialSlowViewState = options.initialViewState;
593
714
  this.trackByMap = options.trackByMap;
594
- this.mergedViewState = deepMergeViewStates(
595
- options.initialViewState,
596
- this.component.viewState || {},
597
- options.trackByMap
598
- );
599
715
  }
600
716
  this.subscribeToUpdates();
601
717
  }
@@ -603,13 +719,7 @@ class AutomationAgent {
603
719
  this.viewStateHandler = () => {
604
720
  this.cachedRaw = null;
605
721
  this.cachedGrouped = null;
606
- if (this.initialSlowViewState && this.trackByMap) {
607
- this.mergedViewState = deepMergeViewStates(
608
- this.initialSlowViewState,
609
- this.component.viewState || {},
610
- this.trackByMap
611
- );
612
- }
722
+ this.mergedViewState = null;
613
723
  this.notifyListeners();
614
724
  };
615
725
  this.component.addEventListener(VIEW_STATE_CHANGE, this.viewStateHandler);
@@ -630,9 +740,22 @@ class AutomationAgent {
630
740
  return this.cachedGrouped;
631
741
  }
632
742
  getPageState() {
743
+ let viewState;
744
+ if (this.initialSlowViewState && this.trackByMap) {
745
+ viewState = deepMergeViewStates(
746
+ this.initialSlowViewState,
747
+ this.component.viewState || {},
748
+ this.trackByMap
749
+ );
750
+ const componentInstances = this.component.viewState?.__headlessInstances;
751
+ if (componentInstances) {
752
+ viewState.__headlessInstances = componentInstances;
753
+ }
754
+ } else {
755
+ viewState = this.component.viewState;
756
+ }
633
757
  return {
634
- // Use merged state if available (slow+fast), otherwise component's viewState
635
- viewState: this.mergedViewState || this.component.viewState,
758
+ viewState,
636
759
  interactions: this.getGrouped(),
637
760
  customEvents: this.getCustomEvents()
638
761
  };
@@ -702,6 +825,7 @@ exports.AUTOMATION_CONTEXT = AUTOMATION_CONTEXT;
702
825
  exports.ActionError = ActionError;
703
826
  exports.HEADLESS_INSTANCES = HEADLESS_INSTANCES;
704
827
  exports.createActionCaller = createActionCaller;
828
+ exports.createStreamCaller = createStreamCaller;
705
829
  exports.hydrateCompositeJayComponent = hydrateCompositeJayComponent;
706
830
  exports.makeCompositeJayComponent = makeCompositeJayComponent;
707
831
  exports.makeHeadlessInstanceComponent = makeHeadlessInstanceComponent;
package/dist/index.d.ts CHANGED
@@ -146,5 +146,25 @@ declare function setActionCallerOptions(options: ActionCallerOptions): void;
146
146
  * ```
147
147
  */
148
148
  declare function createActionCaller<Input, Output>(actionName: string, method?: HttpMethod): (input: Input) => Promise<Output>;
149
+ /**
150
+ * Creates a client-side stream caller that makes an HTTP request and returns
151
+ * an async iterable of chunks via NDJSON streaming.
152
+ *
153
+ * This function is used by the build transform to replace server-side makeJayStream
154
+ * handlers with client-side HTTP stream consumers.
155
+ *
156
+ * @param actionName - The unique action name (matches server registration)
157
+ * @returns A callable function that returns an AsyncIterable of chunks
158
+ *
159
+ * @example
160
+ * ```typescript
161
+ * // Build transform replaces:
162
+ * import { checkInventory } from './actions/inventory-check.actions';
163
+ *
164
+ * // With:
165
+ * const checkInventory = createStreamCaller<void, { name: string }>('inventory.check');
166
+ * ```
167
+ */
168
+ declare function createStreamCaller<Input, Chunk>(actionName: string): (input: Input) => AsyncIterable<Chunk>;
149
169
 
150
- export { type ActionCallerOptions, ActionError, type CompositePart, HEADLESS_INSTANCES, type HeadlessComponentDef, type HeadlessInstancesData, type HttpMethod, createActionCaller, hydrateCompositeJayComponent, makeCompositeJayComponent, makeHeadlessInstanceComponent, setActionCallerOptions };
170
+ export { type ActionCallerOptions, ActionError, type CompositePart, HEADLESS_INSTANCES, type HeadlessComponentDef, type HeadlessInstancesData, type HttpMethod, createActionCaller, createStreamCaller, hydrateCompositeJayComponent, makeCompositeJayComponent, makeHeadlessInstanceComponent, setActionCallerOptions };
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ var __publicField = (obj, key, value) => {
4
4
  __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
5
  return value;
6
6
  };
7
- import { makeJayComponent, createSignal, COMPONENT_CONTEXT, materializeViewState } from "@jay-framework/component";
7
+ import { makeJayComponent, createSignal, materializeViewState, COMPONENT_CONTEXT } from "@jay-framework/component";
8
8
  import { createJayContext, useContext, currentConstructionContext } from "@jay-framework/runtime";
9
9
  function deepMergeViewStates$1(base, overlay, trackByMap, path = "") {
10
10
  if (!base && !overlay)
@@ -128,13 +128,20 @@ function makeHeadlessInstanceComponent(preRender, componentDef, coordinateKey) {
128
128
  const clientVS = { ...resolvedFastVS, ...originalRender() };
129
129
  if (!done) {
130
130
  setHydrationDone(true);
131
+ if (instanceData)
132
+ instanceData.viewStates[resolvedKey] = materializeViewState(resolvedFastVS);
131
133
  return resolvedFastVS;
132
134
  }
135
+ if (instanceData)
136
+ instanceData.viewStates[resolvedKey] = materializeViewState(clientVS);
133
137
  return clientVS;
134
138
  };
135
139
  } else {
136
140
  compCore.render = () => {
137
- return { ...resolvedFastVS, ...originalRender() };
141
+ const vs = { ...resolvedFastVS, ...originalRender() };
142
+ if (instanceData)
143
+ instanceData.viewStates[resolvedKey] = materializeViewState(vs);
144
+ return vs;
138
145
  };
139
146
  }
140
147
  return compCore;
@@ -202,6 +209,10 @@ function makeCompositeJayComponent(preRender, defaultViewState, fastCarryForward
202
209
  );
203
210
  }
204
211
  });
212
+ const ivs = instancesData.viewStates;
213
+ if (Object.keys(ivs).length > 0) {
214
+ viewState.__headlessInstances = ivs;
215
+ }
205
216
  return viewState;
206
217
  }
207
218
  };
@@ -276,6 +287,10 @@ function hydrateCompositeJayComponent(hydratePreRender, defaultViewState, fastCa
276
287
  );
277
288
  }
278
289
  });
290
+ const ivs = instancesData.viewStates;
291
+ if (Object.keys(ivs).length > 0) {
292
+ viewState.__headlessInstances = ivs;
293
+ }
279
294
  return viewState;
280
295
  }
281
296
  };
@@ -385,6 +400,112 @@ function buildActionUrl(baseUrl, actionName, method, input) {
385
400
  }
386
401
  return fullUrl;
387
402
  }
403
+ function createStreamCaller(actionName) {
404
+ return (input) => {
405
+ return {
406
+ [Symbol.asyncIterator]() {
407
+ const baseUrl = globalOptions.baseUrl ?? "";
408
+ const url = `${baseUrl}${ACTION_ENDPOINT_BASE}/${actionName}`;
409
+ let reader = null;
410
+ let buffer = "";
411
+ let done = false;
412
+ let error = null;
413
+ const chunks = [];
414
+ let resolveNext = null;
415
+ const fetchPromise = fetch(url, {
416
+ method: "POST",
417
+ headers: {
418
+ "Content-Type": "application/json",
419
+ ...globalOptions.headers
420
+ },
421
+ body: JSON.stringify(input)
422
+ }).then((response) => {
423
+ if (!response.ok) {
424
+ throw new ActionError(
425
+ "STREAM_ERROR",
426
+ `Stream '${actionName}' failed with status ${response.status}`
427
+ );
428
+ }
429
+ reader = response.body.getReader();
430
+ pump();
431
+ }).catch((err) => {
432
+ error = err instanceof ActionError ? err : new ActionError(
433
+ "NETWORK_ERROR",
434
+ `Network error streaming '${actionName}': ${err.message}`
435
+ );
436
+ if (resolveNext)
437
+ resolveNext();
438
+ });
439
+ const decoder = new TextDecoder();
440
+ function pump() {
441
+ reader.read().then(({ done: readerDone, value }) => {
442
+ if (value) {
443
+ buffer += decoder.decode(value, { stream: true });
444
+ const lines = buffer.split("\n");
445
+ buffer = lines.pop();
446
+ for (const line of lines) {
447
+ if (!line.trim())
448
+ continue;
449
+ const parsed = JSON.parse(line);
450
+ if (parsed.error) {
451
+ error = new ActionError("STREAM_ERROR", parsed.error);
452
+ if (resolveNext)
453
+ resolveNext();
454
+ return;
455
+ }
456
+ if (parsed.done) {
457
+ done = true;
458
+ if (resolveNext)
459
+ resolveNext();
460
+ return;
461
+ }
462
+ if ("chunk" in parsed) {
463
+ chunks.push(parsed.chunk);
464
+ if (resolveNext)
465
+ resolveNext();
466
+ }
467
+ }
468
+ }
469
+ if (readerDone) {
470
+ done = true;
471
+ if (resolveNext)
472
+ resolveNext();
473
+ return;
474
+ }
475
+ pump();
476
+ }).catch((err) => {
477
+ error = new ActionError("STREAM_ERROR", err.message);
478
+ if (resolveNext)
479
+ resolveNext();
480
+ });
481
+ }
482
+ return {
483
+ async next() {
484
+ await fetchPromise;
485
+ while (true) {
486
+ if (chunks.length > 0) {
487
+ return { value: chunks.shift(), done: false };
488
+ }
489
+ if (error) {
490
+ throw error;
491
+ }
492
+ if (done) {
493
+ return { value: void 0, done: true };
494
+ }
495
+ await new Promise((resolve) => {
496
+ resolveNext = resolve;
497
+ });
498
+ resolveNext = null;
499
+ }
500
+ },
501
+ [Symbol.asyncIterator]() {
502
+ return this;
503
+ }
504
+ };
505
+ }
506
+ };
507
+ };
508
+ }
388
509
  function isSimpleObject(obj) {
389
510
  if (typeof obj !== "object" || obj === null) {
390
511
  return false;
@@ -589,11 +710,6 @@ class AutomationAgent {
589
710
  if (options) {
590
711
  this.initialSlowViewState = options.initialViewState;
591
712
  this.trackByMap = options.trackByMap;
592
- this.mergedViewState = deepMergeViewStates(
593
- options.initialViewState,
594
- this.component.viewState || {},
595
- options.trackByMap
596
- );
597
713
  }
598
714
  this.subscribeToUpdates();
599
715
  }
@@ -601,13 +717,7 @@ class AutomationAgent {
601
717
  this.viewStateHandler = () => {
602
718
  this.cachedRaw = null;
603
719
  this.cachedGrouped = null;
604
- if (this.initialSlowViewState && this.trackByMap) {
605
- this.mergedViewState = deepMergeViewStates(
606
- this.initialSlowViewState,
607
- this.component.viewState || {},
608
- this.trackByMap
609
- );
610
- }
720
+ this.mergedViewState = null;
611
721
  this.notifyListeners();
612
722
  };
613
723
  this.component.addEventListener(VIEW_STATE_CHANGE, this.viewStateHandler);
@@ -628,9 +738,22 @@ class AutomationAgent {
628
738
  return this.cachedGrouped;
629
739
  }
630
740
  getPageState() {
741
+ let viewState;
742
+ if (this.initialSlowViewState && this.trackByMap) {
743
+ viewState = deepMergeViewStates(
744
+ this.initialSlowViewState,
745
+ this.component.viewState || {},
746
+ this.trackByMap
747
+ );
748
+ const componentInstances = this.component.viewState?.__headlessInstances;
749
+ if (componentInstances) {
750
+ viewState.__headlessInstances = componentInstances;
751
+ }
752
+ } else {
753
+ viewState = this.component.viewState;
754
+ }
631
755
  return {
632
- // Use merged state if available (slow+fast), otherwise component's viewState
633
- viewState: this.mergedViewState || this.component.viewState,
756
+ viewState,
634
757
  interactions: this.getGrouped(),
635
758
  customEvents: this.getCustomEvents()
636
759
  };
@@ -701,6 +824,7 @@ export {
701
824
  ActionError,
702
825
  HEADLESS_INSTANCES,
703
826
  createActionCaller,
827
+ createStreamCaller,
704
828
  hydrateCompositeJayComponent,
705
829
  makeCompositeJayComponent,
706
830
  makeHeadlessInstanceComponent,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/stack-client-runtime",
3
- "version": "0.15.6",
3
+ "version": "0.16.0",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.js",
@@ -27,15 +27,15 @@
27
27
  "test:watch": "vitest"
28
28
  },
29
29
  "dependencies": {
30
- "@jay-framework/component": "^0.15.6",
31
- "@jay-framework/fullstack-component": "^0.15.6",
32
- "@jay-framework/runtime": "^0.15.6",
33
- "@jay-framework/runtime-automation": "^0.15.6",
34
- "@jay-framework/view-state-merge": "^0.15.6"
30
+ "@jay-framework/component": "^0.16.0",
31
+ "@jay-framework/fullstack-component": "^0.16.0",
32
+ "@jay-framework/runtime": "^0.16.0",
33
+ "@jay-framework/runtime-automation": "^0.16.0",
34
+ "@jay-framework/view-state-merge": "^0.16.0"
35
35
  },
36
36
  "devDependencies": {
37
- "@jay-framework/dev-environment": "^0.15.6",
38
- "@jay-framework/jay-cli": "^0.15.6",
37
+ "@jay-framework/dev-environment": "^0.16.0",
38
+ "@jay-framework/jay-cli": "^0.16.0",
39
39
  "@types/express": "^5.0.2",
40
40
  "@types/node": "^22.15.21",
41
41
  "nodemon": "^3.0.3",