silosdk 0.0.3 → 0.0.5
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 +180 -0
- package/dist/media.cjs +135 -0
- package/dist/media.d.cts +29 -0
- package/dist/media.d.mts +29 -0
- package/dist/media.mjs +133 -0
- package/dist/source.cjs +79 -20
- package/dist/source.d.cts +14 -7
- package/dist/source.d.mts +14 -7
- package/dist/source.mjs +79 -21
- package/package.json +11 -4
package/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Silo SDK
|
|
2
|
+
|
|
3
|
+
Silo SDK provides local-first persistence primitives for Expo apps. It owns local
|
|
4
|
+
storage and returns TanStack-compatible option objects instead of wrapping
|
|
5
|
+
TanStack hooks.
|
|
6
|
+
|
|
7
|
+
Silo owns:
|
|
8
|
+
|
|
9
|
+
- SQLite persistence
|
|
10
|
+
- flat primitive document constraints
|
|
11
|
+
- defaults-first source shape
|
|
12
|
+
- settings storage
|
|
13
|
+
- media file ownership and resolution
|
|
14
|
+
|
|
15
|
+
TanStack owns:
|
|
16
|
+
|
|
17
|
+
- `createCollection()` and live collection queries
|
|
18
|
+
- `useQuery()` and `useMutation()`
|
|
19
|
+
- query and mutation state
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
yarn add silosdk @tanstack/react-db @tanstack/react-query expo-sqlite expo-file-system
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Sources
|
|
28
|
+
|
|
29
|
+
Sources are SQLite-backed document stores that produce options for TanStack DB
|
|
30
|
+
collections.
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { createCollection } from '@tanstack/react-db'
|
|
34
|
+
import { createID, source } from 'silosdk/source'
|
|
35
|
+
|
|
36
|
+
type Post = {
|
|
37
|
+
title: string
|
|
38
|
+
body: string
|
|
39
|
+
published: boolean
|
|
40
|
+
coverImage: string | null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const postsSource = source<Post>('posts', {
|
|
44
|
+
title: '',
|
|
45
|
+
body: '',
|
|
46
|
+
published: false,
|
|
47
|
+
coverImage: null,
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
export const posts = createCollection(postsSource.collectionOptions())
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Query and mutate with TanStack DB directly:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { eq, useLiveQuery } from '@tanstack/react-db'
|
|
57
|
+
|
|
58
|
+
const { data: docs = [] } = useLiveQuery((q) =>
|
|
59
|
+
q.from({ posts }).where(({ posts }) => eq(posts.published, true)),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
const doc = docs[0]
|
|
63
|
+
const post = doc?.data()
|
|
64
|
+
|
|
65
|
+
doc?.id
|
|
66
|
+
post?.title
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
const id = createID()
|
|
71
|
+
|
|
72
|
+
const tx = posts.insert({
|
|
73
|
+
id,
|
|
74
|
+
data: {
|
|
75
|
+
title: 'Hello',
|
|
76
|
+
body: '',
|
|
77
|
+
published: false,
|
|
78
|
+
coverImage: null,
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
await tx.isPersisted.promise
|
|
83
|
+
|
|
84
|
+
await posts.update(id, (doc) => {
|
|
85
|
+
doc.set({ published: true })
|
|
86
|
+
}).isPersisted.promise
|
|
87
|
+
|
|
88
|
+
await posts.delete(id).isPersisted.promise
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Source rows are intentionally flat. Field values must be:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
type FieldValue = string | number | boolean | null
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Settings
|
|
98
|
+
|
|
99
|
+
Settings are small persisted primitive values backed by `expo-sqlite/kv-store`.
|
|
100
|
+
They produce TanStack Query options.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { setting } from 'silosdk/settings'
|
|
104
|
+
|
|
105
|
+
export const theme = setting<'system' | 'light' | 'dark'>('theme', 'system')
|
|
106
|
+
export const reduceMotion = setting('reduceMotion', false)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
|
111
|
+
|
|
112
|
+
const queryClient = useQueryClient()
|
|
113
|
+
const { data: value } = useQuery(theme.queryOptions())
|
|
114
|
+
const setTheme = useMutation(theme.mutationOptions({ queryClient }))
|
|
115
|
+
|
|
116
|
+
setTheme.mutate('dark')
|
|
117
|
+
setTheme.mutate((current) => (current === 'dark' ? 'light' : 'dark'))
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Setting values must also be flat primitives:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
type SettingValue = string | number | boolean | null
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Media
|
|
127
|
+
|
|
128
|
+
Media is singleton infrastructure for app-owned files. Domain documents store
|
|
129
|
+
stable media refs as plain strings, keeping source rows flat.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { media, type MediaRef } from 'silosdk/media'
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Import a file with TanStack Query mutation options:
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
|
139
|
+
|
|
140
|
+
const queryClient = useQueryClient()
|
|
141
|
+
const importMedia = useMutation(media.mutationOptions({ queryClient }))
|
|
142
|
+
|
|
143
|
+
const coverImage = await importMedia.mutateAsync({
|
|
144
|
+
uri: pickedFile.uri,
|
|
145
|
+
kind: 'image',
|
|
146
|
+
name: pickedFile.name,
|
|
147
|
+
mimeType: pickedFile.type,
|
|
148
|
+
})
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Store the returned `media://...` ref in source rows:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
posts.update(postId, (draft) => {
|
|
155
|
+
draft.set({ coverImage })
|
|
156
|
+
})
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Resolve a media ref to a local file URI:
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
const { data: uri } = useQuery(media.queryOptions(post.coverImage))
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`media.queryOptions()` returns `string | null`. It returns `null` when the ref is
|
|
166
|
+
empty, invalid, unknown, or missing on disk.
|
|
167
|
+
|
|
168
|
+
## API Shape
|
|
169
|
+
|
|
170
|
+
Silo exposes definition objects and option factories:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
createCollection(postsSource.collectionOptions())
|
|
174
|
+
useQuery(theme.queryOptions())
|
|
175
|
+
useMutation(theme.mutationOptions({ queryClient }))
|
|
176
|
+
useQuery(media.queryOptions(ref))
|
|
177
|
+
useMutation(media.mutationOptions({ queryClient }))
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
There is no Silo provider and no Silo hook wrapper API.
|
package/dist/media.cjs
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
2
|
+
let expo_sqlite = require("expo-sqlite");
|
|
3
|
+
let nanoid = require("nanoid");
|
|
4
|
+
let __tanstack_react_query = require("@tanstack/react-query");
|
|
5
|
+
let expo_file_system = require("expo-file-system");
|
|
6
|
+
|
|
7
|
+
//#region src/media/index.ts
|
|
8
|
+
const media = {
|
|
9
|
+
queryOptions(ref) {
|
|
10
|
+
return (0, __tanstack_react_query.queryOptions)({
|
|
11
|
+
queryKey: mediaQueryKey(ref),
|
|
12
|
+
queryFn: () => resolveMediaUri(ref)
|
|
13
|
+
});
|
|
14
|
+
},
|
|
15
|
+
mutationOptions(options = {}) {
|
|
16
|
+
return (0, __tanstack_react_query.mutationOptions)({
|
|
17
|
+
mutationKey: [
|
|
18
|
+
"silo",
|
|
19
|
+
"media",
|
|
20
|
+
"import"
|
|
21
|
+
],
|
|
22
|
+
mutationFn: async (input) => {
|
|
23
|
+
const imported = await importMedia(input);
|
|
24
|
+
options.queryClient?.setQueryData(mediaQueryKey(imported.ref), imported.uri);
|
|
25
|
+
return imported.ref;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
function isMediaRef(value) {
|
|
31
|
+
return typeof value === "string" && value.startsWith("media://");
|
|
32
|
+
}
|
|
33
|
+
function mediaQueryKey(ref) {
|
|
34
|
+
return [
|
|
35
|
+
"silo",
|
|
36
|
+
"media",
|
|
37
|
+
ref ?? null
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
async function importMedia(input) {
|
|
41
|
+
assertMediaKind(input.kind);
|
|
42
|
+
const id = (0, nanoid.nanoid)();
|
|
43
|
+
const ref = createMediaRef(input.kind, id);
|
|
44
|
+
const source = new expo_file_system.File(input.uri);
|
|
45
|
+
const name = input.name ?? source.name ?? `${id}${source.extension}`;
|
|
46
|
+
const destinationDirectory = mediaDirectory(input.kind);
|
|
47
|
+
destinationDirectory.create({
|
|
48
|
+
idempotent: true,
|
|
49
|
+
intermediates: true
|
|
50
|
+
});
|
|
51
|
+
const destination = new expo_file_system.File(destinationDirectory, `${id}${extensionFor(name)}`);
|
|
52
|
+
await source.copy(destination);
|
|
53
|
+
const info = destination.info();
|
|
54
|
+
const mimeType = input.mimeType ?? (destination.type || null);
|
|
55
|
+
const size = info.size ?? destination.size;
|
|
56
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
57
|
+
insertMediaFile({
|
|
58
|
+
ref,
|
|
59
|
+
uri: destination.uri,
|
|
60
|
+
kind: input.kind,
|
|
61
|
+
name,
|
|
62
|
+
mimeType,
|
|
63
|
+
size,
|
|
64
|
+
createdAt: timestamp,
|
|
65
|
+
data: input.data ?? {}
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
ref,
|
|
69
|
+
uri: destination.uri
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function resolveMediaUri(ref) {
|
|
73
|
+
if (!isMediaRef(ref)) return null;
|
|
74
|
+
const row = getDatabase().getFirstSync(`SELECT ref, uri FROM media_files WHERE ref = ?`, [ref]);
|
|
75
|
+
if (!row) return null;
|
|
76
|
+
return new expo_file_system.File(row.uri).exists ? row.uri : null;
|
|
77
|
+
}
|
|
78
|
+
function createMediaRef(kind, id) {
|
|
79
|
+
return `media://silo/${kindDirectoryName(kind)}/${id}`;
|
|
80
|
+
}
|
|
81
|
+
function mediaDirectory(kind) {
|
|
82
|
+
return new expo_file_system.Directory(expo_file_system.Paths.document, "silo", "media", kindDirectoryName(kind));
|
|
83
|
+
}
|
|
84
|
+
function kindDirectoryName(kind) {
|
|
85
|
+
switch (kind) {
|
|
86
|
+
case "image": return "images";
|
|
87
|
+
case "video": return "videos";
|
|
88
|
+
case "audio": return "audio";
|
|
89
|
+
case "file": return "files";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function extensionFor(name) {
|
|
93
|
+
return name.match(/\.[A-Za-z0-9]+$/)?.[0] ?? "";
|
|
94
|
+
}
|
|
95
|
+
function assertMediaKind(kind) {
|
|
96
|
+
if (kind !== "image" && kind !== "video" && kind !== "audio" && kind !== "file") throw new Error("Media kind must be one of \"image\", \"video\", \"audio\", or \"file\".");
|
|
97
|
+
}
|
|
98
|
+
let database;
|
|
99
|
+
function getDatabase() {
|
|
100
|
+
if (!database) {
|
|
101
|
+
database = (0, expo_sqlite.openDatabaseSync)("silo.db");
|
|
102
|
+
database.execSync(`
|
|
103
|
+
PRAGMA journal_mode = WAL;
|
|
104
|
+
|
|
105
|
+
CREATE TABLE IF NOT EXISTS media_files (
|
|
106
|
+
ref TEXT PRIMARY KEY,
|
|
107
|
+
uri TEXT NOT NULL,
|
|
108
|
+
kind TEXT NOT NULL,
|
|
109
|
+
name TEXT NOT NULL,
|
|
110
|
+
mimeType TEXT,
|
|
111
|
+
size INTEGER NOT NULL,
|
|
112
|
+
createdAt TEXT NOT NULL,
|
|
113
|
+
data TEXT NOT NULL
|
|
114
|
+
);
|
|
115
|
+
`);
|
|
116
|
+
}
|
|
117
|
+
return database;
|
|
118
|
+
}
|
|
119
|
+
function insertMediaFile(file) {
|
|
120
|
+
getDatabase().runSync(`INSERT INTO media_files (ref, uri, kind, name, mimeType, size, createdAt, data)
|
|
121
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
122
|
+
file.ref,
|
|
123
|
+
file.uri,
|
|
124
|
+
file.kind,
|
|
125
|
+
file.name,
|
|
126
|
+
file.mimeType,
|
|
127
|
+
file.size,
|
|
128
|
+
file.createdAt,
|
|
129
|
+
JSON.stringify(file.data)
|
|
130
|
+
]);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
//#endregion
|
|
134
|
+
exports.isMediaRef = isMediaRef;
|
|
135
|
+
exports.media = media;
|
package/dist/media.d.cts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { QueryClient, UseMutationOptions, UseQueryOptions } from "@tanstack/react-query";
|
|
2
|
+
|
|
3
|
+
//#region src/media/index.d.ts
|
|
4
|
+
type MediaRef = `media://${string}`;
|
|
5
|
+
type MediaKind = 'image' | 'video' | 'audio' | 'file';
|
|
6
|
+
type MediaJson = string | number | boolean | null | MediaJson[] | {
|
|
7
|
+
[key: string]: MediaJson;
|
|
8
|
+
};
|
|
9
|
+
type MediaData = {
|
|
10
|
+
[key: string]: MediaJson;
|
|
11
|
+
};
|
|
12
|
+
type MediaImportInput = {
|
|
13
|
+
uri: string;
|
|
14
|
+
kind: MediaKind;
|
|
15
|
+
name?: string;
|
|
16
|
+
mimeType?: string | null;
|
|
17
|
+
data?: MediaData;
|
|
18
|
+
};
|
|
19
|
+
type MediaQueryKey = readonly ['silo', 'media', MediaRef | string | null];
|
|
20
|
+
type MediaMutationOptions = {
|
|
21
|
+
queryClient?: QueryClient;
|
|
22
|
+
};
|
|
23
|
+
declare const media: {
|
|
24
|
+
queryOptions(ref: MediaRef | string | null | undefined): UseQueryOptions<string | null, Error, string | null, MediaQueryKey>;
|
|
25
|
+
mutationOptions(options?: MediaMutationOptions): UseMutationOptions<MediaRef, Error, MediaImportInput>;
|
|
26
|
+
};
|
|
27
|
+
declare function isMediaRef(value: unknown): value is MediaRef;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { MediaData, MediaImportInput, MediaJson, MediaKind, MediaMutationOptions, MediaQueryKey, MediaRef, isMediaRef, media };
|
package/dist/media.d.mts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { QueryClient, UseMutationOptions, UseQueryOptions } from "@tanstack/react-query";
|
|
2
|
+
|
|
3
|
+
//#region src/media/index.d.ts
|
|
4
|
+
type MediaRef = `media://${string}`;
|
|
5
|
+
type MediaKind = 'image' | 'video' | 'audio' | 'file';
|
|
6
|
+
type MediaJson = string | number | boolean | null | MediaJson[] | {
|
|
7
|
+
[key: string]: MediaJson;
|
|
8
|
+
};
|
|
9
|
+
type MediaData = {
|
|
10
|
+
[key: string]: MediaJson;
|
|
11
|
+
};
|
|
12
|
+
type MediaImportInput = {
|
|
13
|
+
uri: string;
|
|
14
|
+
kind: MediaKind;
|
|
15
|
+
name?: string;
|
|
16
|
+
mimeType?: string | null;
|
|
17
|
+
data?: MediaData;
|
|
18
|
+
};
|
|
19
|
+
type MediaQueryKey = readonly ['silo', 'media', MediaRef | string | null];
|
|
20
|
+
type MediaMutationOptions = {
|
|
21
|
+
queryClient?: QueryClient;
|
|
22
|
+
};
|
|
23
|
+
declare const media: {
|
|
24
|
+
queryOptions(ref: MediaRef | string | null | undefined): UseQueryOptions<string | null, Error, string | null, MediaQueryKey>;
|
|
25
|
+
mutationOptions(options?: MediaMutationOptions): UseMutationOptions<MediaRef, Error, MediaImportInput>;
|
|
26
|
+
};
|
|
27
|
+
declare function isMediaRef(value: unknown): value is MediaRef;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { MediaData, MediaImportInput, MediaJson, MediaKind, MediaMutationOptions, MediaQueryKey, MediaRef, isMediaRef, media };
|
package/dist/media.mjs
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { openDatabaseSync } from "expo-sqlite";
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
3
|
+
import { mutationOptions, queryOptions } from "@tanstack/react-query";
|
|
4
|
+
import { Directory, File, Paths } from "expo-file-system";
|
|
5
|
+
|
|
6
|
+
//#region src/media/index.ts
|
|
7
|
+
const media = {
|
|
8
|
+
queryOptions(ref) {
|
|
9
|
+
return queryOptions({
|
|
10
|
+
queryKey: mediaQueryKey(ref),
|
|
11
|
+
queryFn: () => resolveMediaUri(ref)
|
|
12
|
+
});
|
|
13
|
+
},
|
|
14
|
+
mutationOptions(options = {}) {
|
|
15
|
+
return mutationOptions({
|
|
16
|
+
mutationKey: [
|
|
17
|
+
"silo",
|
|
18
|
+
"media",
|
|
19
|
+
"import"
|
|
20
|
+
],
|
|
21
|
+
mutationFn: async (input) => {
|
|
22
|
+
const imported = await importMedia(input);
|
|
23
|
+
options.queryClient?.setQueryData(mediaQueryKey(imported.ref), imported.uri);
|
|
24
|
+
return imported.ref;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
function isMediaRef(value) {
|
|
30
|
+
return typeof value === "string" && value.startsWith("media://");
|
|
31
|
+
}
|
|
32
|
+
function mediaQueryKey(ref) {
|
|
33
|
+
return [
|
|
34
|
+
"silo",
|
|
35
|
+
"media",
|
|
36
|
+
ref ?? null
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
async function importMedia(input) {
|
|
40
|
+
assertMediaKind(input.kind);
|
|
41
|
+
const id = nanoid();
|
|
42
|
+
const ref = createMediaRef(input.kind, id);
|
|
43
|
+
const source = new File(input.uri);
|
|
44
|
+
const name = input.name ?? source.name ?? `${id}${source.extension}`;
|
|
45
|
+
const destinationDirectory = mediaDirectory(input.kind);
|
|
46
|
+
destinationDirectory.create({
|
|
47
|
+
idempotent: true,
|
|
48
|
+
intermediates: true
|
|
49
|
+
});
|
|
50
|
+
const destination = new File(destinationDirectory, `${id}${extensionFor(name)}`);
|
|
51
|
+
await source.copy(destination);
|
|
52
|
+
const info = destination.info();
|
|
53
|
+
const mimeType = input.mimeType ?? (destination.type || null);
|
|
54
|
+
const size = info.size ?? destination.size;
|
|
55
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
56
|
+
insertMediaFile({
|
|
57
|
+
ref,
|
|
58
|
+
uri: destination.uri,
|
|
59
|
+
kind: input.kind,
|
|
60
|
+
name,
|
|
61
|
+
mimeType,
|
|
62
|
+
size,
|
|
63
|
+
createdAt: timestamp,
|
|
64
|
+
data: input.data ?? {}
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
ref,
|
|
68
|
+
uri: destination.uri
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function resolveMediaUri(ref) {
|
|
72
|
+
if (!isMediaRef(ref)) return null;
|
|
73
|
+
const row = getDatabase().getFirstSync(`SELECT ref, uri FROM media_files WHERE ref = ?`, [ref]);
|
|
74
|
+
if (!row) return null;
|
|
75
|
+
return new File(row.uri).exists ? row.uri : null;
|
|
76
|
+
}
|
|
77
|
+
function createMediaRef(kind, id) {
|
|
78
|
+
return `media://silo/${kindDirectoryName(kind)}/${id}`;
|
|
79
|
+
}
|
|
80
|
+
function mediaDirectory(kind) {
|
|
81
|
+
return new Directory(Paths.document, "silo", "media", kindDirectoryName(kind));
|
|
82
|
+
}
|
|
83
|
+
function kindDirectoryName(kind) {
|
|
84
|
+
switch (kind) {
|
|
85
|
+
case "image": return "images";
|
|
86
|
+
case "video": return "videos";
|
|
87
|
+
case "audio": return "audio";
|
|
88
|
+
case "file": return "files";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function extensionFor(name) {
|
|
92
|
+
return name.match(/\.[A-Za-z0-9]+$/)?.[0] ?? "";
|
|
93
|
+
}
|
|
94
|
+
function assertMediaKind(kind) {
|
|
95
|
+
if (kind !== "image" && kind !== "video" && kind !== "audio" && kind !== "file") throw new Error("Media kind must be one of \"image\", \"video\", \"audio\", or \"file\".");
|
|
96
|
+
}
|
|
97
|
+
let database;
|
|
98
|
+
function getDatabase() {
|
|
99
|
+
if (!database) {
|
|
100
|
+
database = openDatabaseSync("silo.db");
|
|
101
|
+
database.execSync(`
|
|
102
|
+
PRAGMA journal_mode = WAL;
|
|
103
|
+
|
|
104
|
+
CREATE TABLE IF NOT EXISTS media_files (
|
|
105
|
+
ref TEXT PRIMARY KEY,
|
|
106
|
+
uri TEXT NOT NULL,
|
|
107
|
+
kind TEXT NOT NULL,
|
|
108
|
+
name TEXT NOT NULL,
|
|
109
|
+
mimeType TEXT,
|
|
110
|
+
size INTEGER NOT NULL,
|
|
111
|
+
createdAt TEXT NOT NULL,
|
|
112
|
+
data TEXT NOT NULL
|
|
113
|
+
);
|
|
114
|
+
`);
|
|
115
|
+
}
|
|
116
|
+
return database;
|
|
117
|
+
}
|
|
118
|
+
function insertMediaFile(file) {
|
|
119
|
+
getDatabase().runSync(`INSERT INTO media_files (ref, uri, kind, name, mimeType, size, createdAt, data)
|
|
120
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
121
|
+
file.ref,
|
|
122
|
+
file.uri,
|
|
123
|
+
file.kind,
|
|
124
|
+
file.name,
|
|
125
|
+
file.mimeType,
|
|
126
|
+
file.size,
|
|
127
|
+
file.createdAt,
|
|
128
|
+
JSON.stringify(file.data)
|
|
129
|
+
]);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
//#endregion
|
|
133
|
+
export { isMediaRef, media };
|
package/dist/source.cjs
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
2
2
|
let expo_sqlite = require("expo-sqlite");
|
|
3
|
+
let nanoid = require("nanoid");
|
|
3
4
|
|
|
4
5
|
//#region src/source/index.ts
|
|
5
6
|
const sourceNames = /* @__PURE__ */ new Set();
|
|
6
7
|
const sourceNamePattern = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
8
|
+
const reservedDocumentFields = new Set([
|
|
9
|
+
"id",
|
|
10
|
+
"data",
|
|
11
|
+
"set"
|
|
12
|
+
]);
|
|
13
|
+
const deletedDocs = /* @__PURE__ */ new WeakSet();
|
|
14
|
+
function createID() {
|
|
15
|
+
return (0, nanoid.nanoid)();
|
|
16
|
+
}
|
|
7
17
|
function source(name, defaults) {
|
|
8
18
|
assertSourceName(name);
|
|
9
19
|
assertFlatDefaults(name, defaults);
|
|
@@ -38,7 +48,7 @@ function source(name, defaults) {
|
|
|
38
48
|
await updateRows(name, transaction.mutations);
|
|
39
49
|
},
|
|
40
50
|
onDelete: async ({ transaction }) => {
|
|
41
|
-
await deleteRows(name, transaction.mutations
|
|
51
|
+
await deleteRows(name, transaction.mutations);
|
|
42
52
|
}
|
|
43
53
|
};
|
|
44
54
|
}
|
|
@@ -48,7 +58,10 @@ function assertSourceName(name) {
|
|
|
48
58
|
if (!sourceNamePattern.test(name)) throw new Error(`Source names must start with a letter and contain only letters, numbers, underscores, or hyphens. Received "${name}".`);
|
|
49
59
|
}
|
|
50
60
|
function assertFlatDefaults(sourceName, defaults) {
|
|
51
|
-
for (const [field, defaultValue] of Object.entries(defaults))
|
|
61
|
+
for (const [field, defaultValue] of Object.entries(defaults)) {
|
|
62
|
+
if (reservedDocumentFields.has(field)) throw new Error(`Source "${sourceName}" field "${field}" is reserved by Silo documents.`);
|
|
63
|
+
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.`);
|
|
64
|
+
}
|
|
52
65
|
}
|
|
53
66
|
function createSourceSchema(sourceName, defaults) {
|
|
54
67
|
return { "~standard": {
|
|
@@ -56,21 +69,64 @@ function createSourceSchema(sourceName, defaults) {
|
|
|
56
69
|
vendor: "silo",
|
|
57
70
|
validate(value) {
|
|
58
71
|
if (!isRecord(value)) return failure("Expected a flat source row object.");
|
|
59
|
-
if (typeof value.id !== "string") return failure("Expected source
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
|
|
72
|
+
if (typeof value.id !== "string") return failure("Expected source document field \"id\" to be a string.", ["id"]);
|
|
73
|
+
const inputData = isRecord(value.data) ? value.data : extractDocumentData(value);
|
|
74
|
+
const data = {};
|
|
75
|
+
for (const [field, fieldValue] of Object.entries(inputData)) {
|
|
76
|
+
if (reservedDocumentFields.has(field)) return failure(`Source "${sourceName}" field "${field}" is reserved by Silo documents.`, ["data", field]);
|
|
77
|
+
if (!isFieldValue(fieldValue)) return failure(`Source "${sourceName}" field "${field}" must be a string, number, boolean, or null.`, ["data", field]);
|
|
78
|
+
data[field] = fieldValue;
|
|
65
79
|
}
|
|
66
80
|
for (const [field, defaultValue] of Object.entries(defaults)) {
|
|
67
|
-
if (field in
|
|
68
|
-
|
|
81
|
+
if (field in data) continue;
|
|
82
|
+
data[field] = resolveDefault(defaultValue);
|
|
69
83
|
}
|
|
70
|
-
return { value:
|
|
84
|
+
return { value: createDoc(value.id, data) };
|
|
71
85
|
}
|
|
72
86
|
} };
|
|
73
87
|
}
|
|
88
|
+
function createDoc(id, data) {
|
|
89
|
+
const doc = {};
|
|
90
|
+
Object.defineProperty(doc, "id", {
|
|
91
|
+
value: id,
|
|
92
|
+
enumerable: true,
|
|
93
|
+
writable: false,
|
|
94
|
+
configurable: false
|
|
95
|
+
});
|
|
96
|
+
for (const [field, value] of Object.entries(data)) Object.defineProperty(doc, field, {
|
|
97
|
+
value,
|
|
98
|
+
enumerable: true,
|
|
99
|
+
writable: true,
|
|
100
|
+
configurable: true
|
|
101
|
+
});
|
|
102
|
+
Object.defineProperty(doc, "data", {
|
|
103
|
+
value() {
|
|
104
|
+
return deletedDocs.has(this) ? null : extractDocumentData(this);
|
|
105
|
+
},
|
|
106
|
+
enumerable: true,
|
|
107
|
+
writable: false,
|
|
108
|
+
configurable: false
|
|
109
|
+
});
|
|
110
|
+
Object.defineProperty(doc, "set", {
|
|
111
|
+
get() {
|
|
112
|
+
return function setDocumentData(patch) {
|
|
113
|
+
Object.assign(this, patch);
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
enumerable: true,
|
|
117
|
+
configurable: false
|
|
118
|
+
});
|
|
119
|
+
return doc;
|
|
120
|
+
}
|
|
121
|
+
function extractDocumentData(value) {
|
|
122
|
+
if (!isRecord(value)) return {};
|
|
123
|
+
const data = {};
|
|
124
|
+
for (const [field, fieldValue] of Object.entries(value)) {
|
|
125
|
+
if (reservedDocumentFields.has(field) || field.startsWith("$") || typeof fieldValue === "function") continue;
|
|
126
|
+
if (isFieldValue(fieldValue)) data[field] = fieldValue;
|
|
127
|
+
}
|
|
128
|
+
return data;
|
|
129
|
+
}
|
|
74
130
|
function resolveDefault(value) {
|
|
75
131
|
const resolved = typeof value === "function" ? value() : value;
|
|
76
132
|
if (!isFieldValue(resolved)) throw new Error("Source defaults must resolve to a string, number, boolean, or null.");
|
|
@@ -109,21 +165,18 @@ function getDatabase() {
|
|
|
109
165
|
return database;
|
|
110
166
|
}
|
|
111
167
|
function loadRows(sourceName) {
|
|
112
|
-
return getDatabase().getAllSync(`SELECT id, data FROM sources WHERE source = ?`, [sourceName]).map((row) => (
|
|
113
|
-
id: row.id,
|
|
114
|
-
...JSON.parse(row.data)
|
|
115
|
-
}));
|
|
168
|
+
return getDatabase().getAllSync(`SELECT id, data FROM sources WHERE source = ?`, [sourceName]).map((row) => createDoc(row.id, JSON.parse(row.data)));
|
|
116
169
|
}
|
|
117
170
|
function insertRows(sourceName, rows) {
|
|
118
171
|
const db = getDatabase();
|
|
119
172
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
120
173
|
db.withTransactionSync(() => {
|
|
121
174
|
for (const row of rows) {
|
|
122
|
-
const
|
|
175
|
+
const data = extractDocumentData(row);
|
|
123
176
|
db.runSync(`INSERT INTO sources (source, id, data, createdAt, updatedAt, version)
|
|
124
177
|
VALUES (?, ?, ?, ?, ?, 1)`, [
|
|
125
178
|
sourceName,
|
|
126
|
-
id,
|
|
179
|
+
row.id,
|
|
127
180
|
JSON.stringify(data),
|
|
128
181
|
timestamp,
|
|
129
182
|
timestamp
|
|
@@ -137,7 +190,8 @@ function updateRows(sourceName, mutations) {
|
|
|
137
190
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
138
191
|
db.withTransactionSync(() => {
|
|
139
192
|
for (const mutation of mutations) {
|
|
140
|
-
const
|
|
193
|
+
const data = extractDocumentData(mutation.modified);
|
|
194
|
+
mutation.modified = createDoc(String(mutation.key), data);
|
|
141
195
|
db.runSync(`UPDATE sources
|
|
142
196
|
SET data = ?, updatedAt = ?, version = version + 1
|
|
143
197
|
WHERE source = ? AND id = ?`, [
|
|
@@ -150,13 +204,18 @@ function updateRows(sourceName, mutations) {
|
|
|
150
204
|
});
|
|
151
205
|
return Promise.resolve();
|
|
152
206
|
}
|
|
153
|
-
function deleteRows(sourceName,
|
|
207
|
+
function deleteRows(sourceName, mutations) {
|
|
154
208
|
const db = getDatabase();
|
|
155
209
|
db.withTransactionSync(() => {
|
|
156
|
-
for (const
|
|
210
|
+
for (const mutation of mutations) {
|
|
211
|
+
if (mutation.original) deletedDocs.add(mutation.original);
|
|
212
|
+
deletedDocs.add(mutation.modified);
|
|
213
|
+
db.runSync(`DELETE FROM sources WHERE source = ? AND id = ?`, [sourceName, String(mutation.key)]);
|
|
214
|
+
}
|
|
157
215
|
});
|
|
158
216
|
return Promise.resolve();
|
|
159
217
|
}
|
|
160
218
|
|
|
161
219
|
//#endregion
|
|
220
|
+
exports.createID = createID;
|
|
162
221
|
exports.source = source;
|
package/dist/source.d.cts
CHANGED
|
@@ -9,21 +9,28 @@ type InvalidDocumentKeys<T extends object> = { [K in keyof T]-?: Exclude<T[K], u
|
|
|
9
9
|
type DocumentData<T extends object> = InvalidDocumentKeys<T> extends never ? T : never;
|
|
10
10
|
type DefaultValue<T> = Exclude<T, undefined> | (() => Exclude<T, undefined>);
|
|
11
11
|
type SourceDefaults<T extends object> = { [K in RequiredKeys<DocumentData<T>>]: DefaultValue<DocumentData<T>[K]> } & { [K in OptionalKeys<DocumentData<T>>]?: DefaultValue<DocumentData<T>[K]> };
|
|
12
|
-
type SourceRow<T extends object> = {
|
|
13
|
-
id: string;
|
|
14
|
-
} & DocumentData<T>;
|
|
15
12
|
type SourceInput<T extends object> = {
|
|
16
13
|
id: string;
|
|
17
|
-
|
|
14
|
+
data: Partial<DocumentData<T>>;
|
|
15
|
+
};
|
|
16
|
+
type Doc<T extends object> = {
|
|
17
|
+
readonly id: string;
|
|
18
|
+
data(): DocumentData<T> | null;
|
|
19
|
+
readonly set: (patch: Partial<DocumentData<T>>) => void;
|
|
20
|
+
} & DocumentData<T>;
|
|
21
|
+
type SourceRow<T extends object> = Doc<T>;
|
|
18
22
|
type SourceCollectionOptions<T extends object> = {
|
|
19
23
|
startSync?: boolean;
|
|
20
24
|
};
|
|
21
25
|
type Source<T extends object> = {
|
|
22
26
|
readonly name: string;
|
|
23
27
|
readonly defaults: SourceDefaults<T>;
|
|
24
|
-
readonly schema: StandardSchemaV1<
|
|
25
|
-
collectionOptions(options?: SourceCollectionOptions<T>): CollectionConfig<SourceRow<T>, string, StandardSchemaV1<
|
|
28
|
+
readonly schema: StandardSchemaV1<any, SourceRow<T>>;
|
|
29
|
+
collectionOptions(options?: SourceCollectionOptions<T>): CollectionConfig<SourceRow<T>, string, StandardSchemaV1<any, SourceRow<T>>> & {
|
|
30
|
+
schema: StandardSchemaV1<any, SourceRow<T>>;
|
|
31
|
+
};
|
|
26
32
|
};
|
|
33
|
+
declare function createID(): string;
|
|
27
34
|
declare function source<T extends object>(name: string, defaults: SourceDefaults<T>): Source<T>;
|
|
28
35
|
//#endregion
|
|
29
|
-
export { DefaultValue, DocumentData, FieldValue, Source, SourceCollectionOptions, SourceDefaults, SourceInput, SourceRow, source };
|
|
36
|
+
export { DefaultValue, Doc, DocumentData, FieldValue, Source, SourceCollectionOptions, SourceDefaults, SourceInput, SourceRow, createID, source };
|
package/dist/source.d.mts
CHANGED
|
@@ -9,21 +9,28 @@ type InvalidDocumentKeys<T extends object> = { [K in keyof T]-?: Exclude<T[K], u
|
|
|
9
9
|
type DocumentData<T extends object> = InvalidDocumentKeys<T> extends never ? T : never;
|
|
10
10
|
type DefaultValue<T> = Exclude<T, undefined> | (() => Exclude<T, undefined>);
|
|
11
11
|
type SourceDefaults<T extends object> = { [K in RequiredKeys<DocumentData<T>>]: DefaultValue<DocumentData<T>[K]> } & { [K in OptionalKeys<DocumentData<T>>]?: DefaultValue<DocumentData<T>[K]> };
|
|
12
|
-
type SourceRow<T extends object> = {
|
|
13
|
-
id: string;
|
|
14
|
-
} & DocumentData<T>;
|
|
15
12
|
type SourceInput<T extends object> = {
|
|
16
13
|
id: string;
|
|
17
|
-
|
|
14
|
+
data: Partial<DocumentData<T>>;
|
|
15
|
+
};
|
|
16
|
+
type Doc<T extends object> = {
|
|
17
|
+
readonly id: string;
|
|
18
|
+
data(): DocumentData<T> | null;
|
|
19
|
+
readonly set: (patch: Partial<DocumentData<T>>) => void;
|
|
20
|
+
} & DocumentData<T>;
|
|
21
|
+
type SourceRow<T extends object> = Doc<T>;
|
|
18
22
|
type SourceCollectionOptions<T extends object> = {
|
|
19
23
|
startSync?: boolean;
|
|
20
24
|
};
|
|
21
25
|
type Source<T extends object> = {
|
|
22
26
|
readonly name: string;
|
|
23
27
|
readonly defaults: SourceDefaults<T>;
|
|
24
|
-
readonly schema: StandardSchemaV1<
|
|
25
|
-
collectionOptions(options?: SourceCollectionOptions<T>): CollectionConfig<SourceRow<T>, string, StandardSchemaV1<
|
|
28
|
+
readonly schema: StandardSchemaV1<any, SourceRow<T>>;
|
|
29
|
+
collectionOptions(options?: SourceCollectionOptions<T>): CollectionConfig<SourceRow<T>, string, StandardSchemaV1<any, SourceRow<T>>> & {
|
|
30
|
+
schema: StandardSchemaV1<any, SourceRow<T>>;
|
|
31
|
+
};
|
|
26
32
|
};
|
|
33
|
+
declare function createID(): string;
|
|
27
34
|
declare function source<T extends object>(name: string, defaults: SourceDefaults<T>): Source<T>;
|
|
28
35
|
//#endregion
|
|
29
|
-
export { DefaultValue, DocumentData, FieldValue, Source, SourceCollectionOptions, SourceDefaults, SourceInput, SourceRow, source };
|
|
36
|
+
export { DefaultValue, Doc, DocumentData, FieldValue, Source, SourceCollectionOptions, SourceDefaults, SourceInput, SourceRow, createID, source };
|
package/dist/source.mjs
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { openDatabaseSync } from "expo-sqlite";
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
2
3
|
|
|
3
4
|
//#region src/source/index.ts
|
|
4
5
|
const sourceNames = /* @__PURE__ */ new Set();
|
|
5
6
|
const sourceNamePattern = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
7
|
+
const reservedDocumentFields = new Set([
|
|
8
|
+
"id",
|
|
9
|
+
"data",
|
|
10
|
+
"set"
|
|
11
|
+
]);
|
|
12
|
+
const deletedDocs = /* @__PURE__ */ new WeakSet();
|
|
13
|
+
function createID() {
|
|
14
|
+
return nanoid();
|
|
15
|
+
}
|
|
6
16
|
function source(name, defaults) {
|
|
7
17
|
assertSourceName(name);
|
|
8
18
|
assertFlatDefaults(name, defaults);
|
|
@@ -37,7 +47,7 @@ function source(name, defaults) {
|
|
|
37
47
|
await updateRows(name, transaction.mutations);
|
|
38
48
|
},
|
|
39
49
|
onDelete: async ({ transaction }) => {
|
|
40
|
-
await deleteRows(name, transaction.mutations
|
|
50
|
+
await deleteRows(name, transaction.mutations);
|
|
41
51
|
}
|
|
42
52
|
};
|
|
43
53
|
}
|
|
@@ -47,7 +57,10 @@ function assertSourceName(name) {
|
|
|
47
57
|
if (!sourceNamePattern.test(name)) throw new Error(`Source names must start with a letter and contain only letters, numbers, underscores, or hyphens. Received "${name}".`);
|
|
48
58
|
}
|
|
49
59
|
function assertFlatDefaults(sourceName, defaults) {
|
|
50
|
-
for (const [field, defaultValue] of Object.entries(defaults))
|
|
60
|
+
for (const [field, defaultValue] of Object.entries(defaults)) {
|
|
61
|
+
if (reservedDocumentFields.has(field)) throw new Error(`Source "${sourceName}" field "${field}" is reserved by Silo documents.`);
|
|
62
|
+
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.`);
|
|
63
|
+
}
|
|
51
64
|
}
|
|
52
65
|
function createSourceSchema(sourceName, defaults) {
|
|
53
66
|
return { "~standard": {
|
|
@@ -55,21 +68,64 @@ function createSourceSchema(sourceName, defaults) {
|
|
|
55
68
|
vendor: "silo",
|
|
56
69
|
validate(value) {
|
|
57
70
|
if (!isRecord(value)) return failure("Expected a flat source row object.");
|
|
58
|
-
if (typeof value.id !== "string") return failure("Expected source
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
|
|
71
|
+
if (typeof value.id !== "string") return failure("Expected source document field \"id\" to be a string.", ["id"]);
|
|
72
|
+
const inputData = isRecord(value.data) ? value.data : extractDocumentData(value);
|
|
73
|
+
const data = {};
|
|
74
|
+
for (const [field, fieldValue] of Object.entries(inputData)) {
|
|
75
|
+
if (reservedDocumentFields.has(field)) return failure(`Source "${sourceName}" field "${field}" is reserved by Silo documents.`, ["data", field]);
|
|
76
|
+
if (!isFieldValue(fieldValue)) return failure(`Source "${sourceName}" field "${field}" must be a string, number, boolean, or null.`, ["data", field]);
|
|
77
|
+
data[field] = fieldValue;
|
|
64
78
|
}
|
|
65
79
|
for (const [field, defaultValue] of Object.entries(defaults)) {
|
|
66
|
-
if (field in
|
|
67
|
-
|
|
80
|
+
if (field in data) continue;
|
|
81
|
+
data[field] = resolveDefault(defaultValue);
|
|
68
82
|
}
|
|
69
|
-
return { value:
|
|
83
|
+
return { value: createDoc(value.id, data) };
|
|
70
84
|
}
|
|
71
85
|
} };
|
|
72
86
|
}
|
|
87
|
+
function createDoc(id, data) {
|
|
88
|
+
const doc = {};
|
|
89
|
+
Object.defineProperty(doc, "id", {
|
|
90
|
+
value: id,
|
|
91
|
+
enumerable: true,
|
|
92
|
+
writable: false,
|
|
93
|
+
configurable: false
|
|
94
|
+
});
|
|
95
|
+
for (const [field, value] of Object.entries(data)) Object.defineProperty(doc, field, {
|
|
96
|
+
value,
|
|
97
|
+
enumerable: true,
|
|
98
|
+
writable: true,
|
|
99
|
+
configurable: true
|
|
100
|
+
});
|
|
101
|
+
Object.defineProperty(doc, "data", {
|
|
102
|
+
value() {
|
|
103
|
+
return deletedDocs.has(this) ? null : extractDocumentData(this);
|
|
104
|
+
},
|
|
105
|
+
enumerable: true,
|
|
106
|
+
writable: false,
|
|
107
|
+
configurable: false
|
|
108
|
+
});
|
|
109
|
+
Object.defineProperty(doc, "set", {
|
|
110
|
+
get() {
|
|
111
|
+
return function setDocumentData(patch) {
|
|
112
|
+
Object.assign(this, patch);
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
enumerable: true,
|
|
116
|
+
configurable: false
|
|
117
|
+
});
|
|
118
|
+
return doc;
|
|
119
|
+
}
|
|
120
|
+
function extractDocumentData(value) {
|
|
121
|
+
if (!isRecord(value)) return {};
|
|
122
|
+
const data = {};
|
|
123
|
+
for (const [field, fieldValue] of Object.entries(value)) {
|
|
124
|
+
if (reservedDocumentFields.has(field) || field.startsWith("$") || typeof fieldValue === "function") continue;
|
|
125
|
+
if (isFieldValue(fieldValue)) data[field] = fieldValue;
|
|
126
|
+
}
|
|
127
|
+
return data;
|
|
128
|
+
}
|
|
73
129
|
function resolveDefault(value) {
|
|
74
130
|
const resolved = typeof value === "function" ? value() : value;
|
|
75
131
|
if (!isFieldValue(resolved)) throw new Error("Source defaults must resolve to a string, number, boolean, or null.");
|
|
@@ -108,21 +164,18 @@ function getDatabase() {
|
|
|
108
164
|
return database;
|
|
109
165
|
}
|
|
110
166
|
function loadRows(sourceName) {
|
|
111
|
-
return getDatabase().getAllSync(`SELECT id, data FROM sources WHERE source = ?`, [sourceName]).map((row) => (
|
|
112
|
-
id: row.id,
|
|
113
|
-
...JSON.parse(row.data)
|
|
114
|
-
}));
|
|
167
|
+
return getDatabase().getAllSync(`SELECT id, data FROM sources WHERE source = ?`, [sourceName]).map((row) => createDoc(row.id, JSON.parse(row.data)));
|
|
115
168
|
}
|
|
116
169
|
function insertRows(sourceName, rows) {
|
|
117
170
|
const db = getDatabase();
|
|
118
171
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
119
172
|
db.withTransactionSync(() => {
|
|
120
173
|
for (const row of rows) {
|
|
121
|
-
const
|
|
174
|
+
const data = extractDocumentData(row);
|
|
122
175
|
db.runSync(`INSERT INTO sources (source, id, data, createdAt, updatedAt, version)
|
|
123
176
|
VALUES (?, ?, ?, ?, ?, 1)`, [
|
|
124
177
|
sourceName,
|
|
125
|
-
id,
|
|
178
|
+
row.id,
|
|
126
179
|
JSON.stringify(data),
|
|
127
180
|
timestamp,
|
|
128
181
|
timestamp
|
|
@@ -136,7 +189,8 @@ function updateRows(sourceName, mutations) {
|
|
|
136
189
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
137
190
|
db.withTransactionSync(() => {
|
|
138
191
|
for (const mutation of mutations) {
|
|
139
|
-
const
|
|
192
|
+
const data = extractDocumentData(mutation.modified);
|
|
193
|
+
mutation.modified = createDoc(String(mutation.key), data);
|
|
140
194
|
db.runSync(`UPDATE sources
|
|
141
195
|
SET data = ?, updatedAt = ?, version = version + 1
|
|
142
196
|
WHERE source = ? AND id = ?`, [
|
|
@@ -149,13 +203,17 @@ function updateRows(sourceName, mutations) {
|
|
|
149
203
|
});
|
|
150
204
|
return Promise.resolve();
|
|
151
205
|
}
|
|
152
|
-
function deleteRows(sourceName,
|
|
206
|
+
function deleteRows(sourceName, mutations) {
|
|
153
207
|
const db = getDatabase();
|
|
154
208
|
db.withTransactionSync(() => {
|
|
155
|
-
for (const
|
|
209
|
+
for (const mutation of mutations) {
|
|
210
|
+
if (mutation.original) deletedDocs.add(mutation.original);
|
|
211
|
+
deletedDocs.add(mutation.modified);
|
|
212
|
+
db.runSync(`DELETE FROM sources WHERE source = ? AND id = ?`, [sourceName, String(mutation.key)]);
|
|
213
|
+
}
|
|
156
214
|
});
|
|
157
215
|
return Promise.resolve();
|
|
158
216
|
}
|
|
159
217
|
|
|
160
218
|
//#endregion
|
|
161
|
-
export { source };
|
|
219
|
+
export { createID, source };
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "silosdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "tsdown",
|
|
7
7
|
"watch": "tsdown --watch",
|
|
8
8
|
"test": "vitest run",
|
|
9
|
-
"
|
|
9
|
+
"pub": "tsdown && npm publish"
|
|
10
10
|
},
|
|
11
11
|
"packageManager": "yarn@1.22.22",
|
|
12
12
|
"files": [
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"@tanstack/react-query": "^5.101.0",
|
|
18
18
|
"@types/node": "^25.3.0",
|
|
19
19
|
"@types/react": "^19",
|
|
20
|
+
"expo-file-system": "^56.0.8",
|
|
20
21
|
"expo-sqlite": "*",
|
|
21
22
|
"prettier": "^3.7.4",
|
|
22
23
|
"react-native": "0.85.0",
|
|
@@ -26,11 +27,12 @@
|
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {
|
|
28
29
|
"@standard-schema/spec": "^1.0.0",
|
|
29
|
-
"@tanstack/react-db": "^0.1.86",
|
|
30
|
-
"@tanstack/react-query": "^5.101.0",
|
|
31
30
|
"nanoid": "5.1.6"
|
|
32
31
|
},
|
|
33
32
|
"peerDependencies": {
|
|
33
|
+
"@tanstack/react-db": "^0.1.86",
|
|
34
|
+
"@tanstack/react-query": "^5.101.0",
|
|
35
|
+
"expo-file-system": "^56.0.8",
|
|
34
36
|
"expo-sqlite": "*",
|
|
35
37
|
"react": "^19",
|
|
36
38
|
"react-native": "0.85.x"
|
|
@@ -56,6 +58,11 @@
|
|
|
56
58
|
"import": "./dist/settings.mjs",
|
|
57
59
|
"require": "./dist/settings.cjs"
|
|
58
60
|
},
|
|
61
|
+
"./media": {
|
|
62
|
+
"types": "./dist/media.d.mts",
|
|
63
|
+
"import": "./dist/media.mjs",
|
|
64
|
+
"require": "./dist/media.cjs"
|
|
65
|
+
},
|
|
59
66
|
"./package.json": "./package.json"
|
|
60
67
|
}
|
|
61
68
|
}
|