@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,357 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cache-configuration
|
|
3
|
+
description: >
|
|
4
|
+
createMediaCache options and targeted modifications: storagePath with
|
|
5
|
+
appPath and segments, devPassthrough mode, assetBaseUrl origin override,
|
|
6
|
+
onSyncFailure mode selection (serve-last-snapshot vs throw),
|
|
7
|
+
maxCacheBytes, reserveFreeBytes, staleDeleteAfterMs, syncHistoryLimit,
|
|
8
|
+
nested logging config with custom sinks (pino, logtape), log levels,
|
|
9
|
+
console formats, and MediaCacheLogEvent structure.
|
|
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/media-cache.ts"
|
|
17
|
+
- "rockhallweb/electron-offline-content:src/shared/types.ts"
|
|
18
|
+
- "rockhallweb/electron-offline-content:src/internal/validation.ts"
|
|
19
|
+
- "rockhallweb/electron-offline-content:src/main/storage-root-lock.ts"
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# Cache Configuration
|
|
23
|
+
|
|
24
|
+
This skill builds on getting-started. Read it first for full main → preload → renderer wiring.
|
|
25
|
+
|
|
26
|
+
## Setup
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { app } from "electron";
|
|
30
|
+
import { createMediaCache, createMediaStore } from "@rockhall/electron-offline-content/main";
|
|
31
|
+
import pino from "pino";
|
|
32
|
+
|
|
33
|
+
const logger = pino({ name: "media-cache" });
|
|
34
|
+
|
|
35
|
+
if (!app.requestSingleInstanceLock()) {
|
|
36
|
+
app.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const mediaCache = createMediaCache({
|
|
40
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
41
|
+
onSyncFailure: "serve-last-snapshot",
|
|
42
|
+
maxCacheBytes: 10 * 1024 * 1024 * 1024,
|
|
43
|
+
reserveFreeBytes: 1 * 1024 * 1024 * 1024,
|
|
44
|
+
logging: {
|
|
45
|
+
level: "info",
|
|
46
|
+
onLog: (entry) => {
|
|
47
|
+
logger[entry.level === "debug" ? "debug" : entry.level](entry, entry.event);
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
resolveStore: async () => {
|
|
51
|
+
const res = await fetch("https://cms.example.com/api/content");
|
|
52
|
+
const data = await res.json();
|
|
53
|
+
const store = createMediaStore();
|
|
54
|
+
for (const item of data.items) {
|
|
55
|
+
store.add(item.id, {
|
|
56
|
+
version: item.updatedAt,
|
|
57
|
+
mimeType: item.mimeType,
|
|
58
|
+
url: item.url,
|
|
59
|
+
metadata: item.metadata,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return store;
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Core Patterns
|
|
68
|
+
|
|
69
|
+
### Storage path configuration
|
|
70
|
+
|
|
71
|
+
`storagePath` maps to Electron's `app.getPath()` names. The `appPath` field accepts any `MediaCacheAppPath` value (`"userData"`, `"temp"`, `"documents"`, etc.). Use the `segments` array to add subdirectories — the package joins them with the platform path separator.
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
const mediaCache = createMediaCache({
|
|
75
|
+
storagePath: {
|
|
76
|
+
appPath: "userData",
|
|
77
|
+
segments: ["my-app", "offline-media"],
|
|
78
|
+
},
|
|
79
|
+
resolveStore: async () => store,
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
This resolves to `<userData>/my-app/offline-media/` on disk. Each segment becomes a directory level — never include path separators inside a segment string.
|
|
84
|
+
|
|
85
|
+
### Dev passthrough mode
|
|
86
|
+
|
|
87
|
+
`devPassthrough` skips downloads entirely and serves remote URLs directly. It defaults to `true` when `NODE_ENV === "development"` and `false` otherwise. Set it explicitly for clarity:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
const mediaCache = createMediaCache({
|
|
91
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
92
|
+
devPassthrough: true,
|
|
93
|
+
resolveStore: async () => store,
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
When `devPassthrough` is `true`:
|
|
98
|
+
|
|
99
|
+
- Downloads are skipped — assets load from their original remote URLs.
|
|
100
|
+
- `onSyncFailure` is overridden to `"throw"` — there is no snapshot to serve.
|
|
101
|
+
- Hook URLs return remote `http://` or `https://` URLs instead of `media://` URLs.
|
|
102
|
+
- `assetBaseUrl` becomes available for origin overrides.
|
|
103
|
+
|
|
104
|
+
### Storage limits
|
|
105
|
+
|
|
106
|
+
Three options control disk usage:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
const mediaCache = createMediaCache({
|
|
110
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
111
|
+
maxCacheBytes: 10 * 1024 * 1024 * 1024,
|
|
112
|
+
reserveFreeBytes: 1 * 1024 * 1024 * 1024,
|
|
113
|
+
staleDeleteAfterMs: 7 * 24 * 60 * 60 * 1000,
|
|
114
|
+
resolveStore: async () => store,
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
- `maxCacheBytes` — soft cap on total cache size in bytes. The sync pipeline skips new downloads when the cache exceeds this limit.
|
|
119
|
+
- `reserveFreeBytes` — minimum free disk space to preserve on the cache volume (default **1 GiB** when omitted; **`0`** disables). Still recommended to set an explicit value on kiosk hardware to match SSD capacity and OS needs.
|
|
120
|
+
- `staleDeleteAfterMs` — how long removed assets (no longer in the store) stay on disk before deletion. Defaults to 7 days (604,800,000 ms) when unset.
|
|
121
|
+
|
|
122
|
+
### Structured logging
|
|
123
|
+
|
|
124
|
+
Use the nested `logging` object for all log configuration. `logging.onLog` receives `MediaCacheLogEvent` objects with structured fields (`timestamp`, `level`, `event`, `service`, `component`, plus context-specific keys). Pipe them to any structured logger:
|
|
125
|
+
|
|
126
|
+
**pino:**
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import pino from "pino";
|
|
130
|
+
|
|
131
|
+
const logger = pino({ name: "media-cache" });
|
|
132
|
+
|
|
133
|
+
const mediaCache = createMediaCache({
|
|
134
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
135
|
+
logging: {
|
|
136
|
+
level: "info",
|
|
137
|
+
onLog: (entry) => {
|
|
138
|
+
logger[entry.level === "debug" ? "debug" : entry.level](entry, entry.event);
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
resolveStore: async () => store,
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**logtape:**
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { getLogger } from "@logtape/logtape";
|
|
149
|
+
|
|
150
|
+
const logger = getLogger(["media-cache"]);
|
|
151
|
+
|
|
152
|
+
const mediaCache = createMediaCache({
|
|
153
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
154
|
+
logging: {
|
|
155
|
+
level: "info",
|
|
156
|
+
onLog: (entry) => {
|
|
157
|
+
logger[entry.level](entry.event, entry);
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
resolveStore: async () => store,
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
When `logging.onLog` is omitted and `NODE_ENV !== "production"`, the package prints to `console`. Default `logging.level` is `"debug"` for the built-in console sink and `"info"` when a custom `logging.onLog` is provided.
|
|
165
|
+
|
|
166
|
+
Use `logging.format` only with the built-in console sink:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
const mediaCache = createMediaCache({
|
|
170
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
171
|
+
logging: {
|
|
172
|
+
level: "debug",
|
|
173
|
+
format: "json",
|
|
174
|
+
},
|
|
175
|
+
resolveStore: async () => store,
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Breaking migration:
|
|
180
|
+
|
|
181
|
+
- Old flat options `onLog`, `logLevel`, and `logFormat` were removed in `0.2.0`.
|
|
182
|
+
- Move them under `logging`.
|
|
183
|
+
- Do not combine `logging.format` with `logging.onLog`; custom sinks already receive structured events.
|
|
184
|
+
|
|
185
|
+
## Common Mistakes
|
|
186
|
+
|
|
187
|
+
### HIGH: Setting assetBaseUrl without devPassthrough
|
|
188
|
+
|
|
189
|
+
`assetBaseUrl` is only valid in dev passthrough mode. The constructor throws if `assetBaseUrl` is set while `devPassthrough` is `false` (or defaults to `false`).
|
|
190
|
+
|
|
191
|
+
Wrong:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
const mediaCache = createMediaCache({
|
|
195
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
196
|
+
assetBaseUrl: "http://localhost:3000",
|
|
197
|
+
resolveStore: async () => store,
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Correct:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
const mediaCache = createMediaCache({
|
|
205
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
206
|
+
devPassthrough: true,
|
|
207
|
+
assetBaseUrl: "http://localhost:3000",
|
|
208
|
+
resolveStore: async () => store,
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Source: media-cache.ts constructor
|
|
213
|
+
|
|
214
|
+
### HIGH: Using arbitrary file paths for storagePath
|
|
215
|
+
|
|
216
|
+
`storagePath` requires an object with `appPath` (an Electron `app.getPath` name) and optional `segments`. Raw string paths are not accepted.
|
|
217
|
+
|
|
218
|
+
Wrong:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
const mediaCache = createMediaCache({
|
|
222
|
+
storagePath: "/tmp/my-cache" as any,
|
|
223
|
+
resolveStore: async () => store,
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Correct:
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
const mediaCache = createMediaCache({
|
|
231
|
+
storagePath: { appPath: "temp", segments: ["my-app", "cache"] },
|
|
232
|
+
resolveStore: async () => store,
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Source: types.ts; validation.ts
|
|
237
|
+
|
|
238
|
+
### HIGH: Two cache instances targeting same storage root
|
|
239
|
+
|
|
240
|
+
Wrong:
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
const cacheA = createMediaCache({
|
|
244
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
245
|
+
resolveStore: async () => storeA,
|
|
246
|
+
});
|
|
247
|
+
const cacheB = createMediaCache({
|
|
248
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
249
|
+
resolveStore: async () => storeB,
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Correct:
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
const cacheA = createMediaCache({
|
|
257
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
258
|
+
resolveStore: async () => storeA,
|
|
259
|
+
});
|
|
260
|
+
const cacheB = createMediaCache({
|
|
261
|
+
storagePath: { appPath: "userData", segments: ["offline-media-b"] },
|
|
262
|
+
resolveStore: async () => storeB,
|
|
263
|
+
});
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
`MediaCache` acquires exclusive ownership of its storage directory via a lock file. A second instance targeting the same path throws `StorageOwnershipError`. Use `app.requestSingleInstanceLock()` to prevent duplicate processes.
|
|
267
|
+
|
|
268
|
+
Source: storage-root-lock.ts
|
|
269
|
+
|
|
270
|
+
### MEDIUM: assetBaseUrl with path or query string
|
|
271
|
+
|
|
272
|
+
`assetBaseUrl` must be an origin only — protocol, hostname, and optional port. Including a path, query string, hash, or credentials causes the constructor to throw.
|
|
273
|
+
|
|
274
|
+
Wrong:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
const mediaCache = createMediaCache({
|
|
278
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
279
|
+
devPassthrough: true,
|
|
280
|
+
assetBaseUrl: "http://localhost:3000/api/assets?v=2",
|
|
281
|
+
resolveStore: async () => store,
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Correct:
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
const mediaCache = createMediaCache({
|
|
289
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
290
|
+
devPassthrough: true,
|
|
291
|
+
assetBaseUrl: "http://localhost:3000",
|
|
292
|
+
resolveStore: async () => store,
|
|
293
|
+
});
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Source: media-cache.ts normalizeAssetBaseUrl
|
|
297
|
+
|
|
298
|
+
### MEDIUM: Path separators in storagePath segments
|
|
299
|
+
|
|
300
|
+
Each entry in the `segments` array becomes a single directory name. Segments must not contain `"/"` or `"\\"` — use separate array entries instead.
|
|
301
|
+
|
|
302
|
+
Wrong:
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
const mediaCache = createMediaCache({
|
|
306
|
+
storagePath: { appPath: "userData", segments: ["my-app/offline-media"] },
|
|
307
|
+
resolveStore: async () => store,
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Correct:
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
const mediaCache = createMediaCache({
|
|
315
|
+
storagePath: { appPath: "userData", segments: ["my-app", "offline-media"] },
|
|
316
|
+
resolveStore: async () => store,
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Source: validation.ts
|
|
321
|
+
|
|
322
|
+
### CRITICAL: devPassthrough left enabled in production (cross-skill: production-checklist)
|
|
323
|
+
|
|
324
|
+
In production Electron builds, `NODE_ENV` may be unset — which defaults `devPassthrough` to `false`. But if `NODE_ENV` is explicitly set to `"development"` in a deployed build, all downloads are silently skipped and the app serves only remote URLs. Set `devPassthrough: false` explicitly for production builds:
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
const mediaCache = createMediaCache({
|
|
328
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
329
|
+
devPassthrough: false,
|
|
330
|
+
resolveStore: async () => store,
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Source: media-cache.ts; types.ts
|
|
335
|
+
See also: production-checklist/SKILL.md § Common Mistakes
|
|
336
|
+
|
|
337
|
+
### HIGH Tension: Dev passthrough simplicity vs production correctness
|
|
338
|
+
|
|
339
|
+
`devPassthrough` makes development fast but changes behavior: downloads are skipped, `onSyncFailure` is overridden to `"throw"`, URLs are remote instead of `media://`. Code working in dev passthrough may break in production offline mode.
|
|
340
|
+
|
|
341
|
+
See also: production-checklist/SKILL.md § Common Mistakes
|
|
342
|
+
|
|
343
|
+
### HIGH Tension: Sync resilience vs stale content
|
|
344
|
+
|
|
345
|
+
`"serve-last-snapshot"` is safe for kiosks (never blank screen) but may serve outdated content indefinitely if syncs keep failing. `"throw"` is honest but can leave the UI empty.
|
|
346
|
+
|
|
347
|
+
See also: production-checklist/SKILL.md § Common Mistakes
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
See also: getting-started/SKILL.md — Initial createMediaCache setup
|
|
352
|
+
See also: production-checklist/SKILL.md — Go-live configuration audit
|
|
353
|
+
See also: authenticated-downloads/SKILL.md — Auth embedded in resolveStore
|
|
354
|
+
|
|
355
|
+
## References
|
|
356
|
+
|
|
357
|
+
- [Complete MediaCacheOptions reference](references/options.md)
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
# MediaCacheOptions Reference
|
|
2
|
+
|
|
3
|
+
Complete field-by-field reference for `createMediaCache(options)`.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { createMediaCache } from "@rockhall/electron-offline-content/main";
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## storagePath
|
|
12
|
+
|
|
13
|
+
| | |
|
|
14
|
+
| ------------ | ----------------------- |
|
|
15
|
+
| **Type** | `MediaCacheStoragePath` |
|
|
16
|
+
| **Required** | Yes |
|
|
17
|
+
| **Default** | — |
|
|
18
|
+
|
|
19
|
+
Root directory for cached assets, metadata database, and lock file. Built from an Electron `app.getPath` name plus optional subdirectory segments.
|
|
20
|
+
|
|
21
|
+
**Constraints:** `appPath` must be a valid `MediaCacheAppPath` value. `segments`, if provided, must not contain `"/"` or `"\\"` characters.
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
storagePath: { appPath: "userData", segments: ["my-app", "offline-media"] }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### MediaCacheStoragePath
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
interface MediaCacheStoragePath {
|
|
31
|
+
appPath: MediaCacheAppPath;
|
|
32
|
+
segments?: string[];
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### MediaCacheAppPath
|
|
37
|
+
|
|
38
|
+
Union of Electron `app.getPath` names:
|
|
39
|
+
|
|
40
|
+
| Value | Electron path |
|
|
41
|
+
| --------------- | ------------------------------------------------------------------------- |
|
|
42
|
+
| `"home"` | User home directory |
|
|
43
|
+
| `"appData"` | Per-user application data (`%APPDATA%` / `~/Library/Application Support`) |
|
|
44
|
+
| `"userData"` | `appData` + app name |
|
|
45
|
+
| `"sessionData"` | Session-specific data |
|
|
46
|
+
| `"temp"` | Temporary directory |
|
|
47
|
+
| `"exe"` | Executable path |
|
|
48
|
+
| `"module"` | `libchromiumcontent` library |
|
|
49
|
+
| `"desktop"` | Desktop directory |
|
|
50
|
+
| `"documents"` | Documents directory |
|
|
51
|
+
| `"downloads"` | Downloads directory |
|
|
52
|
+
| `"music"` | Music directory |
|
|
53
|
+
| `"pictures"` | Pictures directory |
|
|
54
|
+
| `"videos"` | Videos directory |
|
|
55
|
+
| `"recent"` | Recent files directory |
|
|
56
|
+
| `"logs"` | Log files directory |
|
|
57
|
+
| `"crashDumps"` | Crash dumps directory |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## devPassthrough
|
|
62
|
+
|
|
63
|
+
| | |
|
|
64
|
+
| ------------ | ----------------------------------------------------------- |
|
|
65
|
+
| **Type** | `boolean` |
|
|
66
|
+
| **Required** | No |
|
|
67
|
+
| **Default** | `true` when `NODE_ENV === "development"`, `false` otherwise |
|
|
68
|
+
|
|
69
|
+
Skips all downloads and serves remote URLs directly. Intended for development only.
|
|
70
|
+
|
|
71
|
+
**Constraints:** When `true`, downloads are skipped, `onSyncFailure` is overridden to `"throw"`, and hook URLs return remote `http://` or `https://` URLs instead of `media://`.
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
devPassthrough: true;
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## assetBaseUrl
|
|
80
|
+
|
|
81
|
+
| | |
|
|
82
|
+
| ------------ | ---------------- |
|
|
83
|
+
| **Type** | `string \| null` |
|
|
84
|
+
| **Required** | No |
|
|
85
|
+
| **Default** | `null` |
|
|
86
|
+
|
|
87
|
+
Origin override for asset URLs in dev passthrough mode. Replaces the origin of each asset’s `url` (the top-level string on every store entry).
|
|
88
|
+
|
|
89
|
+
**Constraints:** Must be an origin only (protocol + hostname + optional port). Must not include path, query, hash, or credentials. Requires `devPassthrough: true` — constructor throws if set while `devPassthrough` is `false`.
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
assetBaseUrl: "http://localhost:3000";
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## maxCacheBytes
|
|
98
|
+
|
|
99
|
+
| | |
|
|
100
|
+
| ------------ | --------- |
|
|
101
|
+
| **Type** | `number` |
|
|
102
|
+
| **Required** | No |
|
|
103
|
+
| **Default** | Unlimited |
|
|
104
|
+
|
|
105
|
+
Soft cap on total cache size in bytes. The sync pipeline skips new downloads when the cache exceeds this limit.
|
|
106
|
+
|
|
107
|
+
**Constraints:** Must be a positive integer when provided.
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
maxCacheBytes: 10 * 1024 * 1024 * 1024; // 10 GB
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## reserveFreeBytes
|
|
116
|
+
|
|
117
|
+
| | |
|
|
118
|
+
| ------------ | ----------------------------------- |
|
|
119
|
+
| **Type** | `number` |
|
|
120
|
+
| **Required** | No |
|
|
121
|
+
| **Default** | `1073741824` (1 GiB, `1024³` bytes) |
|
|
122
|
+
|
|
123
|
+
Minimum free disk space to preserve in bytes on the volume that holds the cache. Sync refuses work when projected free space would drop below this value. **`0`** disables the reservation (legacy behavior when the option was omitted).
|
|
124
|
+
|
|
125
|
+
**Constraints:** Must be a non-negative integer when provided. Omit the option to use the default; set **`0`** explicitly to allow filling the volume up to other limits.
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
reserveFreeBytes: 1 * 1024 * 1024 * 1024; // 1 GB
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## staleDeleteAfterMs
|
|
134
|
+
|
|
135
|
+
| | |
|
|
136
|
+
| ------------ | -------------------- |
|
|
137
|
+
| **Type** | `number` |
|
|
138
|
+
| **Required** | No |
|
|
139
|
+
| **Default** | `604800000` (7 days) |
|
|
140
|
+
|
|
141
|
+
Milliseconds to retain assets that are no longer present in the store before deleting them from disk.
|
|
142
|
+
|
|
143
|
+
**Constraints:** Must be a non-negative integer when provided.
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
staleDeleteAfterMs: 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## onSyncFailure
|
|
152
|
+
|
|
153
|
+
| | |
|
|
154
|
+
| ------------ | ---------------------------------- |
|
|
155
|
+
| **Type** | `"serve-last-snapshot" \| "throw"` |
|
|
156
|
+
| **Required** | No |
|
|
157
|
+
| **Default** | `"serve-last-snapshot"` |
|
|
158
|
+
|
|
159
|
+
Behavior when a sync fails.
|
|
160
|
+
|
|
161
|
+
- `"serve-last-snapshot"` — continue serving the most recently committed store generation. Safe for kiosks (prevents blank screens) but may serve stale content.
|
|
162
|
+
- `"throw"` — propagate the sync error. Honest, but can leave the UI empty if no prior generation exists.
|
|
163
|
+
|
|
164
|
+
**Constraints:** Overridden to `"throw"` when `devPassthrough` is `true`.
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
onSyncFailure: "serve-last-snapshot";
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## syncHistoryLimit
|
|
173
|
+
|
|
174
|
+
| | |
|
|
175
|
+
| ------------ | ---------------------------- |
|
|
176
|
+
| **Type** | `number` |
|
|
177
|
+
| **Required** | No |
|
|
178
|
+
| **Default** | Package default (see source) |
|
|
179
|
+
|
|
180
|
+
Maximum number of sync generation records retained in the metadata database. Older generations are pruned after successful commits.
|
|
181
|
+
|
|
182
|
+
**Constraints:** Must be a positive integer when provided.
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
syncHistoryLimit: 5;
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## logging
|
|
191
|
+
|
|
192
|
+
| | |
|
|
193
|
+
| ------------ | ---------------------------------- |
|
|
194
|
+
| **Type** | `MediaCacheLoggingOptions` |
|
|
195
|
+
| **Required** | No |
|
|
196
|
+
| **Default** | Built-in console sink when omitted |
|
|
197
|
+
|
|
198
|
+
Nested logging configuration for either the built-in console sink or a custom structured logger.
|
|
199
|
+
|
|
200
|
+
**Constraints:** `format` cannot be combined with `onLog` in the same object. Use `format` only for the built-in console sink.
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
logging: {
|
|
204
|
+
level: "info",
|
|
205
|
+
onLog: (entry) => {
|
|
206
|
+
logger.info(entry, entry.event);
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
### MediaCacheLoggingOptions
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
type MediaCacheLoggingOptions =
|
|
217
|
+
| MediaCacheCustomLoggingOptions
|
|
218
|
+
| {
|
|
219
|
+
level?: "debug" | "info" | "warn" | "error";
|
|
220
|
+
format?: never;
|
|
221
|
+
onLog: (entry: MediaCacheLogEvent) => void;
|
|
222
|
+
}
|
|
223
|
+
| {
|
|
224
|
+
level?: "debug" | "info" | "warn" | "error";
|
|
225
|
+
format?: "english" | "json";
|
|
226
|
+
onLog?: undefined;
|
|
227
|
+
};
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
`MediaCacheLoggingOptions` is a discriminated union. The custom-sink branch is represented by `MediaCacheCustomLoggingOptions` in `types.ts`, and TypeScript rejects `format` when `onLog` is present because that branch uses `format?: never`.
|
|
231
|
+
|
|
232
|
+
### `logging.level`
|
|
233
|
+
|
|
234
|
+
| | |
|
|
235
|
+
| ------------ | ---------------------------------------------------- |
|
|
236
|
+
| **Type** | `"debug" \| "info" \| "warn" \| "error"` |
|
|
237
|
+
| **Required** | No |
|
|
238
|
+
| **Default** | `"debug"` (console sink) / `"info"` (custom `onLog`) |
|
|
239
|
+
|
|
240
|
+
Minimum log level emitted. Events below this level are discarded.
|
|
241
|
+
|
|
242
|
+
**Constraints:** None.
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
logging: {
|
|
246
|
+
level: "info";
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### `logging.format`
|
|
251
|
+
|
|
252
|
+
| | |
|
|
253
|
+
| ------------ | --------------------- |
|
|
254
|
+
| **Type** | `"english" \| "json"` |
|
|
255
|
+
| **Required** | No |
|
|
256
|
+
| **Default** | `"english"` |
|
|
257
|
+
|
|
258
|
+
Format for the built-in console sink. `"english"` produces human-readable lines. `"json"` produces one JSON object per line.
|
|
259
|
+
|
|
260
|
+
**Constraints:** Invalid when `logging.onLog` is provided.
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
logging: {
|
|
264
|
+
format: "json";
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### `logging.onLog`
|
|
269
|
+
|
|
270
|
+
| | |
|
|
271
|
+
| ------------ | ----------------------------------------------------------------- |
|
|
272
|
+
| **Type** | `(entry: MediaCacheLogEvent) => void` |
|
|
273
|
+
| **Required** | No |
|
|
274
|
+
| **Default** | Built-in console sink (disabled when `NODE_ENV === "production"`) |
|
|
275
|
+
|
|
276
|
+
Custom log handler. Receives structured `MediaCacheLogEvent` objects for every log emission. When provided, the built-in console sink is disabled.
|
|
277
|
+
|
|
278
|
+
**Constraints:** The handler is called synchronously on the main thread. Cannot be combined with `logging.format`.
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
logging: {
|
|
282
|
+
onLog: (entry) => {
|
|
283
|
+
logger.info(entry, entry.event);
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### MediaCacheLogEvent
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
interface MediaCacheLogEvent {
|
|
292
|
+
[key: string]: JsonValue | undefined;
|
|
293
|
+
timestamp: string; // ISO 8601
|
|
294
|
+
level: MediaCacheLogLevel; // "debug" | "info" | "warn" | "error"
|
|
295
|
+
event: string; // e.g. "sync.asset.downloaded", "protocol.request.served"
|
|
296
|
+
service: string; // package identifier
|
|
297
|
+
component: string; // e.g. "sync", "protocol", "database"
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Additional context-specific keys vary by event (e.g. `assetKey`, `bytesDownloaded`, `durationMs`). All values are JSON-serializable.
|
|
302
|
+
|
|
303
|
+
### Migration
|
|
304
|
+
|
|
305
|
+
Flat logging options were removed in `0.2.0`.
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
// Before
|
|
309
|
+
createMediaCache({
|
|
310
|
+
logLevel: "info",
|
|
311
|
+
onLog: (entry) => logger.info(entry, entry.event),
|
|
312
|
+
resolveStore,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// After
|
|
316
|
+
createMediaCache({
|
|
317
|
+
logging: {
|
|
318
|
+
level: "info",
|
|
319
|
+
onLog: (entry) => logger.info(entry, entry.event),
|
|
320
|
+
},
|
|
321
|
+
resolveStore,
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## resolveStore
|
|
328
|
+
|
|
329
|
+
| | |
|
|
330
|
+
| ------------ | ----------------------------------------- |
|
|
331
|
+
| **Type** | `() => Promise<MediaStore> \| MediaStore` |
|
|
332
|
+
| **Required** | Yes |
|
|
333
|
+
| **Default** | — |
|
|
334
|
+
|
|
335
|
+
Function called at the start of each sync cycle to produce the current asset store. May be async. The return value is normalized and validated before the download pipeline begins.
|
|
336
|
+
|
|
337
|
+
**Constraints:** Must return a valid `MediaStore` built with `createMediaStore()`. See store-authoring/SKILL.md for structure and validation rules.
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
import { createMediaStore } from "@rockhall/electron-offline-content/main";
|
|
341
|
+
|
|
342
|
+
resolveStore: async () => {
|
|
343
|
+
const res = await fetch("https://cms.example.com/api/content");
|
|
344
|
+
const data = await res.json();
|
|
345
|
+
const store = createMediaStore();
|
|
346
|
+
for (const item of data.items) {
|
|
347
|
+
store.add(item.id, {
|
|
348
|
+
version: item.updatedAt,
|
|
349
|
+
mimeType: item.mimeType,
|
|
350
|
+
url: item.url,
|
|
351
|
+
metadata: item.metadata,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
return store;
|
|
355
|
+
};
|
|
356
|
+
```
|