silosdk 0.0.8 → 0.0.10
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 +29 -5
- package/dist/internal/indexed-db.cjs +44 -0
- package/dist/internal/indexed-db.mjs +41 -0
- package/dist/media/shared.cjs +99 -0
- package/dist/media/shared.d.cts +26 -0
- package/dist/media/shared.d.mts +26 -0
- package/dist/media/shared.mjs +93 -0
- package/dist/media/storage.native.cjs +102 -0
- package/dist/media/storage.native.mjs +101 -0
- package/dist/media/storage.web.cjs +79 -0
- package/dist/media/storage.web.mjs +78 -0
- package/dist/media.cjs +5 -131
- package/dist/media.d.cts +8 -25
- package/dist/media.d.mts +8 -25
- package/dist/media.mjs +4 -129
- package/dist/media.web.cjs +9 -0
- package/dist/media.web.d.cts +12 -0
- package/dist/media.web.d.mts +12 -0
- package/dist/media.web.mjs +8 -0
- package/dist/settings/shared.cjs +63 -0
- package/dist/settings/shared.d.cts +19 -0
- package/dist/settings/shared.d.mts +19 -0
- package/dist/settings/shared.mjs +59 -0
- package/dist/settings/storage.native.cjs +24 -0
- package/dist/settings/storage.native.mjs +22 -0
- package/dist/settings/storage.web.cjs +34 -0
- package/dist/settings/storage.web.mjs +34 -0
- package/dist/settings.cjs +4 -66
- package/dist/settings.d.cts +4 -18
- package/dist/settings.d.mts +4 -18
- package/dist/settings.mjs +4 -64
- package/dist/settings.web.cjs +8 -0
- package/dist/settings.web.d.cts +6 -0
- package/dist/settings.web.d.mts +6 -0
- package/dist/settings.web.mjs +8 -0
- package/dist/store/shared.cjs +338 -0
- package/dist/store/shared.d.cts +58 -0
- package/dist/store/shared.d.mts +58 -0
- package/dist/store/shared.mjs +333 -0
- package/dist/store/storage.native.cjs +133 -0
- package/dist/store/storage.native.mjs +132 -0
- package/dist/store/storage.web.cjs +142 -0
- package/dist/store/storage.web.mjs +142 -0
- package/dist/store.cjs +4 -421
- package/dist/store.d.cts +4 -58
- package/dist/store.d.mts +4 -58
- package/dist/store.mjs +4 -421
- package/dist/store.web.cjs +15 -0
- package/dist/store.web.d.cts +7 -0
- package/dist/store.web.d.mts +7 -0
- package/dist/store.web.mjs +12 -0
- package/package.json +31 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { assertSettingValue, resolveDefault, storageKey } from "./shared.mjs";
|
|
2
|
+
import Storage from "expo-sqlite/kv-store";
|
|
3
|
+
|
|
4
|
+
//#region src/settings/storage.native.ts
|
|
5
|
+
const settingsStorage = {
|
|
6
|
+
read,
|
|
7
|
+
write
|
|
8
|
+
};
|
|
9
|
+
function read(name, defaultValue) {
|
|
10
|
+
const stored = Storage.getItemSync(storageKey(name));
|
|
11
|
+
if (stored === null) return resolveDefault(defaultValue);
|
|
12
|
+
const value = JSON.parse(stored);
|
|
13
|
+
assertSettingValue(value);
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
function write(name, value) {
|
|
17
|
+
assertSettingValue(value);
|
|
18
|
+
Storage.setItemSync(storageKey(name), JSON.stringify(value));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
export { settingsStorage };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const require_indexed_db = require('../internal/indexed-db.cjs');
|
|
2
|
+
const require_shared = require('./shared.cjs');
|
|
3
|
+
|
|
4
|
+
//#region src/settings/storage.web.ts
|
|
5
|
+
const settingsStorage = {
|
|
6
|
+
read,
|
|
7
|
+
write
|
|
8
|
+
};
|
|
9
|
+
const openSiloSettingsIndexedDb = require_indexed_db.createIndexedDbOpener("silo-settings", 1, (db) => {
|
|
10
|
+
if (!db.objectStoreNames.contains("settings")) db.createObjectStore("settings", { keyPath: "key" });
|
|
11
|
+
});
|
|
12
|
+
async function read(name, defaultValue) {
|
|
13
|
+
const transaction = (await openSiloSettingsIndexedDb()).transaction("settings", "readonly");
|
|
14
|
+
const done = require_indexed_db.transactionDone(transaction);
|
|
15
|
+
const row = await require_indexed_db.requestResult(transaction.objectStore("settings").get(require_shared.storageKey(name)));
|
|
16
|
+
await done;
|
|
17
|
+
if (!row) return require_shared.resolveDefault(defaultValue);
|
|
18
|
+
const value = JSON.parse(row.value);
|
|
19
|
+
require_shared.assertSettingValue(value);
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
async function write(name, value) {
|
|
23
|
+
require_shared.assertSettingValue(value);
|
|
24
|
+
const transaction = (await openSiloSettingsIndexedDb()).transaction("settings", "readwrite");
|
|
25
|
+
transaction.objectStore("settings").put({
|
|
26
|
+
key: require_shared.storageKey(name),
|
|
27
|
+
value: JSON.stringify(value),
|
|
28
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
29
|
+
});
|
|
30
|
+
await require_indexed_db.transactionDone(transaction);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
exports.settingsStorage = settingsStorage;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createIndexedDbOpener, requestResult, transactionDone } from "../internal/indexed-db.mjs";
|
|
2
|
+
import { assertSettingValue, resolveDefault, storageKey } from "./shared.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/settings/storage.web.ts
|
|
5
|
+
const settingsStorage = {
|
|
6
|
+
read,
|
|
7
|
+
write
|
|
8
|
+
};
|
|
9
|
+
const openSiloSettingsIndexedDb = createIndexedDbOpener("silo-settings", 1, (db) => {
|
|
10
|
+
if (!db.objectStoreNames.contains("settings")) db.createObjectStore("settings", { keyPath: "key" });
|
|
11
|
+
});
|
|
12
|
+
async function read(name, defaultValue) {
|
|
13
|
+
const transaction = (await openSiloSettingsIndexedDb()).transaction("settings", "readonly");
|
|
14
|
+
const done = transactionDone(transaction);
|
|
15
|
+
const row = await requestResult(transaction.objectStore("settings").get(storageKey(name)));
|
|
16
|
+
await done;
|
|
17
|
+
if (!row) return resolveDefault(defaultValue);
|
|
18
|
+
const value = JSON.parse(row.value);
|
|
19
|
+
assertSettingValue(value);
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
async function write(name, value) {
|
|
23
|
+
assertSettingValue(value);
|
|
24
|
+
const transaction = (await openSiloSettingsIndexedDb()).transaction("settings", "readwrite");
|
|
25
|
+
transaction.objectStore("settings").put({
|
|
26
|
+
key: storageKey(name),
|
|
27
|
+
value: JSON.stringify(value),
|
|
28
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
29
|
+
});
|
|
30
|
+
await transactionDone(transaction);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
export { settingsStorage };
|
package/dist/settings.cjs
CHANGED
|
@@ -1,70 +1,8 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
let expo_sqlite_kv_store = require("expo-sqlite/kv-store");
|
|
4
|
-
expo_sqlite_kv_store = require_rolldown_runtime.__toESM(expo_sqlite_kv_store);
|
|
1
|
+
const require_shared = require('./settings/shared.cjs');
|
|
2
|
+
const require_storage_native = require('./settings/storage.native.cjs');
|
|
5
3
|
|
|
6
|
-
//#region src/settings/index.ts
|
|
7
|
-
const
|
|
8
|
-
const settingNamePattern = /^[A-Za-z][A-Za-z0-9_.:-]*$/;
|
|
9
|
-
function setting(name, defaultValue) {
|
|
10
|
-
assertSettingName(name);
|
|
11
|
-
assertSettingValue(resolveDefault(defaultValue));
|
|
12
|
-
if (settingNames.has(name)) throw new Error(`Setting "${name}" is already registered. Define each setting once and import the existing setting instead.`);
|
|
13
|
-
settingNames.add(name);
|
|
14
|
-
const queryKey = [
|
|
15
|
-
"silo",
|
|
16
|
-
"setting",
|
|
17
|
-
name
|
|
18
|
-
];
|
|
19
|
-
return {
|
|
20
|
-
name,
|
|
21
|
-
defaultValue,
|
|
22
|
-
queryKey,
|
|
23
|
-
queryOptions() {
|
|
24
|
-
return (0, __tanstack_react_query.queryOptions)({
|
|
25
|
-
queryKey,
|
|
26
|
-
queryFn: () => readSetting(name, defaultValue)
|
|
27
|
-
});
|
|
28
|
-
},
|
|
29
|
-
mutationOptions(options = {}) {
|
|
30
|
-
return (0, __tanstack_react_query.mutationOptions)({
|
|
31
|
-
mutationKey: queryKey,
|
|
32
|
-
mutationFn: async (valueOrUpdater) => {
|
|
33
|
-
const current = readSetting(name, defaultValue);
|
|
34
|
-
const next = typeof valueOrUpdater === "function" ? valueOrUpdater(current) : valueOrUpdater;
|
|
35
|
-
writeSetting(name, next);
|
|
36
|
-
return next;
|
|
37
|
-
},
|
|
38
|
-
onSuccess(value) {
|
|
39
|
-
options.queryClient?.setQueryData(queryKey, value);
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
function readSetting(name, defaultValue) {
|
|
46
|
-
const stored = expo_sqlite_kv_store.default.getItemSync(storageKey(name));
|
|
47
|
-
if (stored === null) return resolveDefault(defaultValue);
|
|
48
|
-
const value = JSON.parse(stored);
|
|
49
|
-
assertSettingValue(value);
|
|
50
|
-
return value;
|
|
51
|
-
}
|
|
52
|
-
function writeSetting(name, value) {
|
|
53
|
-
assertSettingValue(value);
|
|
54
|
-
expo_sqlite_kv_store.default.setItemSync(storageKey(name), JSON.stringify(value));
|
|
55
|
-
}
|
|
56
|
-
function storageKey(name) {
|
|
57
|
-
return `silo:setting:${name}`;
|
|
58
|
-
}
|
|
59
|
-
function resolveDefault(defaultValue) {
|
|
60
|
-
return typeof defaultValue === "function" ? defaultValue() : defaultValue;
|
|
61
|
-
}
|
|
62
|
-
function assertSettingName(name) {
|
|
63
|
-
if (!settingNamePattern.test(name)) throw new Error(`Setting names must start with a letter and contain only letters, numbers, underscores, dots, colons, or hyphens. Received "${name}".`);
|
|
64
|
-
}
|
|
65
|
-
function assertSettingValue(value) {
|
|
66
|
-
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean" && value !== null) throw new Error("Settings must be strings, numbers, booleans, or null.");
|
|
67
|
-
}
|
|
4
|
+
//#region src/settings/index.native.ts
|
|
5
|
+
const { setting } = require_shared.createSettingApi(require_storage_native.settingsStorage);
|
|
68
6
|
|
|
69
7
|
//#endregion
|
|
70
8
|
exports.setting = setting;
|
package/dist/settings.d.cts
CHANGED
|
@@ -1,20 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Setting, SettingDefault, SettingMutation, SettingMutationOptions, SettingQueryKey, SettingValue } from "./settings/shared.cjs";
|
|
2
2
|
|
|
3
|
-
//#region src/settings/index.d.ts
|
|
4
|
-
|
|
5
|
-
type SettingDefault<T extends SettingValue> = T | (() => T);
|
|
6
|
-
type SettingMutation<T extends SettingValue> = T | ((current: T) => T);
|
|
7
|
-
type SettingQueryKey = readonly ['silo', 'setting', string];
|
|
8
|
-
type SettingMutationOptions = {
|
|
9
|
-
queryClient?: QueryClient;
|
|
10
|
-
};
|
|
11
|
-
type Setting<T extends SettingValue> = {
|
|
12
|
-
readonly name: string;
|
|
13
|
-
readonly defaultValue: SettingDefault<T>;
|
|
14
|
-
readonly queryKey: SettingQueryKey;
|
|
15
|
-
queryOptions(): UseQueryOptions<T, Error, T, SettingQueryKey>;
|
|
16
|
-
mutationOptions(options?: SettingMutationOptions): UseMutationOptions<T, Error, SettingMutation<T>>;
|
|
17
|
-
};
|
|
18
|
-
declare function setting<T extends SettingValue>(name: string, defaultValue: SettingDefault<T>): Setting<T>;
|
|
3
|
+
//#region src/settings/index.native.d.ts
|
|
4
|
+
declare const setting: <T extends SettingValue>(name: string, defaultValue: SettingDefault<T>) => Setting<T>;
|
|
19
5
|
//#endregion
|
|
20
|
-
export { Setting, SettingDefault, SettingMutation, SettingMutationOptions, SettingQueryKey, SettingValue, setting };
|
|
6
|
+
export { type Setting, type SettingDefault, type SettingMutation, type SettingMutationOptions, type SettingQueryKey, type SettingValue, setting };
|
package/dist/settings.d.mts
CHANGED
|
@@ -1,20 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Setting, SettingDefault, SettingMutation, SettingMutationOptions, SettingQueryKey, SettingValue } from "./settings/shared.mjs";
|
|
2
2
|
|
|
3
|
-
//#region src/settings/index.d.ts
|
|
4
|
-
|
|
5
|
-
type SettingDefault<T extends SettingValue> = T | (() => T);
|
|
6
|
-
type SettingMutation<T extends SettingValue> = T | ((current: T) => T);
|
|
7
|
-
type SettingQueryKey = readonly ['silo', 'setting', string];
|
|
8
|
-
type SettingMutationOptions = {
|
|
9
|
-
queryClient?: QueryClient;
|
|
10
|
-
};
|
|
11
|
-
type Setting<T extends SettingValue> = {
|
|
12
|
-
readonly name: string;
|
|
13
|
-
readonly defaultValue: SettingDefault<T>;
|
|
14
|
-
readonly queryKey: SettingQueryKey;
|
|
15
|
-
queryOptions(): UseQueryOptions<T, Error, T, SettingQueryKey>;
|
|
16
|
-
mutationOptions(options?: SettingMutationOptions): UseMutationOptions<T, Error, SettingMutation<T>>;
|
|
17
|
-
};
|
|
18
|
-
declare function setting<T extends SettingValue>(name: string, defaultValue: SettingDefault<T>): Setting<T>;
|
|
3
|
+
//#region src/settings/index.native.d.ts
|
|
4
|
+
declare const setting: <T extends SettingValue>(name: string, defaultValue: SettingDefault<T>) => Setting<T>;
|
|
19
5
|
//#endregion
|
|
20
|
-
export { Setting, SettingDefault, SettingMutation, SettingMutationOptions, SettingQueryKey, SettingValue, setting };
|
|
6
|
+
export { type Setting, type SettingDefault, type SettingMutation, type SettingMutationOptions, type SettingQueryKey, type SettingValue, setting };
|
package/dist/settings.mjs
CHANGED
|
@@ -1,68 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import { createSettingApi } from "./settings/shared.mjs";
|
|
2
|
+
import { settingsStorage } from "./settings/storage.native.mjs";
|
|
3
3
|
|
|
4
|
-
//#region src/settings/index.ts
|
|
5
|
-
const
|
|
6
|
-
const settingNamePattern = /^[A-Za-z][A-Za-z0-9_.:-]*$/;
|
|
7
|
-
function setting(name, defaultValue) {
|
|
8
|
-
assertSettingName(name);
|
|
9
|
-
assertSettingValue(resolveDefault(defaultValue));
|
|
10
|
-
if (settingNames.has(name)) throw new Error(`Setting "${name}" is already registered. Define each setting once and import the existing setting instead.`);
|
|
11
|
-
settingNames.add(name);
|
|
12
|
-
const queryKey = [
|
|
13
|
-
"silo",
|
|
14
|
-
"setting",
|
|
15
|
-
name
|
|
16
|
-
];
|
|
17
|
-
return {
|
|
18
|
-
name,
|
|
19
|
-
defaultValue,
|
|
20
|
-
queryKey,
|
|
21
|
-
queryOptions() {
|
|
22
|
-
return queryOptions({
|
|
23
|
-
queryKey,
|
|
24
|
-
queryFn: () => readSetting(name, defaultValue)
|
|
25
|
-
});
|
|
26
|
-
},
|
|
27
|
-
mutationOptions(options = {}) {
|
|
28
|
-
return mutationOptions({
|
|
29
|
-
mutationKey: queryKey,
|
|
30
|
-
mutationFn: async (valueOrUpdater) => {
|
|
31
|
-
const current = readSetting(name, defaultValue);
|
|
32
|
-
const next = typeof valueOrUpdater === "function" ? valueOrUpdater(current) : valueOrUpdater;
|
|
33
|
-
writeSetting(name, next);
|
|
34
|
-
return next;
|
|
35
|
-
},
|
|
36
|
-
onSuccess(value) {
|
|
37
|
-
options.queryClient?.setQueryData(queryKey, value);
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
function readSetting(name, defaultValue) {
|
|
44
|
-
const stored = Storage.getItemSync(storageKey(name));
|
|
45
|
-
if (stored === null) return resolveDefault(defaultValue);
|
|
46
|
-
const value = JSON.parse(stored);
|
|
47
|
-
assertSettingValue(value);
|
|
48
|
-
return value;
|
|
49
|
-
}
|
|
50
|
-
function writeSetting(name, value) {
|
|
51
|
-
assertSettingValue(value);
|
|
52
|
-
Storage.setItemSync(storageKey(name), JSON.stringify(value));
|
|
53
|
-
}
|
|
54
|
-
function storageKey(name) {
|
|
55
|
-
return `silo:setting:${name}`;
|
|
56
|
-
}
|
|
57
|
-
function resolveDefault(defaultValue) {
|
|
58
|
-
return typeof defaultValue === "function" ? defaultValue() : defaultValue;
|
|
59
|
-
}
|
|
60
|
-
function assertSettingName(name) {
|
|
61
|
-
if (!settingNamePattern.test(name)) throw new Error(`Setting names must start with a letter and contain only letters, numbers, underscores, dots, colons, or hyphens. Received "${name}".`);
|
|
62
|
-
}
|
|
63
|
-
function assertSettingValue(value) {
|
|
64
|
-
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean" && value !== null) throw new Error("Settings must be strings, numbers, booleans, or null.");
|
|
65
|
-
}
|
|
4
|
+
//#region src/settings/index.native.ts
|
|
5
|
+
const { setting } = createSettingApi(settingsStorage);
|
|
66
6
|
|
|
67
7
|
//#endregion
|
|
68
8
|
export { setting };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const require_shared = require('./settings/shared.cjs');
|
|
2
|
+
const require_storage_web = require('./settings/storage.web.cjs');
|
|
3
|
+
|
|
4
|
+
//#region src/settings/index.web.ts
|
|
5
|
+
const { setting } = require_shared.createSettingApi(require_storage_web.settingsStorage);
|
|
6
|
+
|
|
7
|
+
//#endregion
|
|
8
|
+
exports.setting = setting;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Setting, SettingDefault, SettingMutation, SettingMutationOptions, SettingQueryKey, SettingValue } from "./settings/shared.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/settings/index.web.d.ts
|
|
4
|
+
declare const setting: <T extends SettingValue>(name: string, defaultValue: SettingDefault<T>) => Setting<T>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { type Setting, type SettingDefault, type SettingMutation, type SettingMutationOptions, type SettingQueryKey, type SettingValue, setting };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Setting, SettingDefault, SettingMutation, SettingMutationOptions, SettingQueryKey, SettingValue } from "./settings/shared.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/settings/index.web.d.ts
|
|
4
|
+
declare const setting: <T extends SettingValue>(name: string, defaultValue: SettingDefault<T>) => Setting<T>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { type Setting, type SettingDefault, type SettingMutation, type SettingMutationOptions, type SettingQueryKey, type SettingValue, setting };
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/store/shared.ts
|
|
3
|
+
const sourceNames = /* @__PURE__ */ new Set();
|
|
4
|
+
const sourceNamePattern = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
5
|
+
const reservedDocumentFields = new Set([
|
|
6
|
+
"id",
|
|
7
|
+
"data",
|
|
8
|
+
"set"
|
|
9
|
+
]);
|
|
10
|
+
const deletedDocs = /* @__PURE__ */ new WeakSet();
|
|
11
|
+
function createStoreApi(storage) {
|
|
12
|
+
function createStore(defaults) {
|
|
13
|
+
const names = Object.keys(defaults);
|
|
14
|
+
const nameSet = new Set(names);
|
|
15
|
+
for (const name of names) {
|
|
16
|
+
assertSourceName(name);
|
|
17
|
+
assertFlatDefaults(name, defaults[name]);
|
|
18
|
+
}
|
|
19
|
+
if (new Set(names).size !== names.length) throw new Error("Store collection names must be unique.");
|
|
20
|
+
return {
|
|
21
|
+
names,
|
|
22
|
+
collectionOptions(name, options = {}) {
|
|
23
|
+
assertStoreCollectionName(nameSet, name);
|
|
24
|
+
return createSourceCollectionOptions(storage, name, defaults[name], options);
|
|
25
|
+
},
|
|
26
|
+
linkCollectionOptions(options = {}) {
|
|
27
|
+
return createLinkCollectionOptions(storage, names, nameSet, options);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function source(name, defaults) {
|
|
32
|
+
assertSourceName(name);
|
|
33
|
+
assertFlatDefaults(name, defaults);
|
|
34
|
+
if (sourceNames.has(name)) throw new Error(`Source "${name}" is already registered. Define each source once and import the existing source instead.`);
|
|
35
|
+
sourceNames.add(name);
|
|
36
|
+
return {
|
|
37
|
+
name,
|
|
38
|
+
defaults,
|
|
39
|
+
schema: createSourceSchema(name, defaults),
|
|
40
|
+
collectionOptions(options = {}) {
|
|
41
|
+
return createSourceCollectionOptions(storage, name, defaults, options);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
createStore,
|
|
47
|
+
source
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function createSourceCollectionOptions(storage, name, defaults, options = {}) {
|
|
51
|
+
return {
|
|
52
|
+
id: name,
|
|
53
|
+
getKey: (item) => item.id,
|
|
54
|
+
schema: createSourceSchema(name, defaults),
|
|
55
|
+
startSync: options.startSync,
|
|
56
|
+
sync: { sync({ begin, write, commit, markReady, truncate }) {
|
|
57
|
+
Promise.resolve(storage.loadRows(name)).then((rows) => {
|
|
58
|
+
begin();
|
|
59
|
+
truncate();
|
|
60
|
+
for (const row of rows) write({
|
|
61
|
+
type: "insert",
|
|
62
|
+
value: row
|
|
63
|
+
});
|
|
64
|
+
commit();
|
|
65
|
+
markReady();
|
|
66
|
+
}).catch((error) => {
|
|
67
|
+
reportSyncError(name, error);
|
|
68
|
+
begin();
|
|
69
|
+
truncate();
|
|
70
|
+
commit();
|
|
71
|
+
markReady();
|
|
72
|
+
});
|
|
73
|
+
} },
|
|
74
|
+
onInsert: async ({ transaction }) => {
|
|
75
|
+
await storage.insertRows(name, transaction.mutations.map((mutation) => mutation.modified));
|
|
76
|
+
},
|
|
77
|
+
onUpdate: async ({ transaction }) => {
|
|
78
|
+
await storage.updateRows(name, transaction.mutations);
|
|
79
|
+
},
|
|
80
|
+
onDelete: async ({ transaction }) => {
|
|
81
|
+
await storage.deleteRows(name, transaction.mutations);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function createLinkCollectionOptions(storage, names, nameSet, options = {}) {
|
|
86
|
+
const schema = createLinkSchema();
|
|
87
|
+
let syncBegin;
|
|
88
|
+
let syncWrite;
|
|
89
|
+
let syncCommit;
|
|
90
|
+
const writeSyncedLink = (type, row) => {
|
|
91
|
+
if (!syncBegin || !syncWrite || !syncCommit) return;
|
|
92
|
+
syncBegin();
|
|
93
|
+
syncWrite({
|
|
94
|
+
type,
|
|
95
|
+
value: row
|
|
96
|
+
});
|
|
97
|
+
syncCommit();
|
|
98
|
+
};
|
|
99
|
+
return {
|
|
100
|
+
id: "silo-links",
|
|
101
|
+
getKey: (item) => item.id,
|
|
102
|
+
schema,
|
|
103
|
+
startSync: options.startSync,
|
|
104
|
+
sync: { sync({ begin, write, commit, markReady, truncate }) {
|
|
105
|
+
syncBegin = begin;
|
|
106
|
+
syncWrite = write;
|
|
107
|
+
syncCommit = commit;
|
|
108
|
+
Promise.resolve(storage.loadLinkRows()).then((storedRows) => {
|
|
109
|
+
const rows = storedRows.map((row) => createLinkRow(names, row));
|
|
110
|
+
begin();
|
|
111
|
+
truncate();
|
|
112
|
+
for (const row of rows) write({
|
|
113
|
+
type: "insert",
|
|
114
|
+
value: row
|
|
115
|
+
});
|
|
116
|
+
commit();
|
|
117
|
+
markReady();
|
|
118
|
+
}).catch((error) => {
|
|
119
|
+
reportSyncError("silo-links", error);
|
|
120
|
+
begin();
|
|
121
|
+
truncate();
|
|
122
|
+
commit();
|
|
123
|
+
markReady();
|
|
124
|
+
});
|
|
125
|
+
} },
|
|
126
|
+
onInsert: async ({ transaction }) => {
|
|
127
|
+
for (const mutation of transaction.mutations) await storage.insertLinkRow(extractStoredLinkRow(mutation.modified));
|
|
128
|
+
},
|
|
129
|
+
onDelete: async ({ transaction }) => {
|
|
130
|
+
for (const mutation of transaction.mutations) await storage.deleteLinkRow(String(mutation.key));
|
|
131
|
+
},
|
|
132
|
+
utils: {
|
|
133
|
+
async link(collectionA, idA, collectionB, idB) {
|
|
134
|
+
const row = createStoredLinkRow(nameSet, collectionA, idA, collectionB, idB);
|
|
135
|
+
if (!await storage.insertLinkRow(row)) writeSyncedLink("insert", createLinkRow(names, row));
|
|
136
|
+
},
|
|
137
|
+
async unlink(collectionA, idA, collectionB, idB) {
|
|
138
|
+
const row = createStoredLinkRow(nameSet, collectionA, idA, collectionB, idB);
|
|
139
|
+
if (await storage.deleteLinkRow(row.id)) writeSyncedLink("delete", createLinkRow(names, row));
|
|
140
|
+
},
|
|
141
|
+
has(collectionA, idA, collectionB, idB) {
|
|
142
|
+
const row = createStoredLinkRow(nameSet, collectionA, idA, collectionB, idB);
|
|
143
|
+
return storage.hasLinkRow(row.id);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function assertSourceName(name) {
|
|
149
|
+
if (!sourceNamePattern.test(name)) throw new Error(`Source names must start with a letter and contain only letters, numbers, underscores, or hyphens. Received "${name}".`);
|
|
150
|
+
}
|
|
151
|
+
function assertStoreCollectionName(nameSet, name) {
|
|
152
|
+
if (!nameSet.has(name)) throw new Error(`Store collection "${name}" is not registered.`);
|
|
153
|
+
}
|
|
154
|
+
function assertLinkCollectionName(nameSet, name) {
|
|
155
|
+
if (!nameSet.has(name)) throw new Error(`Cannot link unknown collection "${name}".`);
|
|
156
|
+
}
|
|
157
|
+
function createStoredLinkRow(nameSet, collectionA, idA, collectionB, idB) {
|
|
158
|
+
assertLinkCollectionName(nameSet, collectionA);
|
|
159
|
+
assertLinkCollectionName(nameSet, collectionB);
|
|
160
|
+
if (collectionA === collectionB) throw new Error(`Links between the same collection are not supported. Received "${collectionA}".`);
|
|
161
|
+
const [firstCollection, firstId, secondCollection, secondId] = collectionA < collectionB ? [
|
|
162
|
+
collectionA,
|
|
163
|
+
idA,
|
|
164
|
+
collectionB,
|
|
165
|
+
idB
|
|
166
|
+
] : [
|
|
167
|
+
collectionB,
|
|
168
|
+
idB,
|
|
169
|
+
collectionA,
|
|
170
|
+
idA
|
|
171
|
+
];
|
|
172
|
+
const collections = encodePair(firstCollection, secondCollection);
|
|
173
|
+
const ids = encodePair(firstId, secondId);
|
|
174
|
+
return {
|
|
175
|
+
id: encodePair(collections, ids),
|
|
176
|
+
collections,
|
|
177
|
+
ids,
|
|
178
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function createLinkRow(names, row) {
|
|
182
|
+
const [firstCollection, secondCollection] = decodePair(row.collections);
|
|
183
|
+
const [firstId, secondId] = decodePair(row.ids);
|
|
184
|
+
const result = {
|
|
185
|
+
id: row.id,
|
|
186
|
+
collections: row.collections,
|
|
187
|
+
ids: row.ids,
|
|
188
|
+
createdAt: row.createdAt
|
|
189
|
+
};
|
|
190
|
+
for (const other of names) {
|
|
191
|
+
const byCurrent = {};
|
|
192
|
+
for (const current of names) {
|
|
193
|
+
let id = null;
|
|
194
|
+
if (other !== current) {
|
|
195
|
+
if (current === firstCollection && other === secondCollection) id = firstId;
|
|
196
|
+
else if (current === secondCollection && other === firstCollection) id = secondId;
|
|
197
|
+
}
|
|
198
|
+
byCurrent[current] = { id };
|
|
199
|
+
}
|
|
200
|
+
result[other] = byCurrent;
|
|
201
|
+
}
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
function extractStoredLinkRow(row) {
|
|
205
|
+
if (!isRecord(row)) throw new Error("Expected a link row object.");
|
|
206
|
+
if (typeof row.id !== "string" || typeof row.collections !== "string" || typeof row.ids !== "string" || typeof row.createdAt !== "string") throw new Error("Link rows must include string id, collections, ids, and createdAt fields.");
|
|
207
|
+
return {
|
|
208
|
+
id: row.id,
|
|
209
|
+
collections: row.collections,
|
|
210
|
+
ids: row.ids,
|
|
211
|
+
createdAt: row.createdAt
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function encodePair(first, second) {
|
|
215
|
+
return `${encodePart(first)}:${encodePart(second)}`;
|
|
216
|
+
}
|
|
217
|
+
function decodePair(value) {
|
|
218
|
+
const parts = value.split(":");
|
|
219
|
+
if (parts.length !== 2) throw new Error(`Invalid encoded link pair "${value}".`);
|
|
220
|
+
return [decodePart(parts[0]), decodePart(parts[1])];
|
|
221
|
+
}
|
|
222
|
+
function encodePart(value) {
|
|
223
|
+
return encodeURIComponent(value);
|
|
224
|
+
}
|
|
225
|
+
function decodePart(value) {
|
|
226
|
+
return decodeURIComponent(value);
|
|
227
|
+
}
|
|
228
|
+
function assertFlatDefaults(sourceName, defaults) {
|
|
229
|
+
for (const [field, defaultValue] of Object.entries(defaults)) {
|
|
230
|
+
if (reservedDocumentFields.has(field)) throw new Error(`Source "${sourceName}" field "${field}" is reserved by Silo documents.`);
|
|
231
|
+
if (!isFieldValue(resolveDefault(defaultValue))) throw new Error(`Source "${sourceName}" field "${field}" must default to a string, number, boolean, null, or a function returning one of those values.`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function createSourceSchema(sourceName, defaults) {
|
|
235
|
+
return { "~standard": {
|
|
236
|
+
version: 1,
|
|
237
|
+
vendor: "silo",
|
|
238
|
+
validate(value) {
|
|
239
|
+
if (!isRecord(value)) return failure("Expected a flat source row object.");
|
|
240
|
+
if (typeof value.id !== "string") return failure("Expected source document field \"id\" to be a string.", ["id"]);
|
|
241
|
+
const inputData = isRecord(value.data) ? value.data : extractDocumentData(value);
|
|
242
|
+
const data = {};
|
|
243
|
+
for (const [field, fieldValue] of Object.entries(inputData)) {
|
|
244
|
+
if (reservedDocumentFields.has(field)) return failure(`Source "${sourceName}" field "${field}" is reserved by Silo documents.`, ["data", field]);
|
|
245
|
+
if (!isFieldValue(fieldValue)) return failure(`Source "${sourceName}" field "${field}" must be a string, number, boolean, or null.`, ["data", field]);
|
|
246
|
+
data[field] = fieldValue;
|
|
247
|
+
}
|
|
248
|
+
for (const [field, defaultValue] of Object.entries(defaults)) {
|
|
249
|
+
if (field in data) continue;
|
|
250
|
+
data[field] = resolveDefault(defaultValue);
|
|
251
|
+
}
|
|
252
|
+
return { value: createDoc(value.id, data) };
|
|
253
|
+
}
|
|
254
|
+
} };
|
|
255
|
+
}
|
|
256
|
+
function createLinkSchema() {
|
|
257
|
+
return { "~standard": {
|
|
258
|
+
version: 1,
|
|
259
|
+
vendor: "silo",
|
|
260
|
+
validate(value) {
|
|
261
|
+
try {
|
|
262
|
+
extractStoredLinkRow(value);
|
|
263
|
+
return { value };
|
|
264
|
+
} catch (error) {
|
|
265
|
+
return failure(error instanceof Error ? error.message : "Expected a link row.");
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
} };
|
|
269
|
+
}
|
|
270
|
+
function createDoc(id, data) {
|
|
271
|
+
const doc = {};
|
|
272
|
+
Object.defineProperty(doc, "id", {
|
|
273
|
+
value: id,
|
|
274
|
+
enumerable: true,
|
|
275
|
+
writable: false,
|
|
276
|
+
configurable: false
|
|
277
|
+
});
|
|
278
|
+
for (const [field, value] of Object.entries(data)) Object.defineProperty(doc, field, {
|
|
279
|
+
value,
|
|
280
|
+
enumerable: true,
|
|
281
|
+
writable: true,
|
|
282
|
+
configurable: true
|
|
283
|
+
});
|
|
284
|
+
Object.defineProperty(doc, "data", {
|
|
285
|
+
value() {
|
|
286
|
+
return deletedDocs.has(this) ? null : extractDocumentData(this);
|
|
287
|
+
},
|
|
288
|
+
enumerable: true,
|
|
289
|
+
writable: false,
|
|
290
|
+
configurable: false
|
|
291
|
+
});
|
|
292
|
+
Object.defineProperty(doc, "set", {
|
|
293
|
+
get() {
|
|
294
|
+
return function setDocumentData(patch) {
|
|
295
|
+
Object.assign(this, patch);
|
|
296
|
+
};
|
|
297
|
+
},
|
|
298
|
+
enumerable: true,
|
|
299
|
+
configurable: false
|
|
300
|
+
});
|
|
301
|
+
return doc;
|
|
302
|
+
}
|
|
303
|
+
function extractDocumentData(value) {
|
|
304
|
+
if (!isRecord(value)) return {};
|
|
305
|
+
const data = {};
|
|
306
|
+
for (const [field, fieldValue] of Object.entries(value)) {
|
|
307
|
+
if (reservedDocumentFields.has(field) || field.startsWith("$") || typeof fieldValue === "function") continue;
|
|
308
|
+
if (isFieldValue(fieldValue)) data[field] = fieldValue;
|
|
309
|
+
}
|
|
310
|
+
return data;
|
|
311
|
+
}
|
|
312
|
+
function resolveDefault(value) {
|
|
313
|
+
const resolved = typeof value === "function" ? value() : value;
|
|
314
|
+
if (!isFieldValue(resolved)) throw new Error("Source defaults must resolve to a string, number, boolean, or null.");
|
|
315
|
+
return resolved;
|
|
316
|
+
}
|
|
317
|
+
function isRecord(value) {
|
|
318
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
319
|
+
}
|
|
320
|
+
function isFieldValue(value) {
|
|
321
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null;
|
|
322
|
+
}
|
|
323
|
+
function failure(message, path) {
|
|
324
|
+
return { issues: [{
|
|
325
|
+
message,
|
|
326
|
+
path
|
|
327
|
+
}] };
|
|
328
|
+
}
|
|
329
|
+
function reportSyncError(collectionName, error) {
|
|
330
|
+
console.error(`Failed to sync Silo collection "${collectionName}".`, error);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
//#endregion
|
|
334
|
+
exports.createDoc = createDoc;
|
|
335
|
+
exports.createStoreApi = createStoreApi;
|
|
336
|
+
exports.decodePair = decodePair;
|
|
337
|
+
exports.deletedDocs = deletedDocs;
|
|
338
|
+
exports.extractDocumentData = extractDocumentData;
|