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
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Operations
|
|
2
|
+
|
|
3
|
+
Runtime roles use durable polling as the correctness fallback. The default
|
|
4
|
+
generation-based wake-up adapter interrupts waits for new actor messages,
|
|
5
|
+
effects, reminders, and broadcasts in the same Node process. Notification
|
|
6
|
+
errors are isolated and logged by role and error class without failing the
|
|
7
|
+
committed work. Graceful shutdown stops new claims and allows active turns to
|
|
8
|
+
finish within `shutdownTimeoutMilliseconds`, which defaults to 15 seconds. A
|
|
9
|
+
component still running or stopping at the deadline emits
|
|
10
|
+
`solid_objects.supervisor.component_shutdown_timeout`; the runtime then returns
|
|
11
|
+
without pretending JavaScript code was forcibly terminated. Operators should
|
|
12
|
+
monitor oldest ready work, claimed work, dead letters, effect failures,
|
|
13
|
+
reminder lag, lease loss, process heartbeats, and database contention.
|
|
14
|
+
|
|
15
|
+
`runtime.run()` replaces built-in roles and registered application components
|
|
16
|
+
that return or reject before shutdown. `supervisorRestartDelayMilliseconds`
|
|
17
|
+
defaults to 100 ms and doubles after consecutive run or construction failures,
|
|
18
|
+
capped by `supervisorMaximumRestartDelayMilliseconds` at 10 seconds. Replacement
|
|
19
|
+
and replacement-failure events contain the role class, failure count, and error
|
|
20
|
+
class only. Repeated failure messages are excluded from instrumentation.
|
|
21
|
+
|
|
22
|
+
`maxMessagesPerActivationPass` defaults to 50 and
|
|
23
|
+
`maxActivationDurationMilliseconds` defaults to 5 seconds. A pass yields when
|
|
24
|
+
either budget is exhausted. Lower values improve fairness when a few actor
|
|
25
|
+
identities stay continuously busy; higher values reduce claim overhead for
|
|
26
|
+
isolated backlogs. `solid_objects.activation.yielded` reports the actor
|
|
27
|
+
identity, turns processed, and remaining due membership count.
|
|
28
|
+
|
|
29
|
+
`claimScanLimit` defaults to 100. Global claims inspect a bounded ordered set of
|
|
30
|
+
actor identities and continue after a lost lease race, preserving worker
|
|
31
|
+
parallelism without an unbounded scan.
|
|
32
|
+
|
|
33
|
+
Workers retain a hydrated actor and its fenced lease for
|
|
34
|
+
`idleDeactivationTimeoutMilliseconds`, which defaults to 30 seconds. Idle
|
|
35
|
+
leases renew at `leaseRenewalIntervalMilliseconds`; the worker polling cadence
|
|
36
|
+
is capped at that interval while any activation may be cached. Fairness yield,
|
|
37
|
+
lease loss, timeout, and shutdown release the lease. `runUntilIdle()` and the
|
|
38
|
+
runtime's synchronous caller release before returning because they are no
|
|
39
|
+
longer polling.
|
|
40
|
+
|
|
41
|
+
Actors can override protected `onActivate()` and `onDeactivate()` methods for
|
|
42
|
+
nondurable, process-local resources. Either hook may be asynchronous. Hook code
|
|
43
|
+
runs under the application-write guard, and `onDeactivate()` is best effort:
|
|
44
|
+
it may not run after a crash, cannot establish a correctness guarantee, and a
|
|
45
|
+
failure is logged without preventing lease release.
|
|
46
|
+
|
|
47
|
+
`runtime.processes.all()` returns administration-authorized immutable process
|
|
48
|
+
metadata with hostname, host process ID, Node and Solid Objects versions, and a
|
|
49
|
+
current `stale` flag. Graceful shutdown first persists `draining` with a
|
|
50
|
+
`shutdownRequestedAt` timestamp, then deactivates owned actors and atomically
|
|
51
|
+
releases every role claim before persisting `stopped`. `cleanup()` reauthorizes
|
|
52
|
+
separately and performs the same release for stale running or draining
|
|
53
|
+
processes. The cleanup count is instrumented; application payloads are not.
|
|
54
|
+
|
|
55
|
+
Committed calls and `message.wait()` apply `timeoutMilliseconds` to the entire
|
|
56
|
+
durable wait, beginning before enqueue or message lookup. Adapter deadlines
|
|
57
|
+
bound serialized SQLite access and lock waits, PostgreSQL pool acquisition,
|
|
58
|
+
statements, and locks, and MySQL pool acquisition, queries, and transaction
|
|
59
|
+
lock waits. A `SyncEnqueueTimeout` means the enqueue transaction did not commit
|
|
60
|
+
and there is no durable message to recover. Once enqueue commits, a
|
|
61
|
+
`SyncTimeout` includes `waitingOn`, activation and process metadata, an earlier
|
|
62
|
+
blocking message when present, and the original `messageReference`. Database
|
|
63
|
+
contention that prevents inspection is reported as `databaseContention`. The
|
|
64
|
+
durable message continues after an ordinary wait timeout, so callers can store
|
|
65
|
+
that reference or await `error.messageReference.wait()` later. Timeout
|
|
66
|
+
instrumentation excludes actor arguments, results, and error messages.
|
|
67
|
+
|
|
68
|
+
JavaScript promises already running in the process are cooperative and are not
|
|
69
|
+
forcefully terminated. An actor operation that has started may therefore
|
|
70
|
+
finish after the caller's timeout; durable leases and fenced commits remain the
|
|
71
|
+
correctness boundary.
|
|
72
|
+
|
|
73
|
+
Committed calls and `message.wait()` fail with `SyncInsideTransaction` when
|
|
74
|
+
invoked inside `database.transaction(...)` on the configured Solid Objects
|
|
75
|
+
adapter. The check happens before enqueue for direct calls. This prevents the
|
|
76
|
+
caller from waiting on a pool connection or SQLite access slot that its own
|
|
77
|
+
ambient transaction still holds.
|
|
78
|
+
|
|
79
|
+
Authorized operators can inspect terminal actor failures with
|
|
80
|
+
`runtime.deadLetters.all()` and retry one with `runtime.deadLetters.retry()`.
|
|
81
|
+
Retry is idempotent per dead letter: the record retains the replacement message
|
|
82
|
+
ID and later calls return a reference to that same message.
|
|
83
|
+
|
|
84
|
+
Self-scheduling actors need a low-frequency reconciler because application
|
|
85
|
+
alarms can still be lost. `runtime.reconciliation` provides administration-
|
|
86
|
+
authorized, read-only views for active instances, quiet instances without
|
|
87
|
+
ready work, claimed work, or scheduled reminders, migrated state batches, and
|
|
88
|
+
orphaned actor IDs. Collection reads use a maximum page size of 1,000 and a
|
|
89
|
+
stable cursor.
|
|
90
|
+
|
|
91
|
+
The host application supplies its current owner IDs to `orphaned()` because
|
|
92
|
+
Node applications do not share an Active Record relation abstraction. Send
|
|
93
|
+
every repair through the actor's typed `send` dispatcher, optionally with a
|
|
94
|
+
future `availableAt` to spread large repairs. Never update persisted actor
|
|
95
|
+
state from reconciliation code.
|
|
96
|
+
|
|
97
|
+
The runtime automatically prunes expired message and stopped-process history
|
|
98
|
+
once at startup and every `retentionIntervalMilliseconds`, which defaults to
|
|
99
|
+
one hour. Stale process ownership is recovered independently every
|
|
100
|
+
`deadProcessCleanupIntervalMilliseconds`, which defaults to one minute. Set an
|
|
101
|
+
interval to zero to disable its scheduler. Failed passes emit metadata-only
|
|
102
|
+
events and retry with bounded exponential backoff without stopping other
|
|
103
|
+
runtime roles.
|
|
104
|
+
|
|
105
|
+
Operators can also use `runtime.retention.preview()` and
|
|
106
|
+
`runtime.retention.prune()`. Both calls require administration authorization;
|
|
107
|
+
use preview first and alert on an unexpected count before executing deletion.
|
|
108
|
+
Message history defaults to 30 days with optional per-actor overrides. Stopped
|
|
109
|
+
process history defaults to 7 days. Instance expiration is disabled unless an
|
|
110
|
+
actor type appears in `instanceRetentionByActorType`, and remains an explicit
|
|
111
|
+
operator action because it deletes the entire actor incarnation.
|
|
112
|
+
|
|
113
|
+
Pruning selects and rechecks at most `pruneBatchSize` rows per transaction. It
|
|
114
|
+
preserves ready and claimed messages, dead-letter originals and replacements,
|
|
115
|
+
unfinished effects and broadcasts, scheduled reminders, leased or paused
|
|
116
|
+
instances, and processes that still own a claim or activation. Instance
|
|
117
|
+
expiration removes the entire actor incarnation and all of its retained
|
|
118
|
+
history, so use it only for actor types whose state is safely disposable.
|
|
119
|
+
|
|
120
|
+
`runtime.doctor.run()` returns structured configuration, schema, database,
|
|
121
|
+
authorization-posture, live-role, and durable-round-trip checks. A warning does
|
|
122
|
+
not make the report unhealthy; a failed check does. The round trip targets only
|
|
123
|
+
its probe actor, uses the ordinary lease and fenced commit path, and removes its
|
|
124
|
+
instance and process records afterward. Use `{ roundTrip: "skip" }` when the
|
|
125
|
+
diagnostic must not write.
|
|
126
|
+
|
|
127
|
+
The doctor reports whether each authorization callback was explicitly
|
|
128
|
+
configured and probes all configured policies with a neutral context. It warns
|
|
129
|
+
when every policy denies the probe, a policy needs unavailable application
|
|
130
|
+
context, or destruction, subscription, or administration allows it. The probe
|
|
131
|
+
does not replace application-specific authorization tests. Do not expose the
|
|
132
|
+
report through an HTTP or CLI surface without applying the host application's
|
|
133
|
+
own administration policy.
|
|
134
|
+
|
|
135
|
+
The packaged `solid-objects` executable loads a configured runtime exported as
|
|
136
|
+
default or `runtime` from `solid-objects.config.js`, or from `--config PATH`.
|
|
137
|
+
It provides `start`, `doctor`, `status`, `cleanup`, `dead-letters`,
|
|
138
|
+
`retry-dead-letter`, `reminders`, `resume-reminder`, and `prune`. Output is JSON,
|
|
139
|
+
and destructive retention requires `prune TARGET --execute`; the unqualified
|
|
140
|
+
command is a preview. Administrative commands consistently pass
|
|
141
|
+
`{ source: "cli" }` through the ordinary policy boundary.
|
|
142
|
+
|
|
143
|
+
Tests can use `runtime.testing.drain()` to process reminders, actor turns,
|
|
144
|
+
effects, actor callbacks, and broadcasts in deterministic passes. The helper
|
|
145
|
+
stops every temporary runner even when a handler raises. `reset()` must only run
|
|
146
|
+
while the supervised runtime is stopped; it explicitly clears child tables,
|
|
147
|
+
instances, and process records and replaces the cached synchronous caller.
|
|
148
|
+
|
|
149
|
+
The optional `instrumentation` callback receives immutable events with the
|
|
150
|
+
`solid_objects.` prefix. Core events cover runtime lifecycle, message
|
|
151
|
+
enqueue/start/completion/rejection/failure, activation loss, commit actions,
|
|
152
|
+
effects, reminders, broadcasts, dead letters, worker setup failures, actor
|
|
153
|
+
destruction, and retention pruning. Failed realtime delivery emits
|
|
154
|
+
`subscription.delivery_failed` and
|
|
155
|
+
removes that session's actor registration without retrying application socket
|
|
156
|
+
code. Event attributes are restricted to identities, operation or handler
|
|
157
|
+
names, delivery mode, sequence, attempt, outcome, counts, durations, and error
|
|
158
|
+
class names. The runtime catches sink exceptions and reports only the failed
|
|
159
|
+
event name and sink error class through the configured logger.
|
|
160
|
+
|
|
161
|
+
`runtime.reminders.all()` provides administration-authorized, cursor-paginated
|
|
162
|
+
alarm metadata filtered by actor type or status. Arguments and error messages
|
|
163
|
+
remain private. `resume()` moves a paused reminder back to `scheduled`, clears
|
|
164
|
+
its claim and stored error, and optionally assigns a new run time. Repeating
|
|
165
|
+
resume on an already scheduled reminder is a no-op; completed reminders cannot
|
|
166
|
+
be resumed.
|
|
167
|
+
|
|
168
|
+
Scheduling an existing actor operation for a different time emits
|
|
169
|
+
`solid_objects.reminder.replaced` after the fenced actor commit. Scheduling it
|
|
170
|
+
for the same time emits nothing.
|
package/docs/parity.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Ruby parity ledger
|
|
2
|
+
|
|
3
|
+
This ledger tracks spiritual feature parity with the Ruby `solid_objects` gem.
|
|
4
|
+
Parity means preserving a capability and its correctness or security boundary,
|
|
5
|
+
not copying a Rails API into Node.
|
|
6
|
+
|
|
7
|
+
Reference: Ruby `solid_objects` 0.12.0 at commit `a01b6f5`.
|
|
8
|
+
|
|
9
|
+
The Node `0.12.0` implementation has spiritual parity with that reference. Its
|
|
10
|
+
relational runtime, correctness boundaries, administration, diagnostics,
|
|
11
|
+
realtime projections, browser behavior, and supported adapters have native
|
|
12
|
+
equivalents. Rails engine and rendering surfaces are intentionally replaced by
|
|
13
|
+
transport- and framework-neutral JavaScript APIs. The partial guard row and the
|
|
14
|
+
shared planned result-lookup row below are explicit scope boundaries, not
|
|
15
|
+
missing Ruby capabilities.
|
|
16
|
+
|
|
17
|
+
## Status vocabulary
|
|
18
|
+
|
|
19
|
+
- **Native**: the TypeScript runtime provides the capability in a Node-native
|
|
20
|
+
shape.
|
|
21
|
+
- **Partial**: the core exists, but an important Ruby guarantee or operational
|
|
22
|
+
surface is missing.
|
|
23
|
+
- **Planned**: no defensible equivalent exists yet.
|
|
24
|
+
- **Not applicable**: the Ruby feature belongs specifically to Rails, Active
|
|
25
|
+
Record, Action Cable, Turbo, or Ruby language constraints. The equivalent
|
|
26
|
+
Node capability is tracked separately when one is useful.
|
|
27
|
+
|
|
28
|
+
## Runtime and correctness
|
|
29
|
+
|
|
30
|
+
| Capability | Status | TypeScript shape or remaining work |
|
|
31
|
+
| ------------------------------------------------------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
32
|
+
| Actor registry, durable identity, JSON state, and adjacent state migrations | Native | Ordinary classes, static actor types, inferred state, explicit migrations, and isolated runtime context across every actor-instance callback. |
|
|
33
|
+
| Fluent committed calls and background delivery | Native | `await reference.operation()` and `reference.send.operation()`. |
|
|
34
|
+
| Ordered mailbox, sequence allocation, idempotency, retries, dead letters, leases, renewal, and fenced commits | Native | Relational ready/claimed membership tables, distinct generated request IDs and caller idempotency keys, durable history, and adapter-appropriate sequence locking. |
|
|
35
|
+
| Domain rejection and strict poison ordering | Native | Rejections roll back without retry; retryable failures block later operations until completion or dead-lettering. |
|
|
36
|
+
| Bounded activation passes and hot-actor fairness | Native | Configurable turn-count and elapsed-time budgets bound each pass, then move only that actor's already-due memberships behind actors already waiting. |
|
|
37
|
+
| Bounded claim candidate scan | Native | A configurable ordered scan continues to another ready actor when a worker loses the first candidate's lease race. |
|
|
38
|
+
| Idle activation cache | Native | Long-running workers retain hydrated actors under renewable fenced leases, restore public state after failed turns, and release on timeout, fairness yield, lease loss, or shutdown. |
|
|
39
|
+
| Transactional effects and outcome operations | Native | At-least-once handlers receive immutable stable effect, attempt, source-message, and actor identity; success and failure return through actor operations. |
|
|
40
|
+
| Actor-to-actor delivery | Native | `sendTo(reference).operation()` stages delivery in the source actor commit. |
|
|
41
|
+
| One-shot and recurring reminders | Native | Scheduling, replacement events, catch-up policy, stale-claim recovery, pausing, authorized inspection, and idempotent resume are implemented. |
|
|
42
|
+
| Same-database commit actions | Native | Registered actions receive source-message identity, mailbox sequence, activation generation, and the fenced transaction connection. |
|
|
43
|
+
| Ambient transaction rejection | Native | Committed calls and message waits fail before blocking when the current async context already owns a transaction on the Solid Objects adapter. |
|
|
44
|
+
| Direct application-write isolation during actor code | Partial | `guardApplicationDatabase()` fails closed for operations, projections, migrations, and commit actions; only the supplied fenced commit-action connection may write. Unwrapped clients cannot be intercepted. |
|
|
45
|
+
| Committed snapshots | Native | `snapshot()` returns authorized persisted fields and inferred getters from one read-only committed state image; realtime replay reads explicit observables without mailbox history. |
|
|
46
|
+
| Actor destruction and incarnation fencing | Native | Authorized cascading deletion creates a fresh instance ID on recreation; an authorized waiter receives `ActorDestroyed` when that incarnation disappears. |
|
|
47
|
+
| Result recovery and sync timeout diagnostics | Native | Status, result, and wait reauthorize the stored operation; terminal failure raises structured `MessageFailed`; whole-call adapter deadlines distinguish enqueue, wait, database, activation, and mailbox blockers. |
|
|
48
|
+
| Result lookup by request ID | Planned | This is also an open Ruby roadmap item and will be implemented in both runtimes when its authorization shape is settled. |
|
|
49
|
+
|
|
50
|
+
## Operations
|
|
51
|
+
|
|
52
|
+
| Capability | Status | TypeScript shape or remaining work |
|
|
53
|
+
| ----------------------------------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
54
|
+
| Process registration, heartbeats, stale claim recovery, and graceful shutdown | Native | Runtime roles persist host, PID, runtime versions, draining and stopped transitions, cooperative cancellation, and a bounded shutdown deadline; cleanup recovers stale claims. |
|
|
55
|
+
| Failed-role replacement | Native | Built-in and registered roles are rebuilt through their factories with capped backoff; shutdown is the terminal replacement boundary. |
|
|
56
|
+
| Additional supervised components | Native | `registerComponent()` builds, validates, runs, and stops application components with the runtime. |
|
|
57
|
+
| Dead-letter inspection and retry | Native | `runtime.deadLetters` provides deny-by-default immutable inspection and idempotent durable retry linkage. |
|
|
58
|
+
| Reconciliation reads | Native | Authorized cursor pages cover active, quiet, and orphaned instances; bounded state batches are migrated and deeply frozen. |
|
|
59
|
+
| Message, process, and opt-in instance retention | Native | Supervised scheduling bounds message and process growth; authorized manual APIs add preview and keep destructive instance expiration explicit. |
|
|
60
|
+
| Doctor and schema verification | Native | Structured checks cover configuration, schema/version shape, adapter server versions, neutral-context policy probes, live roles, and a targeted round trip. |
|
|
61
|
+
| CLI | Native | The packaged executable loads an application runtime and exposes start, diagnostics, processes, dead letters, reminders, and explicit retention pruning as JSON. |
|
|
62
|
+
| Structured instrumentation | Native | An isolated transport-neutral sink emits immutable lifecycle metadata and structurally excludes application payloads. |
|
|
63
|
+
| Public test helper | Native | `runtime.testing` provides role-selective deterministic draining and dependency-ordered reset without relying on cascades. |
|
|
64
|
+
|
|
65
|
+
## Databases and wake-up
|
|
66
|
+
|
|
67
|
+
| Capability | Status | TypeScript shape or remaining work |
|
|
68
|
+
| ------------------------ | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
69
|
+
| SQLite | Native | Uses built-in `node:sqlite`, serialized process-local access, bounded transient writer retries, foreign keys, strict tables, database time, and deadline-bounded access and lock waits. |
|
|
70
|
+
| PostgreSQL | Native | Optional `pg` 8.23 peer, bounded pooling, 64-bit schema, row-locked sequences, server checks, and deadline-bounded pool, statement, and lock waits. |
|
|
71
|
+
| MySQL | Native | Optional `mysql2` 3.23 peer, bounded pooling, InnoDB schema, row-locked sequences, scoped deadlock retry, and deadline-bounded pool, query, and lock waits. |
|
|
72
|
+
| Durable polling fallback | Native | Every role progresses without a notification service. |
|
|
73
|
+
| In-process wake-up | Native | A generation-based default adapter prevents claim-to-wait signal loss; commits wake role-specific waiters and polling remains the fallback. |
|
|
74
|
+
| PostgreSQL wake-up | Native | `database.wakeUp()` uses one dedicated event-driven client, role-specific `LISTEN/NOTIFY`, generation fencing, reconnectable listeners, and durable polling fallback. |
|
|
75
|
+
| Redis wake-up | Native | An optional `redis` peer provides role-specific Pub/Sub over separate lazy publisher/subscriber connections, with bounded failures and durable polling fallback. |
|
|
76
|
+
|
|
77
|
+
## Realtime and browser behavior
|
|
78
|
+
|
|
79
|
+
| Capability | Status | TypeScript shape or remaining work |
|
|
80
|
+
| ------------------------------------------------------------ | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
81
|
+
| Explicit observable projection and durable invalidations | Native | `observables()` is opt-in; only changed projected values enter the broadcast outbox. |
|
|
82
|
+
| Action Cable channels and signed stream names | Not applicable | `runtime.realtime` provides authenticated transport-neutral sessions; the host owns its HTTP/WebSocket server and authentication. |
|
|
83
|
+
| Authorized subscriptions | Native | Each request is denied by default and authorized before actor lookup; sessions replay committed observables and fence ordered durable revisions. Multi-process hosts explicitly bridge their shared transport. |
|
|
84
|
+
| Turbo scalar replacement | Not applicable | The browser client exposes invalidations to application rendering code. Framework adapters can be separate packages. |
|
|
85
|
+
| Keyed component refresh, morph/replace, and batch coalescing | Native | A typed framework-neutral registry selects explicit dependencies, coalesces batch requests, aborts superseded work, fences each target, and delegates synchronous application strategy to the host. |
|
|
86
|
+
| Personalized payload broadcasts | Native | Static typed projections run against committed state under each fresh subscriber context, reauthorize as queries, isolate failures, and carry independent revision fences. |
|
|
87
|
+
| Real-browser compatibility suite | Native | Playwright exercises subscription replay over native WebSocket, incarnation/revision fences, payload delivery, component batching, and cancellation in Chromium. |
|
|
88
|
+
|
|
89
|
+
## Rails-specific surfaces
|
|
90
|
+
|
|
91
|
+
The Rails engine, generators, Active Record models/controllers, ERB helpers,
|
|
92
|
+
Turbo renderer, and Action Cable channel are not copied into this package.
|
|
93
|
+
Their underlying runtime, administration, authorization, and realtime
|
|
94
|
+
capabilities are represented above by Node-native APIs and protocols.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Releasing
|
|
2
|
+
|
|
3
|
+
Publishing is driven by version tags and npm Trusted Publishing. GitHub Actions
|
|
4
|
+
uses a short-lived OpenID Connect identity, so the repository does not need an
|
|
5
|
+
npm token.
|
|
6
|
+
|
|
7
|
+
## npm trusted publisher
|
|
8
|
+
|
|
9
|
+
The `solid-objects` package must authorize this GitHub Actions identity:
|
|
10
|
+
|
|
11
|
+
- Organization or user: `cardmagic`
|
|
12
|
+
- Repository: `solid-objects-js`
|
|
13
|
+
- Workflow filename: `ci.yml`
|
|
14
|
+
- Environment: none
|
|
15
|
+
- Allowed action: `npm publish`
|
|
16
|
+
|
|
17
|
+
The relationship can be created while authenticated as a package owner:
|
|
18
|
+
|
|
19
|
+
```shell
|
|
20
|
+
npm trust github solid-objects \
|
|
21
|
+
--repo cardmagic/solid-objects-js \
|
|
22
|
+
--file ci.yml \
|
|
23
|
+
--allow-publish \
|
|
24
|
+
--yes
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Release procedure
|
|
28
|
+
|
|
29
|
+
1. Update the version in `package.json` and `pnpm-lock.yaml` and move the
|
|
30
|
+
release notes out of the Unreleased section in `CHANGELOG.md`.
|
|
31
|
+
2. Run `pnpm run format:check`, `pnpm run check`, `pnpm run test:coverage`,
|
|
32
|
+
`pnpm run build`, `pnpm run pack:check`, and
|
|
33
|
+
`pnpm audit --audit-level=high`.
|
|
34
|
+
3. Commit and push `main`.
|
|
35
|
+
4. Create and push an annotated tag matching the package version:
|
|
36
|
+
|
|
37
|
+
```shell
|
|
38
|
+
git tag -a v0.13.0 -m "Version 0.13.0"
|
|
39
|
+
git push origin v0.13.0
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The tag runs the complete CI matrix. The publish job starts only after every
|
|
43
|
+
quality, database, Redis, and browser job succeeds. It rejects tags that do not
|
|
44
|
+
match `package.json`, safely skips versions already present in npm, and
|
|
45
|
+
publishes new versions with npm provenance.
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# State and lifecycle
|
|
2
|
+
|
|
3
|
+
An actor class is both the persisted state definition and the typed operation
|
|
4
|
+
surface. Registration constructs the class to discover its enumerable public
|
|
5
|
+
fields, then walks its prototype chain to discover methods and getters.
|
|
6
|
+
|
|
7
|
+
## Persisted state and operations
|
|
8
|
+
|
|
9
|
+
- Enumerable public instance fields are persisted JSON state.
|
|
10
|
+
- Public methods are ordered operations and accept zero arguments or one
|
|
11
|
+
object argument.
|
|
12
|
+
- Public fields and getters are ordered, read-only queries on a reference.
|
|
13
|
+
- `#private` fields are process-local and survive only while an activation is
|
|
14
|
+
cached.
|
|
15
|
+
- Actor arguments, state, results, observables, and payloads must be
|
|
16
|
+
JSON-compatible and fit their configured byte limits.
|
|
17
|
+
- Operation, field, and getter names must not collide with the reference API.
|
|
18
|
+
|
|
19
|
+
The constructor must establish every persisted field and must not depend on
|
|
20
|
+
external state. Solid Objects invokes it while validating the class, creating
|
|
21
|
+
defaults, hydrating state, and projecting a snapshot.
|
|
22
|
+
|
|
23
|
+
## State migrations
|
|
24
|
+
|
|
25
|
+
Every actor starts at state version 1. Increase `stateVersion` when existing
|
|
26
|
+
rows need a structural transformation and retain an adjacent migration for
|
|
27
|
+
every version:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { Actor, type JsonObject } from "solid-objects"
|
|
31
|
+
|
|
32
|
+
class Document extends Actor {
|
|
33
|
+
static override readonly actorType = "Document"
|
|
34
|
+
static override readonly stateVersion = 3
|
|
35
|
+
static override readonly migrations = [
|
|
36
|
+
{
|
|
37
|
+
from: 1,
|
|
38
|
+
to: 2,
|
|
39
|
+
migrate: (state: JsonObject): JsonObject => ({
|
|
40
|
+
title: state.name ?? "Untitled",
|
|
41
|
+
}),
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
from: 2,
|
|
45
|
+
to: 3,
|
|
46
|
+
migrate: (state: JsonObject): JsonObject => ({
|
|
47
|
+
...state,
|
|
48
|
+
archived: false,
|
|
49
|
+
}),
|
|
50
|
+
},
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
title = "Untitled"
|
|
54
|
+
archived = false
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Migrations run synchronously in ascending order when an actor is hydrated for
|
|
59
|
+
an operation, snapshot, reconciliation state read, observable replay, or
|
|
60
|
+
personalized payload. Each receives a detached JSON object and must return a
|
|
61
|
+
JSON object. After the chain completes, any new persisted field missing from
|
|
62
|
+
the result receives its constructor default. Migrated state is persisted only
|
|
63
|
+
by the next successful fenced operation commit; read-only projections do not
|
|
64
|
+
rewrite the row.
|
|
65
|
+
|
|
66
|
+
A migration must be deterministic and side-effect free. It cannot perform
|
|
67
|
+
asynchronous work or write through a database wrapped by
|
|
68
|
+
`guardApplicationDatabase()`. Missing, duplicate, or non-adjacent migrations,
|
|
69
|
+
a failing migration, or state newer than the running code raises
|
|
70
|
+
`StateMigrationError`.
|
|
71
|
+
|
|
72
|
+
For a destructive shape change, use expand/contract deployment: first deploy
|
|
73
|
+
readers that understand both shapes, then deploy the migration, wait for
|
|
74
|
+
operational evidence that actors have advanced, and only then remove old-shape
|
|
75
|
+
support. If old code cannot understand the new shape, drain it before new code
|
|
76
|
+
can persist the migration. Keep old migration steps so actors idle for several
|
|
77
|
+
releases can still advance one version at a time.
|
|
78
|
+
|
|
79
|
+
Actor state migration is separate from `runtime.install()`. The latter applies
|
|
80
|
+
the package's relational schema migrations; it does not eagerly rewrite actor
|
|
81
|
+
state.
|
|
82
|
+
|
|
83
|
+
## Activation lifecycle
|
|
84
|
+
|
|
85
|
+
Override `onActivate()` and `onDeactivate()` for process-local resources:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
class Device extends Actor {
|
|
89
|
+
static override readonly actorType = "Device"
|
|
90
|
+
|
|
91
|
+
#connection: DeviceConnection | undefined
|
|
92
|
+
|
|
93
|
+
protected override async onActivate(): Promise<void> {
|
|
94
|
+
this.#connection = await connectToDevice(this.actorId)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
protected override async onDeactivate(): Promise<void> {
|
|
98
|
+
await this.#connection?.close()
|
|
99
|
+
this.#connection = undefined
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Activation happens after state migration and before an operation attempt
|
|
105
|
+
starts. A setup failure restores the claimed message without consuming an
|
|
106
|
+
attempt. A long-running worker may reuse the hydrated actor and its private
|
|
107
|
+
fields until `idleDeactivationTimeoutMilliseconds`, fairness yield, lease loss,
|
|
108
|
+
or shutdown.
|
|
109
|
+
|
|
110
|
+
Lifecycle hooks have no `currentMessage` and run under the application-write
|
|
111
|
+
guard. Intents staged by `onActivate()` join the first turn's commit; use that
|
|
112
|
+
only when reactivation should repeat the intent safely. `onDeactivate()` is
|
|
113
|
+
best effort, its staged intents are discarded, and it may not run after a
|
|
114
|
+
crash, so it cannot establish correctness.
|
|
115
|
+
|
|
116
|
+
## Domain rejection and failure
|
|
117
|
+
|
|
118
|
+
Call `this.reject(code, { message, details })` when an operation is valid but
|
|
119
|
+
the domain refuses it. Codes contain lowercase letters, digits, and
|
|
120
|
+
underscores. The turn rolls back, staged work is discarded, the message becomes
|
|
121
|
+
`rejected`, and later mailbox work remains eligible. The caller receives
|
|
122
|
+
`Rejected` with the code, frozen JSON details, and durable message ID.
|
|
123
|
+
|
|
124
|
+
Throwing another error rolls the turn back and schedules a retry according to
|
|
125
|
+
`maxAttempts` and `retryDelayMilliseconds`. Throw a custom subclass of
|
|
126
|
+
`NonRetryableError` only when retry cannot help; the message goes directly to a
|
|
127
|
+
terminal dead letter. See [Errors and recovery](errors-and-recovery.md).
|
|
128
|
+
|
|
129
|
+
## Queries and snapshots
|
|
130
|
+
|
|
131
|
+
Reading a public field or getter through an actor reference is an ordered
|
|
132
|
+
mailbox query:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
const count = await Counter.ref("primary").count
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Use `snapshot()` when the read need not wait behind mailbox work:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
const snapshot = await Counter.ref("primary").snapshot({
|
|
142
|
+
authorizationContext: currentUser,
|
|
143
|
+
})
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Both paths call `authorizeQuery`. A snapshot hydrates one committed state
|
|
147
|
+
image, returns all fields and getters as a deeply frozen value, and never adds
|
|
148
|
+
a message to history. A getter that mutates state or stages work raises
|
|
149
|
+
`QueryMutatedState` for the whole snapshot.
|
|
150
|
+
|
|
151
|
+
## Destruction and reincarnation
|
|
152
|
+
|
|
153
|
+
`reference.destroy()` calls the separate `authorizeDestroy` policy, deletes
|
|
154
|
+
the current actor incarnation and every owned row, and returns `true` when an
|
|
155
|
+
incarnation existed. Repeating it returns `false`.
|
|
156
|
+
|
|
157
|
+
An actor cannot destroy another actor from inside a turn. A later message to
|
|
158
|
+
the same actor type and ID creates a new instance ID and revision sequence.
|
|
159
|
+
Old activation leases cannot commit to the new incarnation, and an authorized
|
|
160
|
+
caller waiting on the deleted incarnation receives `ActorDestroyed`. An
|
|
161
|
+
external effect or broadcast already executing cannot be recalled, but its
|
|
162
|
+
stale completion cannot recreate or enqueue a callback to the deleted source.
|
package/package.json
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "solid-objects",
|
|
3
|
+
"version": "0.12.0",
|
|
4
|
+
"description": "Durable, addressable objects for Node.js",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Lucas Carlson",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/cardmagic/solid-objects-js.git"
|
|
11
|
+
},
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=24.15.0"
|
|
14
|
+
},
|
|
15
|
+
"packageManager": "pnpm@11.11.0",
|
|
16
|
+
"bin": {
|
|
17
|
+
"solid-objects": "dist/executable.js"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"docs",
|
|
22
|
+
"README.md",
|
|
23
|
+
"CHANGELOG.md",
|
|
24
|
+
"MIT-LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./database/sqlite": {
|
|
32
|
+
"types": "./dist/database/sqlite.d.ts",
|
|
33
|
+
"import": "./dist/database/sqlite.js"
|
|
34
|
+
},
|
|
35
|
+
"./database/postgresql": {
|
|
36
|
+
"types": "./dist/database/postgresql.d.ts",
|
|
37
|
+
"import": "./dist/database/postgresql.js"
|
|
38
|
+
},
|
|
39
|
+
"./database/mysql": {
|
|
40
|
+
"types": "./dist/database/mysql.d.ts",
|
|
41
|
+
"import": "./dist/database/mysql.js"
|
|
42
|
+
},
|
|
43
|
+
"./wake-up/redis": {
|
|
44
|
+
"types": "./dist/wake-up/redis.d.ts",
|
|
45
|
+
"import": "./dist/wake-up/redis.js"
|
|
46
|
+
},
|
|
47
|
+
"./browser": {
|
|
48
|
+
"types": "./dist/browser/index.d.ts",
|
|
49
|
+
"import": "./dist/browser/index.js"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "pnpm run clean && tsc -p tsconfig.build.json && node scripts/prepare-executable.mjs",
|
|
54
|
+
"check": "pnpm run check:parameters && pnpm run check:documentation && tsc -p tsconfig.json --noEmit",
|
|
55
|
+
"check:documentation": "node scripts/check-documentation.mjs",
|
|
56
|
+
"check:parameters": "node scripts/check-parameter-style.mjs",
|
|
57
|
+
"clean": "node scripts/clean.mjs",
|
|
58
|
+
"format": "prettier --write .",
|
|
59
|
+
"format:check": "prettier --check .",
|
|
60
|
+
"test": "vitest run",
|
|
61
|
+
"test:browser": "pnpm run build && playwright test",
|
|
62
|
+
"test:coverage": "vitest run --coverage",
|
|
63
|
+
"test:postgresql": "vitest run test/postgresql.test.ts",
|
|
64
|
+
"test:mysql": "vitest run test/mysql.test.ts",
|
|
65
|
+
"test:redis": "vitest run test/redis-wake-up.test.ts",
|
|
66
|
+
"test:watch": "vitest",
|
|
67
|
+
"pack:check": "pnpm pack --dry-run && node scripts/check-package.mjs",
|
|
68
|
+
"prepack": "pnpm run build"
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"@playwright/test": "^1.62.1",
|
|
72
|
+
"@types/node": "^24.0.0",
|
|
73
|
+
"@types/pg": "^8.21.0",
|
|
74
|
+
"@types/ws": "^8.18.1",
|
|
75
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
76
|
+
"mysql2": "^3.23.3",
|
|
77
|
+
"pg": "^8.23.0",
|
|
78
|
+
"prettier": "^3.9.6",
|
|
79
|
+
"redis": "^6.2.1",
|
|
80
|
+
"typescript": "^5.9.0",
|
|
81
|
+
"vitest": "^4.1.10",
|
|
82
|
+
"ws": "^8.21.3"
|
|
83
|
+
},
|
|
84
|
+
"peerDependencies": {
|
|
85
|
+
"mysql2": "^3.23.3",
|
|
86
|
+
"pg": "^8.23.0",
|
|
87
|
+
"redis": "^6.2.1"
|
|
88
|
+
},
|
|
89
|
+
"peerDependenciesMeta": {
|
|
90
|
+
"mysql2": {
|
|
91
|
+
"optional": true
|
|
92
|
+
},
|
|
93
|
+
"pg": {
|
|
94
|
+
"optional": true
|
|
95
|
+
},
|
|
96
|
+
"redis": {
|
|
97
|
+
"optional": true
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|