@camstack/server 1.2.404 → 1.2.406
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/api/core/event-bus-proxy.router.js +11 -4
- package/dist/api/core/live-events.router.js +16 -6
- package/dist/api/core/public-event-filter.js +26 -0
- package/dist/api/core/system-events.router.js +10 -6
- package/dist/api/trpc/generated-cap-routers.js +54 -0
- package/dist/core/moleculer/moleculer.service.js +29 -0
- package/package.json +7 -7
|
@@ -26,6 +26,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
26
26
|
exports.createEventBusProxyRouter = createEventBusProxyRouter;
|
|
27
27
|
const zod_1 = require("zod");
|
|
28
28
|
const trpc_middleware_js_1 = require("../trpc/trpc.middleware.js");
|
|
29
|
+
const principal_visibility_js_1 = require("../trpc/principal-visibility.js");
|
|
30
|
+
const public_event_filter_js_1 = require("./public-event-filter.js");
|
|
29
31
|
/**
|
|
30
32
|
* Upper bound on the `getRecent` limit — kept in step with the hub's
|
|
31
33
|
* `recent[]` ring-buffer size (`MAX_RECENT_EVENTS` in `event-bus-core.ts`).
|
|
@@ -111,10 +113,15 @@ function createEventBusProxyRouter(eventBus) {
|
|
|
111
113
|
getRecent: trpc_middleware_js_1.protectedProcedure
|
|
112
114
|
.input(GetRecentInputSchema)
|
|
113
115
|
.output(zod_1.z.array(RecentEventOutputSchema))
|
|
114
|
-
.query(({ input }) => {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
.query(({ input, ctx }) => {
|
|
117
|
+
// This router is on the PUBLIC app router too, not only the worker
|
|
118
|
+
// bridge: the same two projections as `systemEvents.getRecent` apply —
|
|
119
|
+
// owner-only categories are never served (D224) and a device-restricted
|
|
120
|
+
// scoped token reads only its devices (F0.5). A worker reaches this over
|
|
121
|
+
// the mesh context (`isAdmin: true`), so it skips only the SCOPE
|
|
122
|
+
// projection — owner-only categories are stripped for every caller.
|
|
123
|
+
const keep = (0, principal_visibility_js_1.scopedEventVisibility)(ctx.user, ctx.deviceScopeLookup);
|
|
124
|
+
return (0, public_event_filter_js_1.publicRecent)(eventBus, input.category !== undefined ? { category: input.category } : undefined, input.limit, keep).map(serializeRecentEvent);
|
|
118
125
|
}),
|
|
119
126
|
});
|
|
120
127
|
}
|
|
@@ -11,6 +11,7 @@ const zod_1 = require("zod");
|
|
|
11
11
|
const trpc_middleware_js_1 = require("../trpc/trpc.middleware.js");
|
|
12
12
|
const share_view_access_js_1 = require("../trpc/share-view-access.js");
|
|
13
13
|
const principal_visibility_js_1 = require("../trpc/principal-visibility.js");
|
|
14
|
+
const public_event_filter_js_1 = require("./public-event-filter.js");
|
|
14
15
|
// The pushed wire shape is `SystemEvent` from @camstack/types — the exact
|
|
15
16
|
// type `EventBusService.subscribe` hands its handlers. It MUST be a type
|
|
16
17
|
// exported from @camstack/types (not a local interface): the AppRouter
|
|
@@ -27,14 +28,15 @@ function createLiveEventsRouter(eb, ar) {
|
|
|
27
28
|
})
|
|
28
29
|
.optional())
|
|
29
30
|
.query(({ input, ctx }) => {
|
|
30
|
-
const recent = eb.getRecent(input ?? {}, input?.limit ?? 50);
|
|
31
31
|
// SECURITY (F0.5): a device-restricted scoped token must not read the
|
|
32
32
|
// whole deployment's event history. `scopedEventVisibility` returns null
|
|
33
33
|
// for admins and broad category-view operators (no filtering), or a
|
|
34
34
|
// per-event predicate that keeps only events for devices the caller may
|
|
35
35
|
// view. (Share-view tokens cannot reach this method — not allowlisted.)
|
|
36
|
+
// An owner-only category (D224) is not history anyone may read. Both
|
|
37
|
+
// projections run BEFORE the limit (`publicRecent`).
|
|
36
38
|
const keep = (0, principal_visibility_js_1.scopedEventVisibility)(ctx.user, ctx.deviceScopeLookup);
|
|
37
|
-
return
|
|
39
|
+
return (0, public_event_filter_js_1.publicRecent)(eb, input?.category ? { category: input.category } : {}, input?.limit ?? 50, keep);
|
|
38
40
|
}),
|
|
39
41
|
onEvent: trpc_middleware_js_1.protectedProcedure
|
|
40
42
|
.input(zod_1.z.object({ category: zod_1.z.string().optional() }))
|
|
@@ -52,20 +54,25 @@ function createLiveEventsRouter(eb, ar) {
|
|
|
52
54
|
const filter = {};
|
|
53
55
|
if (input.category)
|
|
54
56
|
filter.category = input.category;
|
|
57
|
+
// Every branch refuses an owner-only category first (D224): a
|
|
58
|
+
// subscriber that names no category gets every PUBLIC one.
|
|
55
59
|
if (shareScope) {
|
|
56
60
|
const allowedDeviceIds = new Set(shareScope.deviceIds);
|
|
57
61
|
return eb.subscribe(filter, (evt) => {
|
|
58
|
-
if ((0, share_view_access_js_1.liveEventInShareScope)(evt, allowedDeviceIds))
|
|
62
|
+
if ((0, public_event_filter_js_1.isPublicEvent)(evt) && (0, share_view_access_js_1.liveEventInShareScope)(evt, allowedDeviceIds))
|
|
59
63
|
push(evt);
|
|
60
64
|
});
|
|
61
65
|
}
|
|
62
66
|
if (scopedKeep) {
|
|
63
67
|
return eb.subscribe(filter, (evt) => {
|
|
64
|
-
if (scopedKeep(evt))
|
|
68
|
+
if ((0, public_event_filter_js_1.isPublicEvent)(evt) && scopedKeep(evt))
|
|
65
69
|
push(evt);
|
|
66
70
|
});
|
|
67
71
|
}
|
|
68
|
-
return eb.subscribe(filter,
|
|
72
|
+
return eb.subscribe(filter, (evt) => {
|
|
73
|
+
if ((0, public_event_filter_js_1.isPublicEvent)(evt))
|
|
74
|
+
push(evt);
|
|
75
|
+
});
|
|
69
76
|
});
|
|
70
77
|
}),
|
|
71
78
|
onDeviceEvent: trpc_middleware_js_1.protectedProcedure
|
|
@@ -93,7 +100,10 @@ function createLiveEventsRouter(eb, ar) {
|
|
|
93
100
|
throw new Error('Access denied');
|
|
94
101
|
}
|
|
95
102
|
}
|
|
96
|
-
return eb.subscribe({ source: { type: 'device', id: input.deviceId } },
|
|
103
|
+
return eb.subscribe({ source: { type: 'device', id: input.deviceId } }, (evt) => {
|
|
104
|
+
if ((0, public_event_filter_js_1.isPublicEvent)(evt))
|
|
105
|
+
push(evt);
|
|
106
|
+
});
|
|
97
107
|
});
|
|
98
108
|
}),
|
|
99
109
|
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isPublicEvent = isPublicEvent;
|
|
4
|
+
exports.publicRecent = publicRecent;
|
|
5
|
+
/**
|
|
6
|
+
* The one predicate every public event procedure applies before an event
|
|
7
|
+
* leaves the hub (D224, D663): an owner-only category — today the native
|
|
8
|
+
* shadow of a claimed cap — is never served, whatever the caller's role or
|
|
9
|
+
* filter. A subscriber that omits `category` gets every PUBLIC category, not
|
|
10
|
+
* every category.
|
|
11
|
+
*
|
|
12
|
+
* `publicRecent` is the history half: it filters BEFORE the limit, so a
|
|
13
|
+
* client asking for N public events gets N — an owner-only row in the window
|
|
14
|
+
* never costs the caller a slot. The ring is bounded (`MAX_RECENT_EVENTS`), so
|
|
15
|
+
* reading it whole and cutting afterwards is one bounded pass.
|
|
16
|
+
*/
|
|
17
|
+
const types_1 = require("@camstack/types");
|
|
18
|
+
function isPublicEvent(evt) {
|
|
19
|
+
return !(0, types_1.isOwnerOnlyEventCategory)(evt.category);
|
|
20
|
+
}
|
|
21
|
+
function publicRecent(source, filter, limit, keep) {
|
|
22
|
+
const visible = source
|
|
23
|
+
.getRecent(filter)
|
|
24
|
+
.filter((evt) => isPublicEvent(evt) && (keep === null || keep(evt)));
|
|
25
|
+
return limit !== undefined ? visible.slice(-limit) : visible;
|
|
26
|
+
}
|
|
@@ -12,6 +12,7 @@ exports.createSystemEventsRouter = createSystemEventsRouter;
|
|
|
12
12
|
const zod_1 = require("zod");
|
|
13
13
|
const trpc_middleware_js_1 = require("../trpc/trpc.middleware.js");
|
|
14
14
|
const principal_visibility_js_1 = require("../trpc/principal-visibility.js");
|
|
15
|
+
const public_event_filter_js_1 = require("./public-event-filter.js");
|
|
15
16
|
function serialize(e) {
|
|
16
17
|
return {
|
|
17
18
|
id: e.id,
|
|
@@ -82,17 +83,18 @@ const SubscribeInputSchema = ScopeFieldsSchema.extend({
|
|
|
82
83
|
function createSystemEventsRouter(eb) {
|
|
83
84
|
return (0, trpc_middleware_js_1.trpcRouter)({
|
|
84
85
|
getRecent: trpc_middleware_js_1.protectedProcedure.input(GetRecentInputSchema).query(({ input, ctx }) => {
|
|
85
|
-
|
|
86
|
+
// SECURITY (F0.5): filter the history to what a device-restricted scoped
|
|
87
|
+
// token may view. null ⇒ admin / broad category-view operator ⇒ unfiltered.
|
|
88
|
+
// An owner-only category (D224) is not history anyone may read. Both
|
|
89
|
+
// projections run BEFORE the limit, so N asked is N public rows answered.
|
|
90
|
+
const keep = (0, principal_visibility_js_1.scopedEventVisibility)(ctx.user, ctx.deviceScopeLookup);
|
|
91
|
+
return (0, public_event_filter_js_1.publicRecent)(eb, {
|
|
86
92
|
...(input.source ? { source: input.source } : {}),
|
|
87
93
|
...(input.agentId ? { agentId: input.agentId } : {}),
|
|
88
94
|
...(input.addonId ? { addonId: input.addonId } : {}),
|
|
89
95
|
...(input.deviceId !== undefined ? { deviceId: input.deviceId } : {}),
|
|
90
96
|
...(input.category ? { category: input.category } : {}),
|
|
91
|
-
}, input.limit);
|
|
92
|
-
// SECURITY (F0.5): filter the history to what a device-restricted scoped
|
|
93
|
-
// token may view. null ⇒ admin / broad category-view operator ⇒ unfiltered.
|
|
94
|
-
const keep = (0, principal_visibility_js_1.scopedEventVisibility)(ctx.user, ctx.deviceScopeLookup);
|
|
95
|
-
return (keep ? recent.filter((evt) => keep(evt)) : recent).map(serialize);
|
|
97
|
+
}, input.limit, keep).map(serialize);
|
|
96
98
|
}),
|
|
97
99
|
subscribe: trpc_middleware_js_1.protectedProcedure.input(SubscribeInputSchema).subscription(({ input, ctx }) => {
|
|
98
100
|
// SECURITY (F0.5): a device-restricted scoped token must not receive every
|
|
@@ -106,6 +108,8 @@ function createSystemEventsRouter(eb) {
|
|
|
106
108
|
...(input.deviceId !== undefined ? { deviceId: input.deviceId } : {}),
|
|
107
109
|
...(input.category ? { category: input.category } : {}),
|
|
108
110
|
}, (event) => {
|
|
111
|
+
if (!(0, public_event_filter_js_1.isPublicEvent)(event))
|
|
112
|
+
return;
|
|
109
113
|
if (!keep || keep(event))
|
|
110
114
|
push(serialize(event));
|
|
111
115
|
});
|
|
@@ -2053,6 +2053,15 @@ function createCapRouter_coreBlocks(getProvider, createRemoteProxy) {
|
|
|
2053
2053
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
2054
2054
|
return p.listCompositions(methodInput);
|
|
2055
2055
|
}),
|
|
2056
|
+
getCustomization: trpc_middleware_js_1.adminProcedure
|
|
2057
|
+
.input(types_34.coreBlocksCapability.methods.getCustomization.input.loose())
|
|
2058
|
+
.output(types_34.coreBlocksCapability.methods.getCustomization.output)
|
|
2059
|
+
.query(async ({ input, ctx }) => {
|
|
2060
|
+
const { nodeId, ...methodInput } = input;
|
|
2061
|
+
const p = resolveProvider('core-blocks', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
2062
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
2063
|
+
return p.getCustomization(methodInput);
|
|
2064
|
+
}),
|
|
2056
2065
|
validateComposition: trpc_middleware_js_1.adminProcedure
|
|
2057
2066
|
.input(types_34.coreBlocksCapability.methods.validateComposition.input.loose())
|
|
2058
2067
|
.output(types_34.coreBlocksCapability.methods.validateComposition.output)
|
|
@@ -3737,6 +3746,51 @@ function createCapRouter_deviceState(getProvider, createRemoteProxy) {
|
|
|
3737
3746
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
3738
3747
|
return p.setCapSlice(methodInput);
|
|
3739
3748
|
}),
|
|
3749
|
+
claimFields: trpc_middleware_js_1.adminProcedure
|
|
3750
|
+
.input(types_48.deviceStateCapability.methods.claimFields.input.loose())
|
|
3751
|
+
.output(types_48.deviceStateCapability.methods.claimFields.output)
|
|
3752
|
+
.mutation(async ({ input, ctx }) => {
|
|
3753
|
+
const { nodeId, ...methodInput } = input;
|
|
3754
|
+
const p = resolveProvider('device-state', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
3755
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
3756
|
+
return p.claimFields(methodInput);
|
|
3757
|
+
}),
|
|
3758
|
+
patchOwnedFields: trpc_middleware_js_1.adminProcedure
|
|
3759
|
+
.input(types_48.deviceStateCapability.methods.patchOwnedFields.input.loose())
|
|
3760
|
+
.output(types_48.deviceStateCapability.methods.patchOwnedFields.output)
|
|
3761
|
+
.mutation(async ({ input, ctx }) => {
|
|
3762
|
+
const { nodeId, ...methodInput } = input;
|
|
3763
|
+
const p = resolveProvider('device-state', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
3764
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
3765
|
+
return p.patchOwnedFields(methodInput);
|
|
3766
|
+
}),
|
|
3767
|
+
releaseClaim: trpc_middleware_js_1.adminProcedure
|
|
3768
|
+
.input(types_48.deviceStateCapability.methods.releaseClaim.input.loose())
|
|
3769
|
+
.output(types_48.deviceStateCapability.methods.releaseClaim.output)
|
|
3770
|
+
.mutation(async ({ input, ctx }) => {
|
|
3771
|
+
const { nodeId, ...methodInput } = input;
|
|
3772
|
+
const p = resolveProvider('device-state', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
3773
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
3774
|
+
return p.releaseClaim(methodInput);
|
|
3775
|
+
}),
|
|
3776
|
+
listClaims: trpc_middleware_js_1.adminProcedure
|
|
3777
|
+
.input(types_48.deviceStateCapability.methods.listClaims.input.loose())
|
|
3778
|
+
.output(types_48.deviceStateCapability.methods.listClaims.output)
|
|
3779
|
+
.query(async ({ input, ctx }) => {
|
|
3780
|
+
const { nodeId, ...methodInput } = input;
|
|
3781
|
+
const p = resolveProvider('device-state', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
3782
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
3783
|
+
return p.listClaims(methodInput);
|
|
3784
|
+
}),
|
|
3785
|
+
getNativeCapSlice: trpc_middleware_js_1.adminProcedure
|
|
3786
|
+
.input(types_48.deviceStateCapability.methods.getNativeCapSlice.input.loose())
|
|
3787
|
+
.output(types_48.deviceStateCapability.methods.getNativeCapSlice.output)
|
|
3788
|
+
.query(async ({ input, ctx }) => {
|
|
3789
|
+
const { nodeId, ...methodInput } = input;
|
|
3790
|
+
const p = resolveProvider('device-state', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
3791
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
3792
|
+
return p.getNativeCapSlice(methodInput);
|
|
3793
|
+
}),
|
|
3740
3794
|
});
|
|
3741
3795
|
}
|
|
3742
3796
|
function createCapRouter_deviceStatus(getProvider, createRemoteProxy) {
|
|
@@ -311,6 +311,12 @@ class MoleculerService {
|
|
|
311
311
|
resolveNative: (capName, deviceId) => this.capabilityService
|
|
312
312
|
.getRegistry()
|
|
313
313
|
?.getNativeProvider(capName, deviceId) ?? null,
|
|
314
|
+
// `getStatus` of a CLAIMED cap (D663, S3) — the hub mirror answers a
|
|
315
|
+
// forked child's `battery.getStatus({ deviceId })` (exporters, the
|
|
316
|
+
// notification center) before the resolver would dispatch it to the
|
|
317
|
+
// owning runner, whose `getStatus` is the native slice. The SAME
|
|
318
|
+
// question the registry's resolvers ask, through the same method.
|
|
319
|
+
claimedStatus: (capName, deviceId) => this.resolveClaimedStatus(capName, deviceId),
|
|
314
320
|
// `nodeIdMode: 'data'` signal: for these caps (`addon-settings`,
|
|
315
321
|
// `addons`, `nodes`, `pipeline-orchestrator`) an inline `args.nodeId`
|
|
316
322
|
// is provider DATA — the hub singleton dispatches internally — never a
|
|
@@ -397,6 +403,10 @@ class MoleculerService {
|
|
|
397
403
|
// onUnownedCall (a pin to ANOTHER node still bypasses the sibling).
|
|
398
404
|
ownNodeId: nodeId,
|
|
399
405
|
onUnownedCall,
|
|
406
|
+
// `getStatus` of a CLAIMED cap is answered by the hub mirror BEFORE the
|
|
407
|
+
// sibling short-circuit can dispatch it to the owning child (D663, S3)
|
|
408
|
+
// — the same resolver the handler above and `callCapabilityOnNode` use.
|
|
409
|
+
claimedStatus: (capName, deviceId) => this.resolveClaimedStatus(capName, deviceId),
|
|
400
410
|
// A collection ARRAY method must reach `onUnownedCall` (which fans it
|
|
401
411
|
// across every provider) instead of being short-circuited to ONE local
|
|
402
412
|
// sibling. Same predicate the aggregate itself uses, so the two agree
|
|
@@ -719,6 +729,17 @@ class MoleculerService {
|
|
|
719
729
|
registerCoreCapService(service) {
|
|
720
730
|
this.brokerSafe.createService(service);
|
|
721
731
|
}
|
|
732
|
+
/**
|
|
733
|
+
* The hub's claim authority (D663, S3): `CapabilityRegistry.resolveClaimedStatus`,
|
|
734
|
+
* which resolves the device's claims first. Every entry that routes a
|
|
735
|
+
* device-scoped `getStatus` by name asks it through `answerClaimedGetStatus`.
|
|
736
|
+
*/
|
|
737
|
+
resolveClaimedStatus(capName, deviceId) {
|
|
738
|
+
const registry = this.capabilityService.getRegistry();
|
|
739
|
+
return registry !== null
|
|
740
|
+
? registry.resolveClaimedStatus(capName, deviceId)
|
|
741
|
+
: Promise.resolve(system_1.UNCLAIMED);
|
|
742
|
+
}
|
|
722
743
|
/**
|
|
723
744
|
* Call a capability method on a specific node.
|
|
724
745
|
*
|
|
@@ -737,6 +758,14 @@ class MoleculerService {
|
|
|
737
758
|
* have run before the resolver was constructed).
|
|
738
759
|
*/
|
|
739
760
|
async callCapabilityOnNode(nodeId, capabilityName, methodName, params) {
|
|
761
|
+
// `getStatus` of a CLAIMED cap — the hub mirror answers (D663, S3). This is
|
|
762
|
+
// the third entry that resolves a cap call by name (the hub's own tRPC with
|
|
763
|
+
// a `nodeId` pin — the generated DeviceProxy always sends one — and every
|
|
764
|
+
// hub in-process addon's `ctx.api` through `deviceCapResolverLink`); a pin
|
|
765
|
+
// does not bypass the claim authority. Same shared function as the other two.
|
|
766
|
+
const claimed = await (0, system_1.answerClaimedGetStatus)((capName, deviceId) => this.resolveClaimedStatus(capName, deviceId), { capName: capabilityName, method: methodName, args: params });
|
|
767
|
+
if (claimed !== null)
|
|
768
|
+
return claimed.slice;
|
|
740
769
|
const resolver = this.resolver;
|
|
741
770
|
if (resolver !== null) {
|
|
742
771
|
// Extract deviceId from params so device-scoped native caps (ptz, motion-zones, …)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.406",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"private": false,
|
|
6
6
|
"files": [
|
|
@@ -37,18 +37,18 @@
|
|
|
37
37
|
]
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@camstack/addon-admin-ui": "1.2.
|
|
41
|
-
"@camstack/addon-agent-ui": "1.2.
|
|
40
|
+
"@camstack/addon-admin-ui": "1.2.283",
|
|
41
|
+
"@camstack/addon-agent-ui": "1.2.154",
|
|
42
42
|
"@camstack/addon-auth": "1.2.123",
|
|
43
43
|
"@camstack/addon-decoder-nodeav": "1.2.115",
|
|
44
44
|
"@camstack/addon-notifiers": "1.2.121",
|
|
45
45
|
"@camstack/addon-pipeline": "1.2.304",
|
|
46
46
|
"@camstack/addon-pipeline-orchestrator": "1.2.261",
|
|
47
|
-
"@camstack/addon-post-analysis": "1.2.
|
|
47
|
+
"@camstack/addon-post-analysis": "1.2.293",
|
|
48
48
|
"@camstack/sdk": "1.2.118",
|
|
49
|
-
"@camstack/system": "1.2.
|
|
50
|
-
"@camstack/types": "1.2.
|
|
51
|
-
"@camstack/ui-library": "1.2.
|
|
49
|
+
"@camstack/system": "1.2.315",
|
|
50
|
+
"@camstack/types": "1.2.269",
|
|
51
|
+
"@camstack/ui-library": "1.2.212",
|
|
52
52
|
"@fastify/compress": "^9.0.0",
|
|
53
53
|
"@fastify/cookie": "^11.0.2",
|
|
54
54
|
"@fastify/cors": "^11.2.0",
|