@super-line/core 0.5.0 → 0.7.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 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
  });
@@ -102,7 +103,10 @@ var InspectorContract = defineContract({
102
103
  getTopology: { input: s(), output: s() },
103
104
  listConnections: { input: s(), output: s() },
104
105
  getNode: { input: s(), output: s() },
105
- getConn: { input: s(), output: s() }
106
+ getConn: { input: s(), output: s() },
107
+ listStores: { input: s(), output: s() },
108
+ listResources: { input: s(), output: s() },
109
+ readResource: { input: s(), output: s() }
106
110
  },
107
111
  serverToClient: {
108
112
  events: { payload: s(), subscribe: true }
@@ -153,6 +157,23 @@ function classifyContract(contract, convert) {
153
157
 
154
158
  // src/wire.ts
155
159
  var PROTOCOL = "superline.v1";
160
+
161
+ // src/store.ts
162
+ var removeAtPath = (root, path) => {
163
+ if (path.length === 0) return root;
164
+ if (typeof root !== "object" || root === null) return root;
165
+ const [head, ...rest] = path;
166
+ if (Array.isArray(root)) {
167
+ const next2 = root.slice();
168
+ if (rest.length === 0) next2.splice(Number(head), 1);
169
+ else next2[Number(head)] = removeAtPath(next2[Number(head)], rest);
170
+ return next2;
171
+ }
172
+ const next = { ...root };
173
+ if (rest.length === 0) delete next[head];
174
+ else next[head] = removeAtPath(next[head], rest);
175
+ return next;
176
+ };
156
177
  // Annotate the CommonJS export names for ESM import in node:
157
178
  0 && (module.exports = {
158
179
  INSPECTOR_ROLE,
@@ -163,6 +184,7 @@ var PROTOCOL = "superline.v1";
163
184
  classifyContract,
164
185
  defineContract,
165
186
  jsonSerializer,
187
+ removeAtPath,
166
188
  validate,
167
189
  validateSync
168
190
  });
package/dist/index.d.cts CHANGED
@@ -344,6 +344,19 @@ interface ConnView {
344
344
  /** Whether ctx/data could be read (false for conns on another node). */
345
345
  ctxAvailable: boolean;
346
346
  }
347
+ /** A configured Store — what `listStores` returns. `model` is the backend's declared consistency model, if any. */
348
+ interface StoreInfo {
349
+ name: string;
350
+ model?: 'lww' | 'crdt';
351
+ }
352
+ /** A Resource's current value — what `readResource` returns. Materialized + safe-serialized server-side. */
353
+ interface StoreResourceView {
354
+ data: unknown;
355
+ accessRules: Record<string, {
356
+ read: boolean;
357
+ write: boolean;
358
+ }>;
359
+ }
347
360
  /** A failed response/reply, carried on `msg.response` / `msg.serverReply`. */
348
361
  interface MessageError {
349
362
  code: string;
@@ -418,6 +431,14 @@ type InspectorEvent = {
418
431
  ok: boolean;
419
432
  output?: unknown;
420
433
  error?: MessageError;
434
+ } | {
435
+ type: 'store.create';
436
+ store: string;
437
+ id: string;
438
+ } | {
439
+ type: 'store.delete';
440
+ store: string;
441
+ id: string;
421
442
  } | {
422
443
  type: 'store.write';
423
444
  store: string;
@@ -483,6 +504,28 @@ declare const InspectorContract: {
483
504
  }>;
484
505
  readonly output: StandardSchemaV1<ConnView, ConnView>;
485
506
  };
507
+ readonly listStores: {
508
+ readonly input: StandardSchemaV1<void, void>;
509
+ readonly output: StandardSchemaV1<StoreInfo[], StoreInfo[]>;
510
+ };
511
+ readonly listResources: {
512
+ readonly input: StandardSchemaV1<{
513
+ store: string;
514
+ }, {
515
+ store: string;
516
+ }>;
517
+ readonly output: StandardSchemaV1<string[], string[]>;
518
+ };
519
+ readonly readResource: {
520
+ readonly input: StandardSchemaV1<{
521
+ store: string;
522
+ id: string;
523
+ }, {
524
+ store: string;
525
+ id: string;
526
+ }>;
527
+ readonly output: StandardSchemaV1<StoreResourceView, StoreResourceView>;
528
+ };
486
529
  };
487
530
  readonly serverToClient: {
488
531
  readonly events: {
@@ -617,6 +660,13 @@ type Frame = ClientFrame | ServerFrame;
617
660
  * siblings behind this interface.
618
661
  */
619
662
  type Awaitable<T> = T | Promise<T>;
663
+ /**
664
+ * Return a structural clone of `root` with the value at `path` removed — the surgical-delete primitive shared
665
+ * by every Store's replica halves. Clones only along the path (not a deep clone): fed to a diff-and-patch
666
+ * `set`, only the removed key is rewritten, so concurrent edits to sibling keys still merge. Never mutates
667
+ * `root` (the live snapshot must stay intact). `path === []` returns `root` unchanged.
668
+ */
669
+ declare const removeAtPath: (root: unknown, path: (string | number)[]) => unknown;
620
670
  /** The ACL identity a Resource's access is keyed by (`identify(conn) ?? conn.id`). */
621
671
  type Principal = string;
622
672
  /** Per-principal capabilities on a Resource. */
@@ -656,6 +706,8 @@ interface ServerStore {
656
706
  * or `self` (the store owns a shared backend and core fans only to local subscribers).
657
707
  */
658
708
  readonly clustering: 'relay' | 'self';
709
+ /** Consistency model, surfaced to the inspector for display. Optional; omit if a backend has no clean label. */
710
+ readonly model?: 'lww' | 'crdt';
659
711
  /** Current snapshot of a Resource (for catch-up on subscribe), or undefined if absent. */
660
712
  read(id: string): Awaitable<Resource | undefined>;
661
713
  /** Create a Resource with initial data + access rules (server-authoritative). */
@@ -670,6 +722,15 @@ interface ServerStore {
670
722
  list(): Awaitable<string[]>;
671
723
  /** Subscribe to every applied mutation — the single fan-out source. Returns an unsubscribe fn. */
672
724
  onChange(cb: (change: StoreChange) => void): () => void;
725
+ /**
726
+ * Open a reactive in-process replica over a Resource's canonical state — the server-side co-writer.
727
+ * Optional; stores opt in (those that don't, surface as "reactive open not supported"). Mutations made
728
+ * through it fan out via {@link ServerStore.onChange} exactly like {@link ServerStore.apply}. `origin`
729
+ * (default `"server"`) is stamped on its fan-out Changes for echo-break + inspector attribution.
730
+ */
731
+ open?(id: string, opts?: {
732
+ origin?: string;
733
+ }): ServerReplica;
673
734
  /** Release any resources held by the store. */
674
735
  close?(): Awaitable<void>;
675
736
  }
@@ -695,9 +756,27 @@ interface ResourceReplica {
695
756
  subscribe(cb: () => void): () => void;
696
757
  set(data: unknown): StoreChange | null;
697
758
  update(partial: unknown): StoreChange | null;
759
+ /** Remove the value at `path` (a surgical key removal that merges, unlike a full-doc `set`). */
760
+ delete(path: (string | number)[]): StoreChange | null;
698
761
  applyRemote(change: StoreChange): void;
699
762
  seed(snapshot: unknown): void;
700
763
  }
764
+ /**
765
+ * A reactive **server-side** replica over one Resource's canonical state — the server half's mirror of
766
+ * {@link ResourceReplica}, simpler because the server mutates canonical state directly: there is no wire to
767
+ * send up (no return Change to forward) and no second copy to reconcile (no `applyRemote`/`seed`). `set`/
768
+ * `update`/`delete` mutate canonical state in place and fan out through {@link ServerStore.onChange}; reads
769
+ * are live and `subscribe` reflects every applied mutation (local co-writes AND relayed remote Changes).
770
+ * Returned by {@link ServerStore.open}; surfaced to apps as `srv.store(name).open(id)`.
771
+ */
772
+ interface ServerReplica {
773
+ getSnapshot(): unknown;
774
+ subscribe(cb: () => void): () => void;
775
+ set(data: unknown): void;
776
+ update(partial: unknown): void;
777
+ delete(path: (string | number)[]): void;
778
+ close(): void;
779
+ }
701
780
 
702
781
  /**
703
782
  * The client↔server transport seam. A transport moves opaque encoded bytes over a
@@ -777,4 +856,4 @@ interface ClientTransport {
777
856
  }): RawConn;
778
857
  }
779
858
 
780
- 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 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, validate, validateSync };
859
+ 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 StoreInfo, type StoreResourceView, type SubFrame, SuperLineError, type SuperLineErrorCode, type Topics, type UnsubFrame, classifyContract, defineContract, jsonSerializer, removeAtPath, validate, validateSync };
package/dist/index.d.ts CHANGED
@@ -344,6 +344,19 @@ interface ConnView {
344
344
  /** Whether ctx/data could be read (false for conns on another node). */
345
345
  ctxAvailable: boolean;
346
346
  }
347
+ /** A configured Store — what `listStores` returns. `model` is the backend's declared consistency model, if any. */
348
+ interface StoreInfo {
349
+ name: string;
350
+ model?: 'lww' | 'crdt';
351
+ }
352
+ /** A Resource's current value — what `readResource` returns. Materialized + safe-serialized server-side. */
353
+ interface StoreResourceView {
354
+ data: unknown;
355
+ accessRules: Record<string, {
356
+ read: boolean;
357
+ write: boolean;
358
+ }>;
359
+ }
347
360
  /** A failed response/reply, carried on `msg.response` / `msg.serverReply`. */
348
361
  interface MessageError {
349
362
  code: string;
@@ -418,6 +431,14 @@ type InspectorEvent = {
418
431
  ok: boolean;
419
432
  output?: unknown;
420
433
  error?: MessageError;
434
+ } | {
435
+ type: 'store.create';
436
+ store: string;
437
+ id: string;
438
+ } | {
439
+ type: 'store.delete';
440
+ store: string;
441
+ id: string;
421
442
  } | {
422
443
  type: 'store.write';
423
444
  store: string;
@@ -483,6 +504,28 @@ declare const InspectorContract: {
483
504
  }>;
484
505
  readonly output: StandardSchemaV1<ConnView, ConnView>;
485
506
  };
507
+ readonly listStores: {
508
+ readonly input: StandardSchemaV1<void, void>;
509
+ readonly output: StandardSchemaV1<StoreInfo[], StoreInfo[]>;
510
+ };
511
+ readonly listResources: {
512
+ readonly input: StandardSchemaV1<{
513
+ store: string;
514
+ }, {
515
+ store: string;
516
+ }>;
517
+ readonly output: StandardSchemaV1<string[], string[]>;
518
+ };
519
+ readonly readResource: {
520
+ readonly input: StandardSchemaV1<{
521
+ store: string;
522
+ id: string;
523
+ }, {
524
+ store: string;
525
+ id: string;
526
+ }>;
527
+ readonly output: StandardSchemaV1<StoreResourceView, StoreResourceView>;
528
+ };
486
529
  };
487
530
  readonly serverToClient: {
488
531
  readonly events: {
@@ -617,6 +660,13 @@ type Frame = ClientFrame | ServerFrame;
617
660
  * siblings behind this interface.
618
661
  */
619
662
  type Awaitable<T> = T | Promise<T>;
663
+ /**
664
+ * Return a structural clone of `root` with the value at `path` removed — the surgical-delete primitive shared
665
+ * by every Store's replica halves. Clones only along the path (not a deep clone): fed to a diff-and-patch
666
+ * `set`, only the removed key is rewritten, so concurrent edits to sibling keys still merge. Never mutates
667
+ * `root` (the live snapshot must stay intact). `path === []` returns `root` unchanged.
668
+ */
669
+ declare const removeAtPath: (root: unknown, path: (string | number)[]) => unknown;
620
670
  /** The ACL identity a Resource's access is keyed by (`identify(conn) ?? conn.id`). */
621
671
  type Principal = string;
622
672
  /** Per-principal capabilities on a Resource. */
@@ -656,6 +706,8 @@ interface ServerStore {
656
706
  * or `self` (the store owns a shared backend and core fans only to local subscribers).
657
707
  */
658
708
  readonly clustering: 'relay' | 'self';
709
+ /** Consistency model, surfaced to the inspector for display. Optional; omit if a backend has no clean label. */
710
+ readonly model?: 'lww' | 'crdt';
659
711
  /** Current snapshot of a Resource (for catch-up on subscribe), or undefined if absent. */
660
712
  read(id: string): Awaitable<Resource | undefined>;
661
713
  /** Create a Resource with initial data + access rules (server-authoritative). */
@@ -670,6 +722,15 @@ interface ServerStore {
670
722
  list(): Awaitable<string[]>;
671
723
  /** Subscribe to every applied mutation — the single fan-out source. Returns an unsubscribe fn. */
672
724
  onChange(cb: (change: StoreChange) => void): () => void;
725
+ /**
726
+ * Open a reactive in-process replica over a Resource's canonical state — the server-side co-writer.
727
+ * Optional; stores opt in (those that don't, surface as "reactive open not supported"). Mutations made
728
+ * through it fan out via {@link ServerStore.onChange} exactly like {@link ServerStore.apply}. `origin`
729
+ * (default `"server"`) is stamped on its fan-out Changes for echo-break + inspector attribution.
730
+ */
731
+ open?(id: string, opts?: {
732
+ origin?: string;
733
+ }): ServerReplica;
673
734
  /** Release any resources held by the store. */
674
735
  close?(): Awaitable<void>;
675
736
  }
@@ -695,9 +756,27 @@ interface ResourceReplica {
695
756
  subscribe(cb: () => void): () => void;
696
757
  set(data: unknown): StoreChange | null;
697
758
  update(partial: unknown): StoreChange | null;
759
+ /** Remove the value at `path` (a surgical key removal that merges, unlike a full-doc `set`). */
760
+ delete(path: (string | number)[]): StoreChange | null;
698
761
  applyRemote(change: StoreChange): void;
699
762
  seed(snapshot: unknown): void;
700
763
  }
764
+ /**
765
+ * A reactive **server-side** replica over one Resource's canonical state — the server half's mirror of
766
+ * {@link ResourceReplica}, simpler because the server mutates canonical state directly: there is no wire to
767
+ * send up (no return Change to forward) and no second copy to reconcile (no `applyRemote`/`seed`). `set`/
768
+ * `update`/`delete` mutate canonical state in place and fan out through {@link ServerStore.onChange}; reads
769
+ * are live and `subscribe` reflects every applied mutation (local co-writes AND relayed remote Changes).
770
+ * Returned by {@link ServerStore.open}; surfaced to apps as `srv.store(name).open(id)`.
771
+ */
772
+ interface ServerReplica {
773
+ getSnapshot(): unknown;
774
+ subscribe(cb: () => void): () => void;
775
+ set(data: unknown): void;
776
+ update(partial: unknown): void;
777
+ delete(path: (string | number)[]): void;
778
+ close(): void;
779
+ }
701
780
 
702
781
  /**
703
782
  * The client↔server transport seam. A transport moves opaque encoded bytes over a
@@ -777,4 +856,4 @@ interface ClientTransport {
777
856
  }): RawConn;
778
857
  }
779
858
 
780
- 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 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, validate, validateSync };
859
+ 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 StoreInfo, type StoreResourceView, type SubFrame, SuperLineError, type SuperLineErrorCode, type Topics, type UnsubFrame, classifyContract, defineContract, jsonSerializer, removeAtPath, validate, validateSync };
package/dist/index.js CHANGED
@@ -67,7 +67,10 @@ var InspectorContract = defineContract({
67
67
  getTopology: { input: s(), output: s() },
68
68
  listConnections: { input: s(), output: s() },
69
69
  getNode: { input: s(), output: s() },
70
- getConn: { input: s(), output: s() }
70
+ getConn: { input: s(), output: s() },
71
+ listStores: { input: s(), output: s() },
72
+ listResources: { input: s(), output: s() },
73
+ readResource: { input: s(), output: s() }
71
74
  },
72
75
  serverToClient: {
73
76
  events: { payload: s(), subscribe: true }
@@ -118,6 +121,23 @@ function classifyContract(contract, convert) {
118
121
 
119
122
  // src/wire.ts
120
123
  var PROTOCOL = "superline.v1";
124
+
125
+ // src/store.ts
126
+ var removeAtPath = (root, path) => {
127
+ if (path.length === 0) return root;
128
+ if (typeof root !== "object" || root === null) return root;
129
+ const [head, ...rest] = path;
130
+ if (Array.isArray(root)) {
131
+ const next2 = root.slice();
132
+ if (rest.length === 0) next2.splice(Number(head), 1);
133
+ else next2[Number(head)] = removeAtPath(next2[Number(head)], rest);
134
+ return next2;
135
+ }
136
+ const next = { ...root };
137
+ if (rest.length === 0) delete next[head];
138
+ else next[head] = removeAtPath(next[head], rest);
139
+ return next;
140
+ };
121
141
  export {
122
142
  INSPECTOR_ROLE,
123
143
  INSPECTOR_SUBPROTOCOL,
@@ -127,6 +147,7 @@ export {
127
147
  classifyContract,
128
148
  defineContract,
129
149
  jsonSerializer,
150
+ removeAtPath,
130
151
  validate,
131
152
  validateSync
132
153
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@super-line/core",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "description": "Shared contract, validation, wire protocol and errors for super-line.",
6
6
  "license": "MIT",