silosdk 0.0.0 → 0.0.1
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 +3 -1
- package/dist/_virtual/rolldown_runtime.cjs +29 -0
- package/dist/cli/d1.cjs +93 -0
- package/dist/cli/d1.mjs +92 -0
- package/dist/cli/index.cjs +93 -0
- package/dist/cli/index.d.cts +1 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.mjs +94 -0
- package/dist/cli/init.cjs +134 -0
- package/dist/cli/init.mjs +133 -0
- package/dist/cli/kv.cjs +63 -0
- package/dist/cli/kv.mjs +60 -0
- package/dist/cli/r2.cjs +83 -0
- package/dist/cli/r2.mjs +82 -0
- package/dist/cli/wrangler.cjs +93 -0
- package/dist/cli/wrangler.mjs +89 -0
- package/dist/local/adapters/cloudflare.cjs +200 -0
- package/dist/local/adapters/cloudflare.d.cts +50 -0
- package/dist/local/adapters/cloudflare.d.mts +50 -0
- package/dist/local/adapters/cloudflare.mjs +200 -0
- package/dist/local/auth-context.cjs +14 -0
- package/dist/local/auth-context.d.cts +7 -0
- package/dist/local/auth-context.d.mts +7 -0
- package/dist/local/auth-context.mjs +12 -0
- package/dist/local/auth.cjs +109 -0
- package/dist/local/auth.d.cts +26 -0
- package/dist/local/auth.d.mts +26 -0
- package/dist/local/auth.mjs +99 -0
- package/dist/local/commit.cjs +350 -0
- package/dist/local/commit.d.cts +59 -0
- package/dist/local/commit.d.mts +59 -0
- package/dist/local/commit.mjs +349 -0
- package/dist/local/config.cjs +17 -0
- package/dist/local/config.mjs +15 -0
- package/dist/local/index.cjs +16 -0
- package/dist/local/index.d.cts +10 -0
- package/dist/local/index.d.mts +10 -0
- package/dist/local/index.mjs +9 -0
- package/dist/local/provider.cjs +204 -0
- package/dist/local/provider.d.cts +25 -0
- package/dist/local/provider.d.mts +25 -0
- package/dist/local/provider.mjs +203 -0
- package/dist/local/query-store.cjs +276 -0
- package/dist/local/query-store.mjs +274 -0
- package/dist/local/storage.cjs +71 -0
- package/dist/local/storage.d.cts +7 -0
- package/dist/local/storage.d.mts +7 -0
- package/dist/local/storage.mjs +68 -0
- package/dist/local/sync.cjs +124 -0
- package/dist/local/sync.d.cts +36 -0
- package/dist/local/sync.d.mts +36 -0
- package/dist/local/sync.mjs +122 -0
- package/dist/local/view.cjs +257 -0
- package/dist/local/view.d.cts +24 -0
- package/dist/local/view.d.mts +24 -0
- package/dist/local/view.mjs +254 -0
- package/dist/package.cjs +11 -0
- package/dist/package.mjs +5 -0
- package/dist/schema/index.cjs +276 -0
- package/dist/schema/index.d.cts +207 -0
- package/dist/schema/index.d.mts +207 -0
- package/dist/schema/index.mjs +265 -0
- package/dist/server/auth.cjs +132 -0
- package/dist/server/auth.d.cts +49 -0
- package/dist/server/auth.d.mts +49 -0
- package/dist/server/auth.mjs +122 -0
- package/dist/server/d1.cjs +120 -0
- package/dist/server/d1.mjs +116 -0
- package/dist/server/do.cjs +132 -0
- package/dist/server/do.d.cts +21 -0
- package/dist/server/do.d.mts +21 -0
- package/dist/server/do.mjs +131 -0
- package/dist/server/index.cjs +355 -0
- package/dist/server/index.d.cts +65 -0
- package/dist/server/index.d.mts +65 -0
- package/dist/server/index.mjs +348 -0
- package/dist/server/protect.cjs +34 -0
- package/dist/server/protect.d.cts +32 -0
- package/dist/server/protect.d.mts +32 -0
- package/dist/server/protect.mjs +33 -0
- package/dist/server/r2.cjs +58 -0
- package/dist/server/r2.d.cts +4 -0
- package/dist/server/r2.d.mts +4 -0
- package/dist/server/r2.mjs +53 -0
- package/package.json +55 -2
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import { useConfig } from "./config.mjs";
|
|
2
|
+
import { getQueryStoreInstance } from "./view.mjs";
|
|
3
|
+
import { isAssetValue, isResolvedAsset, resolvePendingAsset } from "./storage.mjs";
|
|
4
|
+
import { useSQLiteContext } from "expo-sqlite";
|
|
5
|
+
import { nanoid } from "nanoid/non-secure";
|
|
6
|
+
|
|
7
|
+
//#region src/local/commit.ts
|
|
8
|
+
function isCrudOp(op) {
|
|
9
|
+
return op.kind === "add" || op.kind === "update" || op.kind === "remove";
|
|
10
|
+
}
|
|
11
|
+
function isObject(value) {
|
|
12
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
function collectResolvedAssetKeys(value, out) {
|
|
15
|
+
if (Array.isArray(value)) {
|
|
16
|
+
for (const item of value) collectResolvedAssetKeys(item, out);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (!isObject(value)) return;
|
|
20
|
+
if (isResolvedAsset(value)) {
|
|
21
|
+
out.add(value.key);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
for (const nested of Object.values(value)) collectResolvedAssetKeys(nested, out);
|
|
25
|
+
}
|
|
26
|
+
function buildAssetCleanupTasks(oldValue, newValue, scope) {
|
|
27
|
+
const oldKeys = /* @__PURE__ */ new Set();
|
|
28
|
+
const newKeys = /* @__PURE__ */ new Set();
|
|
29
|
+
collectResolvedAssetKeys(oldValue, oldKeys);
|
|
30
|
+
collectResolvedAssetKeys(newValue, newKeys);
|
|
31
|
+
const tasks = [];
|
|
32
|
+
for (const key of oldKeys) if (!newKeys.has(key)) tasks.push({
|
|
33
|
+
key,
|
|
34
|
+
scope
|
|
35
|
+
});
|
|
36
|
+
return tasks;
|
|
37
|
+
}
|
|
38
|
+
async function resolveAssetsDeep(value, scope, url, getToken) {
|
|
39
|
+
if (Array.isArray(value)) {
|
|
40
|
+
const out$1 = [];
|
|
41
|
+
for (const item of value) out$1.push(await resolveAssetsDeep(item, scope, url, getToken));
|
|
42
|
+
return out$1;
|
|
43
|
+
}
|
|
44
|
+
if (!isObject(value)) return value;
|
|
45
|
+
if (isAssetValue(value)) return resolvePendingAsset(value, scope, url, getToken);
|
|
46
|
+
const out = {};
|
|
47
|
+
for (const [k, v] of Object.entries(value)) out[k] = await resolveAssetsDeep(v, scope, url, getToken);
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
async function resolveAssetsInOps(ops, url, getToken) {
|
|
51
|
+
const resolved = [];
|
|
52
|
+
for (const op of ops) {
|
|
53
|
+
if (!isCrudOp(op)) {
|
|
54
|
+
resolved.push(op);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (op.kind === "remove") {
|
|
58
|
+
resolved.push(op);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const scope = op.scope ?? "private";
|
|
62
|
+
if (op.kind === "add") {
|
|
63
|
+
const value$1 = await resolveAssetsDeep(op.value, scope, url, getToken);
|
|
64
|
+
resolved.push({
|
|
65
|
+
...op,
|
|
66
|
+
value: value$1
|
|
67
|
+
});
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const value = await resolveAssetsDeep(op.value, scope, url, getToken);
|
|
71
|
+
resolved.push({
|
|
72
|
+
...op,
|
|
73
|
+
value
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return resolved;
|
|
77
|
+
}
|
|
78
|
+
async function getExistingRowData(db, viewName, id) {
|
|
79
|
+
const row = await db.getFirstAsync(`SELECT data FROM views WHERE id = ? AND view = ?`, [id, viewName]);
|
|
80
|
+
if (!row?.data) return null;
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(row.data);
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function mergeForUpdate(existing, patch) {
|
|
88
|
+
if (!isObject(existing)) return patch;
|
|
89
|
+
if (!isObject(patch)) return existing;
|
|
90
|
+
return {
|
|
91
|
+
...existing,
|
|
92
|
+
...patch
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
async function planAssetCleanupFromOps(db, ops) {
|
|
96
|
+
const tasks = [];
|
|
97
|
+
for (const op of ops) {
|
|
98
|
+
if (!isCrudOp(op)) continue;
|
|
99
|
+
const scope = op.scope ?? "private";
|
|
100
|
+
if (op.kind === "remove") {
|
|
101
|
+
const existing = await getExistingRowData(db, op.view.name, op.id);
|
|
102
|
+
tasks.push(...buildAssetCleanupTasks(existing, null, scope));
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (op.kind === "update") {
|
|
106
|
+
const existing = await getExistingRowData(db, op.view.name, op.id);
|
|
107
|
+
const merged = mergeForUpdate(existing, op.value);
|
|
108
|
+
tasks.push(...buildAssetCleanupTasks(existing, merged, scope));
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return tasks;
|
|
113
|
+
}
|
|
114
|
+
async function performAssetCleanup(tasks, url, getToken) {
|
|
115
|
+
if (!url || tasks.length === 0) return;
|
|
116
|
+
const unique = /* @__PURE__ */ new Map();
|
|
117
|
+
for (const t of tasks) unique.set(`${t.scope}:${t.key}`, t);
|
|
118
|
+
const headers = {};
|
|
119
|
+
if (getToken) {
|
|
120
|
+
const token = await getToken();
|
|
121
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
122
|
+
}
|
|
123
|
+
await Promise.all(Array.from(unique.values()).map(async ({ key }) => {
|
|
124
|
+
const res = await fetch(`${url}/storage/${encodeURIComponent(key)}`, {
|
|
125
|
+
method: "DELETE",
|
|
126
|
+
headers
|
|
127
|
+
});
|
|
128
|
+
if (![
|
|
129
|
+
204,
|
|
130
|
+
404,
|
|
131
|
+
401,
|
|
132
|
+
403
|
|
133
|
+
].includes(res.status)) console.warn(`[silosdk] asset cleanup failed for key ${key}: ${res.status}`);
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
function useCommit() {
|
|
137
|
+
const db = useSQLiteContext();
|
|
138
|
+
const config = useConfig();
|
|
139
|
+
const { subscribers, syncAdapter } = config;
|
|
140
|
+
const ops = [];
|
|
141
|
+
const touchedViews = /* @__PURE__ */ new Set();
|
|
142
|
+
function view(v, idOrOpts, opts) {
|
|
143
|
+
const id = typeof idOrOpts === "string" ? idOrOpts : void 0;
|
|
144
|
+
const scope = (typeof idOrOpts === "object" ? idOrOpts?.scope : opts?.scope) ?? "private";
|
|
145
|
+
if (id) return {
|
|
146
|
+
update: (value) => {
|
|
147
|
+
touchedViews.add(v);
|
|
148
|
+
ops.push({
|
|
149
|
+
kind: "update",
|
|
150
|
+
view: v,
|
|
151
|
+
id,
|
|
152
|
+
value,
|
|
153
|
+
scope
|
|
154
|
+
});
|
|
155
|
+
},
|
|
156
|
+
remove: () => {
|
|
157
|
+
touchedViews.add(v);
|
|
158
|
+
ops.push({
|
|
159
|
+
kind: "remove",
|
|
160
|
+
view: v,
|
|
161
|
+
id,
|
|
162
|
+
scope
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
child: (subview) => ({
|
|
166
|
+
add: (cid) => {
|
|
167
|
+
touchedViews.add(v);
|
|
168
|
+
touchedViews.add(subview);
|
|
169
|
+
ops.push({
|
|
170
|
+
kind: "link",
|
|
171
|
+
parent: {
|
|
172
|
+
view: v,
|
|
173
|
+
id
|
|
174
|
+
},
|
|
175
|
+
child: {
|
|
176
|
+
view: subview,
|
|
177
|
+
id: cid
|
|
178
|
+
},
|
|
179
|
+
scope
|
|
180
|
+
});
|
|
181
|
+
},
|
|
182
|
+
remove: (cid) => {
|
|
183
|
+
touchedViews.add(v);
|
|
184
|
+
touchedViews.add(subview);
|
|
185
|
+
ops.push({
|
|
186
|
+
kind: "unlink",
|
|
187
|
+
parent: {
|
|
188
|
+
view: v,
|
|
189
|
+
id
|
|
190
|
+
},
|
|
191
|
+
child: {
|
|
192
|
+
view: subview,
|
|
193
|
+
id: cid
|
|
194
|
+
},
|
|
195
|
+
scope
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
};
|
|
200
|
+
return { add: (value) => {
|
|
201
|
+
touchedViews.add(v);
|
|
202
|
+
const id$1 = nanoid();
|
|
203
|
+
ops.push({
|
|
204
|
+
kind: "add",
|
|
205
|
+
view: v,
|
|
206
|
+
id: id$1,
|
|
207
|
+
value,
|
|
208
|
+
scope
|
|
209
|
+
});
|
|
210
|
+
return id$1;
|
|
211
|
+
} };
|
|
212
|
+
}
|
|
213
|
+
return async (handler) => {
|
|
214
|
+
await handler({ view });
|
|
215
|
+
const resolvedOps = await resolveAssetsInOps(ops, config.url, config.getToken);
|
|
216
|
+
const cleanupTasks = await planAssetCleanupFromOps(db, resolvedOps);
|
|
217
|
+
const privateOps = resolvedOps.filter((op) => (op.scope ?? "private") === "private");
|
|
218
|
+
const publicOps = resolvedOps.filter((op) => op.scope === "public");
|
|
219
|
+
const syncOps = privateOps.length > 0 ? await applyOpsToSqlite(db, privateOps) : [];
|
|
220
|
+
for (const sub of subscribers) for (const coll of touchedViews) if (coll.scope !== "public" && sub[0].includes(coll.name)) {
|
|
221
|
+
sub[1].update();
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
if (syncAdapter && syncOps.length > 0) await syncAdapter.send({ ops: syncOps });
|
|
225
|
+
if (publicOps.length > 0) {
|
|
226
|
+
const qs = getQueryStoreInstance();
|
|
227
|
+
const url = config.url;
|
|
228
|
+
const getToken = config.getToken;
|
|
229
|
+
const publicViewNames = new Set(publicOps.map((op) => op.kind === "link" || op.kind === "unlink" ? op.parent.view.name : op.view.name));
|
|
230
|
+
qs.invalidate(publicViewNames);
|
|
231
|
+
if (url) {
|
|
232
|
+
const byView = /* @__PURE__ */ new Map();
|
|
233
|
+
for (const op of publicOps) {
|
|
234
|
+
const viewName = op.kind === "link" || op.kind === "unlink" ? op.parent.view.name : op.view.name;
|
|
235
|
+
const existing = byView.get(viewName) ?? [];
|
|
236
|
+
existing.push(op);
|
|
237
|
+
byView.set(viewName, existing);
|
|
238
|
+
}
|
|
239
|
+
const headers = { "Content-Type": "application/json" };
|
|
240
|
+
if (getToken) headers["Authorization"] = `Bearer ${await getToken()}`;
|
|
241
|
+
await Promise.all(Array.from(byView.entries()).map(async ([viewName, viewOps]) => {
|
|
242
|
+
const res = await fetch(`${url}/public/${viewName}`, {
|
|
243
|
+
method: "POST",
|
|
244
|
+
headers,
|
|
245
|
+
body: JSON.stringify({ ops: viewOps })
|
|
246
|
+
});
|
|
247
|
+
if (!res.ok) {
|
|
248
|
+
qs.invalidate(new Set([viewName]));
|
|
249
|
+
throw new Error(`Public write to ${viewName} failed: ${res.status}`);
|
|
250
|
+
}
|
|
251
|
+
}));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
await performAssetCleanup(cleanupTasks, config.url, config.getToken);
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
async function applyOpsToSqlite(db, ops) {
|
|
258
|
+
const syncOps = [];
|
|
259
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
260
|
+
await db.withTransactionAsync(async () => {
|
|
261
|
+
for (const op of ops) {
|
|
262
|
+
if (op.kind === "add" && !!op.value) {
|
|
263
|
+
const version = 1;
|
|
264
|
+
await db.runAsync(`INSERT INTO views (id, view, data, createdAt, updatedAt, version) VALUES (?, ?, ?, ?, ?, ?);`, [
|
|
265
|
+
op.id,
|
|
266
|
+
op.view.name,
|
|
267
|
+
JSON.stringify(op.value),
|
|
268
|
+
timestamp,
|
|
269
|
+
timestamp,
|
|
270
|
+
version
|
|
271
|
+
]);
|
|
272
|
+
syncOps.push({
|
|
273
|
+
...op,
|
|
274
|
+
version,
|
|
275
|
+
timestamp
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
if (op.kind === "update" && !!op.id && !!op.value) {
|
|
279
|
+
await db.runAsync(`UPDATE views
|
|
280
|
+
SET data = jsonb_patch(data, ?), updatedAt = ?, version = version + 1
|
|
281
|
+
WHERE id = ? AND view = ?;`, [
|
|
282
|
+
JSON.stringify(op.value),
|
|
283
|
+
timestamp,
|
|
284
|
+
op.id,
|
|
285
|
+
op.view.name
|
|
286
|
+
]);
|
|
287
|
+
const result = await db.getFirstAsync(`SELECT version, data FROM views WHERE id = ? AND view = ?`, [op.id, op.view.name]);
|
|
288
|
+
const version = result?.version ?? 1;
|
|
289
|
+
const fullData = result?.data ? JSON.parse(result.data) : op.value;
|
|
290
|
+
syncOps.push({
|
|
291
|
+
...op,
|
|
292
|
+
value: fullData,
|
|
293
|
+
version,
|
|
294
|
+
timestamp
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
if (op.kind === "remove" && !!op.id) {
|
|
298
|
+
const version = ((await db.getFirstAsync(`SELECT version FROM views WHERE id = ? AND view = ?`, [op.id, op.view.name]))?.version ?? 0) + 1;
|
|
299
|
+
await db.runAsync(`DELETE FROM views WHERE id = ? AND view = ?;`, [op.id, op.view.name]);
|
|
300
|
+
await db.runAsync(`DELETE FROM relations
|
|
301
|
+
WHERE (parent = ? AND pid = ?)
|
|
302
|
+
OR (child = ? AND cid = ?);`, [
|
|
303
|
+
op.view.name,
|
|
304
|
+
op.id,
|
|
305
|
+
op.view.name,
|
|
306
|
+
op.id
|
|
307
|
+
]);
|
|
308
|
+
syncOps.push({
|
|
309
|
+
...op,
|
|
310
|
+
version,
|
|
311
|
+
timestamp
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
if (op.kind === "link") {
|
|
315
|
+
await db.runAsync(`INSERT INTO relations (parent, pid, child, cid) VALUES (?, ?, ?, ?);`, [
|
|
316
|
+
op.parent.view.name,
|
|
317
|
+
op.parent.id,
|
|
318
|
+
op.child.view.name,
|
|
319
|
+
op.child.id
|
|
320
|
+
]);
|
|
321
|
+
syncOps.push({
|
|
322
|
+
...op,
|
|
323
|
+
version: 0,
|
|
324
|
+
timestamp
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
if (op.kind === "unlink") {
|
|
328
|
+
await db.runAsync(`DELETE FROM relations WHERE parent = ? AND pid = ? AND child = ? AND cid = ?;`, [
|
|
329
|
+
op.parent.view.name,
|
|
330
|
+
op.parent.id,
|
|
331
|
+
op.child.view.name,
|
|
332
|
+
op.child.id
|
|
333
|
+
]);
|
|
334
|
+
syncOps.push({
|
|
335
|
+
...op,
|
|
336
|
+
version: 0,
|
|
337
|
+
timestamp
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}).catch((err) => {
|
|
342
|
+
console.error(err);
|
|
343
|
+
throw err;
|
|
344
|
+
});
|
|
345
|
+
return syncOps;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
//#endregion
|
|
349
|
+
export { useCommit };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
let react = require("react");
|
|
3
|
+
|
|
4
|
+
//#region src/local/config.ts
|
|
5
|
+
const Context = (0, react.createContext)({
|
|
6
|
+
subscribers: /* @__PURE__ */ new Map(),
|
|
7
|
+
authReadyPromise: Promise.resolve()
|
|
8
|
+
});
|
|
9
|
+
function useConfig() {
|
|
10
|
+
const ctx = (0, react.useContext)(Context);
|
|
11
|
+
if (!ctx) throw new Error("silosdk <Provider> is missing above this component");
|
|
12
|
+
return ctx;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
exports.Context = Context;
|
|
17
|
+
exports.useConfig = useConfig;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/local/config.ts
|
|
4
|
+
const Context = createContext({
|
|
5
|
+
subscribers: /* @__PURE__ */ new Map(),
|
|
6
|
+
authReadyPromise: Promise.resolve()
|
|
7
|
+
});
|
|
8
|
+
function useConfig() {
|
|
9
|
+
const ctx = useContext(Context);
|
|
10
|
+
if (!ctx) throw new Error("silosdk <Provider> is missing above this component");
|
|
11
|
+
return ctx;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
export { Context, useConfig };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const require_schema_index = require('../schema/index.cjs');
|
|
2
|
+
const require_cloudflare = require('./adapters/cloudflare.cjs');
|
|
3
|
+
const require_view = require('./view.cjs');
|
|
4
|
+
const require_auth_context = require('./auth-context.cjs');
|
|
5
|
+
const require_provider = require('./provider.cjs');
|
|
6
|
+
const require_storage = require('./storage.cjs');
|
|
7
|
+
const require_commit = require('./commit.cjs');
|
|
8
|
+
|
|
9
|
+
exports.CloudflareSyncAdapter = require_cloudflare.CloudflareSyncAdapter;
|
|
10
|
+
exports.Provider = require_provider.Provider;
|
|
11
|
+
exports.useAssetUrl = require_storage.useAssetUrl;
|
|
12
|
+
exports.useAuth = require_auth_context.useAuth;
|
|
13
|
+
exports.useCommit = require_commit.useCommit;
|
|
14
|
+
exports.useListView = require_view.useListView;
|
|
15
|
+
exports.useView = require_view.useView;
|
|
16
|
+
exports.view = require_schema_index.view;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Provider } from "./provider.cjs";
|
|
2
|
+
import { Infer, View, view } from "../schema/index.cjs";
|
|
3
|
+
import { useListView, useView } from "./view.cjs";
|
|
4
|
+
import { MergeFn, SyncAdapter, SyncOp, SyncPayload } from "./sync.cjs";
|
|
5
|
+
import { useCommit } from "./commit.cjs";
|
|
6
|
+
import { AuthState, AuthUser } from "./auth.cjs";
|
|
7
|
+
import { useAuth } from "./auth-context.cjs";
|
|
8
|
+
import { useAssetUrl } from "./storage.cjs";
|
|
9
|
+
import { CloudflareSyncAdapter } from "./adapters/cloudflare.cjs";
|
|
10
|
+
export { type AuthState, type AuthUser, CloudflareSyncAdapter, type Infer, type MergeFn, Provider, type SyncAdapter, type SyncOp, type SyncPayload, type View, useAssetUrl, useAuth, useCommit, useListView, useView, view };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Provider } from "./provider.mjs";
|
|
2
|
+
import { Infer, View, view } from "../schema/index.mjs";
|
|
3
|
+
import { useListView, useView } from "./view.mjs";
|
|
4
|
+
import { MergeFn, SyncAdapter, SyncOp, SyncPayload } from "./sync.mjs";
|
|
5
|
+
import { useCommit } from "./commit.mjs";
|
|
6
|
+
import { AuthState, AuthUser } from "./auth.mjs";
|
|
7
|
+
import { useAuth } from "./auth-context.mjs";
|
|
8
|
+
import { useAssetUrl } from "./storage.mjs";
|
|
9
|
+
import { CloudflareSyncAdapter } from "./adapters/cloudflare.mjs";
|
|
10
|
+
export { type AuthState, type AuthUser, CloudflareSyncAdapter, type Infer, type MergeFn, Provider, type SyncAdapter, type SyncOp, type SyncPayload, type View, useAssetUrl, useAuth, useCommit, useListView, useView, view };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { view } from "../schema/index.mjs";
|
|
2
|
+
import { CloudflareSyncAdapter } from "./adapters/cloudflare.mjs";
|
|
3
|
+
import { useListView, useView } from "./view.mjs";
|
|
4
|
+
import { useAuth } from "./auth-context.mjs";
|
|
5
|
+
import { Provider } from "./provider.mjs";
|
|
6
|
+
import { useAssetUrl } from "./storage.mjs";
|
|
7
|
+
import { useCommit } from "./commit.mjs";
|
|
8
|
+
|
|
9
|
+
export { CloudflareSyncAdapter, Provider, useAssetUrl, useAuth, useCommit, useListView, useView, view };
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_sync = require('./sync.cjs');
|
|
3
|
+
const require_cloudflare = require('./adapters/cloudflare.cjs');
|
|
4
|
+
const require_auth = require('./auth.cjs');
|
|
5
|
+
const require_config = require('./config.cjs');
|
|
6
|
+
const require_view = require('./view.cjs');
|
|
7
|
+
const require_auth_context = require('./auth-context.cjs');
|
|
8
|
+
let react = require("react");
|
|
9
|
+
let expo_sqlite = require("expo-sqlite");
|
|
10
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
11
|
+
|
|
12
|
+
//#region src/local/provider.tsx
|
|
13
|
+
function Provider(props) {
|
|
14
|
+
const { config } = props;
|
|
15
|
+
const subscribers = (0, react.useRef)(/* @__PURE__ */ new Map()).current;
|
|
16
|
+
const url = config?.url;
|
|
17
|
+
const prodUrl = config?.prodUrl;
|
|
18
|
+
const [user, setUser] = (0, react.useState)(null);
|
|
19
|
+
const [isLoading, setIsLoading] = (0, react.useState)(true);
|
|
20
|
+
const [authReady, setAuthReady] = (0, react.useState)(false);
|
|
21
|
+
const tokenRef = (0, react.useRef)(null);
|
|
22
|
+
const authReadyPromiseRef = (0, react.useRef)(null);
|
|
23
|
+
const authReadyResolveRef = (0, react.useRef)(null);
|
|
24
|
+
if (!authReadyPromiseRef.current) authReadyPromiseRef.current = new Promise((resolve) => {
|
|
25
|
+
authReadyResolveRef.current = resolve;
|
|
26
|
+
});
|
|
27
|
+
const resolvedGetToken = config?.getToken ? config.getToken : config?.url ? () => require_auth.getStoredSessionToken(config?.url) : void 0;
|
|
28
|
+
const dbName = `${config?.name ?? "app"}.db`;
|
|
29
|
+
(0, react.useEffect)(() => {
|
|
30
|
+
require_auth.setAuthProdUrl(prodUrl ?? null);
|
|
31
|
+
}, [prodUrl]);
|
|
32
|
+
(0, react.useEffect)(() => {
|
|
33
|
+
if (!url) {
|
|
34
|
+
setIsLoading(false);
|
|
35
|
+
setAuthReady(true);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
(async () => {
|
|
39
|
+
try {
|
|
40
|
+
const token = await require_auth.getStoredSessionToken(url);
|
|
41
|
+
if (token) {
|
|
42
|
+
tokenRef.current = token;
|
|
43
|
+
const current = await require_auth.fetchCurrentUser(url, token);
|
|
44
|
+
if (current.user) setUser(current.user);
|
|
45
|
+
else {
|
|
46
|
+
await require_auth.clearStoredSessionToken(url);
|
|
47
|
+
tokenRef.current = null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
} catch (err) {
|
|
51
|
+
console.error("[silosdk] Auth init failed:", err);
|
|
52
|
+
} finally {
|
|
53
|
+
setIsLoading(false);
|
|
54
|
+
setAuthReady(true);
|
|
55
|
+
}
|
|
56
|
+
})();
|
|
57
|
+
}, [url]);
|
|
58
|
+
(0, react.useEffect)(() => {
|
|
59
|
+
if (authReady) authReadyResolveRef.current?.();
|
|
60
|
+
}, [authReady]);
|
|
61
|
+
const signIn = (0, react.useCallback)(async (opts) => {
|
|
62
|
+
if (!url) throw new Error("No URL configured");
|
|
63
|
+
await require_auth.requestOtp(url, opts.email);
|
|
64
|
+
}, [url]);
|
|
65
|
+
const verifyOtp = (0, react.useCallback)(async (opts) => {
|
|
66
|
+
if (!url) throw new Error("No URL configured");
|
|
67
|
+
const result = await require_auth.verifyOtpWithServer(url, opts);
|
|
68
|
+
tokenRef.current = result.token;
|
|
69
|
+
setUser(result.user);
|
|
70
|
+
require_auth.authEvents.emit("token-changed");
|
|
71
|
+
}, [url]);
|
|
72
|
+
const signOut = (0, react.useCallback)(async () => {
|
|
73
|
+
if (!url) return;
|
|
74
|
+
const token = tokenRef.current;
|
|
75
|
+
if (token) await require_auth.signOutFromServer(url, token);
|
|
76
|
+
await require_auth.clearStoredSessionToken(url);
|
|
77
|
+
tokenRef.current = null;
|
|
78
|
+
setUser(null);
|
|
79
|
+
require_auth.authEvents.emit("token-changed");
|
|
80
|
+
}, [url]);
|
|
81
|
+
(0, react.useEffect)(() => {
|
|
82
|
+
if (!url) return;
|
|
83
|
+
const handleUnauthorized = () => {
|
|
84
|
+
tokenRef.current = null;
|
|
85
|
+
setUser(null);
|
|
86
|
+
};
|
|
87
|
+
require_auth.authEvents.on("unauthorized", handleUnauthorized);
|
|
88
|
+
return () => require_auth.authEvents.off("unauthorized", handleUnauthorized);
|
|
89
|
+
}, [url]);
|
|
90
|
+
const authState = {
|
|
91
|
+
user,
|
|
92
|
+
isLoading,
|
|
93
|
+
signIn,
|
|
94
|
+
verifyOtp,
|
|
95
|
+
signOut
|
|
96
|
+
};
|
|
97
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_config.Context.Provider, {
|
|
98
|
+
value: {
|
|
99
|
+
subscribers,
|
|
100
|
+
url: config?.url,
|
|
101
|
+
getToken: resolvedGetToken,
|
|
102
|
+
authReady,
|
|
103
|
+
authReadyPromise: authReadyPromiseRef.current ?? Promise.resolve()
|
|
104
|
+
},
|
|
105
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_auth_context.AuthContext.Provider, {
|
|
106
|
+
value: authState,
|
|
107
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(expo_sqlite.SQLiteProvider, {
|
|
108
|
+
databaseName: dbName,
|
|
109
|
+
onInit: async (db) => {
|
|
110
|
+
await db.execAsync(`
|
|
111
|
+
PRAGMA journal_mode = WAL;
|
|
112
|
+
CREATE TABLE IF NOT EXISTS views (
|
|
113
|
+
id TEXT PRIMARY KEY,
|
|
114
|
+
view TEXT,
|
|
115
|
+
data BLOB,
|
|
116
|
+
createdAt TEXT,
|
|
117
|
+
updatedAt TEXT,
|
|
118
|
+
version INTEGER DEFAULT 0
|
|
119
|
+
);
|
|
120
|
+
CREATE TABLE IF NOT EXISTS relations (
|
|
121
|
+
parent TEXT,
|
|
122
|
+
pid TEXT,
|
|
123
|
+
child TEXT,
|
|
124
|
+
cid TEXT
|
|
125
|
+
);
|
|
126
|
+
`);
|
|
127
|
+
},
|
|
128
|
+
options: { enableChangeListener: true },
|
|
129
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SyncManagerWithConfig, {
|
|
130
|
+
config,
|
|
131
|
+
subscribers
|
|
132
|
+
}), props.children]
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function SyncManagerWithConfig({ config, subscribers }) {
|
|
138
|
+
const db = (0, expo_sqlite.useSQLiteContext)();
|
|
139
|
+
const adapterRef = (0, react.useRef)(void 0);
|
|
140
|
+
const [isAuthenticated, setIsAuthenticated] = (0, react.useState)(null);
|
|
141
|
+
const checkAuth = (0, react.useCallback)(async () => {
|
|
142
|
+
if (!config?.url) {
|
|
143
|
+
setIsAuthenticated(false);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const token = await (config.getToken ? config.getToken : () => require_auth.getStoredSessionToken(config.url))();
|
|
147
|
+
if (!token) {
|
|
148
|
+
setIsAuthenticated(false);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
const res = await fetch(`${config.url}/auth/me`, { headers: { Authorization: `Bearer ${token}` } });
|
|
153
|
+
if (res.ok) setIsAuthenticated(!(await res.json()).isAnonymous);
|
|
154
|
+
else setIsAuthenticated(false);
|
|
155
|
+
} catch {
|
|
156
|
+
setIsAuthenticated(false);
|
|
157
|
+
}
|
|
158
|
+
}, [config?.url, config?.getToken]);
|
|
159
|
+
(0, react.useEffect)(() => {
|
|
160
|
+
checkAuth();
|
|
161
|
+
}, [checkAuth]);
|
|
162
|
+
(0, react.useEffect)(() => {
|
|
163
|
+
const handleTokenChanged = () => {
|
|
164
|
+
require_view.getQueryStoreInstance().clear();
|
|
165
|
+
checkAuth();
|
|
166
|
+
adapterRef.current?.resetConnection();
|
|
167
|
+
};
|
|
168
|
+
require_auth.authEvents.on("token-changed", handleTokenChanged);
|
|
169
|
+
return () => {
|
|
170
|
+
require_auth.authEvents.off("token-changed", handleTokenChanged);
|
|
171
|
+
};
|
|
172
|
+
}, [checkAuth]);
|
|
173
|
+
(0, react.useEffect)(() => {
|
|
174
|
+
if (!config?.url || isAuthenticated !== true) return;
|
|
175
|
+
const resolvedGetToken = config.getToken ? config.getToken : () => require_auth.getStoredSessionToken(config.url);
|
|
176
|
+
const adapter = new require_cloudflare.CloudflareSyncAdapter({
|
|
177
|
+
url: config.url,
|
|
178
|
+
getToken: resolvedGetToken,
|
|
179
|
+
db,
|
|
180
|
+
subscribers
|
|
181
|
+
});
|
|
182
|
+
adapterRef.current = adapter;
|
|
183
|
+
let mounted = true;
|
|
184
|
+
adapter.onReceive(async (payload) => {
|
|
185
|
+
if (mounted) await require_sync.applyRemoteOps(db, payload.ops, subscribers, adapter.merge);
|
|
186
|
+
});
|
|
187
|
+
adapter.connect();
|
|
188
|
+
return () => {
|
|
189
|
+
mounted = false;
|
|
190
|
+
adapter.disconnect();
|
|
191
|
+
adapterRef.current = void 0;
|
|
192
|
+
};
|
|
193
|
+
}, [
|
|
194
|
+
config?.url,
|
|
195
|
+
config?.getToken,
|
|
196
|
+
db,
|
|
197
|
+
subscribers,
|
|
198
|
+
isAuthenticated
|
|
199
|
+
]);
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
//#endregion
|
|
204
|
+
exports.Provider = Provider;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
//#region src/local/provider.d.ts
|
|
5
|
+
type ProviderConfig = {
|
|
6
|
+
/** SQLite database name. Defaults to 'app' (.db appended internally). */
|
|
7
|
+
name?: string;
|
|
8
|
+
/** CF Worker base URL. Enables DO sync + public D1 queries when present. */
|
|
9
|
+
url?: string;
|
|
10
|
+
/** Optional production base URL used for keying SecureStore. */
|
|
11
|
+
prodUrl?: string;
|
|
12
|
+
/**
|
|
13
|
+
* BYO auth: returns the bearer token for HTTP requests and WS auth.
|
|
14
|
+
* When omitted and `url` is set, the built-in auth session token is read
|
|
15
|
+
* automatically from expo-secure-store (Phase 4 built-in auth mode).
|
|
16
|
+
* Return null to send no Authorization header.
|
|
17
|
+
*/
|
|
18
|
+
getToken?: () => Promise<string | null>;
|
|
19
|
+
};
|
|
20
|
+
declare function Provider(props: {
|
|
21
|
+
config?: ProviderConfig;
|
|
22
|
+
children: ReactNode;
|
|
23
|
+
}): react_jsx_runtime0.JSX.Element;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { Provider };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
3
|
+
|
|
4
|
+
//#region src/local/provider.d.ts
|
|
5
|
+
type ProviderConfig = {
|
|
6
|
+
/** SQLite database name. Defaults to 'app' (.db appended internally). */
|
|
7
|
+
name?: string;
|
|
8
|
+
/** CF Worker base URL. Enables DO sync + public D1 queries when present. */
|
|
9
|
+
url?: string;
|
|
10
|
+
/** Optional production base URL used for keying SecureStore. */
|
|
11
|
+
prodUrl?: string;
|
|
12
|
+
/**
|
|
13
|
+
* BYO auth: returns the bearer token for HTTP requests and WS auth.
|
|
14
|
+
* When omitted and `url` is set, the built-in auth session token is read
|
|
15
|
+
* automatically from expo-secure-store (Phase 4 built-in auth mode).
|
|
16
|
+
* Return null to send no Authorization header.
|
|
17
|
+
*/
|
|
18
|
+
getToken?: () => Promise<string | null>;
|
|
19
|
+
};
|
|
20
|
+
declare function Provider(props: {
|
|
21
|
+
config?: ProviderConfig;
|
|
22
|
+
children: ReactNode;
|
|
23
|
+
}): react_jsx_runtime0.JSX.Element;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { Provider };
|