@x12i/catalox 3.0.0 → 3.1.1

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.
Files changed (125) hide show
  1. package/README.md +38 -5
  2. package/dist/src/catalox/authorization.js +1 -1
  3. package/dist/src/catalox/authorization.js.map +1 -1
  4. package/dist/src/catalox/catalog-lifecycle.d.ts +29 -0
  5. package/dist/src/catalox/catalog-lifecycle.d.ts.map +1 -0
  6. package/dist/src/catalox/catalog-lifecycle.js +480 -0
  7. package/dist/src/catalox/catalog-lifecycle.js.map +1 -0
  8. package/dist/src/catalox/catalox-bound.d.ts +20 -4
  9. package/dist/src/catalox/catalox-bound.d.ts.map +1 -1
  10. package/dist/src/catalox/catalox-bound.js +30 -6
  11. package/dist/src/catalox/catalox-bound.js.map +1 -1
  12. package/dist/src/catalox/catalox.d.ts +29 -4
  13. package/dist/src/catalox/catalox.d.ts.map +1 -1
  14. package/dist/src/catalox/catalox.js +483 -66
  15. package/dist/src/catalox/catalox.js.map +1 -1
  16. package/dist/src/catalox/context.js +2 -2
  17. package/dist/src/catalox/context.js.map +1 -1
  18. package/dist/src/catalox/create-catalox.d.ts +6 -0
  19. package/dist/src/catalox/create-catalox.d.ts.map +1 -1
  20. package/dist/src/catalox/create-catalox.js +25 -0
  21. package/dist/src/catalox/create-catalox.js.map +1 -1
  22. package/dist/src/catalox/index.d.ts +2 -0
  23. package/dist/src/catalox/index.d.ts.map +1 -1
  24. package/dist/src/catalox/index.js +2 -0
  25. package/dist/src/catalox/index.js.map +1 -1
  26. package/dist/src/catalox/native-catalog-merge.d.ts +12 -0
  27. package/dist/src/catalox/native-catalog-merge.d.ts.map +1 -0
  28. package/dist/src/catalox/native-catalog-merge.js +102 -0
  29. package/dist/src/catalox/native-catalog-merge.js.map +1 -0
  30. package/dist/src/catalox/native-scope.d.ts +28 -0
  31. package/dist/src/catalox/native-scope.d.ts.map +1 -0
  32. package/dist/src/catalox/native-scope.js +184 -0
  33. package/dist/src/catalox/native-scope.js.map +1 -0
  34. package/dist/src/catalox/record-history.d.ts +53 -0
  35. package/dist/src/catalox/record-history.d.ts.map +1 -0
  36. package/dist/src/catalox/record-history.js +158 -0
  37. package/dist/src/catalox/record-history.js.map +1 -0
  38. package/dist/src/cli/index.js +133 -1
  39. package/dist/src/cli/index.js.map +1 -1
  40. package/dist/src/contracts/apps.d.ts +2 -0
  41. package/dist/src/contracts/apps.d.ts.map +1 -1
  42. package/dist/src/contracts/catalog-lifecycle.d.ts +70 -0
  43. package/dist/src/contracts/catalog-lifecycle.d.ts.map +1 -0
  44. package/dist/src/contracts/catalog-lifecycle.js +2 -0
  45. package/dist/src/contracts/catalog-lifecycle.js.map +1 -0
  46. package/dist/src/contracts/catalogs.d.ts +37 -0
  47. package/dist/src/contracts/catalogs.d.ts.map +1 -1
  48. package/dist/src/contracts/catalogs.js.map +1 -1
  49. package/dist/src/contracts/context.d.ts +5 -1
  50. package/dist/src/contracts/context.d.ts.map +1 -1
  51. package/dist/src/contracts/descriptors.d.ts +6 -0
  52. package/dist/src/contracts/descriptors.d.ts.map +1 -1
  53. package/dist/src/contracts/index.d.ts +5 -2
  54. package/dist/src/contracts/index.d.ts.map +1 -1
  55. package/dist/src/contracts/index.js.map +1 -1
  56. package/dist/src/contracts/items.d.ts +19 -0
  57. package/dist/src/contracts/items.d.ts.map +1 -1
  58. package/dist/src/contracts/record-history.d.ts +66 -0
  59. package/dist/src/contracts/record-history.d.ts.map +1 -0
  60. package/dist/src/contracts/record-history.js +2 -0
  61. package/dist/src/contracts/record-history.js.map +1 -0
  62. package/dist/src/firebase/adapter-store.d.ts +1 -0
  63. package/dist/src/firebase/adapter-store.d.ts.map +1 -1
  64. package/dist/src/firebase/adapter-store.js +3 -0
  65. package/dist/src/firebase/adapter-store.js.map +1 -1
  66. package/dist/src/firebase/binding-store.d.ts +2 -0
  67. package/dist/src/firebase/binding-store.d.ts.map +1 -1
  68. package/dist/src/firebase/binding-store.js +10 -0
  69. package/dist/src/firebase/binding-store.js.map +1 -1
  70. package/dist/src/firebase/catalog-data-index-store.d.ts +1 -0
  71. package/dist/src/firebase/catalog-data-index-store.d.ts.map +1 -1
  72. package/dist/src/firebase/catalog-data-index-store.js +3 -0
  73. package/dist/src/firebase/catalog-data-index-store.js.map +1 -1
  74. package/dist/src/firebase/catalog-item-history-store.d.ts +21 -0
  75. package/dist/src/firebase/catalog-item-history-store.d.ts.map +1 -0
  76. package/dist/src/firebase/catalog-item-history-store.js +61 -0
  77. package/dist/src/firebase/catalog-item-history-store.js.map +1 -0
  78. package/dist/src/firebase/catalog-store.d.ts +1 -0
  79. package/dist/src/firebase/catalog-store.d.ts.map +1 -1
  80. package/dist/src/firebase/catalog-store.js +3 -0
  81. package/dist/src/firebase/catalog-store.js.map +1 -1
  82. package/dist/src/firebase/index.d.ts +1 -0
  83. package/dist/src/firebase/index.d.ts.map +1 -1
  84. package/dist/src/firebase/index.js +1 -0
  85. package/dist/src/firebase/index.js.map +1 -1
  86. package/dist/src/firebase/mapping-store.d.ts +1 -0
  87. package/dist/src/firebase/mapping-store.d.ts.map +1 -1
  88. package/dist/src/firebase/mapping-store.js +3 -0
  89. package/dist/src/firebase/mapping-store.js.map +1 -1
  90. package/dist/src/firebase/native-item-store.d.ts +8 -2
  91. package/dist/src/firebase/native-item-store.d.ts.map +1 -1
  92. package/dist/src/firebase/native-item-store.js +22 -6
  93. package/dist/src/firebase/native-item-store.js.map +1 -1
  94. package/dist/src/firebase/reference-store.d.ts +3 -0
  95. package/dist/src/firebase/reference-store.d.ts.map +1 -1
  96. package/dist/src/firebase/reference-store.js +16 -0
  97. package/dist/src/firebase/reference-store.js.map +1 -1
  98. package/dist/src/firebase/renderer-snippet-store.d.ts +3 -0
  99. package/dist/src/firebase/renderer-snippet-store.d.ts.map +1 -1
  100. package/dist/src/firebase/renderer-snippet-store.js +17 -0
  101. package/dist/src/firebase/renderer-snippet-store.js.map +1 -1
  102. package/dist/src/firebase/snapshot-store.d.ts +1 -0
  103. package/dist/src/firebase/snapshot-store.d.ts.map +1 -1
  104. package/dist/src/firebase/snapshot-store.js +8 -0
  105. package/dist/src/firebase/snapshot-store.js.map +1 -1
  106. package/dist/test/integration/firestore.emulator.test.js +7 -1
  107. package/dist/test/integration/firestore.emulator.test.js.map +1 -1
  108. package/dist/test/integration/record-history.live.test.d.ts +2 -0
  109. package/dist/test/integration/record-history.live.test.d.ts.map +1 -0
  110. package/dist/test/integration/record-history.live.test.js +126 -0
  111. package/dist/test/integration/record-history.live.test.js.map +1 -0
  112. package/dist/test/unit/native-catalog-merge.test.d.ts +2 -0
  113. package/dist/test/unit/native-catalog-merge.test.d.ts.map +1 -0
  114. package/dist/test/unit/native-catalog-merge.test.js +33 -0
  115. package/dist/test/unit/native-catalog-merge.test.js.map +1 -0
  116. package/dist/test/unit/native-scope.test.d.ts +2 -0
  117. package/dist/test/unit/native-scope.test.d.ts.map +1 -0
  118. package/dist/test/unit/native-scope.test.js +29 -0
  119. package/dist/test/unit/native-scope.test.js.map +1 -0
  120. package/dist/test/unit/record-history-path.test.d.ts +2 -0
  121. package/dist/test/unit/record-history-path.test.d.ts.map +1 -0
  122. package/dist/test/unit/record-history-path.test.js +24 -0
  123. package/dist/test/unit/record-history-path.test.js.map +1 -0
  124. package/firestore.indexes.json +39 -0
  125. package/package.json +3 -2
package/README.md CHANGED
@@ -8,6 +8,8 @@ Catalox is a **data-tier** package for managing **app-scoped catalogs** in Fireb
8
8
  - **Mapped catalogs** (items normalized from MongoDB or APIs)
9
9
  - **References + validation contracts** (standardized cross-catalog shapes)
10
10
  - **Seed/import/export + batch upsert** workflows for native catalogs
11
+ - **Optional per-record native history** — NDJSON payloads in GCS plus a Firestore `catalogItemHistory` index; list/show/restore/replay APIs and `catalox history …` ([`docs/record-history.md`](docs/record-history.md))
12
+ - **Optional catalog lifecycle** — hard delete (with manifest), restore from manifest, and hard rename across collections; APIs + `catalox catalog …` ([`docs/catalog-crud.md`](docs/catalog-crud.md))
11
13
  - **Optional GCS tools** — NDJSON export/import of Firestore collections to a bucket, **compare** live data vs bucket snapshots, **`backupData` mode `gcs`** (Catalox-shaped NDJSON + manifest under a bucket prefix), and matching CLI commands (`firestore export-gcs`, `import-gcs`, `compare-gcs`, `firestore backup --mode gcs`; see [`docs/firestore-gcs-export.md`](docs/firestore-gcs-export.md), [`docs/backup.md`](docs/backup.md))
12
14
 
13
15
  Catalox **does not** own UI, workflow orchestration, remote execution, artifact blobs, or secret storage.
@@ -83,6 +85,9 @@ GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
83
85
  FIREBASE_SERVICE_ACCOUNT_PATH=/path/to/service-account.json
84
86
  FIREBASE_PROJECT_ID=your-project-id
85
87
  FIRESTORE_LIVE_TESTS=1
88
+ # Optional: record-history live test + CLI (`createCatalox`); use a dedicated test bucket:
89
+ # CATALOX_RECORD_HISTORY_BUCKET=your-test-bucket
90
+ # CATALOX_RECORD_HISTORY_PREFIX=catalox-record-history/
86
91
  # Optional defaults for the packaged `catalox` CLI (Catalox app/store context, not GCP):
87
92
  # CATALOX_APP_ID=myAppId
88
93
  # CATALOX_STORE_ID=myStoreId
@@ -98,6 +103,9 @@ The `catalox` binary loads `.env` via `dotenv`. Use **`CATALOX_*`** for **Catalo
98
103
  - **`CATALOX_STORE_ID`** — Default `storeId` for **`report`** and **`export`** when `--store` is omitted (`--store` overrides).
99
104
  - **`CATALOX_USER_ID`** — Optional user id / actor for authz-sensitive CLI paths.
100
105
  - **`CATALOX_MONGO_URI`** — If set, enables the Mongo catalog adapter in the CLI (wired via **`createCatalox`** in `src/catalox/create-catalox.ts`, called from `src/cli/index.ts`).
106
+ - **`CATALOX_RECORD_HISTORY_BUCKET`** — If set, **`createCatalox`** wires **`recordHistory`** (GCS payloads + **`catalogItemHistory`** on native writes; also required for **`catalox history …`** to persist new events).
107
+ - **`CATALOX_RECORD_HISTORY_PREFIX`** — Optional GCS prefix for record history (default `catalox-record-history/`).
108
+ - **`CATALOX_RECORD_HISTORY_FAIL_CLOSED`** — When **`1`**, failed history writes fail the parent Firestore mutation.
101
109
 
102
110
  **Running commands from this repository:** the shell command `catalox` is only on your `PATH` if the package is installed globally (`npm i -g @x12i/catalox`) or linked (`npm link` from this repo). Otherwise, after `npm run build`, use:
103
111
 
@@ -120,6 +128,13 @@ npm run cli -- firestore report-native-layout --app "narrix"
120
128
  | `firestore export-gcs` / `import-gcs` | NDJSON per collection + optional manifest ([`docs/firestore-gcs-export.md`](docs/firestore-gcs-export.md)). |
121
129
  | `firestore compare-gcs` | Diff live Firestore vs bucket NDJSON (single collection or manifest). |
122
130
 
131
+ #### `history` / `catalog` CLI (3.1+)
132
+
133
+ | Command | Purpose |
134
+ |---------|---------|
135
+ | `history list` / `history show` / `history restore` / `history replay` | Per-record native history from **`catalogItemHistory`** + GCS ([`docs/record-history.md`](docs/record-history.md)). |
136
+ | `catalog delete` / `catalog restore` / `catalog rename` | Hard delete with manifest, restore from manifest, or hard rename ([`docs/catalog-crud.md`](docs/catalog-crud.md)). |
137
+
123
138
  ## Firestore data model (logical collections)
124
139
 
125
140
  For a **full layout** (subcollections, document id conventions, snapshot runs, and query notes), see [`docs/firestore-data-model.md`](docs/firestore-data-model.md). For **native** catalogs specifically (per-catalog `catalogData-*-items`, layout resolution, `listCatalogItems`, filters, troubleshooting), see [`docs/native-catalog-storage-and-api.md`](docs/native-catalog-storage-and-api.md).
@@ -136,6 +151,7 @@ Metadata:
136
151
  - `catalogDescriptors/{catalogId}` (**descriptor metadata** for generic consumption)
137
152
  - `catalogRendererSnippets/{catalogId}:{role}[:{mode}]` (**stored renderer snippets** for list/grid/item/report/dashboard rendering)
138
153
  - `catalogReferences/{referenceId}` (standardized reference records)
154
+ - `catalogItemHistory/{eventId}` (optional **native write history** index; payloads in GCS — [`docs/record-history.md`](docs/record-history.md))
139
155
 
140
156
  Data:
141
157
 
@@ -162,7 +178,11 @@ import { createCatalox } from "@x12i/catalox";
162
178
  import { getFirestore } from "firebase-admin/firestore";
163
179
 
164
180
  const firestore = getFirestore();
165
- const catalox = createCatalox({ firestore });
181
+ const catalox = createCatalox({
182
+ firestore,
183
+ // Optional: per-record native history to GCS + `catalogItemHistory` (or set CATALOX_RECORD_HISTORY_BUCKET for CLI)
184
+ // recordHistory: { gcsBucket: "my-bucket", gcsPrefix: "catalox-record-history/" },
185
+ });
166
186
 
167
187
  // Optional: bind app/tenant context once (no globals; same semantics as passing context each call).
168
188
  const scoped = catalox.withContext({ appId: "myApp" });
@@ -355,7 +375,7 @@ Catalox includes provisioning helpers to reduce “manual Firestore wiring”.
355
375
  ### Create a native catalog (also seeds a minimal descriptor)
356
376
 
357
377
  ```ts
358
- const ctx = { appId: "myAppId", isGodMode: true }; // or bind admin access appropriately
378
+ const ctx = { appId: "myAppId", superAdmin: true }; // set only after host auth; often from AppRecord.superAdminApp
359
379
 
360
380
  await catalox.createCatalog(ctx, {
361
381
  catalogId: "signals",
@@ -485,7 +505,7 @@ Validation APIs exist with standardized contracts, but **current behavior is min
485
505
 
486
506
  ## Publishing
487
507
 
488
- From a clean tree, **`npm publish`** runs **`prepublishOnly`**, which executes **`npm run build`** then **`npm test`** (unit tests). Ensure `dist/` is build output only; the published tarball includes **`dist`**, **`README.md`**, and **`LICENSE`** per `package.json` **`files`**.
508
+ From a clean tree, **`npm publish`** runs **`prepublishOnly`**, which executes **`npm run build`** then **`npm test`** (unit tests). Ensure `dist/` is build output only; the published tarball includes **`dist`**, **`README.md`**, **`LICENSE`**, and **`firestore.indexes.json`** (composite indexes for `catalogItemHistory` queries) per `package.json` **`files`**.
489
509
 
490
510
  ## Tests
491
511
 
@@ -507,16 +527,29 @@ Integration tests are **live-only** (no mocks, no emulators). They require:
507
527
  - `FIREBASE_SERVICE_ACCOUNT_PATH=...`
508
528
  - `FIREBASE_PROJECT_ID=...`
509
529
 
510
- The **`Firestore live integration`** test (`test/integration/firestore.emulator.test.ts`) uses **`createCatalox`** and asserts v3 contracts: **`listCatalogItems`** returns **`listOutcome: "ok"`**, and **`getCatalogItem`** returns **`{ outcome: "found", item }`** for the seeded row.
530
+ The **`Firestore live integration`** test (`test/integration/firestore.emulator.test.ts`) uses **`createCatalox`** and asserts v3 contracts: **`listCatalogItems`** returns **`listOutcome: "ok"`**, and **`getCatalogItem`** returns **`{ outcome: "found", item }`** for the seeded row. The descriptor patch in that test includes **`queryableFields` with `indexed: true`** for filtered fields so **`deriveIndexed`** persists **`indexed.*`** (see **Canonical `indexed` rule** above).
531
+
532
+ When **`CATALOX_RECORD_HISTORY_BUCKET`** is set, **`test/integration/record-history.live.test.ts`** runs as well: ephemeral app/catalog, **`upsert` → `update` → `listCatalogItemHistory` → `getCatalogItemHistoryEvent` → `restoreCatalogItemFromHistory`**, then best-effort Firestore + GCS cleanup for the created history objects.
511
533
 
512
534
  ### Live test safety (read before running)
513
535
 
514
536
  - **Never run against production credentials/projects.** Use a dedicated Firebase project for tests.
515
- - **Touched collections**: `apps`, `catalogs`, `catalogBindings`, `catalogDefinitions`, `catalogDescriptors`, `catalogData/{catalogId}`, and `catalogData-{catalogId}-items/...`. The **`catalox firestore restore-backup`** / **`undo-restore-backup`** commands additionally write `backup-restoreSessions` and `{restoreSessionId}__preRestore-*` sidecar collections (see [`docs/restore-firestore-backup.md`](docs/restore-firestore-backup.md)).
537
+ - **Touched collections**: `apps`, `catalogs`, `catalogBindings`, `catalogDefinitions`, `catalogDescriptors`, `catalogData/{catalogId}`, `catalogData-{catalogId}-items/...`, and (when record history is enabled) `catalogItemHistory/...`. The **`catalox firestore restore-backup`** / **`undo-restore-backup`** commands additionally write `backup-restoreSessions` and `{restoreSessionId}__preRestore-*` sidecar collections (see [`docs/restore-firestore-backup.md`](docs/restore-firestore-backup.md)).
516
538
  - **Cleanup**: tests do best-effort deletes of the docs they created, but do not guarantee full teardown.
517
539
 
518
540
  ## Changelog
519
541
 
542
+ ### 3.1.1
543
+
544
+ - **Live tests:** Firestore integration test now sets **`queryableFields`** on the patched descriptor so equality filters match stored **`indexed.*`** rows (same rule as production descriptors).
545
+ - **Live tests:** Record-history integration test covers **update**, **get event**, **restore**, and **GCS object** cleanup when a bucket is configured.
546
+ - **Package:** publish **`firestore.indexes.json`** in the npm tarball for operators deploying **`catalogItemHistory`** queries.
547
+
548
+ ### 3.1.0
549
+
550
+ - **Per-record history:** optional **`recordHistory`** on **`createCatalox`** (or **`CATALOX_RECORD_HISTORY_*`** env for CLI) writes **GCS NDJSON** + Firestore **`catalogItemHistory`** on native upsert/update/delete/batch/move. APIs: **`listCatalogItemHistory`**, **`getCatalogItemHistoryEvent`**, **`restoreCatalogItemFromHistory`**, **`replayCatalogToPointInTime`**. CLI: **`catalox history …`**. See [`docs/record-history.md`](docs/record-history.md), [`firestore.indexes.json`](firestore.indexes.json).
551
+ - **Catalog lifecycle:** **`deleteCatalog`**, **`restoreDeletedCatalog`**, **`renameCatalog`** (hard rename) + CLI **`catalox catalog …`**. See [`docs/catalog-crud.md`](docs/catalog-crud.md).
552
+
520
553
  ### 3.0.0
521
554
 
522
555
  - **`createCatalox(config)`** — single factory for Firestore-backed stores, authz, optional Mongo/API adapters, optional renderer snippet store (`src/catalox/create-catalox.ts`).
@@ -6,7 +6,7 @@ export class AuthorizationService {
6
6
  this.bindings = bindings;
7
7
  }
8
8
  async requireBindingAccess(context, appId, catalogId, required) {
9
- if (context.isGodMode) {
9
+ if (context.superAdmin) {
10
10
  // God-mode policy: bypass binding for read/admin checks if needed.
11
11
  // We still return a synthetic binding for uniformity.
12
12
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"authorization.js","sourceRoot":"","sources":["../../../src/catalox/authorization.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAI5D,MAAM,OAAO,oBAAoB;IACF;IAA7B,YAA6B,QAAsB;QAAtB,aAAQ,GAAR,QAAQ,CAAc;IAAG,CAAC;IAEvD,KAAK,CAAC,oBAAoB,CACxB,OAAuB,EACvB,KAAY,EACZ,SAAoB,EACpB,QAAwB;QAExB,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;YACtB,mEAAmE;YACnE,sDAAsD;YACtD,OAAO;gBACL,SAAS,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,EAAE;gBAClD,KAAK;gBACL,SAAS;gBACT,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE;gBACzD,MAAM,EAAE,QAAQ;gBAChB,SAAS,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;gBACpC,SAAS,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;aACrC,CAAC;QACJ,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QACvE,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC5C,MAAM,IAAI,wBAAwB,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,+BAA+B,EAAE,CAAC,CAAC;QACpG,CAAC;QAED,MAAM,EAAE,GACN,QAAQ,KAAK,MAAM;YACjB,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC;YACjC,CAAC,CAAC,QAAQ,KAAK,OAAO;gBACpB,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC;gBAClC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAEzC,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,MAAM,IAAI,wBAAwB,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;QACrE,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;CACF"}
1
+ {"version":3,"file":"authorization.js","sourceRoot":"","sources":["../../../src/catalox/authorization.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAI5D,MAAM,OAAO,oBAAoB;IACF;IAA7B,YAA6B,QAAsB;QAAtB,aAAQ,GAAR,QAAQ,CAAc;IAAG,CAAC;IAEvD,KAAK,CAAC,oBAAoB,CACxB,OAAuB,EACvB,KAAY,EACZ,SAAoB,EACpB,QAAwB;QAExB,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;YACvB,mEAAmE;YACnE,sDAAsD;YACtD,OAAO;gBACL,SAAS,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,EAAE;gBAClD,KAAK;gBACL,SAAS;gBACT,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE;gBACzD,MAAM,EAAE,QAAQ;gBAChB,SAAS,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;gBACpC,SAAS,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;aACrC,CAAC;QACJ,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QACvE,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC5C,MAAM,IAAI,wBAAwB,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,+BAA+B,EAAE,CAAC,CAAC;QACpG,CAAC;QAED,MAAM,EAAE,GACN,QAAQ,KAAK,MAAM;YACjB,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC;YACjC,CAAC,CAAC,QAAQ,KAAK,OAAO;gBACpB,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC;gBAClC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAEzC,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,MAAM,IAAI,wBAAwB,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;QACrE,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;CACF"}
@@ -0,0 +1,29 @@
1
+ import type { CatalogId } from "../contracts/ids.js";
2
+ import type { DeleteCatalogInput, DeleteCatalogResult, RenameCatalogInput, RenameCatalogResult, RestoreDeletedCatalogInput, RestoreDeletedCatalogResult } from "../contracts/catalog-lifecycle.js";
3
+ import type { CataloxContext } from "../contracts/context.js";
4
+ import { AdapterStore } from "../firebase/adapter-store.js";
5
+ import { CatalogDataIndexStore } from "../firebase/catalog-data-index-store.js";
6
+ import { DefinitionStore } from "../firebase/definition-store.js";
7
+ import { DescriptorStore } from "../firebase/descriptor-store.js";
8
+ import { MappingStore } from "../firebase/mapping-store.js";
9
+ import { NativeItemStore } from "../firebase/native-item-store.js";
10
+ import { ReferenceStore } from "../firebase/reference-store.js";
11
+ import { RendererSnippetStore } from "../firebase/renderer-snippet-store.js";
12
+ import { SnapshotStore } from "../firebase/snapshot-store.js";
13
+ import { type BackupDataDeps } from "./backup-data.js";
14
+ import { type RecordHistoryDeps } from "./record-history.js";
15
+ export type CatalogLifecycleDeps = BackupDataDeps & {
16
+ definitions: DefinitionStore;
17
+ descriptors: DescriptorStore;
18
+ mappings: MappingStore;
19
+ adapters: AdapterStore;
20
+ references: ReferenceStore;
21
+ nativeItems: NativeItemStore;
22
+ snapshots: SnapshotStore;
23
+ catalogDataIndex: CatalogDataIndexStore;
24
+ rendererSnippets?: RendererSnippetStore;
25
+ };
26
+ export declare function runDeleteCatalog(deps: CatalogLifecycleDeps, recordHistory: RecordHistoryDeps | undefined, context: CataloxContext, catalogId: CatalogId, input: DeleteCatalogInput): Promise<DeleteCatalogResult>;
27
+ export declare function runRestoreDeletedCatalog(deps: CatalogLifecycleDeps, recordHistory: RecordHistoryDeps, input: RestoreDeletedCatalogInput): Promise<RestoreDeletedCatalogResult>;
28
+ export declare function runRenameCatalog(deps: CatalogLifecycleDeps, recordHistory: RecordHistoryDeps | undefined, context: CataloxContext, fromCatalogId: CatalogId, toCatalogId: CatalogId, input: RenameCatalogInput): Promise<RenameCatalogResult>;
29
+ //# sourceMappingURL=catalog-lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-lifecycle.d.ts","sourceRoot":"","sources":["../../../src/catalox/catalog-lifecycle.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAEV,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,0BAA0B,EAC1B,2BAA2B,EAC5B,MAAM,mCAAmC,CAAC;AAQ3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAQ9D,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAE5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AAEhF,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,qBAAqB,CAAC;AAG7B,MAAM,MAAM,oBAAoB,GAAG,cAAc,GAAG;IAClD,WAAW,EAAE,eAAe,CAAC;IAC7B,WAAW,EAAE,eAAe,CAAC;IAC7B,QAAQ,EAAE,YAAY,CAAC;IACvB,QAAQ,EAAE,YAAY,CAAC;IACvB,UAAU,EAAE,cAAc,CAAC;IAC3B,WAAW,EAAE,eAAe,CAAC;IAC7B,SAAS,EAAE,aAAa,CAAC;IACzB,gBAAgB,EAAE,qBAAqB,CAAC;IACxC,gBAAgB,CAAC,EAAE,oBAAoB,CAAC;CACzC,CAAC;AAwDF,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,oBAAoB,EAC1B,aAAa,EAAE,iBAAiB,GAAG,SAAS,EAC5C,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,mBAAmB,CAAC,CAoL9B;AAED,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,oBAAoB,EAC1B,aAAa,EAAE,iBAAiB,EAChC,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,2BAA2B,CAAC,CAuFtC;AAED,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,oBAAoB,EAC1B,aAAa,EAAE,iBAAiB,GAAG,SAAS,EAC5C,OAAO,EAAE,cAAc,EACvB,aAAa,EAAE,SAAS,EACxB,WAAW,EAAE,SAAS,EACtB,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,mBAAmB,CAAC,CAgM9B"}
@@ -0,0 +1,480 @@
1
+ import { AdapterStore } from "../firebase/adapter-store.js";
2
+ import { BindingStore } from "../firebase/binding-store.js";
3
+ import { CatalogDataIndexStore } from "../firebase/catalog-data-index-store.js";
4
+ import { CatalogStore } from "../firebase/catalog-store.js";
5
+ import { DefinitionStore } from "../firebase/definition-store.js";
6
+ import { DescriptorStore } from "../firebase/descriptor-store.js";
7
+ import { MappingStore } from "../firebase/mapping-store.js";
8
+ import { NativeItemStore } from "../firebase/native-item-store.js";
9
+ import { ReferenceStore } from "../firebase/reference-store.js";
10
+ import { RendererSnippetStore } from "../firebase/renderer-snippet-store.js";
11
+ import { SnapshotStore } from "../firebase/snapshot-store.js";
12
+ import { deleteAllDocsInFirestoreCollection, formatBackupTimestamp, runBackupData, } from "./backup-data.js";
13
+ import { buildRecordHistoryGcsClient, emitRecordHistoryEventWithCustomPath, } from "./record-history.js";
14
+ import { nativeItemsCollectionId } from "../firebase/catalog-data-paths.js";
15
+ function gcsUploadJson(gcs, rel, body) {
16
+ return gcs.uploadObject(rel, JSON.stringify(body, null, 2), { contentType: "application/json", resumable: false });
17
+ }
18
+ function gcsUploadNdjson(gcs, rel, lines) {
19
+ const body = lines.length ? `${lines.join("\n")}\n` : "";
20
+ return gcs.uploadObject(rel, body, { contentType: "application/x-ndjson", resumable: false });
21
+ }
22
+ async function deleteCatalogSnapshotsSubtree(fs, catalogId) {
23
+ const root = fs.collection("catalogSnapshots").doc(String(catalogId));
24
+ const runsSnap = await root.collection("runs").get();
25
+ for (const runDoc of runsSnap.docs) {
26
+ await deleteAllDocsInFirestoreCollection(fs, runDoc.ref.collection("items").path);
27
+ await runDoc.ref.delete();
28
+ }
29
+ await deleteAllDocsInFirestoreCollection(fs, root.collection("items").path);
30
+ await root.delete();
31
+ }
32
+ async function listAllNativeItems(nativeItems, catalogId) {
33
+ const out = [];
34
+ let offset = 0;
35
+ const page = 400;
36
+ while (true) {
37
+ const chunk = await nativeItems.list(catalogId, { limit: page, offset });
38
+ if (!chunk.length)
39
+ break;
40
+ out.push(...chunk);
41
+ offset += chunk.length;
42
+ if (chunk.length < page)
43
+ break;
44
+ }
45
+ return out;
46
+ }
47
+ async function readJsonArtifact(gcs, rel) {
48
+ if (!rel)
49
+ return null;
50
+ try {
51
+ const buf = await gcs.readObjectBuffer(rel);
52
+ return JSON.parse(buf.toString("utf8"));
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ async function readNdjsonLines(gcs, rel) {
59
+ const buf = await gcs.readObjectBuffer(rel);
60
+ const text = buf.toString("utf8").trim();
61
+ if (!text)
62
+ return [];
63
+ return text.split("\n").map((l) => l.trim()).filter(Boolean);
64
+ }
65
+ export async function runDeleteCatalog(deps, recordHistory, context, catalogId, input) {
66
+ const issues = [];
67
+ const cid = String(catalogId);
68
+ if (!recordHistory) {
69
+ return {
70
+ ok: false,
71
+ catalogId,
72
+ issues: [
73
+ {
74
+ code: "record_history_required",
75
+ message: "deleteCatalog requires Catalox `recordHistory` (configure `recordHistory` in createCatalox or set CATALOX_RECORD_HISTORY_BUCKET).",
76
+ },
77
+ ],
78
+ };
79
+ }
80
+ const cat = await deps.catalogs.get(catalogId);
81
+ if (!cat) {
82
+ return { ok: false, catalogId, issues: [{ code: "catalog_not_found", message: `No catalogs/${cid}` }] };
83
+ }
84
+ if (input.gcsBackupBucket?.trim()) {
85
+ const br = await runBackupData(deps, {
86
+ mode: "gcs",
87
+ gcsBucket: input.gcsBackupBucket.trim(),
88
+ ...(input.gcsBackupPrefix != null ? { gcsPrefix: input.gcsBackupPrefix } : {}),
89
+ catalogIds: [catalogId],
90
+ appId: context.appId,
91
+ backupLabel: `pre-delete-${cid}`,
92
+ });
93
+ if (!br.ok) {
94
+ issues.push({ code: "backup_failed", message: br.issues?.[0]?.message ?? "backupData failed" });
95
+ }
96
+ }
97
+ const ts = formatBackupTimestamp();
98
+ const folder = `catalogs/${cid}/${ts}__catalog-delete`;
99
+ const gcs = buildRecordHistoryGcsClient(recordHistory);
100
+ const nativeRows = await listAllNativeItems(deps.nativeItems, catalogId);
101
+ const descriptor = await deps.descriptors.get(catalogId);
102
+ const definition = await deps.definitions.get(catalogId);
103
+ const idx = await deps.catalogDataIndex.get(catalogId);
104
+ const bindings = await deps.bindings.listByCatalog(catalogId);
105
+ const refs = await deps.references.listReferencingCatalog(catalogId);
106
+ const snippets = deps.rendererSnippets ? await deps.rendererSnippets.listByCatalogPrefix(catalogId) : [];
107
+ const snapItems = await deps.snapshots.listAllCurrentItems(catalogId);
108
+ const runs = await deps.snapshots.listRuns(catalogId, 500);
109
+ const runLines = [];
110
+ const runItemLines = [];
111
+ for (const r of runs) {
112
+ runLines.push(JSON.stringify(r));
113
+ const items = await deps.snapshots.listRunItems(catalogId, r.runId);
114
+ for (const it of items) {
115
+ runItemLines.push(JSON.stringify({ runId: r.runId, itemId: it.itemId, record: it }));
116
+ }
117
+ }
118
+ let mappingRec = null;
119
+ let adapterRec = null;
120
+ if (definition?.type === "mapped") {
121
+ const md = definition;
122
+ if (md.mappingId)
123
+ mappingRec = (await deps.mappings.get(md.mappingId));
124
+ if (md.adapterId)
125
+ adapterRec = (await deps.adapters.get(md.adapterId));
126
+ }
127
+ const artifacts = {
128
+ catalog: `${folder}/catalog.json`,
129
+ catalogDataIndex: `${folder}/catalogDataIndex.json`,
130
+ nativeItemsNdjson: `${folder}/native-items.ndjson`,
131
+ bindingsNdjson: `${folder}/bindings.ndjson`,
132
+ referencesNdjson: `${folder}/references.ndjson`,
133
+ rendererSnippetsNdjson: `${folder}/renderer-snippets.ndjson`,
134
+ snapshotItemsNdjson: `${folder}/snapshot-items.ndjson`,
135
+ snapshotRunsNdjson: `${folder}/snapshot-runs.ndjson`,
136
+ snapshotRunItemsNdjson: `${folder}/snapshot-run-items.ndjson`,
137
+ };
138
+ if (descriptor) {
139
+ artifacts.descriptor = `${folder}/descriptor.json`;
140
+ await gcsUploadJson(gcs, artifacts.descriptor, descriptor);
141
+ }
142
+ if (definition) {
143
+ artifacts.definition = `${folder}/definition.json`;
144
+ await gcsUploadJson(gcs, artifacts.definition, definition);
145
+ }
146
+ if (mappingRec) {
147
+ artifacts.mapping = `${folder}/mapping.json`;
148
+ await gcsUploadJson(gcs, artifacts.mapping, mappingRec);
149
+ }
150
+ if (adapterRec) {
151
+ artifacts.adapter = `${folder}/adapter.json`;
152
+ await gcsUploadJson(gcs, artifacts.adapter, adapterRec);
153
+ }
154
+ await gcsUploadJson(gcs, artifacts.catalog, cat);
155
+ if (idx)
156
+ await gcsUploadJson(gcs, artifacts.catalogDataIndex, idx);
157
+ await gcsUploadNdjson(gcs, artifacts.nativeItemsNdjson, nativeRows.map((r) => JSON.stringify(r)));
158
+ await gcsUploadNdjson(gcs, artifacts.bindingsNdjson, bindings.map((b) => JSON.stringify(b)));
159
+ await gcsUploadNdjson(gcs, artifacts.referencesNdjson, refs.map((r) => JSON.stringify(r)));
160
+ await gcsUploadNdjson(gcs, artifacts.rendererSnippetsNdjson, snippets.map((s) => JSON.stringify(s)));
161
+ await gcsUploadNdjson(gcs, artifacts.snapshotItemsNdjson, snapItems.map((s) => JSON.stringify(s)));
162
+ await gcsUploadNdjson(gcs, artifacts.snapshotRunsNdjson, runLines);
163
+ await gcsUploadNdjson(gcs, artifacts.snapshotRunItemsNdjson, runItemLines);
164
+ const manifest = {
165
+ schemaVersion: 1,
166
+ kind: "catalox_catalog_delete",
167
+ catalogId: cid,
168
+ deletedAt: new Date().toISOString(),
169
+ artifacts,
170
+ };
171
+ const manifestRel = `${folder}/manifest.json`;
172
+ await gcsUploadJson(gcs, manifestRel, manifest);
173
+ await emitRecordHistoryEventWithCustomPath(recordHistory, context, {
174
+ gcsRelativePath: `${folder}/catalog-delete-event.ndjson`,
175
+ catalogId,
176
+ itemId: "__catalog__",
177
+ op: "catalog_delete_bulk",
178
+ manifest: { manifestRelativePath: manifestRel, folder },
179
+ });
180
+ const fs = deps.firestoreStore.firestore;
181
+ try {
182
+ await deleteAllDocsInFirestoreCollection(fs, nativeItemsCollectionId(catalogId));
183
+ try {
184
+ await deleteAllDocsInFirestoreCollection(fs, fs.collection("catalogData").doc(cid).collection("items").path);
185
+ }
186
+ catch {
187
+ /* ignore missing legacy */
188
+ }
189
+ await deleteCatalogSnapshotsSubtree(fs, catalogId);
190
+ if (deps.rendererSnippets) {
191
+ for (const s of snippets) {
192
+ const docId = `${String(s.catalogId)}:${String(s.role)}${s.mode ? `:${String(s.mode)}` : ""}`;
193
+ await deps.rendererSnippets.deleteDoc(docId);
194
+ }
195
+ }
196
+ for (const r of refs)
197
+ await deps.references.delete(r.referenceId);
198
+ for (const b of bindings)
199
+ await deps.bindings.delete(b.bindingId);
200
+ if (descriptor)
201
+ await fs.collection("catalogDescriptors").doc(cid).delete();
202
+ if (definition) {
203
+ await fs.collection("catalogDefinitions").doc(cid).delete();
204
+ if (definition.type === "mapped") {
205
+ const md = definition;
206
+ if (md.mappingId)
207
+ await deps.mappings.delete(md.mappingId);
208
+ if (md.adapterId)
209
+ await deps.adapters.delete(md.adapterId);
210
+ }
211
+ }
212
+ await deps.catalogDataIndex.delete(catalogId);
213
+ await deps.catalogs.delete(catalogId);
214
+ }
215
+ catch (e) {
216
+ issues.push({ code: "firestore_delete_failed", message: e instanceof Error ? e.message : String(e) });
217
+ return { ok: false, catalogId, manifestRelativePath: manifestRel, issues };
218
+ }
219
+ deps.nativeItems.invalidateLayoutCache?.(catalogId);
220
+ return { ok: true, catalogId, manifestRelativePath: manifestRel, issues };
221
+ }
222
+ export async function runRestoreDeletedCatalog(deps, recordHistory, input) {
223
+ const issues = [];
224
+ const gcs = buildRecordHistoryGcsClient(recordHistory);
225
+ const rel = input.gcsManifestRelativePath.replace(/^\/+/, "");
226
+ let manifest;
227
+ try {
228
+ const raw = await gcs.readObjectBuffer(rel);
229
+ manifest = JSON.parse(raw.toString("utf8"));
230
+ }
231
+ catch (e) {
232
+ return {
233
+ ok: false,
234
+ catalogId: "",
235
+ issues: [{ code: "manifest_read_failed", message: e instanceof Error ? e.message : String(e) }],
236
+ };
237
+ }
238
+ const catalogId = manifest.catalogId;
239
+ const cid = String(catalogId);
240
+ try {
241
+ const cat = (await readJsonArtifact(gcs, manifest.artifacts.catalog));
242
+ if (cat)
243
+ await deps.catalogs.upsert(cat);
244
+ const desc = (await readJsonArtifact(gcs, manifest.artifacts.descriptor));
245
+ if (desc)
246
+ await deps.descriptors.upsert(desc);
247
+ const def = (await readJsonArtifact(gcs, manifest.artifacts.definition));
248
+ const map = manifest.artifacts.mapping
249
+ ? (await readJsonArtifact(gcs, manifest.artifacts.mapping))
250
+ : null;
251
+ const ad = manifest.artifacts.adapter
252
+ ? (await readJsonArtifact(gcs, manifest.artifacts.adapter))
253
+ : null;
254
+ if (ad)
255
+ await deps.adapters.upsert(ad);
256
+ if (map)
257
+ await deps.mappings.upsert(map);
258
+ if (def)
259
+ await deps.definitions.upsert(def);
260
+ const cdi = (await readJsonArtifact(gcs, manifest.artifacts.catalogDataIndex));
261
+ if (cdi)
262
+ await deps.catalogDataIndex.upsert(cdi);
263
+ const nativeLines = await readNdjsonLines(gcs, manifest.artifacts.nativeItemsNdjson);
264
+ for (const line of nativeLines) {
265
+ const rec = JSON.parse(line);
266
+ await deps.nativeItems.upsert(catalogId, rec);
267
+ }
268
+ const bindLines = await readNdjsonLines(gcs, manifest.artifacts.bindingsNdjson);
269
+ for (const line of bindLines) {
270
+ await deps.bindings.upsert(JSON.parse(line));
271
+ }
272
+ const refLines = await readNdjsonLines(gcs, manifest.artifacts.referencesNdjson);
273
+ for (const line of refLines) {
274
+ await deps.references.upsert(JSON.parse(line));
275
+ }
276
+ if (deps.rendererSnippets) {
277
+ const snipLines = await readNdjsonLines(gcs, manifest.artifacts.rendererSnippetsNdjson);
278
+ for (const line of snipLines) {
279
+ await deps.rendererSnippets.upsert(JSON.parse(line));
280
+ }
281
+ }
282
+ const fs = deps.firestoreStore.firestore;
283
+ await fs.collection("catalogSnapshots").doc(cid).set({ catalogId: cid, restoredAt: new Date().toISOString() }, { merge: true });
284
+ const runLs = await readNdjsonLines(gcs, manifest.artifacts.snapshotRunsNdjson);
285
+ for (const line of runLs) {
286
+ const run = JSON.parse(line);
287
+ await deps.snapshots.upsertRun(catalogId, run);
288
+ }
289
+ const runItemLs = await readNdjsonLines(gcs, manifest.artifacts.snapshotRunItemsNdjson);
290
+ for (const line of runItemLs) {
291
+ const { runId, record } = JSON.parse(line);
292
+ await deps.snapshots.upsertRunItem(catalogId, runId, record);
293
+ }
294
+ const snapLs = await readNdjsonLines(gcs, manifest.artifacts.snapshotItemsNdjson);
295
+ for (const line of snapLs) {
296
+ const rec = JSON.parse(line);
297
+ await deps.snapshots.upsert(catalogId, rec);
298
+ }
299
+ deps.nativeItems.invalidateLayoutCache?.(catalogId);
300
+ return { ok: true, catalogId, issues };
301
+ }
302
+ catch (e) {
303
+ issues.push({ code: "restore_failed", message: e instanceof Error ? e.message : String(e) });
304
+ return { ok: false, catalogId, issues };
305
+ }
306
+ }
307
+ export async function runRenameCatalog(deps, recordHistory, context, fromCatalogId, toCatalogId, input) {
308
+ const issues = [];
309
+ const from = String(fromCatalogId);
310
+ const to = String(toCatalogId);
311
+ if (!recordHistory) {
312
+ return {
313
+ ok: false,
314
+ fromCatalogId,
315
+ toCatalogId,
316
+ issues: [
317
+ {
318
+ code: "record_history_required",
319
+ message: "renameCatalog requires Catalox `recordHistory` (configure `recordHistory` in createCatalox or set CATALOX_RECORD_HISTORY_BUCKET).",
320
+ },
321
+ ],
322
+ };
323
+ }
324
+ if (from === to) {
325
+ return { ok: false, fromCatalogId, toCatalogId, issues: [{ code: "same_id", message: "from and to catalog ids must differ" }] };
326
+ }
327
+ const existingTo = await deps.catalogs.get(toCatalogId);
328
+ if (existingTo) {
329
+ return {
330
+ ok: false,
331
+ fromCatalogId,
332
+ toCatalogId,
333
+ issues: [{ code: "target_exists", message: `catalogs/${to} already exists` }],
334
+ };
335
+ }
336
+ const srcCat = await deps.catalogs.get(fromCatalogId);
337
+ if (!srcCat) {
338
+ return { ok: false, fromCatalogId, toCatalogId, issues: [{ code: "source_missing", message: `catalogs/${from} not found` }] };
339
+ }
340
+ if (input.gcsBackupBucket?.trim()) {
341
+ await runBackupData(deps, {
342
+ mode: "gcs",
343
+ gcsBucket: input.gcsBackupBucket.trim(),
344
+ ...(input.gcsBackupPrefix != null ? { gcsPrefix: input.gcsBackupPrefix } : {}),
345
+ catalogIds: [fromCatalogId],
346
+ appId: context.appId,
347
+ backupLabel: `pre-rename-${from}-to-${to}`,
348
+ });
349
+ }
350
+ const ts = formatBackupTimestamp();
351
+ const folder = `catalogs/${from}__to__${to}/${ts}__catalog-rename`;
352
+ const gcs = buildRecordHistoryGcsClient(recordHistory);
353
+ const nativeCount = (await listAllNativeItems(deps.nativeItems, fromCatalogId)).length;
354
+ const manifest = {
355
+ schemaVersion: 1,
356
+ kind: "catalox_catalog_rename",
357
+ fromCatalogId: from,
358
+ toCatalogId: to,
359
+ renamedAt: new Date().toISOString(),
360
+ folder,
361
+ nativeItemCount: nativeCount,
362
+ };
363
+ const manifestRel = `${folder}/manifest.json`;
364
+ await gcsUploadJson(gcs, manifestRel, manifest);
365
+ await emitRecordHistoryEventWithCustomPath(recordHistory, context, {
366
+ gcsRelativePath: `${folder}/catalog-rename-event.ndjson`,
367
+ catalogId: toCatalogId,
368
+ itemId: "__catalog__",
369
+ op: "catalog_rename",
370
+ manifest,
371
+ });
372
+ const fs = deps.firestoreStore.firestore;
373
+ try {
374
+ const nextCat = { ...srcCat, catalogId: toCatalogId, updatedAt: new Date().toISOString() };
375
+ await deps.catalogs.upsert(nextCat);
376
+ const desc = await deps.descriptors.get(fromCatalogId);
377
+ if (desc) {
378
+ const next = {
379
+ ...desc,
380
+ catalogId: toCatalogId,
381
+ descriptor: { ...desc.descriptor, catalogId: toCatalogId },
382
+ updatedAt: new Date().toISOString(),
383
+ };
384
+ await deps.descriptors.upsert(next);
385
+ await fs.collection("catalogDescriptors").doc(from).delete();
386
+ }
387
+ const def = await deps.definitions.get(fromCatalogId);
388
+ if (def) {
389
+ let nextDef = {
390
+ ...def,
391
+ catalogId: toCatalogId,
392
+ updatedAt: new Date().toISOString(),
393
+ };
394
+ if (def.type === "native") {
395
+ const n = def;
396
+ nextDef = {
397
+ ...n,
398
+ catalogId: toCatalogId,
399
+ storage: { ...n.storage, collectionPath: nativeItemsCollectionId(toCatalogId) },
400
+ updatedAt: new Date().toISOString(),
401
+ };
402
+ }
403
+ await deps.definitions.upsert(nextDef);
404
+ await fs.collection("catalogDefinitions").doc(from).delete();
405
+ }
406
+ const cdi = await deps.catalogDataIndex.get(fromCatalogId);
407
+ if (cdi) {
408
+ await deps.catalogDataIndex.upsert({ ...cdi, catalogId: toCatalogId, updatedAt: new Date().toISOString() });
409
+ await deps.catalogDataIndex.delete(fromCatalogId);
410
+ }
411
+ const rows = await listAllNativeItems(deps.nativeItems, fromCatalogId);
412
+ for (const r of rows) {
413
+ const next = { ...r, catalogId: toCatalogId };
414
+ await deps.nativeItems.upsert(toCatalogId, next);
415
+ }
416
+ await deleteAllDocsInFirestoreCollection(fs, nativeItemsCollectionId(fromCatalogId));
417
+ try {
418
+ await deleteAllDocsInFirestoreCollection(fs, fs.collection("catalogData").doc(from).collection("items").path);
419
+ }
420
+ catch {
421
+ /* ignore */
422
+ }
423
+ const runs = await deps.snapshots.listRuns(fromCatalogId, 500);
424
+ for (const run of runs) {
425
+ await deps.snapshots.upsertRun(toCatalogId, { ...run, catalogId: toCatalogId });
426
+ const items = await deps.snapshots.listRunItems(fromCatalogId, run.runId);
427
+ for (const it of items) {
428
+ await deps.snapshots.upsertRunItem(toCatalogId, run.runId, { ...it, catalogId: toCatalogId });
429
+ }
430
+ }
431
+ const snapItems = await deps.snapshots.listAllCurrentItems(fromCatalogId);
432
+ for (const s of snapItems) {
433
+ await deps.snapshots.upsert(toCatalogId, { ...s, catalogId: toCatalogId });
434
+ }
435
+ await deleteCatalogSnapshotsSubtree(fs, fromCatalogId);
436
+ if (deps.rendererSnippets) {
437
+ const snippets = await deps.rendererSnippets.listByCatalogPrefix(fromCatalogId);
438
+ for (const s of snippets) {
439
+ const oldId = `${String(s.catalogId)}:${String(s.role)}${s.mode ? `:${String(s.mode)}` : ""}`;
440
+ const next = { ...s, catalogId: toCatalogId };
441
+ await deps.rendererSnippets.upsert(next);
442
+ await deps.rendererSnippets.deleteDoc(oldId);
443
+ }
444
+ }
445
+ const refs = await deps.references.listReferencingCatalog(fromCatalogId);
446
+ for (const r of refs) {
447
+ const fc = r.fromCatalogId === from ? to : String(r.fromCatalogId);
448
+ const tc = r.toCatalogId === from ? to : String(r.toCatalogId);
449
+ const nextRefId = `${fc}:${r.fromItemId}:${r.relationType}:${tc}:${r.toItemId}`;
450
+ await deps.references.delete(r.referenceId);
451
+ await deps.references.upsert({
452
+ ...r,
453
+ referenceId: nextRefId,
454
+ fromCatalogId: fc,
455
+ toCatalogId: tc,
456
+ updatedAt: new Date().toISOString(),
457
+ });
458
+ }
459
+ const binds = await deps.bindings.listByCatalog(fromCatalogId);
460
+ for (const b of binds) {
461
+ await deps.bindings.delete(b.bindingId);
462
+ const nb = {
463
+ ...b,
464
+ catalogId: toCatalogId,
465
+ bindingId: `${String(b.appId)}:${to}`,
466
+ updatedAt: new Date().toISOString(),
467
+ };
468
+ await deps.bindings.upsert(nb);
469
+ }
470
+ await deps.catalogs.delete(fromCatalogId);
471
+ deps.nativeItems.invalidateLayoutCache?.(fromCatalogId);
472
+ deps.nativeItems.invalidateLayoutCache?.(toCatalogId);
473
+ return { ok: true, fromCatalogId, toCatalogId, manifestRelativePath: manifestRel, issues };
474
+ }
475
+ catch (e) {
476
+ issues.push({ code: "rename_failed", message: e instanceof Error ? e.message : String(e) });
477
+ return { ok: false, fromCatalogId, toCatalogId, manifestRelativePath: manifestRel, issues };
478
+ }
479
+ }
480
+ //# sourceMappingURL=catalog-lifecycle.js.map