@spine-event-engine/server 2.0.0-snapshot.23 → 2.0.0-snapshot.25
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/README.md +38 -11
- package/REFERENCE.md +278 -141
- package/dist/agent/agent-acceptance-receipts.d.ts +69 -0
- package/dist/agent/agent-acceptance-receipts.d.ts.map +1 -0
- package/dist/agent/agent-acceptance-receipts.js +198 -0
- package/dist/agent/agent-acceptance-receipts.js.map +1 -0
- package/dist/agent/agent-execution-capacity.d.ts +37 -9
- package/dist/agent/agent-execution-capacity.d.ts.map +1 -1
- package/dist/agent/agent-execution-capacity.js +73 -15
- package/dist/agent/agent-execution-capacity.js.map +1 -1
- package/dist/agent/agent-history.d.ts +15 -10
- package/dist/agent/agent-history.d.ts.map +1 -1
- package/dist/agent/agent-history.js +26 -14
- package/dist/agent/agent-history.js.map +1 -1
- package/dist/agent/agent-live-ai-audit.d.ts +95 -0
- package/dist/agent/agent-live-ai-audit.d.ts.map +1 -0
- package/dist/agent/agent-live-ai-audit.js +290 -0
- package/dist/agent/agent-live-ai-audit.js.map +1 -0
- package/dist/agent/agent-live-ai-runtime.d.ts +300 -0
- package/dist/agent/agent-live-ai-runtime.d.ts.map +1 -0
- package/dist/agent/agent-live-ai-runtime.js +611 -0
- package/dist/agent/agent-live-ai-runtime.js.map +1 -0
- package/dist/agent/agent-live-audit.d.ts +67 -0
- package/dist/agent/agent-live-audit.d.ts.map +1 -0
- package/dist/agent/agent-live-audit.js +121 -0
- package/dist/agent/agent-live-audit.js.map +1 -0
- package/dist/agent/agent-live-budget.d.ts +84 -0
- package/dist/agent/agent-live-budget.d.ts.map +1 -0
- package/dist/agent/agent-live-budget.js +161 -0
- package/dist/agent/agent-live-budget.js.map +1 -0
- package/dist/agent/agent-live-fault.d.ts +18 -0
- package/dist/agent/agent-live-fault.d.ts.map +1 -0
- package/dist/agent/{agent-execution-fault.js → agent-live-fault.js} +7 -7
- package/dist/agent/agent-live-fault.js.map +1 -0
- package/dist/agent/agent-live-inputs.d.ts +74 -0
- package/dist/agent/agent-live-inputs.d.ts.map +1 -0
- package/dist/agent/agent-live-inputs.js +15 -0
- package/dist/agent/agent-live-inputs.js.map +1 -0
- package/dist/agent/agent-live-interaction-events.d.ts +46 -0
- package/dist/agent/agent-live-interaction-events.d.ts.map +1 -0
- package/dist/agent/agent-live-interaction-events.js +104 -0
- package/dist/agent/agent-live-interaction-events.js.map +1 -0
- package/dist/agent/agent-live-mcp-controls.d.ts +129 -0
- package/dist/agent/agent-live-mcp-controls.d.ts.map +1 -0
- package/dist/agent/agent-live-mcp-controls.js +228 -0
- package/dist/agent/agent-live-mcp-controls.js.map +1 -0
- package/dist/agent/agent-live-mcp-host.d.ts +88 -0
- package/dist/agent/agent-live-mcp-host.d.ts.map +1 -0
- package/dist/agent/{agent-mcp-host.js → agent-live-mcp-host.js} +1 -1
- package/dist/agent/agent-live-mcp-host.js.map +1 -0
- package/dist/agent/{agent-mcp-runtime.d.ts → agent-live-mcp-runtime.d.ts} +6 -6
- package/dist/agent/agent-live-mcp-runtime.d.ts.map +1 -0
- package/dist/agent/{agent-mcp-runtime.js → agent-live-mcp-runtime.js} +13 -42
- package/dist/agent/agent-live-mcp-runtime.js.map +1 -0
- package/dist/agent/agent-live-read-runtime.d.ts +61 -0
- package/dist/agent/agent-live-read-runtime.d.ts.map +1 -0
- package/dist/agent/agent-live-read-runtime.js +121 -0
- package/dist/agent/agent-live-read-runtime.js.map +1 -0
- package/dist/agent/agent-model-selection.d.ts +21 -34
- package/dist/agent/agent-model-selection.d.ts.map +1 -1
- package/dist/agent/agent-model-selection.js +61 -78
- package/dist/agent/agent-model-selection.js.map +1 -1
- package/dist/bus/command-bus.d.ts +14 -18
- package/dist/bus/command-bus.d.ts.map +1 -1
- package/dist/bus/command-bus.js +31 -48
- package/dist/bus/command-bus.js.map +1 -1
- package/dist/bus/event-bus.d.ts +82 -104
- package/dist/bus/event-bus.d.ts.map +1 -1
- package/dist/bus/event-bus.js +87 -127
- package/dist/bus/event-bus.js.map +1 -1
- package/dist/context/bounded-context.d.ts +143 -91
- package/dist/context/bounded-context.d.ts.map +1 -1
- package/dist/context/bounded-context.js +753 -359
- package/dist/context/bounded-context.js.map +1 -1
- package/dist/context/entity-inbox.d.ts +7 -2
- package/dist/context/entity-inbox.d.ts.map +1 -1
- package/dist/context/entity-inbox.js +43 -20
- package/dist/context/entity-inbox.js.map +1 -1
- package/dist/context/local-inbox-handoff.d.ts +35 -34
- package/dist/context/local-inbox-handoff.d.ts.map +1 -1
- package/dist/context/local-inbox-handoff.js +123 -135
- package/dist/context/local-inbox-handoff.js.map +1 -1
- package/dist/context/projection-handoff.d.ts +2 -2
- package/dist/context/projection-handoff.d.ts.map +1 -1
- package/dist/context/projection-handoff.js +12 -8
- package/dist/context/projection-handoff.js.map +1 -1
- package/dist/context/tenant-index.d.ts +36 -31
- package/dist/context/tenant-index.d.ts.map +1 -1
- package/dist/context/tenant-index.js +123 -125
- package/dist/context/tenant-index.js.map +1 -1
- package/dist/context/tenant-unknown-error.d.ts +33 -0
- package/dist/context/tenant-unknown-error.d.ts.map +1 -0
- package/dist/context/tenant-unknown-error.js +75 -0
- package/dist/context/tenant-unknown-error.js.map +1 -0
- package/dist/delivery/agent-delivery-scope.d.ts +66 -0
- package/dist/delivery/agent-delivery-scope.d.ts.map +1 -0
- package/dist/delivery/agent-delivery-scope.js +44 -0
- package/dist/delivery/agent-delivery-scope.js.map +1 -0
- package/dist/delivery/delivery-builder.d.ts +25 -1
- package/dist/delivery/delivery-builder.d.ts.map +1 -1
- package/dist/delivery/delivery-builder.js +89 -0
- package/dist/delivery/delivery-builder.js.map +1 -1
- package/dist/delivery/delivery-monitor.d.ts +12 -9
- package/dist/delivery/delivery-monitor.d.ts.map +1 -1
- package/dist/delivery/delivery-monitor.js +15 -9
- package/dist/delivery/delivery-monitor.js.map +1 -1
- package/dist/delivery/delivery-ports.d.ts +6 -13
- package/dist/delivery/delivery-ports.d.ts.map +1 -1
- package/dist/delivery/delivery-run-control.d.ts +21 -0
- package/dist/delivery/delivery-run-control.d.ts.map +1 -1
- package/dist/delivery/delivery-run-control.js +10 -0
- package/dist/delivery/delivery-run-control.js.map +1 -1
- package/dist/delivery/delivery-supervisor.d.ts +33 -5
- package/dist/delivery/delivery-supervisor.d.ts.map +1 -1
- package/dist/delivery/delivery-supervisor.js +178 -26
- package/dist/delivery/delivery-supervisor.js.map +1 -1
- package/dist/delivery/delivery-tenant.d.ts +17 -0
- package/dist/delivery/delivery-tenant.d.ts.map +1 -0
- package/dist/delivery/delivery-tenant.js +50 -0
- package/dist/delivery/delivery-tenant.js.map +1 -0
- package/dist/delivery/delivery.d.ts +96 -2
- package/dist/delivery/delivery.d.ts.map +1 -1
- package/dist/delivery/delivery.js +803 -89
- package/dist/delivery/delivery.js.map +1 -1
- package/dist/delivery/inbox-records.d.ts.map +1 -1
- package/dist/delivery/inbox-records.js +9 -16
- package/dist/delivery/inbox-records.js.map +1 -1
- package/dist/delivery/inbox-storage.d.ts +14 -11
- package/dist/delivery/inbox-storage.d.ts.map +1 -1
- package/dist/delivery/inbox-storage.js +72 -78
- package/dist/delivery/inbox-storage.js.map +1 -1
- package/dist/delivery/inbox.d.ts +9 -12
- package/dist/delivery/inbox.d.ts.map +1 -1
- package/dist/delivery/inbox.js +13 -15
- package/dist/delivery/inbox.js.map +1 -1
- package/dist/delivery/reception-observation.d.ts +64 -0
- package/dist/delivery/reception-observation.d.ts.map +1 -0
- package/dist/delivery/reception-observation.js +78 -0
- package/dist/delivery/reception-observation.js.map +1 -0
- package/dist/delivery/sharded-work-registry.d.ts +6 -0
- package/dist/delivery/sharded-work-registry.d.ts.map +1 -1
- package/dist/delivery/sharded-work-registry.js +41 -4
- package/dist/delivery/sharded-work-registry.js.map +1 -1
- package/dist/entity/entity-storage-descriptor.d.ts.map +1 -1
- package/dist/entity/entity-storage-descriptor.js +7 -4
- package/dist/entity/entity-storage-descriptor.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/integration/external-messages.d.ts.map +1 -1
- package/dist/integration/external-messages.js +9 -15
- package/dist/integration/external-messages.js.map +1 -1
- package/dist/integration/integration-broker.d.ts.map +1 -1
- package/dist/integration/integration-broker.js +112 -6
- package/dist/integration/integration-broker.js.map +1 -1
- package/dist/integration/third-party-context.d.ts.map +1 -1
- package/dist/integration/third-party-context.js +7 -10
- package/dist/integration/third-party-context.js.map +1 -1
- package/dist/repository/primitive-id.js +26 -11
- package/dist/repository/primitive-id.js.map +1 -1
- package/dist/repository/repository.d.ts +33 -62
- package/dist/repository/repository.d.ts.map +1 -1
- package/dist/repository/repository.js +1475 -1827
- package/dist/repository/repository.js.map +1 -1
- package/dist/runtime/runtime.d.ts +24 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +31 -0
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/signal-metadata.d.ts.map +1 -1
- package/dist/runtime/signal-metadata.js +8 -15
- package/dist/runtime/signal-metadata.js.map +1 -1
- package/dist/runtime/signal-publisher.d.ts +8 -36
- package/dist/runtime/signal-publisher.d.ts.map +1 -1
- package/dist/runtime/signal-publisher.js +68 -61
- package/dist/runtime/signal-publisher.js.map +1 -1
- package/dist/runtime/standalone-handler-runtime.d.ts +0 -7
- package/dist/runtime/standalone-handler-runtime.d.ts.map +1 -1
- package/dist/runtime/standalone-handler-runtime.js +14 -130
- package/dist/runtime/standalone-handler-runtime.js.map +1 -1
- package/dist/server/managed-delivery-gate.d.ts +34 -0
- package/dist/server/managed-delivery-gate.d.ts.map +1 -0
- package/dist/server/managed-delivery-gate.js +70 -0
- package/dist/server/managed-delivery-gate.js.map +1 -0
- package/dist/server/managed-server-application.d.ts +2 -2
- package/dist/server/managed-server-application.d.ts.map +1 -1
- package/dist/server/managed-server-application.js +471 -178
- package/dist/server/managed-server-application.js.map +1 -1
- package/dist/server/node-coordinator.d.ts +14 -0
- package/dist/server/node-coordinator.d.ts.map +1 -1
- package/dist/server/node-coordinator.js +266 -45
- package/dist/server/node-coordinator.js.map +1 -1
- package/dist/server/server-environment.d.ts +203 -25
- package/dist/server/server-environment.d.ts.map +1 -1
- package/dist/server/server-environment.js +661 -133
- package/dist/server/server-environment.js.map +1 -1
- package/dist/server/server-log.d.ts +7 -0
- package/dist/server/server-log.d.ts.map +1 -1
- package/dist/server/server-log.js +18 -1
- package/dist/server/server-log.js.map +1 -1
- package/dist/server/server.d.ts +18 -32
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +62 -203
- package/dist/server/server.js.map +1 -1
- package/dist/services/spine-services.d.ts +1 -1
- package/dist/services/spine-services.d.ts.map +1 -1
- package/dist/services/spine-services.js +399 -370
- package/dist/services/spine-services.js.map +1 -1
- package/dist/stand/event-origin-filter.d.ts +16 -0
- package/dist/stand/event-origin-filter.d.ts.map +1 -0
- package/dist/stand/event-origin-filter.js +124 -0
- package/dist/stand/event-origin-filter.js.map +1 -0
- package/dist/stand/stand.d.ts +14 -0
- package/dist/stand/stand.d.ts.map +1 -1
- package/dist/stand/stand.js +53 -9
- package/dist/stand/stand.js.map +1 -1
- package/dist/stand/subscription-observer.d.ts.map +1 -1
- package/dist/stand/subscription-observer.js +61 -40
- package/dist/stand/subscription-observer.js.map +1 -1
- package/dist/stand/subscription-registry.d.ts +6 -2
- package/dist/stand/subscription-registry.d.ts.map +1 -1
- package/dist/stand/subscription-registry.js +15 -11
- package/dist/stand/subscription-registry.js.map +1 -1
- package/dist/stand/subscription-runtime.d.ts +23 -4
- package/dist/stand/subscription-runtime.d.ts.map +1 -1
- package/dist/stand/subscription-runtime.js +84 -13
- package/dist/stand/subscription-runtime.js.map +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +3 -2
- package/dist/testing/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +15 -15
- package/dist/agent/agent-admission.d.ts +0 -80
- package/dist/agent/agent-admission.d.ts.map +0 -1
- package/dist/agent/agent-admission.js +0 -127
- package/dist/agent/agent-admission.js.map +0 -1
- package/dist/agent/agent-ai-runtime.d.ts +0 -76
- package/dist/agent/agent-ai-runtime.d.ts.map +0 -1
- package/dist/agent/agent-ai-runtime.js +0 -1269
- package/dist/agent/agent-ai-runtime.js.map +0 -1
- package/dist/agent/agent-execution-fault.d.ts +0 -18
- package/dist/agent/agent-execution-fault.d.ts.map +0 -1
- package/dist/agent/agent-execution-fault.js.map +0 -1
- package/dist/agent/agent-execution-session.d.ts +0 -103
- package/dist/agent/agent-execution-session.d.ts.map +0 -1
- package/dist/agent/agent-execution-session.js +0 -238
- package/dist/agent/agent-execution-session.js.map +0 -1
- package/dist/agent/agent-interaction-audit.d.ts +0 -58
- package/dist/agent/agent-interaction-audit.d.ts.map +0 -1
- package/dist/agent/agent-interaction-audit.js +0 -114
- package/dist/agent/agent-interaction-audit.js.map +0 -1
- package/dist/agent/agent-interaction-events.d.ts +0 -40
- package/dist/agent/agent-interaction-events.d.ts.map +0 -1
- package/dist/agent/agent-interaction-events.js +0 -129
- package/dist/agent/agent-interaction-events.js.map +0 -1
- package/dist/agent/agent-mcp-budget.d.ts +0 -125
- package/dist/agent/agent-mcp-budget.d.ts.map +0 -1
- package/dist/agent/agent-mcp-budget.js +0 -566
- package/dist/agent/agent-mcp-budget.js.map +0 -1
- package/dist/agent/agent-mcp-host.d.ts +0 -125
- package/dist/agent/agent-mcp-host.d.ts.map +0 -1
- package/dist/agent/agent-mcp-host.js.map +0 -1
- package/dist/agent/agent-mcp-runtime.d.ts.map +0 -1
- package/dist/agent/agent-mcp-runtime.js.map +0 -1
- package/dist/agent/agent-read-runtime.d.ts +0 -70
- package/dist/agent/agent-read-runtime.d.ts.map +0 -1
- package/dist/agent/agent-read-runtime.js +0 -320
- package/dist/agent/agent-read-runtime.js.map +0 -1
- package/dist/agent/agent-revisions.d.ts +0 -35
- package/dist/agent/agent-revisions.d.ts.map +0 -1
- package/dist/agent/agent-revisions.js +0 -133
- package/dist/agent/agent-revisions.js.map +0 -1
- package/dist/agent/agent-scheduler.d.ts +0 -99
- package/dist/agent/agent-scheduler.d.ts.map +0 -1
- package/dist/agent/agent-scheduler.js +0 -398
- package/dist/agent/agent-scheduler.js.map +0 -1
- package/dist/bus/saved-dispatcher-binding.d.ts +0 -96
- package/dist/bus/saved-dispatcher-binding.d.ts.map +0 -1
- package/dist/bus/saved-dispatcher-binding.js +0 -35
- package/dist/bus/saved-dispatcher-binding.js.map +0 -1
- package/dist/delivery/delivery-run-coordinator.d.ts +0 -218
- package/dist/delivery/delivery-run-coordinator.d.ts.map +0 -1
- package/dist/delivery/delivery-run-coordinator.js +0 -609
- package/dist/delivery/delivery-run-coordinator.js.map +0 -1
- package/dist/delivery/parked-delivery-obligations.d.ts +0 -182
- package/dist/delivery/parked-delivery-obligations.d.ts.map +0 -1
- package/dist/delivery/parked-delivery-obligations.js +0 -390
- package/dist/delivery/parked-delivery-obligations.js.map +0 -1
- package/dist/server/environment-attachment.d.ts +0 -356
- package/dist/server/environment-attachment.d.ts.map +0 -1
- package/dist/server/environment-attachment.js +0 -2062
- package/dist/server/environment-attachment.js.map +0 -1
- package/dist/server/environment-delivery-records.d.ts +0 -77
- package/dist/server/environment-delivery-records.d.ts.map +0 -1
- package/dist/server/environment-delivery-records.js +0 -282
- package/dist/server/environment-delivery-records.js.map +0 -1
- package/dist/server/environment-delivery-worker.d.ts +0 -161
- package/dist/server/environment-delivery-worker.d.ts.map +0 -1
- package/dist/server/environment-delivery-worker.js +0 -673
- package/dist/server/environment-delivery-worker.js.map +0 -1
|
@@ -13,31 +13,33 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { randomUUID } from "node:crypto";
|
|
15
15
|
import { Code, ConnectError } from "@connectrpc/connect";
|
|
16
|
-
import { clone, create, ScalarType
|
|
16
|
+
import { clone, create, ScalarType } from "@bufbuild/protobuf";
|
|
17
17
|
import { AnySchema, BoolValueSchema, BytesValueSchema, DoubleValueSchema, EmptySchema, FloatValueSchema, Int32ValueSchema, Int64ValueSchema, StringValueSchema, UInt32ValueSchema, UInt64ValueSchema, } from "@bufbuild/protobuf/wkt";
|
|
18
|
-
import { TypeUrls,
|
|
19
|
-
import { CommandIdSchema,
|
|
18
|
+
import { TypeUrls, AnyPacker } from "@spine-event-engine/core";
|
|
19
|
+
import { CommandIdSchema, TenantIdSchema, ValidationErrorSchema, VersionSchema, } from "@spine-event-engine/proto";
|
|
20
20
|
import { ErrorSchema } from "@spine-event-engine/proto";
|
|
21
21
|
import { CommandService } from "@spine-event-engine/proto/client";
|
|
22
22
|
import { CompositeFilter_CompositeOperator, Filter_Operator, } from "@spine-event-engine/proto/client";
|
|
23
23
|
import { EntityStateWithVersionSchema as EntityStateVersionSchema, OrderBy_Direction, QueryResponseSchema, } from "@spine-event-engine/proto/client";
|
|
24
24
|
import { QueryService } from "@spine-event-engine/proto/client";
|
|
25
|
-
import {
|
|
25
|
+
import { SubscriptionIdSchema, SubscriptionSchema, SubscriptionUpdateSchema, TopicSchema, } from "@spine-event-engine/proto/client";
|
|
26
26
|
import { SubscriptionService } from "@spine-event-engine/proto/client";
|
|
27
|
+
import { EventOriginFilters } from "../stand/event-origin-filter.js";
|
|
28
|
+
import { TenantAdmissions, TenantUnknownError } from "../context/tenant-unknown-error.js";
|
|
27
29
|
import { AckSchema } from "@spine-event-engine/proto";
|
|
28
30
|
import { ResponseSchema, StatusSchema, } from "@spine-event-engine/proto";
|
|
29
31
|
import {} from "@spine-event-engine/storage";
|
|
30
32
|
import { TenantBoundary } from "@spine-event-engine/storage/provider";
|
|
31
|
-
import { boundedContextAccess } from "../context/bounded-context.js";
|
|
33
|
+
import { BoundedContext, boundedContextAccess } from "../context/bounded-context.js";
|
|
32
34
|
import { EffectiveTenants } from "../context/effective-tenant.js";
|
|
33
35
|
import { CommandValidationError } from "../bus/command-errors.js";
|
|
34
36
|
import { TransitionValidationError } from "../repository/command-errors.js";
|
|
35
37
|
import {} from "../stand/stand.js";
|
|
36
38
|
import { QueryReader } from "./query-reader.js";
|
|
37
39
|
import { RegisteredTargets } from "./registered-targets.js";
|
|
38
|
-
import { emitServerWarning } from "../server/server-log.js";
|
|
40
|
+
import { emitServerError, emitServerWarning } from "../server/server-log.js";
|
|
39
41
|
import { managedChildSubscriptionAccess } from "../server/managed-child-subscription.js";
|
|
40
|
-
import {
|
|
42
|
+
import {} from "../stand/subscription-registry.js";
|
|
41
43
|
const serviceLoggers = new WeakMap();
|
|
42
44
|
const serviceInstances = new WeakSet();
|
|
43
45
|
/**
|
|
@@ -85,7 +87,6 @@ export class SpineServices {
|
|
|
85
87
|
#activationTails = new Map();
|
|
86
88
|
#removals = new Map();
|
|
87
89
|
#unknownRemovals = new Set();
|
|
88
|
-
#testRegistries = new WeakMap();
|
|
89
90
|
#queueLimit;
|
|
90
91
|
#subscriptionLimit;
|
|
91
92
|
/**
|
|
@@ -170,28 +171,14 @@ export class SpineServices {
|
|
|
170
171
|
* @returns Acknowledgement for route validation or dispatch.
|
|
171
172
|
*/
|
|
172
173
|
async #post(command) {
|
|
173
|
-
const messageId = command.id &&
|
|
174
|
+
const messageId = command.id && AnyPacker.pack(CommandIdSchema, command.id);
|
|
174
175
|
const typeUrl = command.message?.typeUrl;
|
|
175
|
-
if (typeUrl === undefined || typeUrl.length === 0)
|
|
176
|
-
return
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
const route = this.#commandRoutes.get(typeUrl);
|
|
182
|
-
if (route === undefined) {
|
|
183
|
-
return create(AckSchema, {
|
|
184
|
-
messageId,
|
|
185
|
-
status: ServiceValues.errorStatus("UNSUPPORTED_COMMAND", "No bounded context accepted the command."),
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
const tenantError = ServiceValues.tenantMismatch(route.context.isMultitenant, ServiceValues.commandTenant(command), "command");
|
|
189
|
-
if (tenantError !== undefined) {
|
|
190
|
-
return create(AckSchema, {
|
|
191
|
-
messageId,
|
|
192
|
-
status: ServiceValues.errorStatus(tenantError.type, tenantError.message),
|
|
193
|
-
});
|
|
194
|
-
}
|
|
176
|
+
if (typeUrl === undefined || typeUrl.length === 0)
|
|
177
|
+
return ServiceValues.commandErrorAck(messageId, "INVALID_COMMAND", "Command message type is required.");
|
|
178
|
+
const admission = this.#commandAdmission(command, messageId, typeUrl);
|
|
179
|
+
if ("ack" in admission)
|
|
180
|
+
return admission.ack;
|
|
181
|
+
const { route } = admission;
|
|
195
182
|
try {
|
|
196
183
|
await route.context.commandBus().post(command);
|
|
197
184
|
return create(AckSchema, {
|
|
@@ -200,13 +187,61 @@ export class SpineServices {
|
|
|
200
187
|
});
|
|
201
188
|
}
|
|
202
189
|
catch (error) {
|
|
190
|
+
if (error instanceof TenantUnknownError)
|
|
191
|
+
throw TenantStatuses.unknown(error);
|
|
203
192
|
const postError = ServiceValues.commandPostError(error);
|
|
193
|
+
this.#logCommandPostFailure(route, command, error);
|
|
204
194
|
return create(AckSchema, {
|
|
205
195
|
messageId,
|
|
206
196
|
status: ServiceValues.errorStatus(postError.type, postError.message, postError.details),
|
|
207
197
|
});
|
|
208
198
|
}
|
|
209
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Checks route and tenant admission before command dispatch.
|
|
202
|
+
*
|
|
203
|
+
* @param command The public Command request.
|
|
204
|
+
* @param messageId The packed Command ID returned in the Ack.
|
|
205
|
+
* @param typeUrl The validated Command message type.
|
|
206
|
+
* @returns A selected route or a stable rejection Ack.
|
|
207
|
+
*/
|
|
208
|
+
#commandAdmission(command, messageId, typeUrl) {
|
|
209
|
+
const route = this.#commandRoutes.get(typeUrl);
|
|
210
|
+
if (route === undefined) {
|
|
211
|
+
return {
|
|
212
|
+
ack: ServiceValues.commandErrorAck(messageId, "UNSUPPORTED_COMMAND", "No bounded context accepted the command."),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
const tenantError = ServiceValues.tenantMismatch(route.context.isMultitenant, ServiceValues.commandTenant(command), "command");
|
|
216
|
+
if (tenantError !== undefined) {
|
|
217
|
+
return {
|
|
218
|
+
ack: ServiceValues.commandErrorAck(messageId, tenantError.type, tenantError.message),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
return { route };
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Records one failed command post with safe correlation fields.
|
|
225
|
+
*
|
|
226
|
+
* @param route The selected bounded context and command type.
|
|
227
|
+
* @param command The failed Command envelope.
|
|
228
|
+
* @param error The internal failure, retained for diagnostic classification only.
|
|
229
|
+
*/
|
|
230
|
+
#logCommandPostFailure(route, command, error) {
|
|
231
|
+
if (error instanceof TransitionValidationError || error instanceof CommandValidationError)
|
|
232
|
+
return;
|
|
233
|
+
const logger = serviceLoggers.get(this);
|
|
234
|
+
if (logger === undefined)
|
|
235
|
+
return;
|
|
236
|
+
emitServerError(logger, "Command post failed.", {
|
|
237
|
+
contextName: route.context.name.value,
|
|
238
|
+
commandId: command.id?.uuid,
|
|
239
|
+
commandType: route.typeUrl,
|
|
240
|
+
operation: "command.post",
|
|
241
|
+
errorName: InternalErrorFacts.name(error),
|
|
242
|
+
errorCode: InternalErrorFacts.code(error),
|
|
243
|
+
});
|
|
244
|
+
}
|
|
210
245
|
/**
|
|
211
246
|
* Validates a public state query before reading its registered Stand.
|
|
212
247
|
*
|
|
@@ -244,7 +279,9 @@ export class SpineServices {
|
|
|
244
279
|
const destination = EffectiveTenants.destination(effective, route.context.isMultitenant);
|
|
245
280
|
return await this.#query(route, ServiceValues.createReadPlan(target, query, route), destination);
|
|
246
281
|
}
|
|
247
|
-
catch {
|
|
282
|
+
catch (error) {
|
|
283
|
+
if (error instanceof TenantUnknownError)
|
|
284
|
+
throw TenantStatuses.unknown(error);
|
|
248
285
|
return ServiceValues.queryErrorResponse("QUERY_READ_ERROR", "Query read failed.");
|
|
249
286
|
}
|
|
250
287
|
}
|
|
@@ -258,10 +295,16 @@ export class SpineServices {
|
|
|
258
295
|
message: results.map((result) => ServiceValues.packVersionedState(route.schema, result)),
|
|
259
296
|
});
|
|
260
297
|
}
|
|
298
|
+
/**
|
|
299
|
+
* Validates a topic synchronously and checks live membership before registry creation.
|
|
300
|
+
*
|
|
301
|
+
* @param topic Requested subscription topic.
|
|
302
|
+
* @returns Created subscription after admission.
|
|
303
|
+
*/
|
|
261
304
|
#subscribe(topic) {
|
|
262
305
|
const route = this.#subscriptionRoute(topic);
|
|
263
306
|
ServiceValues.validateTopic(topic);
|
|
264
|
-
|
|
307
|
+
ServiceValues.createSubscriptionShape(topic, route);
|
|
265
308
|
const tenantId = ServiceValues.topicTenant(topic);
|
|
266
309
|
const tenantError = ServiceValues.tenantMismatch(route.context.isMultitenant, tenantId, "subscription");
|
|
267
310
|
if (tenantError !== undefined) {
|
|
@@ -275,53 +318,24 @@ export class SpineServices {
|
|
|
275
318
|
if (id === undefined) {
|
|
276
319
|
throw new ConnectError("Subscription ID is required.", Code.InvalidArgument);
|
|
277
320
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
return registry.create(subscription).then(() => clone(SubscriptionSchema, subscription));
|
|
321
|
+
const registry = boundedContextAccess.subscriptionRegistry(route.context);
|
|
322
|
+
return TenantStatuses.require(route.context, tenantId, async () => {
|
|
323
|
+
await registry.create(subscription);
|
|
324
|
+
}).then(() => clone(SubscriptionSchema, subscription));
|
|
284
325
|
}
|
|
285
326
|
async *#activate(subscription) {
|
|
286
327
|
const id = subscription.id?.value;
|
|
287
328
|
if (id === undefined) {
|
|
288
329
|
return;
|
|
289
330
|
}
|
|
290
|
-
|
|
291
|
-
await this.#serializeActivation(id, async () => {
|
|
292
|
-
const local = this.#subscriptions.get(id);
|
|
293
|
-
record = local ?? (await this.#findSubscription(id));
|
|
294
|
-
if (record === undefined)
|
|
295
|
-
return;
|
|
296
|
-
if (record.delivery.active) {
|
|
297
|
-
record = undefined;
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
const activation = await this.#subscriptionRegistry(record.route.context)?.activate(create(SubscriptionIdSchema, { value: id }));
|
|
301
|
-
if (activation === undefined ||
|
|
302
|
-
activation.kind === "missing" ||
|
|
303
|
-
activation.kind === "expired" ||
|
|
304
|
-
record.delivery.closed) {
|
|
305
|
-
record = undefined;
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
try {
|
|
309
|
-
this.#subscriptions.set(id, record);
|
|
310
|
-
await this.#activateRecord(record);
|
|
311
|
-
}
|
|
312
|
-
catch (error) {
|
|
313
|
-
try {
|
|
314
|
-
await this.#removeSubscription(id);
|
|
315
|
-
}
|
|
316
|
-
catch (cleanupError) {
|
|
317
|
-
throw new AggregateError([error, cleanupError], "Subscription activation and cleanup failed.");
|
|
318
|
-
}
|
|
319
|
-
throw error;
|
|
320
|
-
}
|
|
321
|
-
});
|
|
331
|
+
const record = await this.#activationRecord(id);
|
|
322
332
|
if (record === undefined)
|
|
323
333
|
return;
|
|
324
334
|
try {
|
|
335
|
+
yield create(SubscriptionUpdateSchema, {
|
|
336
|
+
subscription: clone(SubscriptionSchema, record.subscription),
|
|
337
|
+
response: ServiceValues.okResponse(),
|
|
338
|
+
});
|
|
325
339
|
for (;;) {
|
|
326
340
|
const update = await record.delivery.next();
|
|
327
341
|
if (update === undefined) {
|
|
@@ -336,6 +350,65 @@ export class SpineServices {
|
|
|
336
350
|
}
|
|
337
351
|
}
|
|
338
352
|
}
|
|
353
|
+
/**
|
|
354
|
+
* Serializes one native activation and returns its attached record.
|
|
355
|
+
*
|
|
356
|
+
* @param id The logical subscription identifier.
|
|
357
|
+
* @returns The attached record, or undefined when admission fails.
|
|
358
|
+
*/
|
|
359
|
+
async #activationRecord(id) {
|
|
360
|
+
let attached;
|
|
361
|
+
await this.#serializeActivation(id, async () => {
|
|
362
|
+
attached = await this.#startActivationRecord(id);
|
|
363
|
+
});
|
|
364
|
+
return attached;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Validates and attaches one retained subscription before acknowledging readiness.
|
|
368
|
+
*
|
|
369
|
+
* @param id The logical subscription identifier.
|
|
370
|
+
* @returns The attached record, or undefined when unavailable.
|
|
371
|
+
*/
|
|
372
|
+
async #startActivationRecord(id) {
|
|
373
|
+
const record = this.#subscriptions.get(id) ?? (await this.#findSubscription(id));
|
|
374
|
+
if (record === undefined)
|
|
375
|
+
return undefined;
|
|
376
|
+
let attached;
|
|
377
|
+
await this.#requireSubscriptionTenant(record, async () => {
|
|
378
|
+
attached = await this.#activateKnownRecord(record, id);
|
|
379
|
+
});
|
|
380
|
+
return attached;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Activates and attaches one admitted record before its registry closes.
|
|
384
|
+
*
|
|
385
|
+
* @param record Discovered subscription record.
|
|
386
|
+
* @param id Canonical subscription ID.
|
|
387
|
+
* @returns Attached record, if activation remains eligible.
|
|
388
|
+
*/
|
|
389
|
+
async #activateKnownRecord(record, id) {
|
|
390
|
+
if (record.delivery.active)
|
|
391
|
+
return undefined;
|
|
392
|
+
const activation = await boundedContextAccess
|
|
393
|
+
.subscriptionRegistry(record.route.context)
|
|
394
|
+
.activate(create(SubscriptionIdSchema, { value: id }));
|
|
395
|
+
if (activation.kind === "missing" || activation.kind === "expired" || record.delivery.closed)
|
|
396
|
+
return undefined;
|
|
397
|
+
try {
|
|
398
|
+
this.#subscriptions.set(id, record);
|
|
399
|
+
await this.#activateRecord(record);
|
|
400
|
+
return record;
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
try {
|
|
404
|
+
await this.#removeSubscription(id);
|
|
405
|
+
}
|
|
406
|
+
catch (cleanupError) {
|
|
407
|
+
throw new AggregateError([error, cleanupError], "Subscription activation and cleanup failed.");
|
|
408
|
+
}
|
|
409
|
+
throw error;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
339
412
|
async #serializeActivation(id, work) {
|
|
340
413
|
const previous = this.#activationTails.get(id) ?? Promise.resolve();
|
|
341
414
|
let finish = () => undefined;
|
|
@@ -354,12 +427,41 @@ export class SpineServices {
|
|
|
354
427
|
this.#activationTails.delete(id);
|
|
355
428
|
}
|
|
356
429
|
}
|
|
430
|
+
/**
|
|
431
|
+
* Leaves an absent subscription ID inert and checks known records before removal.
|
|
432
|
+
*
|
|
433
|
+
* @param subscription Subscription identity supplied by the caller.
|
|
434
|
+
* @returns Cancellation response after any required membership check.
|
|
435
|
+
*/
|
|
357
436
|
#cancel(subscription) {
|
|
358
437
|
const id = subscription.id?.value;
|
|
359
438
|
if (id === undefined) {
|
|
360
439
|
return ServiceValues.okResponse();
|
|
361
440
|
}
|
|
362
|
-
return this.#
|
|
441
|
+
return this.#cancelKnown(id);
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Checks the canonical record's tenant before cancellation side effects.
|
|
445
|
+
*
|
|
446
|
+
* @param id Subscription ID with a possible shared-registry record.
|
|
447
|
+
* @returns Successful cancellation response after removal.
|
|
448
|
+
*/
|
|
449
|
+
async #cancelKnown(id) {
|
|
450
|
+
const record = this.#subscriptions.get(id) ?? (await this.#findSubscription(id));
|
|
451
|
+
if (record === undefined)
|
|
452
|
+
await this.#removeSubscription(id);
|
|
453
|
+
else
|
|
454
|
+
await this.#requireSubscriptionTenant(record, () => this.#removeSubscription(id));
|
|
455
|
+
return ServiceValues.okResponse();
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Checks the discovered subscription tenant before tenant-specific control work.
|
|
459
|
+
*
|
|
460
|
+
* @param record Canonical shared-registry subscription record.
|
|
461
|
+
* @param onReady Complete admitted one-shot mutation, if present.
|
|
462
|
+
*/
|
|
463
|
+
async #requireSubscriptionTenant(record, onReady) {
|
|
464
|
+
await TenantStatuses.require(record.route.context, record.tenantId, onReady);
|
|
363
465
|
}
|
|
364
466
|
#subscriptionRoute(topic) {
|
|
365
467
|
const target = topic.target;
|
|
@@ -372,60 +474,14 @@ export class SpineServices {
|
|
|
372
474
|
return route;
|
|
373
475
|
}
|
|
374
476
|
async #activateRecord(record) {
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
try {
|
|
382
|
-
const consumer = await boundedContextAccess.consumeSubscription(record.route.context, record.id, (update) => {
|
|
383
|
-
record.delivery.push(update);
|
|
384
|
-
if (record.delivery.closed) {
|
|
385
|
-
this.#removeDetachedSubscription(record.id);
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
|
-
record.delivery.attach(consumer);
|
|
389
|
-
}
|
|
390
|
-
catch (error) {
|
|
391
|
-
if (!(error instanceof TypeError) ||
|
|
392
|
-
(!error.message.includes("requires a Stand instance") &&
|
|
393
|
-
!error.message.includes("requires a built BoundedContext instance"))) {
|
|
394
|
-
throw error;
|
|
395
|
-
}
|
|
396
|
-
this.#createSubscriptionAttachment(record);
|
|
397
|
-
}
|
|
477
|
+
const consumer = await boundedContextAccess.consumeSubscription(record.route.context, record.id, (update) => {
|
|
478
|
+
record.delivery.push(update);
|
|
479
|
+
if (record.delivery.closed)
|
|
480
|
+
this.#removeDetachedSubscription(record.id);
|
|
481
|
+
});
|
|
482
|
+
record.delivery.attach(consumer);
|
|
398
483
|
managedChildSubscriptionAccess.installed(record.id);
|
|
399
484
|
}
|
|
400
|
-
#createSubscriptionAttachment(record) {
|
|
401
|
-
if (record.kind === "event") {
|
|
402
|
-
const eventSubscription = boundedContextAccess.subscribeToEvent(record.route.context, record.route.typeUrl, {
|
|
403
|
-
onEvent: (event) => {
|
|
404
|
-
if (!ServiceValues.eventTenantMatches(record, event)) {
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
record.delivery.push(ServiceValues.createEventUpdate(record, event));
|
|
408
|
-
if (record.delivery.closed) {
|
|
409
|
-
this.#removeDetachedSubscription(record.id);
|
|
410
|
-
}
|
|
411
|
-
},
|
|
412
|
-
});
|
|
413
|
-
record.delivery.attach(eventSubscription);
|
|
414
|
-
return eventSubscription;
|
|
415
|
-
}
|
|
416
|
-
const stateRecord = record;
|
|
417
|
-
const standSubscription = stateRecord.route.context.stand().subscribe(stateRecord.route.schema, (update) => {
|
|
418
|
-
const subscriptionUpdate = ServiceValues.createEntityUpdate(stateRecord, update);
|
|
419
|
-
if (subscriptionUpdate !== undefined) {
|
|
420
|
-
stateRecord.delivery.push(subscriptionUpdate);
|
|
421
|
-
}
|
|
422
|
-
if (stateRecord.delivery.closed) {
|
|
423
|
-
this.#removeDetachedSubscription(stateRecord.id);
|
|
424
|
-
}
|
|
425
|
-
}, ServiceValues.tenantOptions(stateRecord.tenantId));
|
|
426
|
-
stateRecord.delivery.attach(standSubscription);
|
|
427
|
-
return standSubscription;
|
|
428
|
-
}
|
|
429
485
|
#removeSubscription(id) {
|
|
430
486
|
const existing = this.#removals.get(id);
|
|
431
487
|
if (existing !== undefined) {
|
|
@@ -464,15 +520,12 @@ export class SpineServices {
|
|
|
464
520
|
async #runRemoval(id, removal, record, unknown) {
|
|
465
521
|
try {
|
|
466
522
|
if (record !== undefined) {
|
|
467
|
-
await
|
|
523
|
+
await boundedContextAccess
|
|
524
|
+
.subscriptionRegistry(record.route.context)
|
|
525
|
+
.delete(create(SubscriptionIdSchema, { value: id }));
|
|
468
526
|
}
|
|
469
527
|
else {
|
|
470
|
-
await
|
|
471
|
-
const registry = this.#subscriptionRegistry(context);
|
|
472
|
-
return registry === undefined
|
|
473
|
-
? []
|
|
474
|
-
: [registry.delete(create(SubscriptionIdSchema, { value: id }))];
|
|
475
|
-
}));
|
|
528
|
+
await this.#deleteUnknownSubscription(id);
|
|
476
529
|
}
|
|
477
530
|
removal.resolve();
|
|
478
531
|
}
|
|
@@ -488,10 +541,25 @@ export class SpineServices {
|
|
|
488
541
|
}
|
|
489
542
|
}
|
|
490
543
|
}
|
|
544
|
+
/**
|
|
545
|
+
* Deletes an unknown subscription from every configured context registry.
|
|
546
|
+
*
|
|
547
|
+
* @param id Subscription ID to remove.
|
|
548
|
+
*/
|
|
549
|
+
async #deleteUnknownSubscription(id) {
|
|
550
|
+
await Promise.all(ServiceValues.uniqueContexts(this.#contexts).map((context) => this.#registryOperation(context, async () => {
|
|
551
|
+
await boundedContextAccess
|
|
552
|
+
.subscriptionRegistry(context)
|
|
553
|
+
.delete(create(SubscriptionIdSchema, { value: id }));
|
|
554
|
+
})));
|
|
555
|
+
}
|
|
491
556
|
async #findSubscription(id) {
|
|
492
557
|
const subscriptionId = create(SubscriptionIdSchema, { value: id });
|
|
493
558
|
for (const context of ServiceValues.uniqueContexts(this.#contexts)) {
|
|
494
|
-
|
|
559
|
+
let entry;
|
|
560
|
+
await this.#registryOperation(context, async () => {
|
|
561
|
+
entry = await boundedContextAccess.subscriptionRegistry(context).get(subscriptionId);
|
|
562
|
+
});
|
|
495
563
|
const subscription = entry?.subscription;
|
|
496
564
|
const topic = subscription?.topic;
|
|
497
565
|
if (entry === undefined || subscription === undefined || topic === undefined)
|
|
@@ -514,24 +582,15 @@ export class SpineServices {
|
|
|
514
582
|
}
|
|
515
583
|
return undefined;
|
|
516
584
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
// contexts always provide their configured registry through the access seam.
|
|
527
|
-
const key = context;
|
|
528
|
-
let registry = this.#testRegistries.get(key);
|
|
529
|
-
if (registry === undefined) {
|
|
530
|
-
registry = new InMemorySubscriptionRegistry();
|
|
531
|
-
this.#testRegistries.set(key, registry);
|
|
532
|
-
}
|
|
533
|
-
return registry;
|
|
534
|
-
}
|
|
585
|
+
/**
|
|
586
|
+
* Retains one short registry read or delete through the context close drain.
|
|
587
|
+
*
|
|
588
|
+
* @param context Context selected by the shared subscription route.
|
|
589
|
+
* @param onOperation Complete registry callback.
|
|
590
|
+
* @returns Completion before the selected registry may close.
|
|
591
|
+
*/
|
|
592
|
+
#registryOperation(context, onOperation) {
|
|
593
|
+
return boundedContextAccess.admitServiceRegistryOperation(context, onOperation);
|
|
535
594
|
}
|
|
536
595
|
}
|
|
537
596
|
/**
|
|
@@ -765,7 +824,8 @@ function validateQueryIds(ids, route) {
|
|
|
765
824
|
if (schema === undefined || ids === undefined) {
|
|
766
825
|
return undefined;
|
|
767
826
|
}
|
|
768
|
-
if (ids.some((id) => id === undefined ||
|
|
827
|
+
if (ids.some((id) => id === undefined ||
|
|
828
|
+
(schema.typeName !== AnySchema.typeName && AnyPacker.tryUnpack(id, schema) === undefined))) {
|
|
769
829
|
return invalidQueryError(`QueryService.Read id_filter values must pack ${schema.typeName}.`);
|
|
770
830
|
}
|
|
771
831
|
return undefined;
|
|
@@ -876,7 +936,7 @@ function normalizedFilters(filters, route) {
|
|
|
876
936
|
}
|
|
877
937
|
function decodeQueryIdValue(value, route) {
|
|
878
938
|
const schema = route.idField.message;
|
|
879
|
-
return schema === undefined ? decodeAnyValue(value) :
|
|
939
|
+
return schema === undefined ? decodeAnyValue(value) : decodeAnyValue(value, schema);
|
|
880
940
|
}
|
|
881
941
|
function normalizedComposite(filter, route) {
|
|
882
942
|
const predicates = [
|
|
@@ -908,13 +968,13 @@ function requiredComparison(filter) {
|
|
|
908
968
|
function decodeColumnValue(filter, route) {
|
|
909
969
|
const column = filter.fieldPath?.fieldName[0] ?? "";
|
|
910
970
|
if (column === "version" && filter.value !== undefined) {
|
|
911
|
-
const value =
|
|
971
|
+
const value = AnyPacker.tryUnpack(filter.value, VersionSchema);
|
|
912
972
|
if (value !== undefined)
|
|
913
973
|
return value;
|
|
914
974
|
throw new TypeError('Validated query column "version" has an invalid value.');
|
|
915
975
|
}
|
|
916
976
|
if ((column === "archived" || column === "deleted") && filter.value !== undefined) {
|
|
917
|
-
const value =
|
|
977
|
+
const value = AnyPacker.tryUnpack(filter.value, BoolValueSchema);
|
|
918
978
|
if (value !== undefined)
|
|
919
979
|
return value.value;
|
|
920
980
|
throw new TypeError(`Validated query column "${column}" has an invalid value.`);
|
|
@@ -924,7 +984,8 @@ function decodeColumnValue(filter, route) {
|
|
|
924
984
|
}
|
|
925
985
|
function filterValueMatches(field, value) {
|
|
926
986
|
const schema = filterValueSchema(field);
|
|
927
|
-
if (schema === undefined ||
|
|
987
|
+
if (schema === undefined ||
|
|
988
|
+
(schema.typeName !== AnySchema.typeName && value.typeUrl !== TypeUrls.derive(schema)))
|
|
928
989
|
return false;
|
|
929
990
|
const decoded = field.fieldKind === "message" ? decodeAnyValue(value, schema) : decodeAnyValue(value);
|
|
930
991
|
if (field.fieldKind === "message") {
|
|
@@ -982,11 +1043,11 @@ function filterValueSchema(field) {
|
|
|
982
1043
|
function systemValueMatches(column, value) {
|
|
983
1044
|
if (column === "version") {
|
|
984
1045
|
return (value.typeUrl === TypeUrls.derive(VersionSchema) &&
|
|
985
|
-
|
|
1046
|
+
AnyPacker.tryUnpack(value, VersionSchema) !== undefined);
|
|
986
1047
|
}
|
|
987
1048
|
if (column === "archived" || column === "deleted") {
|
|
988
1049
|
return (value.typeUrl === TypeUrls.derive(BoolValueSchema) &&
|
|
989
|
-
|
|
1050
|
+
AnyPacker.tryUnpack(value, BoolValueSchema) !== undefined);
|
|
990
1051
|
}
|
|
991
1052
|
return false;
|
|
992
1053
|
}
|
|
@@ -1097,7 +1158,7 @@ function validateTopic(topic) {
|
|
|
1097
1158
|
}
|
|
1098
1159
|
}
|
|
1099
1160
|
/**
|
|
1100
|
-
* Validates a topic and
|
|
1161
|
+
* Validates a topic and returns its state or Event subscription shape.
|
|
1101
1162
|
*/
|
|
1102
1163
|
function createSubscriptionShape(topic, route) {
|
|
1103
1164
|
const target = topic.target;
|
|
@@ -1113,13 +1174,10 @@ function createSubscriptionShape(topic, route) {
|
|
|
1113
1174
|
if (!target.criterion.value) {
|
|
1114
1175
|
throw new ConnectError("SubscriptionService.Subscribe requires filters or include_all = true.", Code.InvalidArgument);
|
|
1115
1176
|
}
|
|
1116
|
-
return { kind: "state", route
|
|
1177
|
+
return { kind: "state", route };
|
|
1117
1178
|
case "filters":
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
route,
|
|
1121
|
-
matcher: createFilteredSubscriptionMatcher(target.criterion.value, route),
|
|
1122
|
-
};
|
|
1179
|
+
ServiceValues.validateFilteredSubscriptionTopic(target.criterion.value, route);
|
|
1180
|
+
return { kind: "state", route };
|
|
1123
1181
|
default:
|
|
1124
1182
|
throw new ConnectError("SubscriptionService.Subscribe requires filters or include_all = true.", Code.InvalidArgument);
|
|
1125
1183
|
}
|
|
@@ -1135,42 +1193,12 @@ function validateEventSubscriptionTopic(target) {
|
|
|
1135
1193
|
}
|
|
1136
1194
|
return;
|
|
1137
1195
|
case "filters":
|
|
1138
|
-
|
|
1196
|
+
EventOriginFilters.compile(target.criterion.value);
|
|
1197
|
+
return;
|
|
1139
1198
|
default:
|
|
1140
1199
|
throw new ConnectError("SubscriptionService.Subscribe requires filters or include_all = true.", Code.InvalidArgument);
|
|
1141
1200
|
}
|
|
1142
1201
|
}
|
|
1143
|
-
/**
|
|
1144
|
-
* Matches complete state updates against a topic's ID and field filters.
|
|
1145
|
-
*/
|
|
1146
|
-
function createFilteredSubscriptionMatcher(filters, route) {
|
|
1147
|
-
ServiceValues.validateSubscriptionFilters(filters);
|
|
1148
|
-
const idValues = filters.idFilter?.id.map((id) => decodeSubscriptionIdValue(id, route));
|
|
1149
|
-
const statePredicate = createSubscriptionPredicate(filters.filter, route);
|
|
1150
|
-
return {
|
|
1151
|
-
/**
|
|
1152
|
-
* Matches one state update against the topic's ID and field filters.
|
|
1153
|
-
*
|
|
1154
|
-
* @param update State update to inspect.
|
|
1155
|
-
* @returns Delivery kind, if the update changes a matching state.
|
|
1156
|
-
*/
|
|
1157
|
-
match(update) {
|
|
1158
|
-
if (idValues !== undefined &&
|
|
1159
|
-
!idValues.some((id) => valuesEqual(update.id, id, route.idField.message))) {
|
|
1160
|
-
return undefined;
|
|
1161
|
-
}
|
|
1162
|
-
if (statePredicate(update.state))
|
|
1163
|
-
return "state";
|
|
1164
|
-
if (update.previousState !== undefined && statePredicate(update.previousState))
|
|
1165
|
-
return "noLongerMatching";
|
|
1166
|
-
return undefined;
|
|
1167
|
-
},
|
|
1168
|
-
};
|
|
1169
|
-
}
|
|
1170
|
-
function createSubscriptionPredicate(filters, route) {
|
|
1171
|
-
const predicates = filters.map((filter) => createCompositePredicate(filter, route));
|
|
1172
|
-
return (state) => predicates.every((predicate) => predicate(state));
|
|
1173
|
-
}
|
|
1174
1202
|
function validateSubscriptionFilterTree(filters) {
|
|
1175
1203
|
if (filters.length > SubscriptionLimits.compositeCount) {
|
|
1176
1204
|
throw new ConnectError("SubscriptionService.Subscribe may contain at most 8 composite filters.", Code.InvalidArgument);
|
|
@@ -1227,30 +1255,6 @@ function enqueueSubscriptionFilterChildren(filter, depth, stack) {
|
|
|
1227
1255
|
stack.push({ filter: nested, depth: depth + 1 });
|
|
1228
1256
|
}
|
|
1229
1257
|
}
|
|
1230
|
-
function createCompositePredicate(filter, route) {
|
|
1231
|
-
const children = [
|
|
1232
|
-
...filter.filter.map((simple) => createSimplePredicate(simple, route)),
|
|
1233
|
-
...filter.compositeFilter.map((nested) => createCompositePredicate(nested, route)),
|
|
1234
|
-
];
|
|
1235
|
-
if (children.length === 0) {
|
|
1236
|
-
return () => true;
|
|
1237
|
-
}
|
|
1238
|
-
return filter.operator === CompositeFilter_CompositeOperator.ALL
|
|
1239
|
-
? (state) => children.every((predicate) => predicate(state))
|
|
1240
|
-
: (state) => children.some((predicate) => predicate(state));
|
|
1241
|
-
}
|
|
1242
|
-
function createSimplePredicate(filter, route) {
|
|
1243
|
-
if (filter.operator !== Filter_Operator.EQUAL) {
|
|
1244
|
-
throw new ConnectError("SubscriptionService.Subscribe supports only EQUAL field filters.", Code.InvalidArgument);
|
|
1245
|
-
}
|
|
1246
|
-
if (filter.value === undefined) {
|
|
1247
|
-
throw new ConnectError("SubscriptionService.Subscribe field filter value is required.", Code.InvalidArgument);
|
|
1248
|
-
}
|
|
1249
|
-
validateSubscriptionPath(filter.fieldPath?.fieldName ?? [], "field filter");
|
|
1250
|
-
const resolved = resolveMessagePath(route.schema, filter.fieldPath?.fieldName ?? [], "field filter");
|
|
1251
|
-
const expected = decodeFieldFilterValue(filter.value, resolved.leafSchema);
|
|
1252
|
-
return (state) => valuesEqual(readPathValue(state, resolved.localPath), expected, resolved.leafSchema);
|
|
1253
|
-
}
|
|
1254
1258
|
/**
|
|
1255
1259
|
* Checks a state filter path before resolving its fields.
|
|
1256
1260
|
*/
|
|
@@ -1272,7 +1276,9 @@ function decodeSubscriptionIdValue(value, route) {
|
|
|
1272
1276
|
if (route.idField.message === undefined) {
|
|
1273
1277
|
return decodeAnyValue(value);
|
|
1274
1278
|
}
|
|
1275
|
-
const decoded =
|
|
1279
|
+
const decoded = route.idField.message.typeName === AnySchema.typeName
|
|
1280
|
+
? value
|
|
1281
|
+
: AnyPacker.tryUnpack(value, route.idField.message);
|
|
1276
1282
|
if (decoded === undefined) {
|
|
1277
1283
|
throw new ConnectError(`SubscriptionService.Subscribe id_filter values must pack ${route.idField.message.typeName}.`, Code.InvalidArgument);
|
|
1278
1284
|
}
|
|
@@ -1282,7 +1288,7 @@ function decodeFieldFilterValue(value, schema) {
|
|
|
1282
1288
|
if (value === undefined || schema === undefined) {
|
|
1283
1289
|
return decodeAnyValue(value);
|
|
1284
1290
|
}
|
|
1285
|
-
const decoded =
|
|
1291
|
+
const decoded = schema.typeName === AnySchema.typeName ? value : AnyPacker.tryUnpack(value, schema);
|
|
1286
1292
|
if (decoded === undefined) {
|
|
1287
1293
|
throw new ConnectError(`SubscriptionService.Subscribe field filter value must pack ${schema.typeName}.`, Code.InvalidArgument);
|
|
1288
1294
|
}
|
|
@@ -1293,7 +1299,9 @@ function decodeAnyValue(value, schema) {
|
|
|
1293
1299
|
return undefined;
|
|
1294
1300
|
}
|
|
1295
1301
|
if (schema !== undefined) {
|
|
1296
|
-
|
|
1302
|
+
if (schema.typeName === AnySchema.typeName)
|
|
1303
|
+
return value;
|
|
1304
|
+
const decoded = AnyPacker.tryUnpack(value, schema);
|
|
1297
1305
|
if (decoded !== undefined) {
|
|
1298
1306
|
return decoded;
|
|
1299
1307
|
}
|
|
@@ -1310,7 +1318,6 @@ function decodeAnyValue(value, schema) {
|
|
|
1310
1318
|
* Resolves a state filter path to generated message fields.
|
|
1311
1319
|
*/
|
|
1312
1320
|
function resolveMessagePath(schema, fieldPath, label) {
|
|
1313
|
-
const localPath = [];
|
|
1314
1321
|
let currentSchema = schema;
|
|
1315
1322
|
let leafSchema;
|
|
1316
1323
|
for (const [index, name] of fieldPath.entries()) {
|
|
@@ -1318,7 +1325,6 @@ function resolveMessagePath(schema, fieldPath, label) {
|
|
|
1318
1325
|
if (field === undefined) {
|
|
1319
1326
|
throw new ConnectError(`SubscriptionService.Subscribe ${label} "${fieldPath.join(".")}" is not a state field.`, Code.InvalidArgument);
|
|
1320
1327
|
}
|
|
1321
|
-
localPath.push(field.localName);
|
|
1322
1328
|
leafSchema = field.message;
|
|
1323
1329
|
currentSchema = index === fieldPath.length - 1 ? undefined : field.message;
|
|
1324
1330
|
if (currentSchema === undefined && index < fieldPath.length - 1) {
|
|
@@ -1326,7 +1332,6 @@ function resolveMessagePath(schema, fieldPath, label) {
|
|
|
1326
1332
|
}
|
|
1327
1333
|
}
|
|
1328
1334
|
return Object.freeze({
|
|
1329
|
-
localPath,
|
|
1330
1335
|
...(leafSchema === undefined ? {} : { leafSchema }),
|
|
1331
1336
|
});
|
|
1332
1337
|
}
|
|
@@ -1334,37 +1339,12 @@ function findMessageField(schema, name) {
|
|
|
1334
1339
|
const fields = (schema?.fields ?? []);
|
|
1335
1340
|
return fields.find((field) => field.name === name || field.localName === name);
|
|
1336
1341
|
}
|
|
1337
|
-
function readPathValue(value, path) {
|
|
1338
|
-
let current = value;
|
|
1339
|
-
for (const segment of path) {
|
|
1340
|
-
if (typeof current !== "object" || current === null) {
|
|
1341
|
-
return undefined;
|
|
1342
|
-
}
|
|
1343
|
-
current = Reflect.get(current, segment);
|
|
1344
|
-
}
|
|
1345
|
-
return current;
|
|
1346
|
-
}
|
|
1347
|
-
function valuesEqual(actual, expected, schema) {
|
|
1348
|
-
if (schema !== undefined && isMessage(actual) && isMessage(expected)) {
|
|
1349
|
-
return bytesEqual(toBinary(schema, actual), toBinary(schema, expected));
|
|
1350
|
-
}
|
|
1351
|
-
if (isAny(actual) && isAny(expected)) {
|
|
1352
|
-
return bytesEqual(toBinary(AnySchema, actual), toBinary(AnySchema, expected));
|
|
1353
|
-
}
|
|
1354
|
-
if (actual instanceof Uint8Array && expected instanceof Uint8Array) {
|
|
1355
|
-
return bytesEqual(actual, expected);
|
|
1356
|
-
}
|
|
1357
|
-
return Object.is(actual, expected);
|
|
1358
|
-
}
|
|
1359
1342
|
function isMessage(value) {
|
|
1360
1343
|
return typeof value === "object" && value !== null && "$typeName" in value;
|
|
1361
1344
|
}
|
|
1362
1345
|
function isAny(value) {
|
|
1363
1346
|
return isMessage(value) && value.$typeName === "google.protobuf.Any";
|
|
1364
1347
|
}
|
|
1365
|
-
function bytesEqual(left, right) {
|
|
1366
|
-
return left.length === right.length && left.every((byte, index) => byte === right[index]);
|
|
1367
|
-
}
|
|
1368
1348
|
/**
|
|
1369
1349
|
* Packs a state snapshot and version for a public query result.
|
|
1370
1350
|
*
|
|
@@ -1375,7 +1355,7 @@ function bytesEqual(left, right) {
|
|
|
1375
1355
|
*/
|
|
1376
1356
|
function packVersionedState(schema, result) {
|
|
1377
1357
|
return create(EntityStateVersionSchema, {
|
|
1378
|
-
state:
|
|
1358
|
+
state: AnyPacker.pack(schema, result.state),
|
|
1379
1359
|
version: result.version ?? create(VersionSchema),
|
|
1380
1360
|
});
|
|
1381
1361
|
}
|
|
@@ -1385,22 +1365,6 @@ function commandTenant(command) {
|
|
|
1385
1365
|
function topicTenant(topic) {
|
|
1386
1366
|
return tenantValue(topic?.context?.tenantId);
|
|
1387
1367
|
}
|
|
1388
|
-
function eventTenant(event) {
|
|
1389
|
-
switch (event.context?.origin.case) {
|
|
1390
|
-
case "importContext":
|
|
1391
|
-
return tenantValue(event.context.origin.value.tenantId);
|
|
1392
|
-
case "pastMessage":
|
|
1393
|
-
return tenantValue(event.context.origin.value.actorContext?.tenantId);
|
|
1394
|
-
default:
|
|
1395
|
-
return undefined;
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1398
|
-
function eventTenantMatches(record, event) {
|
|
1399
|
-
const tenantId = eventTenant(event);
|
|
1400
|
-
return (record.tenantId === undefined ||
|
|
1401
|
-
(tenantId !== undefined &&
|
|
1402
|
-
TenantBoundary.from(tenantId).key === TenantBoundary.from(record.tenantId).key));
|
|
1403
|
-
}
|
|
1404
1368
|
function tenantValue(tenantId) {
|
|
1405
1369
|
if (tenantId === undefined)
|
|
1406
1370
|
return undefined;
|
|
@@ -1421,6 +1385,53 @@ function stateRouteIdField(schema, idField) {
|
|
|
1421
1385
|
}
|
|
1422
1386
|
return field;
|
|
1423
1387
|
}
|
|
1388
|
+
/**
|
|
1389
|
+
* Maps confirmed tenant absence to native service errors.
|
|
1390
|
+
*/
|
|
1391
|
+
const TenantStatuses = Object.freeze({
|
|
1392
|
+
/**
|
|
1393
|
+
* Checks live membership and maps only confirmed missing membership to NotFound.
|
|
1394
|
+
*
|
|
1395
|
+
* @param context Receiving context and its selected index.
|
|
1396
|
+
* @param tenantId Complete selected tenant identity.
|
|
1397
|
+
* @param onReady Complete one-shot mutation after membership and preparation.
|
|
1398
|
+
* @returns Completion after membership and any mutation settle.
|
|
1399
|
+
*/
|
|
1400
|
+
async require(context, tenantId, onReady) {
|
|
1401
|
+
try {
|
|
1402
|
+
const runtimeContext = context;
|
|
1403
|
+
if (runtimeContext instanceof BoundedContext)
|
|
1404
|
+
await boundedContextAccess.admitServiceInput(runtimeContext, tenantId, () => TenantAdmissions.require(context.tenantIndex, context.name.value, context.isMultitenant, tenantId), onReady);
|
|
1405
|
+
else {
|
|
1406
|
+
await TenantAdmissions.require(context.tenantIndex, context.name.value, context.isMultitenant, tenantId);
|
|
1407
|
+
await onReady?.();
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
catch (error) {
|
|
1411
|
+
if (error instanceof TenantUnknownError)
|
|
1412
|
+
throw TenantStatuses.unknown(error);
|
|
1413
|
+
throw error;
|
|
1414
|
+
}
|
|
1415
|
+
},
|
|
1416
|
+
/**
|
|
1417
|
+
* Exposes a confirmed unknown tenant as a native NotFound RPC with Spine details.
|
|
1418
|
+
*
|
|
1419
|
+
* @param error Receiving-context membership failure.
|
|
1420
|
+
* @returns Connect failure preserving its stable Spine error type.
|
|
1421
|
+
*/
|
|
1422
|
+
unknown(error) {
|
|
1423
|
+
return new ConnectError(error.message, Code.NotFound, undefined, [
|
|
1424
|
+
{
|
|
1425
|
+
desc: ErrorSchema,
|
|
1426
|
+
value: create(ErrorSchema, {
|
|
1427
|
+
type: error.type,
|
|
1428
|
+
code: Code.NotFound,
|
|
1429
|
+
message: error.message,
|
|
1430
|
+
}),
|
|
1431
|
+
},
|
|
1432
|
+
], error);
|
|
1433
|
+
},
|
|
1434
|
+
});
|
|
1424
1435
|
function tenantMismatch(multitenant, tenantId, subject) {
|
|
1425
1436
|
if (multitenant) {
|
|
1426
1437
|
return tenantId === undefined
|
|
@@ -1442,18 +1453,14 @@ function commandPostError(error) {
|
|
|
1442
1453
|
return {
|
|
1443
1454
|
type: error.type,
|
|
1444
1455
|
message: error.clientMessage,
|
|
1445
|
-
details:
|
|
1446
|
-
validate: false,
|
|
1447
|
-
}),
|
|
1456
|
+
details: AnyPacker.pack(ValidationErrorSchema, error.validationError),
|
|
1448
1457
|
};
|
|
1449
1458
|
}
|
|
1450
1459
|
if (error instanceof CommandValidationError) {
|
|
1451
1460
|
return {
|
|
1452
1461
|
type: "COMMAND_VALIDATION_ERROR",
|
|
1453
1462
|
message: "Command payload validation failed.",
|
|
1454
|
-
details:
|
|
1455
|
-
validate: false,
|
|
1456
|
-
}),
|
|
1463
|
+
details: AnyPacker.pack(ValidationErrorSchema, error.validationError),
|
|
1457
1464
|
};
|
|
1458
1465
|
}
|
|
1459
1466
|
return {
|
|
@@ -1461,104 +1468,77 @@ function commandPostError(error) {
|
|
|
1461
1468
|
message: "Command post failed.",
|
|
1462
1469
|
};
|
|
1463
1470
|
}
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1471
|
+
const recognizedSqlErrorCodes = new Set([
|
|
1472
|
+
"40001",
|
|
1473
|
+
"40P01",
|
|
1474
|
+
"42703",
|
|
1475
|
+
"42P01",
|
|
1476
|
+
"23503",
|
|
1477
|
+
"23505",
|
|
1478
|
+
"P0001",
|
|
1479
|
+
"08006",
|
|
1480
|
+
"ER_BAD_FIELD_ERROR",
|
|
1481
|
+
"ER_LOCK_DEADLOCK",
|
|
1482
|
+
"ER_LOCK_WAIT_TIMEOUT",
|
|
1483
|
+
"ER_SIGNAL_EXCEPTION",
|
|
1477
1484
|
]);
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1485
|
+
const InternalErrorFacts = Object.freeze({
|
|
1486
|
+
/**
|
|
1487
|
+
* Reads a recognized SQL code from an internal failure chain.
|
|
1488
|
+
*
|
|
1489
|
+
* @param error The internal failure.
|
|
1490
|
+
* @returns A recognized code, if present.
|
|
1491
|
+
*/
|
|
1492
|
+
code(error) {
|
|
1493
|
+
const seen = new Set();
|
|
1494
|
+
let current = error;
|
|
1495
|
+
for (let depth = 0; depth < 8 && typeof current === "object" && current !== null; depth++) {
|
|
1496
|
+
if (seen.has(current))
|
|
1497
|
+
break;
|
|
1498
|
+
seen.add(current);
|
|
1499
|
+
try {
|
|
1500
|
+
const code = Reflect.get(current, "code");
|
|
1501
|
+
if (typeof code === "string" && recognizedSqlErrorCodes.has(code))
|
|
1502
|
+
return code;
|
|
1503
|
+
current = Reflect.get(current, "cause");
|
|
1504
|
+
}
|
|
1505
|
+
catch {
|
|
1506
|
+
return undefined;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1487
1509
|
return undefined;
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1510
|
+
},
|
|
1511
|
+
/**
|
|
1512
|
+
* Reads a safe error class name without trusting a rejection's accessors.
|
|
1513
|
+
*
|
|
1514
|
+
* @param error The internal failure.
|
|
1515
|
+
* @returns A known error name, if present.
|
|
1516
|
+
*/
|
|
1517
|
+
name(error) {
|
|
1518
|
+
try {
|
|
1519
|
+
if (!(error instanceof Error))
|
|
1520
|
+
return undefined;
|
|
1521
|
+
const name = error.name;
|
|
1522
|
+
return ["Error", "TypeError", "RangeError", "AggregateError"].includes(name)
|
|
1523
|
+
? name
|
|
1524
|
+
: undefined;
|
|
1494
1525
|
}
|
|
1495
|
-
|
|
1496
|
-
return create(SubscriptionUpdateSchema, {
|
|
1497
|
-
subscription: clone(SubscriptionSchema, record.subscription),
|
|
1498
|
-
response: okResponse(),
|
|
1499
|
-
update: {
|
|
1500
|
-
case: "entityUpdates",
|
|
1501
|
-
value: create(EntityUpdatesSchema, {
|
|
1502
|
-
update: [
|
|
1503
|
-
create(EntityStateUpdateSchema, {
|
|
1504
|
-
id: packEntityId(record.route, update.id),
|
|
1505
|
-
kind,
|
|
1506
|
-
}),
|
|
1507
|
-
],
|
|
1508
|
-
}),
|
|
1509
|
-
},
|
|
1510
|
-
});
|
|
1511
|
-
}
|
|
1512
|
-
function createEventUpdate(record, event) {
|
|
1513
|
-
return create(SubscriptionUpdateSchema, {
|
|
1514
|
-
subscription: clone(SubscriptionSchema, record.subscription),
|
|
1515
|
-
response: okResponse(),
|
|
1516
|
-
update: {
|
|
1517
|
-
case: "eventUpdates",
|
|
1518
|
-
value: create(EventUpdatesSchema, {
|
|
1519
|
-
event: [cloneClientEvent(event)],
|
|
1520
|
-
}),
|
|
1521
|
-
},
|
|
1522
|
-
});
|
|
1523
|
-
}
|
|
1524
|
-
function cloneClientEvent(event) {
|
|
1525
|
-
const clientEvent = clone(EventSchema, event);
|
|
1526
|
-
const rejection = clientEvent.context?.rejection;
|
|
1527
|
-
if (rejection !== undefined) {
|
|
1528
|
-
rejection.command = undefined;
|
|
1529
|
-
// Clear the legacy wire payload as part of client-side security redaction.
|
|
1530
|
-
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
1531
|
-
rejection.commandMessage = undefined;
|
|
1532
|
-
rejection.stacktrace = "";
|
|
1533
|
-
}
|
|
1534
|
-
return clientEvent;
|
|
1535
|
-
}
|
|
1536
|
-
function packString(value) {
|
|
1537
|
-
return AnyMessages.pack(StringValueSchema, create(StringValueSchema, { value }));
|
|
1538
|
-
}
|
|
1539
|
-
function packEntityId(route, id) {
|
|
1540
|
-
if (isAny(id)) {
|
|
1541
|
-
return clone(AnySchema, id);
|
|
1542
|
-
}
|
|
1543
|
-
if (route.idField.message !== undefined && isMessage(id)) {
|
|
1544
|
-
return AnyMessages.pack(route.idField.message, id, { validate: false });
|
|
1545
|
-
}
|
|
1546
|
-
if (id instanceof Uint8Array) {
|
|
1547
|
-
return AnyMessages.pack(BytesValueSchema, create(BytesValueSchema, { value: id }));
|
|
1548
|
-
}
|
|
1549
|
-
switch (typeof id) {
|
|
1550
|
-
case "string":
|
|
1551
|
-
return packString(id);
|
|
1552
|
-
case "boolean":
|
|
1553
|
-
return AnyMessages.pack(BoolValueSchema, create(BoolValueSchema, { value: id }));
|
|
1554
|
-
case "number":
|
|
1555
|
-
return AnyMessages.pack(DoubleValueSchema, create(DoubleValueSchema, { value: id }));
|
|
1556
|
-
case "bigint":
|
|
1557
|
-
return AnyMessages.pack(Int64ValueSchema, create(Int64ValueSchema, { value: id }));
|
|
1558
|
-
default:
|
|
1526
|
+
catch {
|
|
1559
1527
|
return undefined;
|
|
1560
|
-
|
|
1561
|
-
}
|
|
1528
|
+
}
|
|
1529
|
+
},
|
|
1530
|
+
});
|
|
1531
|
+
const VALUE_DECODERS = Object.freeze([
|
|
1532
|
+
(value) => AnyPacker.tryUnpack(value, StringValueSchema)?.value,
|
|
1533
|
+
(value) => AnyPacker.tryUnpack(value, BoolValueSchema)?.value,
|
|
1534
|
+
(value) => AnyPacker.tryUnpack(value, Int32ValueSchema)?.value,
|
|
1535
|
+
(value) => AnyPacker.tryUnpack(value, UInt32ValueSchema)?.value,
|
|
1536
|
+
(value) => AnyPacker.tryUnpack(value, Int64ValueSchema)?.value,
|
|
1537
|
+
(value) => AnyPacker.tryUnpack(value, UInt64ValueSchema)?.value,
|
|
1538
|
+
(value) => AnyPacker.tryUnpack(value, FloatValueSchema)?.value,
|
|
1539
|
+
(value) => AnyPacker.tryUnpack(value, DoubleValueSchema)?.value,
|
|
1540
|
+
(value) => AnyPacker.tryUnpack(value, BytesValueSchema)?.value,
|
|
1541
|
+
]);
|
|
1562
1542
|
/**
|
|
1563
1543
|
* Groups non-public service contract, query, and subscription operations.
|
|
1564
1544
|
*/
|
|
@@ -1616,6 +1596,48 @@ const ServiceValues = Object.freeze({
|
|
|
1616
1596
|
}
|
|
1617
1597
|
return undefined;
|
|
1618
1598
|
},
|
|
1599
|
+
/**
|
|
1600
|
+
* Validates state ID and field filters before storing the topic.
|
|
1601
|
+
*
|
|
1602
|
+
* @param filters The topic's declared filters.
|
|
1603
|
+
* @param route The registered state route.
|
|
1604
|
+
*/
|
|
1605
|
+
validateFilteredSubscriptionTopic(filters, route) {
|
|
1606
|
+
ServiceValues.validateSubscriptionFilters(filters);
|
|
1607
|
+
for (const id of filters.idFilter?.id ?? [])
|
|
1608
|
+
decodeSubscriptionIdValue(id, route);
|
|
1609
|
+
for (const filter of filters.filter)
|
|
1610
|
+
ServiceValues.validateCompositeFilter(filter, route);
|
|
1611
|
+
},
|
|
1612
|
+
/**
|
|
1613
|
+
* Validates nested state filter values without retaining an observer predicate.
|
|
1614
|
+
*
|
|
1615
|
+
* @param filter A validated composite filter.
|
|
1616
|
+
* @param route The registered state route.
|
|
1617
|
+
*/
|
|
1618
|
+
validateCompositeFilter(filter, route) {
|
|
1619
|
+
for (const simple of filter.filter)
|
|
1620
|
+
ServiceValues.validateSimpleFilter(simple, route);
|
|
1621
|
+
for (const nested of filter.compositeFilter)
|
|
1622
|
+
ServiceValues.validateCompositeFilter(nested, route);
|
|
1623
|
+
},
|
|
1624
|
+
/**
|
|
1625
|
+
* Validates one field path and packed comparison value.
|
|
1626
|
+
*
|
|
1627
|
+
* @param filter A simple field filter.
|
|
1628
|
+
* @param route The registered state route.
|
|
1629
|
+
*/
|
|
1630
|
+
validateSimpleFilter(filter, route) {
|
|
1631
|
+
if (filter.operator !== Filter_Operator.EQUAL) {
|
|
1632
|
+
throw new ConnectError("SubscriptionService.Subscribe supports only EQUAL field filters.", Code.InvalidArgument);
|
|
1633
|
+
}
|
|
1634
|
+
if (filter.value === undefined) {
|
|
1635
|
+
throw new ConnectError("SubscriptionService.Subscribe field filter value is required.", Code.InvalidArgument);
|
|
1636
|
+
}
|
|
1637
|
+
validateSubscriptionPath(filter.fieldPath?.fieldName ?? [], "field filter");
|
|
1638
|
+
const resolved = resolveMessagePath(route.schema, filter.fieldPath?.fieldName ?? [], "field filter");
|
|
1639
|
+
decodeFieldFilterValue(filter.value, resolved.leafSchema);
|
|
1640
|
+
},
|
|
1619
1641
|
/**
|
|
1620
1642
|
* Checks that a state topic has a usable ID or field filter.
|
|
1621
1643
|
*
|
|
@@ -1634,9 +1656,18 @@ const ServiceValues = Object.freeze({
|
|
|
1634
1656
|
validateSubscriptionFilterTree(filters.filter);
|
|
1635
1657
|
},
|
|
1636
1658
|
commandPostError,
|
|
1659
|
+
/**
|
|
1660
|
+
* Builds a stable Command rejection acknowledgement.
|
|
1661
|
+
*
|
|
1662
|
+
* @param messageId The packed Command ID, if supplied.
|
|
1663
|
+
* @param type The public error code.
|
|
1664
|
+
* @param message The public error message.
|
|
1665
|
+
* @returns A sanitized Ack.
|
|
1666
|
+
*/
|
|
1667
|
+
commandErrorAck(messageId, type, message) {
|
|
1668
|
+
return create(AckSchema, { messageId, status: errorStatus(type, message) });
|
|
1669
|
+
},
|
|
1637
1670
|
commandTenant,
|
|
1638
|
-
createEntityUpdate,
|
|
1639
|
-
createEventUpdate,
|
|
1640
1671
|
createReadPlan,
|
|
1641
1672
|
createSubscriptionRecord,
|
|
1642
1673
|
createSubscriptionRemoval,
|
|
@@ -1644,7 +1675,6 @@ const ServiceValues = Object.freeze({
|
|
|
1644
1675
|
defaultQueueLimit: DEFAULT_QUEUE_LIMIT,
|
|
1645
1676
|
defaultSubscriptionLimit: DEFAULT_SUBSCRIPTION_LIMIT,
|
|
1646
1677
|
errorStatus,
|
|
1647
|
-
eventTenantMatches,
|
|
1648
1678
|
okResponse,
|
|
1649
1679
|
okStatus,
|
|
1650
1680
|
positiveInteger,
|
|
@@ -1654,7 +1684,6 @@ const ServiceValues = Object.freeze({
|
|
|
1654
1684
|
stateRouteIdField,
|
|
1655
1685
|
subscriptionLimit,
|
|
1656
1686
|
tenantMismatch,
|
|
1657
|
-
tenantOptions,
|
|
1658
1687
|
tenantValue,
|
|
1659
1688
|
topicTenant,
|
|
1660
1689
|
uniqueContexts,
|