@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
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Breaking changes
|
|
6
|
+
|
|
7
|
+
**Store API (replaces Manifest API)**
|
|
8
|
+
|
|
9
|
+
- `defineManifest()`, `defineItem()`, `defineAsset()` removed. Replaced by `createMediaStore()`, `store.defineIndex()`, `store.add()`.
|
|
10
|
+
- `namespacesFromEntries()`, `itemsFromEntries()`, `assetsFromEntries()` removed.
|
|
11
|
+
- `resolveManifest` option replaced by `resolveStore` (returns a `MediaStore` or `Promise<MediaStore>`).
|
|
12
|
+
- `resolveAssetRequest` option removed entirely; embed presigned (or otherwise auth-bearing) URLs in each asset’s flat `url` field during `resolveStore()`.
|
|
13
|
+
- **`source: { url, method?, headers? }` → flat `url: string`:** The nested `source` object is removed. `MediaAssetInput` and `FlatManifestAsset` require a top-level **`url: string`**. **`MediaRemoteSource` is removed.** Use `store.add(key, { url: "https://..." })` instead of `store.add(key, { source: { url: "https://..." } })`. Custom HTTP methods and per-request headers are not supported; use presigned URLs (or URLs that encode credentials in the query string).
|
|
14
|
+
- Hierarchical `namespace/item/asset` model replaced by flat `assetKey` model with user-defined secondary indexes.
|
|
15
|
+
- Per-asset versioning: each asset has its own `version` string (no longer inherited from parent item).
|
|
16
|
+
- Asset keys are hashed for storage identity (SHA-256, first 16 hex characters). `store.add()`, `getAsset()`, and `useMediaAsset()` accept `string | readonly string[]` (the `AssetKeyInput` type). There is no validation of key characters or shape beyond non-empty strings or non-empty string segments.
|
|
17
|
+
- `ResolvedMediaAsset.key` is the stable hash; the original human-readable key is available as `displayKey`.
|
|
18
|
+
|
|
19
|
+
**Error renames:**
|
|
20
|
+
|
|
21
|
+
- `ManifestValidationError` → `StoreValidationError` (code: `STORE_VALIDATION_ERROR`)
|
|
22
|
+
- `ManifestExpiredError` → `StoreExpiredError` (code: `STORE_EXPIRED`)
|
|
23
|
+
|
|
24
|
+
**Main process API:**
|
|
25
|
+
|
|
26
|
+
- `getItem(namespace, id)` → `getAsset(key)`
|
|
27
|
+
- `listNamespace(namespace, pagination?)` → `listByIndex(indexName, value, pagination?)`
|
|
28
|
+
- `listNamespaceTree(prefix, pagination?)` → removed (use indexes instead)
|
|
29
|
+
|
|
30
|
+
**React hooks:**
|
|
31
|
+
|
|
32
|
+
- `useMedia({ kind: "item", ... })` and `useMedia({ kind: "list", ... })` → removed
|
|
33
|
+
- New hooks: `useMediaAsset(key)`, `useMediaByIndex(indexName, value, options?)`
|
|
34
|
+
- `useFileStemMatch` no longer accepts a `namespace` option
|
|
35
|
+
|
|
36
|
+
**Types:**
|
|
37
|
+
|
|
38
|
+
- Removed: `MediaCacheManifest`, `MediaNamespaceValue`, `MediaItemValue`, `MediaAssetValue`, `ResolvedMediaContentItem`, `MediaRemoteSource`
|
|
39
|
+
- New: `MediaAssetInput`, `FlatManifest`, `FlatManifestAsset`, `IndexDefinition`
|
|
40
|
+
- `ResolvedMediaAsset` now has: `key` (hash), `displayKey` (original key), `version`, `kind: MediaKind`, `mimeType`, `indexes: Record<string, string>`, `metadata: Record<string, unknown>`
|
|
41
|
+
- `FileStemMatch` now has `asset: ResolvedMediaAsset` instead of `item: ResolvedMediaContentItem`
|
|
42
|
+
- `SyncProgress.phase` includes `"resolving-store"` instead of `"resolving-manifest"`
|
|
43
|
+
|
|
44
|
+
**New exports:**
|
|
45
|
+
|
|
46
|
+
- `createMediaStore`, `MediaStore`, `MediaIndex`, `mediaKindFromMime`
|
|
47
|
+
- Types: `AssetKeyInput`, `IndexTag`
|
|
48
|
+
|
|
49
|
+
**Protocol URL:**
|
|
50
|
+
|
|
51
|
+
- Old: `media://{namespace}/{itemId}/{assetId}`
|
|
52
|
+
- New: `media://asset/{encodedAssetKey}`
|
|
53
|
+
|
|
54
|
+
**IPC channels:**
|
|
55
|
+
|
|
56
|
+
- `getItem`, `listNamespace`, `listNamespaceTree` → `getAsset`, `listByIndex`
|
|
57
|
+
|
|
58
|
+
### Migration
|
|
59
|
+
|
|
60
|
+
**Store creation**
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
// Before — hierarchical manifest with defineManifest / defineItem / defineAsset
|
|
64
|
+
import {
|
|
65
|
+
defineAsset,
|
|
66
|
+
defineItem,
|
|
67
|
+
defineManifest,
|
|
68
|
+
itemsFromEntries,
|
|
69
|
+
} from "@rockhall/electron-offline-content/main";
|
|
70
|
+
|
|
71
|
+
const manifest = defineManifest({
|
|
72
|
+
namespaces: {
|
|
73
|
+
videos: {
|
|
74
|
+
items: itemsFromEntries(data.videos, (v) => [
|
|
75
|
+
v.slug,
|
|
76
|
+
defineItem({
|
|
77
|
+
version: v.updatedAt,
|
|
78
|
+
kind: "video",
|
|
79
|
+
assets: {
|
|
80
|
+
main: defineAsset({
|
|
81
|
+
role: "primary",
|
|
82
|
+
kind: "video",
|
|
83
|
+
source: { url: v.videoUrl },
|
|
84
|
+
}),
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
87
|
+
]),
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// After — flat store with createMediaStore / defineIndex / store.add
|
|
93
|
+
import { createMediaStore } from "@rockhall/electron-offline-content/main";
|
|
94
|
+
|
|
95
|
+
const store = createMediaStore();
|
|
96
|
+
const category = store.defineIndex("category");
|
|
97
|
+
|
|
98
|
+
for (const v of data.videos) {
|
|
99
|
+
store.add(["video", v.slug], {
|
|
100
|
+
version: v.updatedAt,
|
|
101
|
+
mimeType: "video/mp4",
|
|
102
|
+
url: v.videoUrl,
|
|
103
|
+
metadata: { title: v.title, category: "videos" },
|
|
104
|
+
indexes: [category("videos")],
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Main process wiring**
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
// Before
|
|
113
|
+
const mediaCache = createMediaCache({
|
|
114
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
115
|
+
resolveManifest: async () => {
|
|
116
|
+
const res = await fetch("https://cms.example.com/api/content");
|
|
117
|
+
return res.json();
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// After
|
|
122
|
+
const mediaCache = createMediaCache({
|
|
123
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
124
|
+
resolveStore: async () => {
|
|
125
|
+
const res = await fetch("https://cms.example.com/api/content");
|
|
126
|
+
const data = await res.json();
|
|
127
|
+
const store = createMediaStore();
|
|
128
|
+
const category = store.defineIndex("category");
|
|
129
|
+
for (const item of data.items) {
|
|
130
|
+
store.add(["items", item.id], {
|
|
131
|
+
version: item.updatedAt,
|
|
132
|
+
mimeType: item.mimeType,
|
|
133
|
+
url: item.url,
|
|
134
|
+
metadata: item.metadata,
|
|
135
|
+
indexes: [category("catalog")],
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return store;
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**React hooks**
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
// Before — useMedia with kind discriminator
|
|
147
|
+
const item = useMedia({ kind: "item", namespace: "videos", id: "welcome" });
|
|
148
|
+
const list = useMedia({ kind: "list", namespace: "videos", limit: 20 });
|
|
149
|
+
// item.data.assetsByRole.primary?.url
|
|
150
|
+
// list.data.items.map(...)
|
|
151
|
+
|
|
152
|
+
// After — useMediaAsset / useMediaByIndex (key may be a string or string[]; must match resolveStore)
|
|
153
|
+
const asset = useMediaAsset(["video", "welcome"]);
|
|
154
|
+
const videos = useMediaByIndex("category", "videos", { limit: 20 });
|
|
155
|
+
// asset.data?.url
|
|
156
|
+
// videos.data?.items.map(...)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Auth (resolveAssetRequest → presigned `url` in resolveStore)**
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
// Before — resolveAssetRequest callback signed each download
|
|
163
|
+
const mediaCache = createMediaCache({
|
|
164
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
165
|
+
resolveAssetRequest: async (ctx) => ({
|
|
166
|
+
url: await getSignedUrl(s3, new GetObjectCommand({ Bucket: "b", Key: ctx.asset.source.url }), {
|
|
167
|
+
expiresIn: 3600,
|
|
168
|
+
}),
|
|
169
|
+
}),
|
|
170
|
+
resolveManifest: async () => fetchManifest(),
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// After — embed signed URLs in each asset’s flat url during resolveStore()
|
|
174
|
+
const mediaCache = createMediaCache({
|
|
175
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
176
|
+
resolveStore: async () => {
|
|
177
|
+
const store = createMediaStore();
|
|
178
|
+
for (const item of await fetchCatalog()) {
|
|
179
|
+
const signedUrl = await getSignedUrl(
|
|
180
|
+
s3,
|
|
181
|
+
new GetObjectCommand({ Bucket: "b", Key: item.key }),
|
|
182
|
+
{ expiresIn: 3600 },
|
|
183
|
+
);
|
|
184
|
+
store.add(["assets", item.id], {
|
|
185
|
+
version: item.revision,
|
|
186
|
+
mimeType: "video/mp4",
|
|
187
|
+
url: signedUrl,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return store;
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## 0.3.0
|
|
196
|
+
|
|
197
|
+
### Breaking changes
|
|
198
|
+
|
|
199
|
+
**React (`@rockhall/electron-offline-content/react`)**
|
|
200
|
+
|
|
201
|
+
- Replaced `useMediaItem`, `useMediaItems`, `useMediaNamespace`, and `useMediaNamespaceTree` with a single discriminated hook, `useMedia({ kind: "item", ... })` and `useMedia({ kind: "list", ... })`.
|
|
202
|
+
- Renamed `useMediaCacheBridge` to `useMediaBridge` (same bridge surface, plus shared `phase`, `status`, and aggregated `errors`).
|
|
203
|
+
- `useMediaCacheErrors()` no longer accepts hook results; it aggregates sync, status, and all active queries for the current `MediaCacheProvider`.
|
|
204
|
+
- `useMediaCacheStatus()` now exposes a top-level `phase` field (cache phase or `"loading"` before the first status snapshot).
|
|
205
|
+
|
|
206
|
+
**Main (`@rockhall/electron-offline-content/main`)**
|
|
207
|
+
|
|
208
|
+
- Manifest authoring uses `Record`-keyed maps: `namespaces`, per-namespace `items`, and per-item `assets` are keyed by stable id strings; item and asset values no longer carry redundant `id` fields.
|
|
209
|
+
- Renamed `defineManifestItem` → `defineItem` and `defineManifestAsset` → `defineAsset`.
|
|
210
|
+
- Added `namespacesFromEntries`, `itemsFromEntries`, and `assetsFromEntries` to build those records from arrays with validation and duplicate-key checks.
|
|
211
|
+
- Public types follow the new value shapes (`MediaNamespaceValue`, `MediaItemValue`, `MediaAssetValue`, and related sync types). See published `.d.ts` files for the full surface.
|
|
212
|
+
|
|
213
|
+
### Migration
|
|
214
|
+
|
|
215
|
+
**React**
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
// Item lookup
|
|
219
|
+
// Before
|
|
220
|
+
const item = useMediaItem("space", "hubble-cosmos");
|
|
221
|
+
|
|
222
|
+
// After
|
|
223
|
+
const item = useMedia({ kind: "item", namespace: "space", id: "hubble-cosmos" });
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
// Namespace list or tree
|
|
228
|
+
// Before
|
|
229
|
+
const flat = useMediaItems("videos", { limit: 20 });
|
|
230
|
+
const tree = useMediaItems("courses", { recursive: true, limit: 40 });
|
|
231
|
+
// or (deprecated)
|
|
232
|
+
const flat = useMediaNamespace("videos", { limit: 20 });
|
|
233
|
+
const tree = useMediaNamespaceTree("courses", { limit: 40 });
|
|
234
|
+
|
|
235
|
+
// After
|
|
236
|
+
const flat = useMedia({ kind: "list", namespace: "videos", limit: 20 });
|
|
237
|
+
const tree = useMedia({
|
|
238
|
+
kind: "list",
|
|
239
|
+
namespace: "courses",
|
|
240
|
+
recursive: true,
|
|
241
|
+
limit: 40,
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
// Bridge + errors
|
|
247
|
+
// Before
|
|
248
|
+
const bridge = useMediaCacheBridge();
|
|
249
|
+
const errors = useMediaCacheErrors(status, item, list);
|
|
250
|
+
|
|
251
|
+
// After (errors on the bridge match `useMediaCacheErrors()`)
|
|
252
|
+
const { syncNow, phase, errors } = useMediaBridge();
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Manifest**
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
// Before (array-shaped namespaces / items / assets with inline ids)
|
|
259
|
+
defineManifest({
|
|
260
|
+
namespaces: [
|
|
261
|
+
{
|
|
262
|
+
key: "videos",
|
|
263
|
+
items: [
|
|
264
|
+
{
|
|
265
|
+
id: "clip-1",
|
|
266
|
+
version: "1",
|
|
267
|
+
kind: "video",
|
|
268
|
+
assets: [{ id: "main", role: "primary", kind: "video", source: { url } }],
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
},
|
|
272
|
+
],
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// After (records keyed by id)
|
|
276
|
+
defineManifest({
|
|
277
|
+
namespaces: {
|
|
278
|
+
videos: {
|
|
279
|
+
items: {
|
|
280
|
+
"clip-1": defineItem({
|
|
281
|
+
version: "1",
|
|
282
|
+
kind: "video",
|
|
283
|
+
assets: {
|
|
284
|
+
main: defineAsset({ role: "primary", kind: "video", source: { url } }),
|
|
285
|
+
},
|
|
286
|
+
}),
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Prefer `itemsFromEntries` / `assetsFromEntries` / `namespacesFromEntries` when building from CMS or API arrays (see README).
|
|
294
|
+
|
|
295
|
+
### Changed
|
|
296
|
+
|
|
297
|
+
- **`reserveFreeBytes` default:** When the option is omitted, offline sync now preserves **1 GiB** (`1024³` bytes) of free space on the cache volume instead of treating the reserve as zero. Set **`reserveFreeBytes: 0`** to restore the previous behavior.
|
|
298
|
+
|
|
299
|
+
## 0.2.0
|
|
300
|
+
|
|
301
|
+
### Changed
|
|
302
|
+
|
|
303
|
+
- Breaking: removed the flat `onLog`, `logLevel`, and `logFormat` `MediaCacheOptions` fields in favor of a nested `logging` object.
|
|
304
|
+
- `logging` is now a discriminated configuration shape: use `logging.onLog` for a custom structured sink, or `logging.format` for the built-in console sink, but not both together.
|
|
305
|
+
|
|
306
|
+
### Migration
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
// Before
|
|
310
|
+
createMediaCache({
|
|
311
|
+
logLevel: "info",
|
|
312
|
+
onLog: (entry) => logger.info(entry, entry.event),
|
|
313
|
+
resolveManifest,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// After
|
|
317
|
+
createMediaCache({
|
|
318
|
+
logging: {
|
|
319
|
+
level: "info",
|
|
320
|
+
onLog: (entry) => logger.info(entry, entry.event),
|
|
321
|
+
},
|
|
322
|
+
resolveManifest,
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
// Before
|
|
328
|
+
createMediaCache({
|
|
329
|
+
logLevel: "debug",
|
|
330
|
+
logFormat: "json",
|
|
331
|
+
resolveManifest,
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// After
|
|
335
|
+
createMediaCache({
|
|
336
|
+
logging: {
|
|
337
|
+
level: "debug",
|
|
338
|
+
format: "json",
|
|
339
|
+
},
|
|
340
|
+
resolveManifest,
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
`logging.format` is only for the built-in console sink and cannot be used with `logging.onLog`.
|
|
345
|
+
|
|
346
|
+
## 0.1.3
|
|
347
|
+
|
|
348
|
+
### Added
|
|
349
|
+
|
|
350
|
+
- Manifest expiration support: optional `expiresAt` field causes sync to fail fast with `ManifestExpiredError` once pre-signed URLs are past their TTL.
|
|
351
|
+
- Cursor worktree helpers (`pnpm worktree:new`, `worktree:open`, `worktree:list`, `worktree:prune`) for package development workflows.
|
|
352
|
+
|
|
353
|
+
## 0.1.2
|
|
354
|
+
|
|
355
|
+
### Fixed
|
|
356
|
+
|
|
357
|
+
- Clean up orphaned staged generations during startup so interrupted syncs do not leave behind unused SQLite rows or blob files.
|
|
358
|
+
|
|
359
|
+
## 0.1.1
|
|
360
|
+
|
|
361
|
+
### Added
|
|
362
|
+
|
|
363
|
+
- AI agent skill specifications in `skills/_artifacts/` — domain map, skill spec, and skill tree covering getting-started, store-authoring, cache-configuration, react-rendering, authenticated-downloads, and production-checklist workflows.
|
|
364
|
+
- `@tanstack/intent` dev dependency for skill tooling.
|
|
365
|
+
|
|
366
|
+
### Changed
|
|
367
|
+
|
|
368
|
+
- Minor README formatting adjustments around markdown tables.
|
|
369
|
+
|
|
370
|
+
## 0.1.0
|
|
371
|
+
|
|
372
|
+
Initial release.
|
|
373
|
+
|
|
374
|
+
- Full-catalog manifest sync with SQLite metadata index.
|
|
375
|
+
- Disk-backed binary asset cache with atomic downloads.
|
|
376
|
+
- Privileged `media://` protocol for renderer-safe local URLs.
|
|
377
|
+
- Preload bridge and React hooks for renderer access.
|
|
378
|
+
- Dev passthrough mode for local development without downloading assets.
|
|
379
|
+
- Authenticated asset downloads via `resolveAssetRequest` or static source headers.
|
|
380
|
+
- Storage limits (`maxCacheBytes`, `reserveFreeBytes`, `staleDeleteAfterMs`).
|
|
381
|
+
- Structured logging with pluggable sinks.
|
|
382
|
+
- Sync failure resilience (`serve-last-snapshot` / `throw`).
|
|
383
|
+
- Namespace-based content organization with dot-delimited hierarchies.
|
|
384
|
+
- Two example apps (local fixtures, NASA SVS).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rock & Roll Hall of Fame
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|