@jay-framework/stack-client-runtime 0.15.5 → 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
@@ -122,9 +122,30 @@ function makeHeadlessInstanceComponent(preRender, componentDef, coordinateKey) {
122
122
  ...pluginResolvedContexts
123
123
  );
124
124
  const originalRender = compCore.render;
125
- compCore.render = () => {
126
- return { ...resolvedFastVS, ...originalRender() };
127
- };
125
+ const isHydrating = runtime.currentConstructionContext()?.isHydrating;
126
+ if (isHydrating) {
127
+ const [hydrationDone, setHydrationDone] = component.createSignal(false);
128
+ compCore.render = () => {
129
+ const done = hydrationDone();
130
+ const clientVS = { ...resolvedFastVS, ...originalRender() };
131
+ if (!done) {
132
+ setHydrationDone(true);
133
+ if (instanceData)
134
+ instanceData.viewStates[resolvedKey] = component.materializeViewState(resolvedFastVS);
135
+ return resolvedFastVS;
136
+ }
137
+ if (instanceData)
138
+ instanceData.viewStates[resolvedKey] = component.materializeViewState(clientVS);
139
+ return clientVS;
140
+ };
141
+ } else {
142
+ compCore.render = () => {
143
+ const vs = { ...resolvedFastVS, ...originalRender() };
144
+ if (instanceData)
145
+ instanceData.viewStates[resolvedKey] = component.materializeViewState(vs);
146
+ return vs;
147
+ };
148
+ }
128
149
  return compCore;
129
150
  };
130
151
  return component.makeJayComponent(preRender, wrappedConstructor, ...resolvedContexts);
@@ -190,6 +211,10 @@ function makeCompositeJayComponent(preRender, defaultViewState, fastCarryForward
190
211
  );
191
212
  }
192
213
  });
214
+ const ivs = instancesData.viewStates;
215
+ if (Object.keys(ivs).length > 0) {
216
+ viewState.__headlessInstances = ivs;
217
+ }
193
218
  return viewState;
194
219
  }
195
220
  };
@@ -264,6 +289,10 @@ function hydrateCompositeJayComponent(hydratePreRender, defaultViewState, fastCa
264
289
  );
265
290
  }
266
291
  });
292
+ const ivs = instancesData.viewStates;
293
+ if (Object.keys(ivs).length > 0) {
294
+ viewState.__headlessInstances = ivs;
295
+ }
267
296
  return viewState;
268
297
  }
269
298
  };
@@ -373,6 +402,112 @@ function buildActionUrl(baseUrl, actionName, method, input) {
373
402
  }
374
403
  return fullUrl;
375
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
+ }
376
511
  function isSimpleObject(obj) {
377
512
  if (typeof obj !== "object" || obj === null) {
378
513
  return false;
@@ -577,11 +712,6 @@ class AutomationAgent {
577
712
  if (options) {
578
713
  this.initialSlowViewState = options.initialViewState;
579
714
  this.trackByMap = options.trackByMap;
580
- this.mergedViewState = deepMergeViewStates(
581
- options.initialViewState,
582
- this.component.viewState || {},
583
- options.trackByMap
584
- );
585
715
  }
586
716
  this.subscribeToUpdates();
587
717
  }
@@ -589,13 +719,7 @@ class AutomationAgent {
589
719
  this.viewStateHandler = () => {
590
720
  this.cachedRaw = null;
591
721
  this.cachedGrouped = null;
592
- if (this.initialSlowViewState && this.trackByMap) {
593
- this.mergedViewState = deepMergeViewStates(
594
- this.initialSlowViewState,
595
- this.component.viewState || {},
596
- this.trackByMap
597
- );
598
- }
722
+ this.mergedViewState = null;
599
723
  this.notifyListeners();
600
724
  };
601
725
  this.component.addEventListener(VIEW_STATE_CHANGE, this.viewStateHandler);
@@ -616,9 +740,22 @@ class AutomationAgent {
616
740
  return this.cachedGrouped;
617
741
  }
618
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
+ }
619
757
  return {
620
- // Use merged state if available (slow+fast), otherwise component's viewState
621
- viewState: this.mergedViewState || this.component.viewState,
758
+ viewState,
622
759
  interactions: this.getGrouped(),
623
760
  customEvents: this.getCustomEvents()
624
761
  };
@@ -688,6 +825,7 @@ exports.AUTOMATION_CONTEXT = AUTOMATION_CONTEXT;
688
825
  exports.ActionError = ActionError;
689
826
  exports.HEADLESS_INSTANCES = HEADLESS_INSTANCES;
690
827
  exports.createActionCaller = createActionCaller;
828
+ exports.createStreamCaller = createStreamCaller;
691
829
  exports.hydrateCompositeJayComponent = hydrateCompositeJayComponent;
692
830
  exports.makeCompositeJayComponent = makeCompositeJayComponent;
693
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)
@@ -120,9 +120,30 @@ function makeHeadlessInstanceComponent(preRender, componentDef, coordinateKey) {
120
120
  ...pluginResolvedContexts
121
121
  );
122
122
  const originalRender = compCore.render;
123
- compCore.render = () => {
124
- return { ...resolvedFastVS, ...originalRender() };
125
- };
123
+ const isHydrating = currentConstructionContext()?.isHydrating;
124
+ if (isHydrating) {
125
+ const [hydrationDone, setHydrationDone] = createSignal(false);
126
+ compCore.render = () => {
127
+ const done = hydrationDone();
128
+ const clientVS = { ...resolvedFastVS, ...originalRender() };
129
+ if (!done) {
130
+ setHydrationDone(true);
131
+ if (instanceData)
132
+ instanceData.viewStates[resolvedKey] = materializeViewState(resolvedFastVS);
133
+ return resolvedFastVS;
134
+ }
135
+ if (instanceData)
136
+ instanceData.viewStates[resolvedKey] = materializeViewState(clientVS);
137
+ return clientVS;
138
+ };
139
+ } else {
140
+ compCore.render = () => {
141
+ const vs = { ...resolvedFastVS, ...originalRender() };
142
+ if (instanceData)
143
+ instanceData.viewStates[resolvedKey] = materializeViewState(vs);
144
+ return vs;
145
+ };
146
+ }
126
147
  return compCore;
127
148
  };
128
149
  return makeJayComponent(preRender, wrappedConstructor, ...resolvedContexts);
@@ -188,6 +209,10 @@ function makeCompositeJayComponent(preRender, defaultViewState, fastCarryForward
188
209
  );
189
210
  }
190
211
  });
212
+ const ivs = instancesData.viewStates;
213
+ if (Object.keys(ivs).length > 0) {
214
+ viewState.__headlessInstances = ivs;
215
+ }
191
216
  return viewState;
192
217
  }
193
218
  };
@@ -262,6 +287,10 @@ function hydrateCompositeJayComponent(hydratePreRender, defaultViewState, fastCa
262
287
  );
263
288
  }
264
289
  });
290
+ const ivs = instancesData.viewStates;
291
+ if (Object.keys(ivs).length > 0) {
292
+ viewState.__headlessInstances = ivs;
293
+ }
265
294
  return viewState;
266
295
  }
267
296
  };
@@ -371,6 +400,112 @@ function buildActionUrl(baseUrl, actionName, method, input) {
371
400
  }
372
401
  return fullUrl;
373
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
+ }
374
509
  function isSimpleObject(obj) {
375
510
  if (typeof obj !== "object" || obj === null) {
376
511
  return false;
@@ -575,11 +710,6 @@ class AutomationAgent {
575
710
  if (options) {
576
711
  this.initialSlowViewState = options.initialViewState;
577
712
  this.trackByMap = options.trackByMap;
578
- this.mergedViewState = deepMergeViewStates(
579
- options.initialViewState,
580
- this.component.viewState || {},
581
- options.trackByMap
582
- );
583
713
  }
584
714
  this.subscribeToUpdates();
585
715
  }
@@ -587,13 +717,7 @@ class AutomationAgent {
587
717
  this.viewStateHandler = () => {
588
718
  this.cachedRaw = null;
589
719
  this.cachedGrouped = null;
590
- if (this.initialSlowViewState && this.trackByMap) {
591
- this.mergedViewState = deepMergeViewStates(
592
- this.initialSlowViewState,
593
- this.component.viewState || {},
594
- this.trackByMap
595
- );
596
- }
720
+ this.mergedViewState = null;
597
721
  this.notifyListeners();
598
722
  };
599
723
  this.component.addEventListener(VIEW_STATE_CHANGE, this.viewStateHandler);
@@ -614,9 +738,22 @@ class AutomationAgent {
614
738
  return this.cachedGrouped;
615
739
  }
616
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
+ }
617
755
  return {
618
- // Use merged state if available (slow+fast), otherwise component's viewState
619
- viewState: this.mergedViewState || this.component.viewState,
756
+ viewState,
620
757
  interactions: this.getGrouped(),
621
758
  customEvents: this.getCustomEvents()
622
759
  };
@@ -687,6 +824,7 @@ export {
687
824
  ActionError,
688
825
  HEADLESS_INSTANCES,
689
826
  createActionCaller,
827
+ createStreamCaller,
690
828
  hydrateCompositeJayComponent,
691
829
  makeCompositeJayComponent,
692
830
  makeHeadlessInstanceComponent,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/stack-client-runtime",
3
- "version": "0.15.5",
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.5",
31
- "@jay-framework/fullstack-component": "^0.15.5",
32
- "@jay-framework/runtime": "^0.15.5",
33
- "@jay-framework/runtime-automation": "^0.15.5",
34
- "@jay-framework/view-state-merge": "^0.15.5"
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.5",
38
- "@jay-framework/jay-cli": "^0.15.5",
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",