@catalyst-cloud/sdk 0.1.0 → 0.2.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 +81 -144
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/live-sync-client.d.ts +13 -11
- package/dist/live-sync-client.d.ts.map +1 -1
- package/dist/live-sync-client.js +21 -16
- package/dist/live-sync-client.js.map +1 -1
- package/dist/node.d.ts +8 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +22 -0
- package/dist/node.js.map +1 -0
- package/dist/replica/catalyst-replica.d.ts +153 -0
- package/dist/replica/catalyst-replica.d.ts.map +1 -0
- package/dist/replica/catalyst-replica.js +409 -0
- package/dist/replica/catalyst-replica.js.map +1 -0
- package/dist/replica/engine.d.ts +67 -0
- package/dist/replica/engine.d.ts.map +1 -0
- package/dist/replica/engine.js +216 -0
- package/dist/replica/engine.js.map +1 -0
- package/dist/replica/writer-lock.d.ts +27 -0
- package/dist/replica/writer-lock.d.ts.map +1 -0
- package/dist/replica/writer-lock.js +125 -0
- package/dist/replica/writer-lock.js.map +1 -0
- package/dist/types.d.ts +14 -15
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +6 -10
- package/dist/types.js.map +1 -1
- package/package.json +24 -2
package/README.md
CHANGED
|
@@ -1,129 +1,66 @@
|
|
|
1
1
|
# @catalyst-cloud/sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Keep a live local copy of your Linear and GitHub project data — pushed to you in real time, without polling rate limits or webhook tunnels.**
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
Object and exposes it as a `change_log` feed. This SDK is the **push** consumer of that feed: it opens
|
|
7
|
-
an outbound WebSocket to a tenant's Mirror DO (`{baseUrl}/connect`), sends a cursor-replay request on
|
|
8
|
-
every (re)connect, and hands you each pushed change so you can land it into your own store.
|
|
5
|
+
When you run coding agents at scale — dozens or more working in parallel — they all need the same live view of your Linear and GitHub state to coordinate, and you quickly hit the rate limits of the very systems your team depends on. catalyst-cloud is a service that mirrors your Linear + GitHub state **once** and pushes every change out to your fleet; this SDK is how your code subscribes to that stream and keeps a local copy current.
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
browser, in Node (>=22), or in Bun**. It is the single push transport behind both the host replicas
|
|
12
|
-
and the web UI per [ADR-0008](https://github.com/coalesce-labs/catalyst-cloud/blob/main/docs/adr/0008-catalyst-host-local-replica-consumption.md)
|
|
13
|
-
(host reads from an SDK-managed per-host replica) and [ADR-0009](https://github.com/coalesce-labs/catalyst-cloud/blob/main/docs/adr/0009-browser-live-updates-over-websocket.md)
|
|
14
|
-
(browser live updates over a **hibernating** WebSocket, not SSE — so an idle tab can't pin the DO).
|
|
7
|
+
## Why
|
|
15
8
|
|
|
16
|
-
|
|
9
|
+
- **Real-time updates, no tunnels.** catalyst-cloud is the single webhook subscriber for your Linear and GitHub. Changes are **pushed** to you over an outbound connection — so you never stand up a public endpoint, smee/ngrok tunnel, or webhook gateway just to hear about an update locally.
|
|
10
|
+
- **One subscriber, not N.** Your whole fleet reads from the shared mirror instead of each agent polling Linear and GitHub directly — so a large fleet doesn't multiply load against those rate limits.
|
|
11
|
+
- **A local copy, not just a feed.** You don't get a firehose of events to babysit — you get your data, kept current, that you can query locally.
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
npm install @catalyst-cloud/sdk
|
|
20
|
-
```
|
|
13
|
+
## Coverage
|
|
21
14
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
- **Linear** — issues, projects, cycles, initiatives, comments, labels, history, and relations. **Mature.**
|
|
16
|
+
- **GitHub** — pull requests, checks, commit statuses, and reviews. **Maturing.**
|
|
17
|
+
- **Knowledge base** — thoughts / memories. **On the roadmap.**
|
|
25
18
|
|
|
26
|
-
##
|
|
19
|
+
## Requirements
|
|
27
20
|
|
|
28
|
-
|
|
29
|
-
(`bun:sqlite`, OPFS, in-memory) or how you authenticate — both are **injected**:
|
|
21
|
+
A catalyst-cloud account and an auth token. (In the browser, a same-origin session cookie is used instead — no token in the page.)
|
|
30
22
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- **`reseed`** — an async callback returning the fresh cursor after a full re-seed. Called on a
|
|
35
|
-
`{type:"resync"}` underflow frame (and before the first connect if you have no cursor). A host pulls
|
|
36
|
-
`/snapshot` into SQLite here; a browser re-runs its own seed.
|
|
37
|
-
- **`getCursor` / `onChange`** — you own the cursor and the writes; the client drives the wire.
|
|
38
|
-
- **`onStatus`** — connection lifecycle (`connecting` / `live` / `reconnecting` / `resyncing` /
|
|
39
|
-
`error` / `stopped`) so a UI can show "live" vs "reconnecting".
|
|
40
|
-
|
|
41
|
-
It reconnects with **capped exponential backoff** (1 s → ×2 → 30 s ceiling) and never throws out of
|
|
42
|
-
the event loop. There is **no client-side keepalive** — re-implementing the WebSocket heartbeat would
|
|
43
|
-
re-pin the hibernating DO, defeating ADR-0009.
|
|
44
|
-
|
|
45
|
-
## Usage
|
|
46
|
-
|
|
47
|
-
### Browser (cookie auth, no token)
|
|
48
|
-
|
|
49
|
-
In the browser there is **no token**: pass `auth: { kind: "cookie" }` and the same-origin session
|
|
50
|
-
cookie rides the WebSocket upgrade automatically. The SDK appends nothing secret to the URL, so a
|
|
51
|
-
token can never leak from a browser tab. Storage is yours — here it is a trivial in-memory map; in a
|
|
52
|
-
real app you would seed from your own `/snapshot` fetch into OPFS / IndexedDB.
|
|
53
|
-
|
|
54
|
-
```ts
|
|
55
|
-
import { LiveSyncClient } from "@catalyst-cloud/sdk";
|
|
23
|
+
```sh
|
|
24
|
+
npm install @catalyst-cloud/sdk
|
|
25
|
+
```
|
|
56
26
|
|
|
57
|
-
|
|
58
|
-
let cursor: number | null = null;
|
|
27
|
+
## Today, and where this is going
|
|
59
28
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
accountId: "tenant-0",
|
|
63
|
-
auth: { kind: "cookie" }, // same-origin cookie rides the upgrade; no token ever in the URL
|
|
29
|
+
- **Today** — the SDK is the live-sync client. It keeps your local store current: it manages the connection, replays anything you missed while disconnected, and recovers automatically. You provide the storage (any SQLite, OPFS in the browser, or in-memory) and apply each change.
|
|
30
|
+
- **Coming** — a fully managed local replica: a strongly-typed SQLite database you read **directly via [Drizzle ORM](https://orm.drizzle.team)**, with the syncing handled for you. The typed read layer already exists; we're folding it into the SDK so the database is something you read, not something you assemble.
|
|
64
31
|
|
|
65
|
-
|
|
66
|
-
reseed: async () => {
|
|
67
|
-
const snap = (await (await fetch("/api/v1/snapshot?account=tenant-0")).json()) as {
|
|
68
|
-
cursor: number;
|
|
69
|
-
issues: { id: string }[];
|
|
70
|
-
};
|
|
71
|
-
issues.clear();
|
|
72
|
-
for (const row of snap.issues) issues.set(row.id, row);
|
|
73
|
-
cursor = snap.cursor;
|
|
74
|
-
return snap.cursor;
|
|
75
|
-
},
|
|
76
|
-
|
|
77
|
-
getCursor: () => cursor,
|
|
78
|
-
onChange: (frame) => {
|
|
79
|
-
if (frame.op === "delete") issues.delete(frame.entityId);
|
|
80
|
-
else issues.set(frame.entityId, frame.row);
|
|
81
|
-
cursor = frame.seq;
|
|
82
|
-
},
|
|
83
|
-
onStatus: (s) => setConnectionBadge(s), // "live" / "reconnecting" / …
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
void client.start(); // never await in the browser — it resolves only on stop()
|
|
87
|
-
// on teardown (component unmount, page hide):
|
|
88
|
-
client.stop();
|
|
89
|
-
```
|
|
32
|
+
## Usage
|
|
90
33
|
|
|
91
|
-
### Backend / host
|
|
34
|
+
### Backend / agent host
|
|
92
35
|
|
|
93
|
-
|
|
94
|
-
reaches the DO) and lands every frame into a local SQLite replica. On a `{type:"resync"}` underflow
|
|
95
|
-
(or the very first run with no cursor) it pulls a full `/snapshot` and rewrites the table:
|
|
36
|
+
Authenticate with a token and land every change into a local SQLite replica. On the first run (or after a long disconnect) the SDK asks you to re-seed from a snapshot; after that you just apply each pushed change.
|
|
96
37
|
|
|
97
38
|
```ts
|
|
98
|
-
import { Database } from "bun:sqlite"; //
|
|
39
|
+
import { Database } from "bun:sqlite"; // Node: better-sqlite3 with the same calls
|
|
99
40
|
import { LiveSyncClient } from "@catalyst-cloud/sdk";
|
|
100
41
|
|
|
101
42
|
const db = new Database("replica.sqlite");
|
|
102
43
|
db.run("CREATE TABLE IF NOT EXISTS issues (id TEXT PRIMARY KEY, row TEXT, seq INTEGER)");
|
|
103
44
|
db.run("CREATE TABLE IF NOT EXISTS sync_state (k TEXT PRIMARY KEY, cursor INTEGER)");
|
|
104
45
|
|
|
105
|
-
const baseUrl = process.env.CATALYST_CLOUD_BASE_URL!; // incl. /api/v1
|
|
106
|
-
const token = process.env.ADMIN_TOKEN!;
|
|
107
|
-
|
|
108
46
|
const client = new LiveSyncClient({
|
|
109
|
-
baseUrl
|
|
47
|
+
baseUrl: process.env.CATALYST_CLOUD_BASE_URL!,
|
|
110
48
|
accountId: "tenant-0",
|
|
111
|
-
auth: { kind: "token", token },
|
|
49
|
+
auth: { kind: "token", token: process.env.CATALYST_CLOUD_TOKEN! },
|
|
112
50
|
|
|
113
|
-
// Full re-seed:
|
|
114
|
-
// Must resolve to the fresh cursor (the snapshot's max change_log.seq).
|
|
51
|
+
// Full re-seed: fetch the current snapshot and rewrite the replica; resolve to the fresh cursor.
|
|
115
52
|
reseed: async () => {
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
53
|
+
const snap = (await (
|
|
54
|
+
await fetch(`${process.env.CATALYST_CLOUD_BASE_URL}/snapshot?account=tenant-0`, {
|
|
55
|
+
headers: { authorization: `Bearer ${process.env.CATALYST_CLOUD_TOKEN}` },
|
|
56
|
+
})
|
|
57
|
+
).json()) as { cursor: number; issues: { id: string }[] };
|
|
120
58
|
const tx = db.transaction(() => {
|
|
121
59
|
db.run("DELETE FROM issues");
|
|
122
60
|
const ins = db.prepare("INSERT INTO issues (id, row, seq) VALUES (?, ?, ?)");
|
|
123
61
|
for (const row of snap.issues) ins.run(row.id, JSON.stringify(row), snap.cursor);
|
|
124
62
|
db.run(
|
|
125
|
-
"INSERT INTO sync_state (k, cursor) VALUES ('replica', ?) "
|
|
126
|
-
"ON CONFLICT(k) DO UPDATE SET cursor = excluded.cursor",
|
|
63
|
+
"INSERT INTO sync_state (k, cursor) VALUES ('replica', ?) ON CONFLICT(k) DO UPDATE SET cursor = excluded.cursor",
|
|
127
64
|
[snap.cursor],
|
|
128
65
|
);
|
|
129
66
|
});
|
|
@@ -131,76 +68,76 @@ const client = new LiveSyncClient({
|
|
|
131
68
|
return snap.cursor;
|
|
132
69
|
},
|
|
133
70
|
|
|
134
|
-
// Own the cursor + the writes; the client just drives the wire.
|
|
135
71
|
getCursor: () =>
|
|
136
72
|
(db.query("SELECT cursor FROM sync_state WHERE k = 'replica'").get() as
|
|
137
73
|
| { cursor: number }
|
|
138
74
|
| undefined)?.cursor ?? null,
|
|
139
75
|
|
|
76
|
+
// Apply each pushed change to your local copy.
|
|
140
77
|
onChange: (frame) => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
[frame.entityId, JSON.stringify(frame.row), frame.seq],
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
db.run("UPDATE sync_state SET cursor = ? WHERE k = 'replica'", [frame.seq]);
|
|
152
|
-
});
|
|
153
|
-
tx();
|
|
78
|
+
if (frame.op === "delete") db.run("DELETE FROM issues WHERE id = ?", [frame.entityId]);
|
|
79
|
+
else
|
|
80
|
+
db.run(
|
|
81
|
+
"INSERT INTO issues (id, row, seq) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET row = excluded.row, seq = excluded.seq",
|
|
82
|
+
[frame.entityId, JSON.stringify(frame.row), frame.seq],
|
|
83
|
+
);
|
|
84
|
+
db.run("UPDATE sync_state SET cursor = ? WHERE k = 'replica'", [frame.seq]);
|
|
154
85
|
},
|
|
155
86
|
});
|
|
156
87
|
|
|
157
|
-
await client.start(); // resolves only when stop() is called — keeps the process alive between
|
|
88
|
+
await client.start(); // resolves only when stop() is called — keeps the process alive between changes
|
|
158
89
|
```
|
|
159
90
|
|
|
160
|
-
|
|
161
|
-
> package: `wsFactory: (url) => new WebSocket(url) as unknown as WebSocketLike`. Node 22+ and Bun
|
|
162
|
-
> expose a global `WebSocket`, so no factory is needed.
|
|
91
|
+
### Browser
|
|
163
92
|
|
|
164
|
-
|
|
93
|
+
In the browser there's no token: the same-origin session cookie authenticates the connection, and the SDK never puts anything secret in the URL. Storage is yours (here, a `Map`; in a real app, OPFS / IndexedDB).
|
|
165
94
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
| `LiveSyncClient` | The client. `new LiveSyncClient(opts)`, then `start()` / `stop()` / `connectUrl()`. |
|
|
169
|
-
| `LiveSyncClientOptions` | The full options shape (auth, reseed, getCursor, onChange, onStatus, backoff, wsFactory, log). |
|
|
170
|
-
| `AuthStrategy` | `{ kind: "token"; token }` (backend) or `{ kind: "cookie" }` (browser). |
|
|
171
|
-
| `LiveSyncStatus` | `"connecting"` · `"live"` · `"reconnecting"` · `"resyncing"` · `"error"` · `"stopped"`. |
|
|
172
|
-
| `WebSocketLike` / `WebSocketFactory` | The minimal structural WS surface + the injectable factory (for old Node / tests). |
|
|
173
|
-
| `ChangeFrame` · `ResyncFrame` · `SyncFrame` · `ServerFrame` | The wire frames (see below). |
|
|
174
|
-
| `EntityName` · `ChangeOp` · `ENTITY_NAMES` · `CHANGE_OPS` | The canonical table / op contract (types + frozen runtime arrays). |
|
|
175
|
-
| `buildConnectUrl` · `parseFrame` · `toWsOrigin` | The pure helpers the client is built from (exported for diagnostics/tests). |
|
|
95
|
+
```ts
|
|
96
|
+
import { LiveSyncClient } from "@catalyst-cloud/sdk";
|
|
176
97
|
|
|
177
|
-
|
|
178
|
-
|
|
98
|
+
const issues = new Map<string, unknown>();
|
|
99
|
+
let cursor: number | null = null;
|
|
179
100
|
|
|
180
|
-
|
|
101
|
+
const client = new LiveSyncClient({
|
|
102
|
+
baseUrl: "https://app.example.com/api/v1", // same origin as the page
|
|
103
|
+
accountId: "tenant-0",
|
|
104
|
+
auth: { kind: "cookie" },
|
|
105
|
+
reseed: async () => {
|
|
106
|
+
const snap = (await (await fetch("/api/v1/snapshot?account=tenant-0")).json()) as {
|
|
107
|
+
cursor: number;
|
|
108
|
+
issues: { id: string }[];
|
|
109
|
+
};
|
|
110
|
+
issues.clear();
|
|
111
|
+
for (const row of snap.issues) issues.set(row.id, row);
|
|
112
|
+
cursor = snap.cursor;
|
|
113
|
+
return snap.cursor;
|
|
114
|
+
},
|
|
115
|
+
getCursor: () => cursor,
|
|
116
|
+
onChange: (frame) => {
|
|
117
|
+
if (frame.op === "delete") issues.delete(frame.entityId);
|
|
118
|
+
else issues.set(frame.entityId, frame.row);
|
|
119
|
+
cursor = frame.seq;
|
|
120
|
+
},
|
|
121
|
+
onStatus: (s) => setConnectionBadge(s), // "live" / "reconnecting" / …
|
|
122
|
+
});
|
|
181
123
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
SDK has **no dependency** on that internal package. A contract test pins the literal members so drift
|
|
186
|
-
from the monorepo is caught.
|
|
124
|
+
void client.start(); // never await in the browser
|
|
125
|
+
// client.stop() on teardown
|
|
126
|
+
```
|
|
187
127
|
|
|
188
|
-
|
|
128
|
+
Requires a platform `WebSocket` (browser, Bun, or Node ≥22). On older Node, inject a `wsFactory` that wraps the [`ws`](https://www.npmjs.com/package/ws) package.
|
|
189
129
|
|
|
190
|
-
|
|
191
|
-
(the catalyst-cloud monorepo consumes it via a `file:` dependency). Workflow:
|
|
130
|
+
## API
|
|
192
131
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
132
|
+
| Export | What it is |
|
|
133
|
+
| --- | --- |
|
|
134
|
+
| `LiveSyncClient` | The client — `new LiveSyncClient(opts)`, then `start()` / `stop()`. |
|
|
135
|
+
| `LiveSyncClientOptions` | The options shape (auth, reseed, getCursor, onChange, onStatus, backoff, wsFactory, log). |
|
|
136
|
+
| `AuthStrategy` | `{ kind: "token"; token }` (backend) or `{ kind: "cookie" }` (browser). |
|
|
137
|
+
| `LiveSyncStatus` | `"connecting"` · `"live"` · `"reconnecting"` · `"resyncing"` · `"error"` · `"stopped"`. |
|
|
138
|
+
| `ChangeFrame` · `EntityName` · `ChangeOp` | The change shape + the entity/op contract. |
|
|
199
139
|
|
|
200
|
-
|
|
201
|
-
runs install + build + test on every push and PR. Releases are cut by bumping `version` and running
|
|
202
|
-
`npm publish` (the `prepublishOnly` hook rebuilds `dist/` first; the package is published with public
|
|
203
|
-
access via `publishConfig`).
|
|
140
|
+
`start()` resolves only when `stop()` is called. On a backend, `await` it to keep the process alive; in a browser, never await it — just call `stop()` on teardown.
|
|
204
141
|
|
|
205
142
|
## License
|
|
206
143
|
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// @catalyst-cloud/sdk — public entrypoint.
|
|
2
2
|
//
|
|
3
|
-
// The
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// The live-sync client for the catalyst-cloud change feed (browser + node/bun). Open a WebSocket to a
|
|
4
|
+
// tenant's mirror, send a cursor-replay request on every (re)connect, and apply each pushed change
|
|
5
|
+
// into your own store.
|
|
6
6
|
//
|
|
7
7
|
// The class is storage-agnostic and auth-injected: see {@link LiveSyncClient}.
|
|
8
8
|
export { LiveSyncClient, buildConnectUrl, parseFrame, toWsOrigin, } from "./live-sync-client.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,EAAE;AACF,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,EAAE;AACF,sGAAsG;AACtG,mGAAmG;AACnG,uBAAuB;AACvB,EAAE;AACF,+EAA+E;AAE/E,OAAO,EACL,cAAc,EACd,eAAe,EACf,UAAU,EACV,UAAU,GAOX,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,YAAY,EACZ,UAAU,GAQX,MAAM,YAAY,CAAC"}
|
|
@@ -19,10 +19,10 @@ export type WebSocketFactory = (url: string) => WebSocketLike;
|
|
|
19
19
|
/**
|
|
20
20
|
* How the consumer proves it may open `/connect`.
|
|
21
21
|
*
|
|
22
|
-
* • `token` — a
|
|
23
|
-
* cannot set an Authorization header, so the token rides the URL as `?token=`. The
|
|
24
|
-
* constant-time compares it
|
|
25
|
-
*
|
|
22
|
+
* • `token` — a service bearer token. The WHATWG WebSocket constructor
|
|
23
|
+
* cannot set an Authorization header, so the token rides the URL as `?token=`. The service
|
|
24
|
+
* constant-time compares it and STRIPS it before forwarding internally. Use this ONLY on a
|
|
25
|
+
* trusted backend — never the browser.
|
|
26
26
|
* • `cookie` — append NOTHING to the URL. The browser's same-origin session cookie rides the
|
|
27
27
|
* WebSocket upgrade automatically. This makes it impossible to leak a token from the browser path.
|
|
28
28
|
*/
|
|
@@ -34,20 +34,20 @@ export type AuthStrategy = {
|
|
|
34
34
|
};
|
|
35
35
|
/** Connection lifecycle, surfaced via `onStatus` so a consumer can drive UI. */
|
|
36
36
|
export type LiveSyncStatus = "connecting" | "live" | "reconnecting" | "resyncing" | "error" | "stopped";
|
|
37
|
-
/** Structured log levels
|
|
37
|
+
/** Structured log levels. */
|
|
38
38
|
export type LogLevel = "info" | "warn" | "error";
|
|
39
39
|
/** Configuration for a {@link LiveSyncClient}. */
|
|
40
40
|
export interface LiveSyncClientOptions {
|
|
41
41
|
/**
|
|
42
|
-
* The
|
|
42
|
+
* The service's public origin, http(s), INCLUDING any versioned path prefix (e.g.
|
|
43
43
|
* "https://api.example/api/v1"). The scheme is swapped to ws(s) and `connectPath` is appended
|
|
44
44
|
* verbatim (path-preserving). A trailing slash is trimmed.
|
|
45
45
|
*/
|
|
46
46
|
baseUrl: string;
|
|
47
|
-
/** The tenant id = the
|
|
47
|
+
/** The tenant id = the mirror's name; sent as `?account=` on the connect URL. */
|
|
48
48
|
accountId: string;
|
|
49
49
|
/**
|
|
50
|
-
* The connect route. Default "/connect"; the
|
|
50
|
+
* The connect route. Default "/connect"; the service dual-serves it at "/api/v1/connect" too, so a
|
|
51
51
|
* path-prefixed `baseUrl` ("…/api/v1") with the default "/connect" resolves to "…/api/v1/connect".
|
|
52
52
|
*/
|
|
53
53
|
connectPath?: string;
|
|
@@ -61,7 +61,7 @@ export interface LiveSyncClientOptions {
|
|
|
61
61
|
*/
|
|
62
62
|
reseed: () => Promise<number>;
|
|
63
63
|
/**
|
|
64
|
-
* Read the consumer's durable cursor (the last applied
|
|
64
|
+
* Read the consumer's durable cursor (the last applied feed seq), or null/undefined if it has
|
|
65
65
|
* never seeded. Drives the `{type:"sync", after}` catch-up request. Return `null` to send
|
|
66
66
|
* `after: -1` (replay from the start).
|
|
67
67
|
*/
|
|
@@ -92,6 +92,8 @@ export declare function toWsOrigin(baseUrl: string): string;
|
|
|
92
92
|
* is ever appended (the type system + this single construction point make a browser token leak
|
|
93
93
|
* impossible). Token is ordered FIRST so a truncated log line still reveals the account.
|
|
94
94
|
*/
|
|
95
|
+
/** Strip trailing "/" without a backtracking regex (ReDoS-safe vs `/\/+$/`, CodeQL js/polynomial-redos). */
|
|
96
|
+
export declare function stripTrailingSlashes(s: string): string;
|
|
95
97
|
export declare function buildConnectUrl(opts: {
|
|
96
98
|
baseUrl: string;
|
|
97
99
|
connectPath: string;
|
|
@@ -135,11 +137,11 @@ export declare class LiveSyncClient {
|
|
|
135
137
|
/** Detach handlers BEFORE closing so a programmatic close can't re-enter scheduleReconnect. */
|
|
136
138
|
private closeSocket;
|
|
137
139
|
private scheduleReconnect;
|
|
138
|
-
/** Ask the
|
|
140
|
+
/** Ask the service to replay everything after our durable cursor. */
|
|
139
141
|
private sendSync;
|
|
140
142
|
private handleFrame;
|
|
141
143
|
/**
|
|
142
|
-
* Cursor underflow: the deltas we need were evicted from the
|
|
144
|
+
* Cursor underflow: the deltas we need were evicted from the service's retained change buffer. Close the socket
|
|
143
145
|
* (so no live frame interleaves with the re-seed), re-seed via the injected callback, then reconnect
|
|
144
146
|
* — which re-sends {type:"sync"} from the fresh cursor. `resyncing` guards against a second resync
|
|
145
147
|
* frame and suppresses scheduleReconnect for the duration so we reopen exactly once.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-sync-client.d.ts","sourceRoot":"","sources":["../src/live-sync-client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"live-sync-client.d.ts","sourceRoot":"","sources":["../src/live-sync-client.ts"],"names":[],"mappings":"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"}
|
package/dist/live-sync-client.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
// @catalyst-cloud/sdk — LiveSyncClient: the
|
|
1
|
+
// @catalyst-cloud/sdk — LiveSyncClient: the live-sync client.
|
|
2
2
|
//
|
|
3
|
-
// The push transport for the catalyst-cloud change
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
3
|
+
// The push transport for the catalyst-cloud change feed. It opens an OUTBOUND WebSocket to the
|
|
4
|
+
// tenant's mirror (`{baseUrl}{connectPath}`) and lets the service push each change the instant it
|
|
5
|
+
// lands. On every (re)connect it sends `{type:"sync", after:<cursor>}` so the service replays — in
|
|
6
|
+
// seq order — everything the consumer missed; a `{type:"resync"}` frame (cursor underflow) triggers a
|
|
7
|
+
// full re-seed via the injected `reseed` callback. It reconnects with capped exponential backoff and
|
|
8
|
+
// never throws out of the event loop.
|
|
9
9
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// any host-only assumption. Those are INJECTED:
|
|
10
|
+
// It is deliberately transport-only and storage-agnostic: it does NOT know about a specific storage
|
|
11
|
+
// engine, snapshot endpoint, or auth token — any host-only assumption is INJECTED:
|
|
13
12
|
//
|
|
14
13
|
// • auth — {kind:"token", token} (host → ?token=) OR {kind:"cookie"} (browser → nothing; the
|
|
15
14
|
// same-origin cookie rides the upgrade). A cookie-kind client can never leak a token.
|
|
@@ -23,8 +22,7 @@
|
|
|
23
22
|
// node-only import like 'ws'.
|
|
24
23
|
//
|
|
25
24
|
// There is intentionally NO setInterval keepalive: the WHATWG WebSocket ping/pong is handled by the
|
|
26
|
-
// platform and a re-implemented heartbeat would
|
|
27
|
-
// whole point of moving off SSE.
|
|
25
|
+
// platform, and a re-implemented heartbeat would keep the connection needlessly active.
|
|
28
26
|
/** Resolve the runtime global WebSocket, or fail with an actionable message. */
|
|
29
27
|
function defaultWsFactory(url) {
|
|
30
28
|
const Ctor = globalThis.WebSocket;
|
|
@@ -42,8 +40,15 @@ export function toWsOrigin(baseUrl) {
|
|
|
42
40
|
* is ever appended (the type system + this single construction point make a browser token leak
|
|
43
41
|
* impossible). Token is ordered FIRST so a truncated log line still reveals the account.
|
|
44
42
|
*/
|
|
43
|
+
/** Strip trailing "/" without a backtracking regex (ReDoS-safe vs `/\/+$/`, CodeQL js/polynomial-redos). */
|
|
44
|
+
export function stripTrailingSlashes(s) {
|
|
45
|
+
let end = s.length;
|
|
46
|
+
while (end > 0 && s.charCodeAt(end - 1) === 47 /* "/" */)
|
|
47
|
+
end--;
|
|
48
|
+
return end === s.length ? s : s.slice(0, end);
|
|
49
|
+
}
|
|
45
50
|
export function buildConnectUrl(opts) {
|
|
46
|
-
const origin = toWsOrigin(opts.baseUrl
|
|
51
|
+
const origin = toWsOrigin(stripTrailingSlashes(opts.baseUrl));
|
|
47
52
|
const params = new URLSearchParams();
|
|
48
53
|
if (opts.auth.kind === "token")
|
|
49
54
|
params.set("token", opts.auth.token);
|
|
@@ -71,7 +76,7 @@ export class LiveSyncClient {
|
|
|
71
76
|
reconnectTimer = null;
|
|
72
77
|
resolveDone = null;
|
|
73
78
|
constructor(opts) {
|
|
74
|
-
this.baseUrl = opts.baseUrl
|
|
79
|
+
this.baseUrl = stripTrailingSlashes(opts.baseUrl);
|
|
75
80
|
this.accountId = opts.accountId;
|
|
76
81
|
this.connectPath = opts.connectPath ?? "/connect";
|
|
77
82
|
this.auth = opts.auth;
|
|
@@ -207,7 +212,7 @@ export class LiveSyncClient {
|
|
|
207
212
|
this.openSocket();
|
|
208
213
|
}, delay);
|
|
209
214
|
}
|
|
210
|
-
/** Ask the
|
|
215
|
+
/** Ask the service to replay everything after our durable cursor. */
|
|
211
216
|
sendSync() {
|
|
212
217
|
const after = this.getCursor() ?? -1;
|
|
213
218
|
const frame = { type: "sync", after };
|
|
@@ -240,7 +245,7 @@ export class LiveSyncClient {
|
|
|
240
245
|
}
|
|
241
246
|
}
|
|
242
247
|
/**
|
|
243
|
-
* Cursor underflow: the deltas we need were evicted from the
|
|
248
|
+
* Cursor underflow: the deltas we need were evicted from the service's retained change buffer. Close the socket
|
|
244
249
|
* (so no live frame interleaves with the re-seed), re-seed via the injected callback, then reconnect
|
|
245
250
|
* — which re-sends {type:"sync"} from the fresh cursor. `resyncing` guards against a second resync
|
|
246
251
|
* frame and suppresses scheduleReconnect for the duration so we reopen exactly once.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-sync-client.js","sourceRoot":"","sources":["../src/live-sync-client.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"live-sync-client.js","sourceRoot":"","sources":["../src/live-sync-client.ts"],"names":[],"mappings":"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"}
|
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { CatalystReplica, type CatalystReplicaOptions, type CatalystReplicaReadOnlyOptions, } from "./replica/catalyst-replica.js";
|
|
2
|
+
export { bunSqliteEngine, bunSqliteReadonlyEngine, nodeSqliteEngine, nodeSqliteReadonlyEngine, betterSqlite3Engine, betterSqlite3ReadonlyEngine, autoDetectEngine, autoDetectReadonlyEngine, type ReplicaEngine, type EngineFactory, type EngineBindable, type BetterSqlite3Driver, } from "./replica/engine.js";
|
|
3
|
+
export { claimWriterLock, type WriterGuardOptions, type WriterLockHandle, } from "./replica/writer-lock.js";
|
|
4
|
+
export { LiveSyncClient, type AuthStrategy, type LiveSyncStatus, type LogLevel, type WebSocketLike, type WebSocketFactory, } from "./live-sync-client.js";
|
|
5
|
+
export { ENTITY_NAMES, CHANGE_OPS, type AccountId, type EntityName, type ChangeOp, type ChangeFrame, } from "./types.js";
|
|
6
|
+
export type { SqlExecutor, SqlValue, IssueView, IssueDetailView, PullView, ProjectView, ProjectDetailView, InitiativeView, InitiativeDetailView, } from "@catalyst-cloud/read-model";
|
|
7
|
+
export { mirrorSchema, type MirrorEntityName } from "@catalyst-cloud/schema";
|
|
8
|
+
//# sourceMappingURL=node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAWA,OAAO,EACL,eAAe,EACf,KAAK,sBAAsB,EAC3B,KAAK,8BAA8B,GACpC,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,gBAAgB,EAChB,wBAAwB,EACxB,mBAAmB,EACnB,2BAA2B,EAC3B,gBAAgB,EAChB,wBAAwB,EACxB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EACL,cAAc,EACd,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,gBAAgB,GACtB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,YAAY,EACZ,UAAU,EACV,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,QAAQ,EACb,KAAK,WAAW,GACjB,MAAM,YAAY,CAAC;AAGpB,YAAY,EACV,WAAW,EACX,QAAQ,EACR,SAAS,EACT,eAAe,EACf,QAAQ,EACR,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,oBAAoB,GACrB,MAAM,4BAA4B,CAAC;AAMpC,OAAO,EAAE,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/node.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// @catalyst-cloud/sdk/node — the managed replica for node/bun (CTC-113).
|
|
2
|
+
//
|
|
3
|
+
// `CatalystReplica` is host-sync's writer + read seam behind ONE import: open + migrate a local
|
|
4
|
+
// SQLite replica, stream-seed /snapshot, subscribe to the live change feed (the SDK's LiveSyncClient),
|
|
5
|
+
// and serve the @catalyst-cloud/read-model `build*View` queries SYNCHRONOUSLY over it. Composed from
|
|
6
|
+
// the published @catalyst-cloud/{schema,read-model,replicate} packages; the sqlite driver is INJECTED
|
|
7
|
+
// (default auto-detect: bun:sqlite, else node:sqlite) so the SDK never hard-deps one.
|
|
8
|
+
//
|
|
9
|
+
// The root entrypoint ("@catalyst-cloud/sdk") stays the transport-only, isomorphic surface; this
|
|
10
|
+
// subpath adds the replica. Import this only from a node/bun backend.
|
|
11
|
+
export { CatalystReplica, } from "./replica/catalyst-replica.js";
|
|
12
|
+
export { bunSqliteEngine, bunSqliteReadonlyEngine, nodeSqliteEngine, nodeSqliteReadonlyEngine, betterSqlite3Engine, betterSqlite3ReadonlyEngine, autoDetectEngine, autoDetectReadonlyEngine, } from "./replica/engine.js";
|
|
13
|
+
export { claimWriterLock, } from "./replica/writer-lock.js";
|
|
14
|
+
// Re-export the transport surface so a consumer can stay on one import.
|
|
15
|
+
export { LiveSyncClient, } from "./live-sync-client.js";
|
|
16
|
+
export { ENTITY_NAMES, CHANGE_OPS, } from "./types.js";
|
|
17
|
+
// Re-export the Drizzle table schema so the `.handle` ad-hoc escape hatch is a one-liner — the
|
|
18
|
+
// read-model `build*View` results stay the primary read surface; this is for custom typed queries:
|
|
19
|
+
// import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
20
|
+
// const db = drizzle(replica.handle as Database, { schema: mirrorSchema });
|
|
21
|
+
export { mirrorSchema } from "@catalyst-cloud/schema";
|
|
22
|
+
//# sourceMappingURL=node.js.map
|
package/dist/node.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,EAAE;AACF,gGAAgG;AAChG,uGAAuG;AACvG,qGAAqG;AACrG,sGAAsG;AACtG,sFAAsF;AACtF,EAAE;AACF,iGAAiG;AACjG,sEAAsE;AAEtE,OAAO,EACL,eAAe,GAGhB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,gBAAgB,EAChB,wBAAwB,EACxB,mBAAmB,EACnB,2BAA2B,EAC3B,gBAAgB,EAChB,wBAAwB,GAKzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,eAAe,GAGhB,MAAM,0BAA0B,CAAC;AAElC,wEAAwE;AACxE,OAAO,EACL,cAAc,GAMf,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,YAAY,EACZ,UAAU,GAKX,MAAM,YAAY,CAAC;AAepB,+FAA+F;AAC/F,mGAAmG;AACnG,sDAAsD;AACtD,8EAA8E;AAC9E,OAAO,EAAE,YAAY,EAAyB,MAAM,wBAAwB,CAAC"}
|