@rockhall/electron-offline-content 0.4.0
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/CHANGELOG.md +384 -0
- package/LICENSE +21 -0
- package/README.md +794 -0
- package/dist/internal/asset-file-name.cjs +13 -0
- package/dist/internal/asset-file-name.cjs.map +1 -0
- package/dist/internal/asset-file-name.d.cts +6 -0
- package/dist/internal/asset-file-name.d.cts.map +1 -0
- package/dist/internal/asset-file-name.d.ts +6 -0
- package/dist/internal/asset-file-name.d.ts.map +1 -0
- package/dist/internal/asset-file-name.js +12 -0
- package/dist/internal/asset-file-name.js.map +1 -0
- package/dist/internal/asset-key.cjs +30 -0
- package/dist/internal/asset-key.cjs.map +1 -0
- package/dist/internal/asset-key.d.cts +19 -0
- package/dist/internal/asset-key.d.cts.map +1 -0
- package/dist/internal/asset-key.d.ts +19 -0
- package/dist/internal/asset-key.d.ts.map +1 -0
- package/dist/internal/asset-key.js +27 -0
- package/dist/internal/asset-key.js.map +1 -0
- package/dist/internal/log-format.cjs +98 -0
- package/dist/internal/log-format.cjs.map +1 -0
- package/dist/internal/log-format.d.cts +10 -0
- package/dist/internal/log-format.d.cts.map +1 -0
- package/dist/internal/log-format.d.ts +10 -0
- package/dist/internal/log-format.d.ts.map +1 -0
- package/dist/internal/log-format.js +97 -0
- package/dist/internal/log-format.js.map +1 -0
- package/dist/internal/media-kind.cjs +46 -0
- package/dist/internal/media-kind.cjs.map +1 -0
- package/dist/internal/media-kind.d.cts +20 -0
- package/dist/internal/media-kind.d.cts.map +1 -0
- package/dist/internal/media-kind.d.ts +20 -0
- package/dist/internal/media-kind.d.ts.map +1 -0
- package/dist/internal/media-kind.js +45 -0
- package/dist/internal/media-kind.js.map +1 -0
- package/dist/internal/url-warn.cjs +14 -0
- package/dist/internal/url-warn.cjs.map +1 -0
- package/dist/internal/url-warn.d.cts +10 -0
- package/dist/internal/url-warn.d.cts.map +1 -0
- package/dist/internal/url-warn.d.ts +10 -0
- package/dist/internal/url-warn.d.ts.map +1 -0
- package/dist/internal/url-warn.js +13 -0
- package/dist/internal/url-warn.js.map +1 -0
- package/dist/internal/validation.cjs +222 -0
- package/dist/internal/validation.cjs.map +1 -0
- package/dist/internal/validation.d.cts +78 -0
- package/dist/internal/validation.d.cts.map +1 -0
- package/dist/internal/validation.d.ts +78 -0
- package/dist/internal/validation.d.ts.map +1 -0
- package/dist/internal/validation.js +196 -0
- package/dist/internal/validation.js.map +1 -0
- package/dist/main/asset-download.cjs +265 -0
- package/dist/main/asset-download.cjs.map +1 -0
- package/dist/main/asset-download.d.cts +12 -0
- package/dist/main/asset-download.d.cts.map +1 -0
- package/dist/main/asset-download.d.ts +12 -0
- package/dist/main/asset-download.d.ts.map +1 -0
- package/dist/main/asset-download.js +263 -0
- package/dist/main/asset-download.js.map +1 -0
- package/dist/main/database.cjs +473 -0
- package/dist/main/database.cjs.map +1 -0
- package/dist/main/database.d.cts +81 -0
- package/dist/main/database.d.cts.map +1 -0
- package/dist/main/database.d.ts +81 -0
- package/dist/main/database.d.ts.map +1 -0
- package/dist/main/database.js +472 -0
- package/dist/main/database.js.map +1 -0
- package/dist/main/index.cjs +22 -0
- package/dist/main/index.d.cts +7 -0
- package/dist/main/index.d.ts +7 -0
- package/dist/main/index.js +7 -0
- package/dist/main/media-cache.cjs +862 -0
- package/dist/main/media-cache.cjs.map +1 -0
- package/dist/main/media-cache.d.cts +134 -0
- package/dist/main/media-cache.d.cts.map +1 -0
- package/dist/main/media-cache.d.ts +134 -0
- package/dist/main/media-cache.d.ts.map +1 -0
- package/dist/main/media-cache.js +854 -0
- package/dist/main/media-cache.js.map +1 -0
- package/dist/main/storage-root-lock.cjs +124 -0
- package/dist/main/storage-root-lock.cjs.map +1 -0
- package/dist/main/storage-root-lock.d.cts +11 -0
- package/dist/main/storage-root-lock.d.cts.map +1 -0
- package/dist/main/storage-root-lock.d.ts +11 -0
- package/dist/main/storage-root-lock.d.ts.map +1 -0
- package/dist/main/storage-root-lock.js +120 -0
- package/dist/main/storage-root-lock.js.map +1 -0
- package/dist/main/store.cjs +197 -0
- package/dist/main/store.cjs.map +1 -0
- package/dist/main/store.d.cts +83 -0
- package/dist/main/store.d.cts.map +1 -0
- package/dist/main/store.d.ts +83 -0
- package/dist/main/store.d.ts.map +1 -0
- package/dist/main/store.js +195 -0
- package/dist/main/store.js.map +1 -0
- package/dist/preload/index.cjs +36 -0
- package/dist/preload/index.cjs.map +1 -0
- package/dist/preload/index.d.cts +14 -0
- package/dist/preload/index.d.cts.map +1 -0
- package/dist/preload/index.d.ts +14 -0
- package/dist/preload/index.d.ts.map +1 -0
- package/dist/preload/index.js +34 -0
- package/dist/preload/index.js.map +1 -0
- package/dist/react/index.cjs +199 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +50 -0
- package/dist/react/index.d.cts.map +1 -0
- package/dist/react/index.d.ts +50 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +191 -0
- package/dist/react/index.js.map +1 -0
- package/dist/renderer/helpers.cjs +36 -0
- package/dist/renderer/helpers.cjs.map +1 -0
- package/dist/renderer/helpers.d.cts +11 -0
- package/dist/renderer/helpers.d.cts.map +1 -0
- package/dist/renderer/helpers.d.ts +11 -0
- package/dist/renderer/helpers.d.ts.map +1 -0
- package/dist/renderer/helpers.js +35 -0
- package/dist/renderer/helpers.js.map +1 -0
- package/dist/renderer/index.cjs +20 -0
- package/dist/renderer/index.cjs.map +1 -0
- package/dist/renderer/index.d.cts +14 -0
- package/dist/renderer/index.d.cts.map +1 -0
- package/dist/renderer/index.d.ts +14 -0
- package/dist/renderer/index.d.ts.map +1 -0
- package/dist/renderer/index.js +14 -0
- package/dist/renderer/index.js.map +1 -0
- package/dist/renderer/runtime.cjs +278 -0
- package/dist/renderer/runtime.cjs.map +1 -0
- package/dist/renderer/runtime.d.cts +35 -0
- package/dist/renderer/runtime.d.cts.map +1 -0
- package/dist/renderer/runtime.d.ts +35 -0
- package/dist/renderer/runtime.d.ts.map +1 -0
- package/dist/renderer/runtime.js +273 -0
- package/dist/renderer/runtime.js.map +1 -0
- package/dist/renderer/window-globals.d.cts +9 -0
- package/dist/renderer/window-globals.d.cts.map +1 -0
- package/dist/renderer/window-globals.d.ts +9 -0
- package/dist/renderer/window-globals.d.ts.map +1 -0
- package/dist/shared/errors.cjs +102 -0
- package/dist/shared/errors.cjs.map +1 -0
- package/dist/shared/errors.d.cts +45 -0
- package/dist/shared/errors.d.cts.map +1 -0
- package/dist/shared/errors.d.ts +45 -0
- package/dist/shared/errors.d.ts.map +1 -0
- package/dist/shared/errors.js +93 -0
- package/dist/shared/errors.js.map +1 -0
- package/dist/shared/ipc.cjs +14 -0
- package/dist/shared/ipc.cjs.map +1 -0
- package/dist/shared/ipc.d.cts +12 -0
- package/dist/shared/ipc.d.cts.map +1 -0
- package/dist/shared/ipc.d.ts +12 -0
- package/dist/shared/ipc.d.ts.map +1 -0
- package/dist/shared/ipc.js +13 -0
- package/dist/shared/ipc.js.map +1 -0
- package/dist/shared/normalize.cjs +19 -0
- package/dist/shared/normalize.cjs.map +1 -0
- package/dist/shared/normalize.d.cts +11 -0
- package/dist/shared/normalize.d.cts.map +1 -0
- package/dist/shared/normalize.d.ts +11 -0
- package/dist/shared/normalize.d.ts.map +1 -0
- package/dist/shared/normalize.js +18 -0
- package/dist/shared/normalize.js.map +1 -0
- package/dist/shared/pagination.cjs +32 -0
- package/dist/shared/pagination.cjs.map +1 -0
- package/dist/shared/pagination.d.cts +14 -0
- package/dist/shared/pagination.d.cts.map +1 -0
- package/dist/shared/pagination.d.ts +14 -0
- package/dist/shared/pagination.d.ts.map +1 -0
- package/dist/shared/pagination.js +28 -0
- package/dist/shared/pagination.js.map +1 -0
- package/dist/shared/stem.cjs +16 -0
- package/dist/shared/stem.cjs.map +1 -0
- package/dist/shared/stem.d.cts +6 -0
- package/dist/shared/stem.d.cts.map +1 -0
- package/dist/shared/stem.d.ts +6 -0
- package/dist/shared/stem.d.ts.map +1 -0
- package/dist/shared/stem.js +14 -0
- package/dist/shared/stem.js.map +1 -0
- package/dist/shared/types.cjs +15 -0
- package/dist/shared/types.cjs.map +1 -0
- package/dist/shared/types.d.cts +234 -0
- package/dist/shared/types.d.cts.map +1 -0
- package/dist/shared/types.d.ts +234 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +14 -0
- package/dist/shared/types.js.map +1 -0
- package/package.json +120 -0
- package/skills/authenticated-downloads/SKILL.md +203 -0
- package/skills/cache-configuration/SKILL.md +357 -0
- package/skills/cache-configuration/references/options.md +356 -0
- package/skills/getting-started/SKILL.md +407 -0
- package/skills/production-checklist/SKILL.md +397 -0
- package/skills/react-rendering/SKILL.md +424 -0
- package/skills/react-rendering/references/hooks.md +443 -0
- package/skills/store-authoring/SKILL.md +369 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: store-authoring
|
|
3
|
+
description: >
|
|
4
|
+
Writing resolveStore functions against any remote source (CMS, S3,
|
|
5
|
+
REST API). Using createMediaStore, store.defineIndex, and store.add
|
|
6
|
+
for flat asset stores. User-defined secondary indexes for querying.
|
|
7
|
+
Per-asset versioning, asset key conventions, validation rules,
|
|
8
|
+
version-driven cache busting. Embedding presigned URLs in asset
|
|
9
|
+
url fields during resolveStore.
|
|
10
|
+
type: core
|
|
11
|
+
library: electron-offline-content
|
|
12
|
+
library_version: "0.4.0"
|
|
13
|
+
requires:
|
|
14
|
+
- getting-started
|
|
15
|
+
sources:
|
|
16
|
+
- "rockhallweb/electron-offline-content:src/main/store.ts"
|
|
17
|
+
- "rockhallweb/electron-offline-content:src/shared/normalize.ts"
|
|
18
|
+
- "rockhallweb/electron-offline-content:src/shared/types.ts"
|
|
19
|
+
- "rockhallweb/electron-offline-content:src/internal/validation.ts"
|
|
20
|
+
- "rockhallweb/electron-offline-content:src/internal/asset-file-name.ts"
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
> **Dependency:** This skill builds on getting-started. Read it first for full main → preload → renderer wiring.
|
|
24
|
+
|
|
25
|
+
## Setup
|
|
26
|
+
|
|
27
|
+
A complete `resolveStore` function that fetches from an API and builds a flat asset store with secondary indexes:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { createMediaStore } from "@rockhall/electron-offline-content/main";
|
|
31
|
+
|
|
32
|
+
interface ApiCourse {
|
|
33
|
+
id: string;
|
|
34
|
+
title: string;
|
|
35
|
+
revision: string;
|
|
36
|
+
level: "beginner" | "advanced";
|
|
37
|
+
videoUrl: string;
|
|
38
|
+
posterUrl: string;
|
|
39
|
+
subtitleUrl: string | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function resolveStore() {
|
|
43
|
+
const res = await fetch("https://cms.example.com/api/courses");
|
|
44
|
+
const courses: ApiCourse[] = await res.json();
|
|
45
|
+
|
|
46
|
+
const store = createMediaStore();
|
|
47
|
+
const level = store.defineIndex("level");
|
|
48
|
+
const type = store.defineIndex("type");
|
|
49
|
+
|
|
50
|
+
for (const course of courses) {
|
|
51
|
+
store.add(["course", course.id, "video"], {
|
|
52
|
+
version: course.revision,
|
|
53
|
+
mimeType: "video/mp4",
|
|
54
|
+
url: course.videoUrl,
|
|
55
|
+
metadata: { title: course.title, level: course.level, type: "video" },
|
|
56
|
+
indexes: [level(course.level), type("video")],
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
store.add(["course", course.id, "poster"], {
|
|
60
|
+
version: course.revision,
|
|
61
|
+
mimeType: "image/jpeg",
|
|
62
|
+
url: course.posterUrl,
|
|
63
|
+
metadata: { title: `${course.title} poster`, level: course.level, type: "poster" },
|
|
64
|
+
indexes: [level(course.level), type("poster")],
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (course.subtitleUrl) {
|
|
68
|
+
store.add(["course", course.id, "subs-en"], {
|
|
69
|
+
version: course.revision,
|
|
70
|
+
mimeType: "text/vtt",
|
|
71
|
+
fileName: "en.vtt",
|
|
72
|
+
url: course.subtitleUrl,
|
|
73
|
+
metadata: { title: `${course.title} subtitles`, level: course.level, type: "subtitle" },
|
|
74
|
+
indexes: [level(course.level), type("subtitle")],
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return store;
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Resolved assets expose `asset.key` (storage hash) and `asset.displayKey` (human-readable path, e.g. `course/intro-101/video`).
|
|
84
|
+
|
|
85
|
+
## Core Patterns
|
|
86
|
+
|
|
87
|
+
### User-defined secondary indexes
|
|
88
|
+
|
|
89
|
+
Indexes replace the old namespace hierarchy. Define them with `store.defineIndex()` before adding assets, then pass `IndexTag` values from each handle into `store.add()` for querying with `useMediaByIndex` in the renderer or `listByIndex` in the main process.
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { createMediaStore } from "@rockhall/electron-offline-content/main";
|
|
93
|
+
|
|
94
|
+
const store = createMediaStore();
|
|
95
|
+
|
|
96
|
+
const category = store.defineIndex("category");
|
|
97
|
+
const year = store.defineIndex("year");
|
|
98
|
+
const floor = store.defineIndex("floor");
|
|
99
|
+
|
|
100
|
+
for (const exhibit of exhibits) {
|
|
101
|
+
store.add(["exhibit", exhibit.slug], {
|
|
102
|
+
version: exhibit.revision,
|
|
103
|
+
mimeType: exhibit.mimeType,
|
|
104
|
+
url: exhibit.mediaUrl,
|
|
105
|
+
metadata: {
|
|
106
|
+
title: exhibit.title,
|
|
107
|
+
category: exhibit.category,
|
|
108
|
+
year: exhibit.year,
|
|
109
|
+
floor: exhibit.floor,
|
|
110
|
+
},
|
|
111
|
+
indexes: [category(exhibit.category), year(String(exhibit.year)), floor(exhibit.floor)],
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
// In renderer — query by index
|
|
118
|
+
const floor2 = useMediaByIndex("floor", "2", { limit: 100 });
|
|
119
|
+
const videos2026 = useMediaByIndex("year", "2026");
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Asset key conventions
|
|
123
|
+
|
|
124
|
+
The first argument to `store.add()` is an `AssetKeyInput`: a non-empty string or a non-empty array of non-empty string segments. Arrays avoid ambiguity at segment boundaries and produce a readable `displayKey` (segments joined with `/`) while `key` on resolved assets remains the storage hash.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
store.add(["video", "welcome"], { ... });
|
|
128
|
+
store.add(["video", "welcome", "poster"], { ... });
|
|
129
|
+
store.add(["inductee", "2026", "beyonce", "ceremony"], { ... });
|
|
130
|
+
store.add(["inductee", "2026", "beyonce", "poster"], { ... });
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Use the same `AssetKeyInput` in `useMediaAsset(key)` for single-asset lookups. Protocol URLs still use the encoded storage identity derived from the hash.
|
|
134
|
+
|
|
135
|
+
### Per-asset versioning
|
|
136
|
+
|
|
137
|
+
Each asset has its own `version` string. When the version changes, the asset is re-downloaded. Assets with unchanged versions keep their cached blobs.
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
store.add(["video", "welcome"], {
|
|
141
|
+
version: apiEntry.updatedAt, // timestamp-based: "2026-03-15T08:30:00Z"
|
|
142
|
+
mimeType: "video/mp4",
|
|
143
|
+
url: apiEntry.videoUrl,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
store.add(["image", "logo"], {
|
|
147
|
+
version: apiEntry.contentMd5, // content-hash: "a1b2c3d4e5f6"
|
|
148
|
+
mimeType: "image/png",
|
|
149
|
+
url: apiEntry.logoUrl,
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Embedding auth in resolveStore
|
|
154
|
+
|
|
155
|
+
Since `resolveAssetRequest` has been removed, embed presigned (or otherwise auth-bearing) URLs in each asset’s `url` field during `resolveStore()`:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { createMediaStore } from "@rockhall/electron-offline-content/main";
|
|
159
|
+
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
|
|
160
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
161
|
+
|
|
162
|
+
const s3 = new S3Client({ region: "us-east-1" });
|
|
163
|
+
|
|
164
|
+
async function resolveStore() {
|
|
165
|
+
const store = createMediaStore();
|
|
166
|
+
const catalog = await fetchCatalog();
|
|
167
|
+
|
|
168
|
+
for (const item of catalog) {
|
|
169
|
+
const signedUrl = await getSignedUrl(
|
|
170
|
+
s3,
|
|
171
|
+
new GetObjectCommand({ Bucket: "my-content-bucket", Key: item.objectKey }),
|
|
172
|
+
{ expiresIn: 3600 },
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
store.add(["s3", item.id], {
|
|
176
|
+
version: item.revision,
|
|
177
|
+
mimeType: "video/mp4",
|
|
178
|
+
url: signedUrl,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return store;
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Multi-asset content
|
|
187
|
+
|
|
188
|
+
Related assets share a key prefix and use the same indexes. Use `useMediaAsset(key)` for individual lookups or `useMediaByIndex` to query by a shared index value:
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
const store = createMediaStore();
|
|
192
|
+
const inductee = store.defineIndex("inductee");
|
|
193
|
+
const role = store.defineIndex("role");
|
|
194
|
+
|
|
195
|
+
store.add(["inductee", "2026", "beyonce", "ceremony"], {
|
|
196
|
+
version: "3",
|
|
197
|
+
mimeType: "video/mp4",
|
|
198
|
+
url: "https://cdn.example.com/beyonce-ceremony.mp4",
|
|
199
|
+
metadata: { inducteeId: "beyonce-2026", role: "primary", title: "Beyoncé Induction Ceremony" },
|
|
200
|
+
indexes: [inductee("beyonce-2026"), role("primary")],
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
store.add(["inductee", "2026", "beyonce", "poster"], {
|
|
204
|
+
version: "3",
|
|
205
|
+
mimeType: "image/jpeg",
|
|
206
|
+
url: "https://cdn.example.com/beyonce-poster.jpg",
|
|
207
|
+
metadata: { inducteeId: "beyonce-2026", role: "poster", title: "Beyoncé Poster" },
|
|
208
|
+
indexes: [inductee("beyonce-2026"), role("poster")],
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
store.add(["inductee", "2026", "beyonce", "subs-en"], {
|
|
212
|
+
version: "3",
|
|
213
|
+
mimeType: "text/vtt",
|
|
214
|
+
fileName: "en.vtt",
|
|
215
|
+
url: "https://cdn.example.com/beyonce-en.vtt",
|
|
216
|
+
metadata: { inducteeId: "beyonce-2026", role: "subtitle", title: "English subtitles" },
|
|
217
|
+
indexes: [inductee("beyonce-2026"), role("subtitle")],
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
// In renderer — look up related assets by index
|
|
223
|
+
const beyonceAssets = useMediaByIndex("inductee", "beyonce-2026");
|
|
224
|
+
// Or look up individual assets by the same AssetKeyInput used in resolveStore
|
|
225
|
+
const ceremony = useMediaAsset(["inductee", "2026", "beyonce", "ceremony"]);
|
|
226
|
+
const poster = useMediaAsset(["inductee", "2026", "beyonce", "poster"]);
|
|
227
|
+
// asset.displayKey shows the joined path; asset.key is the stable hash
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Common Mistakes
|
|
231
|
+
|
|
232
|
+
### HIGH: Duplicate asset keys in store.add calls
|
|
233
|
+
|
|
234
|
+
`store.add()` throws `StoreValidationError` when a key has already been added to the store. Each asset key must be unique.
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
// WRONG — duplicate key (same AssetKeyInput resolves to the same storage hash)
|
|
238
|
+
store.add(["video", "welcome"], { version: "1", mimeType: "video/mp4", url });
|
|
239
|
+
store.add(["video", "welcome"], { version: "2", mimeType: "video/mp4", url });
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
// CORRECT — unique keys
|
|
244
|
+
store.add(["video", "welcome"], { version: "1", mimeType: "video/mp4", url });
|
|
245
|
+
store.add(["video", "welcome-v2"], { version: "2", mimeType: "video/mp4", url });
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Source: store.ts; validation.ts
|
|
249
|
+
|
|
250
|
+
### HIGH: Omitting required asset version field
|
|
251
|
+
|
|
252
|
+
`version` is required (min length 1) and drives cache busting. Fails validation at `store.add()` time.
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
// WRONG — missing version
|
|
256
|
+
store.add(["video", "welcome"], {
|
|
257
|
+
mimeType: "video/mp4",
|
|
258
|
+
url,
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
// CORRECT — version present
|
|
264
|
+
store.add(["video", "welcome"], {
|
|
265
|
+
version: "2026-03-15",
|
|
266
|
+
mimeType: "video/mp4",
|
|
267
|
+
url,
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Source: validation.ts
|
|
272
|
+
|
|
273
|
+
### HIGH: Asset URL without filename in path
|
|
274
|
+
|
|
275
|
+
When `fileName` is omitted, it is derived from the URL basename. URLs ending in `/` or with no parseable filename fail derivation.
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
// WRONG — URL has no filename to derive
|
|
279
|
+
store.add(["video", "ceremony"], {
|
|
280
|
+
version: "1",
|
|
281
|
+
mimeType: "video/mp4",
|
|
282
|
+
url: "https://cdn.example.com/api/stream/",
|
|
283
|
+
});
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
// CORRECT — explicit fileName when URL lacks one
|
|
288
|
+
store.add(["video", "ceremony"], {
|
|
289
|
+
version: "1",
|
|
290
|
+
mimeType: "video/mp4",
|
|
291
|
+
fileName: "ceremony.mp4",
|
|
292
|
+
url: "https://cdn.example.com/api/stream/",
|
|
293
|
+
});
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Source: internal/asset-file-name.ts
|
|
297
|
+
|
|
298
|
+
### HIGH: Forgetting to defineIndex before querying
|
|
299
|
+
|
|
300
|
+
Indexes must be defined with `store.defineIndex()` before assets are added. If you query an undefined index with `useMediaByIndex` or `listByIndex`, the query returns no results.
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
// WRONG — index not defined
|
|
304
|
+
const store = createMediaStore();
|
|
305
|
+
store.add(["video", "welcome"], {
|
|
306
|
+
version: "1",
|
|
307
|
+
mimeType: "video/mp4",
|
|
308
|
+
url,
|
|
309
|
+
metadata: { category: "lobby" },
|
|
310
|
+
});
|
|
311
|
+
// useMediaByIndex("category", "lobby") returns nothing
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
// CORRECT — define index before adding assets
|
|
316
|
+
const store = createMediaStore();
|
|
317
|
+
const category = store.defineIndex("category");
|
|
318
|
+
store.add(["video", "welcome"], {
|
|
319
|
+
version: "1",
|
|
320
|
+
mimeType: "video/mp4",
|
|
321
|
+
url,
|
|
322
|
+
metadata: { category: "lobby" },
|
|
323
|
+
indexes: [category("lobby")],
|
|
324
|
+
});
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Source: store.ts; README
|
|
328
|
+
|
|
329
|
+
### MEDIUM: Using non-HTTP asset URLs
|
|
330
|
+
|
|
331
|
+
Validation enforces `http://` or `https://` schemes. `file://`, `data:`, `blob:` are rejected.
|
|
332
|
+
|
|
333
|
+
### MEDIUM: Index function returning inconsistent types
|
|
334
|
+
|
|
335
|
+
Index functions must return a `string`. Returning `undefined`, `null`, or a non-string value for some assets causes those assets to be missing from index queries.
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
// WRONG — returns number, not string
|
|
339
|
+
store.defineIndex("year", (asset) => asset.metadata.year);
|
|
340
|
+
|
|
341
|
+
// CORRECT — always return a string
|
|
342
|
+
store.defineIndex("year", (asset) => String(asset.metadata.year));
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Source: store.ts
|
|
346
|
+
|
|
347
|
+
### MEDIUM: Too many fine-grained indexes
|
|
348
|
+
|
|
349
|
+
Each index adds overhead during store building and sync. Define indexes that match your actual query patterns. Prefer a single index with meaningful values over many single-purpose indexes.
|
|
350
|
+
|
|
351
|
+
Source: Maintainer interview
|
|
352
|
+
|
|
353
|
+
### HIGH Tension: Strict validation vs sync-time failures
|
|
354
|
+
|
|
355
|
+
Use **`createMediaStore`** and **`store.add()`** so errors surface when you build the store, not only during sync. Validation catches missing `version`, duplicate keys, and invalid URLs at build time.
|
|
356
|
+
|
|
357
|
+
See also: getting-started/SKILL.md § Common Mistakes
|
|
358
|
+
|
|
359
|
+
### HIGH Tension: Pre-signed URL TTL vs catalog size
|
|
360
|
+
|
|
361
|
+
Pre-signed URLs embedded in `resolveStore` need a TTL that covers the full download queue. For large catalogs (100+ assets), sign URLs with a generous TTL and set `expiresAt` on the store so the cache can fail fast with `STORE_EXPIRED` before a stale URL is fetched.
|
|
362
|
+
|
|
363
|
+
See also: authenticated-downloads/SKILL.md § Common Mistakes
|
|
364
|
+
|
|
365
|
+
## Cross-References
|
|
366
|
+
|
|
367
|
+
See also: getting-started/SKILL.md — Full main → preload → renderer wiring
|
|
368
|
+
See also: react-rendering/SKILL.md — Index definitions determine how hooks query content
|
|
369
|
+
See also: authenticated-downloads/SKILL.md — Auth strategies for asset sources
|