@columnus/shared 0.1.54 → 0.1.56

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 (77) hide show
  1. package/dist/apiProfile.d.ts +39 -0
  2. package/dist/apiProfile.js +19 -2
  3. package/dist/apiProfile.js.map +1 -1
  4. package/dist/assetFamily.d.ts +36 -0
  5. package/dist/assetFamily.js +73 -0
  6. package/dist/assetFamily.js.map +1 -0
  7. package/dist/assetFieldValues.d.ts +21 -0
  8. package/dist/{storageFieldValues.js → assetFieldValues.js} +159 -60
  9. package/dist/assetFieldValues.js.map +1 -0
  10. package/dist/assetGraph.d.ts +199 -0
  11. package/dist/assetGraph.js +214 -0
  12. package/dist/assetGraph.js.map +1 -0
  13. package/dist/assetJobs.d.ts +95 -0
  14. package/dist/assetJobs.js +41 -0
  15. package/dist/assetJobs.js.map +1 -0
  16. package/dist/assetSettings.d.ts +129 -0
  17. package/dist/assetSettings.js +87 -0
  18. package/dist/assetSettings.js.map +1 -0
  19. package/dist/assetValidators.d.ts +475 -0
  20. package/dist/assetValidators.js +83 -0
  21. package/dist/assetValidators.js.map +1 -0
  22. package/dist/bffProtocol.d.ts +29 -24
  23. package/dist/bffProtocol.js +5 -1
  24. package/dist/bffProtocol.js.map +1 -1
  25. package/dist/ciPreviewEnv.js +20 -3
  26. package/dist/ciPreviewEnv.js.map +1 -1
  27. package/dist/clipboardTransfer.js +112 -12
  28. package/dist/clipboardTransfer.js.map +1 -1
  29. package/dist/csv.js +118 -18
  30. package/dist/csv.js.map +1 -1
  31. package/dist/displayLabel.js +1 -1
  32. package/dist/displayLabel.js.map +1 -1
  33. package/dist/dotenv.js +5 -1
  34. package/dist/dotenv.js.map +1 -1
  35. package/dist/importMatch.js +118 -18
  36. package/dist/importMatch.js.map +1 -1
  37. package/dist/index.d.ts +7 -1
  38. package/dist/index.js +741 -82
  39. package/dist/index.js.map +1 -1
  40. package/dist/postLoginPath.d.ts +21 -0
  41. package/dist/postLoginPath.js +24 -0
  42. package/dist/postLoginPath.js.map +1 -0
  43. package/dist/previewDeployName.d.ts +0 -7
  44. package/dist/previewDeployName.js +21 -4
  45. package/dist/previewDeployName.js.map +1 -1
  46. package/dist/recordTableColumns.js +112 -12
  47. package/dist/recordTableColumns.js.map +1 -1
  48. package/dist/recordTablePreload.js.map +1 -1
  49. package/dist/refDisplayRequests.js +112 -12
  50. package/dist/refDisplayRequests.js.map +1 -1
  51. package/dist/richDoc.d.ts +2 -2
  52. package/dist/richDoc.js +5 -5
  53. package/dist/richDoc.js.map +1 -1
  54. package/dist/schemaGenerator.d.ts +25 -1
  55. package/dist/schemaGenerator.js +4569 -18
  56. package/dist/schemaGenerator.js.map +1 -1
  57. package/dist/schemaShrink.d.ts +3 -1
  58. package/dist/schemaShrink.js +4451 -1
  59. package/dist/schemaShrink.js.map +1 -1
  60. package/dist/schemaShrinkValidators.d.ts +2 -2
  61. package/dist/schemaTypes.d.ts +123 -0
  62. package/dist/schemaTypes.js +107 -13
  63. package/dist/schemaTypes.js.map +1 -1
  64. package/dist/share.js +6 -1
  65. package/dist/share.js.map +1 -1
  66. package/dist/shareApiBridge.js +6 -1
  67. package/dist/shareApiBridge.js.map +1 -1
  68. package/dist/shareProposal.js +112 -12
  69. package/dist/shareProposal.js.map +1 -1
  70. package/dist/spaceBundle.js +112 -12
  71. package/dist/spaceBundle.js.map +1 -1
  72. package/dist/syncChannel.d.ts +15 -0
  73. package/dist/syncChannel.js +4642 -0
  74. package/dist/syncChannel.js.map +1 -0
  75. package/package.json +1 -1
  76. package/dist/storageFieldValues.d.ts +0 -10
  77. package/dist/storageFieldValues.js.map +0 -1
@@ -0,0 +1,36 @@
1
+ export declare const ASSET_FAMILIES: readonly ["image", "document", "audio", "video", "vector", "other"];
2
+ export type AssetFamily = (typeof ASSET_FAMILIES)[number];
3
+ /**
4
+ * Narrow an untyped `family` read off a row. Convex hands asset rows back
5
+ * untyped — the table is generated per tenant — so a reader that needs the
6
+ * union has to check it rather than assert it.
7
+ */
8
+ export declare function isAssetFamily(value: unknown): value is AssetFamily;
9
+ /** Vector types a browser can render in an `<img>`; the rest fall back to an icon. */
10
+ export declare const DISPLAYABLE_VECTOR_CONTENT_TYPES: readonly ["image/svg+xml"];
11
+ /** True when this MIME may be decoded into the G21 rendition graph. */
12
+ export declare function isRasterRenditionSource(contentType: string | undefined): boolean;
13
+ /** `accept` value for a file input restricted to family `vector`. */
14
+ export declare const VECTOR_FILE_ACCEPT = ".svg,.eps,.ai,image/svg+xml,application/postscript,application/illustrator";
15
+ export declare function isVectorContentType(contentType: string | undefined, filename?: string): boolean;
16
+ /**
17
+ * True when the asset can be shown directly in an `<img>` even though it is
18
+ * not family `image`: SVG only, so EPS keeps the file icon.
19
+ */
20
+ export declare function isDisplayableVectorContentType(contentType: string | undefined, filename?: string): boolean;
21
+ /** Derive the stable family used by profiles, graphs, and library filtering. */
22
+ export declare function assetFamilyForContentType(contentType: string | undefined, filename?: string): AssetFamily;
23
+ /**
24
+ * G20: ExifTool extract runs for `image` and `vector` only. Rendition graphs
25
+ * are a stricter gate (`assetBuildsRenditions`: raster `image`).
26
+ */
27
+ export declare function isExtractableFamily(family: AssetFamily | undefined): boolean;
28
+ /**
29
+ * Library eligibility for an `image` field picker: family `image` plus vector
30
+ * assets a browser can display. EPS stays reachable from `file` fields only.
31
+ */
32
+ export declare function isImageFieldPickable(asset: {
33
+ family?: string;
34
+ contentType?: string;
35
+ filename?: string;
36
+ }): boolean;
@@ -0,0 +1,73 @@
1
+ // packages/@columnus/shared/src/assetFamily.ts
2
+ var ASSET_FAMILIES = ["image", "document", "audio", "video", "vector", "other"];
3
+ var ASSET_FAMILY_SET = new Set(ASSET_FAMILIES);
4
+ function isAssetFamily(value) {
5
+ return typeof value === "string" && ASSET_FAMILY_SET.has(value);
6
+ }
7
+ var VECTOR_CONTENT_TYPES = /* @__PURE__ */ new Set([
8
+ "image/svg+xml",
9
+ "application/postscript",
10
+ "application/eps",
11
+ "application/x-eps",
12
+ "image/eps",
13
+ "image/x-eps",
14
+ "application/illustrator",
15
+ "application/vnd.adobe.illustrator"
16
+ ]);
17
+ var DISPLAYABLE_VECTOR_CONTENT_TYPES = ["image/svg+xml"];
18
+ var DISPLAYABLE_VECTOR_CONTENT_TYPE_SET = new Set(
19
+ DISPLAYABLE_VECTOR_CONTENT_TYPES
20
+ );
21
+ var RASTER_RENDITION_CONTENT_TYPES = /* @__PURE__ */ new Set([
22
+ "image/jpeg",
23
+ "image/pjpeg",
24
+ "image/png",
25
+ "image/webp",
26
+ "image/avif",
27
+ "image/tiff",
28
+ "image/heic",
29
+ "image/heif",
30
+ "image/gif"
31
+ ]);
32
+ function isRasterRenditionSource(contentType) {
33
+ return RASTER_RENDITION_CONTENT_TYPES.has(normalizeContentType(contentType));
34
+ }
35
+ var VECTOR_FILE_ACCEPT = ".svg,.eps,.ai,image/svg+xml,application/postscript,application/illustrator";
36
+ function normalizeContentType(contentType) {
37
+ return contentType?.split(";")[0]?.trim().toLowerCase() ?? "";
38
+ }
39
+ function hasVectorExtension(filename) {
40
+ return filename?.trim().toLowerCase().endsWith(".svg") ?? false;
41
+ }
42
+ function isVectorContentType(contentType, filename) {
43
+ const value = normalizeContentType(contentType);
44
+ if (!value) return hasVectorExtension(filename);
45
+ return VECTOR_CONTENT_TYPES.has(value);
46
+ }
47
+ function isDisplayableVectorContentType(contentType, filename) {
48
+ const value = normalizeContentType(contentType);
49
+ if (!value) return hasVectorExtension(filename);
50
+ return DISPLAYABLE_VECTOR_CONTENT_TYPE_SET.has(value);
51
+ }
52
+ function assetFamilyForContentType(contentType, filename) {
53
+ const value = normalizeContentType(contentType);
54
+ if (isVectorContentType(contentType, filename)) return "vector";
55
+ if (value.startsWith("image/")) return "image";
56
+ if (value.startsWith("audio/")) return "audio";
57
+ if (value.startsWith("video/")) return "video";
58
+ if (value === "application/pdf" || value.startsWith("text/") || value.startsWith("application/msword") || value.startsWith("application/vnd.openxmlformats-officedocument") || value.startsWith("application/vnd.oasis.opendocument")) {
59
+ return "document";
60
+ }
61
+ return "other";
62
+ }
63
+ function isExtractableFamily(family) {
64
+ return family === "image" || family === "vector";
65
+ }
66
+ function isImageFieldPickable(asset) {
67
+ if (asset.family === "image") return true;
68
+ return isDisplayableVectorContentType(asset.contentType, asset.filename);
69
+ }
70
+
71
+ export { ASSET_FAMILIES, DISPLAYABLE_VECTOR_CONTENT_TYPES, VECTOR_FILE_ACCEPT, assetFamilyForContentType, isAssetFamily, isDisplayableVectorContentType, isExtractableFamily, isImageFieldPickable, isRasterRenditionSource, isVectorContentType };
72
+ //# sourceMappingURL=assetFamily.js.map
73
+ //# sourceMappingURL=assetFamily.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/assetFamily.ts"],"names":[],"mappings":"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","file":"assetFamily.js","sourcesContent":["// Media family classification shared by the host, the component, Studio, and\n// the generated clients. `vector` was added by 059 S0 (061 grill 2026-08-23):\n// SVG/EPS/Illustrator are their own family so the raster rendition DAG (G38)\n// and the spinner-for-derived rule (G36) never apply to them.\n\nexport const ASSET_FAMILIES = [\"image\", \"document\", \"audio\", \"video\", \"vector\", \"other\"] as const;\nexport type AssetFamily = (typeof ASSET_FAMILIES)[number];\n\nconst ASSET_FAMILY_SET: ReadonlySet<string> = new Set(ASSET_FAMILIES);\n\n/**\n * Narrow an untyped `family` read off a row. Convex hands asset rows back\n * untyped — the table is generated per tenant — so a reader that needs the\n * union has to check it rather than assert it.\n */\nexport function isAssetFamily(value: unknown): value is AssetFamily {\n return typeof value === \"string\" && ASSET_FAMILY_SET.has(value);\n}\n\n/**\n * Vector MIME table (G20/G38). `application/pdf` deliberately stays\n * `document`, which also covers PDF-based `.ai` files.\n */\nconst VECTOR_CONTENT_TYPES = new Set([\n \"image/svg+xml\",\n \"application/postscript\",\n \"application/eps\",\n \"application/x-eps\",\n \"image/eps\",\n \"image/x-eps\",\n \"application/illustrator\",\n \"application/vnd.adobe.illustrator\",\n]);\n\n/** Vector types a browser can render in an `<img>`; the rest fall back to an icon. */\nexport const DISPLAYABLE_VECTOR_CONTENT_TYPES = [\"image/svg+xml\"] as const;\nconst DISPLAYABLE_VECTOR_CONTENT_TYPE_SET: ReadonlySet<string> = new Set(\n DISPLAYABLE_VECTOR_CONTENT_TYPES,\n);\n\n/**\n * G38 raster allowlist: only these get a rendition DAG (and therefore the G36\n * spinner). Animated GIF/WebP are included and rendered **first frame**.\n * Everything else that is family `image` — BMP, ICO, camera RAW — keeps its\n * original and never enters the render pipeline.\n */\nconst RASTER_RENDITION_CONTENT_TYPES: ReadonlySet<string> = new Set([\n \"image/jpeg\",\n \"image/pjpeg\",\n \"image/png\",\n \"image/webp\",\n \"image/avif\",\n \"image/tiff\",\n \"image/heic\",\n \"image/heif\",\n \"image/gif\",\n]);\n\n/** True when this MIME may be decoded into the G21 rendition graph. */\nexport function isRasterRenditionSource(contentType: string | undefined): boolean {\n return RASTER_RENDITION_CONTENT_TYPES.has(normalizeContentType(contentType));\n}\n\n/** `accept` value for a file input restricted to family `vector`. */\nexport const VECTOR_FILE_ACCEPT =\n \".svg,.eps,.ai,image/svg+xml,application/postscript,application/illustrator\";\n\nfunction normalizeContentType(contentType: string | undefined): string {\n return contentType?.split(\";\")[0]?.trim().toLowerCase() ?? \"\";\n}\n\n/** Only `.svg` is inferred from the filename, and only when the MIME is missing. */\nfunction hasVectorExtension(filename: string | undefined): boolean {\n return filename?.trim().toLowerCase().endsWith(\".svg\") ?? false;\n}\n\nexport function isVectorContentType(contentType: string | undefined, filename?: string): boolean {\n const value = normalizeContentType(contentType);\n if (!value) return hasVectorExtension(filename);\n return VECTOR_CONTENT_TYPES.has(value);\n}\n\n/**\n * True when the asset can be shown directly in an `<img>` even though it is\n * not family `image`: SVG only, so EPS keeps the file icon.\n */\nexport function isDisplayableVectorContentType(\n contentType: string | undefined,\n filename?: string,\n): boolean {\n const value = normalizeContentType(contentType);\n if (!value) return hasVectorExtension(filename);\n return DISPLAYABLE_VECTOR_CONTENT_TYPE_SET.has(value);\n}\n\n/** Derive the stable family used by profiles, graphs, and library filtering. */\nexport function assetFamilyForContentType(\n contentType: string | undefined,\n filename?: string,\n): AssetFamily {\n const value = normalizeContentType(contentType);\n if (isVectorContentType(contentType, filename)) return \"vector\";\n if (value.startsWith(\"image/\")) return \"image\";\n if (value.startsWith(\"audio/\")) return \"audio\";\n if (value.startsWith(\"video/\")) return \"video\";\n if (\n value === \"application/pdf\" ||\n value.startsWith(\"text/\") ||\n value.startsWith(\"application/msword\") ||\n value.startsWith(\"application/vnd.openxmlformats-officedocument\") ||\n value.startsWith(\"application/vnd.oasis.opendocument\")\n ) {\n return \"document\";\n }\n return \"other\";\n}\n\n/**\n * G20: ExifTool extract runs for `image` and `vector` only. Rendition graphs\n * are a stricter gate (`assetBuildsRenditions`: raster `image`).\n */\nexport function isExtractableFamily(family: AssetFamily | undefined): boolean {\n return family === \"image\" || family === \"vector\";\n}\n\n/**\n * Library eligibility for an `image` field picker: family `image` plus vector\n * assets a browser can display. EPS stays reachable from `file` fields only.\n */\nexport function isImageFieldPickable(asset: {\n family?: string;\n contentType?: string;\n filename?: string;\n}): boolean {\n if (asset.family === \"image\") return true;\n return isDisplayableVectorContentType(asset.contentType, asset.filename);\n}\n"]}
@@ -0,0 +1,21 @@
1
+ import type { Field, FieldType } from "./schemaTypes.ts";
2
+ export declare function isValidAssetId(value: string): boolean;
3
+ /** Collect persisted asset ids from image/file fields, including nested objects. */
4
+ export declare function collectAssetIds(value: unknown, type: FieldType, out?: Set<string>): Set<string>;
5
+ export declare function collectAssetIdsFromRow(row: Record<string, unknown> | undefined, fields: readonly Field[], out?: Set<string>): Set<string>;
6
+ /**
7
+ * One reverse-reference placement for a stored Media value. `fieldName` is a
8
+ * stable manifest path; a top-level file array uses `arrayIndex`, while deeper
9
+ * arrays include their index in the path to keep the component key unique.
10
+ */
11
+ export type AssetFieldRef = {
12
+ assetId: string;
13
+ fieldName: string;
14
+ arrayIndex: number | null;
15
+ };
16
+ /**
17
+ * Collect placement-precise asset references from a row. This is deliberately
18
+ * separate from `collectAssetIdsFromRow`: display batching only needs ids,
19
+ * whereas the DAM ref index must distinguish two placements of the same id.
20
+ */
21
+ export declare function collectAssetRefsFromRow(row: Record<string, unknown> | undefined, fields: readonly Field[]): AssetFieldRef[];
@@ -4180,7 +4180,13 @@ var RESERVED_TABLE_NAMES = /* @__PURE__ */ new Set([
4180
4180
  "columnus_api_output_secrets",
4181
4181
  "columnus_api_sync_routes",
4182
4182
  "columnus_api_outbox",
4183
- "columnus_api_delivery"
4183
+ "columnus_api_delivery",
4184
+ "columnus_assets",
4185
+ "columnus_asset_versions",
4186
+ "columnus_asset_refs",
4187
+ "columnus_asset_profiles",
4188
+ "columnus_asset_settings",
4189
+ "columnus_asset_jobs"
4184
4190
  ]);
4185
4191
  var displayTemplatesSchema = external_exports.object({
4186
4192
  id: external_exports.string().optional(),
@@ -4213,6 +4219,12 @@ var TABLE_COLORS = [
4213
4219
  "lavender"
4214
4220
  ];
4215
4221
  var tableColorSchema = external_exports.enum(TABLE_COLORS);
4222
+ var TABLE_KINDS = ["record", "asset"];
4223
+ var tableKindSchema = external_exports.enum(TABLE_KINDS);
4224
+ var assetBindingSchema = external_exports.object({
4225
+ profile: external_exports.string().min(1),
4226
+ enforced: external_exports.boolean().optional()
4227
+ });
4216
4228
  var fieldTypeSchema = external_exports.lazy(
4217
4229
  () => external_exports.discriminatedUnion("kind", [
4218
4230
  external_exports.object({ kind: external_exports.literal("string") }),
@@ -4240,7 +4252,9 @@ var fieldTypeSchema = external_exports.lazy(
4240
4252
  name: external_exports.string().regex(IDENT),
4241
4253
  label: external_exports.string().optional(),
4242
4254
  type: fieldTypeSchema,
4243
- optional: external_exports.boolean()
4255
+ optional: external_exports.boolean(),
4256
+ /** Nested image/file placements bind a Media profile like top-level ones. */
4257
+ asset: assetBindingSchema.optional()
4244
4258
  })
4245
4259
  )
4246
4260
  }),
@@ -4254,7 +4268,9 @@ var fieldSchema = external_exports.object({
4254
4268
  name: external_exports.string().regex(IDENT, "must be a valid identifier"),
4255
4269
  label: external_exports.string().optional(),
4256
4270
  type: fieldTypeSchema,
4257
- optional: external_exports.boolean()
4271
+ optional: external_exports.boolean(),
4272
+ /** Image/file fields may pin a Media profile; existence is enforced at upload/pick. */
4273
+ asset: assetBindingSchema.optional()
4258
4274
  });
4259
4275
  var indexSchema = external_exports.object({
4260
4276
  name: external_exports.string().regex(IDENT),
@@ -4271,6 +4287,8 @@ var tableSchema = external_exports.object({
4271
4287
  }),
4272
4288
  label: external_exports.string().optional(),
4273
4289
  color: tableColorSchema.optional(),
4290
+ /** Asset metadata tables are typed root tables but never ordinary user records. */
4291
+ kind: tableKindSchema.optional().default("record"),
4274
4292
  fields: external_exports.array(fieldSchema),
4275
4293
  indexes: external_exports.array(indexSchema).default([]),
4276
4294
  searchIndexes: external_exports.array(searchIndexSchema).default([]),
@@ -4280,6 +4298,7 @@ external_exports.object({
4280
4298
  tables: external_exports.array(tableSchema)
4281
4299
  }).superRefine((manifest, ctx) => {
4282
4300
  const tableNames = /* @__PURE__ */ new Set();
4301
+ const tablesByName = /* @__PURE__ */ new Map();
4283
4302
  for (const t of manifest.tables) {
4284
4303
  if (tableNames.has(t.name)) {
4285
4304
  ctx.addIssue({
@@ -4289,6 +4308,37 @@ external_exports.object({
4289
4308
  });
4290
4309
  }
4291
4310
  tableNames.add(t.name);
4311
+ tablesByName.set(t.name, t);
4312
+ if (tableKind(t) === "asset") {
4313
+ if (t.indexes.length > 0) {
4314
+ ctx.addIssue({
4315
+ code: external_exports.ZodIssueCode.custom,
4316
+ message: `${t.name} is an asset metadata table and cannot define indexes`,
4317
+ path: ["tables"]
4318
+ });
4319
+ }
4320
+ if (t.searchIndexes.length > 0) {
4321
+ ctx.addIssue({
4322
+ code: external_exports.ZodIssueCode.custom,
4323
+ message: `${t.name} is an asset metadata table and cannot define search indexes`,
4324
+ path: ["tables"]
4325
+ });
4326
+ }
4327
+ if (t.displayTemplates !== void 0) {
4328
+ ctx.addIssue({
4329
+ code: external_exports.ZodIssueCode.custom,
4330
+ message: `${t.name} is an asset metadata table and cannot define display templates`,
4331
+ path: ["tables"]
4332
+ });
4333
+ }
4334
+ if (t.color !== void 0) {
4335
+ ctx.addIssue({
4336
+ code: external_exports.ZodIssueCode.custom,
4337
+ message: `${t.name} is an asset metadata table and cannot define a color`,
4338
+ path: ["tables"]
4339
+ });
4340
+ }
4341
+ }
4292
4342
  const fieldNames = /* @__PURE__ */ new Set();
4293
4343
  for (const f of t.fields) {
4294
4344
  if (fieldNames.has(f.name)) {
@@ -4299,36 +4349,43 @@ external_exports.object({
4299
4349
  });
4300
4350
  }
4301
4351
  fieldNames.add(f.name);
4352
+ assertAssetBindingAllowed(ctx, t, f);
4302
4353
  }
4303
4354
  }
4304
4355
  for (const t of manifest.tables) {
4305
4356
  for (const f of t.fields) {
4306
- const danglingRef = findDanglingRef(f.type, tableNames);
4307
- if (danglingRef) {
4357
+ const invalidRef = findInvalidReference(f.type, tablesByName);
4358
+ if (invalidRef) {
4308
4359
  ctx.addIssue({
4309
4360
  code: external_exports.ZodIssueCode.custom,
4310
- message: `${t.name}.${f.name} references unknown table: ${danglingRef}`,
4361
+ message: `${t.name}.${f.name} ${invalidRef}`,
4311
4362
  path: ["tables"]
4312
4363
  });
4313
4364
  }
4314
4365
  }
4315
4366
  }
4316
4367
  });
4317
- function findDanglingRef(t, known) {
4368
+ function findInvalidReference(t, tablesByName) {
4318
4369
  switch (t.kind) {
4319
- case "id":
4320
- return known.has(t.table) ? null : t.table;
4370
+ case "id": {
4371
+ const target = tablesByName.get(t.table);
4372
+ if (!target) return `references unknown table: ${t.table}`;
4373
+ if (tableKind(target) === "asset") {
4374
+ return `cannot reference asset metadata table: ${t.table}`;
4375
+ }
4376
+ return null;
4377
+ }
4321
4378
  case "array":
4322
- return findDanglingRef(t.element, known);
4379
+ return findInvalidReference(t.element, tablesByName);
4323
4380
  case "object":
4324
4381
  for (const f of t.fields) {
4325
- const r = findDanglingRef(f.type, known);
4382
+ const r = findInvalidReference(f.type, tablesByName);
4326
4383
  if (r) return r;
4327
4384
  }
4328
4385
  return null;
4329
4386
  case "union":
4330
4387
  for (const o of t.options) {
4331
- const r = findDanglingRef(o, known);
4388
+ const r = findInvalidReference(o, tablesByName);
4332
4389
  if (r) return r;
4333
4390
  }
4334
4391
  return null;
@@ -4336,6 +4393,9 @@ function findDanglingRef(t, known) {
4336
4393
  return null;
4337
4394
  }
4338
4395
  }
4396
+ function tableKind(table) {
4397
+ return table.kind ?? "record";
4398
+ }
4339
4399
  function isFileFieldType(t) {
4340
4400
  return t.kind === "file" || t.kind === "image";
4341
4401
  }
@@ -4345,6 +4405,37 @@ function isFileArrayFieldType(t) {
4345
4405
  function isStorageFieldType(t) {
4346
4406
  return isFileFieldType(t) || isFileArrayFieldType(t);
4347
4407
  }
4408
+ function assertAssetBindingAllowed(ctx, table, field) {
4409
+ if (field.asset && tableKind(table) === "asset") {
4410
+ ctx.addIssue({
4411
+ code: external_exports.ZodIssueCode.custom,
4412
+ message: `${table.name}.${field.name} asset metadata fields cannot define a Media profile`,
4413
+ path: ["tables"]
4414
+ });
4415
+ } else if (field.asset && !isStorageFieldType(field.type)) {
4416
+ ctx.addIssue({
4417
+ code: external_exports.ZodIssueCode.custom,
4418
+ message: `${table.name}.${field.name} asset profile can only be set on image/file fields`,
4419
+ path: ["tables"]
4420
+ });
4421
+ }
4422
+ assertAssetBindingsInType(ctx, table, field.type);
4423
+ }
4424
+ function assertAssetBindingsInType(ctx, table, type) {
4425
+ switch (type.kind) {
4426
+ case "array":
4427
+ assertAssetBindingsInType(ctx, table, type.element);
4428
+ return;
4429
+ case "object":
4430
+ for (const field of type.fields) assertAssetBindingAllowed(ctx, table, field);
4431
+ return;
4432
+ case "union":
4433
+ for (const option of type.options) assertAssetBindingsInType(ctx, table, option);
4434
+ return;
4435
+ default:
4436
+ return;
4437
+ }
4438
+ }
4348
4439
 
4349
4440
  // packages/@columnus/shared/src/displayLabel.ts
4350
4441
  function toDocumentId(value) {
@@ -4352,70 +4443,78 @@ function toDocumentId(value) {
4352
4443
  return typeof value === "string" ? value : String(value);
4353
4444
  }
4354
4445
 
4355
- // packages/@columnus/shared/src/storageFieldValues.ts
4356
- var STORAGE_ID = /^[a-z0-9]+$/i;
4357
- function isValidStorageId(value) {
4358
- return STORAGE_ID.test(value) && value.length >= 10;
4446
+ // packages/@columnus/shared/src/assetFieldValues.ts
4447
+ var ASSET_ID = /^[a-z0-9]+$/i;
4448
+ function isValidAssetId(value) {
4449
+ return ASSET_ID.test(value) && value.length >= 10;
4359
4450
  }
4360
- function collectStorageIds(value, type, out = /* @__PURE__ */ new Set()) {
4451
+ function collectAssetIds(value, type, out = /* @__PURE__ */ new Set()) {
4361
4452
  if (isFileFieldType(type)) {
4362
4453
  const id = toDocumentId(value);
4363
4454
  if (id) out.add(id);
4364
4455
  return out;
4365
4456
  }
4366
- if (isFileArrayFieldType(type) && Array.isArray(value)) {
4367
- for (const item of value) {
4368
- const id = toDocumentId(item);
4369
- if (id) out.add(id);
4457
+ if (type.kind === "array" && Array.isArray(value)) {
4458
+ for (const item of value) collectAssetIds(item, type.element, out);
4459
+ return out;
4460
+ }
4461
+ if (type.kind === "object" && value !== null && typeof value === "object" && !Array.isArray(value)) {
4462
+ const record = value;
4463
+ for (const field of type.fields) {
4464
+ collectAssetIds(record[field.name], field.type, out);
4370
4465
  }
4466
+ return out;
4467
+ }
4468
+ if (type.kind === "union") {
4469
+ for (const option of type.options) collectAssetIds(value, option, out);
4371
4470
  }
4372
4471
  return out;
4373
4472
  }
4374
- function collectStorageIdsFromRow(row, fields, out = /* @__PURE__ */ new Set()) {
4473
+ function collectAssetIdsFromRow(row, fields, out = /* @__PURE__ */ new Set()) {
4375
4474
  if (!row) return out;
4376
4475
  for (const field of fields) {
4377
- if (!isStorageFieldType(field.type)) continue;
4378
- collectStorageIds(row[field.name], field.type, out);
4476
+ collectAssetIds(row[field.name], field.type, out);
4379
4477
  }
4380
4478
  return out;
4381
4479
  }
4382
- function storageIdsRemovedByPatches(serverRowById, patches, fields) {
4383
- const removed = [];
4384
- for (const { id, patch } of patches) {
4385
- const before = serverRowById.get(id);
4386
- if (!before) continue;
4387
- const after = { ...before, ...patch };
4388
- for (const field of fields) {
4389
- if (!(field.name in patch)) continue;
4390
- if (!isStorageFieldType(field.type)) continue;
4391
- const beforeIds = collectStorageIds(before[field.name], field.type);
4392
- const afterIds = collectStorageIds(after[field.name], field.type);
4393
- for (const storageId of beforeIds) {
4394
- if (!afterIds.has(storageId)) removed.push(storageId);
4480
+ function collectAssetRefsFromRow(row, fields) {
4481
+ if (!row) return [];
4482
+ const refs = /* @__PURE__ */ new Map();
4483
+ const add = (assetId, fieldName, arrayIndex) => {
4484
+ const ref = { assetId, fieldName, arrayIndex };
4485
+ refs.set(`${assetId}\0${fieldName}\0${arrayIndex ?? ""}`, ref);
4486
+ };
4487
+ const visit = (value, type, fieldName, arrayIndex) => {
4488
+ if (isFileFieldType(type)) {
4489
+ const assetId = toDocumentId(value);
4490
+ if (assetId) add(assetId, fieldName, arrayIndex);
4491
+ return;
4492
+ }
4493
+ if (type.kind === "array" && Array.isArray(value)) {
4494
+ for (const [index, item] of value.entries()) {
4495
+ if (isFileFieldType(type.element)) {
4496
+ visit(item, type.element, fieldName, index);
4497
+ } else {
4498
+ visit(item, type.element, `${fieldName}[${index}]`, null);
4499
+ }
4395
4500
  }
4501
+ return;
4396
4502
  }
4397
- }
4398
- return removed;
4399
- }
4400
- function collectReferencedStorageIds(serverRowById, pendingEdits, draftRowIds, fields) {
4401
- const referenced = /* @__PURE__ */ new Set();
4402
- for (const row of serverRowById.values()) {
4403
- collectStorageIdsFromRow(row, fields, referenced);
4404
- }
4405
- for (const [rowId, patch] of pendingEdits) {
4406
- const baseline = isDraftRowId(rowId) ? patch : { ...serverRowById.get(rowId), ...patch };
4407
- collectStorageIdsFromRow(baseline, fields, referenced);
4408
- }
4409
- for (const rowId of draftRowIds) {
4410
- const patch = pendingEdits.get(rowId);
4411
- if (patch) collectStorageIdsFromRow(patch, fields, referenced);
4412
- }
4413
- return referenced;
4414
- }
4415
- function isDraftRowId(rowId) {
4416
- return rowId.startsWith("__draft__");
4503
+ if (type.kind === "object" && value !== null && typeof value === "object" && !Array.isArray(value)) {
4504
+ const record = value;
4505
+ for (const field of type.fields) {
4506
+ visit(record[field.name], field.type, `${fieldName}.${field.name}`, arrayIndex);
4507
+ }
4508
+ return;
4509
+ }
4510
+ if (type.kind === "union") {
4511
+ for (const option of type.options) visit(value, option, fieldName, arrayIndex);
4512
+ }
4513
+ };
4514
+ for (const field of fields) visit(row[field.name], field.type, field.name, null);
4515
+ return [...refs.values()];
4417
4516
  }
4418
4517
 
4419
- export { collectReferencedStorageIds, collectStorageIds, collectStorageIdsFromRow, isValidStorageId, storageIdsRemovedByPatches };
4420
- //# sourceMappingURL=storageFieldValues.js.map
4421
- //# sourceMappingURL=storageFieldValues.js.map
4518
+ export { collectAssetIds, collectAssetIdsFromRow, collectAssetRefsFromRow, isValidAssetId };
4519
+ //# sourceMappingURL=assetFieldValues.js.map
4520
+ //# sourceMappingURL=assetFieldValues.js.map