@empty-sekai/sekai-custom-profile-sdk 0.3.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/LICENSE +235 -0
- package/LICENSE-EXCEPTION +32 -0
- package/NOTICE +11 -0
- package/README.en.md +456 -0
- package/README.md +458 -0
- package/dist/allium_renderer_wasm.js +14 -0
- package/dist/allium_renderer_wasm.wasm +0 -0
- package/dist/authoring.d.ts +14 -0
- package/dist/authoring.js +34 -0
- package/dist/cache/glyphPersistentCache.d.ts +145 -0
- package/dist/cache/glyphPersistentCache.js +346 -0
- package/dist/cache/indexedDbGlyphRecordStore.d.ts +26 -0
- package/dist/cache/indexedDbGlyphRecordStore.js +214 -0
- package/dist/cache/sessionImageResourceCache.d.ts +39 -0
- package/dist/cache/sessionImageResourceCache.js +156 -0
- package/dist/emscripten.d.ts +11 -0
- package/dist/emscripten.js +1 -0
- package/dist/fontProvider.d.ts +36 -0
- package/dist/fontProvider.js +84 -0
- package/dist/fontSdfAtlas.d.ts +92 -0
- package/dist/fontSdfAtlas.js +296 -0
- package/dist/gpu/browserSemanticResources.d.ts +64 -0
- package/dist/gpu/browserSemanticResources.js +232 -0
- package/dist/gpu/generalTextRenderPlacement.d.ts +8 -0
- package/dist/gpu/generalTextRenderPlacement.js +131 -0
- package/dist/gpu/previewTransformTextureLayout.d.ts +1 -0
- package/dist/gpu/previewTransformTextureLayout.js +12 -0
- package/dist/gpu/semanticCommandGeometry.d.ts +21 -0
- package/dist/gpu/semanticCommandGeometry.js +226 -0
- package/dist/gpu/semanticCommandPlanner.d.ts +131 -0
- package/dist/gpu/semanticCommandPlanner.js +203 -0
- package/dist/gpu/semanticTextGlyphBridge.d.ts +3 -0
- package/dist/gpu/semanticTextGlyphBridge.js +22 -0
- package/dist/gpu/semanticWebglSceneRenderer.d.ts +70 -0
- package/dist/gpu/semanticWebglSceneRenderer.js +211 -0
- package/dist/gpu/slotRanges.d.ts +5 -0
- package/dist/gpu/slotRanges.js +14 -0
- package/dist/gpu/webglSdfAtlasTexture.d.ts +17 -0
- package/dist/gpu/webglSdfAtlasTexture.js +72 -0
- package/dist/gpu/webglSdfGlyphPipeline.d.ts +26 -0
- package/dist/gpu/webglSdfGlyphPipeline.js +267 -0
- package/dist/gpu/webglSemanticCommandExecutor.d.ts +79 -0
- package/dist/gpu/webglSemanticCommandExecutor.js +686 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +8 -0
- package/dist/interaction/numericTextRegions.d.ts +46 -0
- package/dist/interaction/numericTextRegions.js +44 -0
- package/dist/localizationProvider.d.ts +33 -0
- package/dist/localizationProvider.js +74 -0
- package/dist/originPrebuiltSdfAtlasPackage.d.ts +39 -0
- package/dist/originPrebuiltSdfAtlasPackage.js +316 -0
- package/dist/prebuiltSdfAtlas.d.ts +36 -0
- package/dist/prebuiltSdfAtlas.js +261 -0
- package/dist/protocol.d.ts +456 -0
- package/dist/protocol.js +1 -0
- package/dist/renderer.d.ts +218 -0
- package/dist/renderer.js +641 -0
- package/dist/resourceProvider.d.ts +24 -0
- package/dist/resourceProvider.js +41 -0
- package/dist/telemetry/rendererTelemetry.d.ts +158 -0
- package/dist/telemetry/rendererTelemetry.js +232 -0
- package/dist/third-party/freetype/FTL.txt +169 -0
- package/dist/types/atlas.d.ts +90 -0
- package/dist/types/atlas.js +1 -0
- package/dist/types/authoring.d.ts +81 -0
- package/dist/types/authoring.js +1 -0
- package/dist/types/core.d.ts +128 -0
- package/dist/types/core.js +1 -0
- package/dist/types/freeType.d.ts +54 -0
- package/dist/types/freeType.js +13 -0
- package/dist/types/glyph.d.ts +33 -0
- package/dist/types/glyph.js +1 -0
- package/dist/types/layout.d.ts +25 -0
- package/dist/types/layout.js +1 -0
- package/dist/types/text.d.ts +28 -0
- package/dist/types/text.js +1 -0
- package/dist/worker/glyphWorkScheduler.d.ts +29 -0
- package/dist/worker/glyphWorkScheduler.js +107 -0
- package/dist/worker-client.d.ts +114 -0
- package/dist/worker-client.js +413 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +708 -0
- package/package.json +46 -0
- package/src/atlas.rs +652 -0
- package/src/authoring.ts +42 -0
- package/src/authoring_runtime.rs +303 -0
- package/src/cache/glyphPersistentCache.ts +463 -0
- package/src/cache/indexedDbGlyphRecordStore.ts +238 -0
- package/src/cache/sessionImageResourceCache.ts +186 -0
- package/src/edt.rs +97 -0
- package/src/emscripten.ts +17 -0
- package/src/fontProvider.ts +127 -0
- package/src/fontSdfAtlas.ts +431 -0
- package/src/geometry.rs +149 -0
- package/src/glyph_plan.rs +226 -0
- package/src/gpu/browserSemanticResources.ts +294 -0
- package/src/gpu/generalTextRenderPlacement.ts +175 -0
- package/src/gpu/previewTransformTextureLayout.ts +12 -0
- package/src/gpu/semanticCommandGeometry.ts +260 -0
- package/src/gpu/semanticCommandPlanner.ts +280 -0
- package/src/gpu/semanticTextGlyphBridge.ts +35 -0
- package/src/gpu/semanticWebglSceneRenderer.ts +261 -0
- package/src/gpu/slotRanges.ts +15 -0
- package/src/gpu/webglSdfAtlasTexture.ts +69 -0
- package/src/gpu/webglSdfGlyphPipeline.ts +299 -0
- package/src/gpu/webglSemanticCommandExecutor.ts +739 -0
- package/src/index.ts +84 -0
- package/src/interaction/numericTextRegions.ts +80 -0
- package/src/layout.rs +1882 -0
- package/src/lib.rs +1443 -0
- package/src/localizationProvider.ts +113 -0
- package/src/main.rs +55 -0
- package/src/masterdata_runtime.rs +496 -0
- package/src/originPrebuiltSdfAtlasPackage.ts +403 -0
- package/src/prebuiltSdfAtlas.ts +306 -0
- package/src/protocol.ts +150 -0
- package/src/renderer.ts +792 -0
- package/src/resourceProvider.ts +69 -0
- package/src/scene.rs +665 -0
- package/src/telemetry/rendererTelemetry.ts +358 -0
- package/src/types/atlas.ts +80 -0
- package/src/types/authoring.ts +85 -0
- package/src/types/core.ts +110 -0
- package/src/types/freeType.ts +63 -0
- package/src/types/glyph.ts +33 -0
- package/src/types/layout.ts +28 -0
- package/src/types/text.ts +28 -0
- package/src/worker/glyphWorkScheduler.ts +130 -0
- package/src/worker-client.ts +484 -0
- package/src/worker.ts +895 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isValidPrebuiltSdfAtlasManifest,
|
|
3
|
+
type PrebuiltSdfAtlasManifest,
|
|
4
|
+
type PrebuiltSdfAtlasProvider,
|
|
5
|
+
} from "./prebuiltSdfAtlas.js";
|
|
6
|
+
|
|
7
|
+
const DATABASE_NAME = "sekai-custom-profile-sdk-prebuilt-atlas-v1";
|
|
8
|
+
const DATABASE_VERSION = 1;
|
|
9
|
+
const ENTRY_STORE = "entries";
|
|
10
|
+
|
|
11
|
+
export type PrebuiltSdfAtlasInstallProgress = {
|
|
12
|
+
family: string;
|
|
13
|
+
completedPages: number;
|
|
14
|
+
totalPages: number;
|
|
15
|
+
storedBytes: number;
|
|
16
|
+
totalBytes: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type PrebuiltSdfAtlasPackageStatus = {
|
|
20
|
+
namespace: string;
|
|
21
|
+
storageAvailable: boolean;
|
|
22
|
+
installedFamilies: string[];
|
|
23
|
+
installedPages: number;
|
|
24
|
+
storedBytes: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type PrebuiltSdfAtlasInstallOptions = {
|
|
28
|
+
concurrency?: number;
|
|
29
|
+
onProgress?: (progress: PrebuiltSdfAtlasInstallProgress) => void;
|
|
30
|
+
requestPersistence?: boolean;
|
|
31
|
+
signal?: AbortSignal;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type OriginPrebuiltSdfAtlasPackage = {
|
|
35
|
+
readonly namespace: string;
|
|
36
|
+
readonly provider: PrebuiltSdfAtlasProvider;
|
|
37
|
+
install(families: readonly string[], options?: PrebuiltSdfAtlasInstallOptions): Promise<PrebuiltSdfAtlasPackageStatus>;
|
|
38
|
+
remove(families?: readonly string[]): Promise<void>;
|
|
39
|
+
status(families: readonly string[]): Promise<PrebuiltSdfAtlasPackageStatus>;
|
|
40
|
+
close(): void;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type PrebuiltSdfAtlasStorageErrorCode =
|
|
44
|
+
| "PREBUILT_ATLAS_STORAGE_UNAVAILABLE"
|
|
45
|
+
| "PREBUILT_ATLAS_INVALID_MANIFEST"
|
|
46
|
+
| "PREBUILT_ATLAS_QUOTA"
|
|
47
|
+
| "PREBUILT_ATLAS_PAGE_HASH";
|
|
48
|
+
|
|
49
|
+
export class PrebuiltSdfAtlasStorageError extends Error {
|
|
50
|
+
constructor(readonly code: PrebuiltSdfAtlasStorageErrorCode, message: string) {
|
|
51
|
+
super(message);
|
|
52
|
+
this.name = "PrebuiltSdfAtlasStorageError";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type ManifestRecord = {
|
|
57
|
+
key: string;
|
|
58
|
+
kind: "manifest";
|
|
59
|
+
namespace: string;
|
|
60
|
+
family: string;
|
|
61
|
+
manifest: PrebuiltSdfAtlasManifest;
|
|
62
|
+
installedPages: number;
|
|
63
|
+
storedBytes: number;
|
|
64
|
+
installedAtMs: number;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
type PageRecord = {
|
|
68
|
+
key: string;
|
|
69
|
+
kind: "page";
|
|
70
|
+
namespace: string;
|
|
71
|
+
family: string;
|
|
72
|
+
file: string;
|
|
73
|
+
sha256: string;
|
|
74
|
+
bytes: ArrayBuffer;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
type AtlasRecord = ManifestRecord | PageRecord;
|
|
78
|
+
|
|
79
|
+
export function createOriginPrebuiltSdfAtlasPackage(options: {
|
|
80
|
+
namespace: string;
|
|
81
|
+
source: PrebuiltSdfAtlasProvider;
|
|
82
|
+
databaseName?: string;
|
|
83
|
+
}): OriginPrebuiltSdfAtlasPackage {
|
|
84
|
+
if (!options.namespace.trim()) throw new TypeError("prebuilt atlas namespace must be non-empty");
|
|
85
|
+
const storage = new OriginPrebuiltAtlasStorage(options.databaseName ?? DATABASE_NAME);
|
|
86
|
+
const manifestCache = new Map<string, PrebuiltSdfAtlasManifest>();
|
|
87
|
+
const namespace = options.namespace.trim();
|
|
88
|
+
|
|
89
|
+
const installedManifest = async (family: string): Promise<PrebuiltSdfAtlasManifest | null> => {
|
|
90
|
+
const cached = manifestCache.get(family);
|
|
91
|
+
if (cached) return cached;
|
|
92
|
+
const record = await storage.getManifest(namespace, family);
|
|
93
|
+
if (!record || !isValidPrebuiltSdfAtlasManifest(record.manifest, family)) return null;
|
|
94
|
+
manifestCache.set(family, record.manifest);
|
|
95
|
+
return record.manifest;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const provider: PrebuiltSdfAtlasProvider = {
|
|
99
|
+
async manifest(family, { signal }) {
|
|
100
|
+
if (signal.aborted) throw abortReason(signal);
|
|
101
|
+
if (!storage.available) return null;
|
|
102
|
+
try {
|
|
103
|
+
return await installedManifest(family);
|
|
104
|
+
} catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
async page(family, file, { signal }) {
|
|
109
|
+
if (signal.aborted) throw abortReason(signal);
|
|
110
|
+
const manifest = await installedManifest(family);
|
|
111
|
+
const descriptor = manifest?.pages.find((page) => page.file === file);
|
|
112
|
+
if (!descriptor) throw new Error(`prebuilt atlas page is not installed: ${family}/${file}`);
|
|
113
|
+
const record = await storage.getPage(namespace, family, file, descriptor.file_sha256);
|
|
114
|
+
if (!record) {
|
|
115
|
+
manifestCache.delete(family);
|
|
116
|
+
await storage.deleteManifest(namespace, family).catch(() => undefined);
|
|
117
|
+
throw new Error(`prebuilt atlas page is missing: ${family}/${file}`);
|
|
118
|
+
}
|
|
119
|
+
return record.bytes.slice(0);
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
namespace,
|
|
125
|
+
provider,
|
|
126
|
+
async status(families) {
|
|
127
|
+
if (!storage.available) return emptyStatus(namespace, false);
|
|
128
|
+
const records = await Promise.all(uniqueFamilies(families).map((family) => storage.getManifest(namespace, family)));
|
|
129
|
+
return records.reduce<PrebuiltSdfAtlasPackageStatus>((status, record) => {
|
|
130
|
+
if (!record || !isValidPrebuiltSdfAtlasManifest(record.manifest, record.family)) return status;
|
|
131
|
+
status.installedFamilies.push(record.family);
|
|
132
|
+
status.installedPages += record.installedPages;
|
|
133
|
+
status.storedBytes += record.storedBytes;
|
|
134
|
+
return status;
|
|
135
|
+
}, emptyStatus(namespace, true));
|
|
136
|
+
},
|
|
137
|
+
async install(families, installOptions = {}) {
|
|
138
|
+
if (!storage.available) {
|
|
139
|
+
throw new PrebuiltSdfAtlasStorageError(
|
|
140
|
+
"PREBUILT_ATLAS_STORAGE_UNAVAILABLE",
|
|
141
|
+
"IndexedDB is unavailable for the prebuilt atlas package",
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
const signal = installOptions.signal ?? new AbortController().signal;
|
|
145
|
+
const requested = uniqueFamilies(families);
|
|
146
|
+
const current = await this.status(requested);
|
|
147
|
+
const installed = new Set(current.installedFamilies);
|
|
148
|
+
const missing = requested.filter((family) => !installed.has(family));
|
|
149
|
+
if (missing.length === 0) return current;
|
|
150
|
+
|
|
151
|
+
const manifests = new Map<string, PrebuiltSdfAtlasManifest>();
|
|
152
|
+
for (const family of missing) {
|
|
153
|
+
if (signal.aborted) throw abortReason(signal);
|
|
154
|
+
const manifest = await options.source.manifest(family, { signal });
|
|
155
|
+
if (!manifest || !isValidPrebuiltSdfAtlasManifest(manifest, family)) {
|
|
156
|
+
throw new PrebuiltSdfAtlasStorageError(
|
|
157
|
+
"PREBUILT_ATLAS_INVALID_MANIFEST",
|
|
158
|
+
`prebuilt atlas manifest is invalid: ${family}`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
manifests.set(family, manifest);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const totalPages = [...manifests.values()].reduce((sum, manifest) => sum + manifest.pages.length, 0);
|
|
165
|
+
const totalBytes = [...manifests.values()].flatMap((manifest) => manifest.pages)
|
|
166
|
+
.reduce((sum, page) => sum + 64 + page.width * page.height, 0);
|
|
167
|
+
await assertStorageQuota(totalBytes);
|
|
168
|
+
if (installOptions.requestPersistence) await requestOriginPersistence();
|
|
169
|
+
|
|
170
|
+
await storage.remove(namespace, missing);
|
|
171
|
+
manifestCache.clear();
|
|
172
|
+
const tasks = [...manifests].flatMap(([family, manifest]) => manifest.pages.map((page) => ({ family, page })));
|
|
173
|
+
const concurrency = boundedConcurrency(installOptions.concurrency ?? 4);
|
|
174
|
+
let cursor = 0;
|
|
175
|
+
let completedPages = 0;
|
|
176
|
+
let storedBytes = 0;
|
|
177
|
+
const familyBytes = new Map<string, number>();
|
|
178
|
+
try {
|
|
179
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, tasks.length) }, async () => {
|
|
180
|
+
while (cursor < tasks.length) {
|
|
181
|
+
const task = tasks[cursor++];
|
|
182
|
+
if (signal.aborted) throw abortReason(signal);
|
|
183
|
+
const buffer = await options.source.page(task.family, task.page.file, { signal });
|
|
184
|
+
await assertPageHash(buffer, task.page.file_sha256, task.family, task.page.file);
|
|
185
|
+
await storage.putPage({
|
|
186
|
+
key: pageKey(namespace, task.family, task.page.file, task.page.file_sha256),
|
|
187
|
+
kind: "page",
|
|
188
|
+
namespace,
|
|
189
|
+
family: task.family,
|
|
190
|
+
file: task.page.file,
|
|
191
|
+
sha256: task.page.file_sha256,
|
|
192
|
+
bytes: buffer,
|
|
193
|
+
});
|
|
194
|
+
completedPages += 1;
|
|
195
|
+
storedBytes += buffer.byteLength;
|
|
196
|
+
familyBytes.set(task.family, (familyBytes.get(task.family) ?? 0) + buffer.byteLength);
|
|
197
|
+
installOptions.onProgress?.({
|
|
198
|
+
family: task.family,
|
|
199
|
+
completedPages,
|
|
200
|
+
totalPages,
|
|
201
|
+
storedBytes,
|
|
202
|
+
totalBytes,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}));
|
|
206
|
+
await storage.putManifests([...manifests].map(([family, manifest]) => ({
|
|
207
|
+
key: manifestKey(namespace, family),
|
|
208
|
+
kind: "manifest",
|
|
209
|
+
namespace,
|
|
210
|
+
family,
|
|
211
|
+
manifest,
|
|
212
|
+
installedPages: manifest.pages.length,
|
|
213
|
+
storedBytes: familyBytes.get(family) ?? 0,
|
|
214
|
+
installedAtMs: Date.now(),
|
|
215
|
+
})));
|
|
216
|
+
} catch (error) {
|
|
217
|
+
await storage.remove(namespace, missing).catch(() => undefined);
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
return this.status(requested);
|
|
221
|
+
},
|
|
222
|
+
async remove(families) {
|
|
223
|
+
manifestCache.clear();
|
|
224
|
+
if (!storage.available) return;
|
|
225
|
+
await storage.remove(namespace, families == null ? undefined : uniqueFamilies(families));
|
|
226
|
+
},
|
|
227
|
+
close() {
|
|
228
|
+
manifestCache.clear();
|
|
229
|
+
storage.close();
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
class OriginPrebuiltAtlasStorage {
|
|
235
|
+
private databasePromise: Promise<IDBDatabase> | null = null;
|
|
236
|
+
|
|
237
|
+
constructor(private readonly databaseName: string) {}
|
|
238
|
+
|
|
239
|
+
get available(): boolean { return typeof indexedDB !== "undefined"; }
|
|
240
|
+
|
|
241
|
+
async getManifest(namespace: string, family: string): Promise<ManifestRecord | null> {
|
|
242
|
+
const record = await this.get(manifestKey(namespace, family));
|
|
243
|
+
return record?.kind === "manifest" ? record : null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async getPage(namespace: string, family: string, file: string, sha256: string): Promise<PageRecord | null> {
|
|
247
|
+
const record = await this.get(pageKey(namespace, family, file, sha256));
|
|
248
|
+
return record?.kind === "page" ? record : null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async putPage(record: PageRecord): Promise<void> {
|
|
252
|
+
const database = await this.database();
|
|
253
|
+
const transaction = database.transaction(ENTRY_STORE, "readwrite", { durability: "relaxed" });
|
|
254
|
+
transaction.objectStore(ENTRY_STORE).put(record);
|
|
255
|
+
await transactionDone(transaction);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async putManifests(records: readonly ManifestRecord[]): Promise<void> {
|
|
259
|
+
const database = await this.database();
|
|
260
|
+
const transaction = database.transaction(ENTRY_STORE, "readwrite");
|
|
261
|
+
const store = transaction.objectStore(ENTRY_STORE);
|
|
262
|
+
for (const record of records) store.put(record);
|
|
263
|
+
await transactionDone(transaction);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async deleteManifest(namespace: string, family: string): Promise<void> {
|
|
267
|
+
const database = await this.database();
|
|
268
|
+
const transaction = database.transaction(ENTRY_STORE, "readwrite");
|
|
269
|
+
transaction.objectStore(ENTRY_STORE).delete(manifestKey(namespace, family));
|
|
270
|
+
await transactionDone(transaction);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async remove(namespace: string, families?: readonly string[]): Promise<void> {
|
|
274
|
+
const database = await this.database();
|
|
275
|
+
const transaction = database.transaction(ENTRY_STORE, "readwrite");
|
|
276
|
+
const store = transaction.objectStore(ENTRY_STORE);
|
|
277
|
+
const prefixes = (families?.length ? families : [""]).map((family) => familyPrefix(namespace, family));
|
|
278
|
+
await deletePrefixes(store, prefixes);
|
|
279
|
+
await transactionDone(transaction);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
close(): void {
|
|
283
|
+
void this.databasePromise?.then((database) => database.close());
|
|
284
|
+
this.databasePromise = null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private async get(key: string): Promise<AtlasRecord | null> {
|
|
288
|
+
const database = await this.database();
|
|
289
|
+
const transaction = database.transaction(ENTRY_STORE, "readonly");
|
|
290
|
+
const record = await request<AtlasRecord | undefined>(transaction.objectStore(ENTRY_STORE).get(key));
|
|
291
|
+
await transactionDone(transaction);
|
|
292
|
+
return record ?? null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private database(): Promise<IDBDatabase> {
|
|
296
|
+
if (!this.available) return Promise.reject(new Error("IndexedDB is unavailable"));
|
|
297
|
+
this.databasePromise ??= new Promise((resolve, reject) => {
|
|
298
|
+
const open = indexedDB.open(this.databaseName, DATABASE_VERSION);
|
|
299
|
+
open.onupgradeneeded = () => {
|
|
300
|
+
if (!open.result.objectStoreNames.contains(ENTRY_STORE)) {
|
|
301
|
+
open.result.createObjectStore(ENTRY_STORE, { keyPath: "key" });
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
open.onerror = () => reject(open.error ?? new Error("open IndexedDB prebuilt atlas storage failed"));
|
|
305
|
+
open.onblocked = () => reject(new Error("IndexedDB prebuilt atlas storage upgrade was blocked"));
|
|
306
|
+
open.onsuccess = () => {
|
|
307
|
+
open.result.onversionchange = () => open.result.close();
|
|
308
|
+
resolve(open.result);
|
|
309
|
+
};
|
|
310
|
+
});
|
|
311
|
+
return this.databasePromise;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function emptyStatus(namespace: string, storageAvailable: boolean): PrebuiltSdfAtlasPackageStatus {
|
|
316
|
+
return { namespace, storageAvailable, installedFamilies: [], installedPages: 0, storedBytes: 0 };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function uniqueFamilies(families: readonly string[]): string[] {
|
|
320
|
+
return [...new Set(families.map((family) => family.trim()).filter(Boolean))];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function familyPrefix(namespace: string, family: string): string {
|
|
324
|
+
return family ? `${namespace}\0${family}\0` : `${namespace}\0`;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function manifestKey(namespace: string, family: string): string {
|
|
328
|
+
return `${familyPrefix(namespace, family)}manifest`;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function pageKey(namespace: string, family: string, file: string, sha256: string): string {
|
|
332
|
+
return `${familyPrefix(namespace, family)}page\0${file}\0${sha256}`;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function boundedConcurrency(value: number): number {
|
|
336
|
+
if (!Number.isInteger(value) || value <= 0) throw new TypeError("prebuilt atlas install concurrency must be positive");
|
|
337
|
+
return Math.min(8, value);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function assertStorageQuota(requiredBytes: number): Promise<void> {
|
|
341
|
+
if (typeof navigator === "undefined" || !navigator.storage?.estimate) return;
|
|
342
|
+
const estimate = await navigator.storage.estimate();
|
|
343
|
+
if (estimate.quota == null || estimate.usage == null) return;
|
|
344
|
+
const available = Math.max(0, estimate.quota - estimate.usage);
|
|
345
|
+
if (requiredBytes > available) {
|
|
346
|
+
throw new PrebuiltSdfAtlasStorageError(
|
|
347
|
+
"PREBUILT_ATLAS_QUOTA",
|
|
348
|
+
`prebuilt atlas requires ${requiredBytes} bytes but only ${available} bytes are available`,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async function requestOriginPersistence(): Promise<void> {
|
|
354
|
+
if (typeof navigator === "undefined" || !navigator.storage?.persist) return;
|
|
355
|
+
await navigator.storage.persist();
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async function assertPageHash(buffer: ArrayBuffer, expected: string, family: string, file: string): Promise<void> {
|
|
359
|
+
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", buffer.slice(0)));
|
|
360
|
+
const actual = [...digest].map((value) => value.toString(16).padStart(2, "0")).join("");
|
|
361
|
+
if (actual !== expected.toLowerCase()) {
|
|
362
|
+
throw new PrebuiltSdfAtlasStorageError(
|
|
363
|
+
"PREBUILT_ATLAS_PAGE_HASH",
|
|
364
|
+
`prebuilt atlas page hash mismatch: ${family}/${file}`,
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function abortReason(signal: AbortSignal): unknown {
|
|
370
|
+
return signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function request<T>(value: IDBRequest<T>): Promise<T> {
|
|
374
|
+
return new Promise((resolve, reject) => {
|
|
375
|
+
value.onsuccess = () => resolve(value.result);
|
|
376
|
+
value.onerror = () => reject(value.error ?? new Error("IndexedDB prebuilt atlas request failed"));
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function transactionDone(transaction: IDBTransaction): Promise<void> {
|
|
381
|
+
return new Promise((resolve, reject) => {
|
|
382
|
+
transaction.oncomplete = () => resolve();
|
|
383
|
+
transaction.onerror = () => reject(transaction.error ?? new Error("IndexedDB prebuilt atlas transaction failed"));
|
|
384
|
+
transaction.onabort = () => reject(transaction.error ?? new Error("IndexedDB prebuilt atlas transaction aborted"));
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function deletePrefixes(store: IDBObjectStore, prefixes: readonly string[]): Promise<void> {
|
|
389
|
+
return new Promise((resolve, reject) => {
|
|
390
|
+
const cursor = store.openKeyCursor();
|
|
391
|
+
cursor.onerror = () => reject(cursor.error ?? new Error("IndexedDB prebuilt atlas cursor failed"));
|
|
392
|
+
cursor.onsuccess = () => {
|
|
393
|
+
const value = cursor.result;
|
|
394
|
+
if (!value) {
|
|
395
|
+
resolve();
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const key = String(value.primaryKey);
|
|
399
|
+
if (prefixes.some((prefix) => key.startsWith(prefix))) store.delete(value.primaryKey);
|
|
400
|
+
value.continue();
|
|
401
|
+
};
|
|
402
|
+
});
|
|
403
|
+
}
|