@columnus/shared 0.1.59 → 0.1.60

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.
@@ -104,6 +104,22 @@ export type AssetVersionSnapshot = {
104
104
  size: number;
105
105
  createdAt: number;
106
106
  };
107
+ /**
108
+ * One live rendition of an asset — mirrors `renditionSnapshotValidator` in the
109
+ * component. Declared once here so the host's component-API declaration and the
110
+ * Media detail panel cannot drift apart, as the version snapshot once did.
111
+ */
112
+ export type AssetRenditionRow = {
113
+ nodeId: string;
114
+ versionId: string;
115
+ storageId: string;
116
+ format: string;
117
+ width: number;
118
+ height: number;
119
+ bytes: number;
120
+ createdAt: number;
121
+ obsoleteAt?: number;
122
+ };
107
123
  /** G27: one indexed placement of an asset in a tenant row. */
108
124
  export type AssetRefRow = {
109
125
  fromTable: string;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/assetSettings.ts"],"names":[],"mappings":"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","file":"assetSettings.js","sourcesContent":["import type { AssetFamily } from \"./assetFamily.ts\";\nimport type { AssetRenditionNode } from \"./assetGraph.ts\";\n\nexport const DEFAULT_MEDIA_WARN_BYTES = 10 * 1024 * 1024 * 1024;\nexport const DEFAULT_MEDIA_REJECT_BYTES = 100 * 1024 * 1024 * 1024;\n\nexport type MediaUploadCaps = {\n warnBytes: number;\n rejectBytes: number;\n};\n\nexport const DEFAULT_MEDIA_UPLOAD_CAPS: MediaUploadCaps = {\n warnBytes: DEFAULT_MEDIA_WARN_BYTES,\n rejectBytes: DEFAULT_MEDIA_REJECT_BYTES,\n};\n\nexport type MediaUploadLimit =\n | { level: \"warn\"; message: string }\n | { level: \"reject\"; message: string };\n\nexport function formatMediaBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;\n}\n\n/** Returns a stable validation message for settings forms and server-side writes. */\nexport function mediaUploadCapsError(caps: MediaUploadCaps): string | null {\n if (!Number.isSafeInteger(caps.warnBytes) || caps.warnBytes < 0) {\n return \"Media warning limit must be a non-negative whole number of bytes\";\n }\n if (!Number.isSafeInteger(caps.rejectBytes) || caps.rejectBytes <= 0) {\n return \"Media reject limit must be a positive whole number of bytes\";\n }\n if (caps.warnBytes > caps.rejectBytes) {\n return \"Media warning limit cannot exceed the reject limit\";\n }\n return null;\n}\n\n/** Determine whether a client should ask for confirmation or reject immediately. */\nexport function mediaUploadLimit(size: number, caps: MediaUploadCaps): MediaUploadLimit | null {\n if (!Number.isSafeInteger(size) || size < 0) {\n return { level: \"reject\", message: \"Media upload size is invalid\" };\n }\n if (size > caps.rejectBytes) {\n return {\n level: \"reject\",\n message: `This file is ${formatMediaBytes(size)}. Media uploads are limited to ${formatMediaBytes(caps.rejectBytes)}.`,\n };\n }\n if (size > caps.warnBytes) {\n return {\n level: \"warn\",\n message: `This file is ${formatMediaBytes(size)}, above the ${formatMediaBytes(caps.warnBytes)} Media warning limit. Continue uploading?`,\n };\n }\n return null;\n}\n\n/** A schema table bound to a family or profile, with its required fields. */\nexport type AssetSchemaBinding = {\n schemaTable: string;\n requiredFields?: string[];\n defaultValues?: unknown;\n};\n\n/** R3: which graph node each Studio delivery slot resolves to. */\nexport type AssetSlotMapping = { compact?: string; preview?: string };\n\nexport type AssetRebuildStatus = {\n pending: { family: AssetFamily; rebuild: string[]; removed: string[] }[];\n cursor?: string | null;\n startedAt: number;\n assetsExamined: number;\n enqueued: number;\n};\n\nexport type AssetSettings = MediaUploadCaps & {\n familyGraphs: Partial<Record<AssetFamily, AssetRenditionNode[]>>;\n familySchemas: Partial<Record<AssetFamily, AssetSchemaBinding[]>>;\n /** R3: Studio delivery slots → graph node id, per family. */\n slots?: Partial<Record<AssetFamily, AssetSlotMapping>>;\n /** R10: rebuild work a graph save signed up for, drained by cron. */\n rebuildStatus?: AssetRebuildStatus;\n updatedAt: number;\n};\n\nexport type AssetProfile = {\n slug: string;\n name: string;\n families?: AssetFamily[];\n schemas: AssetSchemaBinding[];\n extraNodes: AssetRenditionNode[];\n createdAt: number;\n updatedAt: number;\n};\n\n/** G21 default image graph: preview 2000 contain WebP q80, siblings thumb 400 and thumbSm 200. */\nexport const DEFAULT_IMAGE_GRAPH: AssetRenditionNode[] = [\n { id: \"preview\", op: \"contain\", maxWidth: 2000, maxHeight: 2000, format: \"webp\", quality: 80 },\n {\n id: \"thumb\",\n parentId: \"preview\",\n op: \"contain\",\n maxWidth: 400,\n maxHeight: 400,\n format: \"webp\",\n quality: 80,\n },\n {\n id: \"thumbSm\",\n parentId: \"preview\",\n op: \"contain\",\n maxWidth: 200,\n maxHeight: 200,\n format: \"webp\",\n quality: 80,\n },\n];\n\n/** R3 seed: grid/CMS stamp/library take `thumbSm`, Media detail takes `preview`. */\nexport const DEFAULT_ASSET_SLOTS: Partial<Record<AssetFamily, AssetSlotMapping>> = {\n image: { compact: \"thumbSm\", preview: \"preview\" },\n};\n\n/**\n * The settings a tenant starts with. The component seeds a row from this and\n * the Studio form falls back to it, so a fresh tenant and an unsaved form agree\n * on what the defaults are.\n */\nexport function defaultAssetSettings(): Omit<AssetSettings, \"updatedAt\"> {\n return {\n ...DEFAULT_MEDIA_UPLOAD_CAPS,\n familyGraphs: { image: DEFAULT_IMAGE_GRAPH },\n familySchemas: {},\n slots: DEFAULT_ASSET_SLOTS,\n };\n}\n\n/**\n * What every reader should see when a tenant has never saved Media settings.\n *\n * The component seeds a row from `defaultAssetSettings`, but until something\n * writes one `getSettings` answers `null` — and a caller reading that as \"no\n * graphs, no caps\" concludes the tenant builds nothing, which is the opposite\n * of what a fresh tenant is supposed to do. `updatedAt: 0` because there is no\n * save to report.\n */\nexport function unsavedAssetSettings(): AssetSettings {\n return { ...defaultAssetSettings(), updatedAt: 0 };\n}\n\n/**\n * Slot mapping Studio/CMS should apply for a family.\n *\n * Matches the graph builder: a never-saved tenant (or a settings row with no\n * slots for that family) gets the seed defaults. An explicit empty mapping\n * `{ image: {} }` is an opt-out and stays empty so unset still means original.\n */\nexport function resolveFamilySlotMapping(\n settings: AssetSettings | null | undefined,\n family: AssetFamily | undefined,\n): AssetSlotMapping | undefined {\n if (family === undefined) return undefined;\n const resolved = settings ?? unsavedAssetSettings();\n return resolved.slots?.[family] ?? DEFAULT_ASSET_SLOTS[family];\n}\n\n/**\n * One immutable version of an asset's original, as the component stores it and\n * every reader projects it.\n *\n * The host's component-API declaration used to name only `versionId` and\n * `originalStorageId` while Media detail declared its own copy with `size` and\n * `createdAt` — two descriptions of one payload that already disagreed.\n */\nexport type AssetVersionSnapshot = {\n versionId: string;\n assetId: string;\n originalStorageId: string;\n contentType?: string;\n filename?: string;\n size: number;\n createdAt: number;\n};\n\n/** G27: one indexed placement of an asset in a tenant row. */\nexport type AssetRefRow = {\n fromTable: string;\n fromId: string;\n fieldName: string;\n /** `null` for a scalar field; the position for an array placement. */\n arrayIndex: number | null;\n};\n\n/** One attached metadata table, projected for the Media detail editor. */\nexport type AssetMetadataRow = {\n table: string;\n fields: Record<string, unknown>;\n hasStoredRow: boolean;\n requiredFields: string[];\n /** Two active bindings disagree on a default; an editor must choose (G33). */\n conflictingDefaultFields: string[];\n};\n"]}
1
+ {"version":3,"sources":["../src/assetSettings.ts"],"names":[],"mappings":"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","file":"assetSettings.js","sourcesContent":["import type { AssetFamily } from \"./assetFamily.ts\";\nimport type { AssetRenditionNode } from \"./assetGraph.ts\";\n\nexport const DEFAULT_MEDIA_WARN_BYTES = 10 * 1024 * 1024 * 1024;\nexport const DEFAULT_MEDIA_REJECT_BYTES = 100 * 1024 * 1024 * 1024;\n\nexport type MediaUploadCaps = {\n warnBytes: number;\n rejectBytes: number;\n};\n\nexport const DEFAULT_MEDIA_UPLOAD_CAPS: MediaUploadCaps = {\n warnBytes: DEFAULT_MEDIA_WARN_BYTES,\n rejectBytes: DEFAULT_MEDIA_REJECT_BYTES,\n};\n\nexport type MediaUploadLimit =\n | { level: \"warn\"; message: string }\n | { level: \"reject\"; message: string };\n\nexport function formatMediaBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;\n}\n\n/** Returns a stable validation message for settings forms and server-side writes. */\nexport function mediaUploadCapsError(caps: MediaUploadCaps): string | null {\n if (!Number.isSafeInteger(caps.warnBytes) || caps.warnBytes < 0) {\n return \"Media warning limit must be a non-negative whole number of bytes\";\n }\n if (!Number.isSafeInteger(caps.rejectBytes) || caps.rejectBytes <= 0) {\n return \"Media reject limit must be a positive whole number of bytes\";\n }\n if (caps.warnBytes > caps.rejectBytes) {\n return \"Media warning limit cannot exceed the reject limit\";\n }\n return null;\n}\n\n/** Determine whether a client should ask for confirmation or reject immediately. */\nexport function mediaUploadLimit(size: number, caps: MediaUploadCaps): MediaUploadLimit | null {\n if (!Number.isSafeInteger(size) || size < 0) {\n return { level: \"reject\", message: \"Media upload size is invalid\" };\n }\n if (size > caps.rejectBytes) {\n return {\n level: \"reject\",\n message: `This file is ${formatMediaBytes(size)}. Media uploads are limited to ${formatMediaBytes(caps.rejectBytes)}.`,\n };\n }\n if (size > caps.warnBytes) {\n return {\n level: \"warn\",\n message: `This file is ${formatMediaBytes(size)}, above the ${formatMediaBytes(caps.warnBytes)} Media warning limit. Continue uploading?`,\n };\n }\n return null;\n}\n\n/** A schema table bound to a family or profile, with its required fields. */\nexport type AssetSchemaBinding = {\n schemaTable: string;\n requiredFields?: string[];\n defaultValues?: unknown;\n};\n\n/** R3: which graph node each Studio delivery slot resolves to. */\nexport type AssetSlotMapping = { compact?: string; preview?: string };\n\nexport type AssetRebuildStatus = {\n pending: { family: AssetFamily; rebuild: string[]; removed: string[] }[];\n cursor?: string | null;\n startedAt: number;\n assetsExamined: number;\n enqueued: number;\n};\n\nexport type AssetSettings = MediaUploadCaps & {\n familyGraphs: Partial<Record<AssetFamily, AssetRenditionNode[]>>;\n familySchemas: Partial<Record<AssetFamily, AssetSchemaBinding[]>>;\n /** R3: Studio delivery slots → graph node id, per family. */\n slots?: Partial<Record<AssetFamily, AssetSlotMapping>>;\n /** R10: rebuild work a graph save signed up for, drained by cron. */\n rebuildStatus?: AssetRebuildStatus;\n updatedAt: number;\n};\n\nexport type AssetProfile = {\n slug: string;\n name: string;\n families?: AssetFamily[];\n schemas: AssetSchemaBinding[];\n extraNodes: AssetRenditionNode[];\n createdAt: number;\n updatedAt: number;\n};\n\n/** G21 default image graph: preview 2000 contain WebP q80, siblings thumb 400 and thumbSm 200. */\nexport const DEFAULT_IMAGE_GRAPH: AssetRenditionNode[] = [\n { id: \"preview\", op: \"contain\", maxWidth: 2000, maxHeight: 2000, format: \"webp\", quality: 80 },\n {\n id: \"thumb\",\n parentId: \"preview\",\n op: \"contain\",\n maxWidth: 400,\n maxHeight: 400,\n format: \"webp\",\n quality: 80,\n },\n {\n id: \"thumbSm\",\n parentId: \"preview\",\n op: \"contain\",\n maxWidth: 200,\n maxHeight: 200,\n format: \"webp\",\n quality: 80,\n },\n];\n\n/** R3 seed: grid/CMS stamp/library take `thumbSm`, Media detail takes `preview`. */\nexport const DEFAULT_ASSET_SLOTS: Partial<Record<AssetFamily, AssetSlotMapping>> = {\n image: { compact: \"thumbSm\", preview: \"preview\" },\n};\n\n/**\n * The settings a tenant starts with. The component seeds a row from this and\n * the Studio form falls back to it, so a fresh tenant and an unsaved form agree\n * on what the defaults are.\n */\nexport function defaultAssetSettings(): Omit<AssetSettings, \"updatedAt\"> {\n return {\n ...DEFAULT_MEDIA_UPLOAD_CAPS,\n familyGraphs: { image: DEFAULT_IMAGE_GRAPH },\n familySchemas: {},\n slots: DEFAULT_ASSET_SLOTS,\n };\n}\n\n/**\n * What every reader should see when a tenant has never saved Media settings.\n *\n * The component seeds a row from `defaultAssetSettings`, but until something\n * writes one `getSettings` answers `null` — and a caller reading that as \"no\n * graphs, no caps\" concludes the tenant builds nothing, which is the opposite\n * of what a fresh tenant is supposed to do. `updatedAt: 0` because there is no\n * save to report.\n */\nexport function unsavedAssetSettings(): AssetSettings {\n return { ...defaultAssetSettings(), updatedAt: 0 };\n}\n\n/**\n * Slot mapping Studio/CMS should apply for a family.\n *\n * Matches the graph builder: a never-saved tenant (or a settings row with no\n * slots for that family) gets the seed defaults. An explicit empty mapping\n * `{ image: {} }` is an opt-out and stays empty so unset still means original.\n */\nexport function resolveFamilySlotMapping(\n settings: AssetSettings | null | undefined,\n family: AssetFamily | undefined,\n): AssetSlotMapping | undefined {\n if (family === undefined) return undefined;\n const resolved = settings ?? unsavedAssetSettings();\n return resolved.slots?.[family] ?? DEFAULT_ASSET_SLOTS[family];\n}\n\n/**\n * One immutable version of an asset's original, as the component stores it and\n * every reader projects it.\n *\n * The host's component-API declaration used to name only `versionId` and\n * `originalStorageId` while Media detail declared its own copy with `size` and\n * `createdAt` — two descriptions of one payload that already disagreed.\n */\nexport type AssetVersionSnapshot = {\n versionId: string;\n assetId: string;\n originalStorageId: string;\n contentType?: string;\n filename?: string;\n size: number;\n createdAt: number;\n};\n\n/**\n * One live rendition of an asset — mirrors `renditionSnapshotValidator` in the\n * component. Declared once here so the host's component-API declaration and the\n * Media detail panel cannot drift apart, as the version snapshot once did.\n */\nexport type AssetRenditionRow = {\n nodeId: string;\n versionId: string;\n storageId: string;\n format: string;\n width: number;\n height: number;\n bytes: number;\n createdAt: number;\n obsoleteAt?: number;\n};\n\n/** G27: one indexed placement of an asset in a tenant row. */\nexport type AssetRefRow = {\n fromTable: string;\n fromId: string;\n fieldName: string;\n /** `null` for a scalar field; the position for an array placement. */\n arrayIndex: number | null;\n};\n\n/** One attached metadata table, projected for the Media detail editor. */\nexport type AssetMetadataRow = {\n table: string;\n fields: Record<string, unknown>;\n hasStoredRow: boolean;\n requiredFields: string[];\n /** Two active bindings disagree on a default; an editor must choose (G33). */\n conflictingDefaultFields: string[];\n};\n"]}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Redact audit payloads before persistence (invite-floor security).
3
+ * Keeps actor/action/timestamp/entity metadata and field names — never raw values.
4
+ */
5
+ import { REDACTED } from "./bffRedact.ts";
6
+ export type RedactedRowAuditSide = {
7
+ fieldKeys: string[];
8
+ changedFields?: string[];
9
+ };
10
+ export type RedactedManifestAuditSummary = {
11
+ tableNames: string[];
12
+ fieldsByTable: Record<string, string[]>;
13
+ };
14
+ /** Replace a row document with field-key metadata only. */
15
+ export declare function redactRowAuditSide(doc: unknown): RedactedRowAuditSide | null | undefined;
16
+ /** Redact before/after row snapshots for audit persistence. */
17
+ export declare function redactRowAuditPayload(entry: {
18
+ before?: unknown;
19
+ after?: unknown;
20
+ }): {
21
+ before?: RedactedRowAuditSide | null;
22
+ after?: RedactedRowAuditSide | null;
23
+ };
24
+ /** Summarize a manifest for audit: table and field names only. */
25
+ export declare function summarizeManifestForAudit(manifest: unknown): RedactedManifestAuditSummary;
26
+ /** Table names from a persisted schema-audit side (legacy or redacted). */
27
+ export declare function manifestTableNamesFromAuditSide(side: unknown): string[];
28
+ /** Redact schema audit before/after payloads (manifests, deploy logs, errors). */
29
+ export declare function redactSchemaAuditPayload(entry: {
30
+ before?: unknown;
31
+ after?: unknown;
32
+ }): {
33
+ before?: unknown;
34
+ after?: unknown;
35
+ };
36
+ export { REDACTED };