@hoardodile/host 0.1.2 → 0.1.4

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.
@@ -43,6 +43,7 @@ const HOOK_NAMES = [
43
43
  "coverLocal",
44
44
  "listFiles",
45
45
  "imageHashes",
46
+ "onInstall",
46
47
  ]
47
48
 
48
49
  const API_METHOD_NAMES = [
@@ -283,7 +284,11 @@ function send(message) {
283
284
  function approxByteSize(value) {
284
285
  if (value instanceof Uint8Array) return value.byteLength
285
286
  try {
286
- return JSON.stringify(value).length * 2
287
+ // `undefined` (hooks that legitimately return nothing — e.g.
288
+ // `onInstall`) stringifies to `undefined`, not a string: an empty
289
+ // payload must size 0, never throw into the Infinity branch.
290
+ const text = JSON.stringify(value)
291
+ return (typeof text === "string" ? text : "").length * 2
287
292
  } catch {
288
293
  return Infinity
289
294
  }
@@ -188,7 +188,8 @@ declare const RESOURCE_DATA_DIR_NAME = "data";
188
188
  * per-version database snapshot), `db-backups/` (manual backups,
189
189
  * only kept for the current version), `snapshots/` (automatic daily
190
190
  * snapshots, only kept for the current version), `resources/<id>/`,
191
- * `characters/<id>/`, `plugins/<id>/` (installed content plugins
191
+ * `characters/<id>/`, `tags/<id>/` (tag art — see {@link VersionPaths.tag}),
192
+ * `plugins/<id>/` (installed content plugins
192
193
  * frozen with that version; the builtin `file` plugin is not stored
193
194
  * here). Old versions are FROZEN: no writes ever land in
194
195
  * `versions/<v>` once a `versions/<v+1>` exists.
@@ -245,10 +246,18 @@ type VersionPaths = {
245
246
  resources(): string;
246
247
  /** Root folder of all characters in this version: `<root>/versions/<v>/characters`. */
247
248
  characters(): string;
249
+ /** Root folder of all tags in this version: `<root>/versions/<v>/tags`. */
250
+ tags(): string;
248
251
  /** Root folder of all documents in this version: `<root>/versions/<v>/documents`. */
249
252
  documents(): string;
250
253
  /** Root folder of a character: `<root>/versions/<v>/characters/<id>`. */
251
254
  character(id: string): string;
255
+ /**
256
+ * Root folder of a tag: `<root>/versions/<v>/tags/<id>`. Holds the
257
+ * tag's single image slot (`image.<ext>`), the same convention as
258
+ * character avatar/fullbody slots.
259
+ */
260
+ tag(id: string): string;
252
261
  /** Root of manual backups: `<root>/versions/<v>/db-backups`. */
253
262
  dbBackups(): string;
254
263
  /** Path to one manual backup: `<root>/versions/<v>/db-backups/<name>`. */
@@ -263,7 +272,7 @@ type VersionPaths = {
263
272
  * delete cannot remove a folder whose files live under frozen past
264
273
  * archives.
265
274
  */
266
- deletedMarker(kind: "resources" | "characters", id: string): string;
275
+ deletedMarker(kind: "resources" | "characters" | "tags", id: string): string;
267
276
  /** Root folder of a document: `<root>/versions/<v>/documents/<id>`. */
268
277
  document(id: string): string;
269
278
  /**
@@ -290,11 +299,11 @@ type LocalPaths = {
290
299
  logs(): string;
291
300
  /**
292
301
  * Path to a local derived cover/thumb variant:
293
- * `<localRoot>/cache/<resources|characters>/<id>/<variant>.<format>`.
302
+ * `<localRoot>/cache/<resources|characters|tags>/<id>/<variant>.<format>`.
294
303
  * Holds synthesized covers (resource covers, character avatars and
295
- * fullbody images); re-rendered when cleared.
304
+ * fullbody images, tag art); re-rendered when cleared.
296
305
  */
297
- localCover(subjectKind: "resource" | "character", id: string, variant: string, format?: string): string;
306
+ localCover(subjectKind: "resource" | "character" | "tag", id: string, variant: string, format?: string): string;
298
307
  /**
299
308
  * Per-file derived image variant:
300
309
  * `<localRoot>/cache/resources/<id>/file-preview/<sourceCacheId>__<variantKey>.<format>`.
@@ -320,6 +329,13 @@ type LocalPaths = {
320
329
  * (b) thumbnail variants (`avatar.webp`, `fullbody.webp`).
321
330
  */
322
331
  character(id: string): string;
332
+ /**
333
+ * Root of the local per-tag directory:
334
+ * `<localRoot>/cache/tags/<id>`.
335
+ * Holds (a) versioned copies of replaced tag art and (b) the tag
336
+ * thumbnail variant (`image.avif`).
337
+ */
338
+ tag(id: string): string;
323
339
  /** Root of the trash: `<localRoot>/trash`. */
324
340
  trash(): string;
325
341
  /** Path to a single trashed item: `<localRoot>/trash/<id>`. */
@@ -677,6 +693,7 @@ declare function versionedDbFile(root: string, v: number): string;
677
693
  */
678
694
  declare function versionedPath(root: string, v: number): string;
679
695
 
696
+ type VersionedFolderSubjectKind = "resource" | "character" | "tag";
680
697
  type VersionedFolderOps = {
681
698
  /** Ensure the current-version entity folder exists. */
682
699
  ensureFolder(id: string): Promise<void>;
@@ -696,21 +713,9 @@ type VersionedFolderOps = {
696
713
  */
697
714
  moveFolderToTrash(id: string): Promise<string>;
698
715
  };
699
- /**
700
- * The four lifecycle operations shared by the resource and character
701
- * file-system layers. They differ only in which versioned folder they
702
- * target and how the trash / placeholder names are derived.
703
- *
704
- * `moveFolderToTrash` treats `EPERM`/`EBUSY`/`UNKNOWN` as transient Windows
705
- * locks (a file inside the folder is still open) and leaves the source in
706
- * place; a boot-time orphan sweep reclaims it later.
707
- *
708
- * `readOnly` is a live `{ current: boolean }` ref (the server's runtime
709
- * read-only flag), so a version switch mid-request re-reads it.
710
- */
711
716
  declare function buildVersionedFolderOps(paths: StoragePaths, readOnly: {
712
717
  readonly current: boolean;
713
- }, kind: "resource" | "character"): VersionedFolderOps;
718
+ }, kind: VersionedFolderSubjectKind): VersionedFolderOps;
714
719
  /**
715
720
  * Move every file in `sourceFolder` whose name matches `match` into
716
721
  * `destFolder` under a timestamped archive name (`<prefix><stamp><ext>`, or
@@ -748,4 +753,4 @@ type VersionedWriteCommand<T> = (paths: StoragePaths["latest"]) => T | Promise<T
748
753
  */
749
754
  declare function writeVersioned<T>(paths: StoragePaths, readOnly: boolean, cmd: VersionedWriteCommand<T>): Promise<T>;
750
755
 
751
- export { type CreateNextVersionResult, type CreateStoragePathsOptions, type DirSizeOptions, type LocalPaths, ORDER_MANIFEST_NAME, type OccupiedNames, PluginVaultPathError, RESOURCE_DATA_DIR_NAME, type StoragePaths, type VaultCommitResult, type VersionPaths, type VersionedFolderOps, type VersionedWriteCommand, archiveStaleFiles, assertInside, assertSafeSegment, buildVersionedFolderOps, commitVaultFile, createNextVersion, createOccupiedNames, createStoragePaths, currentVersion, discardVaultTempFile, ensureBootstrapVersion, extractArchiveInto, findStagedArchiveFile, findStagedPoolFile, imageVariantKey, listArchiveEntries, listVersions, naturalSort, occupyEntryName, orderEntries, orderManifestPath, parseOrderManifest, parsePluginVaultDest, readActiveVersion, readOrderManifest, removeStagedPoolFile, resolveStagedPoolFiles, sanitizeEntryName, sumDirSizes, uniqueEntryName, validateArchiveBudget, vaultFileSha256, vaultReadFile, vaultRemoveFile, vaultStatFile, vaultTempFile, vaultTotalSize, versionedDbFile, versionedPath, writeActiveVersion, writeOrderManifest, writeStagedArchiveFile, writeStagedPoolFile, writeVersioned };
756
+ export { type CreateNextVersionResult, type CreateStoragePathsOptions, type DirSizeOptions, type LocalPaths, ORDER_MANIFEST_NAME, type OccupiedNames, PluginVaultPathError, RESOURCE_DATA_DIR_NAME, type StoragePaths, type VaultCommitResult, type VersionPaths, type VersionedFolderOps, type VersionedFolderSubjectKind, type VersionedWriteCommand, archiveStaleFiles, assertInside, assertSafeSegment, buildVersionedFolderOps, commitVaultFile, createNextVersion, createOccupiedNames, createStoragePaths, currentVersion, discardVaultTempFile, ensureBootstrapVersion, extractArchiveInto, findStagedArchiveFile, findStagedPoolFile, imageVariantKey, listArchiveEntries, listVersions, naturalSort, occupyEntryName, orderEntries, orderManifestPath, parseOrderManifest, parsePluginVaultDest, readActiveVersion, readOrderManifest, removeStagedPoolFile, resolveStagedPoolFiles, sanitizeEntryName, sumDirSizes, uniqueEntryName, validateArchiveBudget, vaultFileSha256, vaultReadFile, vaultRemoveFile, vaultStatFile, vaultTempFile, vaultTotalSize, versionedDbFile, versionedPath, writeActiveVersion, writeOrderManifest, writeStagedArchiveFile, writeStagedPoolFile, writeVersioned };
@@ -1375,8 +1375,10 @@ function createStoragePaths(opts) {
1375
1375
  resourceData: (id) => join6(vRoot, "resources", assertSafeSegment(id), RESOURCE_DATA_DIR_NAME),
1376
1376
  resources: () => join6(vRoot, "resources"),
1377
1377
  characters: () => join6(vRoot, "characters"),
1378
+ tags: () => join6(vRoot, "tags"),
1378
1379
  documents: () => join6(vRoot, "documents"),
1379
1380
  character: (id) => join6(vRoot, "characters", assertSafeSegment(id)),
1381
+ tag: (id) => join6(vRoot, "tags", assertSafeSegment(id)),
1380
1382
  dbBackups: () => join6(vRoot, "db-backups"),
1381
1383
  dbBackup: (name) => join6(vRoot, "db-backups", assertSafeSegment(name)),
1382
1384
  snapshots: () => join6(vRoot, "snapshots"),
@@ -1412,6 +1414,7 @@ function createStoragePaths(opts) {
1412
1414
  resFilesCache: (id) => join6(cacheRoot, "resources", assertSafeSegment(id), "files-cache.json"),
1413
1415
  resource: (id) => join6(cacheRoot, "resources", assertSafeSegment(id)),
1414
1416
  character: (id) => join6(cacheRoot, "characters", assertSafeSegment(id)),
1417
+ tag: (id) => join6(cacheRoot, "tags", assertSafeSegment(id)),
1415
1418
  trash: () => join6(localRoot, "trash"),
1416
1419
  trashItem: (id) => join6(localRoot, "trash", assertSafeSegment(id)),
1417
1420
  tmp: () => join6(cacheRoot, "tmp"),
@@ -1469,7 +1472,7 @@ function createStoragePaths(opts) {
1469
1472
  };
1470
1473
  }
1471
1474
  function localCoverSubjectDir(subjectKind) {
1472
- return subjectKind === "resource" ? "resources" : "characters";
1475
+ return subjectKind === "resource" ? "resources" : subjectKind === "character" ? "characters" : "tags";
1473
1476
  }
1474
1477
  function toCacheBasename(filename) {
1475
1478
  const dot = filename.lastIndexOf(".");
@@ -1858,10 +1861,25 @@ async function writeVersioned(paths, readOnly, cmd) {
1858
1861
  }
1859
1862
 
1860
1863
  // src/hoard/versioned-folder-ops.ts
1864
+ var KIND_LAYOUT = {
1865
+ resource: { trashPrefix: "resources-", deletedKind: "resources" },
1866
+ character: { trashPrefix: "characters-", deletedKind: "characters" },
1867
+ tag: { trashPrefix: "tags-", deletedKind: "tags" }
1868
+ };
1861
1869
  function buildVersionedFolderOps(paths, readOnly, kind) {
1862
- const folderOf = (id) => (current) => kind === "resource" ? current.resource(id) : current.character(id);
1863
- const trashPrefix = kind === "resource" ? "resources-" : "characters-";
1864
- const deletedKind = kind === "resource" ? "resources" : "characters";
1870
+ const folderOf = (id) => (current) => {
1871
+ switch (kind) {
1872
+ case "resource":
1873
+ return current.resource(id);
1874
+ case "character":
1875
+ return current.character(id);
1876
+ case "tag":
1877
+ return current.tag(id);
1878
+ }
1879
+ };
1880
+ const layout = KIND_LAYOUT[kind];
1881
+ const trashPrefix = layout.trashPrefix;
1882
+ const deletedKind = layout.deletedKind;
1865
1883
  async function ensureFolder(id) {
1866
1884
  await writeVersioned(
1867
1885
  paths,