solid-objects 0.12.0
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/CHANGELOG.md +110 -0
- package/MIT-LICENSE +19 -0
- package/README.md +958 -0
- package/dist/actor.d.ts +81 -0
- package/dist/actor.d.ts.map +1 -0
- package/dist/actor.js +141 -0
- package/dist/actor.js.map +1 -0
- package/dist/application-database.d.ts +3 -0
- package/dist/application-database.d.ts.map +1 -0
- package/dist/application-database.js +59 -0
- package/dist/application-database.js.map +1 -0
- package/dist/broadcast-worker.d.ts +18 -0
- package/dist/broadcast-worker.d.ts.map +1 -0
- package/dist/broadcast-worker.js +74 -0
- package/dist/broadcast-worker.js.map +1 -0
- package/dist/browser/components.d.ts +64 -0
- package/dist/browser/components.d.ts.map +1 -0
- package/dist/browser/components.js +294 -0
- package/dist/browser/components.js.map +1 -0
- package/dist/browser/index.d.ts +46 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +207 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +216 -0
- package/dist/cli.js.map +1 -0
- package/dist/configuration.d.ts +96 -0
- package/dist/configuration.d.ts.map +1 -0
- package/dist/configuration.js +169 -0
- package/dist/configuration.js.map +1 -0
- package/dist/context.d.ts +20 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +24 -0
- package/dist/context.js.map +1 -0
- package/dist/database/deadline.d.ts +9 -0
- package/dist/database/deadline.d.ts.map +1 -0
- package/dist/database/deadline.js +49 -0
- package/dist/database/deadline.js.map +1 -0
- package/dist/database/mysql.d.ts +21 -0
- package/dist/database/mysql.d.ts.map +1 -0
- package/dist/database/mysql.js +192 -0
- package/dist/database/mysql.js.map +1 -0
- package/dist/database/postgresql-sql.d.ts +2 -0
- package/dist/database/postgresql-sql.d.ts.map +1 -0
- package/dist/database/postgresql-sql.js +102 -0
- package/dist/database/postgresql-sql.js.map +1 -0
- package/dist/database/postgresql.d.ts +31 -0
- package/dist/database/postgresql.d.ts.map +1 -0
- package/dist/database/postgresql.js +172 -0
- package/dist/database/postgresql.js.map +1 -0
- package/dist/database/sqlite.d.ts +27 -0
- package/dist/database/sqlite.d.ts.map +1 -0
- package/dist/database/sqlite.js +190 -0
- package/dist/database/sqlite.js.map +1 -0
- package/dist/database/transaction-context.d.ts +3 -0
- package/dist/database/transaction-context.d.ts.map +1 -0
- package/dist/database/transaction-context.js +19 -0
- package/dist/database/transaction-context.js.map +1 -0
- package/dist/database/types.d.ts +20 -0
- package/dist/database/types.d.ts.map +1 -0
- package/dist/database/types.js +2 -0
- package/dist/database/types.js.map +1 -0
- package/dist/dead-letters.d.ts +23 -0
- package/dist/dead-letters.d.ts.map +1 -0
- package/dist/dead-letters.js +13 -0
- package/dist/dead-letters.js.map +1 -0
- package/dist/default-runtime.d.ts +5 -0
- package/dist/default-runtime.d.ts.map +1 -0
- package/dist/default-runtime.js +14 -0
- package/dist/default-runtime.js.map +1 -0
- package/dist/definition.d.ts +32 -0
- package/dist/definition.d.ts.map +1 -0
- package/dist/definition.js +173 -0
- package/dist/definition.js.map +1 -0
- package/dist/doctor.d.ts +32 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +500 -0
- package/dist/doctor.js.map +1 -0
- package/dist/effect-worker.d.ts +18 -0
- package/dist/effect-worker.d.ts.map +1 -0
- package/dist/effect-worker.js +74 -0
- package/dist/effect-worker.js.map +1 -0
- package/dist/errors.d.ts +145 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +120 -0
- package/dist/errors.js.map +1 -0
- package/dist/executable.d.ts +3 -0
- package/dist/executable.d.ts.map +1 -0
- package/dist/executable.js +12 -0
- package/dist/executable.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/maintenance-scheduler.d.ts +17 -0
- package/dist/maintenance-scheduler.d.ts.map +1 -0
- package/dist/maintenance-scheduler.js +41 -0
- package/dist/maintenance-scheduler.js.map +1 -0
- package/dist/process-administration.d.ts +30 -0
- package/dist/process-administration.d.ts.map +1 -0
- package/dist/process-administration.js +13 -0
- package/dist/process-administration.js.map +1 -0
- package/dist/realtime.d.ts +33 -0
- package/dist/realtime.d.ts.map +1 -0
- package/dist/realtime.js +238 -0
- package/dist/realtime.js.map +1 -0
- package/dist/reconciliation.d.ts +41 -0
- package/dist/reconciliation.d.ts.map +1 -0
- package/dist/reconciliation.js +19 -0
- package/dist/reconciliation.js.map +1 -0
- package/dist/records.d.ts +137 -0
- package/dist/records.d.ts.map +1 -0
- package/dist/records.js +2 -0
- package/dist/records.js.map +1 -0
- package/dist/reference.d.ts +97 -0
- package/dist/reference.d.ts.map +1 -0
- package/dist/reference.js +157 -0
- package/dist/reference.js.map +1 -0
- package/dist/reminder-administration.d.ts +35 -0
- package/dist/reminder-administration.d.ts.map +1 -0
- package/dist/reminder-administration.js +13 -0
- package/dist/reminder-administration.js.map +1 -0
- package/dist/reminder-scheduler.d.ts +18 -0
- package/dist/reminder-scheduler.d.ts.map +1 -0
- package/dist/reminder-scheduler.js +88 -0
- package/dist/reminder-scheduler.js.map +1 -0
- package/dist/repository.d.ts +142 -0
- package/dist/repository.d.ts.map +1 -0
- package/dist/repository.js +1237 -0
- package/dist/repository.js.map +1 -0
- package/dist/retention.d.ts +17 -0
- package/dist/retention.d.ts.map +1 -0
- package/dist/retention.js +13 -0
- package/dist/retention.js.map +1 -0
- package/dist/runtime.d.ts +140 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +1737 -0
- package/dist/runtime.js.map +1 -0
- package/dist/schema.d.ts +8 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +260 -0
- package/dist/schema.js.map +1 -0
- package/dist/serialization.d.ts +11 -0
- package/dist/serialization.d.ts.map +1 -0
- package/dist/serialization.js +82 -0
- package/dist/serialization.js.map +1 -0
- package/dist/test-helper.d.ts +15 -0
- package/dist/test-helper.d.ts.map +1 -0
- package/dist/test-helper.js +69 -0
- package/dist/test-helper.js.map +1 -0
- package/dist/types.d.ts +71 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/dist/wake-up/postgresql.d.ts +42 -0
- package/dist/wake-up/postgresql.d.ts.map +1 -0
- package/dist/wake-up/postgresql.js +234 -0
- package/dist/wake-up/postgresql.js.map +1 -0
- package/dist/wake-up/redis.d.ts +35 -0
- package/dist/wake-up/redis.d.ts.map +1 -0
- package/dist/wake-up/redis.js +151 -0
- package/dist/wake-up/redis.js.map +1 -0
- package/dist/wake-up.d.ts +24 -0
- package/dist/wake-up.d.ts.map +1 -0
- package/dist/wake-up.js +66 -0
- package/dist/wake-up.js.map +1 -0
- package/dist/worker.d.ts +36 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +281 -0
- package/dist/worker.js.map +1 -0
- package/docs/api.md +197 -0
- package/docs/architecture.md +113 -0
- package/docs/authorization.md +31 -0
- package/docs/browser-protocol.md +93 -0
- package/docs/configuration.md +168 -0
- package/docs/correctness.md +53 -0
- package/docs/errors-and-recovery.md +89 -0
- package/docs/operations.md +170 -0
- package/docs/parity.md +94 -0
- package/docs/releasing.md +45 -0
- package/docs/state-and-lifecycle.md +162 -0
- package/package.json +100 -0
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,1737 @@
|
|
|
1
|
+
import { BroadcastWorker } from "./broadcast-worker.js";
|
|
2
|
+
import { buildSettings, broadcastsEnabled, validateComponent, } from "./configuration.js";
|
|
3
|
+
import { currentActor, withActorContext, withActorProjection, withApplicationWritesForbidden, } from "./context.js";
|
|
4
|
+
import { DeadLetterManager } from "./dead-letters.js";
|
|
5
|
+
import { Doctor } from "./doctor.js";
|
|
6
|
+
import { clearDefaultRuntime, setDefaultRuntime } from "./default-runtime.js";
|
|
7
|
+
import { actorState, hydrateActor, initialStateFor, migrateState, validateDefinition, } from "./definition.js";
|
|
8
|
+
import { ActorCallCycle, ActorSetupFailed, ActorDestroyed, DatabaseDeadlineExceeded, InvalidPayloadBroadcast, InvalidActor, LostActivation, MessageFailed, NonRetryableError, QueryMutatedState, Rejected, SyncTimeout, SyncEnqueueTimeout, SyncInsideTransaction, Unauthorized, UnknownCommitAction, UnknownDeadLetter, UnknownEffect, UnknownActorType, UnknownOperation, UnknownPayloadBroadcast, } from "./errors.js";
|
|
9
|
+
import { ActorReferenceCore, createActorReference, MessageReference, } from "./reference.js";
|
|
10
|
+
import { ReconciliationManager, } from "./reconciliation.js";
|
|
11
|
+
import { Repository } from "./repository.js";
|
|
12
|
+
import { ProcessManager, } from "./process-administration.js";
|
|
13
|
+
import { RealtimeManager } from "./realtime.js";
|
|
14
|
+
import { MaintenanceScheduler } from "./maintenance-scheduler.js";
|
|
15
|
+
import { ReminderScheduler } from "./reminder-scheduler.js";
|
|
16
|
+
import { ReminderManager, } from "./reminder-administration.js";
|
|
17
|
+
import { RetentionManager } from "./retention.js";
|
|
18
|
+
import { deepCopy, jsonObject, normalizeJson, readonlyCopy, stableJson } from "./serialization.js";
|
|
19
|
+
import { installSchema } from "./schema.js";
|
|
20
|
+
import { SolidObjectsTestHelper } from "./test-helper.js";
|
|
21
|
+
import { waitFor, Worker } from "./worker.js";
|
|
22
|
+
import { EffectWorker } from "./effect-worker.js";
|
|
23
|
+
import { withDatabaseDeadline } from "./database/deadline.js";
|
|
24
|
+
export class SolidObjectsRuntime {
|
|
25
|
+
settings;
|
|
26
|
+
repository;
|
|
27
|
+
deadLetters;
|
|
28
|
+
reconciliation;
|
|
29
|
+
retention;
|
|
30
|
+
doctor;
|
|
31
|
+
testing;
|
|
32
|
+
reminders;
|
|
33
|
+
realtime;
|
|
34
|
+
processes;
|
|
35
|
+
registry = new Map();
|
|
36
|
+
effects = new Map();
|
|
37
|
+
commitActions = new Map();
|
|
38
|
+
additionalComponents = [];
|
|
39
|
+
callerWorker;
|
|
40
|
+
running = false;
|
|
41
|
+
constructor(configuration) {
|
|
42
|
+
this.settings = buildSettings(configuration);
|
|
43
|
+
this.repository = new Repository(this.settings);
|
|
44
|
+
this.deadLetters = new DeadLetterManager(this);
|
|
45
|
+
this.reconciliation = new ReconciliationManager(this);
|
|
46
|
+
this.retention = new RetentionManager(this);
|
|
47
|
+
this.doctor = new Doctor(this);
|
|
48
|
+
this.testing = new SolidObjectsTestHelper(this);
|
|
49
|
+
this.reminders = new ReminderManager(this);
|
|
50
|
+
this.realtime = new RealtimeManager(this);
|
|
51
|
+
this.processes = new ProcessManager(this);
|
|
52
|
+
}
|
|
53
|
+
async install() {
|
|
54
|
+
await this.settings.database.transaction((connection) => installSchema({
|
|
55
|
+
connection,
|
|
56
|
+
family: this.settings.database.family,
|
|
57
|
+
prefix: this.settings.tableNamePrefix,
|
|
58
|
+
schemaIdentity: this.settings.database.schemaIdentity,
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
register(actorClass) {
|
|
62
|
+
const existing = this.registry.get(actorClass.actorType);
|
|
63
|
+
if (existing?.actorClass === actorClass)
|
|
64
|
+
return actorClass;
|
|
65
|
+
const definition = validateDefinition(actorClass);
|
|
66
|
+
const conflicting = this.registry.get(definition.type);
|
|
67
|
+
if (conflicting && conflicting.actorClass !== actorClass) {
|
|
68
|
+
throw new InvalidActor(`actor type ${definition.type} is already registered`);
|
|
69
|
+
}
|
|
70
|
+
const operations = new Set(definition.operations);
|
|
71
|
+
const queries = new Set(definition.queries);
|
|
72
|
+
this.registry.set(definition.type, { actorClass, definition, operations, queries });
|
|
73
|
+
return actorClass;
|
|
74
|
+
}
|
|
75
|
+
ref(actorClass, actorId) {
|
|
76
|
+
this.register(actorClass);
|
|
77
|
+
const entry = this.registry.get(actorClass.actorType);
|
|
78
|
+
if (!entry)
|
|
79
|
+
throw new UnknownActorType(actorClass.actorType);
|
|
80
|
+
return createActorReference({
|
|
81
|
+
runtime: this,
|
|
82
|
+
actorClass,
|
|
83
|
+
actorType: entry.definition.type,
|
|
84
|
+
actorId: String(actorId),
|
|
85
|
+
operations: entry.operations,
|
|
86
|
+
queries: entry.queries,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
registerEffect(name, handler) {
|
|
90
|
+
if (this.effects.has(name))
|
|
91
|
+
throw new TypeError(`effect ${name} is already registered`);
|
|
92
|
+
this.effects.set(name, handler);
|
|
93
|
+
}
|
|
94
|
+
registerCommitAction(name, handler) {
|
|
95
|
+
if (this.commitActions.has(name))
|
|
96
|
+
throw new TypeError(`commit action ${name} is already registered`);
|
|
97
|
+
this.commitActions.set(name, handler);
|
|
98
|
+
}
|
|
99
|
+
registerComponent(factory, options = {}) {
|
|
100
|
+
const count = options.count ?? 1;
|
|
101
|
+
if (!Number.isSafeInteger(count) || count < 1)
|
|
102
|
+
throw new TypeError("component count must be positive");
|
|
103
|
+
this.additionalComponents.push({ count, factory });
|
|
104
|
+
}
|
|
105
|
+
worker() {
|
|
106
|
+
return new Worker(this);
|
|
107
|
+
}
|
|
108
|
+
effectWorker() {
|
|
109
|
+
return new EffectWorker(this);
|
|
110
|
+
}
|
|
111
|
+
reminderScheduler() {
|
|
112
|
+
return new ReminderScheduler(this);
|
|
113
|
+
}
|
|
114
|
+
broadcastWorker() {
|
|
115
|
+
if (!broadcastsEnabled(this.settings))
|
|
116
|
+
throw new TypeError("realtime delivery is not configured");
|
|
117
|
+
return new BroadcastWorker(this);
|
|
118
|
+
}
|
|
119
|
+
async run(signal) {
|
|
120
|
+
if (this.running)
|
|
121
|
+
throw new Error("Solid Objects runtime is already running");
|
|
122
|
+
if (signal.aborted)
|
|
123
|
+
return;
|
|
124
|
+
this.running = true;
|
|
125
|
+
this.emitInstrumentation("runtime.started", {
|
|
126
|
+
workerCount: this.settings.workerCount,
|
|
127
|
+
effectWorkerCount: this.settings.effectWorkerCount,
|
|
128
|
+
reminderSchedulerCount: this.settings.reminderSchedulerCount,
|
|
129
|
+
broadcastWorkerCount: broadcastsEnabled(this.settings)
|
|
130
|
+
? this.settings.broadcastWorkerCount
|
|
131
|
+
: 0,
|
|
132
|
+
retentionEnabled: this.settings.retentionIntervalMilliseconds > 0,
|
|
133
|
+
deadProcessCleanupEnabled: this.settings.deadProcessCleanupIntervalMilliseconds > 0,
|
|
134
|
+
});
|
|
135
|
+
const controller = new AbortController();
|
|
136
|
+
let shutdownDeadline;
|
|
137
|
+
const abort = () => {
|
|
138
|
+
shutdownDeadline ??= performance.now() + this.settings.shutdownTimeoutMilliseconds;
|
|
139
|
+
controller.abort(signal.reason);
|
|
140
|
+
};
|
|
141
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
142
|
+
let slots = [];
|
|
143
|
+
try {
|
|
144
|
+
slots = await this.buildComponentSlots();
|
|
145
|
+
await Promise.all(slots.map((slot) => this.supervise({
|
|
146
|
+
slot,
|
|
147
|
+
signal: controller.signal,
|
|
148
|
+
shutdownDeadline: () => shutdownDeadline,
|
|
149
|
+
})));
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
abort();
|
|
153
|
+
await Promise.allSettled(slots
|
|
154
|
+
.filter(({ cleanupAttempted }) => !cleanupAttempted)
|
|
155
|
+
.map(({ component }) => this.stopComponent(component, {
|
|
156
|
+
shutdown: {
|
|
157
|
+
signal: controller.signal,
|
|
158
|
+
deadline: () => shutdownDeadline,
|
|
159
|
+
},
|
|
160
|
+
})));
|
|
161
|
+
signal.removeEventListener("abort", abort);
|
|
162
|
+
this.running = false;
|
|
163
|
+
this.emitInstrumentation("runtime.stopped", {});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async sendMessage(options) {
|
|
167
|
+
if (currentActor()) {
|
|
168
|
+
throw new ActorCallCycle("actors must use this.sendTo(reference) for transactional delivery");
|
|
169
|
+
}
|
|
170
|
+
const { reference, operation, argumentsValue = {}, options: invocationOptions = {} } = options;
|
|
171
|
+
const actor = this.fetchActor(reference.actorType);
|
|
172
|
+
if (!actor.operations.has(operation)) {
|
|
173
|
+
throw new UnknownOperation(`unknown operation ${JSON.stringify(operation)}`);
|
|
174
|
+
}
|
|
175
|
+
const argumentsObject = jsonObject(argumentsValue, { maxBytes: this.settings.maxPayloadBytes });
|
|
176
|
+
await this.authorize({
|
|
177
|
+
kind: "message",
|
|
178
|
+
reference,
|
|
179
|
+
operation,
|
|
180
|
+
argumentsValue: argumentsObject,
|
|
181
|
+
authorizationContext: invocationOptions.authorizationContext,
|
|
182
|
+
});
|
|
183
|
+
return this.enqueue({
|
|
184
|
+
reference,
|
|
185
|
+
operation,
|
|
186
|
+
deliveryMode: "async",
|
|
187
|
+
argumentsValue: argumentsObject,
|
|
188
|
+
options: invocationOptions,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
async invoke(options) {
|
|
192
|
+
if (currentActor())
|
|
193
|
+
throw new ActorCallCycle("actors cannot synchronously wait for another actor");
|
|
194
|
+
const { reference, operation, argumentsValue = {}, options: invocationOptions = {} } = options;
|
|
195
|
+
const actor = this.fetchActor(reference.actorType);
|
|
196
|
+
const query = this.isQuery(actor.definition, operation);
|
|
197
|
+
const message = actor.operations.has(operation);
|
|
198
|
+
if (!query && !message)
|
|
199
|
+
throw new UnknownOperation(`unknown actor operation ${JSON.stringify(operation)}`);
|
|
200
|
+
const argumentsObject = jsonObject(argumentsValue, { maxBytes: this.settings.maxPayloadBytes });
|
|
201
|
+
await this.authorize({
|
|
202
|
+
kind: query ? "query" : "message",
|
|
203
|
+
reference,
|
|
204
|
+
operation,
|
|
205
|
+
argumentsValue: argumentsObject,
|
|
206
|
+
authorizationContext: invocationOptions.authorizationContext,
|
|
207
|
+
});
|
|
208
|
+
if (this.settings.database.transactionActive?.()) {
|
|
209
|
+
throw new SyncInsideTransaction({
|
|
210
|
+
actorType: reference.actorType,
|
|
211
|
+
actorId: reference.actorId,
|
|
212
|
+
operation,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
const timeout = invocationTimeout(invocationOptions);
|
|
216
|
+
const deadline = performance.now() + timeout;
|
|
217
|
+
let messageReference;
|
|
218
|
+
try {
|
|
219
|
+
messageReference = await withDatabaseDeadline({ timeoutMilliseconds: timeout }, () => this.enqueue({
|
|
220
|
+
reference,
|
|
221
|
+
operation,
|
|
222
|
+
deliveryMode: "sync",
|
|
223
|
+
argumentsValue: argumentsObject,
|
|
224
|
+
options: invocationOptions,
|
|
225
|
+
}));
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
if (!(error instanceof DatabaseDeadlineExceeded))
|
|
229
|
+
throw error;
|
|
230
|
+
this.emitInstrumentation("sync.enqueue_timeout", {
|
|
231
|
+
actorType: reference.actorType,
|
|
232
|
+
actorId: reference.actorId,
|
|
233
|
+
operation,
|
|
234
|
+
timeoutMilliseconds: timeout,
|
|
235
|
+
});
|
|
236
|
+
throw new SyncEnqueueTimeout({
|
|
237
|
+
timeoutMilliseconds: timeout,
|
|
238
|
+
actorType: reference.actorType,
|
|
239
|
+
actorId: reference.actorId,
|
|
240
|
+
operation,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return this.waitForResult(messageReference, { timeout, deadline });
|
|
244
|
+
}
|
|
245
|
+
async wait(messageReference, options = {}) {
|
|
246
|
+
const timeout = invocationTimeout(options);
|
|
247
|
+
const deadline = performance.now() + timeout;
|
|
248
|
+
let message;
|
|
249
|
+
try {
|
|
250
|
+
message = await withDatabaseDeadline({ timeoutMilliseconds: timeout }, () => this.authorizeMessageReference(messageReference, options.authorizationContext));
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
if (!(error instanceof DatabaseDeadlineExceeded))
|
|
254
|
+
throw error;
|
|
255
|
+
throw this.databaseContentionTimeout({
|
|
256
|
+
messageReference,
|
|
257
|
+
timeout,
|
|
258
|
+
operation: "unknown",
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
if (this.settings.database.transactionActive?.()) {
|
|
262
|
+
throw new SyncInsideTransaction({
|
|
263
|
+
actorType: messageReference.actorType,
|
|
264
|
+
actorId: messageReference.actorId,
|
|
265
|
+
operation: message.operation,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return this.waitForResult(messageReference, { timeout, deadline });
|
|
269
|
+
}
|
|
270
|
+
async waitForResult(messageReference, options) {
|
|
271
|
+
this.callerWorker ??= new Worker(this, { setupFailure: "throw" });
|
|
272
|
+
while (performance.now() < options.deadline) {
|
|
273
|
+
let snapshot;
|
|
274
|
+
try {
|
|
275
|
+
snapshot = await this.messageSnapshotBeforeDeadline(messageReference, options.deadline);
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
if (!(error instanceof DatabaseDeadlineExceeded))
|
|
279
|
+
throw error;
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
const { message, status } = snapshot;
|
|
283
|
+
if (!message || message.request_id !== messageReference.requestId) {
|
|
284
|
+
throw actorDestroyedWhileWaiting();
|
|
285
|
+
}
|
|
286
|
+
if (message.rejection !== null)
|
|
287
|
+
throw rejectionFromMessage(message);
|
|
288
|
+
if (status === "completed")
|
|
289
|
+
return readonlyCopy(parseResult(message.result));
|
|
290
|
+
if (status === "dead")
|
|
291
|
+
throw messageFailure(message);
|
|
292
|
+
const processed = await this.callerWorker.runOnce({ activationRetention: "release" });
|
|
293
|
+
const remaining = options.deadline - performance.now();
|
|
294
|
+
if (processed === 0 && remaining > 0)
|
|
295
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(this.settings.syncPollingIntervalMilliseconds, remaining)));
|
|
296
|
+
}
|
|
297
|
+
let finalSnapshot;
|
|
298
|
+
try {
|
|
299
|
+
finalSnapshot = await withDatabaseDeadline({ timeoutMilliseconds: Math.max(this.settings.syncPollingIntervalMilliseconds, 100) }, async () => {
|
|
300
|
+
const message = await this.repository.findMessage(messageReference.id);
|
|
301
|
+
return {
|
|
302
|
+
message,
|
|
303
|
+
status: message ? await this.repository.messageStatus(message.id) : "unknown",
|
|
304
|
+
};
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
if (!(error instanceof DatabaseDeadlineExceeded))
|
|
309
|
+
throw error;
|
|
310
|
+
throw this.databaseContentionTimeout({
|
|
311
|
+
messageReference,
|
|
312
|
+
timeout: options.timeout,
|
|
313
|
+
operation: "unknown",
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
const { message: finalMessage, status: finalStatus } = finalSnapshot;
|
|
317
|
+
if (!finalMessage || finalMessage.request_id !== messageReference.requestId) {
|
|
318
|
+
throw actorDestroyedWhileWaiting();
|
|
319
|
+
}
|
|
320
|
+
if (finalMessage.rejection !== null)
|
|
321
|
+
throw rejectionFromMessage(finalMessage);
|
|
322
|
+
if (finalStatus === "completed") {
|
|
323
|
+
return readonlyCopy(parseResult(finalMessage.result));
|
|
324
|
+
}
|
|
325
|
+
if (finalStatus === "dead") {
|
|
326
|
+
throw messageFailure(finalMessage);
|
|
327
|
+
}
|
|
328
|
+
throw await this.syncTimeout(messageReference, options.timeout);
|
|
329
|
+
}
|
|
330
|
+
messageSnapshotBeforeDeadline(messageReference, deadline) {
|
|
331
|
+
const remaining = Math.max(Math.floor(deadline - performance.now()), 0);
|
|
332
|
+
return withDatabaseDeadline({ timeoutMilliseconds: remaining }, async () => {
|
|
333
|
+
const message = await this.repository.findMessage(messageReference.id);
|
|
334
|
+
return {
|
|
335
|
+
message,
|
|
336
|
+
status: message ? await this.repository.messageStatus(message.id) : "unknown",
|
|
337
|
+
};
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
async syncTimeout(messageReference, timeoutMilliseconds) {
|
|
341
|
+
let diagnostics;
|
|
342
|
+
try {
|
|
343
|
+
diagnostics = await withDatabaseDeadline({ timeoutMilliseconds: Math.max(this.settings.syncPollingIntervalMilliseconds, 100) }, () => this.repository.syncDiagnostics(messageReference.id));
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
if (!(error instanceof DatabaseDeadlineExceeded))
|
|
347
|
+
throw error;
|
|
348
|
+
return this.databaseContentionTimeout({
|
|
349
|
+
messageReference,
|
|
350
|
+
timeout: timeoutMilliseconds,
|
|
351
|
+
operation: "unknown",
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
if (!diagnostics || diagnostics.message.request_id !== messageReference.requestId) {
|
|
355
|
+
throw actorDestroyedWhileWaiting();
|
|
356
|
+
}
|
|
357
|
+
const { instance, message, process, blocker } = diagnostics;
|
|
358
|
+
const activationLive = instance.activation_owner_id !== null &&
|
|
359
|
+
instance.activation_expires_at_ms !== null &&
|
|
360
|
+
Number(instance.activation_expires_at_ms) > diagnostics.nowMilliseconds;
|
|
361
|
+
const waitingOn = Number(instance.paused) !== 0
|
|
362
|
+
? "actorPaused"
|
|
363
|
+
: activationLive
|
|
364
|
+
? "activationHeld"
|
|
365
|
+
: blocker
|
|
366
|
+
? "earlierMessage"
|
|
367
|
+
: diagnostics.status === "claimed"
|
|
368
|
+
? "messageClaimed"
|
|
369
|
+
: diagnostics.readyAvailableAtMilliseconds !== undefined &&
|
|
370
|
+
diagnostics.readyAvailableAtMilliseconds > diagnostics.nowMilliseconds
|
|
371
|
+
? "notYetAvailable"
|
|
372
|
+
: diagnostics.status === "ready"
|
|
373
|
+
? "readyUnclaimed"
|
|
374
|
+
: "unknown";
|
|
375
|
+
const error = new SyncTimeout({
|
|
376
|
+
details: {
|
|
377
|
+
timeoutMilliseconds,
|
|
378
|
+
actorType: message.actor_type,
|
|
379
|
+
actorId: message.actor_id,
|
|
380
|
+
operation: message.operation,
|
|
381
|
+
messageId: message.id,
|
|
382
|
+
requestId: message.request_id,
|
|
383
|
+
sequence: BigInt(message.sequence),
|
|
384
|
+
status: diagnostics.status,
|
|
385
|
+
waitingOn,
|
|
386
|
+
activation: Object.freeze({
|
|
387
|
+
ownerId: instance.activation_owner_id,
|
|
388
|
+
generation: BigInt(instance.activation_generation),
|
|
389
|
+
expiresAt: instance.activation_expires_at_ms === null
|
|
390
|
+
? null
|
|
391
|
+
: new Date(Number(instance.activation_expires_at_ms)),
|
|
392
|
+
process: process
|
|
393
|
+
? Object.freeze({
|
|
394
|
+
kind: process.kind,
|
|
395
|
+
heartbeatAt: new Date(Number(process.heartbeat_at_ms)),
|
|
396
|
+
shutdownState: process.shutdown_state,
|
|
397
|
+
})
|
|
398
|
+
: null,
|
|
399
|
+
}),
|
|
400
|
+
blocker: blocker
|
|
401
|
+
? Object.freeze({
|
|
402
|
+
messageId: blocker.id,
|
|
403
|
+
sequence: BigInt(blocker.sequence),
|
|
404
|
+
operation: blocker.operation,
|
|
405
|
+
status: blocker.membership_status,
|
|
406
|
+
})
|
|
407
|
+
: null,
|
|
408
|
+
},
|
|
409
|
+
messageReference,
|
|
410
|
+
});
|
|
411
|
+
this.emitInstrumentation("sync.timeout", {
|
|
412
|
+
messageId: message.id,
|
|
413
|
+
requestId: message.request_id,
|
|
414
|
+
actorType: message.actor_type,
|
|
415
|
+
actorId: message.actor_id,
|
|
416
|
+
sequence: String(message.sequence),
|
|
417
|
+
status: diagnostics.status,
|
|
418
|
+
waitingOn,
|
|
419
|
+
activationOwnerId: instance.activation_owner_id,
|
|
420
|
+
activationGeneration: String(instance.activation_generation),
|
|
421
|
+
});
|
|
422
|
+
return error;
|
|
423
|
+
}
|
|
424
|
+
databaseContentionTimeout(options) {
|
|
425
|
+
const { messageReference, timeout, operation } = options;
|
|
426
|
+
const error = new SyncTimeout({
|
|
427
|
+
details: {
|
|
428
|
+
timeoutMilliseconds: timeout,
|
|
429
|
+
actorType: messageReference.actorType,
|
|
430
|
+
actorId: messageReference.actorId,
|
|
431
|
+
operation,
|
|
432
|
+
messageId: messageReference.id,
|
|
433
|
+
requestId: messageReference.requestId,
|
|
434
|
+
sequence: messageReference.sequence,
|
|
435
|
+
status: "unknown",
|
|
436
|
+
waitingOn: "databaseContention",
|
|
437
|
+
activation: Object.freeze({
|
|
438
|
+
ownerId: null,
|
|
439
|
+
generation: 0n,
|
|
440
|
+
expiresAt: null,
|
|
441
|
+
process: null,
|
|
442
|
+
}),
|
|
443
|
+
blocker: null,
|
|
444
|
+
},
|
|
445
|
+
messageReference,
|
|
446
|
+
});
|
|
447
|
+
this.emitInstrumentation("sync.timeout", {
|
|
448
|
+
messageId: messageReference.id,
|
|
449
|
+
requestId: messageReference.requestId,
|
|
450
|
+
actorType: messageReference.actorType,
|
|
451
|
+
actorId: messageReference.actorId,
|
|
452
|
+
sequence: String(messageReference.sequence),
|
|
453
|
+
status: "unknown",
|
|
454
|
+
waitingOn: "databaseContention",
|
|
455
|
+
activationOwnerId: null,
|
|
456
|
+
activationGeneration: null,
|
|
457
|
+
});
|
|
458
|
+
return error;
|
|
459
|
+
}
|
|
460
|
+
async messageStatus(messageReference, options = {}) {
|
|
461
|
+
const message = await this.authorizeMessageReference(messageReference, options.authorizationContext);
|
|
462
|
+
return this.repository.messageStatus(message.id);
|
|
463
|
+
}
|
|
464
|
+
async messageResult(messageReference, options = {}) {
|
|
465
|
+
const message = await this.authorizeMessageReference(messageReference, options.authorizationContext);
|
|
466
|
+
if (message.rejection !== null)
|
|
467
|
+
throw rejectionFromMessage(message);
|
|
468
|
+
if ((await this.repository.messageStatus(message.id)) === "dead") {
|
|
469
|
+
throw messageFailure(message);
|
|
470
|
+
}
|
|
471
|
+
if (message.result === null)
|
|
472
|
+
return undefined;
|
|
473
|
+
return readonlyCopy(parseResult(message.result));
|
|
474
|
+
}
|
|
475
|
+
async snapshot(reference, options = {}) {
|
|
476
|
+
await this.authorize({
|
|
477
|
+
kind: "query",
|
|
478
|
+
reference,
|
|
479
|
+
operation: "__snapshot__",
|
|
480
|
+
argumentsValue: {},
|
|
481
|
+
authorizationContext: options.authorizationContext,
|
|
482
|
+
});
|
|
483
|
+
const registered = this.fetchActor(reference.actorType);
|
|
484
|
+
const instance = await this.repository.findInstanceByIdentity(reference.actorType, reference.actorId);
|
|
485
|
+
const state = instance
|
|
486
|
+
? migrateState({
|
|
487
|
+
definition: registered.definition,
|
|
488
|
+
storedVersion: Number(instance.state_version),
|
|
489
|
+
storedState: jsonObject(JSON.parse(instance.state)),
|
|
490
|
+
})
|
|
491
|
+
: initialStateFor(registered.definition);
|
|
492
|
+
const actor = hydrateActor({
|
|
493
|
+
definition: registered.definition,
|
|
494
|
+
actorId: reference.actorId,
|
|
495
|
+
state,
|
|
496
|
+
});
|
|
497
|
+
const stateBefore = stableJson(actorState(actor, registered.definition.stateKeys));
|
|
498
|
+
const intentCount = actor.intentCount();
|
|
499
|
+
const snapshot = { ...state };
|
|
500
|
+
await withActorProjection({ actor, runtime: this }, async () => {
|
|
501
|
+
for (const query of registered.definition.queries) {
|
|
502
|
+
if (registered.definition.stateKeys.includes(query))
|
|
503
|
+
continue;
|
|
504
|
+
const value = await actor[query];
|
|
505
|
+
snapshot[query] = normalizeJson(value === undefined ? null : value, {
|
|
506
|
+
maxBytes: this.settings.maxResultBytes,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
if (stableJson(actorState(actor, registered.definition.stateKeys)) !== stateBefore ||
|
|
511
|
+
actor.intentCount() !== intentCount) {
|
|
512
|
+
throw new QueryMutatedState("snapshot getters must not mutate actor state or stage work");
|
|
513
|
+
}
|
|
514
|
+
return readonlyCopy(snapshot);
|
|
515
|
+
}
|
|
516
|
+
async subscriptionSnapshot(options) {
|
|
517
|
+
const authorized = await this.settings.authorizeSubscription(options);
|
|
518
|
+
if (!authorized)
|
|
519
|
+
throw new Unauthorized("actor subscription is not authorized");
|
|
520
|
+
options.onAuthorized?.();
|
|
521
|
+
const registered = this.fetchActor(options.actorType);
|
|
522
|
+
const instance = await this.repository.findInstanceByIdentity(options.actorType, options.actorId);
|
|
523
|
+
const state = instance
|
|
524
|
+
? migrateState({
|
|
525
|
+
definition: registered.definition,
|
|
526
|
+
storedVersion: Number(instance.state_version),
|
|
527
|
+
storedState: jsonObject(JSON.parse(instance.state)),
|
|
528
|
+
})
|
|
529
|
+
: initialStateFor(registered.definition);
|
|
530
|
+
const actor = hydrateActor({
|
|
531
|
+
definition: registered.definition,
|
|
532
|
+
actorId: options.actorId,
|
|
533
|
+
state,
|
|
534
|
+
});
|
|
535
|
+
return readonlyCopy({
|
|
536
|
+
actorType: options.actorType,
|
|
537
|
+
actorId: options.actorId,
|
|
538
|
+
instanceId: instance?.id ?? "0",
|
|
539
|
+
revision: String(instance?.state_revision ?? 0),
|
|
540
|
+
observables: this.readObservables(actor, registered.definition),
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
async subscriptionPayloads(options) {
|
|
544
|
+
if (options.payloadNames.length === 0)
|
|
545
|
+
return [];
|
|
546
|
+
const registered = this.fetchActor(options.actorType);
|
|
547
|
+
const payloadNames = this.validatePayloadNames(registered, options.payloadNames);
|
|
548
|
+
const snapshot = await this.payloadProjectionSnapshot({
|
|
549
|
+
registered,
|
|
550
|
+
actorType: options.actorType,
|
|
551
|
+
actorId: options.actorId,
|
|
552
|
+
});
|
|
553
|
+
const payloads = [];
|
|
554
|
+
for (const name of payloadNames) {
|
|
555
|
+
const payload = await this.projectSubscriptionPayload({
|
|
556
|
+
registered,
|
|
557
|
+
snapshot,
|
|
558
|
+
actorType: options.actorType,
|
|
559
|
+
actorId: options.actorId,
|
|
560
|
+
name,
|
|
561
|
+
authorizationContext: options.authorizationContext,
|
|
562
|
+
});
|
|
563
|
+
if (payload)
|
|
564
|
+
payloads.push(payload);
|
|
565
|
+
}
|
|
566
|
+
return payloads;
|
|
567
|
+
}
|
|
568
|
+
async destroy(reference, options = {}) {
|
|
569
|
+
if (currentActor())
|
|
570
|
+
throw new ActorCallCycle("actors cannot synchronously destroy another actor");
|
|
571
|
+
const authorized = await this.settings.authorizeDestroy({
|
|
572
|
+
actorType: reference.actorType,
|
|
573
|
+
actorId: reference.actorId,
|
|
574
|
+
authorizationContext: options.authorizationContext,
|
|
575
|
+
});
|
|
576
|
+
if (!authorized)
|
|
577
|
+
throw new Unauthorized("actor destruction is not authorized");
|
|
578
|
+
const destroyed = await this.repository.destroy(reference.actorType, reference.actorId);
|
|
579
|
+
if (destroyed) {
|
|
580
|
+
this.emitInstrumentation("actor.destroyed", {
|
|
581
|
+
actorType: reference.actorType,
|
|
582
|
+
actorId: reference.actorId,
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
return destroyed;
|
|
586
|
+
}
|
|
587
|
+
async inspectDeadLetters(options = {}) {
|
|
588
|
+
await this.authorizeAdministration({
|
|
589
|
+
action: "inspect",
|
|
590
|
+
resource: "dead_letters",
|
|
591
|
+
authorizationContext: options.authorizationContext,
|
|
592
|
+
});
|
|
593
|
+
const deadLetters = await this.repository.listDeadLetters();
|
|
594
|
+
return Object.freeze(deadLetters.map(deadLetterFromRow));
|
|
595
|
+
}
|
|
596
|
+
async retryDeadLetter(id, options = {}) {
|
|
597
|
+
await this.authorizeAdministration({
|
|
598
|
+
action: "retry",
|
|
599
|
+
resource: "dead_letters",
|
|
600
|
+
resourceId: id,
|
|
601
|
+
authorizationContext: options.authorizationContext,
|
|
602
|
+
});
|
|
603
|
+
const deadLetter = await this.repository.findDeadLetter(id);
|
|
604
|
+
if (!deadLetter)
|
|
605
|
+
throw new UnknownDeadLetter(`unknown dead letter ${id}`);
|
|
606
|
+
const actor = this.fetchActor(deadLetter.actor_type);
|
|
607
|
+
if (!actor.operations.has(deadLetter.operation)) {
|
|
608
|
+
throw new UnknownOperation(`unknown dead-letter operation ${JSON.stringify(deadLetter.operation)}`);
|
|
609
|
+
}
|
|
610
|
+
const message = await this.repository.retryDeadLetter({
|
|
611
|
+
id,
|
|
612
|
+
initialState: initialStateFor(actor.definition),
|
|
613
|
+
stateVersion: actor.definition.stateVersion,
|
|
614
|
+
});
|
|
615
|
+
this.emitInstrumentation("dead_letter.retried", {
|
|
616
|
+
deadLetterId: id,
|
|
617
|
+
messageId: message.id,
|
|
618
|
+
actorType: message.actor_type,
|
|
619
|
+
actorId: message.actor_id,
|
|
620
|
+
operation: message.operation,
|
|
621
|
+
});
|
|
622
|
+
this.wakeUp("actors");
|
|
623
|
+
return this.messageReferenceFromRow(message);
|
|
624
|
+
}
|
|
625
|
+
async activeInstances(options = {}) {
|
|
626
|
+
await this.authorizeAdministration({
|
|
627
|
+
action: "active",
|
|
628
|
+
resource: "instances",
|
|
629
|
+
authorizationContext: options.authorizationContext,
|
|
630
|
+
});
|
|
631
|
+
const limit = reconciliationLimit(options.limit);
|
|
632
|
+
const rows = await this.repository.activeInstances({
|
|
633
|
+
limit,
|
|
634
|
+
...(options.actorType === undefined ? {} : { actorType: options.actorType }),
|
|
635
|
+
...(options.cursor === undefined ? {} : { cursor: options.cursor }),
|
|
636
|
+
});
|
|
637
|
+
return reconciliationPage(rows, limit);
|
|
638
|
+
}
|
|
639
|
+
async inspectProcesses(options = {}) {
|
|
640
|
+
await this.authorizeAdministration({
|
|
641
|
+
action: "inspect",
|
|
642
|
+
resource: "processes",
|
|
643
|
+
authorizationContext: options.authorizationContext,
|
|
644
|
+
});
|
|
645
|
+
const result = await this.repository.listProcesses();
|
|
646
|
+
return Object.freeze(result.rows.map((row) => Object.freeze({
|
|
647
|
+
id: row.id,
|
|
648
|
+
kind: row.kind,
|
|
649
|
+
hostname: row.hostname,
|
|
650
|
+
hostProcessId: Number(row.host_process_id),
|
|
651
|
+
metadata: processMetadata(row.metadata),
|
|
652
|
+
shutdownState: row.shutdown_state,
|
|
653
|
+
shutdownRequestedAt: row.shutdown_requested_at_ms === null
|
|
654
|
+
? null
|
|
655
|
+
: new Date(Number(row.shutdown_requested_at_ms)),
|
|
656
|
+
stale: row.shutdown_state !== "stopped" &&
|
|
657
|
+
Number(row.heartbeat_at_ms) <= result.staleAtMilliseconds,
|
|
658
|
+
startedAt: new Date(Number(row.started_at_ms)),
|
|
659
|
+
heartbeatAt: new Date(Number(row.heartbeat_at_ms)),
|
|
660
|
+
stoppedAt: row.stopped_at_ms === null ? null : new Date(Number(row.stopped_at_ms)),
|
|
661
|
+
})));
|
|
662
|
+
}
|
|
663
|
+
async cleanupProcesses(options = {}) {
|
|
664
|
+
await this.authorizeAdministration({
|
|
665
|
+
action: "cleanup",
|
|
666
|
+
resource: "processes",
|
|
667
|
+
authorizationContext: options.authorizationContext,
|
|
668
|
+
});
|
|
669
|
+
const cleaned = await this.cleanupStaleProcesses();
|
|
670
|
+
return Object.freeze({ cleaned });
|
|
671
|
+
}
|
|
672
|
+
async cleanupStaleProcesses() {
|
|
673
|
+
const cleaned = await this.repository.cleanupStaleProcesses();
|
|
674
|
+
if (cleaned > 0) {
|
|
675
|
+
this.wakeUp("actors");
|
|
676
|
+
this.wakeUp("effects");
|
|
677
|
+
this.wakeUp("reminders");
|
|
678
|
+
this.wakeUp("broadcasts");
|
|
679
|
+
this.emitInstrumentation("processes.cleaned", { count: cleaned });
|
|
680
|
+
}
|
|
681
|
+
return cleaned;
|
|
682
|
+
}
|
|
683
|
+
async instancesWithoutPendingWork(options) {
|
|
684
|
+
await this.authorizeAdministration({
|
|
685
|
+
action: "withoutPendingWork",
|
|
686
|
+
resource: "instances",
|
|
687
|
+
authorizationContext: options.authorizationContext,
|
|
688
|
+
});
|
|
689
|
+
if (!Number.isFinite(options.quietForMilliseconds) || options.quietForMilliseconds < 0) {
|
|
690
|
+
throw new TypeError("quietForMilliseconds must be a non-negative number");
|
|
691
|
+
}
|
|
692
|
+
const limit = reconciliationLimit(options.limit);
|
|
693
|
+
const rows = await this.repository.instancesWithoutPendingWork({
|
|
694
|
+
limit,
|
|
695
|
+
quietForMilliseconds: options.quietForMilliseconds,
|
|
696
|
+
...(options.actorType === undefined ? {} : { actorType: options.actorType }),
|
|
697
|
+
...(options.cursor === undefined ? {} : { cursor: options.cursor }),
|
|
698
|
+
});
|
|
699
|
+
return reconciliationPage(rows, limit);
|
|
700
|
+
}
|
|
701
|
+
async instanceStatesFor(options) {
|
|
702
|
+
await this.authorizeAdministration({
|
|
703
|
+
action: "statesFor",
|
|
704
|
+
resource: "instances",
|
|
705
|
+
authorizationContext: options.authorizationContext,
|
|
706
|
+
});
|
|
707
|
+
const actorIds = [...new Set(options.actorIds.map(String))];
|
|
708
|
+
if (actorIds.length > 1_000) {
|
|
709
|
+
throw new TypeError("statesFor accepts at most 1000 actor IDs per batch");
|
|
710
|
+
}
|
|
711
|
+
const actor = this.fetchActor(options.actorType);
|
|
712
|
+
const rows = await this.repository.instanceStatesFor({ actorType: options.actorType, actorIds });
|
|
713
|
+
const states = Object.fromEntries(rows.map((row) => [
|
|
714
|
+
row.actor_id,
|
|
715
|
+
migrateState({
|
|
716
|
+
definition: actor.definition,
|
|
717
|
+
storedVersion: Number(row.state_version),
|
|
718
|
+
storedState: jsonObject(JSON.parse(row.state)),
|
|
719
|
+
}),
|
|
720
|
+
]));
|
|
721
|
+
return readonlyCopy(states);
|
|
722
|
+
}
|
|
723
|
+
async orphanedInstances(options) {
|
|
724
|
+
await this.authorizeAdministration({
|
|
725
|
+
action: "orphaned",
|
|
726
|
+
resource: "instances",
|
|
727
|
+
authorizationContext: options.authorizationContext,
|
|
728
|
+
});
|
|
729
|
+
const limit = reconciliationLimit(options.limit);
|
|
730
|
+
const rows = await this.repository.orphanedInstances({
|
|
731
|
+
actorType: options.actorType,
|
|
732
|
+
ownerIds: [...new Set(options.ownerIds.map(String))],
|
|
733
|
+
limit,
|
|
734
|
+
...(options.cursor === undefined ? {} : { cursor: options.cursor }),
|
|
735
|
+
});
|
|
736
|
+
return reconciliationPage(rows, limit);
|
|
737
|
+
}
|
|
738
|
+
async previewRetention(options) {
|
|
739
|
+
assertRetentionTarget(options.target);
|
|
740
|
+
await this.authorizeAdministration({
|
|
741
|
+
action: "inspect",
|
|
742
|
+
resource: options.target,
|
|
743
|
+
authorizationContext: options.authorizationContext,
|
|
744
|
+
});
|
|
745
|
+
return Object.freeze({
|
|
746
|
+
target: options.target,
|
|
747
|
+
count: await this.repository.previewRetention(options.target),
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
async pruneRetention(options) {
|
|
751
|
+
assertRetentionTarget(options.target);
|
|
752
|
+
await this.authorizeAdministration({
|
|
753
|
+
action: "prune",
|
|
754
|
+
resource: options.target,
|
|
755
|
+
authorizationContext: options.authorizationContext,
|
|
756
|
+
});
|
|
757
|
+
const result = Object.freeze({
|
|
758
|
+
target: options.target,
|
|
759
|
+
count: await this.repository.pruneRetention(options.target),
|
|
760
|
+
});
|
|
761
|
+
this.emitInstrumentation(`${options.target}.pruned`, { count: result.count });
|
|
762
|
+
return result;
|
|
763
|
+
}
|
|
764
|
+
async inspectReminders(options = {}) {
|
|
765
|
+
await this.authorizeAdministration({
|
|
766
|
+
action: "inspect",
|
|
767
|
+
resource: "reminders",
|
|
768
|
+
authorizationContext: options.authorizationContext,
|
|
769
|
+
});
|
|
770
|
+
if (options.status !== undefined &&
|
|
771
|
+
options.status !== "scheduled" &&
|
|
772
|
+
options.status !== "paused" &&
|
|
773
|
+
options.status !== "completed") {
|
|
774
|
+
throw new TypeError(`unknown reminder status ${JSON.stringify(options.status)}`);
|
|
775
|
+
}
|
|
776
|
+
const limit = reminderPageLimit(options.limit);
|
|
777
|
+
const rows = await this.repository.listReminders({
|
|
778
|
+
limit,
|
|
779
|
+
...(options.actorType === undefined ? {} : { actorType: options.actorType }),
|
|
780
|
+
...(options.status === undefined ? {} : { status: options.status }),
|
|
781
|
+
...(options.cursor === undefined ? {} : { cursor: options.cursor }),
|
|
782
|
+
});
|
|
783
|
+
return reminderPage(rows, limit);
|
|
784
|
+
}
|
|
785
|
+
async resumeReminder(id, options = {}) {
|
|
786
|
+
await this.authorizeAdministration({
|
|
787
|
+
action: "resume",
|
|
788
|
+
resource: "reminders",
|
|
789
|
+
resourceId: id,
|
|
790
|
+
authorizationContext: options.authorizationContext,
|
|
791
|
+
});
|
|
792
|
+
const runAtMilliseconds = options.runAt?.getTime();
|
|
793
|
+
if (runAtMilliseconds !== undefined && !Number.isFinite(runAtMilliseconds)) {
|
|
794
|
+
throw new TypeError("reminder runAt must be a valid date");
|
|
795
|
+
}
|
|
796
|
+
const result = await this.repository.resumeReminder({
|
|
797
|
+
id,
|
|
798
|
+
...(runAtMilliseconds === undefined ? {} : { runAtMilliseconds }),
|
|
799
|
+
});
|
|
800
|
+
if (result.resumed) {
|
|
801
|
+
this.emitInstrumentation("reminder.resumed", {
|
|
802
|
+
reminderId: result.reminder.id,
|
|
803
|
+
actorType: result.reminder.actor_type,
|
|
804
|
+
actorId: result.reminder.actor_id,
|
|
805
|
+
operation: result.reminder.operation,
|
|
806
|
+
runAt: new Date(Number(result.reminder.run_at_ms)).toISOString(),
|
|
807
|
+
});
|
|
808
|
+
this.wakeUp("reminders");
|
|
809
|
+
}
|
|
810
|
+
return reminderRecord(result.reminder);
|
|
811
|
+
}
|
|
812
|
+
async executeTurn(turn, cachedActor) {
|
|
813
|
+
let registered;
|
|
814
|
+
let state;
|
|
815
|
+
let actor;
|
|
816
|
+
try {
|
|
817
|
+
registered = this.fetchActor(turn.message.actor_type);
|
|
818
|
+
const definition = registered.definition;
|
|
819
|
+
state = migrateState({
|
|
820
|
+
definition,
|
|
821
|
+
storedVersion: Number(turn.instance.state_version),
|
|
822
|
+
storedState: jsonObject(JSON.parse(turn.instance.state)),
|
|
823
|
+
});
|
|
824
|
+
actor =
|
|
825
|
+
cachedActor ??
|
|
826
|
+
hydrateActor({
|
|
827
|
+
definition,
|
|
828
|
+
actorId: turn.message.actor_id,
|
|
829
|
+
state: deepCopy(state),
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
catch (error) {
|
|
833
|
+
throw new ActorSetupFailed(error);
|
|
834
|
+
}
|
|
835
|
+
const definition = registered.definition;
|
|
836
|
+
let activated = cachedActor !== undefined;
|
|
837
|
+
const messageContext = actorMessageContext(turn.message);
|
|
838
|
+
const renewalController = new AbortController();
|
|
839
|
+
let renewalError;
|
|
840
|
+
const renewal = this.renewLease(turn, renewalController.signal).catch((error) => {
|
|
841
|
+
renewalError = error;
|
|
842
|
+
});
|
|
843
|
+
let stateBefore;
|
|
844
|
+
if (!activated) {
|
|
845
|
+
try {
|
|
846
|
+
await withActorContext({ actor, runtime: this }, () => actor.activate());
|
|
847
|
+
activated = true;
|
|
848
|
+
this.emitInstrumentation("activation.started", {
|
|
849
|
+
actorType: turn.message.actor_type,
|
|
850
|
+
actorId: turn.message.actor_id,
|
|
851
|
+
instanceId: turn.instance.id,
|
|
852
|
+
generation: String(turn.activationGeneration),
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
catch (error) {
|
|
856
|
+
renewalController.abort();
|
|
857
|
+
await renewal;
|
|
858
|
+
actor.discardIntents();
|
|
859
|
+
throw new ActorSetupFailed(renewalError ?? error);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
const startedAt = Date.now();
|
|
863
|
+
this.emitInstrumentation("message.started", messageInstrumentation(turn.message));
|
|
864
|
+
try {
|
|
865
|
+
const oldObservables = this.readObservables(actor, definition);
|
|
866
|
+
stateBefore = deepCopy(actorState(actor, definition.stateKeys));
|
|
867
|
+
const before = stableJson(stateBefore);
|
|
868
|
+
const query = this.isQuery(definition, turn.message.operation);
|
|
869
|
+
const argumentsValue = jsonObject(JSON.parse(turn.message.arguments));
|
|
870
|
+
const rawResult = await withActorContext({ actor, runtime: this, message: messageContext }, async () => {
|
|
871
|
+
if (definition.stateKeys.includes(turn.message.operation)) {
|
|
872
|
+
return actor[turn.message.operation];
|
|
873
|
+
}
|
|
874
|
+
return actor.invoke(turn.message.operation, argumentsValue);
|
|
875
|
+
});
|
|
876
|
+
if (query && stableJson(actorState(actor, definition.stateKeys)) !== before) {
|
|
877
|
+
throw new QueryMutatedState(`query ${turn.message.operation} mutated actor state`);
|
|
878
|
+
}
|
|
879
|
+
if (query && actor.hasIntents()) {
|
|
880
|
+
throw new QueryMutatedState(`query ${turn.message.operation} staged durable work`);
|
|
881
|
+
}
|
|
882
|
+
const result = normalizeJson(rawResult === undefined ? null : rawResult, {
|
|
883
|
+
maxBytes: this.settings.maxResultBytes,
|
|
884
|
+
});
|
|
885
|
+
const committedState = jsonObject(actorState(actor, definition.stateKeys), {
|
|
886
|
+
maxBytes: this.settings.maxStateBytes,
|
|
887
|
+
});
|
|
888
|
+
const observables = this.readObservables(actor, definition);
|
|
889
|
+
const changedObservables = Object.fromEntries(Object.entries(observables).filter(([name, value]) => stableJson(value) !== stableJson(oldObservables[name])));
|
|
890
|
+
const broadcastObservables = Object.keys(changedObservables).length > 0 ||
|
|
891
|
+
(Object.keys(definition.payloads).length > 0 && stableJson(committedState) !== before)
|
|
892
|
+
? changedObservables
|
|
893
|
+
: undefined;
|
|
894
|
+
renewalController.abort();
|
|
895
|
+
await renewal;
|
|
896
|
+
if (renewalError)
|
|
897
|
+
throw renewalError;
|
|
898
|
+
const intents = actor.drainIntents();
|
|
899
|
+
const completion = await this.repository.complete(turn, {
|
|
900
|
+
state: committedState,
|
|
901
|
+
stateVersion: definition.stateVersion,
|
|
902
|
+
result,
|
|
903
|
+
...(broadcastObservables === undefined ? {} : { broadcastObservables }),
|
|
904
|
+
intents,
|
|
905
|
+
executeCommitAction: async (intent, connection) => {
|
|
906
|
+
const handler = this.commitActions.get(intent.name);
|
|
907
|
+
if (!handler)
|
|
908
|
+
throw new UnknownCommitAction(`unknown commit action ${JSON.stringify(intent.name)}`);
|
|
909
|
+
const attributes = {
|
|
910
|
+
commitAction: intent.name,
|
|
911
|
+
...messageInstrumentation(turn.message),
|
|
912
|
+
};
|
|
913
|
+
this.emitInstrumentation("commit_action.started", attributes);
|
|
914
|
+
try {
|
|
915
|
+
await withApplicationWritesForbidden(() => handler(intent.arguments, {
|
|
916
|
+
actorType: turn.message.actor_type,
|
|
917
|
+
actorId: turn.message.actor_id,
|
|
918
|
+
messageId: turn.message.id,
|
|
919
|
+
requestId: turn.message.request_id,
|
|
920
|
+
sequence: BigInt(turn.message.sequence),
|
|
921
|
+
activationGeneration: turn.activationGeneration,
|
|
922
|
+
connection,
|
|
923
|
+
}));
|
|
924
|
+
this.emitInstrumentation("commit_action.completed", attributes);
|
|
925
|
+
}
|
|
926
|
+
catch (error) {
|
|
927
|
+
this.emitInstrumentation("commit_action.failed", {
|
|
928
|
+
...attributes,
|
|
929
|
+
errorName: error instanceof Error ? error.name : "Error",
|
|
930
|
+
});
|
|
931
|
+
throw error;
|
|
932
|
+
}
|
|
933
|
+
},
|
|
934
|
+
});
|
|
935
|
+
if (intents.outboundMessages.length > 0)
|
|
936
|
+
this.wakeUp("actors");
|
|
937
|
+
if (intents.effects.length > 0)
|
|
938
|
+
this.wakeUp("effects");
|
|
939
|
+
if (intents.reminders.length > 0)
|
|
940
|
+
this.wakeUp("reminders");
|
|
941
|
+
if (broadcastObservables !== undefined)
|
|
942
|
+
this.wakeUp("broadcasts");
|
|
943
|
+
for (const replacement of completion.reminderReplacements) {
|
|
944
|
+
this.emitInstrumentation("reminder.replaced", {
|
|
945
|
+
reminderId: replacement.reminderId,
|
|
946
|
+
actorType: turn.message.actor_type,
|
|
947
|
+
actorId: turn.message.actor_id,
|
|
948
|
+
operation: replacement.operation,
|
|
949
|
+
previousRunAt: new Date(replacement.previousRunAtMilliseconds).toISOString(),
|
|
950
|
+
nextRunAt: new Date(replacement.nextRunAtMilliseconds).toISOString(),
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
this.emitInstrumentation("message.completed", {
|
|
954
|
+
...messageInstrumentation(turn.message),
|
|
955
|
+
durationMilliseconds: Date.now() - startedAt,
|
|
956
|
+
});
|
|
957
|
+
return { actor, retainActivation: true, activated };
|
|
958
|
+
}
|
|
959
|
+
catch (error) {
|
|
960
|
+
renewalController.abort();
|
|
961
|
+
await renewal;
|
|
962
|
+
actor.discardIntents();
|
|
963
|
+
if (stateBefore)
|
|
964
|
+
restoreActorState({ actor, definition, state: stateBefore });
|
|
965
|
+
if (error instanceof LostActivation) {
|
|
966
|
+
this.emitInstrumentation("activation.lost", {
|
|
967
|
+
...messageInstrumentation(turn.message),
|
|
968
|
+
durationMilliseconds: Date.now() - startedAt,
|
|
969
|
+
});
|
|
970
|
+
return { actor, retainActivation: false, activated };
|
|
971
|
+
}
|
|
972
|
+
if (error instanceof Rejected) {
|
|
973
|
+
await this.repository.reject(turn, {
|
|
974
|
+
code: error.code,
|
|
975
|
+
message: error.message,
|
|
976
|
+
details: error.details,
|
|
977
|
+
});
|
|
978
|
+
this.emitInstrumentation("message.rejected", {
|
|
979
|
+
...messageInstrumentation(turn.message),
|
|
980
|
+
code: error.code,
|
|
981
|
+
durationMilliseconds: Date.now() - startedAt,
|
|
982
|
+
});
|
|
983
|
+
return { actor, retainActivation: activated, activated };
|
|
984
|
+
}
|
|
985
|
+
const retryable = !(error instanceof NonRetryableError);
|
|
986
|
+
const outcome = await this.repository.fail(turn, {
|
|
987
|
+
error,
|
|
988
|
+
retryable,
|
|
989
|
+
});
|
|
990
|
+
this.emitInstrumentation("message.failed", {
|
|
991
|
+
...messageInstrumentation(turn.message),
|
|
992
|
+
retryable,
|
|
993
|
+
errorName: error instanceof Error ? error.name : "Error",
|
|
994
|
+
durationMilliseconds: Date.now() - startedAt,
|
|
995
|
+
outcome,
|
|
996
|
+
});
|
|
997
|
+
if (outcome === "dead") {
|
|
998
|
+
this.emitInstrumentation("dead_letter.created", messageInstrumentation(turn.message));
|
|
999
|
+
}
|
|
1000
|
+
return { actor, retainActivation: activated, activated };
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
async deactivateActor(options) {
|
|
1004
|
+
const { turn, actor } = options;
|
|
1005
|
+
try {
|
|
1006
|
+
if (actor && options.lifecycle === "activated") {
|
|
1007
|
+
await withActorContext({ actor, runtime: this }, () => actor.deactivate());
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
catch (error) {
|
|
1011
|
+
this.emitInstrumentation("activation.deactivation_failed", {
|
|
1012
|
+
actorType: turn.message.actor_type,
|
|
1013
|
+
actorId: turn.message.actor_id,
|
|
1014
|
+
instanceId: turn.instance.id,
|
|
1015
|
+
generation: String(turn.activationGeneration),
|
|
1016
|
+
errorName: error instanceof Error ? error.name : "Error",
|
|
1017
|
+
});
|
|
1018
|
+
this.settings.logger.error({
|
|
1019
|
+
event: "solid_objects.activation.deactivation_failed",
|
|
1020
|
+
actorType: turn.message.actor_type,
|
|
1021
|
+
actorId: turn.message.actor_id,
|
|
1022
|
+
errorName: error instanceof Error ? error.name : "Error",
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
finally {
|
|
1026
|
+
actor?.discardIntents();
|
|
1027
|
+
await this.repository.releaseActivation({
|
|
1028
|
+
instanceId: turn.instance.id,
|
|
1029
|
+
processId: turn.processId,
|
|
1030
|
+
activationToken: turn.activationToken,
|
|
1031
|
+
activationGeneration: turn.activationGeneration,
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
async close() {
|
|
1036
|
+
if (this.running)
|
|
1037
|
+
throw new Error("abort runtime.run() and wait for it before closing the runtime");
|
|
1038
|
+
await this.callerWorker?.stop();
|
|
1039
|
+
this.realtime.close();
|
|
1040
|
+
await this.settings.wakeUp.close();
|
|
1041
|
+
await this.settings.database.close();
|
|
1042
|
+
clearDefaultRuntime(this);
|
|
1043
|
+
}
|
|
1044
|
+
async resetForTesting() {
|
|
1045
|
+
if (this.running)
|
|
1046
|
+
throw new Error("abort runtime.run() before resetting test state");
|
|
1047
|
+
await this.callerWorker?.stop();
|
|
1048
|
+
this.callerWorker = undefined;
|
|
1049
|
+
this.realtime.close();
|
|
1050
|
+
await this.repository.resetForTesting();
|
|
1051
|
+
}
|
|
1052
|
+
emitInstrumentation(name, attributes) {
|
|
1053
|
+
const instrumentation = this.settings.instrumentation;
|
|
1054
|
+
if (!instrumentation)
|
|
1055
|
+
return;
|
|
1056
|
+
try {
|
|
1057
|
+
instrumentation(Object.freeze({
|
|
1058
|
+
name: `solid_objects.${name}`,
|
|
1059
|
+
occurredAt: new Date().toISOString(),
|
|
1060
|
+
attributes: readonlyCopy(attributes),
|
|
1061
|
+
}));
|
|
1062
|
+
}
|
|
1063
|
+
catch (error) {
|
|
1064
|
+
this.settings.logger.error({
|
|
1065
|
+
event: "solid_objects.instrumentation.failed",
|
|
1066
|
+
instrumentationEvent: `solid_objects.${name}`,
|
|
1067
|
+
errorName: error instanceof Error ? error.name : "Error",
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
async executeEffect(effect) {
|
|
1072
|
+
try {
|
|
1073
|
+
const handler = this.effects.get(effect.name);
|
|
1074
|
+
if (!handler)
|
|
1075
|
+
throw new UnknownEffect(`unknown effect ${JSON.stringify(effect.name)}`);
|
|
1076
|
+
const result = normalizeJson((await handler(jsonObject(JSON.parse(effect.arguments)), {
|
|
1077
|
+
id: effect.id,
|
|
1078
|
+
attempt: Number(effect.attempt_count),
|
|
1079
|
+
actorType: effect.actor_type,
|
|
1080
|
+
actorId: effect.actor_id,
|
|
1081
|
+
sourceMessageId: effect.message_id,
|
|
1082
|
+
})) ?? null, { maxBytes: this.settings.maxResultBytes });
|
|
1083
|
+
await this.repository.completeEffect(effect, result);
|
|
1084
|
+
if (effect.success_operation)
|
|
1085
|
+
this.wakeUp("actors");
|
|
1086
|
+
this.emitInstrumentation("effect.completed", effectInstrumentation(effect));
|
|
1087
|
+
}
|
|
1088
|
+
catch (error) {
|
|
1089
|
+
await this.repository.failEffect({
|
|
1090
|
+
effect,
|
|
1091
|
+
error,
|
|
1092
|
+
retryable: !(error instanceof NonRetryableError),
|
|
1093
|
+
});
|
|
1094
|
+
const exhausted = error instanceof NonRetryableError ||
|
|
1095
|
+
Number(effect.attempt_count) >= Number(effect.max_attempts);
|
|
1096
|
+
if (exhausted && effect.failure_operation)
|
|
1097
|
+
this.wakeUp("actors");
|
|
1098
|
+
this.emitInstrumentation("effect.failed", {
|
|
1099
|
+
...effectInstrumentation(effect),
|
|
1100
|
+
errorName: error instanceof Error ? error.name : "Error",
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
async executeReminder(reminder) {
|
|
1105
|
+
const actor = this.fetchActor(reminder.actor_type);
|
|
1106
|
+
if (!actor.operations.has(reminder.operation)) {
|
|
1107
|
+
throw new UnknownOperation(`unknown reminder operation ${JSON.stringify(reminder.operation)}`);
|
|
1108
|
+
}
|
|
1109
|
+
await this.repository.enqueueReminder(reminder);
|
|
1110
|
+
this.wakeUp("actors");
|
|
1111
|
+
this.emitInstrumentation("reminder.enqueued", {
|
|
1112
|
+
reminderId: reminder.id,
|
|
1113
|
+
actorType: reminder.actor_type,
|
|
1114
|
+
actorId: reminder.actor_id,
|
|
1115
|
+
operation: reminder.operation,
|
|
1116
|
+
occurrence: Number(reminder.occurrence),
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
async executeBroadcast(broadcast) {
|
|
1120
|
+
const deliver = this.settings.broadcast;
|
|
1121
|
+
try {
|
|
1122
|
+
const event = readonlyCopy({
|
|
1123
|
+
actorType: broadcast.actor_type,
|
|
1124
|
+
actorId: broadcast.actor_id,
|
|
1125
|
+
instanceId: broadcast.instance_id,
|
|
1126
|
+
revision: String(broadcast.state_revision),
|
|
1127
|
+
observables: jsonObject(JSON.parse(broadcast.observables)),
|
|
1128
|
+
});
|
|
1129
|
+
await this.realtime.publish(event);
|
|
1130
|
+
await deliver?.(event);
|
|
1131
|
+
await this.repository.completeBroadcast(broadcast);
|
|
1132
|
+
this.emitInstrumentation("broadcast.delivered", broadcastInstrumentation(broadcast));
|
|
1133
|
+
}
|
|
1134
|
+
catch (error) {
|
|
1135
|
+
await this.repository.failBroadcast(broadcast, error);
|
|
1136
|
+
this.emitInstrumentation("broadcast.failed", {
|
|
1137
|
+
...broadcastInstrumentation(broadcast),
|
|
1138
|
+
errorName: error instanceof Error ? error.name : "Error",
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
async enqueue(options) {
|
|
1143
|
+
const { reference, operation, deliveryMode, argumentsValue, options: invocationOptions, } = options;
|
|
1144
|
+
const registered = this.fetchActor(reference.actorType);
|
|
1145
|
+
const message = await this.repository.enqueue({
|
|
1146
|
+
actorType: reference.actorType,
|
|
1147
|
+
actorId: reference.actorId,
|
|
1148
|
+
operation,
|
|
1149
|
+
deliveryMode,
|
|
1150
|
+
arguments: argumentsValue,
|
|
1151
|
+
initialState: initialStateFor(registered.definition),
|
|
1152
|
+
stateVersion: registered.definition.stateVersion,
|
|
1153
|
+
...(invocationOptions.idempotencyKey === undefined
|
|
1154
|
+
? {}
|
|
1155
|
+
: { idempotencyKey: invocationOptions.idempotencyKey }),
|
|
1156
|
+
...("availableAt" in invocationOptions && invocationOptions.availableAt !== undefined
|
|
1157
|
+
? { availableAtMilliseconds: invocationOptions.availableAt.getTime() }
|
|
1158
|
+
: {}),
|
|
1159
|
+
});
|
|
1160
|
+
this.emitInstrumentation("message.enqueued", messageInstrumentation(message));
|
|
1161
|
+
this.wakeUp("actors");
|
|
1162
|
+
return this.messageReferenceFromRow(message);
|
|
1163
|
+
}
|
|
1164
|
+
messageReferenceFromRow(message) {
|
|
1165
|
+
return new MessageReference({
|
|
1166
|
+
runtime: this,
|
|
1167
|
+
id: message.id,
|
|
1168
|
+
requestId: message.request_id,
|
|
1169
|
+
actorType: message.actor_type,
|
|
1170
|
+
actorId: message.actor_id,
|
|
1171
|
+
sequence: BigInt(message.sequence),
|
|
1172
|
+
operation: message.operation,
|
|
1173
|
+
databaseTransactionActive: () => this.settings.database.transactionActive?.() ?? false,
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
async renewLease(turn, signal) {
|
|
1177
|
+
while (!signal.aborted) {
|
|
1178
|
+
await waitFor(this.settings.leaseRenewalIntervalMilliseconds, signal);
|
|
1179
|
+
if (signal.aborted)
|
|
1180
|
+
return;
|
|
1181
|
+
await this.repository.renewTurn(turn);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
async buildComponentSlots() {
|
|
1185
|
+
const factories = [
|
|
1186
|
+
...Array.from({ length: this.settings.workerCount }, () => () => this.worker()),
|
|
1187
|
+
...Array.from({ length: this.settings.effectWorkerCount }, () => () => this.effectWorker()),
|
|
1188
|
+
...Array.from({ length: this.settings.reminderSchedulerCount }, () => () => this.reminderScheduler()),
|
|
1189
|
+
...(broadcastsEnabled(this.settings)
|
|
1190
|
+
? Array.from({ length: this.settings.broadcastWorkerCount }, () => () => this.broadcastWorker())
|
|
1191
|
+
: []),
|
|
1192
|
+
...(this.settings.retentionIntervalMilliseconds > 0
|
|
1193
|
+
? [
|
|
1194
|
+
() => new MaintenanceScheduler({
|
|
1195
|
+
runtime: this,
|
|
1196
|
+
intervalMilliseconds: this.settings.retentionIntervalMilliseconds,
|
|
1197
|
+
failureEvent: "supervisor.retention_failed",
|
|
1198
|
+
operation: () => this.pruneExpiredRecords(),
|
|
1199
|
+
}),
|
|
1200
|
+
]
|
|
1201
|
+
: []),
|
|
1202
|
+
...(this.settings.deadProcessCleanupIntervalMilliseconds > 0
|
|
1203
|
+
? [
|
|
1204
|
+
() => new MaintenanceScheduler({
|
|
1205
|
+
runtime: this,
|
|
1206
|
+
intervalMilliseconds: this.settings.deadProcessCleanupIntervalMilliseconds,
|
|
1207
|
+
failureEvent: "supervisor.process_cleanup_failed",
|
|
1208
|
+
operation: () => this.cleanupStaleProcesses().then(() => undefined),
|
|
1209
|
+
}),
|
|
1210
|
+
]
|
|
1211
|
+
: []),
|
|
1212
|
+
];
|
|
1213
|
+
for (const { count, factory } of this.additionalComponents) {
|
|
1214
|
+
for (let index = 0; index < count; index += 1)
|
|
1215
|
+
factories.push(factory);
|
|
1216
|
+
}
|
|
1217
|
+
const slots = [];
|
|
1218
|
+
try {
|
|
1219
|
+
for (const factory of factories) {
|
|
1220
|
+
const component = factory();
|
|
1221
|
+
slots.push({ factory, component, cleanupAttempted: false });
|
|
1222
|
+
validateComponent(component);
|
|
1223
|
+
}
|
|
1224
|
+
return slots;
|
|
1225
|
+
}
|
|
1226
|
+
catch (error) {
|
|
1227
|
+
await Promise.allSettled(slots.map(({ component }) => this.stopComponent(component)));
|
|
1228
|
+
throw error;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
async pruneExpiredRecords() {
|
|
1232
|
+
for (const target of ["messages", "processes"]) {
|
|
1233
|
+
const count = await this.repository.pruneRetention(target);
|
|
1234
|
+
this.emitInstrumentation(`${target}.pruned`, { count });
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
async supervise(options) {
|
|
1238
|
+
const { slot, signal, shutdownDeadline } = options;
|
|
1239
|
+
let failureCount = 0;
|
|
1240
|
+
let replacementErrorName = null;
|
|
1241
|
+
while (!signal.aborted) {
|
|
1242
|
+
const component = slot.component;
|
|
1243
|
+
const requestShutdown = () => {
|
|
1244
|
+
try {
|
|
1245
|
+
component.requestShutdown();
|
|
1246
|
+
}
|
|
1247
|
+
catch (error) {
|
|
1248
|
+
this.emitInstrumentation("supervisor.component_cleanup_failed", {
|
|
1249
|
+
role: component.constructor.name,
|
|
1250
|
+
errorName: error instanceof Error ? error.name : "Error",
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
signal.addEventListener("abort", requestShutdown, { once: true });
|
|
1255
|
+
const outcome = await this.waitForComponent({ component, signal, shutdownDeadline });
|
|
1256
|
+
signal.removeEventListener("abort", requestShutdown);
|
|
1257
|
+
if (outcome.status === "timeout") {
|
|
1258
|
+
this.emitInstrumentation("supervisor.component_shutdown_timeout", {
|
|
1259
|
+
role: component.constructor.name,
|
|
1260
|
+
phase: "run",
|
|
1261
|
+
timeoutMilliseconds: this.settings.shutdownTimeoutMilliseconds,
|
|
1262
|
+
});
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
await this.stopComponent(component, {
|
|
1266
|
+
shutdown: { signal, deadline: shutdownDeadline },
|
|
1267
|
+
});
|
|
1268
|
+
slot.cleanupAttempted = true;
|
|
1269
|
+
if (outcome.status === "fulfilled") {
|
|
1270
|
+
replacementErrorName = null;
|
|
1271
|
+
}
|
|
1272
|
+
else {
|
|
1273
|
+
replacementErrorName = outcome.error instanceof Error ? outcome.error.name : "Error";
|
|
1274
|
+
}
|
|
1275
|
+
if (signal.aborted)
|
|
1276
|
+
return;
|
|
1277
|
+
while (!signal.aborted) {
|
|
1278
|
+
failureCount += 1;
|
|
1279
|
+
await waitFor(this.supervisorRestartDelay(failureCount), signal);
|
|
1280
|
+
if (signal.aborted)
|
|
1281
|
+
return;
|
|
1282
|
+
let replacement;
|
|
1283
|
+
try {
|
|
1284
|
+
replacement = slot.factory();
|
|
1285
|
+
validateComponent(replacement);
|
|
1286
|
+
slot.component = replacement;
|
|
1287
|
+
slot.cleanupAttempted = false;
|
|
1288
|
+
this.emitInstrumentation("supervisor.role_replaced", {
|
|
1289
|
+
role: component.constructor.name,
|
|
1290
|
+
errorName: replacementErrorName,
|
|
1291
|
+
failureCount,
|
|
1292
|
+
});
|
|
1293
|
+
break;
|
|
1294
|
+
}
|
|
1295
|
+
catch (error) {
|
|
1296
|
+
if (replacement)
|
|
1297
|
+
await this.stopComponent(replacement);
|
|
1298
|
+
replacementErrorName = error instanceof Error ? error.name : "Error";
|
|
1299
|
+
this.emitInstrumentation("supervisor.role_replacement_failed", {
|
|
1300
|
+
role: component.constructor.name,
|
|
1301
|
+
errorName: replacementErrorName,
|
|
1302
|
+
failureCount,
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
waitForComponent(options) {
|
|
1309
|
+
const { component, signal, shutdownDeadline } = options;
|
|
1310
|
+
return new Promise((resolve) => {
|
|
1311
|
+
let settled = false;
|
|
1312
|
+
let timeout;
|
|
1313
|
+
const finish = (outcome) => {
|
|
1314
|
+
if (settled)
|
|
1315
|
+
return;
|
|
1316
|
+
settled = true;
|
|
1317
|
+
if (timeout)
|
|
1318
|
+
clearTimeout(timeout);
|
|
1319
|
+
signal.removeEventListener("abort", beginShutdown);
|
|
1320
|
+
resolve(outcome);
|
|
1321
|
+
};
|
|
1322
|
+
const beginShutdown = () => {
|
|
1323
|
+
const deadline = shutdownDeadline();
|
|
1324
|
+
if (deadline === undefined)
|
|
1325
|
+
return;
|
|
1326
|
+
timeout = setTimeout(() => finish({ status: "timeout" }), Math.max(deadline - performance.now(), 0));
|
|
1327
|
+
};
|
|
1328
|
+
signal.addEventListener("abort", beginShutdown, { once: true });
|
|
1329
|
+
if (signal.aborted)
|
|
1330
|
+
beginShutdown();
|
|
1331
|
+
void Promise.resolve()
|
|
1332
|
+
.then(() => component.run(signal))
|
|
1333
|
+
.then(() => finish({ status: "fulfilled" }), (error) => finish({ status: "rejected", error }));
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
async stopComponent(component, options = {}) {
|
|
1337
|
+
try {
|
|
1338
|
+
component.requestShutdown();
|
|
1339
|
+
}
|
|
1340
|
+
catch (error) {
|
|
1341
|
+
this.reportComponentCleanupFailure(component, error);
|
|
1342
|
+
}
|
|
1343
|
+
try {
|
|
1344
|
+
const stopping = Promise.resolve().then(() => component.stop());
|
|
1345
|
+
if (options.shutdown === undefined) {
|
|
1346
|
+
await stopping;
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
const stopped = await promiseBeforeShutdownDeadline(stopping, options.shutdown);
|
|
1350
|
+
if (stopped)
|
|
1351
|
+
return;
|
|
1352
|
+
this.emitInstrumentation("supervisor.component_shutdown_timeout", {
|
|
1353
|
+
role: component.constructor.name,
|
|
1354
|
+
phase: "stop",
|
|
1355
|
+
timeoutMilliseconds: this.settings.shutdownTimeoutMilliseconds,
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
catch (error) {
|
|
1359
|
+
this.reportComponentCleanupFailure(component, error);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
reportComponentCleanupFailure(component, error) {
|
|
1363
|
+
this.emitInstrumentation("supervisor.component_cleanup_failed", {
|
|
1364
|
+
role: component.constructor.name,
|
|
1365
|
+
errorName: error instanceof Error ? error.name : "Error",
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
supervisorRestartDelay(failureCount) {
|
|
1369
|
+
const exponent = Math.min(failureCount - 1, 16);
|
|
1370
|
+
return Math.min(this.settings.supervisorRestartDelayMilliseconds * 2 ** exponent, this.settings.supervisorMaximumRestartDelayMilliseconds);
|
|
1371
|
+
}
|
|
1372
|
+
readObservables(actor, definition) {
|
|
1373
|
+
const stateBefore = stableJson(actorState(actor, definition.stateKeys));
|
|
1374
|
+
const intentCount = actor.intentCount();
|
|
1375
|
+
const values = withActorProjection({ actor, runtime: this }, () => actor.observableValues());
|
|
1376
|
+
if (stableJson(actorState(actor, definition.stateKeys)) !== stateBefore ||
|
|
1377
|
+
actor.intentCount() !== intentCount) {
|
|
1378
|
+
throw new QueryMutatedState("observables must not mutate actor state or stage durable work");
|
|
1379
|
+
}
|
|
1380
|
+
return values;
|
|
1381
|
+
}
|
|
1382
|
+
validatePayloadNames(registered, requestedNames) {
|
|
1383
|
+
const names = [...new Set(requestedNames)];
|
|
1384
|
+
for (const name of names) {
|
|
1385
|
+
if (registered.definition.payloads[name])
|
|
1386
|
+
continue;
|
|
1387
|
+
throw new UnknownPayloadBroadcast(`unknown payload broadcast ${JSON.stringify(name)}`);
|
|
1388
|
+
}
|
|
1389
|
+
return names;
|
|
1390
|
+
}
|
|
1391
|
+
async payloadProjectionSnapshot(options) {
|
|
1392
|
+
const instance = await this.repository.findInstanceByIdentity(options.actorType, options.actorId);
|
|
1393
|
+
if (!instance) {
|
|
1394
|
+
return {
|
|
1395
|
+
state: initialStateFor(options.registered.definition),
|
|
1396
|
+
instanceId: "0",
|
|
1397
|
+
revision: "0",
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
return {
|
|
1401
|
+
state: migrateState({
|
|
1402
|
+
definition: options.registered.definition,
|
|
1403
|
+
storedVersion: Number(instance.state_version),
|
|
1404
|
+
storedState: jsonObject(JSON.parse(instance.state)),
|
|
1405
|
+
}),
|
|
1406
|
+
instanceId: instance.id,
|
|
1407
|
+
revision: String(instance.state_revision),
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
async projectSubscriptionPayload(options) {
|
|
1411
|
+
try {
|
|
1412
|
+
const authorized = await this.settings.authorizeQuery({
|
|
1413
|
+
actorType: options.actorType,
|
|
1414
|
+
actorId: options.actorId,
|
|
1415
|
+
operation: options.name,
|
|
1416
|
+
arguments: {},
|
|
1417
|
+
authorizationContext: options.authorizationContext,
|
|
1418
|
+
});
|
|
1419
|
+
if (!authorized)
|
|
1420
|
+
return undefined;
|
|
1421
|
+
const actor = hydrateActor({
|
|
1422
|
+
definition: options.registered.definition,
|
|
1423
|
+
actorId: options.actorId,
|
|
1424
|
+
state: deepCopy(options.snapshot.state),
|
|
1425
|
+
});
|
|
1426
|
+
const stateBefore = stableJson(actorState(actor, options.registered.definition.stateKeys));
|
|
1427
|
+
const intentCount = actor.intentCount();
|
|
1428
|
+
const handler = options.registered.definition.payloads[options.name];
|
|
1429
|
+
if (!handler) {
|
|
1430
|
+
throw new UnknownPayloadBroadcast(`unknown payload broadcast ${options.name}`);
|
|
1431
|
+
}
|
|
1432
|
+
const rawPayload = await withActorProjection({ actor, runtime: this }, () => handler(actor, options.authorizationContext));
|
|
1433
|
+
if (stableJson(actorState(actor, options.registered.definition.stateKeys)) !== stateBefore ||
|
|
1434
|
+
actor.intentCount() !== intentCount) {
|
|
1435
|
+
throw new QueryMutatedState("payload broadcasts must not mutate actor state or stage work");
|
|
1436
|
+
}
|
|
1437
|
+
const payload = normalizeJson(rawPayload, { maxBytes: this.settings.maxPayloadBytes });
|
|
1438
|
+
if (!Array.isArray(payload) && (typeof payload !== "object" || payload === null)) {
|
|
1439
|
+
throw new InvalidPayloadBroadcast(`payload broadcast ${JSON.stringify(options.name)} must return a JSON object or array`);
|
|
1440
|
+
}
|
|
1441
|
+
return readonlyCopy({
|
|
1442
|
+
version: 1,
|
|
1443
|
+
kind: "payload",
|
|
1444
|
+
actorType: options.actorType,
|
|
1445
|
+
actorId: options.actorId,
|
|
1446
|
+
instanceId: options.snapshot.instanceId,
|
|
1447
|
+
revision: options.snapshot.revision,
|
|
1448
|
+
name: options.name,
|
|
1449
|
+
payload,
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
catch (error) {
|
|
1453
|
+
this.emitInstrumentation("payload_broadcast.failed", {
|
|
1454
|
+
actorType: options.actorType,
|
|
1455
|
+
actorId: options.actorId,
|
|
1456
|
+
payload: options.name,
|
|
1457
|
+
errorName: error instanceof Error ? error.name : "Error",
|
|
1458
|
+
});
|
|
1459
|
+
return undefined;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
fetchActor(actorType) {
|
|
1463
|
+
const actor = this.registry.get(actorType);
|
|
1464
|
+
if (!actor)
|
|
1465
|
+
throw new UnknownActorType(`unknown actor type ${actorType}`);
|
|
1466
|
+
return actor;
|
|
1467
|
+
}
|
|
1468
|
+
isQuery(definition, name) {
|
|
1469
|
+
return definition.queries.includes(name);
|
|
1470
|
+
}
|
|
1471
|
+
wakeUp(role) {
|
|
1472
|
+
try {
|
|
1473
|
+
Promise.resolve(this.settings.wakeUp.notify(role)).catch((error) => {
|
|
1474
|
+
this.logWakeUpFailure(role, error);
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
catch (error) {
|
|
1478
|
+
this.logWakeUpFailure(role, error);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
logWakeUpFailure(role, error) {
|
|
1482
|
+
this.settings.logger.error({
|
|
1483
|
+
event: "solid_objects.wake_up.failed",
|
|
1484
|
+
role,
|
|
1485
|
+
errorName: error instanceof Error ? error.name : "Error",
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
async authorize(options) {
|
|
1489
|
+
const { kind, reference, operation, argumentsValue, authorizationContext } = options;
|
|
1490
|
+
const policy = kind === "query" ? this.settings.authorizeQuery : this.settings.authorizeMessage;
|
|
1491
|
+
const authorized = await policy({
|
|
1492
|
+
actorType: reference.actorType,
|
|
1493
|
+
actorId: reference.actorId,
|
|
1494
|
+
operation,
|
|
1495
|
+
arguments: argumentsValue,
|
|
1496
|
+
authorizationContext,
|
|
1497
|
+
});
|
|
1498
|
+
if (!authorized)
|
|
1499
|
+
throw new Unauthorized(`actor ${kind} is not authorized`);
|
|
1500
|
+
}
|
|
1501
|
+
async authorizeAdministration(options) {
|
|
1502
|
+
const authorized = await this.settings.authorizeAdministration(options);
|
|
1503
|
+
if (!authorized)
|
|
1504
|
+
throw new Unauthorized("actor administration is not authorized");
|
|
1505
|
+
}
|
|
1506
|
+
async authorizeMessageReference(messageReference, authorizationContext) {
|
|
1507
|
+
const message = await this.repository.findMessage(messageReference.id);
|
|
1508
|
+
if (!message || message.request_id !== messageReference.requestId) {
|
|
1509
|
+
throw new Unauthorized("message result is not authorized");
|
|
1510
|
+
}
|
|
1511
|
+
const registered = this.fetchActor(message.actor_type);
|
|
1512
|
+
const reference = new ActorReferenceCore({
|
|
1513
|
+
runtime: this,
|
|
1514
|
+
actorClass: registered.actorClass,
|
|
1515
|
+
actorType: message.actor_type,
|
|
1516
|
+
actorId: message.actor_id,
|
|
1517
|
+
operations: registered.operations,
|
|
1518
|
+
queries: registered.queries,
|
|
1519
|
+
});
|
|
1520
|
+
const query = this.isQuery(registered.definition, message.operation);
|
|
1521
|
+
await this.authorize({
|
|
1522
|
+
kind: query ? "query" : "message",
|
|
1523
|
+
reference,
|
|
1524
|
+
operation: message.operation,
|
|
1525
|
+
argumentsValue: jsonObject(JSON.parse(message.arguments)),
|
|
1526
|
+
authorizationContext,
|
|
1527
|
+
});
|
|
1528
|
+
return message;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
export function createRuntime(configuration) {
|
|
1532
|
+
return new SolidObjectsRuntime(configuration);
|
|
1533
|
+
}
|
|
1534
|
+
export function configure(configuration) {
|
|
1535
|
+
const runtime = createRuntime(configuration);
|
|
1536
|
+
setDefaultRuntime(runtime);
|
|
1537
|
+
return runtime;
|
|
1538
|
+
}
|
|
1539
|
+
function parseResult(value) {
|
|
1540
|
+
if (value === null)
|
|
1541
|
+
return null;
|
|
1542
|
+
return normalizeJson(JSON.parse(value));
|
|
1543
|
+
}
|
|
1544
|
+
function invocationTimeout(options) {
|
|
1545
|
+
const timeout = options.timeoutMilliseconds ?? 5_000;
|
|
1546
|
+
if (!Number.isFinite(timeout) || timeout < 0) {
|
|
1547
|
+
throw new TypeError("timeoutMilliseconds must be a non-negative number");
|
|
1548
|
+
}
|
|
1549
|
+
return timeout;
|
|
1550
|
+
}
|
|
1551
|
+
function rejectionFromMessage(message) {
|
|
1552
|
+
const rejection = JSON.parse(message.rejection ?? "{}");
|
|
1553
|
+
const error = new Rejected(rejection);
|
|
1554
|
+
error.messageId = message.id;
|
|
1555
|
+
return error;
|
|
1556
|
+
}
|
|
1557
|
+
function messageFailure(message) {
|
|
1558
|
+
const details = jsonObject(JSON.parse(message.error ?? "{}"));
|
|
1559
|
+
return new MessageFailed({ messageId: message.id, details });
|
|
1560
|
+
}
|
|
1561
|
+
function actorDestroyedWhileWaiting() {
|
|
1562
|
+
return new ActorDestroyed("actor was destroyed while waiting for its result");
|
|
1563
|
+
}
|
|
1564
|
+
function messageInstrumentation(message) {
|
|
1565
|
+
return {
|
|
1566
|
+
messageId: message.id,
|
|
1567
|
+
requestId: message.request_id,
|
|
1568
|
+
actorType: message.actor_type,
|
|
1569
|
+
actorId: message.actor_id,
|
|
1570
|
+
sequence: String(message.sequence),
|
|
1571
|
+
operation: message.operation,
|
|
1572
|
+
deliveryMode: message.delivery_mode,
|
|
1573
|
+
attempt: Number(message.attempt_count),
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1576
|
+
function actorMessageContext(message) {
|
|
1577
|
+
return Object.freeze({
|
|
1578
|
+
id: message.id,
|
|
1579
|
+
requestId: message.request_id,
|
|
1580
|
+
idempotencyKey: message.idempotency_key,
|
|
1581
|
+
enqueuedAt: new Date(Number(message.created_at_ms)),
|
|
1582
|
+
actorType: message.actor_type,
|
|
1583
|
+
actorId: message.actor_id,
|
|
1584
|
+
sequence: BigInt(message.sequence),
|
|
1585
|
+
attempt: Number(message.attempt_count),
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
function restoreActorState(options) {
|
|
1589
|
+
const target = options.actor;
|
|
1590
|
+
for (const key of options.definition.stateKeys)
|
|
1591
|
+
target[key] = deepCopy(options.state[key]);
|
|
1592
|
+
}
|
|
1593
|
+
function effectInstrumentation(effect) {
|
|
1594
|
+
return {
|
|
1595
|
+
effectId: effect.id,
|
|
1596
|
+
effectName: effect.name,
|
|
1597
|
+
messageId: effect.message_id,
|
|
1598
|
+
actorType: effect.actor_type,
|
|
1599
|
+
actorId: effect.actor_id,
|
|
1600
|
+
attempt: Number(effect.attempt_count),
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
function broadcastInstrumentation(broadcast) {
|
|
1604
|
+
return {
|
|
1605
|
+
broadcastId: broadcast.id,
|
|
1606
|
+
messageId: broadcast.message_id,
|
|
1607
|
+
actorType: broadcast.actor_type,
|
|
1608
|
+
actorId: broadcast.actor_id,
|
|
1609
|
+
revision: String(broadcast.state_revision),
|
|
1610
|
+
attempt: Number(broadcast.attempt_count),
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
function reminderPageLimit(value) {
|
|
1614
|
+
const limit = value ?? 100;
|
|
1615
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1_000) {
|
|
1616
|
+
throw new TypeError("reminder limit must be an integer between 1 and 1000");
|
|
1617
|
+
}
|
|
1618
|
+
return limit;
|
|
1619
|
+
}
|
|
1620
|
+
function reminderPage(rows, limit) {
|
|
1621
|
+
const pageRows = rows.slice(0, limit);
|
|
1622
|
+
const items = Object.freeze(pageRows.map(reminderRecord));
|
|
1623
|
+
const last = pageRows.at(-1);
|
|
1624
|
+
return Object.freeze({
|
|
1625
|
+
items,
|
|
1626
|
+
nextCursor: rows.length > limit && last ? last.id : null,
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
function reminderRecord(row) {
|
|
1630
|
+
const parsedError = row.error === null ? null : jsonObject(JSON.parse(row.error));
|
|
1631
|
+
return Object.freeze({
|
|
1632
|
+
id: row.id,
|
|
1633
|
+
actorType: row.actor_type,
|
|
1634
|
+
actorId: row.actor_id,
|
|
1635
|
+
operation: row.operation,
|
|
1636
|
+
runAt: new Date(Number(row.run_at_ms)),
|
|
1637
|
+
intervalMilliseconds: row.interval_ms === null ? null : Number(row.interval_ms),
|
|
1638
|
+
missedPolicy: row.missed_policy,
|
|
1639
|
+
occurrence: Number(row.occurrence),
|
|
1640
|
+
status: row.status,
|
|
1641
|
+
errorName: typeof parsedError?.name === "string" ? parsedError.name : null,
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
function deadLetterFromRow(row) {
|
|
1645
|
+
return Object.freeze({
|
|
1646
|
+
id: row.id,
|
|
1647
|
+
messageId: row.message_id,
|
|
1648
|
+
actorType: row.actor_type,
|
|
1649
|
+
actorId: row.actor_id,
|
|
1650
|
+
operation: row.operation,
|
|
1651
|
+
deliveryMode: row.delivery_mode,
|
|
1652
|
+
arguments: readonlyCopy(jsonObject(JSON.parse(row.arguments))),
|
|
1653
|
+
attempts: Number(row.attempts),
|
|
1654
|
+
error: readonlyCopy(jsonObject(JSON.parse(row.error))),
|
|
1655
|
+
createdAt: new Date(Number(row.created_at_ms)),
|
|
1656
|
+
retriedMessageId: row.retried_message_id,
|
|
1657
|
+
});
|
|
1658
|
+
}
|
|
1659
|
+
function reconciliationLimit(value) {
|
|
1660
|
+
const limit = value ?? 100;
|
|
1661
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1_000) {
|
|
1662
|
+
throw new TypeError("reconciliation limit must be an integer between 1 and 1000");
|
|
1663
|
+
}
|
|
1664
|
+
return limit;
|
|
1665
|
+
}
|
|
1666
|
+
function reconciliationPage(rows, limit) {
|
|
1667
|
+
const pageRows = rows.slice(0, limit);
|
|
1668
|
+
const items = Object.freeze(pageRows.map(reconciliationInstanceFromRow));
|
|
1669
|
+
const last = pageRows.at(-1);
|
|
1670
|
+
return Object.freeze({
|
|
1671
|
+
items,
|
|
1672
|
+
nextCursor: rows.length > limit && last ? last.id : null,
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1675
|
+
function reconciliationInstanceFromRow(row) {
|
|
1676
|
+
return Object.freeze({
|
|
1677
|
+
id: row.id,
|
|
1678
|
+
actorType: row.actor_type,
|
|
1679
|
+
actorId: row.actor_id,
|
|
1680
|
+
stateVersion: Number(row.state_version),
|
|
1681
|
+
revision: String(row.state_revision),
|
|
1682
|
+
status: Number(row.paused) === 0 ? "active" : "paused",
|
|
1683
|
+
createdAt: new Date(Number(row.created_at_ms)),
|
|
1684
|
+
updatedAt: new Date(Number(row.updated_at_ms)),
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1687
|
+
function processMetadata(value) {
|
|
1688
|
+
const metadata = jsonObject(JSON.parse(value));
|
|
1689
|
+
if (typeof metadata.solidObjectsVersion !== "string" ||
|
|
1690
|
+
typeof metadata.nodeVersion !== "string") {
|
|
1691
|
+
throw new TypeError("invalid process metadata");
|
|
1692
|
+
}
|
|
1693
|
+
return Object.freeze({
|
|
1694
|
+
solidObjectsVersion: metadata.solidObjectsVersion,
|
|
1695
|
+
nodeVersion: metadata.nodeVersion,
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
function assertRetentionTarget(value) {
|
|
1699
|
+
if (value === "messages" || value === "instances" || value === "processes")
|
|
1700
|
+
return;
|
|
1701
|
+
throw new TypeError(`unknown retention target ${JSON.stringify(value)}`);
|
|
1702
|
+
}
|
|
1703
|
+
async function promiseBeforeShutdownDeadline(promise, shutdown) {
|
|
1704
|
+
return new Promise((resolve, reject) => {
|
|
1705
|
+
let settled = false;
|
|
1706
|
+
let timeout;
|
|
1707
|
+
const finish = (result) => {
|
|
1708
|
+
if (settled)
|
|
1709
|
+
return;
|
|
1710
|
+
settled = true;
|
|
1711
|
+
if (timeout)
|
|
1712
|
+
clearTimeout(timeout);
|
|
1713
|
+
shutdown.signal.removeEventListener("abort", beginShutdown);
|
|
1714
|
+
resolve(result);
|
|
1715
|
+
};
|
|
1716
|
+
const fail = (error) => {
|
|
1717
|
+
if (settled)
|
|
1718
|
+
return;
|
|
1719
|
+
settled = true;
|
|
1720
|
+
if (timeout)
|
|
1721
|
+
clearTimeout(timeout);
|
|
1722
|
+
shutdown.signal.removeEventListener("abort", beginShutdown);
|
|
1723
|
+
reject(error);
|
|
1724
|
+
};
|
|
1725
|
+
const beginShutdown = () => {
|
|
1726
|
+
const deadline = shutdown.deadline();
|
|
1727
|
+
if (deadline === undefined)
|
|
1728
|
+
return;
|
|
1729
|
+
timeout = setTimeout(() => finish(false), Math.max(deadline - performance.now(), 0));
|
|
1730
|
+
};
|
|
1731
|
+
shutdown.signal.addEventListener("abort", beginShutdown, { once: true });
|
|
1732
|
+
if (shutdown.signal.aborted)
|
|
1733
|
+
beginShutdown();
|
|
1734
|
+
void promise.then(() => finish(true), fail);
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
//# sourceMappingURL=runtime.js.map
|