solid-objects 0.16.1 → 0.17.1
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 +84 -0
- package/README.md +11 -2
- 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/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/agents.md +300 -0
- package/docs/api.md +25 -3
- package/docs/authorization.md +6 -0
- package/docs/comparisons.md +36 -0
- package/docs/observability.md +192 -0
- package/docs/parity.md +85 -72
- package/docs/releasing.md +12 -2
- package/docs/virtual-actors.md +242 -0
- package/examples/ticket-sale.ts +57 -0
- package/package.json +8 -5
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { readonlyCopy } from "./serialization.js";
|
|
2
|
+
export const portableAttributes = new Set([
|
|
3
|
+
"failureCount",
|
|
4
|
+
"phase",
|
|
5
|
+
"activationOwnerId",
|
|
6
|
+
"activationGeneration",
|
|
7
|
+
"pollingIntervalMilliseconds",
|
|
8
|
+
"idlePollingIntervalMilliseconds",
|
|
9
|
+
"currentIntervalMilliseconds",
|
|
10
|
+
"component",
|
|
11
|
+
"workers",
|
|
12
|
+
"effectWorkers",
|
|
13
|
+
"broadcastWorkers",
|
|
14
|
+
"reminderSchedulers",
|
|
15
|
+
"actorType",
|
|
16
|
+
"actorId",
|
|
17
|
+
"instanceId",
|
|
18
|
+
"incarnation",
|
|
19
|
+
"revision",
|
|
20
|
+
"messageId",
|
|
21
|
+
"requestId",
|
|
22
|
+
"attempt",
|
|
23
|
+
"sequence",
|
|
24
|
+
"operation",
|
|
25
|
+
"deliveryMode",
|
|
26
|
+
"generation",
|
|
27
|
+
"durationMilliseconds",
|
|
28
|
+
"latenessMilliseconds",
|
|
29
|
+
"ageMilliseconds",
|
|
30
|
+
"depth",
|
|
31
|
+
"count",
|
|
32
|
+
"errorName",
|
|
33
|
+
"outcome",
|
|
34
|
+
"retryable",
|
|
35
|
+
"status",
|
|
36
|
+
"effectId",
|
|
37
|
+
"effectName",
|
|
38
|
+
"reminderId",
|
|
39
|
+
"occurrence",
|
|
40
|
+
"broadcastId",
|
|
41
|
+
"code",
|
|
42
|
+
"role",
|
|
43
|
+
"reason",
|
|
44
|
+
"processId",
|
|
45
|
+
"processKind",
|
|
46
|
+
"ownerId",
|
|
47
|
+
"componentCount",
|
|
48
|
+
"byteCount",
|
|
49
|
+
"thresholdBytes",
|
|
50
|
+
"previousRunAt",
|
|
51
|
+
"nextRunAt",
|
|
52
|
+
"name",
|
|
53
|
+
"commitAction",
|
|
54
|
+
"outboxKind",
|
|
55
|
+
"truncated",
|
|
56
|
+
"payload",
|
|
57
|
+
"waitingOn",
|
|
58
|
+
"timeoutMilliseconds",
|
|
59
|
+
"intervalMilliseconds",
|
|
60
|
+
"previousIntervalMilliseconds",
|
|
61
|
+
]);
|
|
62
|
+
export function telemetryEvent(options) {
|
|
63
|
+
const attributes = {};
|
|
64
|
+
for (const [key, value] of Object.entries(options.attributes)) {
|
|
65
|
+
if (portableAttributes.has(key) &&
|
|
66
|
+
(value === null || ["string", "number", "boolean"].includes(typeof value))) {
|
|
67
|
+
attributes[key] = value;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const name = `solid_objects.${options.name}`;
|
|
71
|
+
const labels = Object.freeze({
|
|
72
|
+
event: name,
|
|
73
|
+
adapter: options.adapter,
|
|
74
|
+
actorType: String(attributes.actorType ?? ""),
|
|
75
|
+
});
|
|
76
|
+
const metrics = [
|
|
77
|
+
{ name: "solid_objects.events", kind: "counter", unit: "1", value: 1, labels },
|
|
78
|
+
];
|
|
79
|
+
for (const [field, metric, kind, unit] of [
|
|
80
|
+
["durationMilliseconds", "solid_objects.duration", "histogram", "ms"],
|
|
81
|
+
["latenessMilliseconds", "solid_objects.reminder.lateness", "histogram", "ms"],
|
|
82
|
+
["ageMilliseconds", "solid_objects.outbox.age", "histogram", "ms"],
|
|
83
|
+
["depth", "solid_objects.mailbox.depth", "gauge", "1"],
|
|
84
|
+
]) {
|
|
85
|
+
const value = attributes[field];
|
|
86
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
87
|
+
metrics.push({ name: metric, kind, unit, value: Math.max(0, value), labels });
|
|
88
|
+
}
|
|
89
|
+
return Object.freeze({
|
|
90
|
+
schemaVersion: 1,
|
|
91
|
+
name,
|
|
92
|
+
occurredAt: new Date().toISOString(),
|
|
93
|
+
adapter: options.adapter,
|
|
94
|
+
actorType: identifier(attributes.actorType),
|
|
95
|
+
actorId: identifier(attributes.actorId),
|
|
96
|
+
incarnation: identifier(attributes.incarnation ?? attributes.instanceId),
|
|
97
|
+
revision: identifier(attributes.revision),
|
|
98
|
+
messageId: identifier(attributes.messageId),
|
|
99
|
+
attempt: typeof attributes.attempt === "number" ? attributes.attempt : 0,
|
|
100
|
+
attributes: readonlyCopy(attributes),
|
|
101
|
+
metrics: Object.freeze(metrics.map((sample) => Object.freeze(sample))),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
export function deliverTelemetry(options) {
|
|
105
|
+
const failed = (error) => {
|
|
106
|
+
try {
|
|
107
|
+
const result = options.logger.error({
|
|
108
|
+
event: "solid_objects.instrumentation.failed",
|
|
109
|
+
instrumentationEvent: options.event.name,
|
|
110
|
+
errorName: error instanceof Error ? error.name : "Error",
|
|
111
|
+
});
|
|
112
|
+
void Promise.resolve(result).catch(() => { });
|
|
113
|
+
}
|
|
114
|
+
catch { }
|
|
115
|
+
};
|
|
116
|
+
try {
|
|
117
|
+
void Promise.resolve(options.observer(options.event)).catch(failed);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
failed(error);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function identifier(value) {
|
|
124
|
+
return typeof value === "string" || typeof value === "number" ? String(value) : null;
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=telemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"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"}
|
package/dist/transmit.js
CHANGED
|
@@ -28,7 +28,7 @@ export async function receiveTransmitEnvelope(options) {
|
|
|
28
28
|
throw new InvalidPayload(`transmit envelope requires a non-empty ${field}`);
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
const argumentsValue = envelope.arguments
|
|
31
|
+
const argumentsValue = envelope.arguments === undefined ? {} : envelope.arguments;
|
|
32
32
|
if (!isJsonObject(argumentsValue)) {
|
|
33
33
|
throw new InvalidPayload("transmit envelope arguments must be a JSON object");
|
|
34
34
|
}
|
|
@@ -80,7 +80,7 @@ async function undeliveredEnvelopesThrough(input) {
|
|
|
80
80
|
AND messages.actor_type = ?
|
|
81
81
|
AND messages.actor_id = ?
|
|
82
82
|
AND messages.sequence <= (SELECT sequence FROM ${messages} WHERE id = ?)
|
|
83
|
-
ORDER BY messages.sequence, effects.id`, [effectName, context.actorType, context.actorId, context.sourceMessageId]));
|
|
83
|
+
ORDER BY messages.sequence, effects.position, effects.id`, [effectName, context.actorType, context.actorId, context.sourceMessageId]));
|
|
84
84
|
const envelopes = [];
|
|
85
85
|
for (const row of rows) {
|
|
86
86
|
const argumentsValue = JSON.parse(row.arguments);
|
package/dist/transmit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transmit.js","sourceRoot":"","sources":["../src/transmit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAI/D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAgBtD,MAAM,OAAO,uBAAwB,SAAQ,iBAAiB;IAC5D,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAA;IACvC,CAAC;CACF;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAgC;IAC/D,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,eAAe,CAAA;IACxD,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,UAAU,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,EAAE;QAC3E,qBAAqB,CAAC,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC,CAAA;QAClD,MAAM,WAAW,GAAG,MAAM,2BAA2B,CAAC;YACpD,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,UAAU;YACV,OAAO;SACR,CAAC,CAAA;QACF,KAAK,MAAM,QAAQ,IAAI,WAAW;YAAE,MAAM,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;QACnE,OAAO,IAAI,CAAA;IACb,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,OAG7C;IACC,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAA;IAC5B,KAAK,MAAM,KAAK,IAAI,CAAC,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,CAAU,EAAE,CAAC;QAC/E,IAAI,OAAO,QAAQ,CAAC,KAAK,CAAC,KAAK,QAAQ,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxE,MAAM,IAAI,cAAc,CAAC,0CAA0C,KAAK,EAAE,CAAC,CAAA;QAC7E,CAAC;IACH,CAAC;IACD,MAAM,cAAc,GAAG,QAAQ,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"transmit.js","sourceRoot":"","sources":["../src/transmit.ts"],"names":[],"mappings":"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"}
|
package/dist/turn.js
CHANGED
|
@@ -5,10 +5,10 @@ import { jsonObject, normalizeJson, stableJson } from "./serialization.js";
|
|
|
5
5
|
export function readActorObservables(options) {
|
|
6
6
|
const { actor, definition, runtime } = options;
|
|
7
7
|
const before = options.stateJson ?? stableJson(actorState(actor, definition.stateKeys));
|
|
8
|
-
const
|
|
8
|
+
const intentsBefore = actor.intentSnapshot();
|
|
9
9
|
const projection = withActorProjection({ actor, runtime }, () => actor.observableValues());
|
|
10
10
|
if (stableJson(actorState(actor, definition.stateKeys)) !== before ||
|
|
11
|
-
actor.
|
|
11
|
+
actor.intentSnapshot() !== intentsBefore) {
|
|
12
12
|
throw new QueryMutatedState("observables must not mutate actor state or stage durable work");
|
|
13
13
|
}
|
|
14
14
|
return projection;
|
package/dist/turn.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"turn.js","sourceRoot":"","sources":["../src/turn.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AACpE,OAAO,EAAE,UAAU,EAAiC,MAAM,iBAAiB,CAAA;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAG1E,MAAM,UAAU,oBAAoB,CAAC,OAKpC;IACC,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,OAAO,CAAA;IAC9C,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,IAAI,UAAU,CAAC,UAAU,CAAC,KAAK,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC,CAAA;IACvF,MAAM,
|
|
1
|
+
{"version":3,"file":"turn.js","sourceRoot":"","sources":["../src/turn.ts"],"names":[],"mappings":"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"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.17.1";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = "0.
|
|
1
|
+
export const VERSION = "0.17.1";
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wake-up-notification.d.ts","sourceRoot":"","sources":["../src/wake-up-notification.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE7D,wBAAgB,YAAY,CAAC,OAAO,EAAE;IACpC,OAAO,EAAE,aAAa,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,UAAU,CAAA;CACjB,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"wake-up-notification.d.ts","sourceRoot":"","sources":["../src/wake-up-notification.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE7D,wBAAgB,YAAY,CAAC,OAAO,EAAE;IACpC,OAAO,EAAE,aAAa,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,UAAU,CAAA;CACjB,GAAG,IAAI,CAkBP"}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
export function notifyWakeUp(options) {
|
|
2
2
|
const logFailure = (errorName) => {
|
|
3
|
-
|
|
3
|
+
try {
|
|
4
|
+
const result = options.logger.error({
|
|
5
|
+
event: "solid_objects.wake_up.failed",
|
|
6
|
+
role: options.role,
|
|
7
|
+
errorName,
|
|
8
|
+
});
|
|
9
|
+
void Promise.resolve(result).catch(() => { });
|
|
10
|
+
}
|
|
11
|
+
catch { }
|
|
4
12
|
};
|
|
5
13
|
try {
|
|
6
14
|
Promise.resolve(options.adapter.notify(options.role)).catch((error) => logFailure(error instanceof Error ? error.name : "Error"));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wake-up-notification.js","sourceRoot":"","sources":["../src/wake-up-notification.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,YAAY,CAAC,OAI5B;IACC,MAAM,UAAU,GAAG,CAAC,SAAiB,EAAQ,EAAE;QAC7C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"wake-up-notification.js","sourceRoot":"","sources":["../src/wake-up-notification.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,YAAY,CAAC,OAI5B;IACC,MAAM,UAAU,GAAG,CAAC,SAAiB,EAAQ,EAAE;QAC7C,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;gBAClC,KAAK,EAAE,8BAA8B;gBACrC,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,SAAS;aACV,CAAC,CAAA;YACF,KAAK,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;QAC9C,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACZ,CAAC,CAAA;IACD,IAAI,CAAC;QACH,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CACpE,UAAU,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAC1D,CAAA;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,UAAU,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;IAC3D,CAAC;AACH,CAAC"}
|
package/docs/agents.md
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# Solid Objects for coding agents (TypeScript and Node.js)
|
|
2
|
+
|
|
3
|
+
This guide is for a coding agent that evaluates or adds Solid Objects in a
|
|
4
|
+
TypeScript or Node.js application. It gives the facts and steps in a fixed
|
|
5
|
+
order. Each step links to the maintained documentation for more detail.
|
|
6
|
+
|
|
7
|
+
## 1. Check the fit first
|
|
8
|
+
|
|
9
|
+
Use Solid Objects only if you can answer yes to most of these questions:
|
|
10
|
+
|
|
11
|
+
1. Does one identity, such as a room, cart, account, device, or document, own
|
|
12
|
+
the state?
|
|
13
|
+
2. Must writes for that identity happen one at a time across requests and
|
|
14
|
+
processes?
|
|
15
|
+
3. Must some work happen later or continue after a process exits?
|
|
16
|
+
4. Is the state a bounded JSON document, not a large relational dataset?
|
|
17
|
+
5. Can the application run `runtime.run(signal)` in at least one process for
|
|
18
|
+
reminders, effects, and realtime delivery?
|
|
19
|
+
|
|
20
|
+
Select a simpler tool in these cases:
|
|
21
|
+
|
|
22
|
+
| Requirement | Use instead |
|
|
23
|
+
| ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
24
|
+
| The invariant fits in one request | A transaction, a constraint, or `SELECT ... FOR UPDATE` |
|
|
25
|
+
| CPU-intensive or parallel work | `worker_threads` or a worker pool. An actor serializes work |
|
|
26
|
+
| Thousands of writes for each second to one key | A counter column or a cache. Solid Objects Pro covers this case for Rails; its Node build is in development |
|
|
27
|
+
| One atomic change across two actor identities | One SQL transaction on normal tables |
|
|
28
|
+
| Exactly-once calls to an external API | Not available from any actor library. Use provider idempotency keys |
|
|
29
|
+
| Replay of named workflow steps from a step log | A durable execution engine, such as DBOS or Temporal |
|
|
30
|
+
|
|
31
|
+
The full list is in [Choosing Solid Objects](fit.md). The category guide is
|
|
32
|
+
[Virtual actors in TypeScript and Node.js](virtual-actors.md).
|
|
33
|
+
|
|
34
|
+
## 2. Package identity
|
|
35
|
+
|
|
36
|
+
| Item | Value |
|
|
37
|
+
| ------------------ | ----------------------------------------------------------------------------------------------------- |
|
|
38
|
+
| npm package | `solid-objects` |
|
|
39
|
+
| Repository | <https://github.com/cardmagic/solid-objects-js> |
|
|
40
|
+
| Website | <https://solidobjects.dev/js> |
|
|
41
|
+
| Ruby on Rails gem | `solid_objects`, a separate implementation of the same model |
|
|
42
|
+
| Commercial package | `@solidobjects/pro`, a separate paid package in development. The open-source package does not need it |
|
|
43
|
+
|
|
44
|
+
Solid Objects is not SolidJS and has no relation to it. It is not affiliated
|
|
45
|
+
with Cloudflare.
|
|
46
|
+
|
|
47
|
+
## 3. Requirements
|
|
48
|
+
|
|
49
|
+
- Node.js 24.4 or newer. Prefer 24.15 or newer, where `node:sqlite` is no
|
|
50
|
+
longer experimental.
|
|
51
|
+
- The package is ESM only. TypeScript applications need TypeScript 5.9 or
|
|
52
|
+
newer.
|
|
53
|
+
- SQLite through the `node:sqlite` module, PostgreSQL 14 or newer with the
|
|
54
|
+
`pg` package, or MySQL 8.0 or newer with InnoDB and the `mysql2` package.
|
|
55
|
+
- Redis is optional. It only shortens wake-up latency and holds no durable
|
|
56
|
+
state.
|
|
57
|
+
|
|
58
|
+
[Supported versions](support.md) lists the CI matrix.
|
|
59
|
+
|
|
60
|
+
## 4. Install
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm install solid-objects
|
|
64
|
+
npx solid-objects quickstart --yes
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The quickstart runs 25 concurrent calls against one identity on a temporary
|
|
68
|
+
SQLite database. It proves that no update is lost. Add `pg` or `mysql2` when
|
|
69
|
+
you use PostgreSQL or MySQL.
|
|
70
|
+
|
|
71
|
+
Create the runtime and its tables at startup:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { configure } from "solid-objects"
|
|
75
|
+
import { sqlite } from "solid-objects/database/sqlite"
|
|
76
|
+
|
|
77
|
+
const runtime = configure({
|
|
78
|
+
database: sqlite({ path: "app.sqlite3" }),
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
await runtime.install()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
[Configuration](configuration.md) lists each option and database adapter.
|
|
85
|
+
|
|
86
|
+
## 5. Authorize
|
|
87
|
+
|
|
88
|
+
All authorization callbacks deny by default. A new runtime answers no actor
|
|
89
|
+
call until you write a policy. Do not remove this behavior.
|
|
90
|
+
|
|
91
|
+
For a local demonstration only, grant messages and queries:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const runtime = configure({
|
|
95
|
+
database: sqlite({ path: "app.sqlite3" }),
|
|
96
|
+
authorizeMessage: () => true,
|
|
97
|
+
authorizeQuery: () => true,
|
|
98
|
+
})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Keep `authorizeDestroy`, `authorizeSubscription`, and
|
|
102
|
+
`authorizeAdministration` denied in a demonstration.
|
|
103
|
+
|
|
104
|
+
A production policy must bind the actor type and ID to the authenticated user
|
|
105
|
+
or tenant. An actor ID is not a permission:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
const ownsCart = ({
|
|
109
|
+
actorType,
|
|
110
|
+
actorId,
|
|
111
|
+
authorizationContext,
|
|
112
|
+
}: {
|
|
113
|
+
actorType: string
|
|
114
|
+
actorId: string
|
|
115
|
+
authorizationContext: unknown
|
|
116
|
+
}) => {
|
|
117
|
+
if (actorType !== "ShoppingCart") return false
|
|
118
|
+
if (typeof authorizationContext !== "object" || authorizationContext === null) return false
|
|
119
|
+
if (!("userId" in authorizationContext)) return false
|
|
120
|
+
return typeof authorizationContext.userId === "string" && actorId === authorizationContext.userId
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const runtime = configure({
|
|
124
|
+
database: sqlite({ path: "app.sqlite3" }),
|
|
125
|
+
authorizeMessage: ownsCart,
|
|
126
|
+
authorizeQuery: ownsCart,
|
|
127
|
+
})
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Pass the context on each call:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
await ShoppingCart.ref(subject.userId)
|
|
134
|
+
.with({ authorizationContext: subject })
|
|
135
|
+
.addItem({ productId: "shirt-123" })
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
[Authorization](authorization.md) covers each entry point.
|
|
139
|
+
|
|
140
|
+
## 6. Define an actor
|
|
141
|
+
|
|
142
|
+
This actor is the example from the [virtual actor guide](virtual-actors.md):
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import { Actor } from "solid-objects"
|
|
146
|
+
|
|
147
|
+
const HOLD_MILLISECONDS = 10 * 60 * 1000
|
|
148
|
+
|
|
149
|
+
export class TicketSale extends Actor {
|
|
150
|
+
static override readonly actorType = "TicketSale"
|
|
151
|
+
|
|
152
|
+
available = 1
|
|
153
|
+
holds: Record<string, number> = {}
|
|
154
|
+
|
|
155
|
+
hold({ buyer }: { buyer: string }): { held: boolean; available: number } {
|
|
156
|
+
if (this.available === 0 || Object.hasOwn(this.holds, buyer)) {
|
|
157
|
+
return { held: false, available: this.available }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
this.available -= 1
|
|
161
|
+
this.holds = { ...this.holds, [buyer]: Date.now() }
|
|
162
|
+
this.schedule({ at: new Date(Date.now() + HOLD_MILLISECONDS), key: buyer }).expire({ buyer })
|
|
163
|
+
return { held: true, available: this.available }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
expire({ buyer }: { buyer: string }): number {
|
|
167
|
+
if (!Object.hasOwn(this.holds, buyer)) return this.available
|
|
168
|
+
|
|
169
|
+
const remainingHolds = { ...this.holds }
|
|
170
|
+
delete remainingHolds[buyer]
|
|
171
|
+
this.holds = remainingHolds
|
|
172
|
+
this.available += 1
|
|
173
|
+
return this.available
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Obey these rules in actor code:
|
|
179
|
+
|
|
180
|
+
- Give each actor class a stable `static actorType`. The database stores it.
|
|
181
|
+
- Enumerable public fields are the durable state. They must be
|
|
182
|
+
JSON-compatible.
|
|
183
|
+
- Public methods are ordered operations. Each one takes no argument or one
|
|
184
|
+
object argument.
|
|
185
|
+
- Use `this.schedule({ at, key })` for delayed work. A new `schedule` with the
|
|
186
|
+
same key moves the alarm.
|
|
187
|
+
- Use `this.reject(code, message)` for a business rule failure that must not
|
|
188
|
+
retry.
|
|
189
|
+
- Do not write application tables directly from an operation. Use
|
|
190
|
+
`this.commitAction()` for a short write in the same database.
|
|
191
|
+
- Do not call an external API in an operation. Use `this.emit()` and an effect
|
|
192
|
+
handler.
|
|
193
|
+
- Write each operation so that it can run again. Delivery is at least once.
|
|
194
|
+
|
|
195
|
+
[State and lifecycle](state-and-lifecycle.md) and the
|
|
196
|
+
[public API](api.md) give the full rules.
|
|
197
|
+
|
|
198
|
+
## 7. Run the background roles
|
|
199
|
+
|
|
200
|
+
A direct call, such as `TicketSale.ref("event-42").hold({ buyer: "ada" })`,
|
|
201
|
+
runs in the caller. These features need a process that calls
|
|
202
|
+
`runtime.run(signal)`:
|
|
203
|
+
|
|
204
|
+
- Reminders from `schedule()`.
|
|
205
|
+
- Effects from `emit()` and their callbacks.
|
|
206
|
+
- Messages to other actors.
|
|
207
|
+
- Realtime broadcasts.
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
const controller = new AbortController()
|
|
211
|
+
process.once("SIGTERM", () => controller.abort())
|
|
212
|
+
await runtime.run(controller.signal)
|
|
213
|
+
await runtime.close()
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The packaged `solid-objects start` command does the same for a runtime that
|
|
217
|
+
`solid-objects.config.js` exports. When no process runs, committed work waits
|
|
218
|
+
in SQL and runs later. [Operations](operations.md) covers roles and shutdown.
|
|
219
|
+
|
|
220
|
+
## 8. Make external effects idempotent
|
|
221
|
+
|
|
222
|
+
Register an effect handler at startup. Use `context.id` as the provider
|
|
223
|
+
idempotency key:
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
runtime.registerEffect("charge_payment", async (argumentsValue, context) => {
|
|
227
|
+
return payments.charge({
|
|
228
|
+
idempotencyKey: context.id,
|
|
229
|
+
paymentId: argumentsValue.paymentId,
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Stage it from an operation:
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
this.emit("charge_payment", {
|
|
238
|
+
arguments: { paymentId: this.paymentId },
|
|
239
|
+
onSuccess: "charged",
|
|
240
|
+
})
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
The effect can run more than once after a crash. The `context.id` value is the
|
|
244
|
+
same each time. [Effect recovery](effect-recovery.md) explains how to retire
|
|
245
|
+
abandoned work.
|
|
246
|
+
|
|
247
|
+
## 9. Verify the implementation
|
|
248
|
+
|
|
249
|
+
Do these checks before you report that the work is complete:
|
|
250
|
+
|
|
251
|
+
1. Call `await runtime.doctor.run()`. The report must contain no failed check.
|
|
252
|
+
2. Send concurrent calls to one identity with `Promise.all`, and from two
|
|
253
|
+
processes if the application runs more than one. Assert the final state.
|
|
254
|
+
3. Test delayed work without sleeps. `runDueReminders({ now })` enqueues the
|
|
255
|
+
reminders that are due at the `Date` you pass. `drain()` then runs the
|
|
256
|
+
enqueued actor work:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
await runtime.testing.runDueReminders({ now: new Date(Date.now() + HOLD_MILLISECONDS) })
|
|
260
|
+
await runtime.testing.drain({ roles: ["actors"] })
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
4. Start a process that calls `runtime.run(signal)`, schedule a short
|
|
264
|
+
reminder, and stop the process. Start it again after the deadline and
|
|
265
|
+
confirm that the reminder ran.
|
|
266
|
+
5. Confirm that each effect handler deduplicates with `context.id`.
|
|
267
|
+
6. Confirm that production policies do not grant access to every caller.
|
|
268
|
+
|
|
269
|
+
The repository runs the same proofs. `pnpm run test:recovery` stops a worker
|
|
270
|
+
process and recovers its work. `pnpm run test:at-least-once` shows a repeated
|
|
271
|
+
effect and its deduplication.
|
|
272
|
+
|
|
273
|
+
## 10. Troubleshooting
|
|
274
|
+
|
|
275
|
+
| Symptom | Cause and fix |
|
|
276
|
+
| --------------------------------------- | ------------------------------------------------------------------------------------ |
|
|
277
|
+
| `Unauthorized` | A policy denied the call. Write the policy, and pass `authorizationContext` |
|
|
278
|
+
| A reminder or effect does not run | No process calls `runtime.run(signal)`. Start one |
|
|
279
|
+
| `SyncInsideTransaction` | The call ran inside an open transaction. Call the actor outside the transaction |
|
|
280
|
+
| `SyncTimeout` | The call did not finish in time. The message is still durable. Wait on its reference |
|
|
281
|
+
| `ApplicationWriteForbidden` | An operation wrote an application table. Use `commitAction()` or `emit()` |
|
|
282
|
+
| `Rejected` | The actor called `reject()`. This is a business result, not a retry |
|
|
283
|
+
| Wrong integer values or SQLite warnings | Node.js is older than 24.4, or older than 24.15 for the warning. Upgrade Node.js |
|
|
284
|
+
| `ERR_REQUIRE_ESM` or import errors | The package is ESM only. Use `import` and an ES module file |
|
|
285
|
+
|
|
286
|
+
## 11. Guarantees to state correctly
|
|
287
|
+
|
|
288
|
+
When you explain Solid Objects to a user, state these limits:
|
|
289
|
+
|
|
290
|
+
- Delivery is at least once, not exactly once.
|
|
291
|
+
- Calls for one identity are ordered. Different identities run concurrently.
|
|
292
|
+
- There are no transactions across actor identities.
|
|
293
|
+
- Fencing stops a stale activation from a commit, but its code can continue to
|
|
294
|
+
run.
|
|
295
|
+
- The package is pre-1.0. It has no measured scale and no known third-party
|
|
296
|
+
production use.
|
|
297
|
+
- The browser client is not the SQL runtime. The Cloudflare backend is
|
|
298
|
+
experimental.
|
|
299
|
+
|
|
300
|
+
The [correctness contract](correctness.md) is the source for each guarantee.
|
package/docs/api.md
CHANGED
|
@@ -105,7 +105,7 @@ envelope. The envelope carries its name in `invalidations`. A component registry
|
|
|
105
105
|
can then refresh a reauthorized endpoint, and the value stays private.
|
|
106
106
|
|
|
107
107
|
`MessageReference` does not retain an invocation's authorization context.
|
|
108
|
-
Supply `authorizationContext` to each `status()`, `result()`, and `wait()` call;
|
|
108
|
+
Supply `authorizationContext` to each `status()`, `result()`, `outcome()`, and `wait()` call;
|
|
109
109
|
the runtime reauthorizes the persisted operation every time. Durable results
|
|
110
110
|
are JSON, so an operation that returns `undefined` or is declared `void`
|
|
111
111
|
resolves as `null`.
|
|
@@ -130,6 +130,10 @@ function playerForSession<PlayerType extends { sessionId: string }>(options: {
|
|
|
130
130
|
|
|
131
131
|
### Reminders
|
|
132
132
|
|
|
133
|
+
Keyed reminder names combine the operation, a colon, and the key. JavaScript
|
|
134
|
+
limits the combined name to 255 UTF-16 code units; Ruby limits it to 191
|
|
135
|
+
characters. Use at most 191 ASCII characters for names shared across runtimes.
|
|
136
|
+
|
|
133
137
|
A reminder is one alarm per actor and name. If you schedule a name that is
|
|
134
138
|
already armed, the runtime **moves the existing alarm**. It does not add a
|
|
135
139
|
second one. A reminder is therefore safe to re-arm from a handler that can run
|
|
@@ -904,6 +908,11 @@ open SAH pool otherwise blocks the next candidate until the worker dies.
|
|
|
904
908
|
|
|
905
909
|
## `solid-objects/transmit`
|
|
906
910
|
|
|
911
|
+
Schema migration 14 persists an effect's staging position within its source message.
|
|
912
|
+
Run `runtime.install()` before upgraded workers start. New effects preserve staging
|
|
913
|
+
order even when two transmits originate in one turn. Legacy rows use position zero
|
|
914
|
+
and their existing ID tie-break; their original order cannot be recovered.
|
|
915
|
+
|
|
907
916
|
The transactional outbox bridge between a local runtime and a server
|
|
908
917
|
runtime. An actor stages a transmit intent with `this.transmit()`
|
|
909
918
|
in the same transaction as its state change. The effect worker drains the
|
|
@@ -923,10 +932,10 @@ outbox with at-least-once delivery, per-actor order, and retry backoff.
|
|
|
923
932
|
`deliver` while offline and the effect retries with backoff. Give a
|
|
924
933
|
browser runtime a generous `maxAttempts`; an effect that exhausts its
|
|
925
934
|
attempts during a long offline period lands in dead letters, and
|
|
926
|
-
`runtime.deadLetters.retry` re-queues it.
|
|
935
|
+
`runtime.deadLetters.effects.retry(id)` re-queues it.
|
|
927
936
|
- `receiveTransmitEnvelope(options)`: idempotent server ingest. `arguments`
|
|
928
937
|
is optional in the envelope and defaults to an empty object, matching the
|
|
929
|
-
staging side and the Ruby ingest. It enqueues an
|
|
938
|
+
staging side and the Ruby ingest. Explicit `null` and arrays are invalid. It enqueues an
|
|
930
939
|
internal message with `transmit:<effectId>` as the idempotency key, so a
|
|
931
940
|
replayed envelope applies once. The host must authenticate the sender
|
|
932
941
|
before this call; internal delivery skips `authorizeMessage`. The call
|
|
@@ -1057,3 +1066,16 @@ Behavior:
|
|
|
1057
1066
|
|
|
1058
1067
|
Mounting, authorization actions, CSRF behavior, pages, and extensions are in
|
|
1059
1068
|
[Operator dashboard](dashboard.md).
|
|
1069
|
+
|
|
1070
|
+
### Portable telemetry and diagnostics
|
|
1071
|
+
|
|
1072
|
+
`InstrumentationEvent` includes the versioned envelope and immutable `MetricSample`
|
|
1073
|
+
values. `EventObserver` is a provider-free event callback. An actor reference offers
|
|
1074
|
+
`observe({ onEvent, authorizationContext })`, `on(name, { onEvent, authorizationContext })`,
|
|
1075
|
+
and `diagnostics(options)`. Both observer methods resolve to an unsubscribe function.
|
|
1076
|
+
They reject a missing `onEvent` with `TypeError` before authorization. A runtime
|
|
1077
|
+
accepts at most 1,000 local observers; the next one rejects with `RangeError`.
|
|
1078
|
+
`close()` removes every local observer.
|
|
1079
|
+
`DiagnosticOptions` accepts `authorizationContext` and a `limit` from 1 to 100.
|
|
1080
|
+
`ActorDiagnostics` holds five bounded `DiagnosticSummary` values, with `sampled`,
|
|
1081
|
+
`truncated`, and `oldestAgeMilliseconds`. See [observability](observability.md).
|
package/docs/authorization.md
CHANGED
|
@@ -10,6 +10,12 @@ resource, optional resource ID, and the caller's authorization context. Retry
|
|
|
10
10
|
authorization happens before lookup so a denied caller cannot use record IDs as
|
|
11
11
|
an existence oracle.
|
|
12
12
|
|
|
13
|
+
Actor diagnostics and actor observers call `authorizeAdministration` with the
|
|
14
|
+
resource `actor_diagnostics` and the resource ID
|
|
15
|
+
`JSON.stringify([actorType, actorId])`. `diagnostics()` uses the action
|
|
16
|
+
`inspect`. `observe()` and `on()` use the action `observe`. The check runs before
|
|
17
|
+
any queue read or observer registration.
|
|
18
|
+
|
|
13
19
|
`runtime.realtime` is transport-neutral. The host application authenticates its
|
|
14
20
|
WebSocket or stream connection, passes that fresh server-side subject as the
|
|
15
21
|
session's `authorizationContext`, and forwards incoming protocol messages to
|