@odla-ai/brand 0.8.0 → 0.8.2

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.
package/dist/index.js CHANGED
@@ -1794,6 +1794,13 @@ function contrastReport(swatches) {
1794
1794
  return report;
1795
1795
  }
1796
1796
 
1797
+ // src/link-shape.ts
1798
+ function hasExactOneLink(value, expectedId) {
1799
+ if (Array.isArray(value))
1800
+ return value.length === 1 && value[0]?.id === expectedId;
1801
+ return typeof value === "object" && value !== null && value.id === expectedId;
1802
+ }
1803
+
1797
1804
  // src/skill/asset-tools.ts
1798
1805
  var MAX_VIEW_BYTES = 4718592;
1799
1806
  var IMAGE_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
@@ -1836,7 +1843,7 @@ function assetTools(ctx) {
1836
1843
  }
1837
1844
  });
1838
1845
  const row = (res[BRAND_NS.asset] ?? [])[0];
1839
- if (!row || row.status !== "live" || row.deletedAt || !Array.isArray(row.book) || row.book.length !== 1 || row.book[0]?.id !== book.id) throw new BrandNotFoundError(`asset ${assetId}`);
1846
+ if (!row || row.status !== "live" || row.deletedAt || !hasExactOneLink(row.book, book.id)) throw new BrandNotFoundError(`asset ${assetId}`);
1840
1847
  return row;
1841
1848
  };
1842
1849
  const viewAsset = {
@@ -1924,7 +1931,7 @@ async function proposalSourceAssetId(ctx, book, value) {
1924
1931
  });
1925
1932
  const rows = result[BRAND_NS.asset] ?? [];
1926
1933
  const asset = rows.length === 1 ? rows[0] : void 0;
1927
- if (!asset || asset.id !== sourceAssetId || asset.bookId !== book.id || asset.status !== "live" || asset.deletedAt !== void 0 || !Array.isArray(asset.book) || asset.book.length !== 1 || asset.book[0]?.id !== book.id) throw new BrandInputError(
1934
+ if (!asset || asset.id !== sourceAssetId || asset.bookId !== book.id || asset.status !== "live" || asset.deletedAt !== void 0 || !hasExactOneLink(asset.book, book.id)) throw new BrandInputError(
1928
1935
  "sourceAssetId must be a live asset in this brand book"
1929
1936
  );
1930
1937
  return sourceAssetId;
@@ -2366,7 +2373,7 @@ function readTools(ctx) {
2366
2373
  book: {}
2367
2374
  }
2368
2375
  });
2369
- const linked = (rows) => rows.filter((row) => row.bookId === authorizedBook.id && Array.isArray(row.book) && row.book.length === 1 && row.book[0]?.id === authorizedBook.id);
2376
+ const linked = (rows) => rows.filter((row) => row.bookId === authorizedBook.id && hasExactOneLink(row.book, authorizedBook.id));
2370
2377
  const sections = linked(
2371
2378
  res[BRAND_NS.section] ?? []
2372
2379
  );
@@ -2416,7 +2423,7 @@ function readTools(ctx) {
2416
2423
  book: {}
2417
2424
  }
2418
2425
  });
2419
- const rows = (res[BRAND_NS.asset] ?? []).filter((asset) => asset.bookId === ctx.bookId && Array.isArray(asset.book) && asset.book.length === 1 && asset.book[0]?.id === ctx.bookId);
2426
+ const rows = (res[BRAND_NS.asset] ?? []).filter((asset) => asset.bookId === ctx.bookId && hasExactOneLink(asset.book, ctx.bookId));
2420
2427
  if (rows.length === 0) {
2421
2428
  return { content: input.kind ? `(no ${String(input.kind)} assets uploaded yet)` : "(no assets uploaded yet)" };
2422
2429
  }
@@ -2545,7 +2552,7 @@ async function linkedAsset(ctx, book, assetId, allowDeleting = false) {
2545
2552
  });
2546
2553
  const row = rowAsWritten(BRAND_NS.asset, (result[BRAND_NS.asset] ?? [])[0]);
2547
2554
  const links = row?.book;
2548
- if (!row || row.bookId !== book.id || !Array.isArray(links) || links.length !== 1 || links[0]?.id !== book.id || row.status !== "live" && !(allowDeleting && row.status === "deleting")) throw new BrandNotFoundError(`asset ${assetId}`);
2555
+ if (!row || row.bookId !== book.id || !hasExactOneLink(links, book.id) || row.status !== "live" && !(allowDeleting && row.status === "deleting")) throw new BrandNotFoundError(`asset ${assetId}`);
2549
2556
  return row;
2550
2557
  }
2551
2558
 
@@ -2748,7 +2755,7 @@ async function handleAssetsRoot(ctx, req, bookId) {
2748
2755
  });
2749
2756
  const assets = (result[BRAND_NS.asset] ?? []).filter((value) => {
2750
2757
  const row = value;
2751
- return row.bookId === book.id && Array.isArray(row.book) && row.book.length === 1 && row.book[0]?.id === book.id;
2758
+ return row.bookId === book.id && hasExactOneLink(row.book, book.id);
2752
2759
  });
2753
2760
  return json({ assets });
2754
2761
  }
@@ -3234,7 +3241,7 @@ async function proposalReceiptByMutation(ctx, mutationKey) {
3234
3241
  }
3235
3242
  });
3236
3243
  const row = (result[BRAND_NS.approvalReceipt] ?? [])[0];
3237
- if (!row || !Array.isArray(row.book) || row.book.length !== 1 || row.book[0]?.id !== row.bookId) return void 0;
3244
+ if (!row || !hasExactOneLink(row.book, row.bookId)) return void 0;
3238
3245
  const { book: _book, ...receipt2 } = row;
3239
3246
  return receiptAsWritten(receipt2);
3240
3247
  }
@@ -3269,7 +3276,7 @@ async function receipt(ctx, id, actionDigest, expected) {
3269
3276
  }
3270
3277
  });
3271
3278
  const row = (result[BRAND_NS.approvalReceipt] ?? [])[0];
3272
- if (!row || row.actionDigest !== actionDigest || row.resolution !== "accepted" || row.bookId !== expected.bookId || row.reviewedProposal.bookId !== expected.bookId || row.reviewedProposal.kind !== expected.kind || !Array.isArray(row.book) || row.book.length !== 1 || row.book[0]?.id !== expected.bookId || expected.paletteId !== void 0 && row.paletteId !== expected.paletteId || expected.proposalId !== void 0 && row.proposalId !== expected.proposalId || expected.content !== void 0 && canonicalBrandJson(row.reviewedProposal.payload.content) !== canonicalBrandJson(expected.content)) throw new BrandConflictError(`approval receipt ${id} is invalid`);
3279
+ if (!row || row.actionDigest !== actionDigest || row.resolution !== "accepted" || row.bookId !== expected.bookId || row.reviewedProposal.bookId !== expected.bookId || row.reviewedProposal.kind !== expected.kind || !hasExactOneLink(row.book, expected.bookId) || expected.paletteId !== void 0 && row.paletteId !== expected.paletteId || expected.proposalId !== void 0 && row.proposalId !== expected.proposalId || expected.content !== void 0 && canonicalBrandJson(row.reviewedProposal.payload.content) !== canonicalBrandJson(expected.content)) throw new BrandConflictError(`approval receipt ${id} is invalid`);
3273
3280
  const { book: _book, ...stored } = row;
3274
3281
  const unhydrated = receiptAsWritten(stored);
3275
3282
  if (!await verifyBrandApprovalReceipt(unhydrated))
@@ -3284,8 +3291,11 @@ async function activePalette(ctx, book) {
3284
3291
  book: {}
3285
3292
  }
3286
3293
  });
3287
- const palette = (result[BRAND_NS.palette] ?? [])[0];
3288
- if (!palette || palette.bookId !== book.id || palette.status !== "active" || !palette.proposalId || !palette.approvalReceiptId || !palette.approvalActionDigest || !Array.isArray(palette.book) || palette.book.length !== 1 || palette.book[0]?.id !== book.id) throw new BrandConflictError(
3294
+ const palette = rowAsWritten(
3295
+ BRAND_NS.palette,
3296
+ (result[BRAND_NS.palette] ?? [])[0]
3297
+ );
3298
+ if (!palette || palette.bookId !== book.id || palette.status !== "active" || !palette.proposalId || !palette.approvalReceiptId || !palette.approvalActionDigest || !hasExactOneLink(palette.book, book.id)) throw new BrandConflictError(
3289
3299
  "active palette is missing valid approval provenance"
3290
3300
  );
3291
3301
  const approval = await receipt(
@@ -3332,9 +3342,12 @@ async function approvedTypography(ctx, book) {
3332
3342
  book: {}
3333
3343
  }
3334
3344
  });
3335
- const section = (result[BRAND_NS.section] ?? [])[0];
3345
+ const section = rowAsWritten(
3346
+ BRAND_NS.section,
3347
+ (result[BRAND_NS.section] ?? [])[0]
3348
+ );
3336
3349
  if (!section || section.status !== "approved") return {};
3337
- if (section.bookId !== book.id || section.kind !== "typography" || !section.approvalReceiptId || !section.approvalActionDigest || !Array.isArray(section.book) || section.book.length !== 1 || section.book[0]?.id !== book.id) throw new BrandConflictError(
3350
+ if (section.bookId !== book.id || section.kind !== "typography" || !section.approvalReceiptId || !section.approvalActionDigest || !hasExactOneLink(section.book, book.id)) throw new BrandConflictError(
3338
3351
  "approved typography is missing valid approval provenance"
3339
3352
  );
3340
3353
  assertSectionContent("typography", section.content);
@@ -3375,7 +3388,7 @@ async function proposalDecisionState(ctx, req, book, proposal, resolution, recei
3375
3388
  }
3376
3389
  });
3377
3390
  sourceAsset = (result[BRAND_NS.asset] ?? [])[0];
3378
- if (!sourceAsset || sourceAsset.id !== source.assetId || sourceAsset.bookId !== book.id || sourceAsset.status !== "live" || sourceAsset.deletedAt !== void 0 || sourceAsset.contentDigest !== source.contentDigest || sourceAsset.size !== source.objectSize || sourceAsset.contentType !== source.contentType || await brandJsonDigest({ appId: ctx.appId, path: sourceAsset.path }) !== source.pathDigest || sourceAsset.analysisRevision !== source.analysisRevision || (sourceAsset.analysisDigest ?? null) !== source.analysisDigest || !Array.isArray(sourceAsset.book) || sourceAsset.book.length !== 1 || sourceAsset.book[0]?.id !== book.id) throw new BrandConflictError(
3391
+ if (!sourceAsset || sourceAsset.id !== source.assetId || sourceAsset.bookId !== book.id || sourceAsset.status !== "live" || sourceAsset.deletedAt !== void 0 || sourceAsset.contentDigest !== source.contentDigest || sourceAsset.size !== source.objectSize || sourceAsset.contentType !== source.contentType || await brandJsonDigest({ appId: ctx.appId, path: sourceAsset.path }) !== source.pathDigest || sourceAsset.analysisRevision !== source.analysisRevision || (sourceAsset.analysisDigest ?? null) !== source.analysisDigest || !hasExactOneLink(sourceAsset.book, book.id)) throw new BrandConflictError(
3379
3392
  "source asset changed since the proposal was reviewed"
3380
3393
  );
3381
3394
  if (!await ctx.verifySourceAssetSnapshot({
@@ -3559,7 +3572,7 @@ async function handleProposalResolution(ctx, req, bookId, proposalId) {
3559
3572
  }
3560
3573
  });
3561
3574
  const proposal = proposalAsWritten((result[BRAND_NS.proposal] ?? [])[0]);
3562
- if (!proposal || proposal.bookId !== book.id || !Array.isArray(proposal.book) || proposal.book.length !== 1 || proposal.book[0]?.id !== book.id) throw new BrandNotFoundError(`proposal ${proposalId}`);
3575
+ if (!proposal || proposal.bookId !== book.id || !hasExactOneLink(proposal.book, book.id)) throw new BrandNotFoundError(`proposal ${proposalId}`);
3563
3576
  const current = proposalReviewSnapshot(proposal);
3564
3577
  const { reviewDigest, ...unsignedReview } = current;
3565
3578
  if (await brandJsonDigest(unsignedReview) !== reviewDigest || canonicalBrandJson(current) !== canonicalBrandJson(reviewed)) throw new BrandConflictError("proposal changed since review");
@@ -3703,7 +3716,7 @@ async function acceptedReceipt(db, bookId, receiptId, actionDigest) {
3703
3716
  }
3704
3717
  });
3705
3718
  const receipt2 = (result[BRAND_NS.approvalReceipt] ?? [])[0];
3706
- if (!receipt2 || receipt2.resolution !== "accepted" || receipt2.actionDigest !== actionDigest || !Array.isArray(receipt2.book) || receipt2.book.length !== 1 || receipt2.book[0]?.id !== bookId) return missing(bookId);
3719
+ if (!receipt2 || receipt2.resolution !== "accepted" || receipt2.actionDigest !== actionDigest || !hasExactOneLink(receipt2.book, bookId)) return missing(bookId);
3707
3720
  const { book: _book, ...stored } = receipt2;
3708
3721
  const unhydrated = receiptAsWritten(stored);
3709
3722
  if (!await verifyBrandApprovalReceipt(unhydrated)) return missing(bookId);
@@ -3719,7 +3732,7 @@ async function approvedCache(db, book) {
3719
3732
  }
3720
3733
  });
3721
3734
  const palette = (paletteResult[BRAND_NS.palette] ?? [])[0];
3722
- if (!palette || palette.status !== "active" || palette.approvalReceiptId !== cache.paletteReceiptId || palette.approvalActionDigest !== cache.paletteActionDigest || !Array.isArray(palette.book) || palette.book.length !== 1 || palette.book[0]?.id !== book.id) return missing(book.id);
3735
+ if (!palette || palette.status !== "active" || palette.approvalReceiptId !== cache.paletteReceiptId || palette.approvalActionDigest !== cache.paletteActionDigest || !hasExactOneLink(palette.book, book.id)) return missing(book.id);
3723
3736
  await acceptedReceipt(
3724
3737
  db,
3725
3738
  book.id,
@@ -3737,7 +3750,7 @@ async function approvedCache(db, book) {
3737
3750
  });
3738
3751
  const typography = (sectionResult[BRAND_NS.section] ?? [])[0];
3739
3752
  if (hasTypeId) {
3740
- if (!typography || typography.bookId !== book.id || typography.kind !== "typography" || typography.status !== "approved" || typography.approvalReceiptId !== cache.typographyReceiptId || typography.approvalActionDigest !== cache.typographyActionDigest || !Array.isArray(typography.book) || typography.book.length !== 1 || typography.book[0]?.id !== book.id) return missing(book.id);
3753
+ if (!typography || typography.bookId !== book.id || typography.kind !== "typography" || typography.status !== "approved" || typography.approvalReceiptId !== cache.typographyReceiptId || typography.approvalActionDigest !== cache.typographyActionDigest || !hasExactOneLink(typography.book, book.id)) return missing(book.id);
3741
3754
  await acceptedReceipt(
3742
3755
  db,
3743
3756
  book.id,
@@ -4142,7 +4155,7 @@ async function attachedBrandAssetIds(db, book, row) {
4142
4155
  const rows = result[BRAND_NS.asset] ?? [];
4143
4156
  if (rows.length !== 1) return null;
4144
4157
  const asset = rows[0];
4145
- return asset.id === id && asset.bookId === book.id && asset.status === "live" && asset.deletedAt === void 0 && Array.isArray(asset.book) && asset.book.length === 1 && asset.book[0]?.id === book.id ? id : null;
4158
+ return asset.id === id && asset.bookId === book.id && asset.status === "live" && asset.deletedAt === void 0 && hasExactOneLink(asset.book, book.id) ? id : null;
4146
4159
  }));
4147
4160
  return assets.filter((id) => id !== null);
4148
4161
  }