solid-objects 0.16.0 → 0.17.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 +78 -0
- package/README.md +1 -0
- package/dist/actor-runtime.d.ts +7 -0
- package/dist/actor-runtime.d.ts.map +1 -1
- package/dist/actor.d.ts +1 -0
- package/dist/actor.d.ts.map +1 -1
- package/dist/actor.js +3 -0
- package/dist/actor.js.map +1 -1
- package/dist/cloudflare/engine.d.ts +1 -0
- package/dist/cloudflare/engine.d.ts.map +1 -1
- package/dist/cloudflare/engine.js +118 -15
- package/dist/cloudflare/engine.js.map +1 -1
- package/dist/cloudflare/records.d.ts +2 -0
- package/dist/cloudflare/records.d.ts.map +1 -1
- package/dist/cloudflare/runtime.d.ts +7 -0
- package/dist/cloudflare/runtime.d.ts.map +1 -1
- package/dist/cloudflare/runtime.js +13 -0
- package/dist/cloudflare/runtime.js.map +1 -1
- package/dist/configuration.d.ts +10 -0
- package/dist/configuration.d.ts.map +1 -1
- package/dist/configuration.js.map +1 -1
- package/dist/diagnostics.d.ts +37 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +73 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/doctor.js +1 -1
- package/dist/doctor.js.map +1 -1
- package/dist/effect-recovery-coordinator.d.ts.map +1 -1
- package/dist/effect-recovery-coordinator.js +4 -1
- package/dist/effect-recovery-coordinator.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/realtime.d.ts.map +1 -1
- package/dist/realtime.js +14 -1
- package/dist/realtime.js.map +1 -1
- package/dist/reference.d.ts +11 -0
- package/dist/reference.d.ts.map +1 -1
- package/dist/reference.js +22 -0
- package/dist/reference.js.map +1 -1
- package/dist/repository.d.ts +1 -1
- package/dist/repository.d.ts.map +1 -1
- package/dist/repository.js +7 -6
- package/dist/repository.js.map +1 -1
- package/dist/runtime.d.ts +8 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +108 -32
- package/dist/runtime.js.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +20 -5
- package/dist/schema.js.map +1 -1
- package/dist/serialization.js +3 -5
- package/dist/serialization.js.map +1 -1
- package/dist/telemetry.d.ts +22 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +126 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/transmit.js +2 -2
- package/dist/transmit.js.map +1 -1
- package/dist/turn.js +2 -2
- package/dist/turn.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/wake-up-notification.d.ts.map +1 -1
- package/dist/wake-up-notification.js +9 -1
- package/dist/wake-up-notification.js.map +1 -1
- package/docs/api.md +25 -3
- package/docs/authorization.md +6 -0
- package/docs/observability.md +192 -0
- package/docs/parity.md +89 -72
- package/package.json +3 -3
package/dist/runtime.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { actorDiagnostics } from "./diagnostics.js";
|
|
2
|
+
import { telemetryEvent, deliverTelemetry } from "./telemetry.js";
|
|
1
3
|
import { BroadcastWorker } from "./broadcast-worker.js";
|
|
2
4
|
import { buildSettings, broadcastsEnabled, validateComponent, } from "./configuration.js";
|
|
3
5
|
import { currentActor, withActorContext, withActorProjection, withApplicationWritesForbidden, } from "./context.js";
|
|
@@ -60,6 +62,7 @@ export class SolidObjectsRuntime {
|
|
|
60
62
|
realtime;
|
|
61
63
|
processes;
|
|
62
64
|
administration;
|
|
65
|
+
observers = new Set();
|
|
63
66
|
registry = new Map();
|
|
64
67
|
effects = new Map();
|
|
65
68
|
commitActions = new Map();
|
|
@@ -580,7 +583,7 @@ export class SolidObjectsRuntime {
|
|
|
580
583
|
: (await this.repository.remindersForInstance(instance.id)).map(scheduledReminderOf),
|
|
581
584
|
});
|
|
582
585
|
const stateBefore = stableJson(actorState(actor, registered.definition.stateKeys));
|
|
583
|
-
const
|
|
586
|
+
const intentsBefore = actor.intentSnapshot();
|
|
584
587
|
const snapshot = { ...state };
|
|
585
588
|
await withActorProjection({ actor, runtime: this }, async () => {
|
|
586
589
|
for (const query of registered.definition.queries) {
|
|
@@ -593,9 +596,15 @@ export class SolidObjectsRuntime {
|
|
|
593
596
|
}
|
|
594
597
|
});
|
|
595
598
|
if (stableJson(actorState(actor, registered.definition.stateKeys)) !== stateBefore ||
|
|
596
|
-
actor.
|
|
599
|
+
actor.intentSnapshot() !== intentsBefore) {
|
|
597
600
|
throw new QueryMutatedState("snapshot getters must not mutate actor state or stage work");
|
|
598
601
|
}
|
|
602
|
+
this.emitInstrumentation("snapshot.read", {
|
|
603
|
+
actorType: reference.actorType,
|
|
604
|
+
actorId: reference.actorId,
|
|
605
|
+
instanceId: instance?.id ?? null,
|
|
606
|
+
revision: String(instance?.state_revision ?? 0),
|
|
607
|
+
});
|
|
599
608
|
return {
|
|
600
609
|
snapshot: readonlyCopy(snapshot),
|
|
601
610
|
instanceId: instance?.id ?? "0",
|
|
@@ -955,6 +964,10 @@ export class SolidObjectsRuntime {
|
|
|
955
964
|
});
|
|
956
965
|
}
|
|
957
966
|
catch (error) {
|
|
967
|
+
this.emitInstrumentation("activation.failed", {
|
|
968
|
+
...activationInstrumentation(turn),
|
|
969
|
+
errorName: error instanceof Error ? error.name : "Error",
|
|
970
|
+
});
|
|
958
971
|
throw new ActorSetupFailed(error);
|
|
959
972
|
}
|
|
960
973
|
const definition = registered.definition;
|
|
@@ -968,23 +981,25 @@ export class SolidObjectsRuntime {
|
|
|
968
981
|
let stateBefore;
|
|
969
982
|
if (!activated) {
|
|
970
983
|
try {
|
|
984
|
+
this.emitInstrumentation("activation.started", activationInstrumentation(turn));
|
|
971
985
|
await withActorContext({ actor, runtime: this }, () => actor.activate());
|
|
972
986
|
activated = true;
|
|
973
|
-
this.emitInstrumentation("activation.
|
|
974
|
-
actorType: turn.message.actor_type,
|
|
975
|
-
actorId: turn.message.actor_id,
|
|
976
|
-
instanceId: turn.instance.id,
|
|
977
|
-
generation: String(turn.activationGeneration),
|
|
978
|
-
});
|
|
987
|
+
this.emitInstrumentation("activation.completed", activationInstrumentation(turn));
|
|
979
988
|
}
|
|
980
989
|
catch (error) {
|
|
981
990
|
renewalController.abort();
|
|
982
991
|
await renewal;
|
|
983
992
|
actor.discardIntents();
|
|
993
|
+
this.emitInstrumentation("activation.failed", {
|
|
994
|
+
...activationInstrumentation(turn),
|
|
995
|
+
errorName: error instanceof Error ? error.name : "Error",
|
|
996
|
+
});
|
|
984
997
|
throw new ActorSetupFailed(renewalError ?? error);
|
|
985
998
|
}
|
|
986
999
|
}
|
|
987
|
-
const startedAt =
|
|
1000
|
+
const startedAt = performance.now();
|
|
1001
|
+
if (Number(turn.message.attempt_count) > 1 && turn.message.error === null)
|
|
1002
|
+
this.emitInstrumentation("recovery.reclaimed", messageInstrumentation(turn.message));
|
|
988
1003
|
this.emitInstrumentation("message.started", messageInstrumentation(turn.message));
|
|
989
1004
|
try {
|
|
990
1005
|
stateBefore = deepCopy(actorState(actor, definition.stateKeys));
|
|
@@ -1022,6 +1037,7 @@ export class SolidObjectsRuntime {
|
|
|
1022
1037
|
throw new UnknownCommitAction(`unknown commit action ${JSON.stringify(intent.name)}`);
|
|
1023
1038
|
const attributes = {
|
|
1024
1039
|
commitAction: intent.name,
|
|
1040
|
+
activationGeneration: String(turn.activationGeneration),
|
|
1025
1041
|
...messageInstrumentation(turn.message),
|
|
1026
1042
|
};
|
|
1027
1043
|
this.emitInstrumentation("commit_action.started", attributes);
|
|
@@ -1064,10 +1080,13 @@ export class SolidObjectsRuntime {
|
|
|
1064
1080
|
nextRunAt: new Date(replacement.nextRunAtMilliseconds).toISOString(),
|
|
1065
1081
|
});
|
|
1066
1082
|
}
|
|
1083
|
+
if (recoveryMessage(turn.message))
|
|
1084
|
+
this.emitInstrumentation("recovery.completed", messageInstrumentation(turn.message));
|
|
1067
1085
|
this.warnAboutLargeState(turn.message, committed);
|
|
1068
1086
|
this.emitInstrumentation("message.completed", {
|
|
1069
1087
|
...messageInstrumentation(turn.message),
|
|
1070
|
-
|
|
1088
|
+
revision: String(turn.message.sequence),
|
|
1089
|
+
durationMilliseconds: performance.now() - startedAt,
|
|
1071
1090
|
});
|
|
1072
1091
|
return { actor, retainActivation: true, activated };
|
|
1073
1092
|
}
|
|
@@ -1080,7 +1099,7 @@ export class SolidObjectsRuntime {
|
|
|
1080
1099
|
if (error instanceof LostActivation) {
|
|
1081
1100
|
this.emitInstrumentation("activation.lost", {
|
|
1082
1101
|
...messageInstrumentation(turn.message),
|
|
1083
|
-
durationMilliseconds:
|
|
1102
|
+
durationMilliseconds: performance.now() - startedAt,
|
|
1084
1103
|
});
|
|
1085
1104
|
return { actor, retainActivation: false, activated };
|
|
1086
1105
|
}
|
|
@@ -1093,7 +1112,7 @@ export class SolidObjectsRuntime {
|
|
|
1093
1112
|
this.emitInstrumentation("message.rejected", {
|
|
1094
1113
|
...messageInstrumentation(turn.message),
|
|
1095
1114
|
code: error.code,
|
|
1096
|
-
durationMilliseconds:
|
|
1115
|
+
durationMilliseconds: performance.now() - startedAt,
|
|
1097
1116
|
});
|
|
1098
1117
|
return { actor, retainActivation: activated, activated };
|
|
1099
1118
|
}
|
|
@@ -1106,12 +1125,15 @@ export class SolidObjectsRuntime {
|
|
|
1106
1125
|
...messageInstrumentation(turn.message),
|
|
1107
1126
|
retryable,
|
|
1108
1127
|
errorName: error instanceof Error ? error.name : "Error",
|
|
1109
|
-
durationMilliseconds:
|
|
1128
|
+
durationMilliseconds: performance.now() - startedAt,
|
|
1110
1129
|
outcome,
|
|
1111
1130
|
});
|
|
1112
|
-
if (outcome === "
|
|
1131
|
+
if (outcome === "retrying")
|
|
1132
|
+
this.emitInstrumentation("message.retry", messageInstrumentation(turn.message));
|
|
1133
|
+
if (outcome === "dead" && recoveryMessage(turn.message))
|
|
1134
|
+
this.emitInstrumentation("recovery.failed", messageInstrumentation(turn.message));
|
|
1135
|
+
if (outcome === "dead")
|
|
1113
1136
|
this.emitInstrumentation("dead_letter.created", messageInstrumentation(turn.message));
|
|
1114
|
-
}
|
|
1115
1137
|
return { actor, retainActivation: activated, activated };
|
|
1116
1138
|
}
|
|
1117
1139
|
}
|
|
@@ -1153,6 +1175,7 @@ export class SolidObjectsRuntime {
|
|
|
1153
1175
|
await this.callerWorker?.stop();
|
|
1154
1176
|
this.realtime.close();
|
|
1155
1177
|
await this.closeWakeUp();
|
|
1178
|
+
this.observers.clear();
|
|
1156
1179
|
await this.settings.database.close();
|
|
1157
1180
|
clearDefaultRuntime(this);
|
|
1158
1181
|
}
|
|
@@ -1208,27 +1231,55 @@ export class SolidObjectsRuntime {
|
|
|
1208
1231
|
await this.discardWakeUp();
|
|
1209
1232
|
await this.repository.resetForTesting();
|
|
1210
1233
|
}
|
|
1234
|
+
diagnostics(reference, options = {}) {
|
|
1235
|
+
return actorDiagnostics({
|
|
1236
|
+
runtime: this,
|
|
1237
|
+
actorType: reference.actorType,
|
|
1238
|
+
actorId: reference.actorId,
|
|
1239
|
+
options,
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
async observe(reference, options) {
|
|
1243
|
+
await this.authorizeAdministration({
|
|
1244
|
+
action: "observe",
|
|
1245
|
+
resource: "actor_diagnostics",
|
|
1246
|
+
resourceId: JSON.stringify([reference.actorType, reference.actorId]),
|
|
1247
|
+
authorizationContext: options.authorizationContext,
|
|
1248
|
+
});
|
|
1249
|
+
if (this.observers.size >= 1000)
|
|
1250
|
+
throw new RangeError("at most 1000 local observers may be registered");
|
|
1251
|
+
const observer = (event) => {
|
|
1252
|
+
if (event.actorType === reference.actorType && event.actorId === reference.actorId)
|
|
1253
|
+
return options.onEvent(event);
|
|
1254
|
+
};
|
|
1255
|
+
this.observers.add(observer);
|
|
1256
|
+
return () => {
|
|
1257
|
+
this.observers.delete(observer);
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1211
1260
|
emitInstrumentation(name, attributes) {
|
|
1212
|
-
|
|
1213
|
-
if (!instrumentation)
|
|
1261
|
+
if (!this.settings.instrumentation && this.observers.size === 0)
|
|
1214
1262
|
return;
|
|
1215
1263
|
try {
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
this.
|
|
1224
|
-
event:
|
|
1225
|
-
instrumentationEvent: `solid_objects.${name}`,
|
|
1226
|
-
errorName: error instanceof Error ? error.name : "Error",
|
|
1227
|
-
});
|
|
1264
|
+
const event = telemetryEvent({ name, adapter: this.settings.database.family, attributes });
|
|
1265
|
+
if (this.settings.instrumentation)
|
|
1266
|
+
deliverTelemetry({
|
|
1267
|
+
observer: this.settings.instrumentation,
|
|
1268
|
+
event,
|
|
1269
|
+
logger: this.settings.logger,
|
|
1270
|
+
});
|
|
1271
|
+
for (const observer of this.observers)
|
|
1272
|
+
deliverTelemetry({ observer, event, logger: this.settings.logger });
|
|
1228
1273
|
}
|
|
1274
|
+
catch { }
|
|
1229
1275
|
}
|
|
1230
1276
|
async executeEffect(effect) {
|
|
1231
1277
|
try {
|
|
1278
|
+
this.emitInstrumentation("outbox.age", {
|
|
1279
|
+
...effectInstrumentation(effect),
|
|
1280
|
+
outboxKind: "effect",
|
|
1281
|
+
ageMilliseconds: Math.max(0, Date.now() - Number(effect.available_at_ms)),
|
|
1282
|
+
});
|
|
1232
1283
|
const handler = this.effects.get(effect.name);
|
|
1233
1284
|
if (!handler)
|
|
1234
1285
|
throw new UnknownEffect(`unknown effect ${JSON.stringify(effect.name)}`);
|
|
@@ -1266,20 +1317,30 @@ export class SolidObjectsRuntime {
|
|
|
1266
1317
|
if (!actor.operations.has(dispatchOperation)) {
|
|
1267
1318
|
throw new UnknownOperation(`unknown reminder operation ${JSON.stringify(dispatchOperation)}`);
|
|
1268
1319
|
}
|
|
1269
|
-
|
|
1320
|
+
const message = await this.repository.enqueueReminder(reminder, options);
|
|
1321
|
+
if (!message)
|
|
1270
1322
|
return;
|
|
1271
1323
|
this.wakeUp("actors");
|
|
1272
1324
|
this.emitInstrumentation("reminder.enqueued", {
|
|
1273
1325
|
reminderId: reminder.id,
|
|
1326
|
+
messageId: message.id,
|
|
1327
|
+
attempt: Number(message.attempt_count),
|
|
1274
1328
|
actorType: reminder.actor_type,
|
|
1275
1329
|
actorId: reminder.actor_id,
|
|
1276
1330
|
operation: reminder.operation,
|
|
1331
|
+
instanceId: reminder.instance_id,
|
|
1332
|
+
latenessMilliseconds: Math.max(0, (options.nowMilliseconds ?? Date.now()) - Number(reminder.run_at_ms)),
|
|
1277
1333
|
occurrence: Number(reminder.occurrence),
|
|
1278
1334
|
});
|
|
1279
1335
|
}
|
|
1280
1336
|
async executeBroadcast(broadcast) {
|
|
1281
1337
|
const deliver = this.settings.broadcast;
|
|
1282
1338
|
try {
|
|
1339
|
+
this.emitInstrumentation("outbox.age", {
|
|
1340
|
+
...broadcastInstrumentation(broadcast),
|
|
1341
|
+
outboxKind: "broadcast",
|
|
1342
|
+
ageMilliseconds: Math.max(0, Date.now() - Number(broadcast.available_at_ms)),
|
|
1343
|
+
});
|
|
1283
1344
|
const event = readonlyCopy({
|
|
1284
1345
|
actorType: broadcast.actor_type,
|
|
1285
1346
|
actorId: broadcast.actor_id,
|
|
@@ -1722,14 +1783,14 @@ export class SolidObjectsRuntime {
|
|
|
1722
1783
|
state: deepCopy(options.snapshot.state),
|
|
1723
1784
|
});
|
|
1724
1785
|
const stateBefore = stableJson(actorState(actor, options.registered.definition.stateKeys));
|
|
1725
|
-
const
|
|
1786
|
+
const intentsBefore = actor.intentSnapshot();
|
|
1726
1787
|
const handler = options.registered.definition.payloads[options.name];
|
|
1727
1788
|
if (!handler) {
|
|
1728
1789
|
throw new UnknownPayloadBroadcast(`unknown payload broadcast ${options.name}`);
|
|
1729
1790
|
}
|
|
1730
1791
|
const rawPayload = await withActorProjection({ actor, runtime: this }, () => handler(actor, options.authorizationContext));
|
|
1731
1792
|
if (stableJson(actorState(actor, options.registered.definition.stateKeys)) !== stateBefore ||
|
|
1732
|
-
actor.
|
|
1793
|
+
actor.intentSnapshot() !== intentsBefore) {
|
|
1733
1794
|
throw new QueryMutatedState("payload broadcasts must not mutate actor state or stage work");
|
|
1734
1795
|
}
|
|
1735
1796
|
const payload = normalizeJson(rawPayload, { maxBytes: this.settings.maxPayloadBytes });
|
|
@@ -1926,8 +1987,14 @@ function messageFailure(message) {
|
|
|
1926
1987
|
function actorDestroyedWhileWaiting() {
|
|
1927
1988
|
return new ActorDestroyed("actor was destroyed while waiting for its result");
|
|
1928
1989
|
}
|
|
1990
|
+
function recoveryMessage(message) {
|
|
1991
|
+
return (message.delivery_mode === "internal" &&
|
|
1992
|
+
message.idempotency_key?.startsWith("effect:") === true &&
|
|
1993
|
+
message.idempotency_key.endsWith(":recovery"));
|
|
1994
|
+
}
|
|
1929
1995
|
function messageInstrumentation(message) {
|
|
1930
1996
|
return {
|
|
1997
|
+
instanceId: message.instance_id,
|
|
1931
1998
|
messageId: message.id,
|
|
1932
1999
|
requestId: message.request_id,
|
|
1933
2000
|
actorType: message.actor_type,
|
|
@@ -1938,6 +2005,13 @@ function messageInstrumentation(message) {
|
|
|
1938
2005
|
attempt: Number(message.attempt_count),
|
|
1939
2006
|
};
|
|
1940
2007
|
}
|
|
2008
|
+
function activationInstrumentation(turn) {
|
|
2009
|
+
return {
|
|
2010
|
+
...messageInstrumentation(turn.message),
|
|
2011
|
+
generation: String(turn.activationGeneration),
|
|
2012
|
+
ownerId: turn.processId,
|
|
2013
|
+
};
|
|
2014
|
+
}
|
|
1941
2015
|
function actorMessageContext(message) {
|
|
1942
2016
|
return Object.freeze({
|
|
1943
2017
|
id: message.id,
|
|
@@ -1957,6 +2031,7 @@ function restoreActorState(options) {
|
|
|
1957
2031
|
}
|
|
1958
2032
|
function effectInstrumentation(effect) {
|
|
1959
2033
|
return {
|
|
2034
|
+
instanceId: effect.instance_id,
|
|
1960
2035
|
effectId: effect.id,
|
|
1961
2036
|
effectName: effect.name,
|
|
1962
2037
|
messageId: effect.message_id,
|
|
@@ -1967,6 +2042,7 @@ function effectInstrumentation(effect) {
|
|
|
1967
2042
|
}
|
|
1968
2043
|
function broadcastInstrumentation(broadcast) {
|
|
1969
2044
|
return {
|
|
2045
|
+
instanceId: broadcast.instance_id,
|
|
1970
2046
|
broadcastId: broadcast.id,
|
|
1971
2047
|
messageId: broadcast.message_id,
|
|
1972
2048
|
actorType: broadcast.actor_type,
|