sietch 0.4.1 → 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.d.ts +2 -0
- package/dist/server/index.d.ts +4 -1
- package/dist/server/index.js +2 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/pg-storage.d.ts +50 -0
- package/dist/server/pg-storage.js +122 -0
- package/dist/server/pg-storage.js.map +1 -0
- package/dist/server/query-bridge.d.ts +14 -3
- package/dist/server/query-bridge.js +7 -7
- package/dist/server/query-bridge.js.map +1 -1
- package/dist/server/server.d.ts +82 -0
- package/dist/server/server.js +210 -0
- package/dist/server/server.js.map +1 -0
- package/dist/storage-node.d.ts +58 -0
- package/dist/storage-node.js +202 -0
- package/dist/storage-node.js.map +1 -0
- package/dist/storage.d.ts +5 -4
- package/dist/storage.js +21 -4
- package/dist/storage.js.map +1 -1
- package/dist/store.d.ts +3 -1
- package/dist/store.js +7 -1
- package/dist/store.js.map +1 -1
- package/dist/types.d.ts +4 -2
- package/dist/yjs-engine.d.ts +11 -0
- package/dist/yjs-engine.js +57 -0
- package/dist/yjs-engine.js.map +1 -1
- package/package.json +19 -14
package/dist/index.d.ts
CHANGED
|
@@ -9,4 +9,6 @@
|
|
|
9
9
|
*/
|
|
10
10
|
export { createStore, Store, Cursor, SubtreeCursor } from './store.js';
|
|
11
11
|
export { ValidationError, SietchError, StorageFullError, SyncError } from './errors.js';
|
|
12
|
+
export type { StorageBackend } from './storage.js';
|
|
13
|
+
export type { StorageType, StorageCapacity } from './types.js';
|
|
12
14
|
export type { CollectionConfig, CrdtValue, MutationEvent, WriteConfirmation, ReadResult, SyncStatus, SubtreeSyncStatus, SyncConfig, AclMap, AclPermissions, StoreOptions, StorageMetrics, SearchQuery, SearchResult, SearchHit, VectorSearchOptions, VectorResult, TraverseOptions, TraverseResult, PublicIdentity, LinkRequest, CypherResult, AclReferenceNode, SubscribeOptions, StorePlugin, WriteContext, } from './store.js';
|
package/dist/server/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* These modules run as a standalone Node.js service,
|
|
5
5
|
* connecting to PostgreSQL (with AGE and pgvector extensions).
|
|
6
6
|
*/
|
|
7
|
+
export { SietchServer, createServer } from './server.js';
|
|
8
|
+
export type { ServerOptions } from './server.js';
|
|
7
9
|
export { ServerAuthService } from './auth.js';
|
|
8
10
|
export type { DeviceLinkRequest, VerificationRecord, UserIdentity, LinkResult, EmailProvider } from './auth.js';
|
|
9
11
|
export { ServerAclAuthority, DefaultSigningBackend } from './acl-authority.js';
|
|
@@ -13,8 +15,9 @@ export type { AclReferenceNode } from '../acl.js';
|
|
|
13
15
|
export { ServerSyncService } from './sync-service.js';
|
|
14
16
|
export type { SubtreeAssignment, ServerSyncConfig, PeerAuthorization } from './sync-service.js';
|
|
15
17
|
export { ServerQueryBridge, InMemoryDatabaseAdapter } from './query-bridge.js';
|
|
16
|
-
export type { DatabaseAdapter } from './query-bridge.js';
|
|
18
|
+
export type { DatabaseAdapter, BridgeStateSource } from './query-bridge.js';
|
|
17
19
|
export { PostgreSQLDatabaseAdapter } from './pg-adapter.js';
|
|
20
|
+
export { PostgresStorageBackend } from './pg-storage.js';
|
|
18
21
|
export { generateKeypair, extractPublicKey } from '../crypto.js';
|
|
19
22
|
export type { HybridKeypair, HybridPublicKey } from '../crypto.js';
|
|
20
23
|
export { createObservabilityContext } from '../observability.js';
|
package/dist/server/index.js
CHANGED
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
* These modules run as a standalone Node.js service,
|
|
5
5
|
* connecting to PostgreSQL (with AGE and pgvector extensions).
|
|
6
6
|
*/
|
|
7
|
+
export { SietchServer, createServer } from './server.js';
|
|
7
8
|
export { ServerAuthService } from './auth.js';
|
|
8
9
|
export { ServerAclAuthority, DefaultSigningBackend } from './acl-authority.js';
|
|
9
10
|
export { serializeRefNode, deserializeRefNode } from '../acl.js';
|
|
10
11
|
export { ServerSyncService } from './sync-service.js';
|
|
11
12
|
export { ServerQueryBridge, InMemoryDatabaseAdapter } from './query-bridge.js';
|
|
12
13
|
export { PostgreSQLDatabaseAdapter } from './pg-adapter.js';
|
|
14
|
+
export { PostgresStorageBackend } from './pg-storage.js';
|
|
13
15
|
export { generateKeypair, extractPublicKey } from '../crypto.js';
|
|
14
16
|
export { createObservabilityContext } from '../observability.js';
|
|
15
17
|
//# sourceMappingURL=index.js.map
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAG9C,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAG/E,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAGjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtD,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAG/E,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAE5D,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGjE,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAG9C,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAG/E,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAGjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtD,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAG/E,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAE5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGjE,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL Storage Backend — implements StorageBackend for PG persistence.
|
|
3
|
+
*
|
|
4
|
+
* Uses two tables:
|
|
5
|
+
* - `subdoc_states` (shared with PostgreSQLDatabaseAdapter) — raw Yjs BYTEA
|
|
6
|
+
* - `sietch_kv` — generic key-value for identity, refs, and metadata
|
|
7
|
+
*
|
|
8
|
+
* The pool is NOT owned by this backend — close() is a no-op.
|
|
9
|
+
* The caller owns the pool and may share it with PostgreSQLDatabaseAdapter.
|
|
10
|
+
*/
|
|
11
|
+
import pg from 'pg';
|
|
12
|
+
import type { StorageBackend } from '../storage.js';
|
|
13
|
+
import type { StorageCapacity } from '../types.js';
|
|
14
|
+
export declare class PostgresStorageBackend implements StorageBackend {
|
|
15
|
+
readonly type: "postgres";
|
|
16
|
+
private pool;
|
|
17
|
+
private schemaReady;
|
|
18
|
+
constructor(pool: pg.Pool);
|
|
19
|
+
/**
|
|
20
|
+
* Ensure required tables exist. Idempotent — safe to call on every startup.
|
|
21
|
+
* Does NOT create `nodes`, `node_history`, `vectors`, or AGE graph —
|
|
22
|
+
* those belong to PostgreSQLDatabaseAdapter.
|
|
23
|
+
*/
|
|
24
|
+
ensureSchema(): Promise<void>;
|
|
25
|
+
persistSubdoc(subtreeId: string, state: Uint8Array): Promise<void>;
|
|
26
|
+
loadSubdoc(subtreeId: string): Promise<Uint8Array | null>;
|
|
27
|
+
deleteSubdoc(subtreeId: string): Promise<void>;
|
|
28
|
+
listSubdocs(): Promise<string[]>;
|
|
29
|
+
capacity(): Promise<StorageCapacity>;
|
|
30
|
+
persistIdentity(encrypted: Uint8Array): Promise<void>;
|
|
31
|
+
loadIdentity(): Promise<Uint8Array | null>;
|
|
32
|
+
persistRef(refId: string, state: Uint8Array): Promise<void>;
|
|
33
|
+
loadRef(refId: string): Promise<Uint8Array | null>;
|
|
34
|
+
getSubdocMetadata(id: string): Promise<{
|
|
35
|
+
lastAccess: number;
|
|
36
|
+
byteSize: number;
|
|
37
|
+
} | null>;
|
|
38
|
+
setSubdocMetadata(id: string, meta: {
|
|
39
|
+
lastAccess: number;
|
|
40
|
+
byteSize: number;
|
|
41
|
+
}): Promise<void>;
|
|
42
|
+
listSubdocMetadata(): Promise<Array<{
|
|
43
|
+
id: string;
|
|
44
|
+
lastAccess: number;
|
|
45
|
+
byteSize: number;
|
|
46
|
+
}>>;
|
|
47
|
+
close(): Promise<void>;
|
|
48
|
+
private kvUpsert;
|
|
49
|
+
private kvLoad;
|
|
50
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL Storage Backend — implements StorageBackend for PG persistence.
|
|
3
|
+
*
|
|
4
|
+
* Uses two tables:
|
|
5
|
+
* - `subdoc_states` (shared with PostgreSQLDatabaseAdapter) — raw Yjs BYTEA
|
|
6
|
+
* - `sietch_kv` — generic key-value for identity, refs, and metadata
|
|
7
|
+
*
|
|
8
|
+
* The pool is NOT owned by this backend — close() is a no-op.
|
|
9
|
+
* The caller owns the pool and may share it with PostgreSQLDatabaseAdapter.
|
|
10
|
+
*/
|
|
11
|
+
// @req FR-02
|
|
12
|
+
export class PostgresStorageBackend {
|
|
13
|
+
type = 'postgres';
|
|
14
|
+
pool;
|
|
15
|
+
schemaReady = false;
|
|
16
|
+
constructor(pool) {
|
|
17
|
+
this.pool = pool;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Ensure required tables exist. Idempotent — safe to call on every startup.
|
|
21
|
+
* Does NOT create `nodes`, `node_history`, `vectors`, or AGE graph —
|
|
22
|
+
* those belong to PostgreSQLDatabaseAdapter.
|
|
23
|
+
*/
|
|
24
|
+
async ensureSchema() {
|
|
25
|
+
if (this.schemaReady)
|
|
26
|
+
return;
|
|
27
|
+
await this.pool.query(`
|
|
28
|
+
CREATE TABLE IF NOT EXISTS subdoc_states (
|
|
29
|
+
subtree_id TEXT PRIMARY KEY,
|
|
30
|
+
yjs_state BYTEA NOT NULL,
|
|
31
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
32
|
+
)
|
|
33
|
+
`);
|
|
34
|
+
await this.pool.query(`
|
|
35
|
+
CREATE TABLE IF NOT EXISTS sietch_kv (
|
|
36
|
+
key TEXT PRIMARY KEY,
|
|
37
|
+
value BYTEA NOT NULL,
|
|
38
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
39
|
+
)
|
|
40
|
+
`);
|
|
41
|
+
this.schemaReady = true;
|
|
42
|
+
}
|
|
43
|
+
// ── Subdoc persistence ──────────────────────────────────
|
|
44
|
+
async persistSubdoc(subtreeId, state) {
|
|
45
|
+
await this.pool.query(`INSERT INTO subdoc_states (subtree_id, yjs_state, updated_at)
|
|
46
|
+
VALUES ($1, $2, now())
|
|
47
|
+
ON CONFLICT (subtree_id) DO UPDATE SET yjs_state = $2, updated_at = now()`, [subtreeId, Buffer.from(state)]);
|
|
48
|
+
}
|
|
49
|
+
async loadSubdoc(subtreeId) {
|
|
50
|
+
const result = await this.pool.query('SELECT yjs_state FROM subdoc_states WHERE subtree_id = $1', [subtreeId]);
|
|
51
|
+
if (result.rows.length === 0)
|
|
52
|
+
return null;
|
|
53
|
+
const buf = result.rows[0].yjs_state;
|
|
54
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
55
|
+
}
|
|
56
|
+
async deleteSubdoc(subtreeId) {
|
|
57
|
+
await this.pool.query('DELETE FROM subdoc_states WHERE subtree_id = $1', [subtreeId]);
|
|
58
|
+
}
|
|
59
|
+
async listSubdocs() {
|
|
60
|
+
const result = await this.pool.query('SELECT subtree_id FROM subdoc_states ORDER BY subtree_id');
|
|
61
|
+
return result.rows.map((row) => row.subtree_id);
|
|
62
|
+
}
|
|
63
|
+
// ── Capacity ────────────────────────────────────────────
|
|
64
|
+
async capacity() {
|
|
65
|
+
return {
|
|
66
|
+
used: 0,
|
|
67
|
+
total: Number.MAX_SAFE_INTEGER,
|
|
68
|
+
available: Number.MAX_SAFE_INTEGER,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// ── Identity persistence ────────────────────────────────
|
|
72
|
+
async persistIdentity(encrypted) {
|
|
73
|
+
await this.kvUpsert('identity:keypair', Buffer.from(encrypted));
|
|
74
|
+
}
|
|
75
|
+
async loadIdentity() {
|
|
76
|
+
return this.kvLoad('identity:keypair');
|
|
77
|
+
}
|
|
78
|
+
// ── Reference node persistence ──────────────────────────
|
|
79
|
+
async persistRef(refId, state) {
|
|
80
|
+
await this.kvUpsert(`ref:${refId}`, Buffer.from(state));
|
|
81
|
+
}
|
|
82
|
+
async loadRef(refId) {
|
|
83
|
+
return this.kvLoad(`ref:${refId}`);
|
|
84
|
+
}
|
|
85
|
+
// ── Subdoc metadata ─────────────────────────────────────
|
|
86
|
+
async getSubdocMetadata(id) {
|
|
87
|
+
const raw = await this.kvLoad(`meta:${id}`);
|
|
88
|
+
if (!raw)
|
|
89
|
+
return null;
|
|
90
|
+
return JSON.parse(new TextDecoder().decode(raw));
|
|
91
|
+
}
|
|
92
|
+
async setSubdocMetadata(id, meta) {
|
|
93
|
+
const encoded = Buffer.from(JSON.stringify(meta));
|
|
94
|
+
await this.kvUpsert(`meta:${id}`, encoded);
|
|
95
|
+
}
|
|
96
|
+
async listSubdocMetadata() {
|
|
97
|
+
const result = await this.pool.query("SELECT key, value FROM sietch_kv WHERE key LIKE 'meta:%' ORDER BY key");
|
|
98
|
+
return result.rows.map((row) => {
|
|
99
|
+
const id = row.key.slice(5); // strip 'meta:'
|
|
100
|
+
const meta = JSON.parse(row.value.toString());
|
|
101
|
+
return { id, ...meta };
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
// ── Lifecycle ───────────────────────────────────────────
|
|
105
|
+
async close() {
|
|
106
|
+
// No-op — pool is owned by the caller
|
|
107
|
+
}
|
|
108
|
+
// ── Private helpers ─────────────────────────────────────
|
|
109
|
+
async kvUpsert(key, value) {
|
|
110
|
+
await this.pool.query(`INSERT INTO sietch_kv (key, value, updated_at)
|
|
111
|
+
VALUES ($1, $2, now())
|
|
112
|
+
ON CONFLICT (key) DO UPDATE SET value = $2, updated_at = now()`, [key, value]);
|
|
113
|
+
}
|
|
114
|
+
async kvLoad(key) {
|
|
115
|
+
const result = await this.pool.query('SELECT value FROM sietch_kv WHERE key = $1', [key]);
|
|
116
|
+
if (result.rows.length === 0)
|
|
117
|
+
return null;
|
|
118
|
+
const buf = result.rows[0].value;
|
|
119
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=pg-storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pg-storage.js","sourceRoot":"","sources":["../../src/server/pg-storage.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,9 +11,20 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Encrypted subtrees are stored as opaque BYTEA only.
|
|
13
13
|
*/
|
|
14
|
-
import
|
|
14
|
+
import * as Y from 'yjs';
|
|
15
15
|
import type { AclManager } from '../acl.js';
|
|
16
16
|
import type { ObservabilityContext } from '../observability.js';
|
|
17
|
+
/** Minimal interface for the state source that feeds the query bridge. */
|
|
18
|
+
export interface BridgeStateSource {
|
|
19
|
+
/** Subscribe to raw Yjs updates on a subtree. */
|
|
20
|
+
onUpdate(subtreeId: string, listener: (subtreeId: string, update: Uint8Array) => void): () => void;
|
|
21
|
+
/** Get the Yjs doc for a subtree (for full-state reads). */
|
|
22
|
+
getSubdoc(subtreeId: string): Y.Doc | Promise<Y.Doc>;
|
|
23
|
+
/** Encode full state as a Yjs update (for encrypted subtree persistence). */
|
|
24
|
+
encodeStateAsUpdate(subtreeId: string, remoteStateVector?: Uint8Array): Uint8Array | Promise<Uint8Array>;
|
|
25
|
+
/** Get list of assigned/active subtrees. */
|
|
26
|
+
getAssignedSubtrees(): string[];
|
|
27
|
+
}
|
|
17
28
|
/** Abstract database interface for PostgreSQL operations. */
|
|
18
29
|
export interface DatabaseAdapter {
|
|
19
30
|
/** Upsert a node's decoded data. */
|
|
@@ -114,14 +125,14 @@ export declare class InMemoryDatabaseAdapter implements DatabaseAdapter {
|
|
|
114
125
|
getVersionAtTimestamp(id: string, timestamp: Date): Promise<Record<string, unknown> | null>;
|
|
115
126
|
}
|
|
116
127
|
export declare class ServerQueryBridge {
|
|
117
|
-
private readonly
|
|
128
|
+
private readonly stateSource;
|
|
118
129
|
private readonly acl;
|
|
119
130
|
private readonly db;
|
|
120
131
|
private readonly obs;
|
|
121
132
|
private readonly unsubscribes;
|
|
122
133
|
/** Per-node version counter. */
|
|
123
134
|
private readonly versionCounters;
|
|
124
|
-
constructor(
|
|
135
|
+
constructor(stateSource: BridgeStateSource, acl: AclManager, db: DatabaseAdapter, obs: ObservabilityContext);
|
|
125
136
|
/**
|
|
126
137
|
* Start observing assigned subtrees and piping changes to the database.
|
|
127
138
|
*/
|
|
@@ -95,15 +95,15 @@ export class InMemoryDatabaseAdapter {
|
|
|
95
95
|
// @req FR-53
|
|
96
96
|
// @req FR-54
|
|
97
97
|
export class ServerQueryBridge {
|
|
98
|
-
|
|
98
|
+
stateSource;
|
|
99
99
|
acl;
|
|
100
100
|
db;
|
|
101
101
|
obs;
|
|
102
102
|
unsubscribes = [];
|
|
103
103
|
/** Per-node version counter. */
|
|
104
104
|
versionCounters = new Map();
|
|
105
|
-
constructor(
|
|
106
|
-
this.
|
|
105
|
+
constructor(stateSource, acl, db, obs) {
|
|
106
|
+
this.stateSource = stateSource;
|
|
107
107
|
this.acl = acl;
|
|
108
108
|
this.db = db;
|
|
109
109
|
this.obs = obs;
|
|
@@ -112,7 +112,7 @@ export class ServerQueryBridge {
|
|
|
112
112
|
* Start observing assigned subtrees and piping changes to the database.
|
|
113
113
|
*/
|
|
114
114
|
start() {
|
|
115
|
-
const subtrees = this.
|
|
115
|
+
const subtrees = this.stateSource.getAssignedSubtrees();
|
|
116
116
|
for (const subtreeId of subtrees) {
|
|
117
117
|
this.observeSubtree(subtreeId);
|
|
118
118
|
}
|
|
@@ -122,7 +122,7 @@ export class ServerQueryBridge {
|
|
|
122
122
|
* Observe a single subtree for changes.
|
|
123
123
|
*/
|
|
124
124
|
observeSubtree(subtreeId) {
|
|
125
|
-
const unsub = this.
|
|
125
|
+
const unsub = this.stateSource.onUpdate(subtreeId, (_sid, update) => {
|
|
126
126
|
void this.processUpdate(subtreeId, update);
|
|
127
127
|
});
|
|
128
128
|
this.unsubscribes.push(unsub);
|
|
@@ -139,13 +139,13 @@ export class ServerQueryBridge {
|
|
|
139
139
|
const node = this.acl.getRefNode(subtreeId);
|
|
140
140
|
if (node?.encrypted) {
|
|
141
141
|
// Encrypted subtrees: store only raw Yjs state
|
|
142
|
-
const state = this.
|
|
142
|
+
const state = await this.stateSource.encodeStateAsUpdate(subtreeId);
|
|
143
143
|
await this.db.upsertSubdocState(subtreeId, state);
|
|
144
144
|
return;
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
// Unencrypted: decode Yjs state and write to PostgreSQL
|
|
148
|
-
const doc = this.
|
|
148
|
+
const doc = await this.stateSource.getSubdoc(subtreeId);
|
|
149
149
|
const nodesMap = doc.getMap('nodes');
|
|
150
150
|
for (const [key, value] of nodesMap.entries()) {
|
|
151
151
|
const nodeId = `${subtreeId}.${key}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-bridge.js","sourceRoot":"","sources":["../../src/server/query-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"query-bridge.js","sourceRoot":"","sources":["../../src/server/query-bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SietchServer — server-focused API class.
|
|
3
|
+
*
|
|
4
|
+
* Composes YjsEngine, SyncEngine, IdentityManager, AclManager, and
|
|
5
|
+
* optionally ServerQueryBridge behind a clean surface with a single
|
|
6
|
+
* Y.Doc pool and coherent lifecycle.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { createServer } from 'sietch/server';
|
|
10
|
+
* const server = await createServer({ listen: ['/ip4/0.0.0.0/tcp/9090/ws'] });
|
|
11
|
+
* await server.put('items.book1', { title: 'Dune', year: 1965 });
|
|
12
|
+
* await server.close();
|
|
13
|
+
*/
|
|
14
|
+
import * as Y from 'yjs';
|
|
15
|
+
import { type PublicIdentity } from '../identity.js';
|
|
16
|
+
import { type StorageBackend } from '../storage.js';
|
|
17
|
+
import type { SyncConfig } from '../sync-engine.js';
|
|
18
|
+
import { type DatabaseAdapter } from './query-bridge.js';
|
|
19
|
+
import { type AclSigningBackend } from './acl-authority.js';
|
|
20
|
+
import type { CrdtValue, MutationEvent, StorageType, Unsubscribe, WriteConfirmation, ReadResult } from '../types.js';
|
|
21
|
+
export interface ServerOptions {
|
|
22
|
+
/** Multiaddrs to listen on (e.g., ['/ip4/0.0.0.0/tcp/9090/ws']). */
|
|
23
|
+
listen: string[];
|
|
24
|
+
/** Storage backend preference (default: 'auto' — filesystem in Node.js). */
|
|
25
|
+
storage?: StorageType | 'auto';
|
|
26
|
+
/** Filesystem storage directory (default: '.sietch'). */
|
|
27
|
+
storagePath?: string;
|
|
28
|
+
/** Pre-initialized storage backend (bypasses auto-detection). */
|
|
29
|
+
storageBackend?: StorageBackend;
|
|
30
|
+
/** Database adapter for query bridge (optional — no PG pipeline without it). */
|
|
31
|
+
database?: DatabaseAdapter;
|
|
32
|
+
/** Log level (default: 'info'). */
|
|
33
|
+
logLevel?: 'debug' | 'info' | 'warn' | 'error';
|
|
34
|
+
/** ACL signing backend for server-owned subtrees. */
|
|
35
|
+
aclSigningBackend?: AclSigningBackend;
|
|
36
|
+
/** Sync reconnect configuration. */
|
|
37
|
+
reconnect?: SyncConfig['reconnect'];
|
|
38
|
+
}
|
|
39
|
+
export declare class SietchServer {
|
|
40
|
+
private readonly yjsEngine;
|
|
41
|
+
private readonly syncEngine;
|
|
42
|
+
private readonly identityMgr;
|
|
43
|
+
private readonly aclMgr;
|
|
44
|
+
private readonly obs;
|
|
45
|
+
private readonly storage;
|
|
46
|
+
private queryBridge?;
|
|
47
|
+
private aclAuthority?;
|
|
48
|
+
/** Subtrees already registered with the query bridge. */
|
|
49
|
+
private readonly bridgedSubtrees;
|
|
50
|
+
/** Private — use createServer(). */
|
|
51
|
+
private constructor();
|
|
52
|
+
/** Factory — initializes all subsystems in correct order. */
|
|
53
|
+
static create(options: ServerOptions): Promise<SietchServer>;
|
|
54
|
+
/** Multiaddrs this server is listening on. */
|
|
55
|
+
get multiaddrs(): string[];
|
|
56
|
+
/** The server's PQ identity. */
|
|
57
|
+
identity(): Promise<PublicIdentity>;
|
|
58
|
+
/** Write a value (goes through YjsEngine — persist + sync to peers). */
|
|
59
|
+
put(path: string, value: CrdtValue): Promise<WriteConfirmation>;
|
|
60
|
+
/** Read a value from the local Yjs state. */
|
|
61
|
+
get(path: string): Promise<ReadResult>;
|
|
62
|
+
/** Shallow-merge fields into an existing value. */
|
|
63
|
+
patch(path: string, fields: Record<string, CrdtValue>): Promise<WriteConfirmation>;
|
|
64
|
+
/** Delete a value. */
|
|
65
|
+
delete(path: string): Promise<WriteConfirmation>;
|
|
66
|
+
/** Subscribe to fine-grained mutation events across all subtrees. */
|
|
67
|
+
onMutation(callback: (event: MutationEvent) => void): Unsubscribe;
|
|
68
|
+
/** Subscribe to raw Yjs updates on a subtree (for external pipelines). */
|
|
69
|
+
onSubdocUpdate(subtreeId: string, listener: (subtreeId: string, update: Uint8Array) => void): Unsubscribe;
|
|
70
|
+
/** Get the raw Y.Doc for a subtree (for y-crdt interop / external observers). */
|
|
71
|
+
getSubdoc(subtreeId: string): Promise<Y.Doc>;
|
|
72
|
+
/** Execute a Cypher query (requires database). */
|
|
73
|
+
cypher(query: string, params?: Record<string, unknown>): Promise<Record<string, unknown>[]>;
|
|
74
|
+
/** Register a query handler for the sync protocol. */
|
|
75
|
+
registerQueryHandler(handler: (q: string, p?: Record<string, unknown>) => Promise<Record<string, unknown>[]>): void;
|
|
76
|
+
/** Ensure the query bridge is observing a subtree. */
|
|
77
|
+
private ensureBridged;
|
|
78
|
+
/** Gracefully shut down all subsystems. */
|
|
79
|
+
close(): Promise<void>;
|
|
80
|
+
}
|
|
81
|
+
/** Convenience factory. */
|
|
82
|
+
export declare function createServer(options: ServerOptions): Promise<SietchServer>;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SietchServer — server-focused API class.
|
|
3
|
+
*
|
|
4
|
+
* Composes YjsEngine, SyncEngine, IdentityManager, AclManager, and
|
|
5
|
+
* optionally ServerQueryBridge behind a clean surface with a single
|
|
6
|
+
* Y.Doc pool and coherent lifecycle.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { createServer } from 'sietch/server';
|
|
10
|
+
* const server = await createServer({ listen: ['/ip4/0.0.0.0/tcp/9090/ws'] });
|
|
11
|
+
* await server.put('items.book1', { title: 'Dune', year: 1965 });
|
|
12
|
+
* await server.close();
|
|
13
|
+
*/
|
|
14
|
+
import * as Y from 'yjs';
|
|
15
|
+
import { YjsEngine } from '../yjs-engine.js';
|
|
16
|
+
import { IdentityManager } from '../identity.js';
|
|
17
|
+
import { AclManager } from '../acl.js';
|
|
18
|
+
import { createStorageBackend } from '../storage.js';
|
|
19
|
+
import { createObservabilityContext } from '../observability.js';
|
|
20
|
+
import { ServerQueryBridge } from './query-bridge.js';
|
|
21
|
+
import { ServerAclAuthority } from './acl-authority.js';
|
|
22
|
+
// ── YjsEngine → BridgeStateSource adapter ──────────────────
|
|
23
|
+
/**
|
|
24
|
+
* Adapts YjsEngine to the BridgeStateSource interface required by
|
|
25
|
+
* ServerQueryBridge, allowing the bridge to observe the same Y.Doc
|
|
26
|
+
* pool used for sync and local operations.
|
|
27
|
+
*/
|
|
28
|
+
class YjsEngineBridgeAdapter {
|
|
29
|
+
engine;
|
|
30
|
+
constructor(engine) {
|
|
31
|
+
this.engine = engine;
|
|
32
|
+
}
|
|
33
|
+
onUpdate(subtreeId, listener) {
|
|
34
|
+
return this.engine.onSubdocUpdate(subtreeId, listener);
|
|
35
|
+
}
|
|
36
|
+
async getSubdoc(subtreeId) {
|
|
37
|
+
return this.engine.getSubdoc(subtreeId);
|
|
38
|
+
}
|
|
39
|
+
async encodeStateAsUpdate(subtreeId, remoteStateVector) {
|
|
40
|
+
const doc = await this.engine.getSubdoc(subtreeId);
|
|
41
|
+
return Y.encodeStateAsUpdate(doc, remoteStateVector);
|
|
42
|
+
}
|
|
43
|
+
getAssignedSubtrees() {
|
|
44
|
+
return this.engine.listLoadedSubdocIds();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// ── SietchServer ───────────────────────────────────────────
|
|
48
|
+
export class SietchServer {
|
|
49
|
+
yjsEngine;
|
|
50
|
+
syncEngine;
|
|
51
|
+
identityMgr;
|
|
52
|
+
aclMgr;
|
|
53
|
+
obs;
|
|
54
|
+
storage;
|
|
55
|
+
queryBridge;
|
|
56
|
+
aclAuthority;
|
|
57
|
+
/** Subtrees already registered with the query bridge. */
|
|
58
|
+
bridgedSubtrees = new Set();
|
|
59
|
+
/** Private — use createServer(). */
|
|
60
|
+
constructor(storage, obs, yjsEngine, syncEngine, identityMgr, aclMgr) {
|
|
61
|
+
this.storage = storage;
|
|
62
|
+
this.obs = obs;
|
|
63
|
+
this.yjsEngine = yjsEngine;
|
|
64
|
+
this.syncEngine = syncEngine;
|
|
65
|
+
this.identityMgr = identityMgr;
|
|
66
|
+
this.aclMgr = aclMgr;
|
|
67
|
+
}
|
|
68
|
+
/** Factory — initializes all subsystems in correct order. */
|
|
69
|
+
static async create(options) {
|
|
70
|
+
const obs = createObservabilityContext(options.logLevel ?? 'info');
|
|
71
|
+
// 1. Storage
|
|
72
|
+
let storage;
|
|
73
|
+
if (options.storageBackend) {
|
|
74
|
+
storage = options.storageBackend;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
storage = await createStorageBackend(obs, options.storage ?? 'auto', options.storagePath);
|
|
78
|
+
}
|
|
79
|
+
// 2. YjsEngine (single doc pool)
|
|
80
|
+
const yjsEngine = new YjsEngine(storage, obs);
|
|
81
|
+
// 3. Identity
|
|
82
|
+
const identityMgr = new IdentityManager(storage, obs);
|
|
83
|
+
await identityMgr.init();
|
|
84
|
+
// 4. ACL
|
|
85
|
+
const aclMgr = new AclManager(obs);
|
|
86
|
+
// 5. SyncEngine (transport)
|
|
87
|
+
const { SyncEngine: SyncEngineCtor } = await import('../sync-engine.js');
|
|
88
|
+
const syncEngine = new SyncEngineCtor(yjsEngine, identityMgr, aclMgr, obs);
|
|
89
|
+
const syncConfig = { listen: options.listen };
|
|
90
|
+
if (options.reconnect) {
|
|
91
|
+
syncConfig.reconnect = options.reconnect;
|
|
92
|
+
}
|
|
93
|
+
await syncEngine.init(syncConfig);
|
|
94
|
+
const server = new SietchServer(storage, obs, yjsEngine, syncEngine, identityMgr, aclMgr);
|
|
95
|
+
// 6. QueryBridge (optional — only if database adapter provided)
|
|
96
|
+
if (options.database) {
|
|
97
|
+
const bridgeSource = new YjsEngineBridgeAdapter(yjsEngine);
|
|
98
|
+
server.queryBridge = new ServerQueryBridge(bridgeSource, aclMgr, options.database, obs);
|
|
99
|
+
server.queryBridge.start();
|
|
100
|
+
syncEngine.registerQueryHandler((q, p) => server.queryBridge.cypherQuery(q, p));
|
|
101
|
+
}
|
|
102
|
+
// 7. ACL authority (optional)
|
|
103
|
+
if (options.aclSigningBackend) {
|
|
104
|
+
const identity = identityMgr.getPublicIdentity();
|
|
105
|
+
server.aclAuthority = new ServerAclAuthority(options.aclSigningBackend, obs, identity.peerId);
|
|
106
|
+
}
|
|
107
|
+
obs.logger.info({ listen: options.listen }, 'server started');
|
|
108
|
+
return server;
|
|
109
|
+
}
|
|
110
|
+
// ── Accessors ──────────────────────────────────────────
|
|
111
|
+
/** Multiaddrs this server is listening on. */
|
|
112
|
+
get multiaddrs() {
|
|
113
|
+
return this.syncEngine.getMultiaddrs();
|
|
114
|
+
}
|
|
115
|
+
/** The server's PQ identity. */
|
|
116
|
+
async identity() {
|
|
117
|
+
if (!this.identityMgr.isInitialized()) {
|
|
118
|
+
await this.identityMgr.init();
|
|
119
|
+
}
|
|
120
|
+
return this.identityMgr.getPublicIdentity();
|
|
121
|
+
}
|
|
122
|
+
// ── Data Operations ──────────────────────────────────────
|
|
123
|
+
/** Write a value (goes through YjsEngine — persist + sync to peers). */
|
|
124
|
+
async put(path, value) {
|
|
125
|
+
const subtreeId = path.split('.')[0] ?? path;
|
|
126
|
+
this.ensureBridged(subtreeId);
|
|
127
|
+
const { update } = await this.yjsEngine.applyLocal(path, value);
|
|
128
|
+
if (update) {
|
|
129
|
+
this.syncEngine.queueUpdate(subtreeId, update);
|
|
130
|
+
}
|
|
131
|
+
return { path, timestamp: Date.now(), persisted: true };
|
|
132
|
+
}
|
|
133
|
+
/** Read a value from the local Yjs state. */
|
|
134
|
+
async get(path) {
|
|
135
|
+
const value = await this.yjsEngine.getState(path);
|
|
136
|
+
return { value: value ?? undefined, source: 'local' };
|
|
137
|
+
}
|
|
138
|
+
/** Shallow-merge fields into an existing value. */
|
|
139
|
+
async patch(path, fields) {
|
|
140
|
+
const subtreeId = path.split('.')[0] ?? path;
|
|
141
|
+
this.ensureBridged(subtreeId);
|
|
142
|
+
const { update } = await this.yjsEngine.applyPatch(path, fields);
|
|
143
|
+
if (update) {
|
|
144
|
+
this.syncEngine.queueUpdate(subtreeId, update);
|
|
145
|
+
}
|
|
146
|
+
return { path, timestamp: Date.now(), persisted: true };
|
|
147
|
+
}
|
|
148
|
+
/** Delete a value. */
|
|
149
|
+
async delete(path) {
|
|
150
|
+
const subtreeId = path.split('.')[0] ?? path;
|
|
151
|
+
this.ensureBridged(subtreeId);
|
|
152
|
+
const { update } = await this.yjsEngine.applyLocal(path, null);
|
|
153
|
+
if (update) {
|
|
154
|
+
this.syncEngine.queueUpdate(subtreeId, update);
|
|
155
|
+
}
|
|
156
|
+
return { path, timestamp: Date.now(), persisted: true };
|
|
157
|
+
}
|
|
158
|
+
// ── Observation ──────────────────────────────────────────
|
|
159
|
+
/** Subscribe to fine-grained mutation events across all subtrees. */
|
|
160
|
+
onMutation(callback) {
|
|
161
|
+
return this.yjsEngine.onMutation(callback);
|
|
162
|
+
}
|
|
163
|
+
/** Subscribe to raw Yjs updates on a subtree (for external pipelines). */
|
|
164
|
+
onSubdocUpdate(subtreeId, listener) {
|
|
165
|
+
return this.yjsEngine.onSubdocUpdate(subtreeId, listener);
|
|
166
|
+
}
|
|
167
|
+
/** Get the raw Y.Doc for a subtree (for y-crdt interop / external observers). */
|
|
168
|
+
async getSubdoc(subtreeId) {
|
|
169
|
+
return this.yjsEngine.getSubdoc(subtreeId);
|
|
170
|
+
}
|
|
171
|
+
// ── Query Bridge (requires database option) ──────────────
|
|
172
|
+
/** Execute a Cypher query (requires database). */
|
|
173
|
+
async cypher(query, params) {
|
|
174
|
+
if (!this.queryBridge) {
|
|
175
|
+
throw new Error('Query bridge not configured — pass `database` option to createServer()');
|
|
176
|
+
}
|
|
177
|
+
return this.queryBridge.cypherQuery(query, params);
|
|
178
|
+
}
|
|
179
|
+
// ── ACL ──────────────────────────────────────────────────
|
|
180
|
+
/** Register a query handler for the sync protocol. */
|
|
181
|
+
registerQueryHandler(handler) {
|
|
182
|
+
this.syncEngine.registerQueryHandler(handler);
|
|
183
|
+
}
|
|
184
|
+
// ── Private ──────────────────────────────────────────────
|
|
185
|
+
/** Ensure the query bridge is observing a subtree. */
|
|
186
|
+
ensureBridged(subtreeId) {
|
|
187
|
+
if (!this.queryBridge)
|
|
188
|
+
return;
|
|
189
|
+
if (this.bridgedSubtrees.has(subtreeId))
|
|
190
|
+
return;
|
|
191
|
+
this.bridgedSubtrees.add(subtreeId);
|
|
192
|
+
this.queryBridge.observeSubtree(subtreeId);
|
|
193
|
+
}
|
|
194
|
+
// ── Lifecycle ────────────────────────────────────────────
|
|
195
|
+
/** Gracefully shut down all subsystems. */
|
|
196
|
+
async close() {
|
|
197
|
+
if (this.queryBridge) {
|
|
198
|
+
this.queryBridge.stop();
|
|
199
|
+
}
|
|
200
|
+
await this.syncEngine.close();
|
|
201
|
+
this.yjsEngine.close();
|
|
202
|
+
await this.storage.close();
|
|
203
|
+
this.obs.logger.info('server stopped');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/** Convenience factory. */
|
|
207
|
+
export async function createServer(options) {
|
|
208
|
+
return SietchServer.create(options);
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=server.js.map
|