@camstack/server 1.2.24 → 1.2.26

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.
@@ -322,6 +322,11 @@ async function startAgent(configPath) {
322
322
  const agentCapabilities = [
323
323
  types_2.storageCapability,
324
324
  types_2.storageProviderCapability,
325
+ // The path browser for "add a storage location on this node" — the agent
326
+ // registers the provider since day one, but without DECLARING the cap
327
+ // here the hub's manifest never learns of it and node-routed browse
328
+ // answers "not available on node X" (2026-07-30 fix).
329
+ types_2.filesystemBrowseCapability,
325
330
  types_2.settingsStoreCapability,
326
331
  types_2.logDestinationCapability,
327
332
  types_2.metricsProviderCapability,
@@ -21,6 +21,7 @@
21
21
  */
22
22
  Object.defineProperty(exports, "__esModule", { value: true });
23
23
  exports.COLLECTION_ARRAY_METHODS = void 0;
24
+ exports.aggregateCollectionCall = aggregateCollectionCall;
24
25
  exports.createCapRouterPrimitives = createCapRouterPrimitives;
25
26
  exports.createCapRouterServices = createCapRouterServices;
26
27
  exports.buildRuntimeCapRouters = buildRuntimeCapRouters;
@@ -86,6 +87,38 @@ function resolveCollectionProvider(reg, capName, addonId) {
86
87
  }
87
88
  return aggregate;
88
89
  }
90
+ /**
91
+ * The collection UNION for a cap call arriving OUTSIDE the tRPC router — a
92
+ * forked addon's `ctx.api.<cap>.<method>` reaching the parent's
93
+ * `onUnownedCall`. Returns `null` when `(capName, method)` is not an
94
+ * aggregated collection array-method, so the caller routes normally.
95
+ *
96
+ * Exists because routing resolves ONE provider while this router fans array
97
+ * methods across ALL of them: the two answers disagreed, and for
98
+ * `notification-output` the routed one was empty — which the Notification
99
+ * Center read as "target deleted" and turned into permanent dead-letters for
100
+ * every notification it ever produced (2026-07-30). Sharing
101
+ * {@link resolveCollectionProvider} keeps the two paths from drifting again.
102
+ *
103
+ * `null` on zero providers too: that is the empty-collection fast-path's job
104
+ * (it answers `[]` without routing), not this one's.
105
+ */
106
+ async function aggregateCollectionCall(reg, capName, method, args) {
107
+ if (!reg)
108
+ return null;
109
+ if (reg.getDefinition(capName)?.mode !== 'collection')
110
+ return null;
111
+ if (!exports.COLLECTION_ARRAY_METHODS.get(capName)?.includes(method))
112
+ return null;
113
+ if (reg.getCollectionEntries(capName).length === 0)
114
+ return null;
115
+ const aggregate = resolveCollectionProvider(reg, capName, undefined);
116
+ const fn = aggregate?.[method];
117
+ if (typeof fn !== 'function')
118
+ return null;
119
+ const out = await fn(args);
120
+ return Array.isArray(out) ? out : null;
121
+ }
89
122
  /**
90
123
  * Fan an array-returning method across every provider and flatten the
91
124
  * results — the runtime equivalent of `concatCollection` for the
@@ -1232,6 +1232,31 @@ function createCapRouter_backup(getProvider, createRemoteProxy) {
1232
1232
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
1233
1233
  return p.previewSchedule(methodInput);
1234
1234
  }),
1235
+ listSchedules: trpc_middleware_js_1.adminProcedure
1236
+ .input(zod_1.z.object({ nodeId: zod_1.z.string().optional() }).optional())
1237
+ .output(types_20.backupCapability.methods.listSchedules.output)
1238
+ .query(async ({ input, ctx }) => {
1239
+ const p = resolveProvider('backup', input?.nodeId, () => getProvider(ctx), createRemoteProxy);
1240
+ return p.listSchedules();
1241
+ }),
1242
+ upsertSchedule: trpc_middleware_js_1.adminProcedure
1243
+ .input(types_20.backupCapability.methods.upsertSchedule.input.loose())
1244
+ .output(types_20.backupCapability.methods.upsertSchedule.output)
1245
+ .mutation(async ({ input, ctx }) => {
1246
+ const { nodeId, ...methodInput } = input;
1247
+ const p = resolveProvider('backup', nodeId, () => getProvider(ctx), createRemoteProxy);
1248
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
1249
+ return p.upsertSchedule(methodInput);
1250
+ }),
1251
+ deleteSchedule: trpc_middleware_js_1.adminProcedure
1252
+ .input(types_20.backupCapability.methods.deleteSchedule.input.loose())
1253
+ .output(types_20.backupCapability.methods.deleteSchedule.output)
1254
+ .mutation(async ({ input, ctx }) => {
1255
+ const { nodeId, ...methodInput } = input;
1256
+ const p = resolveProvider('backup', nodeId, () => getProvider(ctx), createRemoteProxy);
1257
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
1258
+ return p.deleteSchedule(methodInput);
1259
+ }),
1235
1260
  });
1236
1261
  }
1237
1262
  function createCapRouter_battery(getProvider, createRemoteProxy) {
@@ -4018,6 +4043,22 @@ function createCapRouter_localNetwork(getProvider, createRemoteProxy) {
4018
4043
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4019
4044
  return p.getConnectionEndpoints(methodInput);
4020
4045
  }),
4046
+ getNotificationEndpoint: trpc_middleware_js_1.protectedProcedure
4047
+ .input(zod_1.z.object({ nodeId: zod_1.z.string().optional() }).optional())
4048
+ .output(types_54.localNetworkCapability.methods.getNotificationEndpoint.output)
4049
+ .query(async ({ input, ctx }) => {
4050
+ const p = resolveProvider('local-network', input?.nodeId, () => getProvider(ctx), createRemoteProxy);
4051
+ return p.getNotificationEndpoint();
4052
+ }),
4053
+ setNotificationEndpoint: trpc_middleware_js_1.protectedProcedure
4054
+ .input(types_54.localNetworkCapability.methods.setNotificationEndpoint.input.loose())
4055
+ .output(types_54.localNetworkCapability.methods.setNotificationEndpoint.output)
4056
+ .mutation(async ({ input, ctx }) => {
4057
+ const { nodeId, ...methodInput } = input;
4058
+ const p = resolveProvider('local-network', nodeId, () => getProvider(ctx), createRemoteProxy);
4059
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
4060
+ return p.setNotificationEndpoint(methodInput);
4061
+ }),
4021
4062
  getAllowedAddresses: trpc_middleware_js_1.protectedProcedure
4022
4063
  .input(zod_1.z.object({ nodeId: zod_1.z.string().optional() }).optional())
4023
4064
  .output(types_54.localNetworkCapability.methods.getAllowedAddresses.output)
@@ -5362,6 +5403,33 @@ function createCapRouter_pipelineAnalytics(getProvider, createRemoteProxy) {
5362
5403
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5363
5404
  return p.deleteDeviceEvents(methodInput);
5364
5405
  }),
5406
+ relocateMedia: trpc_middleware_js_1.adminProcedure
5407
+ .input(types_78.pipelineAnalyticsCapability.methods.relocateMedia.input.loose())
5408
+ .output(types_78.pipelineAnalyticsCapability.methods.relocateMedia.output)
5409
+ .mutation(async ({ input, ctx }) => {
5410
+ const { nodeId, ...methodInput } = input;
5411
+ const p = resolveProvider('pipeline-analytics', nodeId, () => getProvider(ctx), createRemoteProxy);
5412
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5413
+ return p.relocateMedia(methodInput);
5414
+ }),
5415
+ getMediaRelocateStatus: trpc_middleware_js_1.adminProcedure
5416
+ .input(types_78.pipelineAnalyticsCapability.methods.getMediaRelocateStatus.input.loose())
5417
+ .output(types_78.pipelineAnalyticsCapability.methods.getMediaRelocateStatus.output)
5418
+ .query(async ({ input, ctx }) => {
5419
+ const { nodeId, ...methodInput } = input;
5420
+ const p = resolveProvider('pipeline-analytics', nodeId, () => getProvider(ctx), createRemoteProxy);
5421
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5422
+ return p.getMediaRelocateStatus(methodInput);
5423
+ }),
5424
+ cancelMediaRelocate: trpc_middleware_js_1.adminProcedure
5425
+ .input(types_78.pipelineAnalyticsCapability.methods.cancelMediaRelocate.input.loose())
5426
+ .output(types_78.pipelineAnalyticsCapability.methods.cancelMediaRelocate.output)
5427
+ .mutation(async ({ input, ctx }) => {
5428
+ const { nodeId, ...methodInput } = input;
5429
+ const p = resolveProvider('pipeline-analytics', nodeId, () => getProvider(ctx), createRemoteProxy);
5430
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5431
+ return p.cancelMediaRelocate(methodInput);
5432
+ }),
5365
5433
  listOpsLog: trpc_middleware_js_1.adminProcedure
5366
5434
  .input(types_78.pipelineAnalyticsCapability.methods.listOpsLog.input.loose())
5367
5435
  .output(types_78.pipelineAnalyticsCapability.methods.listOpsLog.output)
@@ -6700,6 +6768,51 @@ function createCapRouter_recording(getProvider, createRemoteProxy) {
6700
6768
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
6701
6769
  return p.listOpsLog(methodInput);
6702
6770
  }),
6771
+ renderGif: trpc_middleware_js_1.adminProcedure
6772
+ .input(types_88.recordingCapability.methods.renderGif.input.loose())
6773
+ .output(types_88.recordingCapability.methods.renderGif.output)
6774
+ .mutation(async ({ input, ctx }) => {
6775
+ const { nodeId, ...methodInput } = input;
6776
+ const p = resolveProvider('recording', nodeId, () => getProvider(ctx), createRemoteProxy);
6777
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
6778
+ return p.renderGif(methodInput);
6779
+ }),
6780
+ renderClip: trpc_middleware_js_1.adminProcedure
6781
+ .input(types_88.recordingCapability.methods.renderClip.input.loose())
6782
+ .output(types_88.recordingCapability.methods.renderClip.output)
6783
+ .mutation(async ({ input, ctx }) => {
6784
+ const { nodeId, ...methodInput } = input;
6785
+ const p = resolveProvider('recording', nodeId, () => getProvider(ctx), createRemoteProxy);
6786
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
6787
+ return p.renderClip(methodInput);
6788
+ }),
6789
+ relocateFootage: trpc_middleware_js_1.adminProcedure
6790
+ .input(types_88.recordingCapability.methods.relocateFootage.input.loose())
6791
+ .output(types_88.recordingCapability.methods.relocateFootage.output)
6792
+ .mutation(async ({ input, ctx }) => {
6793
+ const { nodeId, ...methodInput } = input;
6794
+ const p = resolveProvider('recording', nodeId, () => getProvider(ctx), createRemoteProxy);
6795
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
6796
+ return p.relocateFootage(methodInput);
6797
+ }),
6798
+ getRelocateStatus: trpc_middleware_js_1.adminProcedure
6799
+ .input(types_88.recordingCapability.methods.getRelocateStatus.input.loose())
6800
+ .output(types_88.recordingCapability.methods.getRelocateStatus.output)
6801
+ .query(async ({ input, ctx }) => {
6802
+ const { nodeId, ...methodInput } = input;
6803
+ const p = resolveProvider('recording', nodeId, () => getProvider(ctx), createRemoteProxy);
6804
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
6805
+ return p.getRelocateStatus(methodInput);
6806
+ }),
6807
+ cancelRelocate: trpc_middleware_js_1.adminProcedure
6808
+ .input(types_88.recordingCapability.methods.cancelRelocate.input.loose())
6809
+ .output(types_88.recordingCapability.methods.cancelRelocate.output)
6810
+ .mutation(async ({ input, ctx }) => {
6811
+ const { nodeId, ...methodInput } = input;
6812
+ const p = resolveProvider('recording', nodeId, () => getProvider(ctx), createRemoteProxy);
6813
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
6814
+ return p.cancelRelocate(methodInput);
6815
+ }),
6703
6816
  });
6704
6817
  }
6705
6818
  function createCapRouter_recordingExport(getProvider, createRemoteProxy) {
@@ -7562,6 +7675,15 @@ function createCapRouter_streamBroker(getProvider, createRemoteProxy) {
7562
7675
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
7563
7676
  return p.unassignProfile(methodInput);
7564
7677
  }),
7678
+ renderPreBufferClip: trpc_middleware_js_1.adminProcedure
7679
+ .input(types_100.streamBrokerCapability.methods.renderPreBufferClip.input.loose())
7680
+ .output(types_100.streamBrokerCapability.methods.renderPreBufferClip.output)
7681
+ .mutation(async ({ input, ctx }) => {
7682
+ const { nodeId, ...methodInput } = input;
7683
+ const p = resolveProvider('stream-broker', nodeId, () => getProvider(ctx), createRemoteProxy);
7684
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
7685
+ return p.renderPreBufferClip(methodInput);
7686
+ }),
7565
7687
  listAllCameraStreams: trpc_middleware_js_1.protectedProcedure
7566
7688
  .input(zod_1.z.object({ nodeId: zod_1.z.string().optional() }).optional())
7567
7689
  .output(types_100.streamBrokerCapability.methods.listAllCameraStreams.output)
@@ -2266,7 +2266,9 @@ class AddonRegistryService {
2266
2266
  const provider = this.capabilityRegistry.getProviderByAddon('log-destination', addonId);
2267
2267
  if (!provider)
2268
2268
  return;
2269
- this.loggingService.addDestination(provider);
2269
+ // Tagged with the providing addon so the fan-out can honour the
2270
+ // operator's Enable/Disable toggle for THIS destination.
2271
+ this.loggingService.addDestination(provider, { addonId });
2270
2272
  this.logger.info('Log destination added', { meta: { phase: 'v2' } });
2271
2273
  break;
2272
2274
  }
@@ -2711,6 +2713,9 @@ class AddonRegistryService {
2711
2713
  // deviceId index. The per-device `getBindings` resolver prefers this
2712
2714
  // over filtering the whole-cluster flat view.
2713
2715
  listClusterNativeCapsForDevice: (deviceId) => this.moleculer.listClusterNativeCapsForDevice(deviceId),
2716
+ // Node-routed cap proxy — lets hub builtins (storage-orchestrator)
2717
+ // dispatch provider work to the node that OWNS the data.
2718
+ resolveNodeCapability: (capName, nodeId) => this.moleculer.createCapabilityProxy(capName, nodeId),
2714
2719
  },
2715
2720
  registerProvider,
2716
2721
  resolveProvider: (capName) => {
@@ -290,6 +290,13 @@ class MoleculerService {
290
290
  return null;
291
291
  return [];
292
292
  },
293
+ // Collection FAN-OUT for the same child→parent path: a collection
294
+ // array-method answers with the union across every provider, exactly
295
+ // as this server's tRPC cap-router does (shared code — the two must
296
+ // never disagree again). Routing alone picks ONE provider, which is
297
+ // how the Notification Center saw an empty `notification-output`
298
+ // catalog and dead-lettered every notification (2026-07-30).
299
+ callCollectionAggregate: (capName, method, args) => (0, cap_router_runtime_js_1.aggregateCollectionCall)(this.capabilityService.getRegistry(), capName, method, args),
293
300
  // Hub-core `$`-service discriminant for the general
294
301
  // provider-not-yet-registered recovery. A resolver miss on a NON-core
295
302
  // cap (any addon-provided system/singleton cap, e.g.
@@ -322,6 +329,18 @@ class MoleculerService {
322
329
  // onUnownedCall (a pin to ANOTHER node still bypasses the sibling).
323
330
  ownNodeId: nodeId,
324
331
  onUnownedCall,
332
+ // A collection ARRAY method must reach `onUnownedCall` (which fans it
333
+ // across every provider) instead of being short-circuited to ONE local
334
+ // sibling. Same predicate the aggregate itself uses, so the two agree
335
+ // by construction.
336
+ isAggregatedCollectionMethod: (capName, method) => {
337
+ const reg = this.capabilityService.getRegistry();
338
+ if (!reg)
339
+ return false;
340
+ if (reg.getDefinition(capName)?.mode !== 'collection')
341
+ return false;
342
+ return cap_router_runtime_js_1.COLLECTION_ARRAY_METHODS.get(capName)?.includes(method) === true;
343
+ },
325
344
  logger: {
326
345
  info: (msg, meta) => logger.info(msg, meta !== null && meta !== undefined
327
346
  ? { meta: meta }
@@ -114,6 +114,11 @@ async function bootManual(opts) {
114
114
  // very first DeviceRegistered event populates the cache before any
115
115
  // log line needing `deviceName` is emitted.
116
116
  loggingService.attachDeviceNameStream(eventBusService);
117
+ // The orchestrator's per-destination Enable/Disable toggle becomes the ONE
118
+ // truth for every log destination — it used to be write-only (nothing read
119
+ // the disabled set), so console/winston could not be turned off at all while
120
+ // Loki appeared to work only through a second, private `enabled` setting.
121
+ loggingService.setDestinationEnabledCheck((addonId) => capabilityService.getRegistry()?.isProviderDisabled('log-destination', addonId) !== true);
117
122
  const featureService = new feature_service_1.FeatureService(configService);
118
123
  const authService = new auth_service_1.AuthService(configService);
119
124
  const streamProbeService = new stream_probe_service_1.StreamProbeService(loggingService);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.2.24",
3
+ "version": "1.2.26",
4
4
  "private": false,
5
5
  "files": [
6
6
  "dist",
@@ -33,19 +33,19 @@
33
33
  ]
34
34
  },
35
35
  "dependencies": {
36
- "@camstack/addon-admin-ui": "1.2.15",
37
- "@camstack/addon-agent-ui": "1.2.5",
38
- "@camstack/addon-auth": "1.2.5",
39
- "@camstack/addon-decoder-nodeav": "1.2.5",
40
- "@camstack/addon-notifiers": "1.2.6",
41
- "@camstack/addon-pipeline": "1.2.18",
42
- "@camstack/addon-pipeline-orchestrator": "1.2.10",
43
- "@camstack/addon-post-analysis": "1.2.16",
44
- "@camstack/sdk": "1.2.5",
45
- "@camstack/shm-ring": "1.1.5",
46
- "@camstack/system": "1.2.21",
47
- "@camstack/types": "1.2.15",
48
- "@camstack/ui-library": "1.2.11",
36
+ "@camstack/addon-admin-ui": "1.2.17",
37
+ "@camstack/addon-agent-ui": "1.2.6",
38
+ "@camstack/addon-auth": "1.2.6",
39
+ "@camstack/addon-decoder-nodeav": "1.2.6",
40
+ "@camstack/addon-notifiers": "1.2.8",
41
+ "@camstack/addon-pipeline": "1.2.27",
42
+ "@camstack/addon-pipeline-orchestrator": "1.2.11",
43
+ "@camstack/addon-post-analysis": "1.2.18",
44
+ "@camstack/sdk": "1.2.6",
45
+ "@camstack/shm-ring": "1.1.6",
46
+ "@camstack/system": "1.2.23",
47
+ "@camstack/types": "1.2.17",
48
+ "@camstack/ui-library": "1.2.13",
49
49
  "@fastify/compress": "^9.0.0",
50
50
  "@fastify/cookie": "^11.0.2",
51
51
  "@fastify/cors": "^11.2.0",