@spfn/core 0.3.0-beta.27 → 0.3.0-beta.28
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/contract/index.d.ts +1 -1
- package/dist/db/index.d.ts +39 -11
- package/dist/db/index.js +34 -14
- package/dist/db/index.js.map +1 -1
- package/dist/event/index.d.ts +3 -3
- package/dist/event/index.js +10 -0
- package/dist/event/index.js.map +1 -1
- package/dist/event/sse/client.d.ts +2 -2
- package/dist/event/sse/client.js +27 -17
- package/dist/event/sse/client.js.map +1 -1
- package/dist/event/sse/index.d.ts +3 -3
- package/dist/event/sse/index.js +115 -34
- package/dist/event/sse/index.js.map +1 -1
- package/dist/event/ws/client.d.ts +2 -2
- package/dist/event/ws/index.d.ts +3 -3
- package/dist/{router-D5B0cpVN.d.ts → router-CX5uVjmU.d.ts} +29 -1
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.js +115 -34
- package/dist/server/index.js.map +1 -1
- package/dist/state/client.d.ts +73 -11
- package/dist/state/client.js +231 -31
- package/dist/state/client.js.map +1 -1
- package/dist/state/index.js +11 -1
- package/dist/state/index.js.map +1 -1
- package/dist/state/react.d.ts +18 -3
- package/dist/state/react.js +11 -5
- package/dist/state/react.js.map +1 -1
- package/dist/{types-04gu3uMj.d.ts → types-CurMab2Q.d.ts} +19 -2
- package/dist/{types-CqTsDETV.d.ts → types-DncqXfWN.d.ts} +1 -1
- package/package.json +1 -1
- package/src/db/transaction/README.md +58 -35
- package/src/event/README.md +61 -14
- package/src/state/README.md +161 -35
package/dist/server/index.js
CHANGED
|
@@ -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 (
|
|
457
|
-
|
|
458
|
-
return;
|
|
464
|
+
if (!overflows()) {
|
|
465
|
+
void flush();
|
|
459
466
|
}
|
|
460
|
-
|
|
467
|
+
},
|
|
468
|
+
reserve() {
|
|
469
|
+
if (closed) {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
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
|
-
|
|
846
|
-
|
|
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;
|