@odla-ai/brand 0.8.0 → 0.8.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.
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))
@@ -3285,7 +3292,7 @@ async function activePalette(ctx, book) {
3285
3292
  }
3286
3293
  });
3287
3294
  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(
3295
+ if (!palette || palette.bookId !== book.id || palette.status !== "active" || !palette.proposalId || !palette.approvalReceiptId || !palette.approvalActionDigest || !hasExactOneLink(palette.book, book.id)) throw new BrandConflictError(
3289
3296
  "active palette is missing valid approval provenance"
3290
3297
  );
3291
3298
  const approval = await receipt(
@@ -3334,7 +3341,7 @@ async function approvedTypography(ctx, book) {
3334
3341
  });
3335
3342
  const section = (result[BRAND_NS.section] ?? [])[0];
3336
3343
  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(
3344
+ if (section.bookId !== book.id || section.kind !== "typography" || !section.approvalReceiptId || !section.approvalActionDigest || !hasExactOneLink(section.book, book.id)) throw new BrandConflictError(
3338
3345
  "approved typography is missing valid approval provenance"
3339
3346
  );
3340
3347
  assertSectionContent("typography", section.content);
@@ -3375,7 +3382,7 @@ async function proposalDecisionState(ctx, req, book, proposal, resolution, recei
3375
3382
  }
3376
3383
  });
3377
3384
  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(
3385
+ 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
3386
  "source asset changed since the proposal was reviewed"
3380
3387
  );
3381
3388
  if (!await ctx.verifySourceAssetSnapshot({
@@ -3559,7 +3566,7 @@ async function handleProposalResolution(ctx, req, bookId, proposalId) {
3559
3566
  }
3560
3567
  });
3561
3568
  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}`);
3569
+ if (!proposal || proposal.bookId !== book.id || !hasExactOneLink(proposal.book, book.id)) throw new BrandNotFoundError(`proposal ${proposalId}`);
3563
3570
  const current = proposalReviewSnapshot(proposal);
3564
3571
  const { reviewDigest, ...unsignedReview } = current;
3565
3572
  if (await brandJsonDigest(unsignedReview) !== reviewDigest || canonicalBrandJson(current) !== canonicalBrandJson(reviewed)) throw new BrandConflictError("proposal changed since review");
@@ -3703,7 +3710,7 @@ async function acceptedReceipt(db, bookId, receiptId, actionDigest) {
3703
3710
  }
3704
3711
  });
3705
3712
  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);
3713
+ if (!receipt2 || receipt2.resolution !== "accepted" || receipt2.actionDigest !== actionDigest || !hasExactOneLink(receipt2.book, bookId)) return missing(bookId);
3707
3714
  const { book: _book, ...stored } = receipt2;
3708
3715
  const unhydrated = receiptAsWritten(stored);
3709
3716
  if (!await verifyBrandApprovalReceipt(unhydrated)) return missing(bookId);
@@ -3719,7 +3726,7 @@ async function approvedCache(db, book) {
3719
3726
  }
3720
3727
  });
3721
3728
  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);
3729
+ if (!palette || palette.status !== "active" || palette.approvalReceiptId !== cache.paletteReceiptId || palette.approvalActionDigest !== cache.paletteActionDigest || !hasExactOneLink(palette.book, book.id)) return missing(book.id);
3723
3730
  await acceptedReceipt(
3724
3731
  db,
3725
3732
  book.id,
@@ -3737,7 +3744,7 @@ async function approvedCache(db, book) {
3737
3744
  });
3738
3745
  const typography = (sectionResult[BRAND_NS.section] ?? [])[0];
3739
3746
  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);
3747
+ 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
3748
  await acceptedReceipt(
3742
3749
  db,
3743
3750
  book.id,
@@ -4142,7 +4149,7 @@ async function attachedBrandAssetIds(db, book, row) {
4142
4149
  const rows = result[BRAND_NS.asset] ?? [];
4143
4150
  if (rows.length !== 1) return null;
4144
4151
  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;
4152
+ return asset.id === id && asset.bookId === book.id && asset.status === "live" && asset.deletedAt === void 0 && hasExactOneLink(asset.book, book.id) ? id : null;
4146
4153
  }));
4147
4154
  return assets.filter((id) => id !== null);
4148
4155
  }