@spine-event-engine/server 2.0.0-snapshot.24 → 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 +201 -130
- 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/{bus/saved-dispatcher-binding.js → agent/agent-live-inputs.js} +2 -14
- 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 +17 -4
- package/dist/bus/event-bus.d.ts.map +1 -1
- package/dist/bus/event-bus.js +42 -12
- package/dist/bus/event-bus.js.map +1 -1
- package/dist/context/bounded-context.d.ts +140 -87
- package/dist/context/bounded-context.d.ts.map +1 -1
- package/dist/context/bounded-context.js +702 -329
- 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 +22 -13
- package/dist/context/entity-inbox.js.map +1 -1
- package/dist/context/local-inbox-handoff.d.ts +34 -34
- package/dist/context/local-inbox-handoff.d.ts.map +1 -1
- package/dist/context/local-inbox-handoff.js +108 -150
- 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 +10 -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 +1 -1
- package/dist/delivery/delivery-monitor.js +1 -1
- package/dist/delivery/delivery-run-control.d.ts +6 -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.map +1 -1
- package/dist/delivery/delivery-supervisor.js +28 -4
- 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 +693 -53
- 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 +6 -0
- package/dist/delivery/inbox-storage.d.ts.map +1 -1
- package/dist/delivery/inbox-storage.js +43 -6
- package/dist/delivery/inbox-storage.js.map +1 -1
- package/dist/delivery/inbox.d.ts.map +1 -1
- package/dist/delivery/inbox.js +3 -2
- package/dist/delivery/inbox.js.map +1 -1
- package/dist/delivery/reception-observation.d.ts +12 -4
- package/dist/delivery/reception-observation.d.ts.map +1 -1
- package/dist/delivery/reception-observation.js +14 -3
- package/dist/delivery/reception-observation.js.map +1 -1
- 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 +32 -60
- package/dist/repository/repository.d.ts.map +1 -1
- package/dist/repository/repository.js +1446 -1787
- package/dist/repository/repository.js.map +1 -1
- package/dist/runtime/runtime.d.ts +13 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +18 -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 +6 -6
- package/dist/runtime/signal-metadata.js.map +1 -1
- package/dist/runtime/signal-publisher.d.ts +0 -6
- package/dist/runtime/signal-publisher.d.ts.map +1 -1
- package/dist/runtime/signal-publisher.js +1 -9
- 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 +13 -99
- 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.map +1 -1
- package/dist/server/managed-server-application.js +7 -4
- package/dist/server/managed-server-application.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 +14 -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 +50 -204
- 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 +236 -335
- package/dist/services/spine-services.js.map +1 -1
- package/dist/stand/event-origin-filter.js +3 -3
- package/dist/stand/event-origin-filter.js.map +1 -1
- 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 +54 -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 +5 -3
- package/dist/stand/subscription-runtime.d.ts.map +1 -1
- package/dist/stand/subscription-runtime.js +52 -9
- package/dist/stand/subscription-runtime.js.map +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +4 -3
- package/dist/testing/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -14
- 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 -53
- package/dist/bus/saved-dispatcher-binding.d.ts.map +0 -1
- 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 -973
- package/dist/server/environment-delivery-worker.js.map +0 -1
|
@@ -13,23 +13,24 @@
|
|
|
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
27
|
import { EventOriginFilters } from "../stand/event-origin-filter.js";
|
|
28
|
+
import { TenantAdmissions, TenantUnknownError } from "../context/tenant-unknown-error.js";
|
|
28
29
|
import { AckSchema } from "@spine-event-engine/proto";
|
|
29
30
|
import { ResponseSchema, StatusSchema, } from "@spine-event-engine/proto";
|
|
30
31
|
import {} from "@spine-event-engine/storage";
|
|
31
32
|
import { TenantBoundary } from "@spine-event-engine/storage/provider";
|
|
32
|
-
import { boundedContextAccess } from "../context/bounded-context.js";
|
|
33
|
+
import { BoundedContext, boundedContextAccess } from "../context/bounded-context.js";
|
|
33
34
|
import { EffectiveTenants } from "../context/effective-tenant.js";
|
|
34
35
|
import { CommandValidationError } from "../bus/command-errors.js";
|
|
35
36
|
import { TransitionValidationError } from "../repository/command-errors.js";
|
|
@@ -38,7 +39,7 @@ import { QueryReader } from "./query-reader.js";
|
|
|
38
39
|
import { RegisteredTargets } from "./registered-targets.js";
|
|
39
40
|
import { emitServerError, emitServerWarning } from "../server/server-log.js";
|
|
40
41
|
import { managedChildSubscriptionAccess } from "../server/managed-child-subscription.js";
|
|
41
|
-
import {
|
|
42
|
+
import {} from "../stand/subscription-registry.js";
|
|
42
43
|
const serviceLoggers = new WeakMap();
|
|
43
44
|
const serviceInstances = new WeakSet();
|
|
44
45
|
/**
|
|
@@ -86,7 +87,6 @@ export class SpineServices {
|
|
|
86
87
|
#activationTails = new Map();
|
|
87
88
|
#removals = new Map();
|
|
88
89
|
#unknownRemovals = new Set();
|
|
89
|
-
#testRegistries = new WeakMap();
|
|
90
90
|
#queueLimit;
|
|
91
91
|
#subscriptionLimit;
|
|
92
92
|
/**
|
|
@@ -171,7 +171,7 @@ export class SpineServices {
|
|
|
171
171
|
* @returns Acknowledgement for route validation or dispatch.
|
|
172
172
|
*/
|
|
173
173
|
async #post(command) {
|
|
174
|
-
const messageId = command.id &&
|
|
174
|
+
const messageId = command.id && AnyPacker.pack(CommandIdSchema, command.id);
|
|
175
175
|
const typeUrl = command.message?.typeUrl;
|
|
176
176
|
if (typeUrl === undefined || typeUrl.length === 0)
|
|
177
177
|
return ServiceValues.commandErrorAck(messageId, "INVALID_COMMAND", "Command message type is required.");
|
|
@@ -187,6 +187,8 @@ export class SpineServices {
|
|
|
187
187
|
});
|
|
188
188
|
}
|
|
189
189
|
catch (error) {
|
|
190
|
+
if (error instanceof TenantUnknownError)
|
|
191
|
+
throw TenantStatuses.unknown(error);
|
|
190
192
|
const postError = ServiceValues.commandPostError(error);
|
|
191
193
|
this.#logCommandPostFailure(route, command, error);
|
|
192
194
|
return create(AckSchema, {
|
|
@@ -277,7 +279,9 @@ export class SpineServices {
|
|
|
277
279
|
const destination = EffectiveTenants.destination(effective, route.context.isMultitenant);
|
|
278
280
|
return await this.#query(route, ServiceValues.createReadPlan(target, query, route), destination);
|
|
279
281
|
}
|
|
280
|
-
catch {
|
|
282
|
+
catch (error) {
|
|
283
|
+
if (error instanceof TenantUnknownError)
|
|
284
|
+
throw TenantStatuses.unknown(error);
|
|
281
285
|
return ServiceValues.queryErrorResponse("QUERY_READ_ERROR", "Query read failed.");
|
|
282
286
|
}
|
|
283
287
|
}
|
|
@@ -291,10 +295,16 @@ export class SpineServices {
|
|
|
291
295
|
message: results.map((result) => ServiceValues.packVersionedState(route.schema, result)),
|
|
292
296
|
});
|
|
293
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
|
+
*/
|
|
294
304
|
#subscribe(topic) {
|
|
295
305
|
const route = this.#subscriptionRoute(topic);
|
|
296
306
|
ServiceValues.validateTopic(topic);
|
|
297
|
-
|
|
307
|
+
ServiceValues.createSubscriptionShape(topic, route);
|
|
298
308
|
const tenantId = ServiceValues.topicTenant(topic);
|
|
299
309
|
const tenantError = ServiceValues.tenantMismatch(route.context.isMultitenant, tenantId, "subscription");
|
|
300
310
|
if (tenantError !== undefined) {
|
|
@@ -308,12 +318,10 @@ export class SpineServices {
|
|
|
308
318
|
if (id === undefined) {
|
|
309
319
|
throw new ConnectError("Subscription ID is required.", Code.InvalidArgument);
|
|
310
320
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
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));
|
|
317
325
|
}
|
|
318
326
|
async *#activate(subscription) {
|
|
319
327
|
const id = subscription.id?.value;
|
|
@@ -363,13 +371,28 @@ export class SpineServices {
|
|
|
363
371
|
*/
|
|
364
372
|
async #startActivationRecord(id) {
|
|
365
373
|
const record = this.#subscriptions.get(id) ?? (await this.#findSubscription(id));
|
|
366
|
-
if (record === undefined
|
|
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)
|
|
367
391
|
return undefined;
|
|
368
|
-
const activation = await
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
record.delivery.closed)
|
|
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)
|
|
373
396
|
return undefined;
|
|
374
397
|
try {
|
|
375
398
|
this.#subscriptions.set(id, record);
|
|
@@ -404,12 +427,41 @@ export class SpineServices {
|
|
|
404
427
|
this.#activationTails.delete(id);
|
|
405
428
|
}
|
|
406
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
|
+
*/
|
|
407
436
|
#cancel(subscription) {
|
|
408
437
|
const id = subscription.id?.value;
|
|
409
438
|
if (id === undefined) {
|
|
410
439
|
return ServiceValues.okResponse();
|
|
411
440
|
}
|
|
412
|
-
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);
|
|
413
465
|
}
|
|
414
466
|
#subscriptionRoute(topic) {
|
|
415
467
|
const target = topic.target;
|
|
@@ -422,66 +474,13 @@ export class SpineServices {
|
|
|
422
474
|
return route;
|
|
423
475
|
}
|
|
424
476
|
async #activateRecord(record) {
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
try {
|
|
432
|
-
const consumer = await boundedContextAccess.consumeSubscription(record.route.context, record.id, (update) => {
|
|
433
|
-
record.delivery.push(update);
|
|
434
|
-
if (record.delivery.closed) {
|
|
435
|
-
this.#removeDetachedSubscription(record.id);
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
|
-
record.delivery.attach(consumer);
|
|
439
|
-
}
|
|
440
|
-
catch (error) {
|
|
441
|
-
if (!(error instanceof TypeError) ||
|
|
442
|
-
(!error.message.includes("requires a Stand instance") &&
|
|
443
|
-
!error.message.includes("requires a built BoundedContext instance"))) {
|
|
444
|
-
throw error;
|
|
445
|
-
}
|
|
446
|
-
this.#createSubscriptionAttachment(record);
|
|
447
|
-
}
|
|
448
|
-
managedChildSubscriptionAccess.installed(record.id);
|
|
449
|
-
}
|
|
450
|
-
#createSubscriptionAttachment(record) {
|
|
451
|
-
if (record.kind === "event")
|
|
452
|
-
return this.#attachEventSubscription(record);
|
|
453
|
-
const stateRecord = record;
|
|
454
|
-
const standSubscription = stateRecord.route.context.stand().subscribe(stateRecord.route.schema, (update) => {
|
|
455
|
-
const subscriptionUpdate = ServiceValues.createEntityUpdate(stateRecord, update);
|
|
456
|
-
if (subscriptionUpdate !== undefined) {
|
|
457
|
-
stateRecord.delivery.push(subscriptionUpdate);
|
|
458
|
-
}
|
|
459
|
-
if (stateRecord.delivery.closed) {
|
|
460
|
-
this.#removeDetachedSubscription(stateRecord.id);
|
|
461
|
-
}
|
|
462
|
-
}, ServiceValues.tenantOptions(stateRecord.tenantId));
|
|
463
|
-
stateRecord.delivery.attach(standSubscription);
|
|
464
|
-
return standSubscription;
|
|
465
|
-
}
|
|
466
|
-
/**
|
|
467
|
-
* Attaches one filtered Event observer to its bounded context.
|
|
468
|
-
* @param record The tracked Event subscription.
|
|
469
|
-
* @returns The attached Event observer.
|
|
470
|
-
*/
|
|
471
|
-
#attachEventSubscription(record) {
|
|
472
|
-
const eventSubscription = boundedContextAccess.subscribeToEvent(record.route.context, record.route.typeUrl, {
|
|
473
|
-
onEvent: (event) => {
|
|
474
|
-
if (!ServiceValues.eventTenantMatches(record, event))
|
|
475
|
-
return;
|
|
476
|
-
if (record.eventMatcher !== undefined && !record.eventMatcher(event))
|
|
477
|
-
return;
|
|
478
|
-
record.delivery.push(ServiceValues.createEventUpdate(record, event));
|
|
479
|
-
if (record.delivery.closed)
|
|
480
|
-
this.#removeDetachedSubscription(record.id);
|
|
481
|
-
},
|
|
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);
|
|
482
481
|
});
|
|
483
|
-
record.delivery.attach(
|
|
484
|
-
|
|
482
|
+
record.delivery.attach(consumer);
|
|
483
|
+
managedChildSubscriptionAccess.installed(record.id);
|
|
485
484
|
}
|
|
486
485
|
#removeSubscription(id) {
|
|
487
486
|
const existing = this.#removals.get(id);
|
|
@@ -521,15 +520,12 @@ export class SpineServices {
|
|
|
521
520
|
async #runRemoval(id, removal, record, unknown) {
|
|
522
521
|
try {
|
|
523
522
|
if (record !== undefined) {
|
|
524
|
-
await
|
|
523
|
+
await boundedContextAccess
|
|
524
|
+
.subscriptionRegistry(record.route.context)
|
|
525
|
+
.delete(create(SubscriptionIdSchema, { value: id }));
|
|
525
526
|
}
|
|
526
527
|
else {
|
|
527
|
-
await
|
|
528
|
-
const registry = this.#subscriptionRegistry(context);
|
|
529
|
-
return registry === undefined
|
|
530
|
-
? []
|
|
531
|
-
: [registry.delete(create(SubscriptionIdSchema, { value: id }))];
|
|
532
|
-
}));
|
|
528
|
+
await this.#deleteUnknownSubscription(id);
|
|
533
529
|
}
|
|
534
530
|
removal.resolve();
|
|
535
531
|
}
|
|
@@ -545,10 +541,25 @@ export class SpineServices {
|
|
|
545
541
|
}
|
|
546
542
|
}
|
|
547
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
|
+
}
|
|
548
556
|
async #findSubscription(id) {
|
|
549
557
|
const subscriptionId = create(SubscriptionIdSchema, { value: id });
|
|
550
558
|
for (const context of ServiceValues.uniqueContexts(this.#contexts)) {
|
|
551
|
-
|
|
559
|
+
let entry;
|
|
560
|
+
await this.#registryOperation(context, async () => {
|
|
561
|
+
entry = await boundedContextAccess.subscriptionRegistry(context).get(subscriptionId);
|
|
562
|
+
});
|
|
552
563
|
const subscription = entry?.subscription;
|
|
553
564
|
const topic = subscription?.topic;
|
|
554
565
|
if (entry === undefined || subscription === undefined || topic === undefined)
|
|
@@ -571,24 +582,15 @@ export class SpineServices {
|
|
|
571
582
|
}
|
|
572
583
|
return undefined;
|
|
573
584
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
// contexts always provide their configured registry through the access seam.
|
|
584
|
-
const key = context;
|
|
585
|
-
let registry = this.#testRegistries.get(key);
|
|
586
|
-
if (registry === undefined) {
|
|
587
|
-
registry = new InMemorySubscriptionRegistry();
|
|
588
|
-
this.#testRegistries.set(key, registry);
|
|
589
|
-
}
|
|
590
|
-
return registry;
|
|
591
|
-
}
|
|
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);
|
|
592
594
|
}
|
|
593
595
|
}
|
|
594
596
|
/**
|
|
@@ -822,7 +824,8 @@ function validateQueryIds(ids, route) {
|
|
|
822
824
|
if (schema === undefined || ids === undefined) {
|
|
823
825
|
return undefined;
|
|
824
826
|
}
|
|
825
|
-
if (ids.some((id) => id === undefined ||
|
|
827
|
+
if (ids.some((id) => id === undefined ||
|
|
828
|
+
(schema.typeName !== AnySchema.typeName && AnyPacker.tryUnpack(id, schema) === undefined))) {
|
|
826
829
|
return invalidQueryError(`QueryService.Read id_filter values must pack ${schema.typeName}.`);
|
|
827
830
|
}
|
|
828
831
|
return undefined;
|
|
@@ -933,7 +936,7 @@ function normalizedFilters(filters, route) {
|
|
|
933
936
|
}
|
|
934
937
|
function decodeQueryIdValue(value, route) {
|
|
935
938
|
const schema = route.idField.message;
|
|
936
|
-
return schema === undefined ? decodeAnyValue(value) :
|
|
939
|
+
return schema === undefined ? decodeAnyValue(value) : decodeAnyValue(value, schema);
|
|
937
940
|
}
|
|
938
941
|
function normalizedComposite(filter, route) {
|
|
939
942
|
const predicates = [
|
|
@@ -965,13 +968,13 @@ function requiredComparison(filter) {
|
|
|
965
968
|
function decodeColumnValue(filter, route) {
|
|
966
969
|
const column = filter.fieldPath?.fieldName[0] ?? "";
|
|
967
970
|
if (column === "version" && filter.value !== undefined) {
|
|
968
|
-
const value =
|
|
971
|
+
const value = AnyPacker.tryUnpack(filter.value, VersionSchema);
|
|
969
972
|
if (value !== undefined)
|
|
970
973
|
return value;
|
|
971
974
|
throw new TypeError('Validated query column "version" has an invalid value.');
|
|
972
975
|
}
|
|
973
976
|
if ((column === "archived" || column === "deleted") && filter.value !== undefined) {
|
|
974
|
-
const value =
|
|
977
|
+
const value = AnyPacker.tryUnpack(filter.value, BoolValueSchema);
|
|
975
978
|
if (value !== undefined)
|
|
976
979
|
return value.value;
|
|
977
980
|
throw new TypeError(`Validated query column "${column}" has an invalid value.`);
|
|
@@ -981,7 +984,8 @@ function decodeColumnValue(filter, route) {
|
|
|
981
984
|
}
|
|
982
985
|
function filterValueMatches(field, value) {
|
|
983
986
|
const schema = filterValueSchema(field);
|
|
984
|
-
if (schema === undefined ||
|
|
987
|
+
if (schema === undefined ||
|
|
988
|
+
(schema.typeName !== AnySchema.typeName && value.typeUrl !== TypeUrls.derive(schema)))
|
|
985
989
|
return false;
|
|
986
990
|
const decoded = field.fieldKind === "message" ? decodeAnyValue(value, schema) : decodeAnyValue(value);
|
|
987
991
|
if (field.fieldKind === "message") {
|
|
@@ -1039,11 +1043,11 @@ function filterValueSchema(field) {
|
|
|
1039
1043
|
function systemValueMatches(column, value) {
|
|
1040
1044
|
if (column === "version") {
|
|
1041
1045
|
return (value.typeUrl === TypeUrls.derive(VersionSchema) &&
|
|
1042
|
-
|
|
1046
|
+
AnyPacker.tryUnpack(value, VersionSchema) !== undefined);
|
|
1043
1047
|
}
|
|
1044
1048
|
if (column === "archived" || column === "deleted") {
|
|
1045
1049
|
return (value.typeUrl === TypeUrls.derive(BoolValueSchema) &&
|
|
1046
|
-
|
|
1050
|
+
AnyPacker.tryUnpack(value, BoolValueSchema) !== undefined);
|
|
1047
1051
|
}
|
|
1048
1052
|
return false;
|
|
1049
1053
|
}
|
|
@@ -1154,7 +1158,7 @@ function validateTopic(topic) {
|
|
|
1154
1158
|
}
|
|
1155
1159
|
}
|
|
1156
1160
|
/**
|
|
1157
|
-
* Validates a topic and
|
|
1161
|
+
* Validates a topic and returns its state or Event subscription shape.
|
|
1158
1162
|
*/
|
|
1159
1163
|
function createSubscriptionShape(topic, route) {
|
|
1160
1164
|
const target = topic.target;
|
|
@@ -1162,21 +1166,18 @@ function createSubscriptionShape(topic, route) {
|
|
|
1162
1166
|
throw new ConnectError("Subscription topic target is required.", Code.InvalidArgument);
|
|
1163
1167
|
}
|
|
1164
1168
|
if (route.kind === "event") {
|
|
1165
|
-
|
|
1166
|
-
return { kind: "event", route
|
|
1169
|
+
validateEventSubscriptionTopic(target);
|
|
1170
|
+
return { kind: "event", route };
|
|
1167
1171
|
}
|
|
1168
1172
|
switch (target.criterion.case) {
|
|
1169
1173
|
case "includeAll":
|
|
1170
1174
|
if (!target.criterion.value) {
|
|
1171
1175
|
throw new ConnectError("SubscriptionService.Subscribe requires filters or include_all = true.", Code.InvalidArgument);
|
|
1172
1176
|
}
|
|
1173
|
-
return { kind: "state", route
|
|
1177
|
+
return { kind: "state", route };
|
|
1174
1178
|
case "filters":
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
route,
|
|
1178
|
-
matcher: createFilteredSubscriptionMatcher(target.criterion.value, route),
|
|
1179
|
-
};
|
|
1179
|
+
ServiceValues.validateFilteredSubscriptionTopic(target.criterion.value, route);
|
|
1180
|
+
return { kind: "state", route };
|
|
1180
1181
|
default:
|
|
1181
1182
|
throw new ConnectError("SubscriptionService.Subscribe requires filters or include_all = true.", Code.InvalidArgument);
|
|
1182
1183
|
}
|
|
@@ -1190,44 +1191,14 @@ function validateEventSubscriptionTopic(target) {
|
|
|
1190
1191
|
if (!target.criterion.value) {
|
|
1191
1192
|
throw new ConnectError("SubscriptionService.Subscribe requires filters or include_all = true.", Code.InvalidArgument);
|
|
1192
1193
|
}
|
|
1193
|
-
return
|
|
1194
|
+
return;
|
|
1194
1195
|
case "filters":
|
|
1195
|
-
|
|
1196
|
+
EventOriginFilters.compile(target.criterion.value);
|
|
1197
|
+
return;
|
|
1196
1198
|
default:
|
|
1197
1199
|
throw new ConnectError("SubscriptionService.Subscribe requires filters or include_all = true.", Code.InvalidArgument);
|
|
1198
1200
|
}
|
|
1199
1201
|
}
|
|
1200
|
-
/**
|
|
1201
|
-
* Matches complete state updates against a topic's ID and field filters.
|
|
1202
|
-
*/
|
|
1203
|
-
function createFilteredSubscriptionMatcher(filters, route) {
|
|
1204
|
-
ServiceValues.validateSubscriptionFilters(filters);
|
|
1205
|
-
const idValues = filters.idFilter?.id.map((id) => decodeSubscriptionIdValue(id, route));
|
|
1206
|
-
const statePredicate = createSubscriptionPredicate(filters.filter, route);
|
|
1207
|
-
return {
|
|
1208
|
-
/**
|
|
1209
|
-
* Matches one state update against the topic's ID and field filters.
|
|
1210
|
-
*
|
|
1211
|
-
* @param update State update to inspect.
|
|
1212
|
-
* @returns Delivery kind, if the update changes a matching state.
|
|
1213
|
-
*/
|
|
1214
|
-
match(update) {
|
|
1215
|
-
if (idValues !== undefined &&
|
|
1216
|
-
!idValues.some((id) => valuesEqual(update.id, id, route.idField.message))) {
|
|
1217
|
-
return undefined;
|
|
1218
|
-
}
|
|
1219
|
-
if (statePredicate(update.state))
|
|
1220
|
-
return "state";
|
|
1221
|
-
if (update.previousState !== undefined && statePredicate(update.previousState))
|
|
1222
|
-
return "noLongerMatching";
|
|
1223
|
-
return undefined;
|
|
1224
|
-
},
|
|
1225
|
-
};
|
|
1226
|
-
}
|
|
1227
|
-
function createSubscriptionPredicate(filters, route) {
|
|
1228
|
-
const predicates = filters.map((filter) => createCompositePredicate(filter, route));
|
|
1229
|
-
return (state) => predicates.every((predicate) => predicate(state));
|
|
1230
|
-
}
|
|
1231
1202
|
function validateSubscriptionFilterTree(filters) {
|
|
1232
1203
|
if (filters.length > SubscriptionLimits.compositeCount) {
|
|
1233
1204
|
throw new ConnectError("SubscriptionService.Subscribe may contain at most 8 composite filters.", Code.InvalidArgument);
|
|
@@ -1284,30 +1255,6 @@ function enqueueSubscriptionFilterChildren(filter, depth, stack) {
|
|
|
1284
1255
|
stack.push({ filter: nested, depth: depth + 1 });
|
|
1285
1256
|
}
|
|
1286
1257
|
}
|
|
1287
|
-
function createCompositePredicate(filter, route) {
|
|
1288
|
-
const children = [
|
|
1289
|
-
...filter.filter.map((simple) => createSimplePredicate(simple, route)),
|
|
1290
|
-
...filter.compositeFilter.map((nested) => createCompositePredicate(nested, route)),
|
|
1291
|
-
];
|
|
1292
|
-
if (children.length === 0) {
|
|
1293
|
-
return () => true;
|
|
1294
|
-
}
|
|
1295
|
-
return filter.operator === CompositeFilter_CompositeOperator.ALL
|
|
1296
|
-
? (state) => children.every((predicate) => predicate(state))
|
|
1297
|
-
: (state) => children.some((predicate) => predicate(state));
|
|
1298
|
-
}
|
|
1299
|
-
function createSimplePredicate(filter, route) {
|
|
1300
|
-
if (filter.operator !== Filter_Operator.EQUAL) {
|
|
1301
|
-
throw new ConnectError("SubscriptionService.Subscribe supports only EQUAL field filters.", Code.InvalidArgument);
|
|
1302
|
-
}
|
|
1303
|
-
if (filter.value === undefined) {
|
|
1304
|
-
throw new ConnectError("SubscriptionService.Subscribe field filter value is required.", Code.InvalidArgument);
|
|
1305
|
-
}
|
|
1306
|
-
validateSubscriptionPath(filter.fieldPath?.fieldName ?? [], "field filter");
|
|
1307
|
-
const resolved = resolveMessagePath(route.schema, filter.fieldPath?.fieldName ?? [], "field filter");
|
|
1308
|
-
const expected = decodeFieldFilterValue(filter.value, resolved.leafSchema);
|
|
1309
|
-
return (state) => valuesEqual(readPathValue(state, resolved.localPath), expected, resolved.leafSchema);
|
|
1310
|
-
}
|
|
1311
1258
|
/**
|
|
1312
1259
|
* Checks a state filter path before resolving its fields.
|
|
1313
1260
|
*/
|
|
@@ -1329,7 +1276,9 @@ function decodeSubscriptionIdValue(value, route) {
|
|
|
1329
1276
|
if (route.idField.message === undefined) {
|
|
1330
1277
|
return decodeAnyValue(value);
|
|
1331
1278
|
}
|
|
1332
|
-
const decoded =
|
|
1279
|
+
const decoded = route.idField.message.typeName === AnySchema.typeName
|
|
1280
|
+
? value
|
|
1281
|
+
: AnyPacker.tryUnpack(value, route.idField.message);
|
|
1333
1282
|
if (decoded === undefined) {
|
|
1334
1283
|
throw new ConnectError(`SubscriptionService.Subscribe id_filter values must pack ${route.idField.message.typeName}.`, Code.InvalidArgument);
|
|
1335
1284
|
}
|
|
@@ -1339,7 +1288,7 @@ function decodeFieldFilterValue(value, schema) {
|
|
|
1339
1288
|
if (value === undefined || schema === undefined) {
|
|
1340
1289
|
return decodeAnyValue(value);
|
|
1341
1290
|
}
|
|
1342
|
-
const decoded =
|
|
1291
|
+
const decoded = schema.typeName === AnySchema.typeName ? value : AnyPacker.tryUnpack(value, schema);
|
|
1343
1292
|
if (decoded === undefined) {
|
|
1344
1293
|
throw new ConnectError(`SubscriptionService.Subscribe field filter value must pack ${schema.typeName}.`, Code.InvalidArgument);
|
|
1345
1294
|
}
|
|
@@ -1350,7 +1299,9 @@ function decodeAnyValue(value, schema) {
|
|
|
1350
1299
|
return undefined;
|
|
1351
1300
|
}
|
|
1352
1301
|
if (schema !== undefined) {
|
|
1353
|
-
|
|
1302
|
+
if (schema.typeName === AnySchema.typeName)
|
|
1303
|
+
return value;
|
|
1304
|
+
const decoded = AnyPacker.tryUnpack(value, schema);
|
|
1354
1305
|
if (decoded !== undefined) {
|
|
1355
1306
|
return decoded;
|
|
1356
1307
|
}
|
|
@@ -1367,7 +1318,6 @@ function decodeAnyValue(value, schema) {
|
|
|
1367
1318
|
* Resolves a state filter path to generated message fields.
|
|
1368
1319
|
*/
|
|
1369
1320
|
function resolveMessagePath(schema, fieldPath, label) {
|
|
1370
|
-
const localPath = [];
|
|
1371
1321
|
let currentSchema = schema;
|
|
1372
1322
|
let leafSchema;
|
|
1373
1323
|
for (const [index, name] of fieldPath.entries()) {
|
|
@@ -1375,7 +1325,6 @@ function resolveMessagePath(schema, fieldPath, label) {
|
|
|
1375
1325
|
if (field === undefined) {
|
|
1376
1326
|
throw new ConnectError(`SubscriptionService.Subscribe ${label} "${fieldPath.join(".")}" is not a state field.`, Code.InvalidArgument);
|
|
1377
1327
|
}
|
|
1378
|
-
localPath.push(field.localName);
|
|
1379
1328
|
leafSchema = field.message;
|
|
1380
1329
|
currentSchema = index === fieldPath.length - 1 ? undefined : field.message;
|
|
1381
1330
|
if (currentSchema === undefined && index < fieldPath.length - 1) {
|
|
@@ -1383,7 +1332,6 @@ function resolveMessagePath(schema, fieldPath, label) {
|
|
|
1383
1332
|
}
|
|
1384
1333
|
}
|
|
1385
1334
|
return Object.freeze({
|
|
1386
|
-
localPath,
|
|
1387
1335
|
...(leafSchema === undefined ? {} : { leafSchema }),
|
|
1388
1336
|
});
|
|
1389
1337
|
}
|
|
@@ -1391,37 +1339,12 @@ function findMessageField(schema, name) {
|
|
|
1391
1339
|
const fields = (schema?.fields ?? []);
|
|
1392
1340
|
return fields.find((field) => field.name === name || field.localName === name);
|
|
1393
1341
|
}
|
|
1394
|
-
function readPathValue(value, path) {
|
|
1395
|
-
let current = value;
|
|
1396
|
-
for (const segment of path) {
|
|
1397
|
-
if (typeof current !== "object" || current === null) {
|
|
1398
|
-
return undefined;
|
|
1399
|
-
}
|
|
1400
|
-
current = Reflect.get(current, segment);
|
|
1401
|
-
}
|
|
1402
|
-
return current;
|
|
1403
|
-
}
|
|
1404
|
-
function valuesEqual(actual, expected, schema) {
|
|
1405
|
-
if (schema !== undefined && isMessage(actual) && isMessage(expected)) {
|
|
1406
|
-
return bytesEqual(toBinary(schema, actual), toBinary(schema, expected));
|
|
1407
|
-
}
|
|
1408
|
-
if (isAny(actual) && isAny(expected)) {
|
|
1409
|
-
return bytesEqual(toBinary(AnySchema, actual), toBinary(AnySchema, expected));
|
|
1410
|
-
}
|
|
1411
|
-
if (actual instanceof Uint8Array && expected instanceof Uint8Array) {
|
|
1412
|
-
return bytesEqual(actual, expected);
|
|
1413
|
-
}
|
|
1414
|
-
return Object.is(actual, expected);
|
|
1415
|
-
}
|
|
1416
1342
|
function isMessage(value) {
|
|
1417
1343
|
return typeof value === "object" && value !== null && "$typeName" in value;
|
|
1418
1344
|
}
|
|
1419
1345
|
function isAny(value) {
|
|
1420
1346
|
return isMessage(value) && value.$typeName === "google.protobuf.Any";
|
|
1421
1347
|
}
|
|
1422
|
-
function bytesEqual(left, right) {
|
|
1423
|
-
return left.length === right.length && left.every((byte, index) => byte === right[index]);
|
|
1424
|
-
}
|
|
1425
1348
|
/**
|
|
1426
1349
|
* Packs a state snapshot and version for a public query result.
|
|
1427
1350
|
*
|
|
@@ -1432,7 +1355,7 @@ function bytesEqual(left, right) {
|
|
|
1432
1355
|
*/
|
|
1433
1356
|
function packVersionedState(schema, result) {
|
|
1434
1357
|
return create(EntityStateVersionSchema, {
|
|
1435
|
-
state:
|
|
1358
|
+
state: AnyPacker.pack(schema, result.state),
|
|
1436
1359
|
version: result.version ?? create(VersionSchema),
|
|
1437
1360
|
});
|
|
1438
1361
|
}
|
|
@@ -1442,22 +1365,6 @@ function commandTenant(command) {
|
|
|
1442
1365
|
function topicTenant(topic) {
|
|
1443
1366
|
return tenantValue(topic?.context?.tenantId);
|
|
1444
1367
|
}
|
|
1445
|
-
function eventTenant(event) {
|
|
1446
|
-
switch (event.context?.origin.case) {
|
|
1447
|
-
case "importContext":
|
|
1448
|
-
return tenantValue(event.context.origin.value.tenantId);
|
|
1449
|
-
case "pastMessage":
|
|
1450
|
-
return tenantValue(event.context.origin.value.actorContext?.tenantId);
|
|
1451
|
-
default:
|
|
1452
|
-
return undefined;
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
function eventTenantMatches(record, event) {
|
|
1456
|
-
const tenantId = eventTenant(event);
|
|
1457
|
-
return (record.tenantId === undefined ||
|
|
1458
|
-
(tenantId !== undefined &&
|
|
1459
|
-
TenantBoundary.from(tenantId).key === TenantBoundary.from(record.tenantId).key));
|
|
1460
|
-
}
|
|
1461
1368
|
function tenantValue(tenantId) {
|
|
1462
1369
|
if (tenantId === undefined)
|
|
1463
1370
|
return undefined;
|
|
@@ -1478,6 +1385,53 @@ function stateRouteIdField(schema, idField) {
|
|
|
1478
1385
|
}
|
|
1479
1386
|
return field;
|
|
1480
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
|
+
});
|
|
1481
1435
|
function tenantMismatch(multitenant, tenantId, subject) {
|
|
1482
1436
|
if (multitenant) {
|
|
1483
1437
|
return tenantId === undefined
|
|
@@ -1499,18 +1453,14 @@ function commandPostError(error) {
|
|
|
1499
1453
|
return {
|
|
1500
1454
|
type: error.type,
|
|
1501
1455
|
message: error.clientMessage,
|
|
1502
|
-
details:
|
|
1503
|
-
validate: false,
|
|
1504
|
-
}),
|
|
1456
|
+
details: AnyPacker.pack(ValidationErrorSchema, error.validationError),
|
|
1505
1457
|
};
|
|
1506
1458
|
}
|
|
1507
1459
|
if (error instanceof CommandValidationError) {
|
|
1508
1460
|
return {
|
|
1509
1461
|
type: "COMMAND_VALIDATION_ERROR",
|
|
1510
1462
|
message: "Command payload validation failed.",
|
|
1511
|
-
details:
|
|
1512
|
-
validate: false,
|
|
1513
|
-
}),
|
|
1463
|
+
details: AnyPacker.pack(ValidationErrorSchema, error.validationError),
|
|
1514
1464
|
};
|
|
1515
1465
|
}
|
|
1516
1466
|
return {
|
|
@@ -1578,104 +1528,17 @@ const InternalErrorFacts = Object.freeze({
|
|
|
1578
1528
|
}
|
|
1579
1529
|
},
|
|
1580
1530
|
});
|
|
1581
|
-
function tenantOptions(tenantId) {
|
|
1582
|
-
return tenantId === undefined ? {} : { tenantId: clone(TenantIdSchema, tenantId) };
|
|
1583
|
-
}
|
|
1584
1531
|
const VALUE_DECODERS = Object.freeze([
|
|
1585
|
-
(value) =>
|
|
1586
|
-
(value) =>
|
|
1587
|
-
(value) =>
|
|
1588
|
-
(value) =>
|
|
1589
|
-
(value) =>
|
|
1590
|
-
(value) =>
|
|
1591
|
-
(value) =>
|
|
1592
|
-
(value) =>
|
|
1593
|
-
(value) =>
|
|
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,
|
|
1594
1541
|
]);
|
|
1595
|
-
/**
|
|
1596
|
-
* Builds a subscription update with a complete state or a removal signal.
|
|
1597
|
-
*/
|
|
1598
|
-
function createEntityUpdate(record, update) {
|
|
1599
|
-
const matcher = record.matcher;
|
|
1600
|
-
if (matcher === undefined)
|
|
1601
|
-
return undefined;
|
|
1602
|
-
const match = matcher.match(update);
|
|
1603
|
-
if (match === undefined)
|
|
1604
|
-
return undefined;
|
|
1605
|
-
const kind = match === "state"
|
|
1606
|
-
? {
|
|
1607
|
-
case: "state",
|
|
1608
|
-
value: AnyMessages.pack(record.route.schema, clone(record.route.schema, update.state), {
|
|
1609
|
-
validate: false,
|
|
1610
|
-
}),
|
|
1611
|
-
}
|
|
1612
|
-
: { case: "noLongerMatching", value: true };
|
|
1613
|
-
return create(SubscriptionUpdateSchema, {
|
|
1614
|
-
subscription: clone(SubscriptionSchema, record.subscription),
|
|
1615
|
-
response: okResponse(),
|
|
1616
|
-
update: {
|
|
1617
|
-
case: "entityUpdates",
|
|
1618
|
-
value: create(EntityUpdatesSchema, {
|
|
1619
|
-
update: [
|
|
1620
|
-
create(EntityStateUpdateSchema, {
|
|
1621
|
-
id: packEntityId(record.route, update.id),
|
|
1622
|
-
kind,
|
|
1623
|
-
}),
|
|
1624
|
-
],
|
|
1625
|
-
}),
|
|
1626
|
-
},
|
|
1627
|
-
});
|
|
1628
|
-
}
|
|
1629
|
-
function createEventUpdate(record, event) {
|
|
1630
|
-
return create(SubscriptionUpdateSchema, {
|
|
1631
|
-
subscription: clone(SubscriptionSchema, record.subscription),
|
|
1632
|
-
response: okResponse(),
|
|
1633
|
-
update: {
|
|
1634
|
-
case: "eventUpdates",
|
|
1635
|
-
value: create(EventUpdatesSchema, {
|
|
1636
|
-
event: [cloneClientEvent(event)],
|
|
1637
|
-
}),
|
|
1638
|
-
},
|
|
1639
|
-
});
|
|
1640
|
-
}
|
|
1641
|
-
function cloneClientEvent(event) {
|
|
1642
|
-
const clientEvent = clone(EventSchema, event);
|
|
1643
|
-
const rejection = clientEvent.context?.rejection;
|
|
1644
|
-
if (rejection !== undefined) {
|
|
1645
|
-
rejection.command = undefined;
|
|
1646
|
-
// Clear the legacy wire payload as part of client-side security redaction.
|
|
1647
|
-
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
1648
|
-
rejection.commandMessage = undefined;
|
|
1649
|
-
rejection.stacktrace = "";
|
|
1650
|
-
}
|
|
1651
|
-
return clientEvent;
|
|
1652
|
-
}
|
|
1653
|
-
function packString(value) {
|
|
1654
|
-
return AnyMessages.pack(StringValueSchema, create(StringValueSchema, { value }));
|
|
1655
|
-
}
|
|
1656
|
-
function packEntityId(route, id) {
|
|
1657
|
-
if (isAny(id)) {
|
|
1658
|
-
return clone(AnySchema, id);
|
|
1659
|
-
}
|
|
1660
|
-
if (route.idField.message !== undefined && isMessage(id)) {
|
|
1661
|
-
return AnyMessages.pack(route.idField.message, id, { validate: false });
|
|
1662
|
-
}
|
|
1663
|
-
if (id instanceof Uint8Array) {
|
|
1664
|
-
return AnyMessages.pack(BytesValueSchema, create(BytesValueSchema, { value: id }));
|
|
1665
|
-
}
|
|
1666
|
-
switch (typeof id) {
|
|
1667
|
-
case "string":
|
|
1668
|
-
return packString(id);
|
|
1669
|
-
case "boolean":
|
|
1670
|
-
return AnyMessages.pack(BoolValueSchema, create(BoolValueSchema, { value: id }));
|
|
1671
|
-
case "number":
|
|
1672
|
-
return AnyMessages.pack(DoubleValueSchema, create(DoubleValueSchema, { value: id }));
|
|
1673
|
-
case "bigint":
|
|
1674
|
-
return AnyMessages.pack(Int64ValueSchema, create(Int64ValueSchema, { value: id }));
|
|
1675
|
-
default:
|
|
1676
|
-
return undefined;
|
|
1677
|
-
}
|
|
1678
|
-
}
|
|
1679
1542
|
/**
|
|
1680
1543
|
* Groups non-public service contract, query, and subscription operations.
|
|
1681
1544
|
*/
|
|
@@ -1733,6 +1596,48 @@ const ServiceValues = Object.freeze({
|
|
|
1733
1596
|
}
|
|
1734
1597
|
return undefined;
|
|
1735
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
|
+
},
|
|
1736
1641
|
/**
|
|
1737
1642
|
* Checks that a state topic has a usable ID or field filter.
|
|
1738
1643
|
*
|
|
@@ -1763,8 +1668,6 @@ const ServiceValues = Object.freeze({
|
|
|
1763
1668
|
return create(AckSchema, { messageId, status: errorStatus(type, message) });
|
|
1764
1669
|
},
|
|
1765
1670
|
commandTenant,
|
|
1766
|
-
createEntityUpdate,
|
|
1767
|
-
createEventUpdate,
|
|
1768
1671
|
createReadPlan,
|
|
1769
1672
|
createSubscriptionRecord,
|
|
1770
1673
|
createSubscriptionRemoval,
|
|
@@ -1772,7 +1675,6 @@ const ServiceValues = Object.freeze({
|
|
|
1772
1675
|
defaultQueueLimit: DEFAULT_QUEUE_LIMIT,
|
|
1773
1676
|
defaultSubscriptionLimit: DEFAULT_SUBSCRIPTION_LIMIT,
|
|
1774
1677
|
errorStatus,
|
|
1775
|
-
eventTenantMatches,
|
|
1776
1678
|
okResponse,
|
|
1777
1679
|
okStatus,
|
|
1778
1680
|
positiveInteger,
|
|
@@ -1782,7 +1684,6 @@ const ServiceValues = Object.freeze({
|
|
|
1782
1684
|
stateRouteIdField,
|
|
1783
1685
|
subscriptionLimit,
|
|
1784
1686
|
tenantMismatch,
|
|
1785
|
-
tenantOptions,
|
|
1786
1687
|
tenantValue,
|
|
1787
1688
|
topicTenant,
|
|
1788
1689
|
uniqueContexts,
|