@arcote.tech/arc-host 0.7.14 → 0.7.16
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.js +49 -149
- package/dist/index.js.map +5 -5
- package/dist/src/create-server.d.ts.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/middleware/http.d.ts +0 -5
- package/dist/src/middleware/http.d.ts.map +1 -1
- package/dist/src/middleware/index.d.ts +2 -2
- package/dist/src/middleware/index.d.ts.map +1 -1
- package/dist/src/middleware/live-query.integration.test.d.ts +2 -0
- package/dist/src/middleware/live-query.integration.test.d.ts.map +1 -0
- package/dist/src/middleware/ws.d.ts.map +1 -1
- package/dist/src/types.d.ts +20 -2
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/create-server.ts +0 -2
- package/src/index.ts +1 -3
- package/src/middleware/http.ts +0 -119
- package/src/middleware/index.ts +1 -3
- package/src/middleware/live-query.integration.test.ts +432 -0
- package/src/middleware/ws.ts +81 -78
- package/src/types.ts +16 -2
package/dist/index.js
CHANGED
|
@@ -4677,88 +4677,6 @@ function queryHandler(ch) {
|
|
|
4677
4677
|
}
|
|
4678
4678
|
};
|
|
4679
4679
|
}
|
|
4680
|
-
var streamIdCounter = 0;
|
|
4681
|
-
var streamConnections = new Map;
|
|
4682
|
-
function streamHandler(ch) {
|
|
4683
|
-
return (_req, url, ctx) => {
|
|
4684
|
-
if (!url.pathname.startsWith("/stream/") || _req.method !== "GET")
|
|
4685
|
-
return null;
|
|
4686
|
-
const viewName = url.pathname.split("/stream/")[1];
|
|
4687
|
-
if (!viewName) {
|
|
4688
|
-
return new Response("Invalid stream path", {
|
|
4689
|
-
status: 400,
|
|
4690
|
-
headers: ctx.corsHeaders
|
|
4691
|
-
});
|
|
4692
|
-
}
|
|
4693
|
-
const findOptions = {};
|
|
4694
|
-
const whereParam = url.searchParams.get("where");
|
|
4695
|
-
if (whereParam) {
|
|
4696
|
-
try {
|
|
4697
|
-
findOptions.where = JSON.parse(whereParam);
|
|
4698
|
-
} catch {
|
|
4699
|
-
return new Response("Invalid 'where' parameter", {
|
|
4700
|
-
status: 400,
|
|
4701
|
-
headers: ctx.corsHeaders
|
|
4702
|
-
});
|
|
4703
|
-
}
|
|
4704
|
-
}
|
|
4705
|
-
const orderByParam = url.searchParams.get("orderBy");
|
|
4706
|
-
if (orderByParam) {
|
|
4707
|
-
try {
|
|
4708
|
-
findOptions.orderBy = JSON.parse(orderByParam);
|
|
4709
|
-
} catch {
|
|
4710
|
-
return new Response("Invalid 'orderBy' parameter", {
|
|
4711
|
-
status: 400,
|
|
4712
|
-
headers: ctx.corsHeaders
|
|
4713
|
-
});
|
|
4714
|
-
}
|
|
4715
|
-
}
|
|
4716
|
-
const limitParam = url.searchParams.get("limit");
|
|
4717
|
-
if (limitParam)
|
|
4718
|
-
findOptions.limit = parseInt(limitParam, 10);
|
|
4719
|
-
const streamId = `stream_${++streamIdCounter}_${Date.now()}`;
|
|
4720
|
-
const rawToken = ctx.rawToken;
|
|
4721
|
-
const stream = new ReadableStream({
|
|
4722
|
-
start(controller) {
|
|
4723
|
-
const scoped = new ScopedModel2(ch.getModel(), "stream");
|
|
4724
|
-
if (rawToken)
|
|
4725
|
-
scoped.setToken(rawToken);
|
|
4726
|
-
const descriptor = { element: viewName, method: "find", args: [findOptions] };
|
|
4727
|
-
const sendData = async () => {
|
|
4728
|
-
try {
|
|
4729
|
-
const data = await scoped.callQuery(descriptor);
|
|
4730
|
-
controller.enqueue(new TextEncoder().encode(`data: ${JSON.stringify({ type: "data", data })}
|
|
4731
|
-
|
|
4732
|
-
`));
|
|
4733
|
-
} catch {
|
|
4734
|
-
unsubscribe();
|
|
4735
|
-
}
|
|
4736
|
-
};
|
|
4737
|
-
sendData();
|
|
4738
|
-
const unsubscribe = ch.getEventPublisher().subscribe("*", () => sendData());
|
|
4739
|
-
streamConnections.set(streamId, { id: streamId, controller, unsubscribe });
|
|
4740
|
-
controller.enqueue(new TextEncoder().encode(`data: ${JSON.stringify({ type: "connected", streamId })}
|
|
4741
|
-
|
|
4742
|
-
`));
|
|
4743
|
-
},
|
|
4744
|
-
cancel() {
|
|
4745
|
-
const conn = streamConnections.get(streamId);
|
|
4746
|
-
if (conn) {
|
|
4747
|
-
conn.unsubscribe();
|
|
4748
|
-
streamConnections.delete(streamId);
|
|
4749
|
-
}
|
|
4750
|
-
}
|
|
4751
|
-
});
|
|
4752
|
-
return new Response(stream, {
|
|
4753
|
-
headers: {
|
|
4754
|
-
...ctx.corsHeaders,
|
|
4755
|
-
"Content-Type": "text/event-stream",
|
|
4756
|
-
"Cache-Control": "no-cache",
|
|
4757
|
-
Connection: "keep-alive"
|
|
4758
|
-
}
|
|
4759
|
-
});
|
|
4760
|
-
};
|
|
4761
|
-
}
|
|
4762
4680
|
function eventSyncHandler(ch) {
|
|
4763
4681
|
return async (req, url, ctx) => {
|
|
4764
4682
|
if (url.pathname !== "/sync/events" || req.method !== "POST")
|
|
@@ -4859,25 +4777,19 @@ function arcHttpHandlers(ch, cm) {
|
|
|
4859
4777
|
healthHandler(cm),
|
|
4860
4778
|
commandHandler(ch),
|
|
4861
4779
|
queryHandler(ch),
|
|
4862
|
-
streamHandler(ch),
|
|
4863
4780
|
eventSyncHandler(ch),
|
|
4864
4781
|
routeHandler(ch)
|
|
4865
4782
|
];
|
|
4866
4783
|
}
|
|
4867
|
-
function cleanupStreams() {
|
|
4868
|
-
for (const conn of streamConnections.values())
|
|
4869
|
-
conn.unsubscribe();
|
|
4870
|
-
streamConnections.clear();
|
|
4871
|
-
}
|
|
4872
4784
|
// src/middleware/ws.ts
|
|
4873
|
-
import {
|
|
4874
|
-
var
|
|
4785
|
+
import { LiveQuery } from "@arcote.tech/arc";
|
|
4786
|
+
var clientQuerySubs = new Map;
|
|
4875
4787
|
function cleanupClientSubs(clientId) {
|
|
4876
|
-
const subs =
|
|
4788
|
+
const subs = clientQuerySubs.get(clientId);
|
|
4877
4789
|
if (subs) {
|
|
4878
|
-
for (const
|
|
4879
|
-
|
|
4880
|
-
|
|
4790
|
+
for (const live of subs.values())
|
|
4791
|
+
live.stop();
|
|
4792
|
+
clientQuerySubs.delete(clientId);
|
|
4881
4793
|
}
|
|
4882
4794
|
}
|
|
4883
4795
|
function scopeAuthHandler() {
|
|
@@ -4903,6 +4815,8 @@ function syncEventsHandler() {
|
|
|
4903
4815
|
for (const c of ctx.connectionManager.getAllClients()) {
|
|
4904
4816
|
if (c.id === client.id)
|
|
4905
4817
|
continue;
|
|
4818
|
+
if (!c.wantsEventSync)
|
|
4819
|
+
continue;
|
|
4906
4820
|
const clientTokens = ctx.connectionManager.getAllScopeTokens(c.id);
|
|
4907
4821
|
const authorized = filterEventsForTokens(clientTokens, persisted, ctx.contextHandler.getEventDefinitions());
|
|
4908
4822
|
if (authorized.length > 0) {
|
|
@@ -4925,6 +4839,7 @@ function requestSyncHandler() {
|
|
|
4925
4839
|
return async (client, message, ctx) => {
|
|
4926
4840
|
if (message.type !== "request-sync")
|
|
4927
4841
|
return false;
|
|
4842
|
+
client.wantsEventSync = true;
|
|
4928
4843
|
const allTokens = ctx.connectionManager.getAllScopeTokens(client.id);
|
|
4929
4844
|
const token = allTokens.length > 0 ? allTokens[0] : null;
|
|
4930
4845
|
const events = await ctx.contextHandler.getEventsSince(message.lastHostEventId, token);
|
|
@@ -4976,70 +4891,58 @@ function executeCommandHandler() {
|
|
|
4976
4891
|
function querySubscriptionHandler() {
|
|
4977
4892
|
return async (client, message, ctx) => {
|
|
4978
4893
|
if (message.type === "subscribe-query") {
|
|
4979
|
-
const { subscriptionId, descriptor
|
|
4980
|
-
const
|
|
4894
|
+
const { subscriptionId, descriptor } = message;
|
|
4895
|
+
const scope = message.scope ?? "default";
|
|
4896
|
+
const scopeToken = client.scopeTokens.get(scope) ?? null;
|
|
4981
4897
|
let rawToken = scopeToken?.raw ?? null;
|
|
4982
4898
|
if (!rawToken && client.scopeTokens.size > 0) {
|
|
4983
4899
|
rawToken = client.scopeTokens.values().next().value.raw;
|
|
4984
4900
|
}
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
let lastResultJson = "";
|
|
4989
|
-
const sendData = async () => {
|
|
4990
|
-
try {
|
|
4991
|
-
const data = await scoped.callQuery(descriptor);
|
|
4992
|
-
const json = JSON.stringify(data ?? null);
|
|
4993
|
-
if (json === lastResultJson)
|
|
4994
|
-
return;
|
|
4995
|
-
lastResultJson = json;
|
|
4901
|
+
clientQuerySubs.get(client.id)?.get(subscriptionId)?.stop();
|
|
4902
|
+
const live = new LiveQuery(ctx.contextHandler.getModel(), descriptor, scope, rawToken, (update) => {
|
|
4903
|
+
if (update.type === "changes") {
|
|
4996
4904
|
ctx.connectionManager.sendToClient(client.id, {
|
|
4997
|
-
type: "query-
|
|
4905
|
+
type: "query-changes",
|
|
4998
4906
|
subscriptionId,
|
|
4999
|
-
|
|
4907
|
+
changes: update.changes
|
|
4908
|
+
});
|
|
4909
|
+
} else {
|
|
4910
|
+
ctx.connectionManager.sendToClient(client.id, {
|
|
4911
|
+
type: "query-snapshot",
|
|
4912
|
+
subscriptionId,
|
|
4913
|
+
result: update.result ?? null
|
|
5000
4914
|
});
|
|
5001
|
-
} catch (err) {
|
|
5002
|
-
console.error(`[Arc] Query subscription error:`, err);
|
|
5003
|
-
}
|
|
5004
|
-
};
|
|
5005
|
-
sendData();
|
|
5006
|
-
const element = ctx.contextHandler.getModel().context.get(descriptor.element);
|
|
5007
|
-
const handlers = element?.getHandlers?.();
|
|
5008
|
-
const eventTypes = handlers ? Object.keys(handlers) : [];
|
|
5009
|
-
let debounceTimer;
|
|
5010
|
-
const debouncedSend = () => {
|
|
5011
|
-
if (debounceTimer)
|
|
5012
|
-
clearTimeout(debounceTimer);
|
|
5013
|
-
debounceTimer = setTimeout(() => sendData(), 50);
|
|
5014
|
-
};
|
|
5015
|
-
const unsubscribes = [];
|
|
5016
|
-
if (eventTypes.length > 0) {
|
|
5017
|
-
for (const eventType of eventTypes) {
|
|
5018
|
-
unsubscribes.push(ctx.contextHandler.getEventPublisher().subscribe(eventType, debouncedSend));
|
|
5019
4915
|
}
|
|
5020
|
-
}
|
|
5021
|
-
|
|
4916
|
+
});
|
|
4917
|
+
if (!clientQuerySubs.has(client.id)) {
|
|
4918
|
+
clientQuerySubs.set(client.id, new Map);
|
|
5022
4919
|
}
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
4920
|
+
clientQuerySubs.get(client.id).set(subscriptionId, live);
|
|
4921
|
+
try {
|
|
4922
|
+
const result = await live.start();
|
|
4923
|
+
ctx.connectionManager.sendToClient(client.id, {
|
|
4924
|
+
type: "query-snapshot",
|
|
4925
|
+
subscriptionId,
|
|
4926
|
+
result: result ?? null
|
|
4927
|
+
});
|
|
4928
|
+
live.flush();
|
|
4929
|
+
} catch (err) {
|
|
4930
|
+
live.stop();
|
|
4931
|
+
clientQuerySubs.get(client.id)?.delete(subscriptionId);
|
|
4932
|
+
console.error(`[Arc] Query subscription error (${descriptor?.element}.${descriptor?.method}):`, err);
|
|
4933
|
+
ctx.connectionManager.sendToClient(client.id, {
|
|
4934
|
+
type: "error",
|
|
4935
|
+
message: `Failed to subscribe to query "${descriptor?.element}.${descriptor?.method}"`
|
|
4936
|
+
});
|
|
5031
4937
|
}
|
|
5032
|
-
clientViewSubs.get(client.id).set(subscriptionId, cleanup);
|
|
5033
4938
|
return true;
|
|
5034
4939
|
}
|
|
5035
4940
|
if (message.type === "unsubscribe-query") {
|
|
5036
|
-
const subs =
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
subs.delete(message.subscriptionId);
|
|
5042
|
-
}
|
|
4941
|
+
const subs = clientQuerySubs.get(client.id);
|
|
4942
|
+
const live = subs?.get(message.subscriptionId);
|
|
4943
|
+
if (live) {
|
|
4944
|
+
live.stop();
|
|
4945
|
+
subs.delete(message.subscriptionId);
|
|
5043
4946
|
}
|
|
5044
4947
|
return true;
|
|
5045
4948
|
}
|
|
@@ -5235,7 +5138,6 @@ async function createArcServer(config) {
|
|
|
5235
5138
|
cronScheduler,
|
|
5236
5139
|
stop: () => {
|
|
5237
5140
|
cronScheduler.stop();
|
|
5238
|
-
cleanupStreams();
|
|
5239
5141
|
server.stop();
|
|
5240
5142
|
}
|
|
5241
5143
|
};
|
|
@@ -5249,7 +5151,6 @@ function hostLiveModel(context, dbAdapterFactory) {
|
|
|
5249
5151
|
}
|
|
5250
5152
|
export {
|
|
5251
5153
|
syncEventsHandler,
|
|
5252
|
-
streamHandler,
|
|
5253
5154
|
scopeAuthHandler,
|
|
5254
5155
|
routeHandler,
|
|
5255
5156
|
requestSyncHandler,
|
|
@@ -5263,7 +5164,6 @@ export {
|
|
|
5263
5164
|
eventSyncHandler,
|
|
5264
5165
|
createArcServer,
|
|
5265
5166
|
commandHandler,
|
|
5266
|
-
cleanupStreams,
|
|
5267
5167
|
cleanupClientSubs,
|
|
5268
5168
|
canTokenReceiveEvent,
|
|
5269
5169
|
canTokenEmitEvent,
|
|
@@ -5274,5 +5174,5 @@ export {
|
|
|
5274
5174
|
ConnectionManager
|
|
5275
5175
|
};
|
|
5276
5176
|
|
|
5277
|
-
//# debugId=
|
|
5177
|
+
//# debugId=5FB48DB48B7AE0DA64756E2164756E21
|
|
5278
5178
|
//# sourceMappingURL=index.js.map
|