@spfn/core 0.3.0-beta.27 → 0.3.0-beta.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.
Files changed (48) hide show
  1. package/dist/codegen/index.d.ts +65 -2
  2. package/dist/codegen/index.js +160 -15
  3. package/dist/codegen/index.js.map +1 -1
  4. package/dist/contract/index.d.ts +2 -2
  5. package/dist/db/index.d.ts +39 -11
  6. package/dist/db/index.js +34 -14
  7. package/dist/db/index.js.map +1 -1
  8. package/dist/{error-TsUIjp_x.d.ts → error-CIZlYJlL.d.ts} +1 -1
  9. package/dist/event/index.d.ts +3 -3
  10. package/dist/event/index.js +10 -0
  11. package/dist/event/index.js.map +1 -1
  12. package/dist/event/sse/client.d.ts +2 -2
  13. package/dist/event/sse/client.js +27 -17
  14. package/dist/event/sse/client.js.map +1 -1
  15. package/dist/event/sse/index.d.ts +3 -3
  16. package/dist/event/sse/index.js +115 -34
  17. package/dist/event/sse/index.js.map +1 -1
  18. package/dist/event/ws/client.d.ts +2 -2
  19. package/dist/event/ws/index.d.ts +3 -3
  20. package/dist/ops/index.d.ts +3 -3
  21. package/dist/ops/index.js +15 -5
  22. package/dist/ops/index.js.map +1 -1
  23. package/dist/route/index.d.ts +2 -2
  24. package/dist/route/index.js +15 -5
  25. package/dist/route/index.js.map +1 -1
  26. package/dist/{router-4YoIy5fN.d.ts → router-C57y-vz1.d.ts} +12 -0
  27. package/dist/{router-D5B0cpVN.d.ts → router-CX5uVjmU.d.ts} +29 -1
  28. package/dist/server/index.d.ts +3 -3
  29. package/dist/server/index.js +130 -39
  30. package/dist/server/index.js.map +1 -1
  31. package/dist/state/client.d.ts +73 -11
  32. package/dist/state/client.js +231 -31
  33. package/dist/state/client.js.map +1 -1
  34. package/dist/state/index.js +19 -4
  35. package/dist/state/index.js.map +1 -1
  36. package/dist/state/react.d.ts +18 -3
  37. package/dist/state/react.js +11 -5
  38. package/dist/state/react.js.map +1 -1
  39. package/dist/surface/index.d.ts +3 -3
  40. package/dist/surface/index.js +15 -5
  41. package/dist/surface/index.js.map +1 -1
  42. package/dist/{types-04gu3uMj.d.ts → types-CurMab2Q.d.ts} +19 -2
  43. package/dist/{types-CqTsDETV.d.ts → types-DncqXfWN.d.ts} +1 -1
  44. package/package.json +1 -1
  45. package/src/codegen/README.md +89 -2
  46. package/src/db/transaction/README.md +58 -35
  47. package/src/event/README.md +61 -14
  48. package/src/state/README.md +228 -35
@@ -429,7 +429,15 @@ function loadEnvFiles() {
429
429
  function createBoundedWriter(stream, maxQueue, onClose) {
430
430
  const pending = [];
431
431
  let flushing = false;
432
+ let reserved = 0;
432
433
  let closed = false;
434
+ const overflows = () => {
435
+ if (pending.length + reserved <= maxQueue) {
436
+ return false;
437
+ }
438
+ onClose("outbound queue overflow (slow consumer)");
439
+ return true;
440
+ };
433
441
  const flush = async () => {
434
442
  if (flushing || closed) {
435
443
  return;
@@ -453,11 +461,19 @@ function createBoundedWriter(stream, maxQueue, onClose) {
453
461
  return;
454
462
  }
455
463
  pending.push(frame);
456
- if (pending.length > maxQueue) {
457
- onClose("outbound queue overflow (slow consumer)");
458
- return;
464
+ if (!overflows()) {
465
+ void flush();
466
+ }
467
+ },
468
+ reserve() {
469
+ if (closed) {
470
+ return false;
459
471
  }
460
- void flush();
472
+ reserved++;
473
+ return !overflows();
474
+ },
475
+ clearReserved() {
476
+ reserved = 0;
461
477
  },
462
478
  close() {
463
479
  closed = true;
@@ -709,7 +725,7 @@ async function authorizeConnection(c, router, authConfig, subject, requested) {
709
725
  subject: subject || void 0,
710
726
  clientIp: c.req.header("x-forwarded-for") || c.req.header("x-real-ip")
711
727
  });
712
- return { subject, requested, allowed, denied, request };
728
+ return { subject, requested, allowed, denied, request, principal: resolved.principal };
713
729
  }
714
730
  async function resolveConnectionPrincipal(subject, request, authConfig) {
715
731
  if (!subject || !authConfig?.resolvePrincipal) {
@@ -765,6 +781,16 @@ async function runStream(c, stream, admission, config) {
765
781
  sseLogger.warn("SSE connection closed", { events: [...connection.allowed], reason });
766
782
  cleanup(reason);
767
783
  });
784
+ const hold = createFrameHold(writer);
785
+ const subscriberContext = {
786
+ router: config.router,
787
+ authConfig: config.authConfig,
788
+ subject,
789
+ request,
790
+ writeFrame: createFrameWriter(writer),
791
+ send: hold.send,
792
+ isDead: () => connectionDead
793
+ };
768
794
  const connection = {
769
795
  subject,
770
796
  requested: admission.requested,
@@ -772,45 +798,61 @@ async function runStream(c, stream, admission, config) {
772
798
  denied: new Set(denied),
773
799
  unsubscribes: /* @__PURE__ */ new Map(),
774
800
  request,
775
- subscribe: createEventSubscriber({
776
- router: config.router,
777
- authConfig: config.authConfig,
778
- subject,
779
- request,
780
- writer,
781
- isDead: () => connectionDead
782
- }),
801
+ subscribe: createEventSubscriber(subscriberContext),
783
802
  notify: (frame, eventName) => {
784
803
  if (connectionDead) return;
785
- writer.enqueue({ event: frame, data: JSON.stringify({ event: eventName }) });
804
+ hold.send(() => writer.enqueue({ event: frame, data: JSON.stringify({ event: eventName }) }));
786
805
  }
787
806
  };
788
807
  if (admission.pending?.open(connection) === false) {
789
808
  cleanup("aborted during admission");
790
809
  return;
791
810
  }
792
- openSubscriptions(connection, writer);
811
+ void openSubscriptions(connection, writer, hold, {
812
+ ...subscriberContext,
813
+ principal: admission.principal
814
+ });
793
815
  pingTimer = setInterval(() => {
794
816
  if (connectionDead) return;
795
- writer.enqueue({
817
+ hold.send(() => writer.enqueue({
796
818
  event: "ping",
797
819
  data: JSON.stringify({ timestamp: Date.now() })
798
- });
820
+ }));
799
821
  }, config.pingInterval);
800
822
  await waitForDisconnect(c.req.raw.signal, () => connectionDead, (resolve2) => {
801
823
  onClosed = resolve2;
802
824
  });
803
825
  cleanup();
804
826
  }
805
- function openSubscriptions(connection, writer) {
827
+ async function openSubscriptions(connection, writer, hold, ctx) {
828
+ const connected = { subscribedEvents: [...connection.allowed], deniedEvents: [...connection.denied] };
829
+ const producing = connected.subscribedEvents.filter((eventName) => ctx.router.events[eventName]._initial);
830
+ if (!ctx.subject || producing.length === 0) {
831
+ enqueueConnected(writer, { ...connected, initial: [] });
832
+ subscribeAllowed(connection);
833
+ return;
834
+ }
835
+ hold.hold();
836
+ subscribeAllowed(connection);
837
+ const served = await produceInitialFrames(producing, ctx.router, {
838
+ subject: ctx.subject,
839
+ principal: ctx.principal,
840
+ request: ctx.request
841
+ });
842
+ if (ctx.isDead()) {
843
+ return;
844
+ }
845
+ enqueueConnected(writer, { ...connected, initial: served.map(({ eventName }) => eventName) });
846
+ served.forEach(({ eventName, frames }) => frames.forEach((data) => ctx.writeFrame(eventName, data)));
847
+ hold.release();
848
+ }
849
+ function enqueueConnected(writer, connected) {
806
850
  writer.enqueue({
807
851
  event: "connected",
808
- data: JSON.stringify({
809
- subscribedEvents: [...connection.allowed],
810
- deniedEvents: [...connection.denied],
811
- timestamp: Date.now()
812
- })
852
+ data: JSON.stringify({ ...connected, timestamp: Date.now() })
813
853
  });
854
+ }
855
+ function subscribeAllowed(connection) {
814
856
  for (const eventName of connection.allowed) {
815
857
  connection.unsubscribes.set(eventName, connection.subscribe(eventName));
816
858
  }
@@ -820,6 +862,54 @@ function openSubscriptions(connection, writer) {
820
862
  subscriptionCount: connection.unsubscribes.size
821
863
  });
822
864
  }
865
+ async function produceInitialFrames(events, router, ctx) {
866
+ const results = await Promise.all(events.map((eventName) => askInitial(eventName, router.events[eventName], ctx)));
867
+ return results.filter((frames) => frames !== null);
868
+ }
869
+ async function askInitial(eventName, eventDef, ctx) {
870
+ try {
871
+ const payloads = await eventDef._initial(ctx);
872
+ return { eventName, frames: payloads.map((payload) => serializeFrame(eventName, payload)) };
873
+ } catch {
874
+ sseLogger.warn("SSE initial frames could not be produced \u2014 none sent for this event", {
875
+ event: eventName,
876
+ subject: ctx.subject
877
+ });
878
+ return null;
879
+ }
880
+ }
881
+ function createFrameHold(writer) {
882
+ let pending = null;
883
+ return {
884
+ send: (write) => {
885
+ if (!pending) {
886
+ write();
887
+ } else if (writer.reserve()) {
888
+ pending.push(write);
889
+ }
890
+ },
891
+ hold: () => {
892
+ pending = [];
893
+ },
894
+ release: () => {
895
+ const held = pending ?? [];
896
+ pending = null;
897
+ writer.clearReserved();
898
+ held.forEach((write) => write());
899
+ }
900
+ };
901
+ }
902
+ function createFrameWriter(writer) {
903
+ let messageId = 0;
904
+ return (eventName, data) => {
905
+ messageId++;
906
+ sseLogger.debug("SSE sending event", {
907
+ event: eventName,
908
+ messageId
909
+ });
910
+ writer.enqueue({ id: String(messageId), event: eventName, data });
911
+ };
912
+ }
823
913
  async function waitForDisconnect(abortSignal, isDead, onServerClose) {
824
914
  if (abortSignal.aborted || isDead()) {
825
915
  return;
@@ -834,7 +924,6 @@ async function waitForDisconnect(abortSignal, isDead, onServerClose) {
834
924
  });
835
925
  }
836
926
  function createEventSubscriber(ctx) {
837
- let messageId = 0;
838
927
  return (eventName) => {
839
928
  const eventDef = ctx.router.events[eventName];
840
929
  let active = true;
@@ -842,16 +931,8 @@ function createEventSubscriber(ctx) {
842
931
  if (!active || ctx.isDead() || !passesFrameFilters(eventName, eventDef, payload, ctx)) {
843
932
  return;
844
933
  }
845
- messageId++;
846
- sseLogger.debug("SSE sending event", {
847
- event: eventName,
848
- messageId
849
- });
850
- ctx.writer.enqueue({
851
- id: String(messageId),
852
- event: eventName,
853
- data: serializeFrame(eventName, payload)
854
- });
934
+ const data = serializeFrame(eventName, payload);
935
+ ctx.send(() => ctx.writeFrame(eventName, data));
855
936
  });
856
937
  return () => {
857
938
  active = false;
@@ -2088,7 +2169,7 @@ var route = {
2088
2169
 
2089
2170
  // src/route/router.ts
2090
2171
  function createRouterInstance(state2) {
2091
- return {
2172
+ const router = {
2092
2173
  routes: state2.routes,
2093
2174
  _routes: state2.routes,
2094
2175
  _packageRouters: state2.packageRouters,
@@ -2103,19 +2184,29 @@ function createRouterInstance(state2) {
2103
2184
  }
2104
2185
  }
2105
2186
  assertDistinctSurfaces(newPackageRouters);
2106
- return createRouterInstance({ ...state2, packageRouters: newPackageRouters });
2187
+ return carryMarkers(router, createRouterInstance({ ...state2, packageRouters: newPackageRouters }));
2107
2188
  },
2108
2189
  use(middlewares) {
2109
- return createRouterInstance({
2190
+ return carryMarkers(router, createRouterInstance({
2110
2191
  ...state2,
2111
2192
  globalMiddlewares: [...state2.globalMiddlewares, ...middlewares]
2112
- });
2193
+ }));
2113
2194
  },
2114
2195
  contractVersion(version) {
2115
2196
  assertContractVersion(version);
2116
- return createRouterInstance({ ...state2, contractVersion: version });
2197
+ return carryMarkers(router, createRouterInstance({ ...state2, contractVersion: version }));
2117
2198
  }
2118
2199
  };
2200
+ return router;
2201
+ }
2202
+ var MARKERS = ["_surfaceNamespace", "_stateManifest"];
2203
+ function carryMarkers(from, to) {
2204
+ for (const marker of MARKERS) {
2205
+ if (from[marker] !== void 0) {
2206
+ Object.defineProperty(to, marker, { value: from[marker], enumerable: false });
2207
+ }
2208
+ }
2209
+ return to;
2119
2210
  }
2120
2211
  function assertContractVersion(version) {
2121
2212
  if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)*$/.test(version)) {