arcane-os 0.54.1 → 0.56.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 +60 -0
- package/README.md +35 -28
- package/browser-runtime/core/client.mjs +552 -0
- package/browser-runtime/core/contracts.mjs +85 -0
- package/docs/architecture.md +31 -23
- package/docs/compatibility.md +10 -1
- package/docs/consumer-updates.md +82 -0
- package/docs/platform-targets.md +20 -11
- package/docs/publishing.md +41 -18
- package/docs/reference/README.md +19 -7
- package/docs/reference/availability-and-normalization.md +15 -5
- package/docs/reference/behavioral-testing.md +7 -6
- package/docs/reference/cli.md +12 -4
- package/docs/reference/core-client.md +163 -0
- package/docs/reference/core-native-packaging.md +149 -0
- package/docs/reference/core-runtime.md +185 -0
- package/docs/reference/inventory/package-api.json +271 -5
- package/docs/reference/protocols.md +38 -14
- package/docs/reference/pwa.md +17 -0
- package/docs/reference/sdk-api.md +772 -15
- package/package.json +10 -3
- package/schemas/arcane-app.schema.json +10 -0
- package/schemas/arcane-package.schema.json +10 -0
- package/schemas/native-build-plan.schema.json +8 -0
- package/src/app-descriptor.mjs +18 -1
- package/src/app-layout.mjs +6 -2
- package/src/cli/main.mjs +8 -6
- package/src/core/host.mjs +23 -0
- package/src/core/hosts/arcane-core-loader.cjs +2 -0
- package/src/core/hosts/linux/arcane_core_process.c +568 -0
- package/src/core/hosts/linux/arcane_core_process.h +127 -0
- package/src/core/hosts/sea-launcher.cjs +17 -0
- package/src/core/hosts/windows/ArcaneCoreProcess.cs +416 -0
- package/src/core/runtime.mjs +345 -0
- package/src/core/stdio.mjs +209 -0
- package/src/core-classic-source.mjs +43 -0
- package/src/import-map.mjs +62 -0
- package/src/native/portable-layout.mjs +184 -0
- package/src/native/portable-provider.mjs +211 -0
- package/src/native-plan.mjs +10 -4
- package/src/native-provider-loader.mjs +15 -4
- package/src/packager/core.mjs +131 -32
- package/src/process.mjs +8 -4
- package/src/targets/index.mjs +3 -1
- package/src/toolchain.mjs +23 -6
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Core browser client
|
|
2
|
+
|
|
3
|
+
`arcane-os/core/client` is the browser-safe `arcane/1` RPC owner. It exports
|
|
4
|
+
`createCoreClient`, `createCoreFacade`, `installCoreClient` and
|
|
5
|
+
`getInstalledCoreClient`.
|
|
6
|
+
`arcane-os/core/contracts` exports `CORE_PROTOCOL`, `CORE_READY_EVENTS`,
|
|
7
|
+
`CORE_FRAME_CONTRACTS`, `CORE_METHOD_CONTRACTS`, `CoreError` and
|
|
8
|
+
`serializeCoreError`. Contracts describe transport and existing method shapes;
|
|
9
|
+
they do not select application policy or decide which services are installed.
|
|
10
|
+
|
|
11
|
+
## Native connection and explicit adapters
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import {installCoreClient} from 'arcane-os/core/client';
|
|
15
|
+
|
|
16
|
+
const client=installCoreClient();
|
|
17
|
+
const stop=client.events.when('core.ready',state=>{
|
|
18
|
+
console.log('The moon-base dispatcher is connected.',state);
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Installation immediately supplies `globalThis.Arcane`, `__arcaneReceive`,
|
|
23
|
+
synchronous `Arcane.runtime.current()` and `Arcane.events.completed(name)`.
|
|
24
|
+
Repeated installation returns the same client. Existing namespace method names
|
|
25
|
+
remain available through this facade; their implementations and availability
|
|
26
|
+
belong to the selected host. No repository, app list, model selection or service
|
|
27
|
+
implementation is installed by importing this client.
|
|
28
|
+
|
|
29
|
+
An application using a host-installed client can read it without installing or
|
|
30
|
+
replacing anything:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
import {getInstalledCoreClient} from 'arcane-os/core/client';
|
|
34
|
+
|
|
35
|
+
const client=getInstalledCoreClient();
|
|
36
|
+
// null means this global has no live SDK-installed client.
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`getInstalledCoreClient(global=globalThis)` returns the exact live SDK-installed
|
|
40
|
+
client, including one installed by classic document-created injection. It does
|
|
41
|
+
not connect, install a client or replace an existing `Arcane` object. Applications
|
|
42
|
+
need not inspect private installation fields. It returns `null` after that
|
|
43
|
+
installation closes or fails. A
|
|
44
|
+
foreign application's `Arcane` object is not evidence of an SDK installation.
|
|
45
|
+
|
|
46
|
+
The native adapters select WebView2, WebKitGTK or Android WebView. Ordinary
|
|
47
|
+
browsers remain `standalone`; they do not probe a local server. Development
|
|
48
|
+
HTTP is selected only by the existing explicit `__ARCANE_DEV_HTTP__` flag.
|
|
49
|
+
Native process startup and shutdown belong to the native host/runtime, not
|
|
50
|
+
this browser module.
|
|
51
|
+
|
|
52
|
+
For a separately owned transport, use `createCoreClient({transport})` with
|
|
53
|
+
`transport.name`, `send(frame)`, and an optional `subscribe(receive)` returning
|
|
54
|
+
an unsubscribe function. Alternatively deliver responses/events through
|
|
55
|
+
`client.receive(frame)`. An adapter may return a promise from `send`; rejection
|
|
56
|
+
rejects the associated pending request. If cancellation, timeout or a response
|
|
57
|
+
has already settled it, `onError` observes the later send failure. A transport
|
|
58
|
+
without callable `send(frame)` remains disconnected and reports
|
|
59
|
+
`ARCANE_TRANSPORT_INVALID`; it does not announce `transport.ready`.
|
|
60
|
+
`autoConnect:false` defers connection until `connect()` or the first invocation.
|
|
61
|
+
|
|
62
|
+
`replayRuntimeState:true` sends one `runtime.replay` control after the transport's
|
|
63
|
+
receive listener and `transport.ready` are established. The default is `false`,
|
|
64
|
+
preserving existing hosts. The send is observed without delaying page startup;
|
|
65
|
+
delivery errors reach `onError`. A host supporting this control supplies its
|
|
66
|
+
current dispatcher and service state for a newly connected document, without
|
|
67
|
+
restarting services. Repeated `connect()` calls on the same connection do not
|
|
68
|
+
send another replay. See [runtime replay](core-runtime.md#state-and-frames).
|
|
69
|
+
|
|
70
|
+
`client.invoke(method, parameters, {signal, timeoutMs})` returns the actual
|
|
71
|
+
response result or rejects with `CoreError`. Parameters, result fields and
|
|
72
|
+
event data retain their complete supplied content. JSON encoding belongs only
|
|
73
|
+
to transports that require it; values crossing those transports must be
|
|
74
|
+
JSON-compatible. There is no client method allowlist or content limit.
|
|
75
|
+
The default request timeout is ten minutes; `timeoutMs:0` disables that timer.
|
|
76
|
+
The compatibility facade retains its operation-specific timeouts and streaming
|
|
77
|
+
callbacks. Streaming IDs and isolated-operation IDs are separate protocol
|
|
78
|
+
correlation fields alongside the supplied request fields.
|
|
79
|
+
|
|
80
|
+
## Events and request lifetime
|
|
81
|
+
|
|
82
|
+
`events.on(name, listener)`, `once(name, listener)` and
|
|
83
|
+
`when(name, listener)` return unsubscribe functions. Named listeners receive
|
|
84
|
+
the original `data`; `on('*', listener)` receives `{event, data}`. The shared
|
|
85
|
+
SDK event owner provides delivery, with one source per client and no competing
|
|
86
|
+
event bus. `when` replays the first authoritative `transport.ready` or
|
|
87
|
+
`core.ready` state once, on a microtask. `completed` is synchronous.
|
|
88
|
+
|
|
89
|
+
Transport connection, Core dispatcher readiness, application-service state,
|
|
90
|
+
repository synchronization and model readiness are different states.
|
|
91
|
+
`core.ready` does not claim that a model is loaded or an app repository synced.
|
|
92
|
+
Other service events pass through normally; this client does not fabricate
|
|
93
|
+
service readiness.
|
|
94
|
+
|
|
95
|
+
Abort/timeout sends `request.cancel`; `cancelAll()` and page hide send
|
|
96
|
+
`requests.cancelAll` for pending renderer requests. Late responses cannot
|
|
97
|
+
resolve a cancelled request. These controls do not promise to roll back an
|
|
98
|
+
accepted mutation. Accepted saves, operation journals and graceful drain belong
|
|
99
|
+
to the service/runtime owner. `client.close()` releases its subscriptions,
|
|
100
|
+
native callbacks, timers and pending renderer requests; it does not kill Core.
|
|
101
|
+
Call `stop()` to remove an individual subscription and `client.close()` when
|
|
102
|
+
the client owner ends its lifetime.
|
|
103
|
+
|
|
104
|
+
For an actual terminal child-process or transport failure, the host calls
|
|
105
|
+
`client.failTransport(error)`. Installed clients also own the native callback
|
|
106
|
+
`globalThis.__arcaneTransportFailed(error)`. This boundary rejects pending RPCs
|
|
107
|
+
and WebKit acknowledgements with the complete actual `CoreError`, reports that
|
|
108
|
+
failure once, releases callbacks/subscriptions/timers and rejects future
|
|
109
|
+
invocations with the same failure. It sends no cancellation frames to a failed
|
|
110
|
+
transport and does not invent response identifiers or results. A normal close
|
|
111
|
+
still uses cancellation rather than reporting a transport failure.
|
|
112
|
+
|
|
113
|
+
Closing or failing an installation restores its previous `Arcane` object and
|
|
114
|
+
native callbacks only when those globals still belong to that installation.
|
|
115
|
+
Another owner's later replacements remain unchanged. The read-only accessor
|
|
116
|
+
returns `null` before the installed failure is reported to `onError`.
|
|
117
|
+
|
|
118
|
+
Errors retain complete native fields, including diagnostics and stack/cause
|
|
119
|
+
information. WebView2 and Android bridge failures retain native error codes,
|
|
120
|
+
messages and diagnostic fields; the request method and transport are added
|
|
121
|
+
where the native error does not already supply them. Errors without a native
|
|
122
|
+
code receive the adapter's bridge-call failure code. Keep those details in
|
|
123
|
+
developer diagnostics, rather than ordinary
|
|
124
|
+
product status surfaces. The optional `onError(error)` callback observes
|
|
125
|
+
transport/event-owner failures outside individual request promises.
|
|
126
|
+
|
|
127
|
+
## Classic native-host injection
|
|
128
|
+
|
|
129
|
+
```js
|
|
130
|
+
import {createCoreClassicSource} from 'arcane-os/core/classic-source';
|
|
131
|
+
|
|
132
|
+
const source=await createCoreClassicSource({
|
|
133
|
+
eventOwnerModuleURL:'/arcane/sdk/event-manager.mjs',
|
|
134
|
+
replayRuntimeState:true
|
|
135
|
+
});
|
|
136
|
+
// Supply source to the native host's existing classic-script injection owner.
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
This Node-only source generator reads the canonical client and contracts in the
|
|
140
|
+
installed SDK. It emits the same implementation as a classic script, without
|
|
141
|
+
copying another client or adding a bundler. The caller supplies the actual
|
|
142
|
+
served SDK event-manager URL; the example URL must match its selected layout.
|
|
143
|
+
That module and its existing managed import map must be included by packaging.
|
|
144
|
+
Select `replayRuntimeState:true` only when composing a host with the current
|
|
145
|
+
runtime replay control; omitting it preserves the existing no-replay default.
|
|
146
|
+
|
|
147
|
+
The generated facade, native receive callback and current-state methods install
|
|
148
|
+
synchronously. When the shared event owner already exists, it is reused
|
|
149
|
+
immediately. Document-created injection can precede the page's import map, so
|
|
150
|
+
only its event-owner module import waits until document parsing completes.
|
|
151
|
+
Page rendering and RPC setup continue independently. Complete early event
|
|
152
|
+
frames, subscription operations and response deliveries remain ordered until
|
|
153
|
+
the event owner connects. Work received reentrantly during that handoff joins
|
|
154
|
+
the end of the existing queue, including new subscriptions and response frames.
|
|
155
|
+
Final response completion follows earlier event
|
|
156
|
+
delivery, preserving streaming callbacks before their completion cleanup.
|
|
157
|
+
`client.eventsReady` observes that connection; failures are also reported
|
|
158
|
+
through `onError` and reject pending requests.
|
|
159
|
+
|
|
160
|
+
The SDK's [portable native provider](core-native-packaging.md) assembles the
|
|
161
|
+
Core runtime and selected app services without a native-provider checkout.
|
|
162
|
+
It does not distribute a Core executable or change OS callers. Executable
|
|
163
|
+
platform hosts and their cutover remain separately owned work.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Portable Core packaging
|
|
2
|
+
|
|
3
|
+
`arcane-os/native/portable-provider` owns the portable native payload in the SDK
|
|
4
|
+
package. It does not load Arcane OS source, choose models, download a runtime,
|
|
5
|
+
or compile an executable. Windows, Linux and macOS hosts may compose this
|
|
6
|
+
Node-based payload; Android needs its separate native host adaptation. The
|
|
7
|
+
current CLI's portable platform selections remain Windows and Linux. A portable
|
|
8
|
+
artifact is not a Windows executable, Linux package or Android application.
|
|
9
|
+
|
|
10
|
+
`loadArcaneNativeProvider({target:'portable'})` selects the installed SDK provider
|
|
11
|
+
without an `arcaneRoot`. Supplying an explicit `arcaneRoot` retains the existing
|
|
12
|
+
checkout-provider route. Other native targets remain with their actual platform
|
|
13
|
+
providers; the loader does not substitute this payload for an unavailable host.
|
|
14
|
+
|
|
15
|
+
## Provider and selected input
|
|
16
|
+
|
|
17
|
+
The default export and `arcaneNativeBuilderProvider` are the same provider.
|
|
18
|
+
`createPortableNativeProvider({services:[]})` creates one with application-owned
|
|
19
|
+
service configuration. All implement `arcane-native-builder/1`:
|
|
20
|
+
|
|
21
|
+
- `describe()` identifies the portable target and `executable:false`.
|
|
22
|
+
- `doctor({targetRequest, signal, onEvent})` reports missing SDK Core source files.
|
|
23
|
+
- `prepare({targetRequest, signal, onEvent})` returns the selected SDK version and
|
|
24
|
+
package root. Neither operation builds or imports application services.
|
|
25
|
+
- `build(request)` assembles one selected release and its explicit dependencies.
|
|
26
|
+
- `verify({artifact, targetRequest, signal, onEvent})` explicitly reads its semantic
|
|
27
|
+
manifest and observes that the listed output files exist. It does not execute
|
|
28
|
+
a host or establish native behavior. Build does not implicitly run verification.
|
|
29
|
+
- `run()` reports `ARCANE_NATIVE_RUN_UNSUPPORTED`; portable is not an executable.
|
|
30
|
+
|
|
31
|
+
Use the existing `createNativeBuildPlan()` / `executeNativeBuildPlan()` public
|
|
32
|
+
contract. Build receives `appDescriptor`, `appReleaseRoot`, `release`,
|
|
33
|
+
`readReleaseFile`, `dependencies`, `minimumCoreVersion`, `protectedRoots`,
|
|
34
|
+
`outputRoot`, `targetRequest`, `selectedSdk`, `signal` and `onEvent`. Dependencies provide their
|
|
35
|
+
`appId`, `releaseRoot`, `release` and `readReleaseFile`. The selected release's
|
|
36
|
+
`files` inventory is copied completely through its supplied reader. Direct
|
|
37
|
+
callers may supply the release root for ordinary file reads instead.
|
|
38
|
+
|
|
39
|
+
Both the current flat signing selection and the CLI's nested signing selection
|
|
40
|
+
are accepted by the planning boundary. Portable assembly adds no signing,
|
|
41
|
+
receipts, hashes, content limits or security policy. The package-relative source
|
|
42
|
+
root comes from the provider module's own location, not `toolchainRoot` or an
|
|
43
|
+
Arcane OS machine bundle.
|
|
44
|
+
|
|
45
|
+
## Output and host contract
|
|
46
|
+
|
|
47
|
+
Each build creates a fresh `arcane-portable-*` directory beneath `outputRoot`.
|
|
48
|
+
The result is `{app, target, manifest}`, with its actual directory at
|
|
49
|
+
`target.rootDir`. Existing artifacts are preserved. Cancellation or a failed
|
|
50
|
+
assembly removes only that build's newly created directory. Source/release roots
|
|
51
|
+
cannot be overwritten with generated output.
|
|
52
|
+
|
|
53
|
+
The selected app is copied unchanged under `app/`. Dependencies are copied
|
|
54
|
+
unchanged under `dependencies/<index>/`; the manifest relates each directory to
|
|
55
|
+
its actual app identifier and selected release. No HTML, payload, line ending,
|
|
56
|
+
application import, or base URL is rewritten by this provider. Dependency
|
|
57
|
+
navigation and service imports remain explicit host/application composition;
|
|
58
|
+
the payload does not invent cross-app routes.
|
|
59
|
+
|
|
60
|
+
`node_modules/arcane-os/` contains the SDK consumer source, runtime, schemas,
|
|
61
|
+
commands, package metadata and legal files. Its installed published runtime
|
|
62
|
+
dependency tree is included with licenses and actual nested dependency
|
|
63
|
+
resolution. The generated Core entry uses the ordinary public
|
|
64
|
+
`arcane-os/core/host` import. Application service modules can use those public
|
|
65
|
+
SDK exports through Node's normal package resolution. Existing app-owned
|
|
66
|
+
`node_modules` content remains authoritative where the selected app supplies it;
|
|
67
|
+
its own dependency completeness remains part of that app's selected release.
|
|
68
|
+
For the direct installed browser layout, the ordinary native selection also
|
|
69
|
+
supplies `selectedSdk:{packageRoot,packageSource}` from the application's actual
|
|
70
|
+
installed SDK. Assembly completes its browser-only package directory with
|
|
71
|
+
missing files and dependencies from that selected package, preserving every
|
|
72
|
+
selected file. It never fills an older projection with the builder's newer
|
|
73
|
+
package metadata. An already-supplied `package.json` remains app-owned, without
|
|
74
|
+
replacement or automatic completion. `manifest.appSdk` identifies this app
|
|
75
|
+
package separately from the builder SDK. Low-level callers composing services
|
|
76
|
+
with a partial direct SDK projection must supply the actual selected package
|
|
77
|
+
source; a file inventory alone does not identify its version or Node exports.
|
|
78
|
+
No dependency installer or package script runs. No OS model, Shell, Provisioner,
|
|
79
|
+
catalog, prompt, or native compiler is added.
|
|
80
|
+
|
|
81
|
+
The semantic `arcane-native.json` names the web root, selected start URL, Node
|
|
82
|
+
Core entry, classic client, dependencies and complete file inventory. A native
|
|
83
|
+
host must:
|
|
84
|
+
|
|
85
|
+
1. Mount `app/` at the root of its local application URL origin, preserving the
|
|
86
|
+
selected start path and all navigable documents.
|
|
87
|
+
2. Inject `runtime/arcane-api.js` at document creation. It is produced by the
|
|
88
|
+
canonical `createCoreClassicSource()` generator, not a copied OS client.
|
|
89
|
+
3. Start `runtime/arcane-core.mjs` with the host's supported Node runtime and
|
|
90
|
+
connect the existing framed stdin/stdout transport.
|
|
91
|
+
4. Close input and await Core's drain before terminating the child process.
|
|
92
|
+
|
|
93
|
+
The selected release must contain one shared `sdk/event-manager.mjs` path,
|
|
94
|
+
possibly nested under its SDK payload directory, or the direct installed
|
|
95
|
+
`node_modules/<package-or-alias>/browser-runtime/event-manager.mjs` path.
|
|
96
|
+
The classic client imports that
|
|
97
|
+
same owner with an origin-root URL, including on nested pages. It does not create
|
|
98
|
+
a second event implementation. A plain file-URL launch without this host mapping
|
|
99
|
+
is not the native serving contract. No host is launched during packaging.
|
|
100
|
+
|
|
101
|
+
## Explicit application services
|
|
102
|
+
|
|
103
|
+
Pass native service modules already included in the selected app release:
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
import {createPortableNativeProvider} from 'arcane-os/native/portable-provider';
|
|
107
|
+
|
|
108
|
+
const nativeBuilder = createPortableNativeProvider({
|
|
109
|
+
services: [{module:'native/moon-ledger.mjs', options:{ledgerName:'Cheese debts'}}]
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Each module default-exports a synchronous factory returning the existing Core
|
|
114
|
+
service definition. Its options must be JSON-serializable data. Module imports
|
|
115
|
+
and factories run only when the generated Core entry is launched, never during
|
|
116
|
+
packaging. Put asynchronous initialization in `start()`, not a top-level await
|
|
117
|
+
or asynchronous factory, so unrelated services and dispatcher readiness remain
|
|
118
|
+
independent. Factories own product configuration and any service dependencies;
|
|
119
|
+
the packager does not discover, install or execute them.
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
// native/moon-ledger.mjs, owned by the application
|
|
123
|
+
export default function createMoonLedger({ledgerName}) {
|
|
124
|
+
return {
|
|
125
|
+
name: 'moon-ledger',
|
|
126
|
+
methods: {
|
|
127
|
+
'moon.describe': function describeLedger() {
|
|
128
|
+
return {name:ledgerName, message:'The cheese debts are astronomical.'};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`startCoreHost({application, version, services, input, output, onError, signal})`
|
|
136
|
+
is also available directly from `arcane-os/core/host`. It returns the existing
|
|
137
|
+
stdio owner's `{runtime, closed, close}` without another lifecycle controller.
|
|
138
|
+
An optional abort signal invokes that same idempotent drain. Dispatcher readiness
|
|
139
|
+
does not wait for service/model readiness. Accepted `lifetime:'service'` work
|
|
140
|
+
survives renderer cancellation and is drained before shutdown. Errors remain
|
|
141
|
+
observable through `onError` and `closed`; no process is killed or forcibly exited.
|
|
142
|
+
See [the Core runtime contract](core-runtime.md) for request and service lifetime
|
|
143
|
+
details and [the client contract](core-client.md) for native bridge integration.
|
|
144
|
+
|
|
145
|
+
This source increment supplies portable assembly and service composition.
|
|
146
|
+
Desktop WebView executables, platform child-process ownership, Android dispatch
|
|
147
|
+
and executable build toolchains require their own completed adapters. Published
|
|
148
|
+
consumers must adopt the numeric package that includes this increment; a source
|
|
149
|
+
checkout or a successful source push is not that public package authority.
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Native Core runtime
|
|
2
|
+
|
|
3
|
+
The SDK owns the reusable native dispatcher and framed stdio transport.
|
|
4
|
+
The composing application or native host supplies its identity, version and
|
|
5
|
+
services. Product prompts, model selections, filesystem locations, host
|
|
6
|
+
privileges and application policy remain with that owner.
|
|
7
|
+
|
|
8
|
+
```js
|
|
9
|
+
import {createCoreRuntime} from 'arcane-os/core/runtime';
|
|
10
|
+
import {startCoreStdio} from 'arcane-os/core/stdio';
|
|
11
|
+
import catalogStore from './catalog-store.mjs';
|
|
12
|
+
|
|
13
|
+
const runtime = createCoreRuntime({
|
|
14
|
+
application: {id: 'moon-cheese-catalog', name: 'Moon Cheese Catalog'},
|
|
15
|
+
version: '1.0.0',
|
|
16
|
+
services: [{
|
|
17
|
+
name: 'catalog',
|
|
18
|
+
methods: {
|
|
19
|
+
'catalog.save': {
|
|
20
|
+
lifetime: 'service',
|
|
21
|
+
async handle(record) {
|
|
22
|
+
// catalogStore is supplied by this application's native owner.
|
|
23
|
+
return catalogStore.save(record);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
async drain() {
|
|
28
|
+
await catalogStore.flush();
|
|
29
|
+
},
|
|
30
|
+
async dispose() {
|
|
31
|
+
await catalogStore.close();
|
|
32
|
+
}
|
|
33
|
+
}]
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const transport = startCoreStdio({runtime});
|
|
37
|
+
await transport.closed;
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The example's store is an application dependency, not an SDK-created database.
|
|
41
|
+
The runtime passes each request's `parameters` to its registered handler
|
|
42
|
+
unchanged. It preserves complete results and diagnostic errors; it neither
|
|
43
|
+
selects product data nor supplies replacement results.
|
|
44
|
+
|
|
45
|
+
## Registration and startup
|
|
46
|
+
|
|
47
|
+
`createCoreRuntime({application, version, services})` creates a dispatcher.
|
|
48
|
+
Supply the application's real descriptor and host release version. The built-in
|
|
49
|
+
`app.current` and `version.current` methods return those values; `system.ping`
|
|
50
|
+
returns `{ok:true}`. A ping establishes dispatcher responsiveness, not model,
|
|
51
|
+
service or application readiness.
|
|
52
|
+
|
|
53
|
+
`runtime.registerService({name, methods, start, drain, dispose})` registers one
|
|
54
|
+
native service. Method names are complete RPC names, such as `catalog.save`.
|
|
55
|
+
A method is either a handler function or `{handle, lifetime}`. A handler function
|
|
56
|
+
has request lifetime by default. Duplicate service or method names, including
|
|
57
|
+
the three built-in methods, are reported rather than replacing another owner.
|
|
58
|
+
Registration is a native composition API, not a browser command.
|
|
59
|
+
|
|
60
|
+
Registration returns `{ready, current}`. Reading `ready` starts that service and
|
|
61
|
+
returns its shared startup promise; `current()` exposes its state and actual
|
|
62
|
+
error. `runtime.start()` starts all registered services independently and returns
|
|
63
|
+
the current dispatcher state without waiting for them. A service registered
|
|
64
|
+
after startup starts independently as well. A method waits only for its own
|
|
65
|
+
service's startup before invoking its handler. State errors use the shared
|
|
66
|
+
complete diagnostic serialization; the startup promise retains its original
|
|
67
|
+
rejection value.
|
|
68
|
+
|
|
69
|
+
Each lifecycle hook receives `{application, service, emit}`. Each request handler
|
|
70
|
+
receives `(parameters, {application, service, emit, requestId, signal})`, with
|
|
71
|
+
`this` bound to its service definition. A hook or handler may publish a complete
|
|
72
|
+
service-owned event through `emit(event, data)`.
|
|
73
|
+
|
|
74
|
+
## State and frames
|
|
75
|
+
|
|
76
|
+
`runtime.current()` returns dispatcher state, application, version, per-service
|
|
77
|
+
state/errors and active request identities, methods and lifetimes.
|
|
78
|
+
`runtime.subscribe(listener, {emitCurrent:true, signal})` immediately replays the
|
|
79
|
+
current state and then observes changes through the canonical SDK event owner.
|
|
80
|
+
It returns an unsubscribe function. `runtime.onFrame(listener, {signal})`
|
|
81
|
+
observes outgoing protocol frames and also returns an unsubscribe function.
|
|
82
|
+
Attach transport listeners before starting the runtime to observe initial frames.
|
|
83
|
+
|
|
84
|
+
The shared `arcane-os/core/contracts` module owns `CORE_PROTOCOL` (`arcane/1`),
|
|
85
|
+
`CoreError` and `serializeCoreError`. Requests are:
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
{
|
|
89
|
+
protocol: 'arcane/1',
|
|
90
|
+
type: 'request',
|
|
91
|
+
id: 'request-1',
|
|
92
|
+
method: 'catalog.save',
|
|
93
|
+
parameters: {name: 'Lunar cheddar', notes: 'Keep the complete supplied text.'}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`await runtime.handle(frame)` returns and emits the correlated response:
|
|
98
|
+
`{protocol, type:'response', id, ok:true, result, time}` or
|
|
99
|
+
`{protocol, type:'response', id, ok:false, error, time}`. Framing and method
|
|
100
|
+
availability errors remain observable; unavailable services are not replaced
|
|
101
|
+
with browser behavior.
|
|
102
|
+
|
|
103
|
+
Events have `{protocol, type:'event', event, data, time}`:
|
|
104
|
+
|
|
105
|
+
- `core.ready`: the dispatcher is connected; service/model readiness is separate.
|
|
106
|
+
- `core.state`: the current complete runtime state.
|
|
107
|
+
- `core.service.state`: a service's name, state and serialized error when present.
|
|
108
|
+
- `core.error`: a lifecycle or transport diagnostic.
|
|
109
|
+
|
|
110
|
+
A newly connected document may send
|
|
111
|
+
`{protocol:'arcane/1', type:'control', control:'runtime.replay'}`. The runtime
|
|
112
|
+
publishes one current snapshot: `core.ready` only when the dispatcher is ready,
|
|
113
|
+
then `core.state`, followed by `core.service.state` for every registered service.
|
|
114
|
+
Complete service errors and active request state are retained. Replay does not
|
|
115
|
+
start or restart services, dispatch a request, poll readiness or create a second
|
|
116
|
+
lifecycle owner. The runtime completes that snapshot before publishing lifecycle
|
|
117
|
+
changes triggered reentrantly by its listeners. A draining runtime reports its
|
|
118
|
+
draining state rather than announcing readiness.
|
|
119
|
+
|
|
120
|
+
Service states are `registered`, `starting`, `ready`, `failed`, `draining` and
|
|
121
|
+
`closed`. Dispatcher states are `created`, `ready`, `draining` and `closed`.
|
|
122
|
+
Complete diagnostics belong in the host's developer-facing error handling, not
|
|
123
|
+
in ordinary chat history or a fabricated conversation turn.
|
|
124
|
+
|
|
125
|
+
## Cancellation and shutdown
|
|
126
|
+
|
|
127
|
+
The `request.cancel` control names `requestId`; `requests.cancelAll` applies to
|
|
128
|
+
active request-lifetime operations. Cancellation is cooperative. The handler's
|
|
129
|
+
signal is aborted. An otherwise successful cancelled operation returns
|
|
130
|
+
`REQUEST_ABORTED` after its owned work settles; a handler's own rejection remains
|
|
131
|
+
the actual reported error. If startup is still pending, cancellation is checked before
|
|
132
|
+
the handler runs. A service that does not finish or respond to cancellation is
|
|
133
|
+
still unfinished; the SDK does not report a forced timeout as successful cleanup.
|
|
134
|
+
|
|
135
|
+
Use `lifetime:'service'` for accepted work, such as a save, that must survive a
|
|
136
|
+
renderer cancellation or disconnect. These operations are not aborted by the
|
|
137
|
+
request cancellation controls or by normal Core shutdown. The service still
|
|
138
|
+
owns its persistence, queueing and failure semantics.
|
|
139
|
+
|
|
140
|
+
`runtime.close()` is idempotent and retains the same completion promise. It stops
|
|
141
|
+
accepting new requests, cancels request-lifetime operations, waits for accepted
|
|
142
|
+
responses, then drains and disposes services. Independent services close
|
|
143
|
+
concurrently; each service's startup, drain and dispose remain ordered.
|
|
144
|
+
Shutdown errors remain complete and reject the close promise. A failed drain
|
|
145
|
+
does not prevent the service's dispose hook from running.
|
|
146
|
+
|
|
147
|
+
The final closed state is published before event subscriptions are released.
|
|
148
|
+
`current()` remains available afterward; the disposed runtime is not restarted.
|
|
149
|
+
The host must observe completion before terminating Core. The SDK does not call
|
|
150
|
+
`process.exit()`, kill a host or claim durability after an operating-system crash
|
|
151
|
+
or a host-forced termination.
|
|
152
|
+
|
|
153
|
+
## Stdio transport
|
|
154
|
+
|
|
155
|
+
`startCoreStdio({runtime, input, output, onError})` attaches a runtime and starts
|
|
156
|
+
it. Streams default to `process.stdin` and `process.stdout`; diagnostics default
|
|
157
|
+
to `console.error`, leaving stdout for protocol frames. It returns
|
|
158
|
+
`{runtime, closed, close}`. Observe `closed` for final completion or failure.
|
|
159
|
+
|
|
160
|
+
Frames use the existing `Content-Length` header followed by UTF-8 JSON. Length
|
|
161
|
+
is transport-local framing only. `encodeCoreFrame(frame)` and
|
|
162
|
+
`createCoreFrameDecoder(onFrame)` are also exported for host adapters. The
|
|
163
|
+
decoder accepts fragmented or multiple frames through `push(chunk)`;
|
|
164
|
+
`finish()` reports an incomplete final frame rather than dropping it.
|
|
165
|
+
|
|
166
|
+
Requests dispatch concurrently. Outgoing frames preserve their queued order and
|
|
167
|
+
wait for each writable-stream callback. Input EOF starts runtime shutdown, waits
|
|
168
|
+
for accepted service work and queued output, and resolves `closed` afterward.
|
|
169
|
+
An input stream closed without its normal EOF event also initiates this drain;
|
|
170
|
+
an already ended or destroyed input is handled when attached. Transport failures
|
|
171
|
+
reach `onError` and reject `closed`. The transport detaches
|
|
172
|
+
its listeners and pauses its input; it does not end caller-owned output streams
|
|
173
|
+
or take ownership of the host process.
|
|
174
|
+
|
|
175
|
+
## Platform and delivery boundary
|
|
176
|
+
|
|
177
|
+
These modules use portable Node and JavaScript facilities for Windows, Linux
|
|
178
|
+
and macOS native hosts. Android requires its native transport/host adaptation;
|
|
179
|
+
the module is not a Node runtime embedded inside a WebView. Browser callers use
|
|
180
|
+
the separate [Core client](core-client.md).
|
|
181
|
+
|
|
182
|
+
The dispatcher/transport sources are one part of Core extraction. They do not
|
|
183
|
+
by themselves replace existing native host bundles, platform adapters, provider
|
|
184
|
+
loading, packaging or OS composition. Those owners must adopt the selected
|
|
185
|
+
published SDK boundary and preserve their real shutdown and service lifecycles.
|