@super-line/core 0.4.0 → 0.6.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/dist/index.cjs +19 -0
- package/dist/index.d.cts +194 -3
- package/dist/index.d.ts +194 -3
- package/dist/index.js +18 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -28,6 +28,7 @@ __export(index_exports, {
|
|
|
28
28
|
classifyContract: () => classifyContract,
|
|
29
29
|
defineContract: () => defineContract,
|
|
30
30
|
jsonSerializer: () => jsonSerializer,
|
|
31
|
+
removeAtPath: () => removeAtPath,
|
|
31
32
|
validate: () => validate,
|
|
32
33
|
validateSync: () => validateSync
|
|
33
34
|
});
|
|
@@ -153,6 +154,23 @@ function classifyContract(contract, convert) {
|
|
|
153
154
|
|
|
154
155
|
// src/wire.ts
|
|
155
156
|
var PROTOCOL = "superline.v1";
|
|
157
|
+
|
|
158
|
+
// src/store.ts
|
|
159
|
+
var removeAtPath = (root, path) => {
|
|
160
|
+
if (path.length === 0) return root;
|
|
161
|
+
if (typeof root !== "object" || root === null) return root;
|
|
162
|
+
const [head, ...rest] = path;
|
|
163
|
+
if (Array.isArray(root)) {
|
|
164
|
+
const next2 = root.slice();
|
|
165
|
+
if (rest.length === 0) next2.splice(Number(head), 1);
|
|
166
|
+
else next2[Number(head)] = removeAtPath(next2[Number(head)], rest);
|
|
167
|
+
return next2;
|
|
168
|
+
}
|
|
169
|
+
const next = { ...root };
|
|
170
|
+
if (rest.length === 0) delete next[head];
|
|
171
|
+
else next[head] = removeAtPath(next[head], rest);
|
|
172
|
+
return next;
|
|
173
|
+
};
|
|
156
174
|
// Annotate the CommonJS export names for ESM import in node:
|
|
157
175
|
0 && (module.exports = {
|
|
158
176
|
INSPECTOR_ROLE,
|
|
@@ -163,6 +181,7 @@ var PROTOCOL = "superline.v1";
|
|
|
163
181
|
classifyContract,
|
|
164
182
|
defineContract,
|
|
165
183
|
jsonSerializer,
|
|
184
|
+
removeAtPath,
|
|
166
185
|
validate,
|
|
167
186
|
validateSync
|
|
168
187
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -418,6 +418,37 @@ type InspectorEvent = {
|
|
|
418
418
|
ok: boolean;
|
|
419
419
|
output?: unknown;
|
|
420
420
|
error?: MessageError;
|
|
421
|
+
} | {
|
|
422
|
+
type: 'store.write';
|
|
423
|
+
store: string;
|
|
424
|
+
id: string;
|
|
425
|
+
origin: string;
|
|
426
|
+
connId?: string;
|
|
427
|
+
data: unknown;
|
|
428
|
+
} | {
|
|
429
|
+
type: 'store.grant';
|
|
430
|
+
store: string;
|
|
431
|
+
id: string;
|
|
432
|
+
principal: string;
|
|
433
|
+
perms: {
|
|
434
|
+
read: boolean;
|
|
435
|
+
write: boolean;
|
|
436
|
+
};
|
|
437
|
+
} | {
|
|
438
|
+
type: 'store.revoke';
|
|
439
|
+
store: string;
|
|
440
|
+
id: string;
|
|
441
|
+
principal: string;
|
|
442
|
+
} | {
|
|
443
|
+
type: 'store.subscribe';
|
|
444
|
+
connId: string;
|
|
445
|
+
store: string;
|
|
446
|
+
id: string;
|
|
447
|
+
} | {
|
|
448
|
+
type: 'store.unsubscribe';
|
|
449
|
+
connId: string;
|
|
450
|
+
store: string;
|
|
451
|
+
id: string;
|
|
421
452
|
};
|
|
422
453
|
/**
|
|
423
454
|
* The fixed, library-owned contract describing the inspector surface. Identical for every
|
|
@@ -509,7 +540,32 @@ interface PingFrame {
|
|
|
509
540
|
interface PongFrame {
|
|
510
541
|
t: 'pong';
|
|
511
542
|
}
|
|
512
|
-
|
|
543
|
+
interface SOpenFrame {
|
|
544
|
+
t: 'sopen';
|
|
545
|
+
i: number;
|
|
546
|
+
n: string;
|
|
547
|
+
id: string;
|
|
548
|
+
}
|
|
549
|
+
interface SCloseFrame {
|
|
550
|
+
t: 'sclose';
|
|
551
|
+
n: string;
|
|
552
|
+
id: string;
|
|
553
|
+
}
|
|
554
|
+
interface SWriteFrame {
|
|
555
|
+
t: 'swr';
|
|
556
|
+
i: number;
|
|
557
|
+
n: string;
|
|
558
|
+
id: string;
|
|
559
|
+
u: unknown;
|
|
560
|
+
o: string;
|
|
561
|
+
}
|
|
562
|
+
interface SReadFrame {
|
|
563
|
+
t: 'srd';
|
|
564
|
+
i: number;
|
|
565
|
+
n: string;
|
|
566
|
+
id: string;
|
|
567
|
+
}
|
|
568
|
+
type ClientFrame = ReqFrame | SubFrame | UnsubFrame | SResFrame | SErrFrame | SOpenFrame | SCloseFrame | SWriteFrame | SReadFrame | PingFrame | PongFrame;
|
|
513
569
|
interface ResFrame {
|
|
514
570
|
t: 'res';
|
|
515
571
|
i: number;
|
|
@@ -539,9 +595,144 @@ interface SReqFrame {
|
|
|
539
595
|
m: string;
|
|
540
596
|
d: unknown;
|
|
541
597
|
}
|
|
542
|
-
|
|
598
|
+
interface SChangeFrame {
|
|
599
|
+
t: 'sch';
|
|
600
|
+
n: string;
|
|
601
|
+
id: string;
|
|
602
|
+
u: unknown;
|
|
603
|
+
o: string;
|
|
604
|
+
nd?: string;
|
|
605
|
+
}
|
|
606
|
+
type ServerFrame = ResFrame | ErrFrame | EvtFrame | PubFrame | SReqFrame | SChangeFrame | PingFrame | PongFrame;
|
|
543
607
|
type Frame = ClientFrame | ServerFrame;
|
|
544
608
|
|
|
609
|
+
/**
|
|
610
|
+
* Store — super-line's pluggable persisted-state seam.
|
|
611
|
+
*
|
|
612
|
+
* A Store persists Resources (`{ id, accessRules, data }`) and defines a single consistency model
|
|
613
|
+
* (how a write mutates `data`). It ships as a pair, like a transport: a {@link ServerStore}
|
|
614
|
+
* (persistence + change-notify) and a {@link ClientStore} (a reactive local replica). super-line core
|
|
615
|
+
* relays opaque {@link StoreChange}s between the halves and enforces access — it never parses `update`.
|
|
616
|
+
* "One plumbing, two consistency models": a last-writer-wins memory store and a merging CRDT store are
|
|
617
|
+
* siblings behind this interface.
|
|
618
|
+
*/
|
|
619
|
+
type Awaitable<T> = T | Promise<T>;
|
|
620
|
+
/**
|
|
621
|
+
* Return a structural clone of `root` with the value at `path` removed — the surgical-delete primitive shared
|
|
622
|
+
* by every Store's replica halves. Clones only along the path (not a deep clone): fed to a diff-and-patch
|
|
623
|
+
* `set`, only the removed key is rewritten, so concurrent edits to sibling keys still merge. Never mutates
|
|
624
|
+
* `root` (the live snapshot must stay intact). `path === []` returns `root` unchanged.
|
|
625
|
+
*/
|
|
626
|
+
declare const removeAtPath: (root: unknown, path: (string | number)[]) => unknown;
|
|
627
|
+
/** The ACL identity a Resource's access is keyed by (`identify(conn) ?? conn.id`). */
|
|
628
|
+
type Principal = string;
|
|
629
|
+
/** Per-principal capabilities on a Resource. */
|
|
630
|
+
interface Perms {
|
|
631
|
+
read: boolean;
|
|
632
|
+
write: boolean;
|
|
633
|
+
}
|
|
634
|
+
/** A Resource's access map: which {@link Principal} may read/write. Server-authoritative, deny-by-default. */
|
|
635
|
+
type AccessRules = Record<Principal, Perms>;
|
|
636
|
+
/** The unit a Store persists. `data` is opaque to core (a CRDT doc for a merging store, plain JSON for LWW). */
|
|
637
|
+
interface Resource<T = unknown> {
|
|
638
|
+
id: string;
|
|
639
|
+
accessRules: AccessRules;
|
|
640
|
+
data: T;
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* What a Store emits when a Resource mutates — and the symmetric shape a write carries IN.
|
|
644
|
+
* `update` is a store-DEFINED opaque payload (a CRDT delta, or a full JSON value for last-writer-wins);
|
|
645
|
+
* core relays it without parsing (base64 it if it's bytes under the JSON serializer). `origin` is the
|
|
646
|
+
* per-writer id used for echo-break — never the {@link Principal}, never the CRDT actor id.
|
|
647
|
+
*/
|
|
648
|
+
interface StoreChange {
|
|
649
|
+
id: string;
|
|
650
|
+
update: unknown;
|
|
651
|
+
origin: string;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* The server half of a Store pair: persistence + the consistency model + change-notification.
|
|
655
|
+
* It does NOT enforce access (core does) and does NOT touch the wire. `apply` interprets a
|
|
656
|
+
* {@link StoreChange} per its consistency model (LWW replace vs CRDT merge); every applied mutation —
|
|
657
|
+
* client write, server co-write, or relayed remote change — must surface through {@link ServerStore.onChange},
|
|
658
|
+
* which is core's single fan-out source.
|
|
659
|
+
*/
|
|
660
|
+
interface ServerStore {
|
|
661
|
+
/**
|
|
662
|
+
* How cross-node sync happens: `relay` (core relays Changes over the adapter; each node a replica)
|
|
663
|
+
* or `self` (the store owns a shared backend and core fans only to local subscribers).
|
|
664
|
+
*/
|
|
665
|
+
readonly clustering: 'relay' | 'self';
|
|
666
|
+
/** Current snapshot of a Resource (for catch-up on subscribe), or undefined if absent. */
|
|
667
|
+
read(id: string): Awaitable<Resource | undefined>;
|
|
668
|
+
/** Create a Resource with initial data + access rules (server-authoritative). */
|
|
669
|
+
create(id: string, data: unknown, accessRules: AccessRules): Awaitable<void>;
|
|
670
|
+
/** Apply an inbound Change — replace (LWW) or merge (CRDT), the store's choice. */
|
|
671
|
+
apply(change: StoreChange): Awaitable<void>;
|
|
672
|
+
/** Replace a Resource's access rules. */
|
|
673
|
+
setAccess(id: string, accessRules: AccessRules): Awaitable<void>;
|
|
674
|
+
/** Remove a Resource. */
|
|
675
|
+
delete(id: string): Awaitable<void>;
|
|
676
|
+
/** All Resource ids in this store (core ACL-filters before returning ids to a client). */
|
|
677
|
+
list(): Awaitable<string[]>;
|
|
678
|
+
/** Subscribe to every applied mutation — the single fan-out source. Returns an unsubscribe fn. */
|
|
679
|
+
onChange(cb: (change: StoreChange) => void): () => void;
|
|
680
|
+
/**
|
|
681
|
+
* Open a reactive in-process replica over a Resource's canonical state — the server-side co-writer.
|
|
682
|
+
* Optional; stores opt in (those that don't, surface as "reactive open not supported"). Mutations made
|
|
683
|
+
* through it fan out via {@link ServerStore.onChange} exactly like {@link ServerStore.apply}. `origin`
|
|
684
|
+
* (default `"server"`) is stamped on its fan-out Changes for echo-break + inspector attribution.
|
|
685
|
+
*/
|
|
686
|
+
open?(id: string, opts?: {
|
|
687
|
+
origin?: string;
|
|
688
|
+
}): ServerReplica;
|
|
689
|
+
/** Release any resources held by the store. */
|
|
690
|
+
close?(): Awaitable<void>;
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* The client half of a Store pair: a reactive local replica per opened Resource. A last-writer-wins
|
|
694
|
+
* client wraps a plain value; a CRDT client wraps super-store's `StoreValue` and merges deltas.
|
|
695
|
+
*/
|
|
696
|
+
interface ClientStore {
|
|
697
|
+
/** This client's per-writer id, stamped as {@link StoreChange.origin} for echo-break. */
|
|
698
|
+
readonly origin: string;
|
|
699
|
+
/** Open a reactive replica for one Resource. */
|
|
700
|
+
open(id: string): ResourceReplica;
|
|
701
|
+
/** Release any resources held by the store. */
|
|
702
|
+
close?(): void;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* A reactive handle over one opened Resource (mirrors super-store's `StoreValue` surface).
|
|
706
|
+
* `set`/`update` return the {@link StoreChange} to send up (null on a no-op); `applyRemote` merges an
|
|
707
|
+
* inbound Change (own-origin merges are idempotent / no-ops); `seed` hydrates the catch-up snapshot.
|
|
708
|
+
*/
|
|
709
|
+
interface ResourceReplica {
|
|
710
|
+
getSnapshot(): unknown;
|
|
711
|
+
subscribe(cb: () => void): () => void;
|
|
712
|
+
set(data: unknown): StoreChange | null;
|
|
713
|
+
update(partial: unknown): StoreChange | null;
|
|
714
|
+
/** Remove the value at `path` (a surgical key removal that merges, unlike a full-doc `set`). */
|
|
715
|
+
delete(path: (string | number)[]): StoreChange | null;
|
|
716
|
+
applyRemote(change: StoreChange): void;
|
|
717
|
+
seed(snapshot: unknown): void;
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* A reactive **server-side** replica over one Resource's canonical state — the server half's mirror of
|
|
721
|
+
* {@link ResourceReplica}, simpler because the server mutates canonical state directly: there is no wire to
|
|
722
|
+
* send up (no return Change to forward) and no second copy to reconcile (no `applyRemote`/`seed`). `set`/
|
|
723
|
+
* `update`/`delete` mutate canonical state in place and fan out through {@link ServerStore.onChange}; reads
|
|
724
|
+
* are live and `subscribe` reflects every applied mutation (local co-writes AND relayed remote Changes).
|
|
725
|
+
* Returned by {@link ServerStore.open}; surfaced to apps as `srv.store(name).open(id)`.
|
|
726
|
+
*/
|
|
727
|
+
interface ServerReplica {
|
|
728
|
+
getSnapshot(): unknown;
|
|
729
|
+
subscribe(cb: () => void): () => void;
|
|
730
|
+
set(data: unknown): void;
|
|
731
|
+
update(partial: unknown): void;
|
|
732
|
+
delete(path: (string | number)[]): void;
|
|
733
|
+
close(): void;
|
|
734
|
+
}
|
|
735
|
+
|
|
545
736
|
/**
|
|
546
737
|
* The client↔server transport seam. A transport moves opaque encoded bytes over a
|
|
547
738
|
* LOGICAL connection and hides all physical churn (reconnects, SSE's dual channel,
|
|
@@ -620,4 +811,4 @@ interface ClientTransport {
|
|
|
620
811
|
}): RawConn;
|
|
621
812
|
}
|
|
622
813
|
|
|
623
|
-
export { type Adapter, type AnyData, type AuthOutcome, type ClientFrame, type ClientInput, type ClientTransport, type ConnDescriptor, type ConnView, type Contract, type DataOf, type Directional, type EmitData, type ErrFrame, type ErrorCode, type EventData, type Events, type EvtFrame, type Frame, type Handshake, INSPECTOR_ROLE, INSPECTOR_SUBPROTOCOL, type InferIn, type InferOut, type InspectedContract, type InspectedDirectional, type InspectedMessage, InspectorContract, type InspectorEvent, type MessageFlavor, type NodeStat, type NodeView, type Output, PROTOCOL, type PingFrame, type PongFrame, type PresenceStore, type PubFrame, type RawConn, type ReqFrame, type RequestDef, type Requests, type ResFrame, type RoleBlock, type RoleOf, type RoleRequests, type RoleTopics, type SErrFrame, type SReqFrame, type SResFrame, type Schema, type SchemaConverter, type Serializer, type ServerEntry, type ServerFrame, type ServerInput, type ServerMessageDef, type ServerMessages, type ServerRequestDef, type ServerRequests, type ServerTransport, type SharedEvents, type SharedRequests, type SharedServerRequests, type SharedTopics, type SubFrame, SuperLineError, type SuperLineErrorCode, type Topics, type UnsubFrame, classifyContract, defineContract, jsonSerializer, validate, validateSync };
|
|
814
|
+
export { type AccessRules, type Adapter, type AnyData, type AuthOutcome, type ClientFrame, type ClientInput, type ClientStore, type ClientTransport, type ConnDescriptor, type ConnView, type Contract, type DataOf, type Directional, type EmitData, type ErrFrame, type ErrorCode, type EventData, type Events, type EvtFrame, type Frame, type Handshake, INSPECTOR_ROLE, INSPECTOR_SUBPROTOCOL, type InferIn, type InferOut, type InspectedContract, type InspectedDirectional, type InspectedMessage, InspectorContract, type InspectorEvent, type MessageFlavor, type NodeStat, type NodeView, type Output, PROTOCOL, type Perms, type PingFrame, type PongFrame, type PresenceStore, type Principal, type PubFrame, type RawConn, type ReqFrame, type RequestDef, type Requests, type ResFrame, type Resource, type ResourceReplica, type RoleBlock, type RoleOf, type RoleRequests, type RoleTopics, type SChangeFrame, type SCloseFrame, type SErrFrame, type SOpenFrame, type SReadFrame, type SReqFrame, type SResFrame, type SWriteFrame, type Schema, type SchemaConverter, type Serializer, type ServerEntry, type ServerFrame, type ServerInput, type ServerMessageDef, type ServerMessages, type ServerReplica, type ServerRequestDef, type ServerRequests, type ServerStore, type ServerTransport, type SharedEvents, type SharedRequests, type SharedServerRequests, type SharedTopics, type StoreChange, type SubFrame, SuperLineError, type SuperLineErrorCode, type Topics, type UnsubFrame, classifyContract, defineContract, jsonSerializer, removeAtPath, validate, validateSync };
|
package/dist/index.d.ts
CHANGED
|
@@ -418,6 +418,37 @@ type InspectorEvent = {
|
|
|
418
418
|
ok: boolean;
|
|
419
419
|
output?: unknown;
|
|
420
420
|
error?: MessageError;
|
|
421
|
+
} | {
|
|
422
|
+
type: 'store.write';
|
|
423
|
+
store: string;
|
|
424
|
+
id: string;
|
|
425
|
+
origin: string;
|
|
426
|
+
connId?: string;
|
|
427
|
+
data: unknown;
|
|
428
|
+
} | {
|
|
429
|
+
type: 'store.grant';
|
|
430
|
+
store: string;
|
|
431
|
+
id: string;
|
|
432
|
+
principal: string;
|
|
433
|
+
perms: {
|
|
434
|
+
read: boolean;
|
|
435
|
+
write: boolean;
|
|
436
|
+
};
|
|
437
|
+
} | {
|
|
438
|
+
type: 'store.revoke';
|
|
439
|
+
store: string;
|
|
440
|
+
id: string;
|
|
441
|
+
principal: string;
|
|
442
|
+
} | {
|
|
443
|
+
type: 'store.subscribe';
|
|
444
|
+
connId: string;
|
|
445
|
+
store: string;
|
|
446
|
+
id: string;
|
|
447
|
+
} | {
|
|
448
|
+
type: 'store.unsubscribe';
|
|
449
|
+
connId: string;
|
|
450
|
+
store: string;
|
|
451
|
+
id: string;
|
|
421
452
|
};
|
|
422
453
|
/**
|
|
423
454
|
* The fixed, library-owned contract describing the inspector surface. Identical for every
|
|
@@ -509,7 +540,32 @@ interface PingFrame {
|
|
|
509
540
|
interface PongFrame {
|
|
510
541
|
t: 'pong';
|
|
511
542
|
}
|
|
512
|
-
|
|
543
|
+
interface SOpenFrame {
|
|
544
|
+
t: 'sopen';
|
|
545
|
+
i: number;
|
|
546
|
+
n: string;
|
|
547
|
+
id: string;
|
|
548
|
+
}
|
|
549
|
+
interface SCloseFrame {
|
|
550
|
+
t: 'sclose';
|
|
551
|
+
n: string;
|
|
552
|
+
id: string;
|
|
553
|
+
}
|
|
554
|
+
interface SWriteFrame {
|
|
555
|
+
t: 'swr';
|
|
556
|
+
i: number;
|
|
557
|
+
n: string;
|
|
558
|
+
id: string;
|
|
559
|
+
u: unknown;
|
|
560
|
+
o: string;
|
|
561
|
+
}
|
|
562
|
+
interface SReadFrame {
|
|
563
|
+
t: 'srd';
|
|
564
|
+
i: number;
|
|
565
|
+
n: string;
|
|
566
|
+
id: string;
|
|
567
|
+
}
|
|
568
|
+
type ClientFrame = ReqFrame | SubFrame | UnsubFrame | SResFrame | SErrFrame | SOpenFrame | SCloseFrame | SWriteFrame | SReadFrame | PingFrame | PongFrame;
|
|
513
569
|
interface ResFrame {
|
|
514
570
|
t: 'res';
|
|
515
571
|
i: number;
|
|
@@ -539,9 +595,144 @@ interface SReqFrame {
|
|
|
539
595
|
m: string;
|
|
540
596
|
d: unknown;
|
|
541
597
|
}
|
|
542
|
-
|
|
598
|
+
interface SChangeFrame {
|
|
599
|
+
t: 'sch';
|
|
600
|
+
n: string;
|
|
601
|
+
id: string;
|
|
602
|
+
u: unknown;
|
|
603
|
+
o: string;
|
|
604
|
+
nd?: string;
|
|
605
|
+
}
|
|
606
|
+
type ServerFrame = ResFrame | ErrFrame | EvtFrame | PubFrame | SReqFrame | SChangeFrame | PingFrame | PongFrame;
|
|
543
607
|
type Frame = ClientFrame | ServerFrame;
|
|
544
608
|
|
|
609
|
+
/**
|
|
610
|
+
* Store — super-line's pluggable persisted-state seam.
|
|
611
|
+
*
|
|
612
|
+
* A Store persists Resources (`{ id, accessRules, data }`) and defines a single consistency model
|
|
613
|
+
* (how a write mutates `data`). It ships as a pair, like a transport: a {@link ServerStore}
|
|
614
|
+
* (persistence + change-notify) and a {@link ClientStore} (a reactive local replica). super-line core
|
|
615
|
+
* relays opaque {@link StoreChange}s between the halves and enforces access — it never parses `update`.
|
|
616
|
+
* "One plumbing, two consistency models": a last-writer-wins memory store and a merging CRDT store are
|
|
617
|
+
* siblings behind this interface.
|
|
618
|
+
*/
|
|
619
|
+
type Awaitable<T> = T | Promise<T>;
|
|
620
|
+
/**
|
|
621
|
+
* Return a structural clone of `root` with the value at `path` removed — the surgical-delete primitive shared
|
|
622
|
+
* by every Store's replica halves. Clones only along the path (not a deep clone): fed to a diff-and-patch
|
|
623
|
+
* `set`, only the removed key is rewritten, so concurrent edits to sibling keys still merge. Never mutates
|
|
624
|
+
* `root` (the live snapshot must stay intact). `path === []` returns `root` unchanged.
|
|
625
|
+
*/
|
|
626
|
+
declare const removeAtPath: (root: unknown, path: (string | number)[]) => unknown;
|
|
627
|
+
/** The ACL identity a Resource's access is keyed by (`identify(conn) ?? conn.id`). */
|
|
628
|
+
type Principal = string;
|
|
629
|
+
/** Per-principal capabilities on a Resource. */
|
|
630
|
+
interface Perms {
|
|
631
|
+
read: boolean;
|
|
632
|
+
write: boolean;
|
|
633
|
+
}
|
|
634
|
+
/** A Resource's access map: which {@link Principal} may read/write. Server-authoritative, deny-by-default. */
|
|
635
|
+
type AccessRules = Record<Principal, Perms>;
|
|
636
|
+
/** The unit a Store persists. `data` is opaque to core (a CRDT doc for a merging store, plain JSON for LWW). */
|
|
637
|
+
interface Resource<T = unknown> {
|
|
638
|
+
id: string;
|
|
639
|
+
accessRules: AccessRules;
|
|
640
|
+
data: T;
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* What a Store emits when a Resource mutates — and the symmetric shape a write carries IN.
|
|
644
|
+
* `update` is a store-DEFINED opaque payload (a CRDT delta, or a full JSON value for last-writer-wins);
|
|
645
|
+
* core relays it without parsing (base64 it if it's bytes under the JSON serializer). `origin` is the
|
|
646
|
+
* per-writer id used for echo-break — never the {@link Principal}, never the CRDT actor id.
|
|
647
|
+
*/
|
|
648
|
+
interface StoreChange {
|
|
649
|
+
id: string;
|
|
650
|
+
update: unknown;
|
|
651
|
+
origin: string;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* The server half of a Store pair: persistence + the consistency model + change-notification.
|
|
655
|
+
* It does NOT enforce access (core does) and does NOT touch the wire. `apply` interprets a
|
|
656
|
+
* {@link StoreChange} per its consistency model (LWW replace vs CRDT merge); every applied mutation —
|
|
657
|
+
* client write, server co-write, or relayed remote change — must surface through {@link ServerStore.onChange},
|
|
658
|
+
* which is core's single fan-out source.
|
|
659
|
+
*/
|
|
660
|
+
interface ServerStore {
|
|
661
|
+
/**
|
|
662
|
+
* How cross-node sync happens: `relay` (core relays Changes over the adapter; each node a replica)
|
|
663
|
+
* or `self` (the store owns a shared backend and core fans only to local subscribers).
|
|
664
|
+
*/
|
|
665
|
+
readonly clustering: 'relay' | 'self';
|
|
666
|
+
/** Current snapshot of a Resource (for catch-up on subscribe), or undefined if absent. */
|
|
667
|
+
read(id: string): Awaitable<Resource | undefined>;
|
|
668
|
+
/** Create a Resource with initial data + access rules (server-authoritative). */
|
|
669
|
+
create(id: string, data: unknown, accessRules: AccessRules): Awaitable<void>;
|
|
670
|
+
/** Apply an inbound Change — replace (LWW) or merge (CRDT), the store's choice. */
|
|
671
|
+
apply(change: StoreChange): Awaitable<void>;
|
|
672
|
+
/** Replace a Resource's access rules. */
|
|
673
|
+
setAccess(id: string, accessRules: AccessRules): Awaitable<void>;
|
|
674
|
+
/** Remove a Resource. */
|
|
675
|
+
delete(id: string): Awaitable<void>;
|
|
676
|
+
/** All Resource ids in this store (core ACL-filters before returning ids to a client). */
|
|
677
|
+
list(): Awaitable<string[]>;
|
|
678
|
+
/** Subscribe to every applied mutation — the single fan-out source. Returns an unsubscribe fn. */
|
|
679
|
+
onChange(cb: (change: StoreChange) => void): () => void;
|
|
680
|
+
/**
|
|
681
|
+
* Open a reactive in-process replica over a Resource's canonical state — the server-side co-writer.
|
|
682
|
+
* Optional; stores opt in (those that don't, surface as "reactive open not supported"). Mutations made
|
|
683
|
+
* through it fan out via {@link ServerStore.onChange} exactly like {@link ServerStore.apply}. `origin`
|
|
684
|
+
* (default `"server"`) is stamped on its fan-out Changes for echo-break + inspector attribution.
|
|
685
|
+
*/
|
|
686
|
+
open?(id: string, opts?: {
|
|
687
|
+
origin?: string;
|
|
688
|
+
}): ServerReplica;
|
|
689
|
+
/** Release any resources held by the store. */
|
|
690
|
+
close?(): Awaitable<void>;
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* The client half of a Store pair: a reactive local replica per opened Resource. A last-writer-wins
|
|
694
|
+
* client wraps a plain value; a CRDT client wraps super-store's `StoreValue` and merges deltas.
|
|
695
|
+
*/
|
|
696
|
+
interface ClientStore {
|
|
697
|
+
/** This client's per-writer id, stamped as {@link StoreChange.origin} for echo-break. */
|
|
698
|
+
readonly origin: string;
|
|
699
|
+
/** Open a reactive replica for one Resource. */
|
|
700
|
+
open(id: string): ResourceReplica;
|
|
701
|
+
/** Release any resources held by the store. */
|
|
702
|
+
close?(): void;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* A reactive handle over one opened Resource (mirrors super-store's `StoreValue` surface).
|
|
706
|
+
* `set`/`update` return the {@link StoreChange} to send up (null on a no-op); `applyRemote` merges an
|
|
707
|
+
* inbound Change (own-origin merges are idempotent / no-ops); `seed` hydrates the catch-up snapshot.
|
|
708
|
+
*/
|
|
709
|
+
interface ResourceReplica {
|
|
710
|
+
getSnapshot(): unknown;
|
|
711
|
+
subscribe(cb: () => void): () => void;
|
|
712
|
+
set(data: unknown): StoreChange | null;
|
|
713
|
+
update(partial: unknown): StoreChange | null;
|
|
714
|
+
/** Remove the value at `path` (a surgical key removal that merges, unlike a full-doc `set`). */
|
|
715
|
+
delete(path: (string | number)[]): StoreChange | null;
|
|
716
|
+
applyRemote(change: StoreChange): void;
|
|
717
|
+
seed(snapshot: unknown): void;
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* A reactive **server-side** replica over one Resource's canonical state — the server half's mirror of
|
|
721
|
+
* {@link ResourceReplica}, simpler because the server mutates canonical state directly: there is no wire to
|
|
722
|
+
* send up (no return Change to forward) and no second copy to reconcile (no `applyRemote`/`seed`). `set`/
|
|
723
|
+
* `update`/`delete` mutate canonical state in place and fan out through {@link ServerStore.onChange}; reads
|
|
724
|
+
* are live and `subscribe` reflects every applied mutation (local co-writes AND relayed remote Changes).
|
|
725
|
+
* Returned by {@link ServerStore.open}; surfaced to apps as `srv.store(name).open(id)`.
|
|
726
|
+
*/
|
|
727
|
+
interface ServerReplica {
|
|
728
|
+
getSnapshot(): unknown;
|
|
729
|
+
subscribe(cb: () => void): () => void;
|
|
730
|
+
set(data: unknown): void;
|
|
731
|
+
update(partial: unknown): void;
|
|
732
|
+
delete(path: (string | number)[]): void;
|
|
733
|
+
close(): void;
|
|
734
|
+
}
|
|
735
|
+
|
|
545
736
|
/**
|
|
546
737
|
* The client↔server transport seam. A transport moves opaque encoded bytes over a
|
|
547
738
|
* LOGICAL connection and hides all physical churn (reconnects, SSE's dual channel,
|
|
@@ -620,4 +811,4 @@ interface ClientTransport {
|
|
|
620
811
|
}): RawConn;
|
|
621
812
|
}
|
|
622
813
|
|
|
623
|
-
export { type Adapter, type AnyData, type AuthOutcome, type ClientFrame, type ClientInput, type ClientTransport, type ConnDescriptor, type ConnView, type Contract, type DataOf, type Directional, type EmitData, type ErrFrame, type ErrorCode, type EventData, type Events, type EvtFrame, type Frame, type Handshake, INSPECTOR_ROLE, INSPECTOR_SUBPROTOCOL, type InferIn, type InferOut, type InspectedContract, type InspectedDirectional, type InspectedMessage, InspectorContract, type InspectorEvent, type MessageFlavor, type NodeStat, type NodeView, type Output, PROTOCOL, type PingFrame, type PongFrame, type PresenceStore, type PubFrame, type RawConn, type ReqFrame, type RequestDef, type Requests, type ResFrame, type RoleBlock, type RoleOf, type RoleRequests, type RoleTopics, type SErrFrame, type SReqFrame, type SResFrame, type Schema, type SchemaConverter, type Serializer, type ServerEntry, type ServerFrame, type ServerInput, type ServerMessageDef, type ServerMessages, type ServerRequestDef, type ServerRequests, type ServerTransport, type SharedEvents, type SharedRequests, type SharedServerRequests, type SharedTopics, type SubFrame, SuperLineError, type SuperLineErrorCode, type Topics, type UnsubFrame, classifyContract, defineContract, jsonSerializer, validate, validateSync };
|
|
814
|
+
export { type AccessRules, type Adapter, type AnyData, type AuthOutcome, type ClientFrame, type ClientInput, type ClientStore, type ClientTransport, type ConnDescriptor, type ConnView, type Contract, type DataOf, type Directional, type EmitData, type ErrFrame, type ErrorCode, type EventData, type Events, type EvtFrame, type Frame, type Handshake, INSPECTOR_ROLE, INSPECTOR_SUBPROTOCOL, type InferIn, type InferOut, type InspectedContract, type InspectedDirectional, type InspectedMessage, InspectorContract, type InspectorEvent, type MessageFlavor, type NodeStat, type NodeView, type Output, PROTOCOL, type Perms, type PingFrame, type PongFrame, type PresenceStore, type Principal, type PubFrame, type RawConn, type ReqFrame, type RequestDef, type Requests, type ResFrame, type Resource, type ResourceReplica, type RoleBlock, type RoleOf, type RoleRequests, type RoleTopics, type SChangeFrame, type SCloseFrame, type SErrFrame, type SOpenFrame, type SReadFrame, type SReqFrame, type SResFrame, type SWriteFrame, type Schema, type SchemaConverter, type Serializer, type ServerEntry, type ServerFrame, type ServerInput, type ServerMessageDef, type ServerMessages, type ServerReplica, type ServerRequestDef, type ServerRequests, type ServerStore, type ServerTransport, type SharedEvents, type SharedRequests, type SharedServerRequests, type SharedTopics, type StoreChange, type SubFrame, SuperLineError, type SuperLineErrorCode, type Topics, type UnsubFrame, classifyContract, defineContract, jsonSerializer, removeAtPath, validate, validateSync };
|
package/dist/index.js
CHANGED
|
@@ -118,6 +118,23 @@ function classifyContract(contract, convert) {
|
|
|
118
118
|
|
|
119
119
|
// src/wire.ts
|
|
120
120
|
var PROTOCOL = "superline.v1";
|
|
121
|
+
|
|
122
|
+
// src/store.ts
|
|
123
|
+
var removeAtPath = (root, path) => {
|
|
124
|
+
if (path.length === 0) return root;
|
|
125
|
+
if (typeof root !== "object" || root === null) return root;
|
|
126
|
+
const [head, ...rest] = path;
|
|
127
|
+
if (Array.isArray(root)) {
|
|
128
|
+
const next2 = root.slice();
|
|
129
|
+
if (rest.length === 0) next2.splice(Number(head), 1);
|
|
130
|
+
else next2[Number(head)] = removeAtPath(next2[Number(head)], rest);
|
|
131
|
+
return next2;
|
|
132
|
+
}
|
|
133
|
+
const next = { ...root };
|
|
134
|
+
if (rest.length === 0) delete next[head];
|
|
135
|
+
else next[head] = removeAtPath(next[head], rest);
|
|
136
|
+
return next;
|
|
137
|
+
};
|
|
121
138
|
export {
|
|
122
139
|
INSPECTOR_ROLE,
|
|
123
140
|
INSPECTOR_SUBPROTOCOL,
|
|
@@ -127,6 +144,7 @@ export {
|
|
|
127
144
|
classifyContract,
|
|
128
145
|
defineContract,
|
|
129
146
|
jsonSerializer,
|
|
147
|
+
removeAtPath,
|
|
130
148
|
validate,
|
|
131
149
|
validateSync
|
|
132
150
|
};
|