reflectdb 0.1.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/LICENSE +21 -0
- package/README.md +1260 -0
- package/dist/cjs/client/index.cjs +1252 -0
- package/dist/cjs/client/index.d.cts +629 -0
- package/dist/cjs/client/storage/indexeddb.cjs +253 -0
- package/dist/cjs/client/storage/indexeddb.d.cts +85 -0
- package/dist/cjs/core/index.cjs +202 -0
- package/dist/cjs/core/index.d.cts +473 -0
- package/dist/cjs/react/index.cjs +1512 -0
- package/dist/cjs/react/index.d.cts +748 -0
- package/dist/cjs/server/drizzle.cjs +413 -0
- package/dist/cjs/server/drizzle.d.cts +233 -0
- package/dist/cjs/server/index.cjs +4486 -0
- package/dist/cjs/server/index.d.cts +1988 -0
- package/dist/cjs/svelte/index.cjs +1414 -0
- package/dist/cjs/svelte/index.d.cts +645 -0
- package/dist/cjs/transport/bun-ws.cjs +244 -0
- package/dist/cjs/transport/bun-ws.d.cts +215 -0
- package/dist/cjs/transport/polling.cjs +285 -0
- package/dist/cjs/transport/polling.d.cts +210 -0
- package/dist/cjs/transport/sse.cjs +312 -0
- package/dist/cjs/transport/sse.d.cts +205 -0
- package/dist/cjs/transport/ws.cjs +330 -0
- package/dist/cjs/transport/ws.d.cts +236 -0
- package/dist/cjs/vanilla/index.cjs +1430 -0
- package/dist/cjs/vanilla/index.d.cts +657 -0
- package/dist/client/index.d.ts +629 -0
- package/dist/client/index.js +106 -0
- package/dist/client/storage/indexeddb.d.ts +85 -0
- package/dist/client/storage/indexeddb.js +213 -0
- package/dist/core/index.d.ts +473 -0
- package/dist/core/index.js +56 -0
- package/dist/react/index.d.ts +748 -0
- package/dist/react/index.js +366 -0
- package/dist/server/drizzle.d.ts +233 -0
- package/dist/server/drizzle.js +9 -0
- package/dist/server/index.d.ts +1988 -0
- package/dist/server/index.js +3959 -0
- package/dist/shared/esm-3tkwvysa.js +54 -0
- package/dist/shared/esm-b7xs9cde.js +4 -0
- package/dist/shared/esm-rw7jjtrv.js +58 -0
- package/dist/shared/esm-wkwx6bd9.js +25 -0
- package/dist/shared/esm-ytrd3hbq.js +1007 -0
- package/dist/shared/esm-z1xse19c.js +369 -0
- package/dist/svelte/index.d.ts +645 -0
- package/dist/svelte/index.js +262 -0
- package/dist/transport/bun-ws.d.ts +215 -0
- package/dist/transport/bun-ws.js +140 -0
- package/dist/transport/polling.d.ts +210 -0
- package/dist/transport/polling.js +181 -0
- package/dist/transport/sse.d.ts +205 -0
- package/dist/transport/sse.js +208 -0
- package/dist/transport/ws.d.ts +236 -0
- package/dist/transport/ws.js +226 -0
- package/dist/vanilla/index.d.ts +657 -0
- package/dist/vanilla/index.js +278 -0
- package/package.json +253 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SyncClient,
|
|
3
|
+
pushSafely
|
|
4
|
+
} from "../shared/esm-ytrd3hbq.js";
|
|
5
|
+
import"../shared/esm-rw7jjtrv.js";
|
|
6
|
+
import"../shared/esm-3tkwvysa.js";
|
|
7
|
+
import"../shared/esm-b7xs9cde.js";
|
|
8
|
+
|
|
9
|
+
// src/react/context.tsx
|
|
10
|
+
import { createContext, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
11
|
+
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
12
|
+
var SyncContext = createContext(null);
|
|
13
|
+
function createBrowserWsTransport(url) {
|
|
14
|
+
let ws = null;
|
|
15
|
+
let handler = null;
|
|
16
|
+
let intentionalClose = false;
|
|
17
|
+
const sendQueue = [];
|
|
18
|
+
function ensureConnected() {
|
|
19
|
+
if (ws && ws.readyState !== WebSocket.CLOSED && ws.readyState !== WebSocket.CLOSING) {
|
|
20
|
+
return ws;
|
|
21
|
+
}
|
|
22
|
+
const socket = new WebSocket(url);
|
|
23
|
+
socket.onopen = () => {
|
|
24
|
+
for (const msg of sendQueue) {
|
|
25
|
+
socket.send(JSON.stringify(msg));
|
|
26
|
+
}
|
|
27
|
+
sendQueue.length = 0;
|
|
28
|
+
};
|
|
29
|
+
socket.onmessage = (event) => {
|
|
30
|
+
try {
|
|
31
|
+
handler?.(JSON.parse(event.data));
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.warn("[reflectdb] react WS: malformed message ignored:", err instanceof Error ? err.message : String(err));
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
socket.onclose = () => {
|
|
37
|
+
ws = null;
|
|
38
|
+
if (!intentionalClose) {
|
|
39
|
+
handler?.({ type: "disconnect", reason: "transport_closed" });
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
ws = socket;
|
|
43
|
+
return socket;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
async send(message) {
|
|
47
|
+
const socket = ensureConnected();
|
|
48
|
+
if (socket.readyState === WebSocket.OPEN) {
|
|
49
|
+
socket.send(JSON.stringify(message));
|
|
50
|
+
} else {
|
|
51
|
+
sendQueue.push(message);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
subscribe(h) {
|
|
55
|
+
handler = h;
|
|
56
|
+
},
|
|
57
|
+
async close() {
|
|
58
|
+
intentionalClose = true;
|
|
59
|
+
if (ws) {
|
|
60
|
+
ws.close();
|
|
61
|
+
ws = null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function SyncProvider({
|
|
67
|
+
url,
|
|
68
|
+
token,
|
|
69
|
+
tables,
|
|
70
|
+
clientId,
|
|
71
|
+
storage,
|
|
72
|
+
onReauth,
|
|
73
|
+
onError,
|
|
74
|
+
children
|
|
75
|
+
}) {
|
|
76
|
+
const [ctx, setCtx] = useState(null);
|
|
77
|
+
const tablesKey = useMemo(() => JSON.stringify([...tables ?? []].sort()), [tables]);
|
|
78
|
+
const managedTables = useMemo(() => new Set(tables ?? []), [tablesKey]);
|
|
79
|
+
const onReauthRef = useRef(onReauth);
|
|
80
|
+
onReauthRef.current = onReauth;
|
|
81
|
+
const onErrorRef = useRef(onError);
|
|
82
|
+
onErrorRef.current = onError;
|
|
83
|
+
const tokenRef = useRef(token);
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
tokenRef.current = token;
|
|
86
|
+
}, [token]);
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
let cancelled = false;
|
|
89
|
+
const id = clientId ?? `browser-${crypto.randomUUID().slice(0, 8)}`;
|
|
90
|
+
const transport = createBrowserWsTransport(url);
|
|
91
|
+
const client = new SyncClient({
|
|
92
|
+
clientId: id,
|
|
93
|
+
transport,
|
|
94
|
+
token: tokenRef.current,
|
|
95
|
+
storage,
|
|
96
|
+
onReauth: () => onReauthRef.current?.() ?? Promise.reject(new Error("No onReauth handler")),
|
|
97
|
+
onError: (err) => onErrorRef.current?.(err)
|
|
98
|
+
});
|
|
99
|
+
(async () => {
|
|
100
|
+
if (storage) {
|
|
101
|
+
await client.init();
|
|
102
|
+
if (cancelled)
|
|
103
|
+
return;
|
|
104
|
+
setCtx({ client, managedTables });
|
|
105
|
+
}
|
|
106
|
+
await client.connect();
|
|
107
|
+
if (cancelled)
|
|
108
|
+
return;
|
|
109
|
+
for (const table of managedTables) {
|
|
110
|
+
await client.sync(table);
|
|
111
|
+
}
|
|
112
|
+
await client.resume();
|
|
113
|
+
if (cancelled)
|
|
114
|
+
return;
|
|
115
|
+
if (!storage) {
|
|
116
|
+
setCtx({ client, managedTables });
|
|
117
|
+
}
|
|
118
|
+
})();
|
|
119
|
+
return () => {
|
|
120
|
+
cancelled = true;
|
|
121
|
+
setCtx(null);
|
|
122
|
+
client.close();
|
|
123
|
+
};
|
|
124
|
+
}, [url, clientId, storage, managedTables]);
|
|
125
|
+
if (!ctx)
|
|
126
|
+
return null;
|
|
127
|
+
return /* @__PURE__ */ jsxDEV(SyncContext.Provider, {
|
|
128
|
+
value: ctx,
|
|
129
|
+
children
|
|
130
|
+
}, undefined, false, undefined, this);
|
|
131
|
+
}
|
|
132
|
+
function useSyncClient() {
|
|
133
|
+
const ctx = useContext(SyncContext);
|
|
134
|
+
if (!ctx) {
|
|
135
|
+
throw new Error("useSyncClient must be used within a <SyncProvider>");
|
|
136
|
+
}
|
|
137
|
+
return ctx.client;
|
|
138
|
+
}
|
|
139
|
+
// src/react/hooks.ts
|
|
140
|
+
import {
|
|
141
|
+
useCallback,
|
|
142
|
+
useContext as useContext2,
|
|
143
|
+
useEffect as useEffect2,
|
|
144
|
+
useMemo as useMemo2,
|
|
145
|
+
useRef as useRef2,
|
|
146
|
+
useState as useState2,
|
|
147
|
+
useSyncExternalStore
|
|
148
|
+
} from "react";
|
|
149
|
+
function useSync(table, options) {
|
|
150
|
+
const ctx = useContext2(SyncContext);
|
|
151
|
+
if (!ctx) {
|
|
152
|
+
throw new Error("useSync must be used within a <SyncProvider>");
|
|
153
|
+
}
|
|
154
|
+
const { client, managedTables } = ctx;
|
|
155
|
+
useEffect2(() => {
|
|
156
|
+
if (managedTables.has(table))
|
|
157
|
+
return;
|
|
158
|
+
acquireSync(client, table, options?.params, options?.window ? { window: options.window } : undefined);
|
|
159
|
+
client.scheduleBootstrap();
|
|
160
|
+
return () => {
|
|
161
|
+
releaseSync(client, table);
|
|
162
|
+
};
|
|
163
|
+
}, [client, table, managedTables, stableKey(options?.params), options?.window]);
|
|
164
|
+
const subscribe = useCallback((listener) => client.subscribeTable(table, listener), [client, table]);
|
|
165
|
+
const version = useSyncExternalStore(subscribe, () => client.getTableVersion(table), () => client.getTableVersion(table));
|
|
166
|
+
const rows = useMemo2(() => client.getRows(table, { includeDeleted: options?.includeDeleted }), [client, table, version, options?.includeDeleted]);
|
|
167
|
+
const insert = useCallback((rowId, payload) => {
|
|
168
|
+
client.insert(table, rowId, payload);
|
|
169
|
+
pushSafely(client);
|
|
170
|
+
}, [client, table]);
|
|
171
|
+
const update = useCallback((rowId, payload) => {
|
|
172
|
+
client.update(table, rowId, payload);
|
|
173
|
+
pushSafely(client);
|
|
174
|
+
}, [client, table]);
|
|
175
|
+
const remove = useCallback((rowId) => {
|
|
176
|
+
client.delete(table, rowId);
|
|
177
|
+
pushSafely(client);
|
|
178
|
+
}, [client, table]);
|
|
179
|
+
return { rows, insert, update, remove };
|
|
180
|
+
}
|
|
181
|
+
function useSyncStatus() {
|
|
182
|
+
const client = useSyncClient();
|
|
183
|
+
const subscribe = useCallback((listener) => client.subscribe(listener), [client]);
|
|
184
|
+
return useSyncExternalStore(subscribe, () => client.getState(), () => client.getState());
|
|
185
|
+
}
|
|
186
|
+
function useRow(table, rowId) {
|
|
187
|
+
const client = useSyncClient();
|
|
188
|
+
const subscribe = useCallback((listener) => client.subscribeTable(table, listener), [client, table]);
|
|
189
|
+
const version = useSyncExternalStore(subscribe, () => client.getTableVersion(table), () => client.getTableVersion(table));
|
|
190
|
+
return useMemo2(() => client.getRow(table, rowId), [client, table, rowId, version]);
|
|
191
|
+
}
|
|
192
|
+
function usePendingCount() {
|
|
193
|
+
const client = useSyncClient();
|
|
194
|
+
const subscribe = useCallback((listener) => client.subscribe(listener), [client]);
|
|
195
|
+
const version = useSyncExternalStore(subscribe, () => client.getVersion(), () => client.getVersion());
|
|
196
|
+
return useMemo2(() => client.getPendingCount(), [client, version]);
|
|
197
|
+
}
|
|
198
|
+
function useEphemeral(config) {
|
|
199
|
+
const client = useSyncClient();
|
|
200
|
+
const [events, setEvents] = useState2({});
|
|
201
|
+
const timersRef = useRef2(new Map);
|
|
202
|
+
useEffect2(() => {
|
|
203
|
+
const unsub = client.subscribeEphemeral(config.key, (event) => {
|
|
204
|
+
setEvents((prev) => ({ ...prev, [event.userId]: event.data }));
|
|
205
|
+
if (config.ttlMs) {
|
|
206
|
+
const existing = timersRef.current.get(event.userId);
|
|
207
|
+
if (existing) {
|
|
208
|
+
clearTimeout(existing);
|
|
209
|
+
}
|
|
210
|
+
const timer = setTimeout(() => {
|
|
211
|
+
setEvents((prev) => {
|
|
212
|
+
const next = { ...prev };
|
|
213
|
+
delete next[event.userId];
|
|
214
|
+
return next;
|
|
215
|
+
});
|
|
216
|
+
timersRef.current.delete(event.userId);
|
|
217
|
+
}, config.ttlMs);
|
|
218
|
+
timersRef.current.set(event.userId, timer);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
return () => {
|
|
222
|
+
unsub();
|
|
223
|
+
for (const timer of timersRef.current.values()) {
|
|
224
|
+
clearTimeout(timer);
|
|
225
|
+
}
|
|
226
|
+
timersRef.current.clear();
|
|
227
|
+
};
|
|
228
|
+
}, [client, config.key, config.ttlMs]);
|
|
229
|
+
const broadcast = useCallback((data) => client.sendEphemeral({
|
|
230
|
+
key: config.key,
|
|
231
|
+
userId: config.userId,
|
|
232
|
+
data,
|
|
233
|
+
ttlMs: config.ttlMs
|
|
234
|
+
}), [client, config.key, config.userId, config.ttlMs]);
|
|
235
|
+
return { events, broadcast };
|
|
236
|
+
}
|
|
237
|
+
function useTotalCount(table) {
|
|
238
|
+
const client = useSyncClient();
|
|
239
|
+
const subscribe = useCallback((listener) => client.subscribeTable(table, listener), [client, table]);
|
|
240
|
+
const version = useSyncExternalStore(subscribe, () => client.getTableVersion(table), () => client.getTableVersion(table));
|
|
241
|
+
return useMemo2(() => client.getTotalCount(table), [client, table, version]);
|
|
242
|
+
}
|
|
243
|
+
function useLoadMore(table) {
|
|
244
|
+
const client = useSyncClient();
|
|
245
|
+
return useCallback((count) => {
|
|
246
|
+
client.loadMore(table, count);
|
|
247
|
+
}, [client, table]);
|
|
248
|
+
}
|
|
249
|
+
function stableKey(params) {
|
|
250
|
+
if (!params)
|
|
251
|
+
return "";
|
|
252
|
+
return JSON.stringify(params, Object.keys(params).sort());
|
|
253
|
+
}
|
|
254
|
+
var useSyncRefCounts = new WeakMap;
|
|
255
|
+
var useSyncParamKeys = new WeakMap;
|
|
256
|
+
function acquireSync(client, table, params, options) {
|
|
257
|
+
let counts = useSyncRefCounts.get(client);
|
|
258
|
+
if (!counts) {
|
|
259
|
+
counts = new Map;
|
|
260
|
+
useSyncRefCounts.set(client, counts);
|
|
261
|
+
}
|
|
262
|
+
let keys = useSyncParamKeys.get(client);
|
|
263
|
+
if (!keys) {
|
|
264
|
+
keys = new Map;
|
|
265
|
+
useSyncParamKeys.set(client, keys);
|
|
266
|
+
}
|
|
267
|
+
const key = stableKey(params);
|
|
268
|
+
const prev = keys.get(table);
|
|
269
|
+
const cnt = counts.get(table) ?? 0;
|
|
270
|
+
if (cnt === 0 || prev !== key) {
|
|
271
|
+
if (prev !== undefined && prev !== key)
|
|
272
|
+
client.unsync(table);
|
|
273
|
+
client.sync(table, params, options);
|
|
274
|
+
keys.set(table, key);
|
|
275
|
+
}
|
|
276
|
+
counts.set(table, cnt + 1);
|
|
277
|
+
}
|
|
278
|
+
function releaseSync(client, table) {
|
|
279
|
+
const counts = useSyncRefCounts.get(client);
|
|
280
|
+
const keys = useSyncParamKeys.get(client);
|
|
281
|
+
if (!counts || !keys)
|
|
282
|
+
return;
|
|
283
|
+
const cnt = (counts.get(table) ?? 1) - 1;
|
|
284
|
+
if (cnt <= 0) {
|
|
285
|
+
counts.delete(table);
|
|
286
|
+
keys.delete(table);
|
|
287
|
+
client.unsync(table);
|
|
288
|
+
} else {
|
|
289
|
+
counts.set(table, cnt);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// src/react/typed.ts
|
|
293
|
+
import { useMemo as useMemo3 } from "react";
|
|
294
|
+
function derivePresenceKey(name, params) {
|
|
295
|
+
if (!params)
|
|
296
|
+
return `presence:${name}`;
|
|
297
|
+
const sortedKeys = Object.keys(params).sort();
|
|
298
|
+
const stable = {};
|
|
299
|
+
for (const k of sortedKeys)
|
|
300
|
+
stable[k] = params[k];
|
|
301
|
+
return `presence:${name}:${JSON.stringify(stable)}`;
|
|
302
|
+
}
|
|
303
|
+
function createSyncReact(queries) {
|
|
304
|
+
function useSync2(...args) {
|
|
305
|
+
const [table, options] = args;
|
|
306
|
+
const result = useSync(table, options);
|
|
307
|
+
const def = queries?.[table];
|
|
308
|
+
if (def?.__view) {
|
|
309
|
+
return { rows: result.rows, loading: false };
|
|
310
|
+
}
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
function useRow2(table, rowId) {
|
|
314
|
+
return useRow(table, rowId);
|
|
315
|
+
}
|
|
316
|
+
function useTotalCount2(table) {
|
|
317
|
+
return useTotalCount(table);
|
|
318
|
+
}
|
|
319
|
+
function useLoadMore2(table) {
|
|
320
|
+
return useLoadMore(table);
|
|
321
|
+
}
|
|
322
|
+
function usePresence(...args) {
|
|
323
|
+
const name = args[0];
|
|
324
|
+
const params = args[1];
|
|
325
|
+
const def = queries?.[name] ?? {};
|
|
326
|
+
const ttlMs = def.ttlMs;
|
|
327
|
+
const client = useSyncClient();
|
|
328
|
+
const userId = client.config?.clientId ?? "anonymous";
|
|
329
|
+
const key = useMemo3(() => derivePresenceKey(name, params), [name, params]);
|
|
330
|
+
const { events, broadcast } = useEphemeral({
|
|
331
|
+
key,
|
|
332
|
+
userId,
|
|
333
|
+
ttlMs
|
|
334
|
+
});
|
|
335
|
+
const peers = useMemo3(() => Object.entries(events).map(([uid, state]) => ({
|
|
336
|
+
userId: uid,
|
|
337
|
+
state
|
|
338
|
+
})), [events]);
|
|
339
|
+
return { peers, set: broadcast };
|
|
340
|
+
}
|
|
341
|
+
return {
|
|
342
|
+
SyncProvider,
|
|
343
|
+
useSync: useSync2,
|
|
344
|
+
useSyncStatus,
|
|
345
|
+
useRow: useRow2,
|
|
346
|
+
usePendingCount,
|
|
347
|
+
useEphemeral,
|
|
348
|
+
usePresence,
|
|
349
|
+
useTotalCount: useTotalCount2,
|
|
350
|
+
useLoadMore: useLoadMore2
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
export {
|
|
354
|
+
useTotalCount,
|
|
355
|
+
useSyncStatus,
|
|
356
|
+
useSyncClient,
|
|
357
|
+
useSync,
|
|
358
|
+
useRow,
|
|
359
|
+
usePendingCount,
|
|
360
|
+
useLoadMore,
|
|
361
|
+
useEphemeral,
|
|
362
|
+
derivePresenceKey,
|
|
363
|
+
createSyncReact,
|
|
364
|
+
SyncProvider,
|
|
365
|
+
SyncContext
|
|
366
|
+
};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
type OpType = "insert" | "update" | "delete";
|
|
2
|
+
interface AuthContext {
|
|
3
|
+
userId: string;
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
}
|
|
6
|
+
interface DrizzleTableLike {
|
|
7
|
+
$inferSelect: Record<string, unknown>;
|
|
8
|
+
$inferInsert: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Adapter contract for `server.tx({ atomic: true })` — pluggable so non-drizzle
|
|
12
|
+
* data layers (kysely, prisma, raw SQL) can wrap their own BEGIN/COMMIT/ROLLBACK
|
|
13
|
+
* without reflectdb hard-coding a drizzle dependency.
|
|
14
|
+
*
|
|
15
|
+
* Pass an adapter via:
|
|
16
|
+
* - `server.tx({ atomic: <adapter> }, fn)` — per-call override.
|
|
17
|
+
* - `ServerConfig.txAtomic` — server-wide default for `atomic: true`.
|
|
18
|
+
*
|
|
19
|
+
* If neither is supplied and `atomic: true` is requested, reflectdb falls back to
|
|
20
|
+
* the bundled drizzle adapter via dynamic import (no top-level dep). When
|
|
21
|
+
* drizzle isn't installed the fallback throws a clear error directing users to
|
|
22
|
+
* supply a `txAtomic` adapter.
|
|
23
|
+
*/
|
|
24
|
+
interface TxAtomicAdapter {
|
|
25
|
+
/** Begin a transaction on the given db handle. */
|
|
26
|
+
begin(db: unknown): Promise<void>;
|
|
27
|
+
/** Commit the in-flight transaction. */
|
|
28
|
+
commit(db: unknown): Promise<void>;
|
|
29
|
+
/** Roll back the in-flight transaction. */
|
|
30
|
+
rollback(db: unknown): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
interface ResolvedOp {
|
|
33
|
+
type: OpType;
|
|
34
|
+
table: string;
|
|
35
|
+
rowId: string;
|
|
36
|
+
payload: Record<string, unknown> | null;
|
|
37
|
+
hlc: string;
|
|
38
|
+
}
|
|
39
|
+
interface MutationContext<TAuth extends AuthContext = AuthContext> {
|
|
40
|
+
auth: TAuth;
|
|
41
|
+
params: Record<string, unknown>;
|
|
42
|
+
}
|
|
43
|
+
type AuthorizeAction = {
|
|
44
|
+
type: "read";
|
|
45
|
+
table: string;
|
|
46
|
+
params: Record<string, unknown>;
|
|
47
|
+
} | {
|
|
48
|
+
type: "write";
|
|
49
|
+
table: string;
|
|
50
|
+
op: ResolvedOp;
|
|
51
|
+
};
|
|
52
|
+
type Ctx<
|
|
53
|
+
TAuth extends AuthContext,
|
|
54
|
+
TParams extends Record<string, unknown>
|
|
55
|
+
> = {
|
|
56
|
+
auth: TAuth;
|
|
57
|
+
params: TParams;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Function-form scope returns an adapter-interpreted SQL fragment (e.g. a
|
|
61
|
+
* drizzle `SQL`, a kysely `Expression<boolean>`, or a raw string). The generic
|
|
62
|
+
* factory treats it as opaque (`unknown`) — ORM-specific helpers like
|
|
63
|
+
* `drizzleTable` narrow the return type at their call site.
|
|
64
|
+
*/
|
|
65
|
+
type ScopeFn<
|
|
66
|
+
TAuth extends AuthContext,
|
|
67
|
+
TParams extends Record<string, unknown>
|
|
68
|
+
> = (ctx: Ctx<TAuth, TParams>) => unknown;
|
|
69
|
+
type ScopeOpt<
|
|
70
|
+
TRowKey extends string,
|
|
71
|
+
TAuth extends AuthContext,
|
|
72
|
+
TParams extends Record<string, unknown>
|
|
73
|
+
> = TRowKey | {
|
|
74
|
+
auth: keyof TAuth & string;
|
|
75
|
+
column: TRowKey;
|
|
76
|
+
} | ScopeFn<TAuth, TParams>;
|
|
77
|
+
type StampSource<
|
|
78
|
+
TAuth extends AuthContext,
|
|
79
|
+
TParams extends Record<string, unknown>
|
|
80
|
+
> = "auth.userId" | "auth.name" | string | ((ctx: Ctx<TAuth, TParams>) => unknown);
|
|
81
|
+
type ServerSetSource<
|
|
82
|
+
TAuth extends AuthContext,
|
|
83
|
+
TParams extends Record<string, unknown>
|
|
84
|
+
> = unknown | ((ctx: Ctx<TAuth, TParams>) => unknown);
|
|
85
|
+
type FieldPolicy<
|
|
86
|
+
TAuth extends AuthContext,
|
|
87
|
+
TParams extends Record<string, unknown>
|
|
88
|
+
> = "server-only" | "insert-only" | {
|
|
89
|
+
write: (input: {
|
|
90
|
+
ctx: Ctx<TAuth, TParams>;
|
|
91
|
+
existing?: Record<string, unknown> | null;
|
|
92
|
+
payload: Record<string, unknown>;
|
|
93
|
+
}) => boolean | Promise<boolean>;
|
|
94
|
+
};
|
|
95
|
+
interface DefineTableOpts<
|
|
96
|
+
TRow extends Record<string, unknown> = Record<string, unknown>,
|
|
97
|
+
TAuth extends AuthContext = AuthContext,
|
|
98
|
+
TParams extends Record<string, unknown> = Record<string, unknown>
|
|
99
|
+
> {
|
|
100
|
+
scope?: ScopeOpt<keyof TRow & string, TAuth, TParams>;
|
|
101
|
+
rowId?: string;
|
|
102
|
+
ownerStamp?: Partial<Record<keyof TRow & string, StampSource<TAuth, TParams>>>;
|
|
103
|
+
serverSet?: Partial<Record<keyof TRow & string, ServerSetSource<TAuth, TParams>>>;
|
|
104
|
+
policy?: {
|
|
105
|
+
read?: (input: {
|
|
106
|
+
ctx: Ctx<TAuth, TParams>;
|
|
107
|
+
existing?: Record<string, unknown> | null;
|
|
108
|
+
}) => boolean | Promise<boolean>;
|
|
109
|
+
insert?: (input: {
|
|
110
|
+
ctx: Ctx<TAuth, TParams>;
|
|
111
|
+
payload: Record<string, unknown>;
|
|
112
|
+
}) => boolean | Promise<boolean>;
|
|
113
|
+
update?: (input: {
|
|
114
|
+
ctx: Ctx<TAuth, TParams>;
|
|
115
|
+
existing: Record<string, unknown>;
|
|
116
|
+
payload: Record<string, unknown>;
|
|
117
|
+
}) => boolean | Promise<boolean>;
|
|
118
|
+
delete?: (input: {
|
|
119
|
+
ctx: Ctx<TAuth, TParams>;
|
|
120
|
+
existing: Record<string, unknown>;
|
|
121
|
+
}) => boolean | Promise<boolean>;
|
|
122
|
+
fields?: Partial<Record<keyof TRow & string, FieldPolicy<TAuth, TParams>>>;
|
|
123
|
+
};
|
|
124
|
+
hooks?: {
|
|
125
|
+
beforeInsert?: (input: {
|
|
126
|
+
payload: Record<string, unknown>;
|
|
127
|
+
ctx: Ctx<TAuth, TParams>;
|
|
128
|
+
db: any;
|
|
129
|
+
}) => Promise<void> | void;
|
|
130
|
+
afterInsert?: (input: {
|
|
131
|
+
row: Record<string, unknown>;
|
|
132
|
+
ctx: Ctx<TAuth, TParams>;
|
|
133
|
+
db: any;
|
|
134
|
+
}) => Promise<void> | void;
|
|
135
|
+
beforeUpdate?: (input: {
|
|
136
|
+
payload: Record<string, unknown>;
|
|
137
|
+
existing: Record<string, unknown>;
|
|
138
|
+
ctx: Ctx<TAuth, TParams>;
|
|
139
|
+
db: any;
|
|
140
|
+
}) => Promise<void> | void;
|
|
141
|
+
afterUpdate?: (input: {
|
|
142
|
+
row: Record<string, unknown>;
|
|
143
|
+
previous: Record<string, unknown>;
|
|
144
|
+
ctx: Ctx<TAuth, TParams>;
|
|
145
|
+
db: any;
|
|
146
|
+
}) => Promise<void> | void;
|
|
147
|
+
beforeDelete?: (input: {
|
|
148
|
+
existing: Record<string, unknown>;
|
|
149
|
+
ctx: Ctx<TAuth, TParams>;
|
|
150
|
+
db: any;
|
|
151
|
+
}) => Promise<void> | void;
|
|
152
|
+
afterDelete?: (input: {
|
|
153
|
+
rowId: string;
|
|
154
|
+
ctx: Ctx<TAuth, TParams>;
|
|
155
|
+
db: any;
|
|
156
|
+
}) => Promise<void> | void;
|
|
157
|
+
};
|
|
158
|
+
query?: (ctx: Ctx<TAuth, TParams>, db: any) => unknown;
|
|
159
|
+
insert?: (input: {
|
|
160
|
+
payload: Record<string, unknown>;
|
|
161
|
+
existing?: Record<string, unknown> | null;
|
|
162
|
+
ctx: Ctx<TAuth, TParams>;
|
|
163
|
+
db: any;
|
|
164
|
+
}) => Promise<void> | void;
|
|
165
|
+
update?: (input: {
|
|
166
|
+
payload: Record<string, unknown>;
|
|
167
|
+
existing: Record<string, unknown>;
|
|
168
|
+
ctx: Ctx<TAuth, TParams>;
|
|
169
|
+
db: any;
|
|
170
|
+
}) => Promise<void> | void;
|
|
171
|
+
delete?: (input: {
|
|
172
|
+
rowId: string;
|
|
173
|
+
existing: Record<string, unknown> | null;
|
|
174
|
+
ctx: Ctx<TAuth, TParams>;
|
|
175
|
+
db: any;
|
|
176
|
+
}) => Promise<void> | void;
|
|
177
|
+
pk?: keyof TRow & string;
|
|
178
|
+
}
|
|
179
|
+
import { SQL as SQL_1rji } from "drizzle-orm";
|
|
180
|
+
type SQL = SQL_1rji;
|
|
181
|
+
type Ctx2<
|
|
182
|
+
TAuth extends AuthContext,
|
|
183
|
+
TParams extends Record<string, unknown>
|
|
184
|
+
> = {
|
|
185
|
+
auth: TAuth;
|
|
186
|
+
params: TParams;
|
|
187
|
+
};
|
|
188
|
+
type DrizzleScopeFn<
|
|
189
|
+
TAuth extends AuthContext,
|
|
190
|
+
TParams extends Record<string, unknown>
|
|
191
|
+
> = (ctx: Ctx2<TAuth, TParams>) => SQL;
|
|
192
|
+
type DrizzleScopeOpt<
|
|
193
|
+
TRowKey extends string,
|
|
194
|
+
TAuth extends AuthContext,
|
|
195
|
+
TParams extends Record<string, unknown>
|
|
196
|
+
> = TRowKey | {
|
|
197
|
+
auth: keyof TAuth & string;
|
|
198
|
+
column: TRowKey;
|
|
199
|
+
} | DrizzleScopeFn<TAuth, TParams>;
|
|
200
|
+
/**
|
|
201
|
+
* Drizzle-flavored options. Identical surface to `DefineTableOpts` except the
|
|
202
|
+
* row type is derived from `$inferSelect` and function-form scope returns
|
|
203
|
+
* drizzle's `SQL` (vs. the generic factory's `unknown`). Kept for backward
|
|
204
|
+
* compat with the existing public `drizzleTable<TTable, ...>(opts)` call shape.
|
|
205
|
+
*/
|
|
206
|
+
interface DrizzleTableOpts<
|
|
207
|
+
TTable extends DrizzleTableLike,
|
|
208
|
+
TAuth extends AuthContext = AuthContext,
|
|
209
|
+
TParams extends Record<string, unknown> = Record<string, unknown>
|
|
210
|
+
> extends Omit<DefineTableOpts<TTable["$inferSelect"], TAuth, TParams>, "scope"> {
|
|
211
|
+
scope?: DrizzleScopeOpt<keyof TTable["$inferSelect"] & string, TAuth, TParams>;
|
|
212
|
+
}
|
|
213
|
+
declare function drizzleTable<
|
|
214
|
+
TTable extends DrizzleTableLike,
|
|
215
|
+
TAuth extends AuthContext = AuthContext,
|
|
216
|
+
TParams extends Record<string, unknown> = Record<string, unknown>
|
|
217
|
+
>(table: TTable, opts?: DrizzleTableOpts<TTable, TAuth, TParams>): {
|
|
218
|
+
query: (ctx: Ctx2<TAuth, TParams>, db: any) => unknown;
|
|
219
|
+
mutate: (op: ResolvedOp, ctx: MutationContext<TAuth>, db: any) => Promise<void>;
|
|
220
|
+
authorize: (action: AuthorizeAction, ctx: MutationContext<TAuth>, db: any) => Promise<void>;
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* `TxAtomicAdapter` for drizzle handles. Uses raw SQL via `db.run(sql\`BEGIN\`)`
|
|
224
|
+
* — drizzle's async `db.transaction(...)` is unsafe on the bun-sqlite dialect
|
|
225
|
+
* (sync transaction commits before the async body finishes), so we route
|
|
226
|
+
* through `db.run(...)` which is portable across sqlite and postgres adapters.
|
|
227
|
+
*
|
|
228
|
+
* Exported so users can pass it explicitly to `tx({ atomic: drizzleTxAtomic })`
|
|
229
|
+
* or to `ServerConfig.txAtomic` — and so the server's lazy-loaded fallback
|
|
230
|
+
* has a single source of truth.
|
|
231
|
+
*/
|
|
232
|
+
declare const drizzleTxAtomic: TxAtomicAdapter;
|
|
233
|
+
export { drizzleTxAtomic, drizzleTable, DrizzleTableOpts };
|