@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
@@ -0,0 +1,184 @@
1
+ import { CatalogAccessDeniedError } from "../contracts/errors.js";
2
+ import { resolveCatalogItemId } from "./identity.js";
3
+ const CX = "cx~";
4
+ function b64u(s) {
5
+ return Buffer.from(s, "utf8").toString("base64url");
6
+ }
7
+ function b64uDec(s) {
8
+ return Buffer.from(s, "base64url").toString("utf8");
9
+ }
10
+ /** Global / legacy rows use the logical id as the Firestore document id. */
11
+ export function encodeNativeItemStorageDocId(logicalItemId, scope) {
12
+ if (scope.kind === "global")
13
+ return String(logicalItemId);
14
+ const log = b64u(String(logicalItemId));
15
+ if (scope.kind === "account") {
16
+ return `${CX}a~${b64u(scope.accountId)}~${log}`;
17
+ }
18
+ if (scope.kind === "agent") {
19
+ return `${CX}ag~${b64u(scope.accountId)}~${b64u(scope.agentId)}~${log}`;
20
+ }
21
+ return `${CX}u~${b64u(scope.accountId)}~${b64u(scope.userId)}~${log}`;
22
+ }
23
+ export function decodeNativeItemStorageDocId(docId) {
24
+ if (!docId.startsWith(CX)) {
25
+ return { logicalItemId: docId, scope: { kind: "global" } };
26
+ }
27
+ const rest = docId.slice(CX.length);
28
+ const parts = rest.split("~");
29
+ if (parts[0] === "a" && parts.length === 3) {
30
+ return { logicalItemId: b64uDec(parts[2]), scope: { kind: "account", accountId: b64uDec(parts[1]) } };
31
+ }
32
+ if (parts[0] === "ag" && parts.length === 4) {
33
+ return {
34
+ logicalItemId: b64uDec(parts[3]),
35
+ scope: { kind: "agent", accountId: b64uDec(parts[1]), agentId: b64uDec(parts[2]) },
36
+ };
37
+ }
38
+ if (parts[0] === "u" && parts.length === 4) {
39
+ return {
40
+ logicalItemId: b64uDec(parts[3]),
41
+ scope: { kind: "user", accountId: b64uDec(parts[1]), userId: b64uDec(parts[2]) },
42
+ };
43
+ }
44
+ return null;
45
+ }
46
+ export function normalizeStoredScope(s) {
47
+ if (!s || s.kind === "global")
48
+ return { kind: "global" };
49
+ return s;
50
+ }
51
+ export function isGlobalPhysicalRow(r) {
52
+ const sl = r.indexed?.scopeLayer;
53
+ if (sl != null && sl !== "global")
54
+ return false;
55
+ return normalizeStoredScope(scopeFromRecordField(r.scope)).kind === "global";
56
+ }
57
+ export function scopeFromRecordField(scope) {
58
+ if (!scope || typeof scope !== "object")
59
+ return { kind: "global" };
60
+ const o = scope;
61
+ const k = o.kind;
62
+ if (k === "account" && typeof o.accountId === "string") {
63
+ return {
64
+ kind: "account",
65
+ accountId: o.accountId,
66
+ ...(Array.isArray(o.agentIds) ? { agentIds: o.agentIds.filter((x) => typeof x === "string") } : {}),
67
+ ...(Array.isArray(o.userIds) ? { userIds: o.userIds.filter((x) => typeof x === "string") } : {}),
68
+ };
69
+ }
70
+ if (k === "agent" && typeof o.accountId === "string" && typeof o.agentId === "string") {
71
+ return { kind: "agent", accountId: o.accountId, agentId: o.agentId };
72
+ }
73
+ if (k === "user" && typeof o.accountId === "string" && typeof o.userId === "string") {
74
+ return { kind: "user", accountId: o.accountId, userId: o.userId };
75
+ }
76
+ return { kind: "global" };
77
+ }
78
+ /** Indexed mirror for Firestore `indexed.*` queries. */
79
+ export function indexedScopeFields(scope) {
80
+ const s = normalizeStoredScope(scope);
81
+ if (s.kind === "global")
82
+ return { scopeLayer: "global" };
83
+ if (s.kind === "account") {
84
+ return { scopeLayer: "account", scopeAccountId: s.accountId };
85
+ }
86
+ if (s.kind === "agent") {
87
+ return {
88
+ scopeLayer: "agent",
89
+ scopeAccountId: s.accountId,
90
+ scopeAgentId: s.agentId,
91
+ };
92
+ }
93
+ return {
94
+ scopeLayer: "user",
95
+ scopeAccountId: s.accountId,
96
+ scopeUserId: s.userId,
97
+ };
98
+ }
99
+ export function accountAudienceMatches(scope, fetch) {
100
+ const agents = scope.agentIds ?? [];
101
+ const users = scope.userIds ?? [];
102
+ if (agents.length === 0 && users.length === 0)
103
+ return true;
104
+ if (fetch.agentId && agents.includes(fetch.agentId))
105
+ return true;
106
+ if (fetch.userId && users.includes(fetch.userId))
107
+ return true;
108
+ return false;
109
+ }
110
+ export function physicalRowMatchesFetchScope(scope, fetch) {
111
+ const s = normalizeStoredScope(scope);
112
+ if (s.kind === "global")
113
+ return true;
114
+ if (!fetch.accountId)
115
+ return false;
116
+ if (s.kind === "account") {
117
+ if (s.accountId !== fetch.accountId)
118
+ return false;
119
+ return accountAudienceMatches(s, fetch);
120
+ }
121
+ if (s.kind === "agent") {
122
+ if (s.accountId !== fetch.accountId)
123
+ return false;
124
+ if (!fetch.agentId)
125
+ return false;
126
+ return s.agentId === fetch.agentId;
127
+ }
128
+ if (s.kind === "user") {
129
+ if (s.accountId !== fetch.accountId)
130
+ return false;
131
+ if (!fetch.userId)
132
+ return false;
133
+ return s.userId === fetch.userId;
134
+ }
135
+ return false;
136
+ }
137
+ export function resolveMergeIdentityMode(identity) {
138
+ if (identity.overrideKeys?.length)
139
+ return "byOverrideKeys";
140
+ if (identity.itemIdStrategy === "natural" || identity.itemIdStrategy === "composite")
141
+ return "byItemId";
142
+ return "none";
143
+ }
144
+ export function mergeIdentityKey(identity, data, indexed) {
145
+ const mode = resolveMergeIdentityMode(identity);
146
+ if (mode === "none")
147
+ return null;
148
+ if (mode === "byItemId") {
149
+ try {
150
+ return resolveCatalogItemId({ identity, data });
151
+ }
152
+ catch {
153
+ return null;
154
+ }
155
+ }
156
+ const keys = identity.overrideKeys ?? [];
157
+ const parts = keys.map((k) => {
158
+ const v = data[k] ?? indexed?.[k];
159
+ if (v == null || v === "")
160
+ return null;
161
+ return String(v);
162
+ });
163
+ if (parts.some((p) => p == null))
164
+ return null;
165
+ return parts.join(":");
166
+ }
167
+ export function shallowMergeData(base, overlay) {
168
+ return { ...base, ...overlay };
169
+ }
170
+ export function parseWriteScopeInput(raw) {
171
+ if (raw == null)
172
+ return undefined;
173
+ if (typeof raw !== "object")
174
+ return undefined;
175
+ return scopeFromRecordField(raw);
176
+ }
177
+ export function assertSuperAdminForNonGlobalScope(superAdmin, scope) {
178
+ if (normalizeStoredScope(scope).kind === "global")
179
+ return;
180
+ if (!superAdmin) {
181
+ throw new CatalogAccessDeniedError({ reason: "super_admin_required_for_scoped_write" });
182
+ }
183
+ }
184
+ //# sourceMappingURL=native-scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-scope.js","sourceRoot":"","sources":["../../../src/catalox/native-scope.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ import type { CatalogId } from "../contracts/ids.js";
2
+ import type { NativeCatalogItemRecord } from "../contracts/items.js";
3
+ import type { CatalogItemHistoryEventV1, CatalogItemHistoryIndexRecord, CatalogItemHistoryOp } from "../contracts/record-history.js";
4
+ import type { CataloxContext } from "../contracts/context.js";
5
+ import { CatalogItemHistoryStore } from "../firebase/catalog-item-history-store.js";
6
+ import { type CataloxHelpersGcsClient } from "./backup-data.js";
7
+ export type RecordHistoryDeps = {
8
+ store: CatalogItemHistoryStore;
9
+ gcsBucket: string;
10
+ /** Prefix without leading slash; no trailing slash (helpers client normalizes). */
11
+ gcsBasePrefix: string;
12
+ failClosed: boolean;
13
+ };
14
+ export declare function normalizeRecordHistoryGcsPrefix(raw: string | undefined): string;
15
+ export declare function fingerprintNativeRecord(rec: NativeCatalogItemRecord | null | undefined): string | undefined;
16
+ /** Relative object path under the record-history prefix. */
17
+ export declare function buildRecordHistoryGcsRelativePath(params: {
18
+ catalogId: CatalogId;
19
+ op: CatalogItemHistoryOp;
20
+ itemId: string;
21
+ ts: string;
22
+ }): string;
23
+ export declare function buildRecordHistoryGcsClient(deps: RecordHistoryDeps): CataloxHelpersGcsClient;
24
+ export type EmitRecordHistoryParams = {
25
+ catalogId: CatalogId;
26
+ itemId: string;
27
+ storageDocId?: string;
28
+ op: CatalogItemHistoryOp;
29
+ before?: NativeCatalogItemRecord | null;
30
+ after?: NativeCatalogItemRecord | null;
31
+ manifest?: Record<string, unknown>;
32
+ };
33
+ export type EmitRecordHistoryResult = {
34
+ ok: true;
35
+ eventId: string;
36
+ } | {
37
+ ok: false;
38
+ error: string;
39
+ };
40
+ export declare function emitRecordHistoryEvent(deps: RecordHistoryDeps, context: CataloxContext, params: EmitRecordHistoryParams): Promise<EmitRecordHistoryResult>;
41
+ export declare function readRecordHistoryEventPayload(deps: RecordHistoryDeps, indexRow: CatalogItemHistoryIndexRecord): Promise<CatalogItemHistoryEventV1>;
42
+ export declare function emitRecordHistoryEventWithCustomPath(deps: RecordHistoryDeps, context: CataloxContext, params: {
43
+ gcsRelativePath: string;
44
+ catalogId: CatalogId;
45
+ itemId: string;
46
+ storageDocId?: string;
47
+ op: CatalogItemHistoryOp;
48
+ before?: NativeCatalogItemRecord | null;
49
+ after?: NativeCatalogItemRecord | null;
50
+ manifest?: Record<string, unknown>;
51
+ }): Promise<EmitRecordHistoryResult>;
52
+ export declare function recordHistoryKeyForNativeRow(_catalogId: CatalogId, rec: NativeCatalogItemRecord): string;
53
+ //# sourceMappingURL=record-history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record-history.d.ts","sourceRoot":"","sources":["../../../src/catalox/record-history.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,KAAK,EACV,yBAAyB,EACzB,6BAA6B,EAC7B,oBAAoB,EACrB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,2CAA2C,CAAC;AAEpF,OAAO,EAAiC,KAAK,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAE/F,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,uBAAuB,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,mFAAmF;IACnF,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,wBAAgB,+BAA+B,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAI/E;AAgBD,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,uBAAuB,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAG3G;AAMD,4DAA4D;AAC5D,wBAAgB,iCAAiC,CAAC,MAAM,EAAE;IACxD,SAAS,EAAE,SAAS,CAAC;IACrB,EAAE,EAAE,oBAAoB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;CACZ,GAAG,MAAM,CAQT;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,iBAAiB,GAAG,uBAAuB,CAE5F;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,EAAE,EAAE,oBAAoB,CAAC;IACzB,MAAM,CAAC,EAAE,uBAAuB,GAAG,IAAI,CAAC;IACxC,KAAK,CAAC,EAAE,uBAAuB,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnG,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,iBAAiB,EACvB,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,uBAAuB,CAAC,CAsDlC;AAED,wBAAsB,6BAA6B,CACjD,IAAI,EAAE,iBAAiB,EACvB,QAAQ,EAAE,6BAA6B,GACtC,OAAO,CAAC,yBAAyB,CAAC,CAMpC;AAED,wBAAsB,oCAAoC,CACxD,IAAI,EAAE,iBAAiB,EACvB,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE;IACN,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,EAAE,EAAE,oBAAoB,CAAC;IACzB,MAAM,CAAC,EAAE,uBAAuB,GAAG,IAAI,CAAC;IACxC,KAAK,CAAC,EAAE,uBAAuB,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,GACA,OAAO,CAAC,uBAAuB,CAAC,CA2ClC;AAED,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,SAAS,EAAE,GAAG,EAAE,uBAAuB,GAAG,MAAM,CAExG"}
@@ -0,0 +1,158 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { CatalogItemHistoryStore } from "../firebase/catalog-item-history-store.js";
3
+ import { storageDocIdForNativeRecord } from "../firebase/native-item-store.js";
4
+ import { createCataloxHelpersGcsClient } from "./backup-data.js";
5
+ export function normalizeRecordHistoryGcsPrefix(raw) {
6
+ const p = (raw ?? "catalox-record-history/").trim();
7
+ if (!p)
8
+ return "catalox-record-history";
9
+ return p.replace(/^\/+/, "").replace(/\/+$/, "");
10
+ }
11
+ function stableStringify(value) {
12
+ const seen = new WeakSet();
13
+ const walk = (v) => {
14
+ if (v == null || typeof v !== "object")
15
+ return v;
16
+ if (seen.has(v))
17
+ return "[Circular]";
18
+ seen.add(v);
19
+ if (Array.isArray(v))
20
+ return v.map(walk);
21
+ const out = {};
22
+ for (const k of Object.keys(v).sort())
23
+ out[k] = walk(v[k]);
24
+ return out;
25
+ };
26
+ return JSON.stringify(walk(value));
27
+ }
28
+ export function fingerprintNativeRecord(rec) {
29
+ if (!rec)
30
+ return undefined;
31
+ return createHash("sha256").update(stableStringify(rec)).digest("hex");
32
+ }
33
+ function hashItemIdSegment(itemId) {
34
+ return createHash("sha256").update(String(itemId)).digest("hex").slice(0, 16);
35
+ }
36
+ /** Relative object path under the record-history prefix. */
37
+ export function buildRecordHistoryGcsRelativePath(params) {
38
+ const d = new Date(params.ts);
39
+ const yyyy = String(d.getUTCFullYear());
40
+ const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
41
+ const dd = String(d.getUTCDate()).padStart(2, "0");
42
+ const idHash = hashItemIdSegment(params.itemId);
43
+ const safeTs = params.ts.replace(/[/\\:]/g, "_");
44
+ return `records/${String(params.catalogId)}/${yyyy}/${mm}/${dd}/${safeTs}__${params.op}__${idHash}.ndjson`;
45
+ }
46
+ export function buildRecordHistoryGcsClient(deps) {
47
+ return createCataloxHelpersGcsClient(deps.gcsBucket, deps.gcsBasePrefix);
48
+ }
49
+ export async function emitRecordHistoryEvent(deps, context, params) {
50
+ const eventId = randomUUID();
51
+ const ts = new Date().toISOString();
52
+ const actorId = context.userId ?? context.actor?.id;
53
+ const gcsRelativePath = buildRecordHistoryGcsRelativePath({
54
+ catalogId: params.catalogId,
55
+ op: params.op,
56
+ itemId: params.itemId,
57
+ ts,
58
+ });
59
+ const fpBefore = fingerprintNativeRecord(params.before ?? undefined);
60
+ const fpAfter = fingerprintNativeRecord(params.after ?? undefined);
61
+ const payload = {
62
+ schemaVersion: 1,
63
+ eventId,
64
+ op: params.op,
65
+ catalogId: params.catalogId,
66
+ itemId: params.itemId,
67
+ ...(params.storageDocId != null ? { storageDocId: params.storageDocId } : {}),
68
+ ...(actorId ? { actorId } : {}),
69
+ ts,
70
+ ...(fpBefore != null ? { fingerprintBefore: fpBefore } : {}),
71
+ ...(fpAfter != null ? { fingerprintAfter: fpAfter } : {}),
72
+ ...(params.before !== undefined ? { before: params.before } : {}),
73
+ ...(params.after !== undefined ? { after: params.after } : {}),
74
+ ...(params.manifest != null ? { manifest: params.manifest } : {}),
75
+ };
76
+ const line = `${JSON.stringify(payload)}\n`;
77
+ try {
78
+ const gcs = buildRecordHistoryGcsClient(deps);
79
+ await gcs.uploadObject(gcsRelativePath, line, { contentType: "application/x-ndjson", resumable: false });
80
+ const indexRow = {
81
+ eventId,
82
+ catalogId: params.catalogId,
83
+ itemId: params.itemId,
84
+ op: params.op,
85
+ ...(actorId ? { actorId } : {}),
86
+ ts,
87
+ gcsRelativePath,
88
+ ...(fpBefore != null ? { fingerprintBefore: fpBefore } : {}),
89
+ ...(fpAfter != null ? { fingerprintAfter: fpAfter } : {}),
90
+ ...(params.storageDocId != null ? { storageDocId: params.storageDocId } : {}),
91
+ };
92
+ await deps.store.upsertIndex(indexRow);
93
+ return { ok: true, eventId };
94
+ }
95
+ catch (e) {
96
+ const msg = e instanceof Error ? e.message : String(e);
97
+ if (deps.failClosed)
98
+ throw e;
99
+ return { ok: false, error: msg };
100
+ }
101
+ }
102
+ export async function readRecordHistoryEventPayload(deps, indexRow) {
103
+ const gcs = buildRecordHistoryGcsClient(deps);
104
+ const buf = await gcs.readObjectBuffer(indexRow.gcsRelativePath);
105
+ const text = buf.toString("utf8").trim();
106
+ const firstLine = text.split("\n")[0] ?? text;
107
+ return JSON.parse(firstLine);
108
+ }
109
+ export async function emitRecordHistoryEventWithCustomPath(deps, context, params) {
110
+ const eventId = randomUUID();
111
+ const ts = new Date().toISOString();
112
+ const actorId = context.userId ?? context.actor?.id;
113
+ const fpBefore2 = fingerprintNativeRecord(params.before ?? undefined);
114
+ const fpAfter2 = fingerprintNativeRecord(params.after ?? undefined);
115
+ const payload = {
116
+ schemaVersion: 1,
117
+ eventId,
118
+ op: params.op,
119
+ catalogId: params.catalogId,
120
+ itemId: params.itemId,
121
+ ...(params.storageDocId != null ? { storageDocId: params.storageDocId } : {}),
122
+ ...(actorId ? { actorId } : {}),
123
+ ts,
124
+ ...(fpBefore2 != null ? { fingerprintBefore: fpBefore2 } : {}),
125
+ ...(fpAfter2 != null ? { fingerprintAfter: fpAfter2 } : {}),
126
+ ...(params.before !== undefined ? { before: params.before } : {}),
127
+ ...(params.after !== undefined ? { after: params.after } : {}),
128
+ ...(params.manifest != null ? { manifest: params.manifest } : {}),
129
+ };
130
+ const line = `${JSON.stringify(payload)}\n`;
131
+ try {
132
+ const gcs = buildRecordHistoryGcsClient(deps);
133
+ await gcs.uploadObject(params.gcsRelativePath, line, { contentType: "application/x-ndjson", resumable: false });
134
+ await deps.store.upsertIndex({
135
+ eventId,
136
+ catalogId: params.catalogId,
137
+ itemId: params.itemId,
138
+ op: params.op,
139
+ ...(actorId ? { actorId } : {}),
140
+ ts,
141
+ gcsRelativePath: params.gcsRelativePath,
142
+ ...(fpBefore2 != null ? { fingerprintBefore: fpBefore2 } : {}),
143
+ ...(fpAfter2 != null ? { fingerprintAfter: fpAfter2 } : {}),
144
+ ...(params.storageDocId != null ? { storageDocId: params.storageDocId } : {}),
145
+ });
146
+ return { ok: true, eventId };
147
+ }
148
+ catch (e) {
149
+ const msg = e instanceof Error ? e.message : String(e);
150
+ if (deps.failClosed)
151
+ throw e;
152
+ return { ok: false, error: msg };
153
+ }
154
+ }
155
+ export function recordHistoryKeyForNativeRow(_catalogId, rec) {
156
+ return storageDocIdForNativeRecord(rec);
157
+ }
158
+ //# sourceMappingURL=record-history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record-history.js","sourceRoot":"","sources":["../../../src/catalox/record-history.ts"],"names":[],"mappings":"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"}
@@ -49,7 +49,7 @@ function baseContext(opts) {
49
49
  return {
50
50
  appId,
51
51
  ...(storeId ? { storeId } : {}),
52
- ...(opts.god ? { isGodMode: true } : {}),
52
+ ...(opts.god ? { superAdmin: true } : {}),
53
53
  ...(userId ? { userId, actor: { type: "user", id: userId } } : {}),
54
54
  };
55
55
  }
@@ -669,6 +669,138 @@ firestoreCmd
669
669
  if (!res.ok)
670
670
  process.exitCode = 1;
671
671
  });
672
+ const historyCmd = program.command("history").description("Native item history (Firestore index + GCS payloads)");
673
+ historyCmd
674
+ .command("list")
675
+ .description("List catalogItemHistory rows for a catalog (newest first)")
676
+ .requiredOption("--app <appId>", "AppId for authz")
677
+ .requiredOption("--catalog <catalogId>", "Catalog id")
678
+ .option("--item <itemId>", "Filter by logical item id")
679
+ .option("--op <op>", "Filter by op (update|delete|restore|catalog_delete_bulk|catalog_rename)")
680
+ .option("--since <iso>", "Client-side filter: ts >= since")
681
+ .option("--until <iso>", "Client-side filter: ts <= until")
682
+ .option("--limit <n>", "Page size", "50")
683
+ .option("--cursor <eventId>", "startAfterEventId for pagination")
684
+ .option("--god", "God mode", false)
685
+ .action(async (opts) => {
686
+ const catalox = createCatalox();
687
+ const ctx = baseContext({ app: opts.app, god: opts.god });
688
+ const limit = Math.max(1, Math.min(500, parseInt(String(opts.limit ?? "50"), 10) || 50));
689
+ const res = await catalox.listCatalogItemHistory(ctx, String(opts.catalog), {
690
+ ...(opts.item ? { itemId: String(opts.item) } : {}),
691
+ ...(opts.op ? { op: String(opts.op) } : {}),
692
+ ...(opts.since ? { since: String(opts.since) } : {}),
693
+ ...(opts.until ? { until: String(opts.until) } : {}),
694
+ limit,
695
+ ...(opts.cursor ? { startAfterEventId: String(opts.cursor) } : {}),
696
+ });
697
+ await writeOrStdout(JSON.stringify(res, null, 2));
698
+ });
699
+ historyCmd
700
+ .command("show")
701
+ .description("Load index row + GCS payload for one eventId")
702
+ .requiredOption("--app <appId>", "AppId for authz")
703
+ .argument("<eventId>", "catalogItemHistory document id")
704
+ .option("--god", "God mode", false)
705
+ .action(async (eventId, opts) => {
706
+ const catalox = createCatalox();
707
+ const ctx = baseContext({ app: opts.app, god: opts.god });
708
+ const res = await catalox.getCatalogItemHistoryEvent(ctx, String(eventId));
709
+ await writeOrStdout(JSON.stringify(res, null, 2));
710
+ if (!res)
711
+ process.exitCode = 1;
712
+ });
713
+ historyCmd
714
+ .command("restore")
715
+ .description("Restore a native row from a history event (before or after snapshot)")
716
+ .requiredOption("--app <appId>", "AppId for authz")
717
+ .argument("<eventId>", "catalogItemHistory document id")
718
+ .option("--mode <before|after>", "Which snapshot to apply", "before")
719
+ .option("--god", "God mode", false)
720
+ .action(async (eventId, opts) => {
721
+ const catalox = createCatalox();
722
+ const ctx = baseContext({ app: opts.app, god: opts.god });
723
+ const mode = String(opts.mode ?? "before").toLowerCase() === "after" ? "after" : "before";
724
+ const res = await catalox.restoreCatalogItemFromHistory(ctx, String(eventId), { mode });
725
+ await writeOrStdout(JSON.stringify(res, null, 2));
726
+ });
727
+ historyCmd
728
+ .command("replay")
729
+ .description("Replay history up to --as-of and diff-apply to live native items")
730
+ .requiredOption("--app <appId>", "AppId for authz")
731
+ .requiredOption("--catalog <catalogId>", "Catalog id")
732
+ .requiredOption("--as-of <iso>", "Inclusive upper bound on event ts")
733
+ .option("--god", "God mode", false)
734
+ .action(async (opts) => {
735
+ const catalox = createCatalox();
736
+ const ctx = baseContext({ app: opts.app, god: opts.god });
737
+ const res = await catalox.replayCatalogToPointInTime(ctx, String(opts.catalog), { asOf: String(opts.asOf) });
738
+ await writeOrStdout(JSON.stringify(res, null, 2));
739
+ });
740
+ const catalogCmd = program.command("catalog").description("Catalog lifecycle (delete / restore / hard rename)");
741
+ catalogCmd
742
+ .command("delete")
743
+ .description("Snapshot catalog to GCS, write delete manifest, then hard-delete Firestore rows (requires record history bucket)")
744
+ .requiredOption("--app <appId>", "AppId for authz")
745
+ .requiredOption("--catalog <catalogId>", "Catalog id")
746
+ .option("--confirm", "Must pass --confirm to run", false)
747
+ .option("--backup-bucket <name>", "Optional: run backupData mode=gcs to this bucket before delete")
748
+ .option("--backup-prefix <path>", "Optional gcsPrefix for pre-delete backupData")
749
+ .option("--god", "God mode", false)
750
+ .action(async (opts) => {
751
+ if (!opts.confirm) {
752
+ // eslint-disable-next-line no-console
753
+ console.error("Refusing to delete without --confirm");
754
+ process.exitCode = 1;
755
+ return;
756
+ }
757
+ const catalox = createCatalox();
758
+ const ctx = baseContext({ app: opts.app, god: opts.god });
759
+ const res = await catalox.deleteCatalog(ctx, String(opts.catalog), {
760
+ confirm: true,
761
+ ...(opts.backupBucket ? { gcsBackupBucket: String(opts.backupBucket).trim() } : {}),
762
+ ...(opts.backupPrefix ? { gcsBackupPrefix: String(opts.backupPrefix).trim() } : {}),
763
+ });
764
+ await writeOrStdout(JSON.stringify(res, null, 2));
765
+ if (!res.ok)
766
+ process.exitCode = 1;
767
+ });
768
+ catalogCmd
769
+ .command("restore")
770
+ .description("Restore a catalog from a delete manifest path (requires superAdmin context / --god)")
771
+ .requiredOption("--app <appId>", "AppId (superAdmin)")
772
+ .requiredOption("--manifest <relativePath>", "GCS object path relative to record-history prefix, ending in manifest.json")
773
+ .option("--god", "God mode (superAdmin)", true)
774
+ .action(async (opts) => {
775
+ const catalox = createCatalox();
776
+ const ctx = baseContext({ app: opts.app, god: true });
777
+ const res = await catalox.restoreDeletedCatalog(ctx, {
778
+ gcsManifestRelativePath: String(opts.manifest).trim(),
779
+ });
780
+ await writeOrStdout(JSON.stringify(res, null, 2));
781
+ if (!res.ok)
782
+ process.exitCode = 1;
783
+ });
784
+ catalogCmd
785
+ .command("rename")
786
+ .description("Hard rename catalog id (copy + rewrite + delete source; requires record history bucket)")
787
+ .requiredOption("--app <appId>", "AppId for authz")
788
+ .requiredOption("--from <catalogId>", "Source catalog id")
789
+ .requiredOption("--to <catalogId>", "Target catalog id (must not exist)")
790
+ .option("--backup-bucket <name>", "Optional: backupData gcs bucket before rename")
791
+ .option("--backup-prefix <path>", "Optional gcsPrefix for pre-rename backup")
792
+ .option("--god", "God mode", false)
793
+ .action(async (opts) => {
794
+ const catalox = createCatalox();
795
+ const ctx = baseContext({ app: opts.app, god: opts.god });
796
+ const res = await catalox.renameCatalog(ctx, String(opts.from), String(opts.to), {
797
+ ...(opts.backupBucket ? { gcsBackupBucket: String(opts.backupBucket).trim() } : {}),
798
+ ...(opts.backupPrefix ? { gcsBackupPrefix: String(opts.backupPrefix).trim() } : {}),
799
+ });
800
+ await writeOrStdout(JSON.stringify(res, null, 2));
801
+ if (!res.ok)
802
+ process.exitCode = 1;
803
+ });
672
804
  program.parseAsync(process.argv).catch((err) => {
673
805
  // eslint-disable-next-line no-console
674
806
  console.error(err instanceof Error ? err.message : err);