@cynodia/axiom-server 0.6.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/deps.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Everything this package uses from the rest of Axiom, re-exported through one module.
3
+ *
4
+ * Keeping the imports in one place makes the dependency surface of the authoritative
5
+ * runtime explicit: it is the semantic engine and nothing else. No transport, no database
6
+ * driver and no host API appears here.
7
+ */
8
+ export type { ActionDef, AnyNode, ApplicationIR, Authority, ConstraintDef, EntityDef, Expression, FieldDef, FieldId, LiteralValue, NodeId, ServerIR, StateDef, TransitionConstraintDef, TypeRef, } from '@cynodia/axiom-core';
9
+ export type { ActionOutcome, ActionResult, RuntimeDiagnostic, RuntimeDiagnosticCode, } from '@cynodia/axiom-runtime';
10
+ export { PRINCIPAL, RUNTIME_DIAGNOSTIC_CODES, SERVER_IR_CONTRACT } from './runtime-deps.js';
11
+ //# sourceMappingURL=deps.d.ts.map
package/dist/deps.js ADDED
@@ -0,0 +1 @@
1
+ export { PRINCIPAL, RUNTIME_DIAGNOSTIC_CODES, SERVER_IR_CONTRACT } from './runtime-deps.js';
package/dist/host.d.ts ADDED
@@ -0,0 +1,57 @@
1
+ import type { FieldId, LiteralValue, NodeId, RuntimeDiagnostic } from './deps.js';
2
+ import type { Credential } from './protocol.js';
3
+ /**
4
+ * A caller, as the authority sees it: a record keyed by the field ids of the graph's
5
+ * principal entity. It is never application state — it describes who is asking.
6
+ */
7
+ export type PrincipalRecord = Record<FieldId, LiteralValue>;
8
+ export interface ExecutionContext {
9
+ /** The resolved caller, or `null` for an anonymous request. */
10
+ principal: PrincipalRecord | null;
11
+ /** The credential the caller presented, for host-level logging. Never semantic. */
12
+ credential?: Credential;
13
+ requestId?: string;
14
+ }
15
+ /** What the authoritative runtime records about an execution, for observability. */
16
+ export interface ServerEvent {
17
+ kind: 'invoke' | 'snapshot' | 'reject' | 'conflict' | 'replay';
18
+ actionId?: NodeId;
19
+ /** The principal's identity field, when the graph declares one. Never the whole record. */
20
+ principal?: LiteralValue;
21
+ requestId?: string;
22
+ ok?: boolean;
23
+ /** Milliseconds spent in the authoritative runtime. */
24
+ durationMs?: number;
25
+ revision?: number;
26
+ diagnostics?: RuntimeDiagnostic[];
27
+ /** States the transaction committed. */
28
+ committed?: NodeId[];
29
+ }
30
+ /**
31
+ * Everything the authoritative runtime needs from its environment.
32
+ *
33
+ * The semantic engine reads nothing from globals, exactly as the client runtime does not.
34
+ * No transport, no database driver and no host API appears in the semantics.
35
+ */
36
+ export interface ServerHost {
37
+ now(): string;
38
+ uuid(): string;
39
+ /**
40
+ * Resolves an opaque credential to a caller. Returning `null` means anonymous, which an
41
+ * authorization rule may still accept or refuse.
42
+ *
43
+ * This is authentication, and it is deliberately the host's business. Axiom 0.6 provides
44
+ * no authentication provider of its own.
45
+ */
46
+ authenticate?(credential: Credential): Promise<PrincipalRecord | null> | PrincipalRecord | null;
47
+ /** Structured execution information. An application implements no logging of its own. */
48
+ report?(event: ServerEvent): void;
49
+ }
50
+ /** A host backed by real time and real identifiers, with no authentication. */
51
+ export declare function createServerHost(overrides?: Partial<ServerHost>): ServerHost;
52
+ /**
53
+ * A deterministic host, for conformance runs and tests. `now` and `uuid` count rather than
54
+ * varying, so an expected result is stable.
55
+ */
56
+ export declare function createDeterministicServerHost(overrides?: Partial<ServerHost>): ServerHost;
57
+ //# sourceMappingURL=host.d.ts.map
package/dist/host.js ADDED
@@ -0,0 +1,28 @@
1
+ /** A host backed by real time and real identifiers, with no authentication. */
2
+ export function createServerHost(overrides = {}) {
3
+ return {
4
+ now: () => new Date().toISOString(),
5
+ uuid: () => typeof globalThis.crypto?.randomUUID === 'function'
6
+ ? globalThis.crypto.randomUUID()
7
+ : `id-${Date.now().toString(16)}-${Math.floor(Math.random() * 1e9).toString(16)}`,
8
+ ...overrides,
9
+ };
10
+ }
11
+ /**
12
+ * A deterministic host, for conformance runs and tests. `now` and `uuid` count rather than
13
+ * varying, so an expected result is stable.
14
+ */
15
+ export function createDeterministicServerHost(overrides = {}) {
16
+ let counter = 0;
17
+ return {
18
+ now: () => {
19
+ counter += 1;
20
+ return `2026-01-01T00:00:${String(counter).padStart(2, '0')}.000Z`;
21
+ },
22
+ uuid: () => {
23
+ counter += 1;
24
+ return `id-${counter}`;
25
+ },
26
+ ...overrides,
27
+ };
28
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The authoritative half of Axiom.
3
+ *
4
+ * A client requests semantic actions; this executes them against state it owns, with the
5
+ * same semantic engine and therefore the same transaction guarantees. HTTP, SQLite and
6
+ * Node are implementation details of the adapters here — none of them appears in an
7
+ * ApplicationGraph.
8
+ */
9
+ export type { ServerIR, ServerIRContract } from '@cynodia/axiom-core';
10
+ export { SERVER_IR_CONTRACT, PRINCIPAL } from '@cynodia/axiom-core';
11
+ export * from './protocol.js';
12
+ export * from './persistence.js';
13
+ export * from './sqlite-persistence.js';
14
+ export * from './host.js';
15
+ export * from './server.js';
16
+ export * from './transport.js';
17
+ export * from './node-host.js';
18
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { SERVER_IR_CONTRACT, PRINCIPAL } from '@cynodia/axiom-core';
2
+ export * from './protocol.js';
3
+ export * from './persistence.js';
4
+ export * from './sqlite-persistence.js';
5
+ export * from './host.js';
6
+ export * from './server.js';
7
+ export * from './transport.js';
8
+ export * from './node-host.js';
@@ -0,0 +1,22 @@
1
+ import type { AxiomServer } from './server.js';
2
+ /**
3
+ * The reference Node host.
4
+ *
5
+ * It is infrastructure, not semantics: it reads a body, hands it to the authority, and
6
+ * writes the answer back. There is one endpoint, and it is the same endpoint for every
7
+ * application — no application declares a route, a verb or a handler.
8
+ */
9
+ export interface NodeHostOptions {
10
+ server: AxiomServer;
11
+ port?: number;
12
+ /** The single semantic endpoint. */
13
+ path?: string;
14
+ }
15
+ export interface RunningNodeHost {
16
+ /** The port actually bound, which matters when `port: 0` was requested. */
17
+ port: number;
18
+ url: string;
19
+ close(): Promise<void>;
20
+ }
21
+ export declare function serveOverHttp(options: NodeHostOptions): Promise<RunningNodeHost>;
22
+ //# sourceMappingURL=node-host.d.ts.map
@@ -0,0 +1,63 @@
1
+ import { createServer } from 'node:http';
2
+ import { dispatch } from './transport.js';
3
+ const MAX_BODY_BYTES = 1024 * 1024;
4
+ async function readBody(request) {
5
+ const chunks = [];
6
+ let size = 0;
7
+ for await (const chunk of request) {
8
+ const buffer = chunk;
9
+ size += buffer.length;
10
+ if (size > MAX_BODY_BYTES) {
11
+ throw new Error('Request body is too large');
12
+ }
13
+ chunks.push(buffer);
14
+ }
15
+ if (chunks.length === 0) {
16
+ return null;
17
+ }
18
+ return JSON.parse(Buffer.concat(chunks).toString('utf8'));
19
+ }
20
+ export async function serveOverHttp(options) {
21
+ const path = options.path ?? '/axiom';
22
+ await options.server.start();
23
+ const http = createServer((request, response) => {
24
+ void (async () => {
25
+ if (request.method !== 'POST' || (request.url ?? '').split('?')[0] !== path) {
26
+ response.writeHead(404, { 'content-type': 'application/json' });
27
+ response.end(JSON.stringify({ kind: 'error', diagnostics: [] }));
28
+ return;
29
+ }
30
+ try {
31
+ const answer = await dispatch(options.server, await readBody(request));
32
+ const body = JSON.stringify(answer);
33
+ response.writeHead(200, { 'content-type': 'application/json' });
34
+ response.end(body);
35
+ }
36
+ catch (error) {
37
+ response.writeHead(400, { 'content-type': 'application/json' });
38
+ response.end(JSON.stringify({
39
+ kind: 'error',
40
+ diagnostics: [
41
+ {
42
+ code: 'MALFORMED_REQUEST',
43
+ message: error instanceof Error ? error.message : String(error),
44
+ severity: 'error',
45
+ },
46
+ ],
47
+ }));
48
+ }
49
+ })();
50
+ });
51
+ await new Promise((resolve) => {
52
+ http.listen(options.port ?? 0, '127.0.0.1', resolve);
53
+ });
54
+ const address = http.address();
55
+ const port = typeof address === 'object' && address ? address.port : (options.port ?? 0);
56
+ return {
57
+ port,
58
+ url: `http://127.0.0.1:${port}${path}`,
59
+ close: () => new Promise((resolve, reject) => {
60
+ http.close((error) => (error ? reject(error) : resolve()));
61
+ }),
62
+ };
63
+ }
@@ -0,0 +1,58 @@
1
+ import type { NodeId } from './deps.js';
2
+ /**
3
+ * Where committed authoritative state survives.
4
+ *
5
+ * The contract is deliberately narrow, and deliberately **atomic**: a semantic Axiom
6
+ * transaction that writes several states must persist as one unit. It must never be
7
+ * possible to find an order inserted and one of its two stock debits missing, because that
8
+ * is one transaction.
9
+ *
10
+ * No SQL, no schema and no query language appears in an ApplicationGraph or in this
11
+ * interface. An adapter stores serialized semantic values against state ids; projecting
12
+ * them onto a relational schema is deliberately future work.
13
+ */
14
+ export interface PersistedState {
15
+ stateId: NodeId;
16
+ value: unknown;
17
+ /** Incremented on every committed write to this state. */
18
+ revision: number;
19
+ }
20
+ export interface PersistenceCommit {
21
+ /** The states this transaction writes, with the values it proposes. */
22
+ writes: Array<{
23
+ stateId: NodeId;
24
+ value: unknown;
25
+ }>;
26
+ /**
27
+ * The revision each written state had when the transaction began. A mismatch means
28
+ * something else committed in between, and the commit MUST be refused rather than
29
+ * overwrite it.
30
+ */
31
+ expected: Record<NodeId, number>;
32
+ }
33
+ export interface CommitOutcome {
34
+ committed: boolean;
35
+ /** The new revision of the store when committed. */
36
+ revision: number;
37
+ /** States whose revision no longer matched, when refused. */
38
+ conflicts: NodeId[];
39
+ }
40
+ export interface PersistenceAdapter {
41
+ /** Everything committed so far. Called once, when the authority starts. */
42
+ load(): Promise<PersistedState[]>;
43
+ /**
44
+ * Applies a whole transaction, or none of it. An adapter MUST NOT apply a subset, and
45
+ * MUST refuse when any expected revision no longer matches.
46
+ */
47
+ commit(commit: PersistenceCommit): Promise<CommitOutcome>;
48
+ /** The current store revision, for conflict detection and observation. */
49
+ revision(): Promise<number>;
50
+ close?(): Promise<void>;
51
+ }
52
+ /**
53
+ * In-memory persistence. It implements the complete authoritative transaction model — the
54
+ * atomicity and the revision check included — so a conformance run or a test exercises the
55
+ * same semantics a durable adapter does, deterministically and without a filesystem.
56
+ */
57
+ export declare function createMemoryPersistence(seed?: PersistedState[]): PersistenceAdapter;
58
+ //# sourceMappingURL=persistence.d.ts.map
@@ -0,0 +1,38 @@
1
+ /**
2
+ * In-memory persistence. It implements the complete authoritative transaction model — the
3
+ * atomicity and the revision check included — so a conformance run or a test exercises the
4
+ * same semantics a durable adapter does, deterministically and without a filesystem.
5
+ */
6
+ export function createMemoryPersistence(seed = []) {
7
+ const values = new Map();
8
+ for (const entry of seed) {
9
+ values.set(entry.stateId, { ...entry, value: structuredClone(entry.value) });
10
+ }
11
+ let revision = seed.reduce((highest, entry) => Math.max(highest, entry.revision), 0);
12
+ return {
13
+ async load() {
14
+ return [...values.values()].map((entry) => ({ ...entry, value: structuredClone(entry.value) }));
15
+ },
16
+ async commit(commit) {
17
+ const conflicts = commit.writes
18
+ .map((write) => write.stateId)
19
+ .filter((stateId) => (values.get(stateId)?.revision ?? 0) !== (commit.expected[stateId] ?? 0));
20
+ if (conflicts.length > 0) {
21
+ return { committed: false, revision, conflicts };
22
+ }
23
+ // Nothing is written until every expectation has held: all of it, or none.
24
+ revision += 1;
25
+ for (const write of commit.writes) {
26
+ values.set(write.stateId, {
27
+ stateId: write.stateId,
28
+ value: structuredClone(write.value),
29
+ revision,
30
+ });
31
+ }
32
+ return { committed: true, revision, conflicts: [] };
33
+ },
34
+ async revision() {
35
+ return revision;
36
+ },
37
+ };
38
+ }
@@ -0,0 +1,72 @@
1
+ import type { NodeId, RuntimeDiagnostic } from './deps.js';
2
+ /**
3
+ * The semantic protocol between a client runtime and an authority.
4
+ *
5
+ * A client requests **semantic operations** — "invoke action X with arguments Y" — never a
6
+ * mutation program. The authority resolves the action from its own IR and decides. Nothing
7
+ * here mentions HTTP, and nothing in an ApplicationGraph mentions a route or a verb.
8
+ */
9
+ export declare const PROTOCOL_VERSION = "axiom.protocol.v1";
10
+ /** Opaque credential material. The host, not the semantic runtime, interprets it. */
11
+ export type Credential = string | null;
12
+ export interface SnapshotRequest {
13
+ kind: 'snapshot';
14
+ protocol: typeof PROTOCOL_VERSION;
15
+ credential?: Credential;
16
+ /** When given, only states changed after this revision are returned. */
17
+ sinceRevision?: number;
18
+ }
19
+ export interface InvokeRequest {
20
+ kind: 'invoke';
21
+ protocol: typeof PROTOCOL_VERSION;
22
+ actionId: NodeId;
23
+ /** Keyed by action parameter id. Untyped input: the authority validates it. */
24
+ arguments?: Record<string, unknown>;
25
+ credential?: Credential;
26
+ /**
27
+ * A caller-chosen key that makes a retry safe. The authority returns the recorded
28
+ * response for a key it has already executed rather than executing again.
29
+ */
30
+ requestId?: string;
31
+ }
32
+ export type ServerRequest = SnapshotRequest | InvokeRequest;
33
+ /** The authoritative value of every observable state the caller may see. */
34
+ export interface StateSnapshot {
35
+ revision: number;
36
+ states: Record<NodeId, unknown>;
37
+ }
38
+ export interface InvokeResponse {
39
+ kind: 'result';
40
+ protocol: typeof PROTOCOL_VERSION;
41
+ ok: boolean;
42
+ /** Axiom diagnostics, unchanged: the same codes and details a local failure produces. */
43
+ diagnostics: RuntimeDiagnostic[];
44
+ /** Authoritative values after the transaction settled, for the states it touched. */
45
+ changes: Record<NodeId, unknown>;
46
+ revision: number;
47
+ requestId?: string;
48
+ /** True when this response was replayed for a repeated `requestId`. */
49
+ replayed?: boolean;
50
+ }
51
+ export interface SnapshotResponse {
52
+ kind: 'snapshot';
53
+ protocol: typeof PROTOCOL_VERSION;
54
+ snapshot: StateSnapshot;
55
+ }
56
+ /** A failure of the boundary itself, rather than of an application rule. */
57
+ export interface ErrorResponse {
58
+ kind: 'error';
59
+ protocol: typeof PROTOCOL_VERSION;
60
+ diagnostics: RuntimeDiagnostic[];
61
+ }
62
+ export type ServerResponse = InvokeResponse | SnapshotResponse | ErrorResponse;
63
+ /**
64
+ * How a client reaches an authority. The first implementation is in-process and the second
65
+ * is HTTP; neither is visible to application semantics, so a later WebSocket, worker or IPC
66
+ * transport changes nothing in a graph.
67
+ */
68
+ export interface TransportAdapter {
69
+ send(request: ServerRequest): Promise<ServerResponse>;
70
+ }
71
+ export declare function isServerRequest(value: unknown): value is ServerRequest;
72
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The semantic protocol between a client runtime and an authority.
3
+ *
4
+ * A client requests **semantic operations** — "invoke action X with arguments Y" — never a
5
+ * mutation program. The authority resolves the action from its own IR and decides. Nothing
6
+ * here mentions HTTP, and nothing in an ApplicationGraph mentions a route or a verb.
7
+ */
8
+ export const PROTOCOL_VERSION = 'axiom.protocol.v1';
9
+ export function isServerRequest(value) {
10
+ if (typeof value !== 'object' || value === null) {
11
+ return false;
12
+ }
13
+ const candidate = value;
14
+ return (candidate.protocol === PROTOCOL_VERSION &&
15
+ (candidate.kind === 'invoke' || candidate.kind === 'snapshot'));
16
+ }
@@ -0,0 +1,3 @@
1
+ export { PRINCIPAL, SERVER_IR_CONTRACT } from '@cynodia/axiom-core';
2
+ export { RUNTIME_DIAGNOSTIC_CODES } from '@cynodia/axiom-runtime';
3
+ //# sourceMappingURL=runtime-deps.d.ts.map
@@ -0,0 +1,2 @@
1
+ export { PRINCIPAL, SERVER_IR_CONTRACT } from '@cynodia/axiom-core';
2
+ export { RUNTIME_DIAGNOSTIC_CODES } from '@cynodia/axiom-runtime';
@@ -0,0 +1,58 @@
1
+ import type { NodeId, ServerIR } from '@cynodia/axiom-core';
2
+ import type { MutationLogEntry } from '@cynodia/axiom-runtime';
3
+ import type { PersistenceAdapter } from './persistence.js';
4
+ import type { ServerHost } from './host.js';
5
+ import type { ServerRequest, ServerResponse, StateSnapshot } from './protocol.js';
6
+ /**
7
+ * Diagnostic codes the authority adds to the runtime vocabulary. They describe failures of
8
+ * the boundary, not of an application rule.
9
+ */
10
+ export declare const SERVER_DIAGNOSTIC_CODES: {
11
+ /** The request named an action this authority does not execute. */
12
+ readonly UNKNOWN_SERVER_ACTION: "UNKNOWN_SERVER_ACTION";
13
+ /** An argument did not conform to its declared parameter type. */
14
+ readonly ARGUMENT_TYPE_MISMATCH: "ARGUMENT_TYPE_MISMATCH";
15
+ /** The caller may not invoke this action. */
16
+ readonly AUTHORIZATION_DENIED: "AUTHORIZATION_DENIED";
17
+ /** Another transaction committed the same state first; nothing was applied. */
18
+ readonly CONCURRENCY_CONFLICT: "CONCURRENCY_CONFLICT";
19
+ /** The request itself was malformed, or spoke an unknown protocol. */
20
+ readonly MALFORMED_REQUEST: "MALFORMED_REQUEST";
21
+ /** The authority could not be reached, or did not answer. */
22
+ readonly AUTHORITY_UNREACHABLE: "AUTHORITY_UNREACHABLE";
23
+ };
24
+ export type ServerDiagnosticCode = (typeof SERVER_DIAGNOSTIC_CODES)[keyof typeof SERVER_DIAGNOSTIC_CODES];
25
+ export interface AxiomServerOptions {
26
+ ir: ServerIR;
27
+ persistence?: PersistenceAdapter;
28
+ host?: ServerHost;
29
+ /** How many request ids to remember for idempotent retries. */
30
+ idempotencyWindow?: number;
31
+ }
32
+ export interface AxiomServer {
33
+ /** Loads committed state. Must complete before any request is handled. */
34
+ start(): Promise<void>;
35
+ /** The one entry point. Every transport funnels through here. */
36
+ handle(request: ServerRequest): Promise<ServerResponse>;
37
+ /** Authoritative values of every observable state. */
38
+ snapshot(): StateSnapshot;
39
+ getState(id: NodeId): unknown;
40
+ revision(): number;
41
+ /** Every mutation this authority has applied, with its outcome. */
42
+ mutationLog(): MutationLogEntry[];
43
+ stop(): Promise<void>;
44
+ }
45
+ /**
46
+ * The authoritative runtime.
47
+ *
48
+ * It executes the **same semantic engine** the client runs, given an IR that contains no UI
49
+ * and no routes. That is deliberate rather than convenient: transaction boundaries,
50
+ * provisional writes, `for-each` ordering, constraint and transition evaluation, rollback
51
+ * and the mutation log are not reimplemented here, so a graph cannot behave differently
52
+ * merely because execution moved to the authority.
53
+ *
54
+ * Requests are serialized. One action runs at a time, and its persistence commit completes
55
+ * before the next begins, so two callers cannot both commit from the same snapshot.
56
+ */
57
+ export declare function createAxiomServer(options: AxiomServerOptions): AxiomServer;
58
+ //# sourceMappingURL=server.d.ts.map