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.
Files changed (52) hide show
  1. package/README.md +29 -5
  2. package/dist/internal/indexed-db.cjs +44 -0
  3. package/dist/internal/indexed-db.mjs +41 -0
  4. package/dist/media/shared.cjs +99 -0
  5. package/dist/media/shared.d.cts +26 -0
  6. package/dist/media/shared.d.mts +26 -0
  7. package/dist/media/shared.mjs +93 -0
  8. package/dist/media/storage.native.cjs +102 -0
  9. package/dist/media/storage.native.mjs +101 -0
  10. package/dist/media/storage.web.cjs +79 -0
  11. package/dist/media/storage.web.mjs +78 -0
  12. package/dist/media.cjs +5 -131
  13. package/dist/media.d.cts +8 -25
  14. package/dist/media.d.mts +8 -25
  15. package/dist/media.mjs +4 -129
  16. package/dist/media.web.cjs +9 -0
  17. package/dist/media.web.d.cts +12 -0
  18. package/dist/media.web.d.mts +12 -0
  19. package/dist/media.web.mjs +8 -0
  20. package/dist/settings/shared.cjs +63 -0
  21. package/dist/settings/shared.d.cts +19 -0
  22. package/dist/settings/shared.d.mts +19 -0
  23. package/dist/settings/shared.mjs +59 -0
  24. package/dist/settings/storage.native.cjs +24 -0
  25. package/dist/settings/storage.native.mjs +22 -0
  26. package/dist/settings/storage.web.cjs +34 -0
  27. package/dist/settings/storage.web.mjs +34 -0
  28. package/dist/settings.cjs +4 -66
  29. package/dist/settings.d.cts +4 -18
  30. package/dist/settings.d.mts +4 -18
  31. package/dist/settings.mjs +4 -64
  32. package/dist/settings.web.cjs +8 -0
  33. package/dist/settings.web.d.cts +6 -0
  34. package/dist/settings.web.d.mts +6 -0
  35. package/dist/settings.web.mjs +8 -0
  36. package/dist/store/shared.cjs +338 -0
  37. package/dist/store/shared.d.cts +58 -0
  38. package/dist/store/shared.d.mts +58 -0
  39. package/dist/store/shared.mjs +333 -0
  40. package/dist/store/storage.native.cjs +133 -0
  41. package/dist/store/storage.native.mjs +132 -0
  42. package/dist/store/storage.web.cjs +142 -0
  43. package/dist/store/storage.web.mjs +142 -0
  44. package/dist/store.cjs +4 -421
  45. package/dist/store.d.cts +4 -58
  46. package/dist/store.d.mts +4 -58
  47. package/dist/store.mjs +4 -421
  48. package/dist/store.web.cjs +15 -0
  49. package/dist/store.web.d.cts +7 -0
  50. package/dist/store.web.d.mts +7 -0
  51. package/dist/store.web.mjs +12 -0
  52. 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 require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
2
- let __tanstack_react_query = require("@tanstack/react-query");
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 settingNames = /* @__PURE__ */ new Set();
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;
@@ -1,20 +1,6 @@
1
- import { QueryClient, UseMutationOptions, UseQueryOptions } from "@tanstack/react-query";
1
+ import { Setting, SettingDefault, SettingMutation, SettingMutationOptions, SettingQueryKey, SettingValue } from "./settings/shared.cjs";
2
2
 
3
- //#region src/settings/index.d.ts
4
- type SettingValue = string | number | boolean | null;
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 };
@@ -1,20 +1,6 @@
1
- import { QueryClient, UseMutationOptions, UseQueryOptions } from "@tanstack/react-query";
1
+ import { Setting, SettingDefault, SettingMutation, SettingMutationOptions, SettingQueryKey, SettingValue } from "./settings/shared.mjs";
2
2
 
3
- //#region src/settings/index.d.ts
4
- type SettingValue = string | number | boolean | null;
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 { mutationOptions, queryOptions } from "@tanstack/react-query";
2
- import Storage from "expo-sqlite/kv-store";
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 settingNames = /* @__PURE__ */ new Set();
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,8 @@
1
+ import { createSettingApi } from "./settings/shared.mjs";
2
+ import { settingsStorage } from "./settings/storage.web.mjs";
3
+
4
+ //#region src/settings/index.web.ts
5
+ const { setting } = createSettingApi(settingsStorage);
6
+
7
+ //#endregion
8
+ export { 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;