@super-line/server 0.10.1 → 0.11.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/README.md +1 -1
- package/dist/index.cjs +289 -153
- package/dist/index.d.cts +126 -57
- package/dist/index.d.ts +126 -57
- package/dist/index.js +295 -154
- package/package.json +9 -9
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ServerMessageDef, RawConn, Serializer, ServerFrame, EmitData, ConnDescriptor, Adapter, PresenceStore, Contract, RoleOf, NodeStat, SharedEvents, SharedServerRequests, ClientInput, Output, Directional, TapEvent,
|
|
1
|
+
import { ServerMessageDef, RawConn, Serializer, ServerFrame, EmitData, Expr, CollectionQuery, CrdtServerReplica, DocListOpts, DocSummary, ConnDescriptor, Adapter, PresenceStore, Contract, RoleOf, NodeStat, SharedEvents, SharedServerRequests, ClientInput, Output, Directional, TapEvent, CtsOf, ServerInput, Handshake, SharedRequests, AnyData, RoleRequests, Events, DataOf, RoleTopics, SharedTopics, EventData, CollectionName, CrdtCollectionName, DocOf, RowOf, ServerTransport, CollectionStore, CrdtCollectionStore } from '@super-line/core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* A single client connection, passed to handlers as the third argument.
|
|
@@ -65,6 +65,76 @@ declare class Conn<Ev = Record<string, ServerMessageDef>, Ctx = unknown, Role ex
|
|
|
65
65
|
*/
|
|
66
66
|
declare function resolvePrincipal(conn: Conn, identify?: (conn: Conn) => string | undefined): string;
|
|
67
67
|
|
|
68
|
+
type Awaitable$1<T> = T | Promise<T>;
|
|
69
|
+
/** The three row mutations a write policy guards. */
|
|
70
|
+
type WriteOp = 'insert' | 'update' | 'delete';
|
|
71
|
+
/**
|
|
72
|
+
* Row-security policy for one collection (see ADR-0006, decision 7). Server-side only — callbacks, never
|
|
73
|
+
* serialized to clients. **Deny-by-default**: omit `read` and clients cannot read the collection at all;
|
|
74
|
+
* omit `write` and clients cannot write it. Server co-writes via `srv.collection(n)` bypass both.
|
|
75
|
+
*/
|
|
76
|
+
interface CollectionPolicy<Ctx = unknown, Row = unknown> {
|
|
77
|
+
/**
|
|
78
|
+
* A caller's visibility filter, ANDed into every snapshot, subscription, and change-route for that caller.
|
|
79
|
+
* Return `undefined` for "no filter" (the whole collection is visible). Return an {@link Expr} to restrict.
|
|
80
|
+
* Caveat: it is evaluated at subscribe time; principal-side state captured here (e.g. the caller's channel
|
|
81
|
+
* list) goes stale until the client resubscribes — row-side predicates re-evaluate on every change naturally.
|
|
82
|
+
*/
|
|
83
|
+
read?: (principal: string, ctx: Ctx) => Awaitable$1<Expr | undefined>;
|
|
84
|
+
/**
|
|
85
|
+
* Per-row write guard. `next` is the incoming row (absent on delete), `prev` the current row (absent on
|
|
86
|
+
* insert). Return `false` to reject the op — which aborts the whole atomic batch it belongs to.
|
|
87
|
+
*/
|
|
88
|
+
write?: (principal: string, op: WriteOp, next: Row | undefined, prev: Row | undefined, ctx: Ctx) => Awaitable$1<boolean>;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Server-authoritative handle for a collection (`srv.collection('messages')`). Writes bypass row policy
|
|
92
|
+
* (server is authoritative) but are still schema-validated, fan out, and — under relay — replicate across
|
|
93
|
+
* nodes exactly like a client batch. The door for business-logic mutations that a contract request handler owns.
|
|
94
|
+
*/
|
|
95
|
+
interface ServerCollectionHandle<Row = unknown> {
|
|
96
|
+
/** Insert a row (its `key` field becomes the id). Throws `CONFLICT` if the id already exists. */
|
|
97
|
+
insert(row: Row): Promise<void>;
|
|
98
|
+
/** Replace a row by its `key` (LWW). Throws `NOT_FOUND` if absent. */
|
|
99
|
+
update(row: Row): Promise<void>;
|
|
100
|
+
/** Delete a row by id (idempotent). */
|
|
101
|
+
delete(id: string): Promise<void>;
|
|
102
|
+
/** Read one row by id, or undefined. */
|
|
103
|
+
read(id: string): Promise<Row | undefined>;
|
|
104
|
+
/** Materialize a snapshot (filter/sort/limit); omit the query for the whole collection. Server-side, policy-free. */
|
|
105
|
+
snapshot(query?: CollectionQuery): Promise<Row[]>;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Access policy for a CRDT document collection (ADR-0007, Q7). Guard-shaped — not the RLS filter shape of
|
|
109
|
+
* {@link CollectionPolicy}, because CRDT collections are opened by id (no subset to filter). **Deny-by-default**.
|
|
110
|
+
* Server co-writes via `srv.collection(n)` bypass both.
|
|
111
|
+
*/
|
|
112
|
+
interface CrdtCollectionPolicy<Ctx = unknown, Doc = unknown> {
|
|
113
|
+
/** May this principal open `id`? Gets the post-merge plaintext `snapshot` (content-based auth like RLS reading a row). */
|
|
114
|
+
read?: (principal: string, id: string, snapshot: Doc | undefined, ctx: Ctx) => Awaitable$1<boolean>;
|
|
115
|
+
/** May this principal write to `id`? (Creation is server-authoritative — Q10 — so there is no `create` op here.) */
|
|
116
|
+
write?: (principal: string, id: string, ctx: Ctx) => Awaitable$1<boolean>;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Server-authoritative handle for a CRDT document collection (`srv.collection('scenes')`). Creation is
|
|
120
|
+
* server-only (Q10): `create` validates the initial doc and materializes it; `open` returns a reactive
|
|
121
|
+
* co-writer whose mutations fan out like a relayed client delta. Policy-free (server is authoritative).
|
|
122
|
+
*/
|
|
123
|
+
interface ServerCrdtCollectionHandle<Doc = unknown> {
|
|
124
|
+
/** Create a document with pre-validated initial data. Throws `CONFLICT` if the id exists. */
|
|
125
|
+
create(id: string, data: Doc): Promise<void>;
|
|
126
|
+
/** Open a reactive co-writer over an existing document. `origin` (default `"server"`) attributes its writes. */
|
|
127
|
+
open(id: string, opts?: {
|
|
128
|
+
origin?: string;
|
|
129
|
+
}): CrdtServerReplica;
|
|
130
|
+
/** Read the current plaintext snapshot of a document, or undefined if absent. */
|
|
131
|
+
read(id: string): Promise<Doc | undefined>;
|
|
132
|
+
/** Delete a document (idempotent), fanning the deletion cluster-wide. */
|
|
133
|
+
delete(id: string): Promise<void>;
|
|
134
|
+
/** Enumerate document ids + summaries (no content query). */
|
|
135
|
+
list(opts?: DocListOpts): Promise<DocSummary[]>;
|
|
136
|
+
}
|
|
137
|
+
|
|
68
138
|
/**
|
|
69
139
|
* In-process pub/sub bus. Share one bus across multiple servers to simulate
|
|
70
140
|
* multiple nodes in a test (each server gets its own adapter bound to the bus).
|
|
@@ -226,46 +296,6 @@ interface RoleLens<C extends Contract, R extends RoleOf<C>> {
|
|
|
226
296
|
/** Publish to a topic in role `R`'s surface (reaches that role's subscribers). */
|
|
227
297
|
publish<T extends keyof RoleTopics<C, R>>(topic: T, data: EmitData<RoleTopics<C, R>[T]>): void;
|
|
228
298
|
}
|
|
229
|
-
/**
|
|
230
|
-
* Server-side handle for one configured Store, reached via `srv.store.<name>`. The server is
|
|
231
|
-
* authoritative: it creates Resources, grants/revokes access, and may co-write. `data` is untyped
|
|
232
|
-
* (stores are off-contract — see ADR-0003); callers assert the shape.
|
|
233
|
-
*/
|
|
234
|
-
interface ServerStoreHandle {
|
|
235
|
-
/** Create a Resource with initial data + access rules (deny-by-default for everyone unlisted). */
|
|
236
|
-
create(id: string, data: unknown, accessRules: AccessRules): Promise<void>;
|
|
237
|
-
/** Read a Resource (data + accessRules), or undefined if absent. */
|
|
238
|
-
read(id: string): Promise<Resource | undefined>;
|
|
239
|
-
/**
|
|
240
|
-
* Server co-write: replace the Resource's value (LWW), fanned out to subscribers stamped with `origin`
|
|
241
|
-
* (default `"server"`) for echo-break + inspector attribution — mirrors {@link ServerStore.open}'s origin.
|
|
242
|
-
*/
|
|
243
|
-
write(id: string, data: unknown, opts?: {
|
|
244
|
-
origin?: string;
|
|
245
|
-
}): Promise<void>;
|
|
246
|
-
/** Grant a principal read/write on a Resource. */
|
|
247
|
-
grant(id: string, principal: string, perms: Perms): Promise<void>;
|
|
248
|
-
/** Revoke a principal's access to a Resource entirely. */
|
|
249
|
-
revoke(id: string, principal: string): Promise<void>;
|
|
250
|
-
/** Delete a Resource. */
|
|
251
|
-
delete(id: string): Promise<void>;
|
|
252
|
-
/**
|
|
253
|
-
* Summaries of this store's Resources, server-side filtered / sorted / paginated per {@link ListOpts}
|
|
254
|
-
* (omit `opts` for every Resource, id-ascending). Forwards to {@link ServerStore.list}.
|
|
255
|
-
*/
|
|
256
|
-
list(opts?: ListOpts): Promise<ResourceSummary[]>;
|
|
257
|
-
/** Distinct principals granted anywhere in this store (store-global). Forwards to {@link ServerStore.searchPrincipals}. */
|
|
258
|
-
searchPrincipals(opts: SearchOpts): Promise<string[]>;
|
|
259
|
-
/**
|
|
260
|
-
* Open a reactive in-process co-writer over a Resource's canonical state — the server half's mirror of
|
|
261
|
-
* `client.store(name).open(id)`. Reactive reads, merging `update`, and surgical `delete(path)` (the only
|
|
262
|
-
* way to remove a key server-side), all server-authoritative and fanned out to subscribers. `origin`
|
|
263
|
-
* (default `"server"`) tags its Changes for inspector attribution. Throws if the store has no `open`.
|
|
264
|
-
*/
|
|
265
|
-
open(id: string, opts?: {
|
|
266
|
-
origin?: string;
|
|
267
|
-
}): ServerReplica;
|
|
268
|
-
}
|
|
269
299
|
/**
|
|
270
300
|
* Handlers a plugin provides for its paired surface `S` (ADR-0004): one per `clientToServer` key,
|
|
271
301
|
* typed from `S`. `ctx`/`conn` are loose — a plugin is written independently of the host's per-role ctx.
|
|
@@ -277,9 +307,15 @@ type HandlersFor<S extends Directional> = {
|
|
|
277
307
|
type PluginHandledKeys<U> = U extends SuperLinePlugin<infer S> ? keyof CtsOf<S> & string : never;
|
|
278
308
|
/** Union of the `clientToServer` keys handled across a plugin tuple `P` (subtracted from `implement`). */
|
|
279
309
|
type HandledKeys<P extends readonly SuperLinePlugin<any>[]> = PluginHandledKeys<P[number]>;
|
|
280
|
-
/**
|
|
310
|
+
/**
|
|
311
|
+
* Remove the plugin-handled keys `HK` from every block (each role + `shared`) of a {@link Handlers} map. A block
|
|
312
|
+
* a plugin fully owns collapses to `{}` and becomes OPTIONAL — so a host needn't pass an empty `shared: {}` /
|
|
313
|
+
* `guest: {}` for a role or shared surface a plugin handles entirely.
|
|
314
|
+
*/
|
|
281
315
|
type SubtractHandlers<H, HK extends string> = {
|
|
282
|
-
[B in keyof H]: Omit<H[B], HK>;
|
|
316
|
+
[B in keyof H as [keyof Omit<H[B], HK>] extends [never] ? never : B]: Omit<H[B], HK>;
|
|
317
|
+
} & {
|
|
318
|
+
[B in keyof H as [keyof Omit<H[B], HK>] extends [never] ? B : never]?: Omit<H[B], HK>;
|
|
283
319
|
};
|
|
284
320
|
/**
|
|
285
321
|
* A named, declarative bundle of runtime contributions registered on `plugins: [...]`. All fields
|
|
@@ -312,10 +348,13 @@ interface SuperLinePlugin<S extends Directional = {}> {
|
|
|
312
348
|
*/
|
|
313
349
|
handlers?: (ctx: PluginContext) => HandlersFor<S>;
|
|
314
350
|
/**
|
|
315
|
-
*
|
|
316
|
-
*
|
|
351
|
+
* Row-security policies for the collections the plugin's contract fragment declares (see {@link CollectionPolicy}).
|
|
352
|
+
* Merged into the host's `policies`; a collection already policied by the host or another plugin throws at
|
|
353
|
+
* construction, and a policy for a collection no fragment declared throws too. Deny-by-default still holds —
|
|
354
|
+
* a collection nobody policies is server-only. Lets a plugin ship its collections locked down (e.g. deny-all
|
|
355
|
+
* on secret tables) without the host hand-spreading them.
|
|
317
356
|
*/
|
|
318
|
-
|
|
357
|
+
policies?: Record<string, CollectionPolicy<unknown, unknown>>;
|
|
319
358
|
/**
|
|
320
359
|
* A plugin-owned (reserved) connection class — its own role, handshake negotiation, and parallel contract,
|
|
321
360
|
* served over observer-invisible connections. See {@link PluginConnection}. (Phase 2.)
|
|
@@ -403,10 +442,14 @@ interface PluginContext {
|
|
|
403
442
|
readonly size: number;
|
|
404
443
|
readonly connections: readonly Conn[];
|
|
405
444
|
};
|
|
406
|
-
/** Server-authoritative handle for a
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
|
|
445
|
+
/** Server-authoritative handle for a contract collection (loosely typed here as the LWW row handle — the surface plugins/inspector use); throws if none is configured. */
|
|
446
|
+
collection(name: string): ServerCollectionHandle;
|
|
447
|
+
/** Declared collections (name + key + advisory references) for the schema graph. */
|
|
448
|
+
collectionInfos(): {
|
|
449
|
+
name: string;
|
|
450
|
+
key: string;
|
|
451
|
+
references: Record<string, string>;
|
|
452
|
+
}[];
|
|
410
453
|
/** Full cluster descriptor for a local connection (identity + rooms + `describeConn` extras). */
|
|
411
454
|
describe(conn: Conn): ConnDescriptor;
|
|
412
455
|
/** A connection's descriptor anywhere in the cluster (rejects without presence support); undefined if absent. */
|
|
@@ -450,11 +493,32 @@ interface SuperLineServerOptions<C extends Contract, A extends AuthResult<C>, P
|
|
|
450
493
|
maxMissed?: number;
|
|
451
494
|
} | false;
|
|
452
495
|
/**
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
496
|
+
* The single {@link CollectionStore} backend serving every collection this contract declares (typed rows;
|
|
497
|
+
* ADR-0006), e.g. `collections: memoryCollections()`. One backend ⇒ one transaction domain, so a
|
|
498
|
+
* cross-collection batch commits atomically.
|
|
499
|
+
*/
|
|
500
|
+
collections?: CollectionStore;
|
|
501
|
+
/**
|
|
502
|
+
* The {@link CrdtCollectionStore} backend serving this contract's CRDT document collections (ADR-0007),
|
|
503
|
+
* e.g. `crdtCollections: crdtMemoryCollections()`. A backend per family: CRDT docs never join a
|
|
504
|
+
* cross-collection atomic batch, so they get their own backend, separate from `collections`.
|
|
505
|
+
*/
|
|
506
|
+
crdtCollections?: CrdtCollectionStore;
|
|
507
|
+
/**
|
|
508
|
+
* Access policies per collection. Deny-by-default: a collection with no policy is server-only. LWW row
|
|
509
|
+
* collections take an RLS-style {@link CollectionPolicy} (read → filter); CRDT document collections take a
|
|
510
|
+
* guard-shaped {@link CrdtCollectionPolicy} (read → bool). Server co-writes via `srv.collection(n)` bypass them.
|
|
456
511
|
*/
|
|
457
|
-
|
|
512
|
+
policies?: {
|
|
513
|
+
[N in CollectionName<C>]?: N extends CrdtCollectionName<C> ? CrdtCollectionPolicy<CtxUnion<A>, DocOf<C, N>> : CollectionPolicy<CtxUnion<A>, RowOf<C, N>>;
|
|
514
|
+
};
|
|
515
|
+
/**
|
|
516
|
+
* Opt-in advisory foreign-key checks (see ADR-0006, decision 8). When true, an insert/update whose
|
|
517
|
+
* `references` field points at a non-existent row is rejected at the accepting node. Best-effort under
|
|
518
|
+
* relay clustering (no global serialization point) and does NOT resolve intra-batch parent-then-child
|
|
519
|
+
* references; there are no cascades. Off by default.
|
|
520
|
+
*/
|
|
521
|
+
checkReferences?: boolean;
|
|
458
522
|
/** Called once per accepted connection. */
|
|
459
523
|
onConnection?: (conn: Conn, ctx: CtxUnion<A>) => void;
|
|
460
524
|
/** Called when a connection closes, with the WebSocket close `code`. */
|
|
@@ -493,8 +557,13 @@ interface SuperLineServer<C extends Contract, A extends AuthResult<C>, HK extend
|
|
|
493
557
|
subscribe<T extends keyof SharedTopics<C>>(topic: T, handler: (data: EventData<SharedTopics<C>[T]>, meta: BusMeta) => void): () => void;
|
|
494
558
|
/** Lens for role-scoped sends, e.g. forRole('user').publish('feed', data). */
|
|
495
559
|
forRole<R extends RoleOf<C>>(role: R): RoleLens<C, R>;
|
|
496
|
-
/**
|
|
497
|
-
|
|
560
|
+
/**
|
|
561
|
+
* Server-authoritative handle for a contract collection, typed by the contract: an LWW row collection gives
|
|
562
|
+
* `ServerCollectionHandle` (`insert`/`update`/`batch`), a CRDT document collection gives
|
|
563
|
+
* `ServerCrdtCollectionHandle` (`create`/`open`). Throws `NOT_FOUND` if no matching backend is configured
|
|
564
|
+
* or the name isn't declared.
|
|
565
|
+
*/
|
|
566
|
+
collection<N extends CollectionName<C>>(name: N): N extends CrdtCollectionName<C> ? ServerCrdtCollectionHandle<DocOf<C, N>> : ServerCollectionHandle<RowOf<C, N>>;
|
|
498
567
|
close(): Promise<void>;
|
|
499
568
|
}
|
|
500
569
|
/**
|
|
@@ -521,4 +590,4 @@ interface SuperLineServer<C extends Contract, A extends AuthResult<C>, HK extend
|
|
|
521
590
|
*/
|
|
522
591
|
declare function createSuperLineServer<C extends Contract, A extends AuthResult<C>, const P extends readonly SuperLinePlugin<any>[] = []>(contract: C, opts: SuperLineServerOptions<C, A, P>): SuperLineServer<C, A, HandledKeys<P>>;
|
|
523
592
|
|
|
524
|
-
export { type AuthResult, type BusMeta, type ClusterView, Conn, type ConnTarget, type HandledKeys, type Handlers, type HandlersFor, type LocalView, MemoryBus, type Middleware, type MiddlewareInfo, type PluginChannel, type PluginConnection, type PluginContext, type PluginMiddleware, type RoleLens, type Room, type
|
|
593
|
+
export { type AuthResult, type BusMeta, type ClusterView, type CollectionPolicy, Conn, type ConnTarget, type CrdtCollectionPolicy, type HandledKeys, type Handlers, type HandlersFor, type LocalView, MemoryBus, type Middleware, type MiddlewareInfo, type PluginChannel, type PluginConnection, type PluginContext, type PluginMiddleware, type RoleLens, type Room, type ServerCollectionHandle, type ServerCrdtCollectionHandle, type SubtractHandlers, type SuperLinePlugin, type SuperLineServer, type SuperLineServerOptions, type UserTarget, type WriteOp, createInMemoryAdapter, createSuperLineServer, resolvePrincipal };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ServerMessageDef, RawConn, Serializer, ServerFrame, EmitData, ConnDescriptor, Adapter, PresenceStore, Contract, RoleOf, NodeStat, SharedEvents, SharedServerRequests, ClientInput, Output, Directional, TapEvent,
|
|
1
|
+
import { ServerMessageDef, RawConn, Serializer, ServerFrame, EmitData, Expr, CollectionQuery, CrdtServerReplica, DocListOpts, DocSummary, ConnDescriptor, Adapter, PresenceStore, Contract, RoleOf, NodeStat, SharedEvents, SharedServerRequests, ClientInput, Output, Directional, TapEvent, CtsOf, ServerInput, Handshake, SharedRequests, AnyData, RoleRequests, Events, DataOf, RoleTopics, SharedTopics, EventData, CollectionName, CrdtCollectionName, DocOf, RowOf, ServerTransport, CollectionStore, CrdtCollectionStore } from '@super-line/core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* A single client connection, passed to handlers as the third argument.
|
|
@@ -65,6 +65,76 @@ declare class Conn<Ev = Record<string, ServerMessageDef>, Ctx = unknown, Role ex
|
|
|
65
65
|
*/
|
|
66
66
|
declare function resolvePrincipal(conn: Conn, identify?: (conn: Conn) => string | undefined): string;
|
|
67
67
|
|
|
68
|
+
type Awaitable$1<T> = T | Promise<T>;
|
|
69
|
+
/** The three row mutations a write policy guards. */
|
|
70
|
+
type WriteOp = 'insert' | 'update' | 'delete';
|
|
71
|
+
/**
|
|
72
|
+
* Row-security policy for one collection (see ADR-0006, decision 7). Server-side only — callbacks, never
|
|
73
|
+
* serialized to clients. **Deny-by-default**: omit `read` and clients cannot read the collection at all;
|
|
74
|
+
* omit `write` and clients cannot write it. Server co-writes via `srv.collection(n)` bypass both.
|
|
75
|
+
*/
|
|
76
|
+
interface CollectionPolicy<Ctx = unknown, Row = unknown> {
|
|
77
|
+
/**
|
|
78
|
+
* A caller's visibility filter, ANDed into every snapshot, subscription, and change-route for that caller.
|
|
79
|
+
* Return `undefined` for "no filter" (the whole collection is visible). Return an {@link Expr} to restrict.
|
|
80
|
+
* Caveat: it is evaluated at subscribe time; principal-side state captured here (e.g. the caller's channel
|
|
81
|
+
* list) goes stale until the client resubscribes — row-side predicates re-evaluate on every change naturally.
|
|
82
|
+
*/
|
|
83
|
+
read?: (principal: string, ctx: Ctx) => Awaitable$1<Expr | undefined>;
|
|
84
|
+
/**
|
|
85
|
+
* Per-row write guard. `next` is the incoming row (absent on delete), `prev` the current row (absent on
|
|
86
|
+
* insert). Return `false` to reject the op — which aborts the whole atomic batch it belongs to.
|
|
87
|
+
*/
|
|
88
|
+
write?: (principal: string, op: WriteOp, next: Row | undefined, prev: Row | undefined, ctx: Ctx) => Awaitable$1<boolean>;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Server-authoritative handle for a collection (`srv.collection('messages')`). Writes bypass row policy
|
|
92
|
+
* (server is authoritative) but are still schema-validated, fan out, and — under relay — replicate across
|
|
93
|
+
* nodes exactly like a client batch. The door for business-logic mutations that a contract request handler owns.
|
|
94
|
+
*/
|
|
95
|
+
interface ServerCollectionHandle<Row = unknown> {
|
|
96
|
+
/** Insert a row (its `key` field becomes the id). Throws `CONFLICT` if the id already exists. */
|
|
97
|
+
insert(row: Row): Promise<void>;
|
|
98
|
+
/** Replace a row by its `key` (LWW). Throws `NOT_FOUND` if absent. */
|
|
99
|
+
update(row: Row): Promise<void>;
|
|
100
|
+
/** Delete a row by id (idempotent). */
|
|
101
|
+
delete(id: string): Promise<void>;
|
|
102
|
+
/** Read one row by id, or undefined. */
|
|
103
|
+
read(id: string): Promise<Row | undefined>;
|
|
104
|
+
/** Materialize a snapshot (filter/sort/limit); omit the query for the whole collection. Server-side, policy-free. */
|
|
105
|
+
snapshot(query?: CollectionQuery): Promise<Row[]>;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Access policy for a CRDT document collection (ADR-0007, Q7). Guard-shaped — not the RLS filter shape of
|
|
109
|
+
* {@link CollectionPolicy}, because CRDT collections are opened by id (no subset to filter). **Deny-by-default**.
|
|
110
|
+
* Server co-writes via `srv.collection(n)` bypass both.
|
|
111
|
+
*/
|
|
112
|
+
interface CrdtCollectionPolicy<Ctx = unknown, Doc = unknown> {
|
|
113
|
+
/** May this principal open `id`? Gets the post-merge plaintext `snapshot` (content-based auth like RLS reading a row). */
|
|
114
|
+
read?: (principal: string, id: string, snapshot: Doc | undefined, ctx: Ctx) => Awaitable$1<boolean>;
|
|
115
|
+
/** May this principal write to `id`? (Creation is server-authoritative — Q10 — so there is no `create` op here.) */
|
|
116
|
+
write?: (principal: string, id: string, ctx: Ctx) => Awaitable$1<boolean>;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Server-authoritative handle for a CRDT document collection (`srv.collection('scenes')`). Creation is
|
|
120
|
+
* server-only (Q10): `create` validates the initial doc and materializes it; `open` returns a reactive
|
|
121
|
+
* co-writer whose mutations fan out like a relayed client delta. Policy-free (server is authoritative).
|
|
122
|
+
*/
|
|
123
|
+
interface ServerCrdtCollectionHandle<Doc = unknown> {
|
|
124
|
+
/** Create a document with pre-validated initial data. Throws `CONFLICT` if the id exists. */
|
|
125
|
+
create(id: string, data: Doc): Promise<void>;
|
|
126
|
+
/** Open a reactive co-writer over an existing document. `origin` (default `"server"`) attributes its writes. */
|
|
127
|
+
open(id: string, opts?: {
|
|
128
|
+
origin?: string;
|
|
129
|
+
}): CrdtServerReplica;
|
|
130
|
+
/** Read the current plaintext snapshot of a document, or undefined if absent. */
|
|
131
|
+
read(id: string): Promise<Doc | undefined>;
|
|
132
|
+
/** Delete a document (idempotent), fanning the deletion cluster-wide. */
|
|
133
|
+
delete(id: string): Promise<void>;
|
|
134
|
+
/** Enumerate document ids + summaries (no content query). */
|
|
135
|
+
list(opts?: DocListOpts): Promise<DocSummary[]>;
|
|
136
|
+
}
|
|
137
|
+
|
|
68
138
|
/**
|
|
69
139
|
* In-process pub/sub bus. Share one bus across multiple servers to simulate
|
|
70
140
|
* multiple nodes in a test (each server gets its own adapter bound to the bus).
|
|
@@ -226,46 +296,6 @@ interface RoleLens<C extends Contract, R extends RoleOf<C>> {
|
|
|
226
296
|
/** Publish to a topic in role `R`'s surface (reaches that role's subscribers). */
|
|
227
297
|
publish<T extends keyof RoleTopics<C, R>>(topic: T, data: EmitData<RoleTopics<C, R>[T]>): void;
|
|
228
298
|
}
|
|
229
|
-
/**
|
|
230
|
-
* Server-side handle for one configured Store, reached via `srv.store.<name>`. The server is
|
|
231
|
-
* authoritative: it creates Resources, grants/revokes access, and may co-write. `data` is untyped
|
|
232
|
-
* (stores are off-contract — see ADR-0003); callers assert the shape.
|
|
233
|
-
*/
|
|
234
|
-
interface ServerStoreHandle {
|
|
235
|
-
/** Create a Resource with initial data + access rules (deny-by-default for everyone unlisted). */
|
|
236
|
-
create(id: string, data: unknown, accessRules: AccessRules): Promise<void>;
|
|
237
|
-
/** Read a Resource (data + accessRules), or undefined if absent. */
|
|
238
|
-
read(id: string): Promise<Resource | undefined>;
|
|
239
|
-
/**
|
|
240
|
-
* Server co-write: replace the Resource's value (LWW), fanned out to subscribers stamped with `origin`
|
|
241
|
-
* (default `"server"`) for echo-break + inspector attribution — mirrors {@link ServerStore.open}'s origin.
|
|
242
|
-
*/
|
|
243
|
-
write(id: string, data: unknown, opts?: {
|
|
244
|
-
origin?: string;
|
|
245
|
-
}): Promise<void>;
|
|
246
|
-
/** Grant a principal read/write on a Resource. */
|
|
247
|
-
grant(id: string, principal: string, perms: Perms): Promise<void>;
|
|
248
|
-
/** Revoke a principal's access to a Resource entirely. */
|
|
249
|
-
revoke(id: string, principal: string): Promise<void>;
|
|
250
|
-
/** Delete a Resource. */
|
|
251
|
-
delete(id: string): Promise<void>;
|
|
252
|
-
/**
|
|
253
|
-
* Summaries of this store's Resources, server-side filtered / sorted / paginated per {@link ListOpts}
|
|
254
|
-
* (omit `opts` for every Resource, id-ascending). Forwards to {@link ServerStore.list}.
|
|
255
|
-
*/
|
|
256
|
-
list(opts?: ListOpts): Promise<ResourceSummary[]>;
|
|
257
|
-
/** Distinct principals granted anywhere in this store (store-global). Forwards to {@link ServerStore.searchPrincipals}. */
|
|
258
|
-
searchPrincipals(opts: SearchOpts): Promise<string[]>;
|
|
259
|
-
/**
|
|
260
|
-
* Open a reactive in-process co-writer over a Resource's canonical state — the server half's mirror of
|
|
261
|
-
* `client.store(name).open(id)`. Reactive reads, merging `update`, and surgical `delete(path)` (the only
|
|
262
|
-
* way to remove a key server-side), all server-authoritative and fanned out to subscribers. `origin`
|
|
263
|
-
* (default `"server"`) tags its Changes for inspector attribution. Throws if the store has no `open`.
|
|
264
|
-
*/
|
|
265
|
-
open(id: string, opts?: {
|
|
266
|
-
origin?: string;
|
|
267
|
-
}): ServerReplica;
|
|
268
|
-
}
|
|
269
299
|
/**
|
|
270
300
|
* Handlers a plugin provides for its paired surface `S` (ADR-0004): one per `clientToServer` key,
|
|
271
301
|
* typed from `S`. `ctx`/`conn` are loose — a plugin is written independently of the host's per-role ctx.
|
|
@@ -277,9 +307,15 @@ type HandlersFor<S extends Directional> = {
|
|
|
277
307
|
type PluginHandledKeys<U> = U extends SuperLinePlugin<infer S> ? keyof CtsOf<S> & string : never;
|
|
278
308
|
/** Union of the `clientToServer` keys handled across a plugin tuple `P` (subtracted from `implement`). */
|
|
279
309
|
type HandledKeys<P extends readonly SuperLinePlugin<any>[]> = PluginHandledKeys<P[number]>;
|
|
280
|
-
/**
|
|
310
|
+
/**
|
|
311
|
+
* Remove the plugin-handled keys `HK` from every block (each role + `shared`) of a {@link Handlers} map. A block
|
|
312
|
+
* a plugin fully owns collapses to `{}` and becomes OPTIONAL — so a host needn't pass an empty `shared: {}` /
|
|
313
|
+
* `guest: {}` for a role or shared surface a plugin handles entirely.
|
|
314
|
+
*/
|
|
281
315
|
type SubtractHandlers<H, HK extends string> = {
|
|
282
|
-
[B in keyof H]: Omit<H[B], HK>;
|
|
316
|
+
[B in keyof H as [keyof Omit<H[B], HK>] extends [never] ? never : B]: Omit<H[B], HK>;
|
|
317
|
+
} & {
|
|
318
|
+
[B in keyof H as [keyof Omit<H[B], HK>] extends [never] ? B : never]?: Omit<H[B], HK>;
|
|
283
319
|
};
|
|
284
320
|
/**
|
|
285
321
|
* A named, declarative bundle of runtime contributions registered on `plugins: [...]`. All fields
|
|
@@ -312,10 +348,13 @@ interface SuperLinePlugin<S extends Directional = {}> {
|
|
|
312
348
|
*/
|
|
313
349
|
handlers?: (ctx: PluginContext) => HandlersFor<S>;
|
|
314
350
|
/**
|
|
315
|
-
*
|
|
316
|
-
*
|
|
351
|
+
* Row-security policies for the collections the plugin's contract fragment declares (see {@link CollectionPolicy}).
|
|
352
|
+
* Merged into the host's `policies`; a collection already policied by the host or another plugin throws at
|
|
353
|
+
* construction, and a policy for a collection no fragment declared throws too. Deny-by-default still holds —
|
|
354
|
+
* a collection nobody policies is server-only. Lets a plugin ship its collections locked down (e.g. deny-all
|
|
355
|
+
* on secret tables) without the host hand-spreading them.
|
|
317
356
|
*/
|
|
318
|
-
|
|
357
|
+
policies?: Record<string, CollectionPolicy<unknown, unknown>>;
|
|
319
358
|
/**
|
|
320
359
|
* A plugin-owned (reserved) connection class — its own role, handshake negotiation, and parallel contract,
|
|
321
360
|
* served over observer-invisible connections. See {@link PluginConnection}. (Phase 2.)
|
|
@@ -403,10 +442,14 @@ interface PluginContext {
|
|
|
403
442
|
readonly size: number;
|
|
404
443
|
readonly connections: readonly Conn[];
|
|
405
444
|
};
|
|
406
|
-
/** Server-authoritative handle for a
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
|
|
445
|
+
/** Server-authoritative handle for a contract collection (loosely typed here as the LWW row handle — the surface plugins/inspector use); throws if none is configured. */
|
|
446
|
+
collection(name: string): ServerCollectionHandle;
|
|
447
|
+
/** Declared collections (name + key + advisory references) for the schema graph. */
|
|
448
|
+
collectionInfos(): {
|
|
449
|
+
name: string;
|
|
450
|
+
key: string;
|
|
451
|
+
references: Record<string, string>;
|
|
452
|
+
}[];
|
|
410
453
|
/** Full cluster descriptor for a local connection (identity + rooms + `describeConn` extras). */
|
|
411
454
|
describe(conn: Conn): ConnDescriptor;
|
|
412
455
|
/** A connection's descriptor anywhere in the cluster (rejects without presence support); undefined if absent. */
|
|
@@ -450,11 +493,32 @@ interface SuperLineServerOptions<C extends Contract, A extends AuthResult<C>, P
|
|
|
450
493
|
maxMissed?: number;
|
|
451
494
|
} | false;
|
|
452
495
|
/**
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
496
|
+
* The single {@link CollectionStore} backend serving every collection this contract declares (typed rows;
|
|
497
|
+
* ADR-0006), e.g. `collections: memoryCollections()`. One backend ⇒ one transaction domain, so a
|
|
498
|
+
* cross-collection batch commits atomically.
|
|
499
|
+
*/
|
|
500
|
+
collections?: CollectionStore;
|
|
501
|
+
/**
|
|
502
|
+
* The {@link CrdtCollectionStore} backend serving this contract's CRDT document collections (ADR-0007),
|
|
503
|
+
* e.g. `crdtCollections: crdtMemoryCollections()`. A backend per family: CRDT docs never join a
|
|
504
|
+
* cross-collection atomic batch, so they get their own backend, separate from `collections`.
|
|
505
|
+
*/
|
|
506
|
+
crdtCollections?: CrdtCollectionStore;
|
|
507
|
+
/**
|
|
508
|
+
* Access policies per collection. Deny-by-default: a collection with no policy is server-only. LWW row
|
|
509
|
+
* collections take an RLS-style {@link CollectionPolicy} (read → filter); CRDT document collections take a
|
|
510
|
+
* guard-shaped {@link CrdtCollectionPolicy} (read → bool). Server co-writes via `srv.collection(n)` bypass them.
|
|
456
511
|
*/
|
|
457
|
-
|
|
512
|
+
policies?: {
|
|
513
|
+
[N in CollectionName<C>]?: N extends CrdtCollectionName<C> ? CrdtCollectionPolicy<CtxUnion<A>, DocOf<C, N>> : CollectionPolicy<CtxUnion<A>, RowOf<C, N>>;
|
|
514
|
+
};
|
|
515
|
+
/**
|
|
516
|
+
* Opt-in advisory foreign-key checks (see ADR-0006, decision 8). When true, an insert/update whose
|
|
517
|
+
* `references` field points at a non-existent row is rejected at the accepting node. Best-effort under
|
|
518
|
+
* relay clustering (no global serialization point) and does NOT resolve intra-batch parent-then-child
|
|
519
|
+
* references; there are no cascades. Off by default.
|
|
520
|
+
*/
|
|
521
|
+
checkReferences?: boolean;
|
|
458
522
|
/** Called once per accepted connection. */
|
|
459
523
|
onConnection?: (conn: Conn, ctx: CtxUnion<A>) => void;
|
|
460
524
|
/** Called when a connection closes, with the WebSocket close `code`. */
|
|
@@ -493,8 +557,13 @@ interface SuperLineServer<C extends Contract, A extends AuthResult<C>, HK extend
|
|
|
493
557
|
subscribe<T extends keyof SharedTopics<C>>(topic: T, handler: (data: EventData<SharedTopics<C>[T]>, meta: BusMeta) => void): () => void;
|
|
494
558
|
/** Lens for role-scoped sends, e.g. forRole('user').publish('feed', data). */
|
|
495
559
|
forRole<R extends RoleOf<C>>(role: R): RoleLens<C, R>;
|
|
496
|
-
/**
|
|
497
|
-
|
|
560
|
+
/**
|
|
561
|
+
* Server-authoritative handle for a contract collection, typed by the contract: an LWW row collection gives
|
|
562
|
+
* `ServerCollectionHandle` (`insert`/`update`/`batch`), a CRDT document collection gives
|
|
563
|
+
* `ServerCrdtCollectionHandle` (`create`/`open`). Throws `NOT_FOUND` if no matching backend is configured
|
|
564
|
+
* or the name isn't declared.
|
|
565
|
+
*/
|
|
566
|
+
collection<N extends CollectionName<C>>(name: N): N extends CrdtCollectionName<C> ? ServerCrdtCollectionHandle<DocOf<C, N>> : ServerCollectionHandle<RowOf<C, N>>;
|
|
498
567
|
close(): Promise<void>;
|
|
499
568
|
}
|
|
500
569
|
/**
|
|
@@ -521,4 +590,4 @@ interface SuperLineServer<C extends Contract, A extends AuthResult<C>, HK extend
|
|
|
521
590
|
*/
|
|
522
591
|
declare function createSuperLineServer<C extends Contract, A extends AuthResult<C>, const P extends readonly SuperLinePlugin<any>[] = []>(contract: C, opts: SuperLineServerOptions<C, A, P>): SuperLineServer<C, A, HandledKeys<P>>;
|
|
523
592
|
|
|
524
|
-
export { type AuthResult, type BusMeta, type ClusterView, Conn, type ConnTarget, type HandledKeys, type Handlers, type HandlersFor, type LocalView, MemoryBus, type Middleware, type MiddlewareInfo, type PluginChannel, type PluginConnection, type PluginContext, type PluginMiddleware, type RoleLens, type Room, type
|
|
593
|
+
export { type AuthResult, type BusMeta, type ClusterView, type CollectionPolicy, Conn, type ConnTarget, type CrdtCollectionPolicy, type HandledKeys, type Handlers, type HandlersFor, type LocalView, MemoryBus, type Middleware, type MiddlewareInfo, type PluginChannel, type PluginConnection, type PluginContext, type PluginMiddleware, type RoleLens, type Room, type ServerCollectionHandle, type ServerCrdtCollectionHandle, type SubtractHandlers, type SuperLinePlugin, type SuperLineServer, type SuperLineServerOptions, type UserTarget, type WriteOp, createInMemoryAdapter, createSuperLineServer, resolvePrincipal };
|