@arcote.tech/arc-host 0.1.5 → 0.1.6
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/host.d.ts +1 -0
- package/dist/host.d.ts.map +1 -1
- package/dist/index.js +99 -10
- package/host.ts +93 -17
- package/package.json +1 -1
package/dist/host.d.ts
CHANGED
package/dist/host.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../host.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,aAAa,EAElB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EAGvB,KAAK,4BAA4B,EAClC,MAAM,kBAAkB,CAAC;AAI1B,cAAM,OAAQ,YAAW,4BAA4B;IAMjD,OAAO,CAAC,OAAO;IALjB,OAAO,CAAC,MAAM,CAAU;IACxB,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,KAAK,CAAuB;gBAG1B,OAAO,EAAE,aAAa,EAC9B,SAAS,EAAE,OAAO,CAAC,eAAe,CAAC;IASrC,aAAa,CAAC,OAAO,EAAE,kBAAkB,EAAE,GAAG,IAAI;IAI5C,IAAI,CACR,gBAAgB,EAAE,CAAC,EACjB,KAAK,EACL,IAAI,GACL,EAAE;QACD,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;KACd,KAAK,IAAI,GACT,OAAO,CAAC,IAAI,CAAC;YAIF,eAAe;IAS7B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAW1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAuB1B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAQ7B;;OAEG;YACW,qBAAqB;IAWnC;;OAEG;IACH,OAAO,CAAC,iBAAiB;YAwCX,aAAa;YAoDb,WAAW;
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../host.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,aAAa,EAElB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EAGvB,KAAK,4BAA4B,EAClC,MAAM,kBAAkB,CAAC;AAI1B,cAAM,OAAQ,YAAW,4BAA4B;IAMjD,OAAO,CAAC,OAAO;IALjB,OAAO,CAAC,MAAM,CAAU;IACxB,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,KAAK,CAAuB;gBAG1B,OAAO,EAAE,aAAa,EAC9B,SAAS,EAAE,OAAO,CAAC,eAAe,CAAC;IASrC,aAAa,CAAC,OAAO,EAAE,kBAAkB,EAAE,GAAG,IAAI;IAI5C,IAAI,CACR,gBAAgB,EAAE,CAAC,EACjB,KAAK,EACL,IAAI,GACL,EAAE;QACD,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;KACd,KAAK,IAAI,GACT,OAAO,CAAC,IAAI,CAAC;YAIF,eAAe;IAS7B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAW1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAuB1B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAQ7B;;OAEG;YACW,qBAAqB;IAWnC;;OAEG;IACH,OAAO,CAAC,iBAAiB;YAwCX,aAAa;YAoDb,WAAW;YAiEX,WAAW;IAgEzB,OAAO,CAAC,WAAW;IAmEnB,OAAO,CAAC,gBAAgB;YA4BV,UAAU;YA+CV,SAAS;IAevB,OAAO,CAAC,cAAc;CAGvB;AAED,eAAO,MAAM,cAAc,YACf,aAAa,aAAa,OAAO,CAAC,eAAe,CAAC,kBAE3D,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -4904,6 +4904,7 @@ var constructorString = Object.prototype.constructor.toString();
|
|
|
4904
4904
|
class ArcContextElement {
|
|
4905
4905
|
$event;
|
|
4906
4906
|
name;
|
|
4907
|
+
queryBuilder;
|
|
4907
4908
|
commandContext;
|
|
4908
4909
|
commandClient;
|
|
4909
4910
|
observer;
|
|
@@ -5632,7 +5633,7 @@ class ArcFindQuery extends ArcCollectionQuery {
|
|
|
5632
5633
|
}
|
|
5633
5634
|
onChange(change) {
|
|
5634
5635
|
const lastResult = this.lastResult;
|
|
5635
|
-
const lastResultAsArray = lastResult
|
|
5636
|
+
const lastResultAsArray = lastResult || [];
|
|
5636
5637
|
const index = lastResultAsArray.findIndex((e) => e._id === (change.type === "delete" ? change.id : change.id));
|
|
5637
5638
|
const isInLastResult = index !== -1;
|
|
5638
5639
|
const shouldBeInTheResult = change.type !== "delete" && this.checkItem(change.item);
|
|
@@ -6654,6 +6655,40 @@ class Model extends ModelBase {
|
|
|
6654
6655
|
}
|
|
6655
6656
|
});
|
|
6656
6657
|
}
|
|
6658
|
+
routes(authContext) {
|
|
6659
|
+
return new Proxy({}, {
|
|
6660
|
+
get: (_, name) => {
|
|
6661
|
+
const element3 = this.context.elements.find((element4) => element4.name === name);
|
|
6662
|
+
if (!element3) {
|
|
6663
|
+
throw new Error(`Route element "${String(name)}" not found`);
|
|
6664
|
+
}
|
|
6665
|
+
if (typeof element3.getHandler !== "function") {
|
|
6666
|
+
throw new Error(`Element "${String(name)}" does not have route handlers`);
|
|
6667
|
+
}
|
|
6668
|
+
return async (method, req, routeParams, url) => {
|
|
6669
|
+
const handler = element3.getHandler(method);
|
|
6670
|
+
if (!handler) {
|
|
6671
|
+
throw new Error(`Method ${method} not allowed for route ${String(name)}`);
|
|
6672
|
+
}
|
|
6673
|
+
const forkedDataStorage = this.dataStorage.fork();
|
|
6674
|
+
const eventPublisher = new EventPublisher(this.context, this.dataStorage, authContext);
|
|
6675
|
+
const publishEvent = async (event3) => {
|
|
6676
|
+
await eventPublisher.publishEvent(event3, forkedDataStorage);
|
|
6677
|
+
};
|
|
6678
|
+
const commandContext = this.context.commandContext(forkedDataStorage, publishEvent, authContext);
|
|
6679
|
+
try {
|
|
6680
|
+
const result = await handler(commandContext, req, routeParams, url);
|
|
6681
|
+
await forkedDataStorage.merge();
|
|
6682
|
+
eventPublisher.runAsyncListeners();
|
|
6683
|
+
return result;
|
|
6684
|
+
} catch (error) {
|
|
6685
|
+
this.catchErrorCallback(error);
|
|
6686
|
+
throw error;
|
|
6687
|
+
}
|
|
6688
|
+
};
|
|
6689
|
+
}
|
|
6690
|
+
});
|
|
6691
|
+
}
|
|
6657
6692
|
get $debug() {
|
|
6658
6693
|
return {};
|
|
6659
6694
|
}
|
|
@@ -6746,7 +6781,7 @@ class RTCHost {
|
|
|
6746
6781
|
}
|
|
6747
6782
|
getDefaultAuthContext(ipAddress) {
|
|
6748
6783
|
return {
|
|
6749
|
-
userId:
|
|
6784
|
+
userId: undefined,
|
|
6750
6785
|
roles: [],
|
|
6751
6786
|
ipAddress
|
|
6752
6787
|
};
|
|
@@ -6874,6 +6909,50 @@ class RTCHost {
|
|
|
6874
6909
|
});
|
|
6875
6910
|
}
|
|
6876
6911
|
}
|
|
6912
|
+
async handleRoute(req) {
|
|
6913
|
+
const url = new URL(req.url);
|
|
6914
|
+
const method = req.method;
|
|
6915
|
+
let matchedRoute = null;
|
|
6916
|
+
let routeParams = {};
|
|
6917
|
+
for (const element of this.context.elements) {
|
|
6918
|
+
if (typeof element.matchesRoutePath === "function") {
|
|
6919
|
+
const { matches, params } = element.matchesRoutePath(url.pathname);
|
|
6920
|
+
if (matches) {
|
|
6921
|
+
matchedRoute = element;
|
|
6922
|
+
routeParams = params || {};
|
|
6923
|
+
break;
|
|
6924
|
+
}
|
|
6925
|
+
}
|
|
6926
|
+
}
|
|
6927
|
+
if (!matchedRoute) {
|
|
6928
|
+
return new Response("Route not found", { status: 404 });
|
|
6929
|
+
}
|
|
6930
|
+
const handler = matchedRoute.getHandler(method);
|
|
6931
|
+
if (!handler) {
|
|
6932
|
+
return new Response(`Method ${method} not allowed`, { status: 405 });
|
|
6933
|
+
}
|
|
6934
|
+
try {
|
|
6935
|
+
const authHeader = req.headers.get("Authorization");
|
|
6936
|
+
const token = authHeader?.replace("Bearer ", "");
|
|
6937
|
+
const clientIp = this.getClientIpAddress(req);
|
|
6938
|
+
let authContext;
|
|
6939
|
+
if (token && !matchedRoute.isPublic) {
|
|
6940
|
+
const payload = await this.verifyAuthToken(token);
|
|
6941
|
+
if (!payload) {
|
|
6942
|
+
return new Response("Invalid or expired token", { status: 401 });
|
|
6943
|
+
}
|
|
6944
|
+
authContext = this.tokenToAuthContext(payload, clientIp);
|
|
6945
|
+
} else {
|
|
6946
|
+
authContext = this.getDefaultAuthContext(clientIp);
|
|
6947
|
+
}
|
|
6948
|
+
const routes = this.model.routes(authContext);
|
|
6949
|
+
const response = await routes[matchedRoute.name](method, req, routeParams, url);
|
|
6950
|
+
return response;
|
|
6951
|
+
} catch (error) {
|
|
6952
|
+
console.error(`Error executing route ${matchedRoute.name}:`, error);
|
|
6953
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
6954
|
+
}
|
|
6955
|
+
}
|
|
6877
6956
|
setupServer() {
|
|
6878
6957
|
this.server = Bun.serve({
|
|
6879
6958
|
fetch: async (req, server) => {
|
|
@@ -6904,6 +6983,10 @@ class RTCHost {
|
|
|
6904
6983
|
if (url.pathname === "/query" && req.method === "POST") {
|
|
6905
6984
|
return await this.handleQuery(req);
|
|
6906
6985
|
}
|
|
6986
|
+
const routeResponse = await this.handleRoute(req);
|
|
6987
|
+
if (routeResponse.status !== 404) {
|
|
6988
|
+
return routeResponse;
|
|
6989
|
+
}
|
|
6907
6990
|
return new Response("Not Found", { status: 404 });
|
|
6908
6991
|
},
|
|
6909
6992
|
websocket: {
|
|
@@ -6919,15 +7002,21 @@ class RTCHost {
|
|
|
6919
7002
|
});
|
|
6920
7003
|
}
|
|
6921
7004
|
isPublicEndpoint(pathname) {
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
7005
|
+
for (const element of this.context.elements) {
|
|
7006
|
+
if (typeof element.matchesCommandPath === "function") {
|
|
7007
|
+
const { matches, isPublic } = element.matchesCommandPath(pathname);
|
|
7008
|
+
if (matches) {
|
|
7009
|
+
return isPublic;
|
|
7010
|
+
}
|
|
7011
|
+
}
|
|
7012
|
+
if (typeof element.matchesRoutePath === "function") {
|
|
7013
|
+
const { matches, isPublic } = element.matchesRoutePath(pathname);
|
|
7014
|
+
if (matches) {
|
|
7015
|
+
return isPublic;
|
|
7016
|
+
}
|
|
7017
|
+
}
|
|
6928
7018
|
}
|
|
6929
|
-
|
|
6930
|
-
return contextElement && "isPublic" in contextElement ? contextElement.isPublic === true : false;
|
|
7019
|
+
return false;
|
|
6931
7020
|
}
|
|
6932
7021
|
async handleSync(lastDate) {
|
|
6933
7022
|
const syncDate = new Date;
|
package/host.ts
CHANGED
|
@@ -102,7 +102,7 @@ class RTCHost implements RealTimeCommunicationAdapter {
|
|
|
102
102
|
*/
|
|
103
103
|
private getDefaultAuthContext(ipAddress?: string): AuthContext {
|
|
104
104
|
return {
|
|
105
|
-
userId:
|
|
105
|
+
userId: undefined as any,
|
|
106
106
|
roles: [],
|
|
107
107
|
ipAddress,
|
|
108
108
|
};
|
|
@@ -282,6 +282,70 @@ class RTCHost implements RealTimeCommunicationAdapter {
|
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
+
private async handleRoute(req: Request) {
|
|
286
|
+
const url = new URL(req.url);
|
|
287
|
+
const method = req.method;
|
|
288
|
+
|
|
289
|
+
// Find matching route
|
|
290
|
+
let matchedRoute: any = null;
|
|
291
|
+
let routeParams: Record<string, string> = {};
|
|
292
|
+
|
|
293
|
+
for (const element of this.context.elements) {
|
|
294
|
+
// Check if element has matchesRoutePath method (ArcRoute)
|
|
295
|
+
if (typeof (element as any).matchesRoutePath === "function") {
|
|
296
|
+
const { matches, params } = (element as any).matchesRoutePath(
|
|
297
|
+
url.pathname,
|
|
298
|
+
);
|
|
299
|
+
if (matches) {
|
|
300
|
+
matchedRoute = element;
|
|
301
|
+
routeParams = params || {};
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (!matchedRoute) {
|
|
308
|
+
return new Response("Route not found", { status: 404 });
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const handler = matchedRoute.getHandler(method);
|
|
312
|
+
if (!handler) {
|
|
313
|
+
return new Response(`Method ${method} not allowed`, { status: 405 });
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
// Extract token from Authorization header
|
|
318
|
+
const authHeader = req.headers.get("Authorization");
|
|
319
|
+
const token = authHeader?.replace("Bearer ", "");
|
|
320
|
+
|
|
321
|
+
const clientIp = this.getClientIpAddress(req);
|
|
322
|
+
let authContext: AuthContext;
|
|
323
|
+
|
|
324
|
+
if (token && !matchedRoute.isPublic) {
|
|
325
|
+
const payload = await this.verifyAuthToken(token);
|
|
326
|
+
if (!payload) {
|
|
327
|
+
return new Response("Invalid or expired token", { status: 401 });
|
|
328
|
+
}
|
|
329
|
+
authContext = this.tokenToAuthContext(payload, clientIp);
|
|
330
|
+
} else {
|
|
331
|
+
authContext = this.getDefaultAuthContext(clientIp);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Use the model's routes method to properly handle event publishing
|
|
335
|
+
const routes = this.model.routes(authContext);
|
|
336
|
+
const response = await routes[matchedRoute.name](
|
|
337
|
+
method,
|
|
338
|
+
req,
|
|
339
|
+
routeParams,
|
|
340
|
+
url,
|
|
341
|
+
);
|
|
342
|
+
return response;
|
|
343
|
+
} catch (error) {
|
|
344
|
+
console.error(`Error executing route ${matchedRoute.name}:`, error);
|
|
345
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
285
349
|
private setupServer() {
|
|
286
350
|
this.server = Bun.serve({
|
|
287
351
|
fetch: async (req, server) => {
|
|
@@ -328,6 +392,12 @@ class RTCHost implements RealTimeCommunicationAdapter {
|
|
|
328
392
|
return await this.handleQuery(req);
|
|
329
393
|
}
|
|
330
394
|
|
|
395
|
+
// Try to handle as a route
|
|
396
|
+
const routeResponse = await this.handleRoute(req);
|
|
397
|
+
if (routeResponse.status !== 404) {
|
|
398
|
+
return routeResponse;
|
|
399
|
+
}
|
|
400
|
+
|
|
331
401
|
return new Response("Not Found", { status: 404 });
|
|
332
402
|
},
|
|
333
403
|
websocket: {
|
|
@@ -344,25 +414,31 @@ class RTCHost implements RealTimeCommunicationAdapter {
|
|
|
344
414
|
}
|
|
345
415
|
|
|
346
416
|
private isPublicEndpoint(pathname: string): boolean {
|
|
347
|
-
//
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
417
|
+
// Iterate through all context elements and check if any match and are public
|
|
418
|
+
for (const element of this.context.elements) {
|
|
419
|
+
// Check if element has matchesCommandPath method (ArcCommand)
|
|
420
|
+
if (typeof (element as any).matchesCommandPath === "function") {
|
|
421
|
+
const { matches, isPublic } = (element as any).matchesCommandPath(
|
|
422
|
+
pathname,
|
|
423
|
+
);
|
|
424
|
+
if (matches) {
|
|
425
|
+
return isPublic;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
351
428
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
429
|
+
// Check if element has matchesRoutePath method (ArcRoute)
|
|
430
|
+
if (typeof (element as any).matchesRoutePath === "function") {
|
|
431
|
+
const { matches, isPublic } = (element as any).matchesRoutePath(
|
|
432
|
+
pathname,
|
|
433
|
+
);
|
|
434
|
+
if (matches) {
|
|
435
|
+
return isPublic;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
355
438
|
}
|
|
356
439
|
|
|
357
|
-
//
|
|
358
|
-
|
|
359
|
-
(element: any) => element.name === commandName,
|
|
360
|
-
);
|
|
361
|
-
|
|
362
|
-
// Check if it's an ArcCommand instance and if it's marked as public
|
|
363
|
-
return contextElement && "isPublic" in contextElement
|
|
364
|
-
? contextElement.isPublic === true
|
|
365
|
-
: false;
|
|
440
|
+
// Default to non-public if no matching element found
|
|
441
|
+
return false;
|
|
366
442
|
}
|
|
367
443
|
|
|
368
444
|
private async handleSync(lastDate: string | null) {
|