silosdk 0.0.9 → 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 +16 -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 -163
- package/dist/media.d.cts +7 -27
- package/dist/media.d.mts +7 -27
- package/dist/media.mjs +4 -161
- 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,78 @@
|
|
|
1
|
+
import { createIndexedDbOpener, requestResult, transactionDone } from "../internal/indexed-db.mjs";
|
|
2
|
+
import { createMediaRef, extensionForMimeType } from "./shared.mjs";
|
|
3
|
+
import { randomUUID } from "expo-crypto";
|
|
4
|
+
|
|
5
|
+
//#region src/media/storage.web.ts
|
|
6
|
+
const objectUrls = /* @__PURE__ */ new Map();
|
|
7
|
+
const mediaStorage = {
|
|
8
|
+
importMedia,
|
|
9
|
+
resolveMediaUri,
|
|
10
|
+
deleteMedia
|
|
11
|
+
};
|
|
12
|
+
const openSiloMediaIndexedDb = createIndexedDbOpener("silo-media", 1, (db) => {
|
|
13
|
+
if (!db.objectStoreNames.contains("media_files")) db.createObjectStore("media_files", { keyPath: "ref" });
|
|
14
|
+
if (!db.objectStoreNames.contains("media_blobs")) db.createObjectStore("media_blobs", { keyPath: "ref" });
|
|
15
|
+
});
|
|
16
|
+
async function importMedia(input) {
|
|
17
|
+
const id = randomUUID();
|
|
18
|
+
const ref = createMediaRef(input.kind, id);
|
|
19
|
+
const blob = await (await fetch(input.uri)).blob();
|
|
20
|
+
const name = input.name ?? `${id}${extensionForMimeType(input.mimeType ?? blob.type)}`;
|
|
21
|
+
const mimeType = input.mimeType ?? (blob.type || null);
|
|
22
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
23
|
+
const transaction = (await openSiloMediaIndexedDb()).transaction(["media_files", "media_blobs"], "readwrite");
|
|
24
|
+
transaction.objectStore("media_files").put({
|
|
25
|
+
ref,
|
|
26
|
+
uri: ref,
|
|
27
|
+
kind: input.kind,
|
|
28
|
+
name,
|
|
29
|
+
mimeType,
|
|
30
|
+
size: blob.size,
|
|
31
|
+
createdAt: timestamp,
|
|
32
|
+
data: JSON.stringify(input.data ?? {})
|
|
33
|
+
});
|
|
34
|
+
transaction.objectStore("media_blobs").put({
|
|
35
|
+
ref,
|
|
36
|
+
blob
|
|
37
|
+
});
|
|
38
|
+
await transactionDone(transaction);
|
|
39
|
+
const uri = URL.createObjectURL(blob);
|
|
40
|
+
objectUrls.set(ref, uri);
|
|
41
|
+
return {
|
|
42
|
+
ref,
|
|
43
|
+
uri
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async function resolveMediaUri(ref) {
|
|
47
|
+
const existingUrl = objectUrls.get(ref);
|
|
48
|
+
if (existingUrl) return existingUrl;
|
|
49
|
+
const transaction = (await openSiloMediaIndexedDb()).transaction("media_blobs", "readonly");
|
|
50
|
+
const done = transactionDone(transaction);
|
|
51
|
+
const row = await requestResult(transaction.objectStore("media_blobs").get(ref));
|
|
52
|
+
await done;
|
|
53
|
+
if (!row) return null;
|
|
54
|
+
const uri = URL.createObjectURL(row.blob);
|
|
55
|
+
objectUrls.set(ref, uri);
|
|
56
|
+
return uri;
|
|
57
|
+
}
|
|
58
|
+
async function deleteMedia(ref) {
|
|
59
|
+
const db = await openSiloMediaIndexedDb();
|
|
60
|
+
const readTransaction = db.transaction("media_files", "readonly");
|
|
61
|
+
const readDone = transactionDone(readTransaction);
|
|
62
|
+
const existing = await requestResult(readTransaction.objectStore("media_files").get(ref));
|
|
63
|
+
await readDone;
|
|
64
|
+
if (!existing) return false;
|
|
65
|
+
const transaction = db.transaction(["media_files", "media_blobs"], "readwrite");
|
|
66
|
+
transaction.objectStore("media_files").delete(ref);
|
|
67
|
+
transaction.objectStore("media_blobs").delete(ref);
|
|
68
|
+
await transactionDone(transaction);
|
|
69
|
+
const objectUrl = objectUrls.get(ref);
|
|
70
|
+
if (objectUrl) {
|
|
71
|
+
URL.revokeObjectURL(objectUrl);
|
|
72
|
+
objectUrls.delete(ref);
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
//#endregion
|
|
78
|
+
export { mediaStorage };
|
package/dist/media.cjs
CHANGED
|
@@ -1,167 +1,9 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
let expo_sqlite = require("expo-sqlite");
|
|
4
|
-
let __tanstack_react_query = require("@tanstack/react-query");
|
|
5
|
-
let expo_file_system = require("expo-file-system");
|
|
1
|
+
const require_shared = require('./media/shared.cjs');
|
|
2
|
+
const require_storage_native = require('./media/storage.native.cjs');
|
|
6
3
|
|
|
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
|
-
delete(ref) {
|
|
30
|
-
return deleteMedia(ref);
|
|
31
|
-
},
|
|
32
|
-
deleteMutationOptions(options = {}) {
|
|
33
|
-
return (0, __tanstack_react_query.mutationOptions)({
|
|
34
|
-
mutationKey: [
|
|
35
|
-
"silo",
|
|
36
|
-
"media",
|
|
37
|
-
"delete"
|
|
38
|
-
],
|
|
39
|
-
mutationFn: async (ref) => {
|
|
40
|
-
const deleted = await deleteMedia(ref);
|
|
41
|
-
if (isMediaRef(ref)) options.queryClient?.setQueryData(mediaQueryKey(ref), null);
|
|
42
|
-
return deleted;
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
function isMediaRef(value) {
|
|
48
|
-
return typeof value === "string" && value.startsWith("media://");
|
|
49
|
-
}
|
|
50
|
-
function mediaQueryKey(ref) {
|
|
51
|
-
return [
|
|
52
|
-
"silo",
|
|
53
|
-
"media",
|
|
54
|
-
ref ?? null
|
|
55
|
-
];
|
|
56
|
-
}
|
|
57
|
-
async function importMedia(input) {
|
|
58
|
-
assertMediaKind(input.kind);
|
|
59
|
-
const id = (0, expo_crypto.randomUUID)();
|
|
60
|
-
const ref = createMediaRef(input.kind, id);
|
|
61
|
-
const source = new expo_file_system.File(input.uri);
|
|
62
|
-
const name = input.name ?? source.name ?? `${id}${source.extension}`;
|
|
63
|
-
const destinationDirectory = mediaDirectory(input.kind);
|
|
64
|
-
destinationDirectory.create({
|
|
65
|
-
idempotent: true,
|
|
66
|
-
intermediates: true
|
|
67
|
-
});
|
|
68
|
-
const destination = new expo_file_system.File(destinationDirectory, `${id}${extensionFor(name)}`);
|
|
69
|
-
await source.copy(destination);
|
|
70
|
-
const info = destination.info();
|
|
71
|
-
const mimeType = input.mimeType ?? (destination.type || null);
|
|
72
|
-
const size = info.size ?? destination.size;
|
|
73
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
74
|
-
insertMediaFile({
|
|
75
|
-
ref,
|
|
76
|
-
uri: destination.uri,
|
|
77
|
-
kind: input.kind,
|
|
78
|
-
name,
|
|
79
|
-
mimeType,
|
|
80
|
-
size,
|
|
81
|
-
createdAt: timestamp,
|
|
82
|
-
data: input.data ?? {}
|
|
83
|
-
});
|
|
84
|
-
return {
|
|
85
|
-
ref,
|
|
86
|
-
uri: destination.uri
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
function resolveMediaUri(ref) {
|
|
90
|
-
if (!isMediaRef(ref)) return null;
|
|
91
|
-
const row = getMediaFile(ref);
|
|
92
|
-
if (!row) return null;
|
|
93
|
-
return new expo_file_system.File(row.uri).exists ? row.uri : null;
|
|
94
|
-
}
|
|
95
|
-
async function deleteMedia(ref) {
|
|
96
|
-
if (!isMediaRef(ref)) return false;
|
|
97
|
-
const row = getMediaFile(ref);
|
|
98
|
-
if (!row) return false;
|
|
99
|
-
const file = new expo_file_system.File(row.uri);
|
|
100
|
-
if (file.exists) await file.delete();
|
|
101
|
-
deleteMediaFileRow(ref);
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
function getMediaFile(ref) {
|
|
105
|
-
return getDatabase().getFirstSync(`SELECT ref, uri FROM media_files WHERE ref = ?`, [ref]);
|
|
106
|
-
}
|
|
107
|
-
function createMediaRef(kind, id) {
|
|
108
|
-
return `media://silo/${kindDirectoryName(kind)}/${id}`;
|
|
109
|
-
}
|
|
110
|
-
function mediaDirectory(kind) {
|
|
111
|
-
return new expo_file_system.Directory(expo_file_system.Paths.document, "silo", "media", kindDirectoryName(kind));
|
|
112
|
-
}
|
|
113
|
-
function kindDirectoryName(kind) {
|
|
114
|
-
switch (kind) {
|
|
115
|
-
case "image": return "images";
|
|
116
|
-
case "video": return "videos";
|
|
117
|
-
case "audio": return "audio";
|
|
118
|
-
case "file": return "files";
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
function extensionFor(name) {
|
|
122
|
-
return name.match(/\.[A-Za-z0-9]+$/)?.[0] ?? "";
|
|
123
|
-
}
|
|
124
|
-
function assertMediaKind(kind) {
|
|
125
|
-
if (kind !== "image" && kind !== "video" && kind !== "audio" && kind !== "file") throw new Error("Media kind must be one of \"image\", \"video\", \"audio\", or \"file\".");
|
|
126
|
-
}
|
|
127
|
-
let database;
|
|
128
|
-
function getDatabase() {
|
|
129
|
-
if (!database) {
|
|
130
|
-
database = (0, expo_sqlite.openDatabaseSync)("silo.db");
|
|
131
|
-
database.execSync(`
|
|
132
|
-
PRAGMA journal_mode = WAL;
|
|
133
|
-
|
|
134
|
-
CREATE TABLE IF NOT EXISTS media_files (
|
|
135
|
-
ref TEXT PRIMARY KEY,
|
|
136
|
-
uri TEXT NOT NULL,
|
|
137
|
-
kind TEXT NOT NULL,
|
|
138
|
-
name TEXT NOT NULL,
|
|
139
|
-
mimeType TEXT,
|
|
140
|
-
size INTEGER NOT NULL,
|
|
141
|
-
createdAt TEXT NOT NULL,
|
|
142
|
-
data TEXT NOT NULL
|
|
143
|
-
);
|
|
144
|
-
`);
|
|
145
|
-
}
|
|
146
|
-
return database;
|
|
147
|
-
}
|
|
148
|
-
function insertMediaFile(file) {
|
|
149
|
-
getDatabase().runSync(`INSERT INTO media_files (ref, uri, kind, name, mimeType, size, createdAt, data)
|
|
150
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
151
|
-
file.ref,
|
|
152
|
-
file.uri,
|
|
153
|
-
file.kind,
|
|
154
|
-
file.name,
|
|
155
|
-
file.mimeType,
|
|
156
|
-
file.size,
|
|
157
|
-
file.createdAt,
|
|
158
|
-
JSON.stringify(file.data)
|
|
159
|
-
]);
|
|
160
|
-
}
|
|
161
|
-
function deleteMediaFileRow(ref) {
|
|
162
|
-
getDatabase().runSync(`DELETE FROM media_files WHERE ref = ?`, [ref]);
|
|
163
|
-
}
|
|
4
|
+
//#region src/media/index.native.ts
|
|
5
|
+
const media = require_shared.createMediaApi(require_storage_native.mediaStorage);
|
|
164
6
|
|
|
165
7
|
//#endregion
|
|
166
|
-
exports.isMediaRef = isMediaRef;
|
|
8
|
+
exports.isMediaRef = require_shared.isMediaRef;
|
|
167
9
|
exports.media = media;
|
package/dist/media.d.cts
CHANGED
|
@@ -1,32 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MediaData, MediaDeleteInput, MediaImportInput, MediaJson, MediaKind, MediaMutationOptions, MediaQueryKey, MediaRef, isMediaRef } from "./media/shared.cjs";
|
|
2
|
+
import * as _tanstack_react_query2 from "@tanstack/react-query";
|
|
2
3
|
|
|
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 MediaDeleteInput = MediaRef | string | null | undefined;
|
|
20
|
-
type MediaQueryKey = readonly ['silo', 'media', MediaRef | string | null];
|
|
21
|
-
type MediaMutationOptions = {
|
|
22
|
-
queryClient?: QueryClient;
|
|
23
|
-
};
|
|
4
|
+
//#region src/media/index.native.d.ts
|
|
24
5
|
declare const media: {
|
|
25
|
-
queryOptions(ref: MediaRef | string | null | undefined): UseQueryOptions<string | null, Error, string | null, MediaQueryKey>;
|
|
26
|
-
mutationOptions(options?: MediaMutationOptions): UseMutationOptions<MediaRef, Error, MediaImportInput>;
|
|
6
|
+
queryOptions(ref: MediaRef | string | null | undefined): _tanstack_react_query2.UseQueryOptions<string | null, Error, string | null, MediaQueryKey>;
|
|
7
|
+
mutationOptions(options?: MediaMutationOptions): _tanstack_react_query2.UseMutationOptions<MediaRef, Error, MediaImportInput>;
|
|
27
8
|
delete(ref: MediaDeleteInput): Promise<boolean>;
|
|
28
|
-
deleteMutationOptions(options?: MediaMutationOptions): UseMutationOptions<boolean, Error, MediaDeleteInput>;
|
|
9
|
+
deleteMutationOptions(options?: MediaMutationOptions): _tanstack_react_query2.UseMutationOptions<boolean, Error, MediaDeleteInput>;
|
|
29
10
|
};
|
|
30
|
-
declare function isMediaRef(value: unknown): value is MediaRef;
|
|
31
11
|
//#endregion
|
|
32
|
-
export { MediaData, MediaDeleteInput, MediaImportInput, MediaJson, MediaKind, MediaMutationOptions, MediaQueryKey, MediaRef, isMediaRef, media };
|
|
12
|
+
export { type MediaData, type MediaDeleteInput, type MediaImportInput, type MediaJson, type MediaKind, type MediaMutationOptions, type MediaQueryKey, type MediaRef, isMediaRef, media };
|
package/dist/media.d.mts
CHANGED
|
@@ -1,32 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MediaData, MediaDeleteInput, MediaImportInput, MediaJson, MediaKind, MediaMutationOptions, MediaQueryKey, MediaRef, isMediaRef } from "./media/shared.mjs";
|
|
2
|
+
import * as _tanstack_react_query2 from "@tanstack/react-query";
|
|
2
3
|
|
|
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 MediaDeleteInput = MediaRef | string | null | undefined;
|
|
20
|
-
type MediaQueryKey = readonly ['silo', 'media', MediaRef | string | null];
|
|
21
|
-
type MediaMutationOptions = {
|
|
22
|
-
queryClient?: QueryClient;
|
|
23
|
-
};
|
|
4
|
+
//#region src/media/index.native.d.ts
|
|
24
5
|
declare const media: {
|
|
25
|
-
queryOptions(ref: MediaRef | string | null | undefined): UseQueryOptions<string | null, Error, string | null, MediaQueryKey>;
|
|
26
|
-
mutationOptions(options?: MediaMutationOptions): UseMutationOptions<MediaRef, Error, MediaImportInput>;
|
|
6
|
+
queryOptions(ref: MediaRef | string | null | undefined): _tanstack_react_query2.UseQueryOptions<string | null, Error, string | null, MediaQueryKey>;
|
|
7
|
+
mutationOptions(options?: MediaMutationOptions): _tanstack_react_query2.UseMutationOptions<MediaRef, Error, MediaImportInput>;
|
|
27
8
|
delete(ref: MediaDeleteInput): Promise<boolean>;
|
|
28
|
-
deleteMutationOptions(options?: MediaMutationOptions): UseMutationOptions<boolean, Error, MediaDeleteInput>;
|
|
9
|
+
deleteMutationOptions(options?: MediaMutationOptions): _tanstack_react_query2.UseMutationOptions<boolean, Error, MediaDeleteInput>;
|
|
29
10
|
};
|
|
30
|
-
declare function isMediaRef(value: unknown): value is MediaRef;
|
|
31
11
|
//#endregion
|
|
32
|
-
export { MediaData, MediaDeleteInput, MediaImportInput, MediaJson, MediaKind, MediaMutationOptions, MediaQueryKey, MediaRef, isMediaRef, media };
|
|
12
|
+
export { type MediaData, type MediaDeleteInput, type MediaImportInput, type MediaJson, type MediaKind, type MediaMutationOptions, type MediaQueryKey, type MediaRef, isMediaRef, media };
|
package/dist/media.mjs
CHANGED
|
@@ -1,165 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { mutationOptions, queryOptions } from "@tanstack/react-query";
|
|
4
|
-
import { Directory, File, Paths } from "expo-file-system";
|
|
1
|
+
import { createMediaApi, isMediaRef } from "./media/shared.mjs";
|
|
2
|
+
import { mediaStorage } from "./media/storage.native.mjs";
|
|
5
3
|
|
|
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
|
-
delete(ref) {
|
|
29
|
-
return deleteMedia(ref);
|
|
30
|
-
},
|
|
31
|
-
deleteMutationOptions(options = {}) {
|
|
32
|
-
return mutationOptions({
|
|
33
|
-
mutationKey: [
|
|
34
|
-
"silo",
|
|
35
|
-
"media",
|
|
36
|
-
"delete"
|
|
37
|
-
],
|
|
38
|
-
mutationFn: async (ref) => {
|
|
39
|
-
const deleted = await deleteMedia(ref);
|
|
40
|
-
if (isMediaRef(ref)) options.queryClient?.setQueryData(mediaQueryKey(ref), null);
|
|
41
|
-
return deleted;
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
function isMediaRef(value) {
|
|
47
|
-
return typeof value === "string" && value.startsWith("media://");
|
|
48
|
-
}
|
|
49
|
-
function mediaQueryKey(ref) {
|
|
50
|
-
return [
|
|
51
|
-
"silo",
|
|
52
|
-
"media",
|
|
53
|
-
ref ?? null
|
|
54
|
-
];
|
|
55
|
-
}
|
|
56
|
-
async function importMedia(input) {
|
|
57
|
-
assertMediaKind(input.kind);
|
|
58
|
-
const id = randomUUID();
|
|
59
|
-
const ref = createMediaRef(input.kind, id);
|
|
60
|
-
const source = new File(input.uri);
|
|
61
|
-
const name = input.name ?? source.name ?? `${id}${source.extension}`;
|
|
62
|
-
const destinationDirectory = mediaDirectory(input.kind);
|
|
63
|
-
destinationDirectory.create({
|
|
64
|
-
idempotent: true,
|
|
65
|
-
intermediates: true
|
|
66
|
-
});
|
|
67
|
-
const destination = new File(destinationDirectory, `${id}${extensionFor(name)}`);
|
|
68
|
-
await source.copy(destination);
|
|
69
|
-
const info = destination.info();
|
|
70
|
-
const mimeType = input.mimeType ?? (destination.type || null);
|
|
71
|
-
const size = info.size ?? destination.size;
|
|
72
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
73
|
-
insertMediaFile({
|
|
74
|
-
ref,
|
|
75
|
-
uri: destination.uri,
|
|
76
|
-
kind: input.kind,
|
|
77
|
-
name,
|
|
78
|
-
mimeType,
|
|
79
|
-
size,
|
|
80
|
-
createdAt: timestamp,
|
|
81
|
-
data: input.data ?? {}
|
|
82
|
-
});
|
|
83
|
-
return {
|
|
84
|
-
ref,
|
|
85
|
-
uri: destination.uri
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
function resolveMediaUri(ref) {
|
|
89
|
-
if (!isMediaRef(ref)) return null;
|
|
90
|
-
const row = getMediaFile(ref);
|
|
91
|
-
if (!row) return null;
|
|
92
|
-
return new File(row.uri).exists ? row.uri : null;
|
|
93
|
-
}
|
|
94
|
-
async function deleteMedia(ref) {
|
|
95
|
-
if (!isMediaRef(ref)) return false;
|
|
96
|
-
const row = getMediaFile(ref);
|
|
97
|
-
if (!row) return false;
|
|
98
|
-
const file = new File(row.uri);
|
|
99
|
-
if (file.exists) await file.delete();
|
|
100
|
-
deleteMediaFileRow(ref);
|
|
101
|
-
return true;
|
|
102
|
-
}
|
|
103
|
-
function getMediaFile(ref) {
|
|
104
|
-
return getDatabase().getFirstSync(`SELECT ref, uri FROM media_files WHERE ref = ?`, [ref]);
|
|
105
|
-
}
|
|
106
|
-
function createMediaRef(kind, id) {
|
|
107
|
-
return `media://silo/${kindDirectoryName(kind)}/${id}`;
|
|
108
|
-
}
|
|
109
|
-
function mediaDirectory(kind) {
|
|
110
|
-
return new Directory(Paths.document, "silo", "media", kindDirectoryName(kind));
|
|
111
|
-
}
|
|
112
|
-
function kindDirectoryName(kind) {
|
|
113
|
-
switch (kind) {
|
|
114
|
-
case "image": return "images";
|
|
115
|
-
case "video": return "videos";
|
|
116
|
-
case "audio": return "audio";
|
|
117
|
-
case "file": return "files";
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
function extensionFor(name) {
|
|
121
|
-
return name.match(/\.[A-Za-z0-9]+$/)?.[0] ?? "";
|
|
122
|
-
}
|
|
123
|
-
function assertMediaKind(kind) {
|
|
124
|
-
if (kind !== "image" && kind !== "video" && kind !== "audio" && kind !== "file") throw new Error("Media kind must be one of \"image\", \"video\", \"audio\", or \"file\".");
|
|
125
|
-
}
|
|
126
|
-
let database;
|
|
127
|
-
function getDatabase() {
|
|
128
|
-
if (!database) {
|
|
129
|
-
database = openDatabaseSync("silo.db");
|
|
130
|
-
database.execSync(`
|
|
131
|
-
PRAGMA journal_mode = WAL;
|
|
132
|
-
|
|
133
|
-
CREATE TABLE IF NOT EXISTS media_files (
|
|
134
|
-
ref TEXT PRIMARY KEY,
|
|
135
|
-
uri TEXT NOT NULL,
|
|
136
|
-
kind TEXT NOT NULL,
|
|
137
|
-
name TEXT NOT NULL,
|
|
138
|
-
mimeType TEXT,
|
|
139
|
-
size INTEGER NOT NULL,
|
|
140
|
-
createdAt TEXT NOT NULL,
|
|
141
|
-
data TEXT NOT NULL
|
|
142
|
-
);
|
|
143
|
-
`);
|
|
144
|
-
}
|
|
145
|
-
return database;
|
|
146
|
-
}
|
|
147
|
-
function insertMediaFile(file) {
|
|
148
|
-
getDatabase().runSync(`INSERT INTO media_files (ref, uri, kind, name, mimeType, size, createdAt, data)
|
|
149
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
150
|
-
file.ref,
|
|
151
|
-
file.uri,
|
|
152
|
-
file.kind,
|
|
153
|
-
file.name,
|
|
154
|
-
file.mimeType,
|
|
155
|
-
file.size,
|
|
156
|
-
file.createdAt,
|
|
157
|
-
JSON.stringify(file.data)
|
|
158
|
-
]);
|
|
159
|
-
}
|
|
160
|
-
function deleteMediaFileRow(ref) {
|
|
161
|
-
getDatabase().runSync(`DELETE FROM media_files WHERE ref = ?`, [ref]);
|
|
162
|
-
}
|
|
4
|
+
//#region src/media/index.native.ts
|
|
5
|
+
const media = createMediaApi(mediaStorage);
|
|
163
6
|
|
|
164
7
|
//#endregion
|
|
165
8
|
export { isMediaRef, media };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const require_shared = require('./media/shared.cjs');
|
|
2
|
+
const require_storage_web = require('./media/storage.web.cjs');
|
|
3
|
+
|
|
4
|
+
//#region src/media/index.web.ts
|
|
5
|
+
const media = require_shared.createMediaApi(require_storage_web.mediaStorage);
|
|
6
|
+
|
|
7
|
+
//#endregion
|
|
8
|
+
exports.isMediaRef = require_shared.isMediaRef;
|
|
9
|
+
exports.media = media;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { MediaData, MediaDeleteInput, MediaImportInput, MediaJson, MediaKind, MediaMutationOptions, MediaQueryKey, MediaRef, isMediaRef } from "./media/shared.cjs";
|
|
2
|
+
import * as _tanstack_react_query0 from "@tanstack/react-query";
|
|
3
|
+
|
|
4
|
+
//#region src/media/index.web.d.ts
|
|
5
|
+
declare const media: {
|
|
6
|
+
queryOptions(ref: MediaRef | string | null | undefined): _tanstack_react_query0.UseQueryOptions<string | null, Error, string | null, MediaQueryKey>;
|
|
7
|
+
mutationOptions(options?: MediaMutationOptions): _tanstack_react_query0.UseMutationOptions<MediaRef, Error, MediaImportInput>;
|
|
8
|
+
delete(ref: MediaDeleteInput): Promise<boolean>;
|
|
9
|
+
deleteMutationOptions(options?: MediaMutationOptions): _tanstack_react_query0.UseMutationOptions<boolean, Error, MediaDeleteInput>;
|
|
10
|
+
};
|
|
11
|
+
//#endregion
|
|
12
|
+
export { type MediaData, type MediaDeleteInput, type MediaImportInput, type MediaJson, type MediaKind, type MediaMutationOptions, type MediaQueryKey, type MediaRef, isMediaRef, media };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { MediaData, MediaDeleteInput, MediaImportInput, MediaJson, MediaKind, MediaMutationOptions, MediaQueryKey, MediaRef, isMediaRef } from "./media/shared.mjs";
|
|
2
|
+
import * as _tanstack_react_query0 from "@tanstack/react-query";
|
|
3
|
+
|
|
4
|
+
//#region src/media/index.web.d.ts
|
|
5
|
+
declare const media: {
|
|
6
|
+
queryOptions(ref: MediaRef | string | null | undefined): _tanstack_react_query0.UseQueryOptions<string | null, Error, string | null, MediaQueryKey>;
|
|
7
|
+
mutationOptions(options?: MediaMutationOptions): _tanstack_react_query0.UseMutationOptions<MediaRef, Error, MediaImportInput>;
|
|
8
|
+
delete(ref: MediaDeleteInput): Promise<boolean>;
|
|
9
|
+
deleteMutationOptions(options?: MediaMutationOptions): _tanstack_react_query0.UseMutationOptions<boolean, Error, MediaDeleteInput>;
|
|
10
|
+
};
|
|
11
|
+
//#endregion
|
|
12
|
+
export { type MediaData, type MediaDeleteInput, type MediaImportInput, type MediaJson, type MediaKind, type MediaMutationOptions, type MediaQueryKey, type MediaRef, isMediaRef, media };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
let __tanstack_react_query = require("@tanstack/react-query");
|
|
3
|
+
|
|
4
|
+
//#region src/settings/shared.ts
|
|
5
|
+
const settingNames = /* @__PURE__ */ new Set();
|
|
6
|
+
const settingNamePattern = /^[A-Za-z][A-Za-z0-9_.:-]*$/;
|
|
7
|
+
function createSettingApi(storage) {
|
|
8
|
+
function setting(name, defaultValue) {
|
|
9
|
+
assertSettingName(name);
|
|
10
|
+
assertSettingValue(resolveDefault(defaultValue));
|
|
11
|
+
if (settingNames.has(name)) throw new Error(`Setting "${name}" is already registered. Define each setting once and import the existing setting instead.`);
|
|
12
|
+
settingNames.add(name);
|
|
13
|
+
const queryKey = [
|
|
14
|
+
"silo",
|
|
15
|
+
"setting",
|
|
16
|
+
name
|
|
17
|
+
];
|
|
18
|
+
return {
|
|
19
|
+
name,
|
|
20
|
+
defaultValue,
|
|
21
|
+
queryKey,
|
|
22
|
+
queryOptions() {
|
|
23
|
+
return (0, __tanstack_react_query.queryOptions)({
|
|
24
|
+
queryKey,
|
|
25
|
+
queryFn: () => storage.read(name, defaultValue)
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
mutationOptions(options = {}) {
|
|
29
|
+
return (0, __tanstack_react_query.mutationOptions)({
|
|
30
|
+
mutationKey: queryKey,
|
|
31
|
+
mutationFn: async (valueOrUpdater) => {
|
|
32
|
+
const current = await storage.read(name, defaultValue);
|
|
33
|
+
const next = typeof valueOrUpdater === "function" ? valueOrUpdater(current) : valueOrUpdater;
|
|
34
|
+
await storage.write(name, next);
|
|
35
|
+
return next;
|
|
36
|
+
},
|
|
37
|
+
onSuccess(value) {
|
|
38
|
+
options.queryClient?.setQueryData(queryKey, value);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return { setting };
|
|
45
|
+
}
|
|
46
|
+
function storageKey(name) {
|
|
47
|
+
return `silo:setting:${name}`;
|
|
48
|
+
}
|
|
49
|
+
function resolveDefault(defaultValue) {
|
|
50
|
+
return typeof defaultValue === "function" ? defaultValue() : defaultValue;
|
|
51
|
+
}
|
|
52
|
+
function assertSettingValue(value) {
|
|
53
|
+
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean" && value !== null) throw new Error("Settings must be strings, numbers, booleans, or null.");
|
|
54
|
+
}
|
|
55
|
+
function assertSettingName(name) {
|
|
56
|
+
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}".`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
exports.assertSettingValue = assertSettingValue;
|
|
61
|
+
exports.createSettingApi = createSettingApi;
|
|
62
|
+
exports.resolveDefault = resolveDefault;
|
|
63
|
+
exports.storageKey = storageKey;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { QueryClient, UseMutationOptions, UseQueryOptions } from "@tanstack/react-query";
|
|
2
|
+
|
|
3
|
+
//#region src/settings/shared.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
|
+
//#endregion
|
|
19
|
+
export { Setting, SettingDefault, SettingMutation, SettingMutationOptions, SettingQueryKey, SettingValue };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { QueryClient, UseMutationOptions, UseQueryOptions } from "@tanstack/react-query";
|
|
2
|
+
|
|
3
|
+
//#region src/settings/shared.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
|
+
//#endregion
|
|
19
|
+
export { Setting, SettingDefault, SettingMutation, SettingMutationOptions, SettingQueryKey, SettingValue };
|