oxianjs 0.21.3 → 0.22.0-next.29ff900
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 +20 -0
- package/README.md +14 -0
- package/docs/migration-0.22.md +70 -0
- package/docs/owner-retirement.md +122 -0
- package/package.json +44 -9
- package/src/adapters/node/connection-budget.d.ts +17 -0
- package/src/adapters/node/connection-budget.js +34 -0
- package/src/adapters/node/index.d.ts +4 -0
- package/src/adapters/node/index.js +2 -0
- package/src/adapters/node/server.d.ts +4 -0
- package/src/adapters/node/server.js +178 -0
- package/src/adapters/node/types.d.ts +7 -0
- package/src/app/application.d.ts +6 -0
- package/src/app/application.js +227 -0
- package/src/app/base_path.d.ts +11 -0
- package/src/app/base_path.js +31 -0
- package/src/app/core.d.ts +4 -0
- package/src/app/core.js +4 -0
- package/src/app/middleware.d.ts +8 -0
- package/src/app/middleware.js +52 -0
- package/src/app/response.d.ts +10 -0
- package/src/app/response.js +102 -0
- package/src/app/sse.d.ts +7 -0
- package/src/app/sse.js +168 -0
- package/src/app/types.d.ts +83 -0
- package/src/app/types.js +1 -0
- package/src/core.d.ts +15 -0
- package/src/core.js +13 -0
- package/src/http/body.d.ts +1 -0
- package/src/http/body.js +69 -0
- package/src/http/gateway.d.ts +2 -0
- package/src/http/gateway.js +149 -0
- package/src/http/index.d.ts +5 -0
- package/src/http/index.js +5 -0
- package/src/http/metadata.d.ts +20 -0
- package/src/http/metadata.js +259 -0
- package/src/http/types.d.ts +34 -0
- package/src/http/types.js +3 -0
- package/src/http/workload.d.ts +3 -0
- package/src/http/workload.js +61 -0
- package/src/hypervisor/config.d.ts +29 -0
- package/src/hypervisor/config.js +123 -0
- package/src/hypervisor/hypervisor.d.ts +10 -0
- package/src/hypervisor/hypervisor.js +449 -0
- package/src/hypervisor/index.d.ts +3 -0
- package/src/hypervisor/index.js +2 -0
- package/src/hypervisor/internal/admission.d.ts +25 -0
- package/src/hypervisor/internal/admission.js +117 -0
- package/src/hypervisor/internal/connection.d.ts +33 -0
- package/src/hypervisor/internal/connection.js +199 -0
- package/src/hypervisor/internal/directory.d.ts +16 -0
- package/src/hypervisor/internal/directory.js +51 -0
- package/src/hypervisor/internal/dispatch-queue.d.ts +35 -0
- package/src/hypervisor/internal/dispatch-queue.js +204 -0
- package/src/hypervisor/internal/dispatch.d.ts +20 -0
- package/src/hypervisor/internal/dispatch.js +171 -0
- package/src/hypervisor/internal/drain.d.ts +15 -0
- package/src/hypervisor/internal/drain.js +102 -0
- package/src/hypervisor/internal/ephemeral.d.ts +11 -0
- package/src/hypervisor/internal/ephemeral.js +64 -0
- package/src/hypervisor/internal/lifecycle.d.ts +32 -0
- package/src/hypervisor/internal/lifecycle.js +189 -0
- package/src/hypervisor/internal/listener.d.ts +6 -0
- package/src/hypervisor/internal/listener.js +17 -0
- package/src/hypervisor/internal/model.d.ts +88 -0
- package/src/hypervisor/internal/model.js +1 -0
- package/src/hypervisor/internal/primitives.d.ts +27 -0
- package/src/hypervisor/internal/primitives.js +149 -0
- package/src/hypervisor/internal/session.d.ts +34 -0
- package/src/hypervisor/internal/session.js +228 -0
- package/src/hypervisor/internal/work-stream.d.ts +34 -0
- package/src/hypervisor/internal/work-stream.js +676 -0
- package/src/hypervisor/types.d.ts +162 -0
- package/src/hypervisor/types.js +1 -0
- package/src/lifecycle/index.d.ts +1 -0
- package/src/lifecycle/index.js +1 -0
- package/src/lifecycle/types.d.ts +193 -0
- package/src/lifecycle/types.js +1 -0
- package/src/mod.d.ts +7 -0
- package/src/mod.js +7 -0
- package/src/protocol/binary.d.ts +40 -0
- package/src/protocol/binary.js +182 -0
- package/src/protocol/control.d.ts +47 -0
- package/src/protocol/control.js +630 -0
- package/src/protocol/flow_control.d.ts +27 -0
- package/src/protocol/flow_control.js +66 -0
- package/src/protocol/index.d.ts +7 -0
- package/src/protocol/index.js +7 -0
- package/src/protocol/limits.d.ts +14 -0
- package/src/protocol/limits.js +7 -0
- package/src/protocol/state.d.ts +60 -0
- package/src/protocol/state.js +542 -0
- package/src/protocol/violation.d.ts +11 -0
- package/src/protocol/violation.js +35 -0
- package/src/providers/cloud_run_jobs.d.ts +49 -0
- package/src/providers/cloud_run_jobs.js +1213 -0
- package/src/providers/conformance.d.ts +22 -0
- package/src/providers/conformance.js +104 -0
- package/src/providers/contract.d.ts +35 -0
- package/src/providers/contract.js +258 -0
- package/src/providers/core.d.ts +5 -0
- package/src/providers/core.js +5 -0
- package/src/providers/externally_attached.d.ts +49 -0
- package/src/providers/externally_attached.js +167 -0
- package/src/providers/types.d.ts +69 -0
- package/src/providers/types.js +1 -0
- package/src/router/types.d.ts +71 -0
- package/src/router/types.js +15 -0
- package/src/supervisor/credentials.d.ts +48 -0
- package/src/supervisor/credentials.js +259 -0
- package/src/supervisor/dispatcher.d.ts +81 -0
- package/src/supervisor/dispatcher.js +522 -0
- package/src/supervisor/index.d.ts +6 -0
- package/src/supervisor/index.js +5 -0
- package/src/supervisor/internal.d.ts +15 -0
- package/src/supervisor/internal.js +121 -0
- package/src/supervisor/sessions.d.ts +60 -0
- package/src/supervisor/sessions.js +334 -0
- package/src/supervisor/state.d.ts +29 -0
- package/src/supervisor/state.js +128 -0
- package/src/supervisor/store.d.ts +32 -0
- package/src/supervisor/store.js +144 -0
- package/src/supervisor/types.d.ts +119 -0
- package/src/supervisor/types.js +1 -0
- package/src/transport/declarations.d.ts +29 -0
- package/src/transport/declarations.js +1 -0
- package/src/transport/frame.d.ts +44 -0
- package/src/transport/frame.js +335 -0
- package/src/transport/in-process.d.ts +42 -0
- package/src/transport/in-process.js +276 -0
- package/src/transport/index.d.ts +5 -0
- package/src/transport/index.js +4 -0
- package/src/transport/queue.d.ts +19 -0
- package/src/transport/queue.js +83 -0
- package/src/transport/socket.d.ts +6 -0
- package/src/transport/socket.js +102 -0
- package/src/transport/types.d.ts +104 -0
- package/src/transport/types.js +1 -0
- package/src/transport/websocket.d.ts +13 -0
- package/src/transport/websocket.js +506 -0
- package/src/work/index.d.ts +1 -0
- package/src/work/index.js +1 -0
- package/src/work/types.d.ts +27 -0
- package/src/work/types.js +1 -0
- package/src/worker/backoff.d.ts +14 -0
- package/src/worker/backoff.js +46 -0
- package/src/worker/index.d.ts +3 -0
- package/src/worker/index.js +3 -0
- package/src/worker/internal/async.d.ts +11 -0
- package/src/worker/internal/async.js +132 -0
- package/src/worker/internal/credentials.d.ts +32 -0
- package/src/worker/internal/credentials.js +118 -0
- package/src/worker/internal/errors.d.ts +5 -0
- package/src/worker/internal/errors.js +40 -0
- package/src/worker/internal/execution_ledger.d.ts +13 -0
- package/src/worker/internal/execution_ledger.js +42 -0
- package/src/worker/internal/lifecycle.d.ts +44 -0
- package/src/worker/internal/lifecycle.js +120 -0
- package/src/worker/internal/session-options.d.ts +40 -0
- package/src/worker/internal/session-options.js +1 -0
- package/src/worker/internal/validation.d.ts +2 -0
- package/src/worker/internal/validation.js +19 -0
- package/src/worker/internal/work.d.ts +8 -0
- package/src/worker/internal/work.js +41 -0
- package/src/worker/session.d.ts +7 -0
- package/src/worker/session.js +1456 -0
- package/src/worker/types.d.ts +162 -0
- package/src/worker/types.js +1 -0
- package/src/worker/worker.d.ts +10 -0
- package/src/worker/worker.js +373 -0
- package/src/host/host.d.ts +0 -5
- package/src/host/host.js +0 -84
- package/src/host/index.d.ts +0 -3
- package/src/host/index.js +0 -2
- package/src/host/types.d.ts +0 -46
- package/src/host/types.js +0 -2
- package/src/owner/index.d.ts +0 -6
- package/src/owner/index.js +0 -4
- package/src/owner/memory.d.ts +0 -15
- package/src/owner/memory.js +0 -216
- package/src/owner/owner.d.ts +0 -3
- package/src/owner/owner.js +0 -462
- package/src/owner/primitives.d.ts +0 -9
- package/src/owner/primitives.js +0 -51
- package/src/owner/remote.d.ts +0 -44
- package/src/owner/remote.js +0 -175
- package/src/owner/types.d.ts +0 -203
- /package/src/{owner → adapters/node}/types.js +0 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# oxianjs
|
|
2
|
+
|
|
3
|
+
## 0.22.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Prepare the 0.22 prerelease line with a breaking removal of the unused generic
|
|
8
|
+
owner, owner-host and Postgres-owner APIs. Durable ownership moves to the
|
|
9
|
+
concrete Copilotz runtime, whose replacement implementation remains pending.
|
|
10
|
+
|
|
11
|
+
Retain worker dispatch, transports, sessions and independent Node connection
|
|
12
|
+
budgeting. Publish the existing Node-portable root and explicit subpaths, with
|
|
13
|
+
installed-package type and in-process/WebSocket proofs; remove the owner-only
|
|
14
|
+
PostgreSQL dependency and CI service.
|
|
15
|
+
|
|
16
|
+
This base version is used for merge-triggered `0.22.0-next.<mergeSHA>` builds
|
|
17
|
+
on npm's `next` tag and the same-version JSR mirror. It does not authorize a
|
|
18
|
+
stable `v0.22.0` tag or move npm's `latest` tag. See the migration and
|
|
19
|
+
retirement documentation for removed APIs, supported exports and preserved
|
|
20
|
+
conformance evidence.
|
package/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Oxian for Node
|
|
2
|
+
|
|
3
|
+
Fetch-native applications and streaming work over in-process or WebSocket transports.
|
|
4
|
+
|
|
5
|
+
Requires Node 24 or newer. This package exports the portable root, http, hypervisor,
|
|
6
|
+
protocol, providers, transport, work, worker, and adapters/node. The providers
|
|
7
|
+
subpath contains only portable providers; it does not include local processes.
|
|
8
|
+
|
|
9
|
+
The filesystem app factories, config loader, file router, local composition, edge,
|
|
10
|
+
Deno adapter, and CLI remain available only in the Deno/JSR package during the
|
|
11
|
+
Node migration. The retired owner, host, and PostgreSQL-owner APIs are not included;
|
|
12
|
+
see the [retirement and migration notes](docs/owner-retirement.md).
|
|
13
|
+
|
|
14
|
+
See https://github.com/AxionCompany/oxian-js for documentation.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Migrating to the 0.22 prerelease
|
|
2
|
+
|
|
3
|
+
The 0.22.0 source base introduces a breaking removal of unused owner APIs.
|
|
4
|
+
Successful verification on `main` publishes `0.22.0-next.<mergeSHA>` to npm's
|
|
5
|
+
`next` tag and mirrors the exact same version to JSR. This is a prerelease:
|
|
6
|
+
`latest` stays at 0.21.3, and no `v0.22.0` stable tag is created by this work.
|
|
7
|
+
|
|
8
|
+
Use `npm install oxianjs@next` to try the new Node package. For a reproducible
|
|
9
|
+
installation, pin the exact returned version. JSR users should likewise select
|
|
10
|
+
the matching published next version rather than assume a stable 0.22.0 exists.
|
|
11
|
+
Current source API examples use the base version; substitute that next version
|
|
12
|
+
when installing from the registry.
|
|
13
|
+
|
|
14
|
+
## Removed APIs
|
|
15
|
+
|
|
16
|
+
The `/owner`, `/host`, and `/adapters/postgres` package entrypoints no longer
|
|
17
|
+
exist. This removes `createOwner`, `createMemoryOwnerStore`, command/snapshot
|
|
18
|
+
sessions, `createOwnerHost`, shard maps and the Postgres-owner store. The
|
|
19
|
+
standalone implementation was unused by applications. It is not retained behind
|
|
20
|
+
aliases or compatibility stubs, and importing those entrypoints now fails.
|
|
21
|
+
|
|
22
|
+
A downstream experiment that still uses these APIs must remain pinned to its
|
|
23
|
+
previous version or preserved checkout until its concrete replacement is ready.
|
|
24
|
+
The full Git history and the behavioral migration inventory remain available;
|
|
25
|
+
see [retirement evidence](owner-retirement.md). No database data is deleted or
|
|
26
|
+
migrated by upgrading the package.
|
|
27
|
+
|
|
28
|
+
Copilotz will own its durable command and storage runtime. That replacement,
|
|
29
|
+
authenticated reverse calls, real-table group commits, recovery and deployment
|
|
30
|
+
integration are not implemented by this release. Removing the library does not
|
|
31
|
+
certify those guarantees or add worker-to-owner invocation to the protocol.
|
|
32
|
+
|
|
33
|
+
## Retained Node surface
|
|
34
|
+
|
|
35
|
+
The npm build now exposes:
|
|
36
|
+
|
|
37
|
+
- `oxianjs`: the portable application, HTTP, Hypervisor, worker and work core
|
|
38
|
+
- `/adapters/node`: the Node HTTP/WebSocket listener and connection budget
|
|
39
|
+
- `/http`, `/hypervisor`, `/protocol`, `/transport`, `/work`, `/worker`
|
|
40
|
+
- `/providers`: the portable provider contract, externally attached provider,
|
|
41
|
+
Cloud Run Jobs provider and conformance helpers
|
|
42
|
+
|
|
43
|
+
Node 24 or newer is required. `ws` is the Node listener's runtime dependency;
|
|
44
|
+
Postgres and its type dependency have been removed. Importing the portable root
|
|
45
|
+
still does not create listeners, spawn processes or require a database.
|
|
46
|
+
|
|
47
|
+
The npm `/providers` subpath excludes the Deno local-process provider. The
|
|
48
|
+
Deno-bound `/app`, `/config`, `/local`, `/router`, `/edge`, `/cli`, `/bin`, and
|
|
49
|
+
`/adapters/deno` remain available only in the retained JSR surface during the
|
|
50
|
+
Node migration. The npm build is deliberately not advertised as full JSR parity.
|
|
51
|
+
|
|
52
|
+
## Node listener migration
|
|
53
|
+
|
|
54
|
+
`serve({ hypervisor })` still defaults to a 1,000-connection budget. To share a
|
|
55
|
+
lower limit between listeners, import `createConnectionBudget` from
|
|
56
|
+
`oxianjs/adapters/node` and pass the same value as each listener's `budget`.
|
|
57
|
+
This replaces the old owner-host object for that purpose. Budget acquisition
|
|
58
|
+
refuses excess upgrades with HTTP 503; socket closure releases the lease once,
|
|
59
|
+
including for rejected upgrades. Closing a listener does not close an injected
|
|
60
|
+
budget or the application-owned Hypervisor.
|
|
61
|
+
|
|
62
|
+
## Verification
|
|
63
|
+
|
|
64
|
+
The release retains Oxian's protocol, supervisor, transport, worker and
|
|
65
|
+
Hypervisor suites. CI checks Node and Deno plus an installed npm tarball:
|
|
66
|
+
public exports, missing retired subpaths, declaration consumption, absence of
|
|
67
|
+
tests/raw TypeScript in the npm payload, and real in-process and Node WebSocket
|
|
68
|
+
work dispatch. Historical owner tests remain migration evidence, not executable
|
|
69
|
+
replacement coverage. Stable publication still requires its separate exact-tag
|
|
70
|
+
workflow and is outside this prerelease.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Durable owner retirement: migration evidence
|
|
2
|
+
|
|
3
|
+
Status: owner API retirement, 2026-10-16. This is an inventory of preserved
|
|
4
|
+
requirements and historical evidence, not a replacement implementation or a
|
|
5
|
+
claim that Copilotz owner conformance passes.
|
|
6
|
+
|
|
7
|
+
## Release and compatibility
|
|
8
|
+
|
|
9
|
+
This change prepares the 0.22.0 base for `0.22.0-next.<mergeSHA>` prereleases.
|
|
10
|
+
The successful main verification workflow publishes npm `next` and the matching
|
|
11
|
+
JSR version. It leaves npm `latest` at 0.21.3. No stable tag is part of this
|
|
12
|
+
release. See [the migration notes](migration-0.22.md) for the removed entrypoints
|
|
13
|
+
and retained transport boundary.
|
|
14
|
+
|
|
15
|
+
## Boundary
|
|
16
|
+
|
|
17
|
+
Oxian keeps its portable application/worker/Hypervisor, transports, dispatch,
|
|
18
|
+
session fencing, admission hooks and protocol. The unused generic owner,
|
|
19
|
+
owner-host and Postgres-owner API is removed. Durable command handling and
|
|
20
|
+
application storage belong in the concrete Copilotz runtime. The approved
|
|
21
|
+
worker-originated owner-call extension is still to be specified and built.
|
|
22
|
+
There is no retained test-only owner runtime or placeholder owner API.
|
|
23
|
+
|
|
24
|
+
The Node listener's connection limiter remains, independently of ownership.
|
|
25
|
+
It can be shared across listeners, refuses overload, and releases leases on
|
|
26
|
+
socket closure. Its active tests live beside the adapter.
|
|
27
|
+
|
|
28
|
+
## Immutable recovery points
|
|
29
|
+
|
|
30
|
+
- Public main before retirement:
|
|
31
|
+
`6cf55dc13834bafcfe9ef4c4067372b68982ae2f`
|
|
32
|
+
([source](https://github.com/AxionCompany/oxian-js/tree/6cf55dc13834bafcfe9ef4c4067372b68982ae2f))
|
|
33
|
+
- Preserved local owner-records work:
|
|
34
|
+
`2dfebacf3408c0e25672289ec31e7201588d6cca`, on the separate recovery branch
|
|
35
|
+
and in the transferred Git bundle. This commit is not a public release.
|
|
36
|
+
- Copilotz's unpushed parallel-kernel work and uncommitted wire changes remain
|
|
37
|
+
in their original checkout. Their behavioral cases are reference material;
|
|
38
|
+
the new runtime must use Copilotz's real tables, not adopt `owner_records`.
|
|
39
|
+
|
|
40
|
+
Use `git show <commit>:<path>` to recover an exact historical test. The retirement
|
|
41
|
+
branch starts at public main; it neither cherry-picks nor deletes the unpublished
|
|
42
|
+
owner-records commit. No existing database is modified by deleting these APIs.
|
|
43
|
+
|
|
44
|
+
## Test inventory
|
|
45
|
+
|
|
46
|
+
At the public-main recovery point, five files declare 46 cases:
|
|
47
|
+
|
|
48
|
+
| Historical path | Cases | Disposition |
|
|
49
|
+
|---|---:|---|
|
|
50
|
+
| `tests/owner/owner_test.ts` | 18 | Receipt/command cases for the concrete Copilotz owner |
|
|
51
|
+
| `tests/owner/sequence_test.ts` | 9 | Sequence/alarm cases for the real event log |
|
|
52
|
+
| `tests/owner/remote_test.ts` | 5 | Snapshot/recovery cases for authenticated worker calls |
|
|
53
|
+
| `tests/owner/postgres_test.ts` | 10 | Real-table transaction/fence cases |
|
|
54
|
+
| `tests/host/host_test.ts` | 4 | Two ownership cases remain migration work; two connection-budget cases rehomed |
|
|
55
|
+
|
|
56
|
+
`tests/owner/postgres_database.ts` was the test helper, not a conformance case.
|
|
57
|
+
Its Mac-specific local-cluster setup and the owner-only CI database service are
|
|
58
|
+
removed with the suites. The local owner-records commit adds three Postgres
|
|
59
|
+
cases, bringing its historical total to 49. Removing executable tests here
|
|
60
|
+
must not be reported as completion of their replacement coverage.
|
|
61
|
+
|
|
62
|
+
## Behavioral cases to port
|
|
63
|
+
|
|
64
|
+
The destination is tests next to Copilotz's concrete owner and storage code,
|
|
65
|
+
using existing domain/event tables. The F5/F7/F9 contracts and guarantee map
|
|
66
|
+
remain the acceptance bar. This checklist is evidence, not the whole plan.
|
|
67
|
+
|
|
68
|
+
| Guarantee | Historical tests or fixture | Replacement requirement |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| Result, effects and receipt are atomic | `owner_test.ts`: “a committed command returns its result and keeps its receipt” | One fenced outer transaction on real tables |
|
|
71
|
+
| Compatible replay, conflicting reuse, identity domains | “the same request returns its receipt; a different request is rejected”; “identity domains do not share receipts” | Stable logical intent, distinct incarnation/occurrence keys and separate current authority |
|
|
72
|
+
| Command-local rollback and timestamp | “one failing command rolls back only itself”; “a thrown command rolls back its writes and keeps the rest of the group”; “a lease crossed inside a group fails only the later command” | A savepoint and timestamp per command; no partial effects or publication |
|
|
73
|
+
| Unknown versus definitive commit outcome | “a lost commit stops the owner and the successor trusts receipts”; “a lost commit that aborted reruns each command once”; “a definitive rollback retries each command on its own” | Successor waits for server outcome; receipt recovery before preparing a different effect |
|
|
74
|
+
| Outer output gate | “post-commit callbacks run only after the commit is durable”; “a failed command does not run its post-commit callback” | Results, materialization, dispatch and notifications follow the outer commit |
|
|
75
|
+
| Admission and bounded queue | “admission admits 32 and recovers a receipt at capacity”; “a full queue refuses and a stopped owner reports ownership lost” | Atomic cap and bounded refusal; receipt recovery at capacity |
|
|
76
|
+
| Input/result isolation | “a command runs the input that was submitted”; “changing a returned receipt does not change the recorded one”; “commands in one group keep their own results” | Caller mutation cannot change accepted intent or recorded outcome |
|
|
77
|
+
| Epoch fencing | “a stale owner fails once a successor acquires the epoch”; Postgres takeover/open-transaction/assignment cases | Fence actual writes; takeover blocks behind unresolved transaction; routing alone is not authority |
|
|
78
|
+
| Mixed group uncertainty | `postgres_test.ts` lost-commit fixture mixes compatible duplicate, conflicting duplicate, success and failure, under committed/aborted outcomes | Keep this matrix and add actual in-flight COMMIT uncertainty |
|
|
79
|
+
| Failed transaction cleanup | Postgres failed-BEGIN/rollback case | Discard an unusable pooled connection |
|
|
80
|
+
| Receipt recovery without stale mutation | `remote_test.ts`: “an old session cannot mutate after takeover and can still read receipts” | Authenticated, owner-scoped same-worker recovery; no implicit protected-value hydration |
|
|
81
|
+
| Snapshot lifecycle | Remaining four `remote_test.ts` cases | Repeatable snapshot, cancellation, takeover, idle/lifetime expiry and in-flight failure; bounded sessions |
|
|
82
|
+
| Gap-free event sequence | `sequence_test.ts`: “a failed command and a duplicate consume no sequence”; “a rolled-back group consumes no sequence” | Existing `events.seq` shares effects/receipts/fence commit |
|
|
83
|
+
| Atomic attach and repair | “attach captures the head of its commit”; “a heartbeat head shows a dropped last update”; “a dropped push resets that observer” | Real push transport, reset and heartbeat-head repair |
|
|
84
|
+
| Durable alarms | Remaining four `sequence_test.ts` cases; Postgres alarm revision/sequence takeover cases | Wake after restart, early-timer guard, transient-read retry, rollback cleanup and actual scheduler semantics |
|
|
85
|
+
| Placement | `host_test.ts`: “a shard that does not own an id does not move its epoch”; “one process runs every owner assigned to it” | Named-owner routing and host-specific activation; no ownership claim from process count alone |
|
|
86
|
+
| Node overload protection | Former final two `host_test.ts` cases | Active `src/adapters/node/connection-budget.test.ts`; no owner-host dependency |
|
|
87
|
+
|
|
88
|
+
The three extra `2dfebac` cases assert that commit-after-rollback cannot persist
|
|
89
|
+
writes; committed records survive takeover while rolled-back writes do not;
|
|
90
|
+
and records are owner-isolated with bounded read-only snapshots. Preserve those
|
|
91
|
+
invariants against Copilotz tables. Generic map insertion order and the
|
|
92
|
+
`owner_records` schema are not requirements on domain storage.
|
|
93
|
+
|
|
94
|
+
## Limits of the old evidence
|
|
95
|
+
|
|
96
|
+
- Lost COMMIT responses were injected after completed commit/rollback. Killing
|
|
97
|
+
an open transaction does not exercise an owner dying during COMMIT.
|
|
98
|
+
- Remote-session tests use local helpers, not authenticated worker RPC or the
|
|
99
|
+
combined worker, connection, owner and execution-attempt fences.
|
|
100
|
+
- Observer tests manually acknowledge/reset; real dropped transport messages,
|
|
101
|
+
subscriber authorization and end-to-end repair remain to prove.
|
|
102
|
+
- Alarm tests do not establish exact-due schedule claims, occurrence identity,
|
|
103
|
+
misfire skipping, or paused/manual-run semantics.
|
|
104
|
+
- These files do not establish fresh stream allocation across attempts,
|
|
105
|
+
protected-value grants, two-phase settlement, await/resume, effect
|
|
106
|
+
reconciliation, full activation readiness, or saturated HTTP/Action progress.
|
|
107
|
+
|
|
108
|
+
These missing cases remain work. Retirement neither drops them nor marks them
|
|
109
|
+
passing. Durable Object activation/storage and production routing remain
|
|
110
|
+
host-specific work; deleting the old library does not implement either.
|
|
111
|
+
|
|
112
|
+
## Evidence that remains active in Oxian
|
|
113
|
+
|
|
114
|
+
All protocol, supervisor, transport, worker and Hypervisor suites remain.
|
|
115
|
+
In particular, `tests/protocol/*` and `tests/supervisor/*` retain their 95 declared
|
|
116
|
+
cases for byte/sequence credit, capacity reservation, cancellation, drain,
|
|
117
|
+
half-close, session generations, credential resume, durable acceptance and
|
|
118
|
+
committed-versus-indeterminate dispatch. The installed npm-package proof now
|
|
119
|
+
uses retained worker dispatch instead of the deleted owner/host API.
|
|
120
|
+
|
|
121
|
+
No generic substring deletion of “owner” is appropriate: existing transport
|
|
122
|
+
resource ownership and admission responsibilities still have their own meaning.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oxianjs",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.22.0-next.29ff900",
|
|
4
|
+
"description": "Fetch-native applications and streaming worker dispatch",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -12,17 +12,52 @@
|
|
|
12
12
|
"node": ">=24"
|
|
13
13
|
},
|
|
14
14
|
"exports": {
|
|
15
|
-
"
|
|
16
|
-
"types": "./src/
|
|
17
|
-
"default": "./src/
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./src/mod.d.ts",
|
|
17
|
+
"default": "./src/mod.js"
|
|
18
18
|
},
|
|
19
|
-
"./
|
|
20
|
-
"types": "./src/
|
|
21
|
-
"default": "./src/
|
|
19
|
+
"./adapters/node": {
|
|
20
|
+
"types": "./src/adapters/node/index.d.ts",
|
|
21
|
+
"default": "./src/adapters/node/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./http": {
|
|
24
|
+
"types": "./src/http/index.d.ts",
|
|
25
|
+
"default": "./src/http/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./hypervisor": {
|
|
28
|
+
"types": "./src/hypervisor/index.d.ts",
|
|
29
|
+
"default": "./src/hypervisor/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./protocol": {
|
|
32
|
+
"types": "./src/protocol/index.d.ts",
|
|
33
|
+
"default": "./src/protocol/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./providers": {
|
|
36
|
+
"types": "./src/providers/core.d.ts",
|
|
37
|
+
"default": "./src/providers/core.js"
|
|
38
|
+
},
|
|
39
|
+
"./transport": {
|
|
40
|
+
"types": "./src/transport/index.d.ts",
|
|
41
|
+
"default": "./src/transport/index.js"
|
|
42
|
+
},
|
|
43
|
+
"./work": {
|
|
44
|
+
"types": "./src/work/index.d.ts",
|
|
45
|
+
"default": "./src/work/index.js"
|
|
46
|
+
},
|
|
47
|
+
"./worker": {
|
|
48
|
+
"types": "./src/worker/index.d.ts",
|
|
49
|
+
"default": "./src/worker/index.js"
|
|
22
50
|
}
|
|
23
51
|
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"ws": "8.22.0"
|
|
54
|
+
},
|
|
24
55
|
"files": [
|
|
25
56
|
"src",
|
|
26
|
-
"
|
|
57
|
+
"docs/owner-retirement.md",
|
|
58
|
+
"docs/migration-0.22.md",
|
|
59
|
+
"CHANGELOG.md",
|
|
60
|
+
"LICENSE",
|
|
61
|
+
"README.md"
|
|
27
62
|
]
|
|
28
63
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Maximum accepted WebSocket connections in a listener's budget. */
|
|
2
|
+
export declare const CONNECTION_BUDGET = 1000;
|
|
3
|
+
/** One accepted connection. Releasing it twice is harmless. */
|
|
4
|
+
export type ConnectionLease = Readonly<{
|
|
5
|
+
release(): void;
|
|
6
|
+
}>;
|
|
7
|
+
/** May be shared by several listeners in the same process. */
|
|
8
|
+
export type ConnectionBudget = Readonly<{
|
|
9
|
+
connect(): ConnectionLease;
|
|
10
|
+
snapshot(): Readonly<{
|
|
11
|
+
connections: number;
|
|
12
|
+
budget: number;
|
|
13
|
+
}>;
|
|
14
|
+
/** Refuses new connections; existing leases can still be released. */
|
|
15
|
+
close(): void;
|
|
16
|
+
}>;
|
|
17
|
+
export declare function createConnectionBudget(maxConnections?: number): ConnectionBudget;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Maximum accepted WebSocket connections in a listener's budget. */
|
|
2
|
+
export const CONNECTION_BUDGET = 1_000;
|
|
3
|
+
export function createConnectionBudget(maxConnections = CONNECTION_BUDGET) {
|
|
4
|
+
if (!Number.isSafeInteger(maxConnections) ||
|
|
5
|
+
maxConnections < 1 ||
|
|
6
|
+
maxConnections > CONNECTION_BUDGET) {
|
|
7
|
+
throw new RangeError(`maxConnections must be from 1 to ${CONNECTION_BUDGET}`);
|
|
8
|
+
}
|
|
9
|
+
let connections = 0;
|
|
10
|
+
let closed = false;
|
|
11
|
+
return Object.freeze({
|
|
12
|
+
connect() {
|
|
13
|
+
if (closed)
|
|
14
|
+
throw new Error("connection budget is closed");
|
|
15
|
+
if (connections >= maxConnections) {
|
|
16
|
+
throw new Error("connection budget exceeded");
|
|
17
|
+
}
|
|
18
|
+
connections += 1;
|
|
19
|
+
let released = false;
|
|
20
|
+
return Object.freeze({
|
|
21
|
+
release() {
|
|
22
|
+
if (released)
|
|
23
|
+
return;
|
|
24
|
+
released = true;
|
|
25
|
+
connections -= 1;
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
snapshot: () => Object.freeze({ connections, budget: maxConnections }),
|
|
30
|
+
close() {
|
|
31
|
+
closed = true;
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { HypervisorListener } from "../../hypervisor/types.ts";
|
|
2
|
+
import type { NodeServeOptions } from "./types.ts";
|
|
3
|
+
/** Starts a Node listener for an existing application-owned Hypervisor. */
|
|
4
|
+
export declare function serve(options: NodeServeOptions): Promise<HypervisorListener>;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { createServer, } from "node:http";
|
|
2
|
+
import { Readable } from "node:stream";
|
|
3
|
+
import { pipeline } from "node:stream/promises";
|
|
4
|
+
import { WebSocketServer } from "ws";
|
|
5
|
+
import { createConnectionBudget, } from "./connection-budget.js";
|
|
6
|
+
import { observeListenerSettlement } from "../../hypervisor/internal/listener.js";
|
|
7
|
+
import { createHypervisorError, listenerUrl, } from "../../hypervisor/internal/primitives.js";
|
|
8
|
+
import { WORKER_PROTOCOL_LIMITS } from "../../protocol/limits.js";
|
|
9
|
+
import { adaptWebSocket } from "../../transport/socket.js";
|
|
10
|
+
/** Starts a Node listener for an existing application-owned Hypervisor. */
|
|
11
|
+
export async function serve(options) {
|
|
12
|
+
const { hypervisor, hostname = "127.0.0.1", port = 0, signal } = options;
|
|
13
|
+
const budget = options.budget ?? createConnectionBudget();
|
|
14
|
+
if (!hypervisor.snapshot().acceptingConnections) {
|
|
15
|
+
throw createHypervisorError("shutting_down", "cannot listen after Hypervisor shutdown begins");
|
|
16
|
+
}
|
|
17
|
+
signal?.throwIfAborted();
|
|
18
|
+
const selected = new WeakMap();
|
|
19
|
+
const sockets = new WebSocketServer({
|
|
20
|
+
noServer: true,
|
|
21
|
+
perMessageDeflate: false,
|
|
22
|
+
maxPayload: WORKER_PROTOCOL_LIMITS.maxDataPayloadBytes +
|
|
23
|
+
WORKER_PROTOCOL_LIMITS.maxControlFrameBytes,
|
|
24
|
+
handleProtocols: (offered, request) => {
|
|
25
|
+
const protocol = selected.get(request);
|
|
26
|
+
return protocol !== undefined && offered.has(protocol) ? protocol : false;
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
let origin = listenerUrl(hostname, port);
|
|
30
|
+
const server = createServer((request, response) => {
|
|
31
|
+
void respond(request, response);
|
|
32
|
+
});
|
|
33
|
+
async function respond(request, response) {
|
|
34
|
+
try {
|
|
35
|
+
const decision = hypervisor.prepare(toRequest(request, origin, true));
|
|
36
|
+
if (decision.kind === "upgrade") {
|
|
37
|
+
decision.cancel("upgrade_event_required");
|
|
38
|
+
await send(response, new Response("WebSocket upgrade required", { status: 400 }));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
await send(response, await decision.response);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
if (!response.headersSent)
|
|
45
|
+
response.writeHead(500);
|
|
46
|
+
response.end();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
server.on("upgrade", (request, socket, head) => {
|
|
50
|
+
void upgrade(request, socket, head);
|
|
51
|
+
});
|
|
52
|
+
async function upgrade(request, socket, head) {
|
|
53
|
+
if (takeConnection(budget, socket) === "refused") {
|
|
54
|
+
await reject(socket, new Response("connection budget exceeded", { status: 503 }));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
let cancel;
|
|
58
|
+
const failed = () => cancel?.("upgrade_closed");
|
|
59
|
+
socket.on("error", failed);
|
|
60
|
+
socket.once("close", failed);
|
|
61
|
+
try {
|
|
62
|
+
const decision = hypervisor.prepare(toRequest(request, origin, false));
|
|
63
|
+
if (decision.kind === "response") {
|
|
64
|
+
await reject(socket, await decision.response);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
cancel = decision.cancel;
|
|
68
|
+
selected.set(request, decision.protocol);
|
|
69
|
+
sockets.handleUpgrade(request, socket, head, (connection) => {
|
|
70
|
+
socket.removeListener("error", failed);
|
|
71
|
+
socket.removeListener("close", failed);
|
|
72
|
+
cancel = undefined;
|
|
73
|
+
try {
|
|
74
|
+
decision.attach(adaptWebSocket(connection), decision.protocol);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
decision.cancel("connection_rejected");
|
|
78
|
+
connection.close(4400, "connection_rejected");
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
cancel?.("invalid_upgrade");
|
|
84
|
+
socket.destroy();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
sockets.on("wsClientError", (_error, socket) => {
|
|
88
|
+
socket.end("HTTP/1.1 400 Bad Request\r\nConnection: close\r\nContent-Length: 0\r\n\r\n");
|
|
89
|
+
});
|
|
90
|
+
const finished = Promise.withResolvers();
|
|
91
|
+
server.on("error", (error) => finished.reject(error));
|
|
92
|
+
server.once("close", () => finished.resolve());
|
|
93
|
+
await new Promise((resolve, reject) => {
|
|
94
|
+
server.once("error", reject);
|
|
95
|
+
server.listen(port, hostname, () => {
|
|
96
|
+
server.removeListener("error", reject);
|
|
97
|
+
resolve();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
const address = server.address();
|
|
101
|
+
if (address === null || typeof address === "string") {
|
|
102
|
+
throw new TypeError("Hypervisor listener must use a TCP address");
|
|
103
|
+
}
|
|
104
|
+
origin = listenerUrl(address.address, address.port);
|
|
105
|
+
let closed;
|
|
106
|
+
const shutdown = () => {
|
|
107
|
+
if (closed !== undefined)
|
|
108
|
+
return closed;
|
|
109
|
+
for (const client of sockets.clients)
|
|
110
|
+
client.terminate();
|
|
111
|
+
sockets.close();
|
|
112
|
+
server.close();
|
|
113
|
+
server.closeAllConnections();
|
|
114
|
+
closed = finished.promise.catch(() => undefined);
|
|
115
|
+
return closed;
|
|
116
|
+
};
|
|
117
|
+
const listener = Object.freeze({
|
|
118
|
+
hostname: address.address,
|
|
119
|
+
port: address.port,
|
|
120
|
+
url: origin,
|
|
121
|
+
finished: finished.promise,
|
|
122
|
+
shutdown,
|
|
123
|
+
});
|
|
124
|
+
observeListenerSettlement({
|
|
125
|
+
finished: finished.promise,
|
|
126
|
+
signal,
|
|
127
|
+
close: shutdown,
|
|
128
|
+
cleanup: () => undefined,
|
|
129
|
+
});
|
|
130
|
+
return listener;
|
|
131
|
+
}
|
|
132
|
+
function takeConnection(budget, socket) {
|
|
133
|
+
if (budget === undefined)
|
|
134
|
+
return undefined;
|
|
135
|
+
let held;
|
|
136
|
+
try {
|
|
137
|
+
held = budget.connect();
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return "refused";
|
|
141
|
+
}
|
|
142
|
+
if (socket.destroyed)
|
|
143
|
+
held.release();
|
|
144
|
+
else
|
|
145
|
+
socket.once("close", () => held.release());
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
function toRequest(request, origin, withBody) {
|
|
149
|
+
const headers = new Headers();
|
|
150
|
+
for (let index = 0; index < request.rawHeaders.length; index += 2) {
|
|
151
|
+
headers.append(request.rawHeaders[index], request.rawHeaders[index + 1]);
|
|
152
|
+
}
|
|
153
|
+
const hasBody = withBody && request.method !== "GET" && request.method !== "HEAD";
|
|
154
|
+
return new Request(new URL(request.url ?? "/", origin), {
|
|
155
|
+
method: request.method,
|
|
156
|
+
headers,
|
|
157
|
+
...(hasBody
|
|
158
|
+
? {
|
|
159
|
+
body: Readable.toWeb(request),
|
|
160
|
+
duplex: "half",
|
|
161
|
+
}
|
|
162
|
+
: {}),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
async function send(target, response) {
|
|
166
|
+
target.writeHead(response.status, [...response.headers.entries()].flat());
|
|
167
|
+
if (response.body === null) {
|
|
168
|
+
target.end();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
await pipeline(Readable.fromWeb(response.body), target);
|
|
172
|
+
}
|
|
173
|
+
async function reject(socket, response) {
|
|
174
|
+
const body = Buffer.from(await response.arrayBuffer());
|
|
175
|
+
socket.write(`HTTP/1.1 ${response.status} ${response.statusText || "Rejected"}\r\n` +
|
|
176
|
+
`Connection: close\r\nContent-Length: ${body.length}\r\n\r\n`);
|
|
177
|
+
socket.end(body);
|
|
178
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ConnectionBudget } from "./connection-budget.ts";
|
|
2
|
+
import type { Hypervisor, HypervisorListenOptions } from "../../hypervisor/types.ts";
|
|
3
|
+
export type NodeServeOptions = Readonly<{
|
|
4
|
+
hypervisor: Hypervisor;
|
|
5
|
+
/** Each accepted upgrade takes one connection from this shared budget. */
|
|
6
|
+
budget?: ConnectionBudget;
|
|
7
|
+
}> & HypervisorListenOptions;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Application, ApplicationOptions } from "./types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Initializes one Fetch-native application instance. Route modules have
|
|
4
|
+
* already been imported and validated by the file-router factory.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createApplication<State = undefined>(options: ApplicationOptions<State>): Promise<Application<State>>;
|