@blamejs/blamejs-shop 0.1.28 → 0.1.30
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/CHANGELOG.md +4 -0
- package/README.md +5 -1
- package/SECURITY.md +9 -0
- package/lib/admin.js +317 -1
- package/lib/asset-manifest.json +1 -1
- package/lib/product-qa.js +88 -0
- package/lib/storefront.js +397 -1
- package/lib/vendor/MANIFEST.json +2 -2
- package/lib/vendor/blamejs/CHANGELOG.md +6 -0
- package/lib/vendor/blamejs/README.md +3 -0
- package/lib/vendor/blamejs/api-snapshot.json +74 -2
- package/lib/vendor/blamejs/index.js +6 -0
- package/lib/vendor/blamejs/lib/base32.js +154 -0
- package/lib/vendor/blamejs/lib/json-schema.js +740 -0
- package/lib/vendor/blamejs/lib/totp.js +10 -31
- package/lib/vendor/blamejs/lib/uri-template.js +286 -0
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.12.64.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.65.json +27 -0
- package/lib/vendor/blamejs/release-notes/v0.12.66.json +18 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/base32.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-schema.test.js +134 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/uri-template.test.js +99 -0
- package/package.json +1 -1
package/lib/storefront.js
CHANGED
|
@@ -162,6 +162,7 @@ var LAYOUT =
|
|
|
162
162
|
" <ul>\n" +
|
|
163
163
|
" <li><a href=\"/\">All products</a></li>\n" +
|
|
164
164
|
" <li><a href=\"/collections\">Collections</a></li>\n" +
|
|
165
|
+
" <li><a href=\"/categories\">Categories</a></li>\n" +
|
|
165
166
|
" <li><a href=\"/?sort=new\">New arrivals</a></li>\n" +
|
|
166
167
|
" <li><a href=\"/?sort=sale\">On sale</a></li>\n" +
|
|
167
168
|
" <li><a href=\"/cart\">Cart</a></li>\n" +
|
|
@@ -747,6 +748,7 @@ var PRODUCT_PAGE =
|
|
|
747
748
|
" </div>\n" +
|
|
748
749
|
" </div>\n" +
|
|
749
750
|
" RAW_REVIEWS_PLACEHOLDER\n" +
|
|
751
|
+
" RAW_QA_PLACEHOLDER\n" +
|
|
750
752
|
"</section>\n";
|
|
751
753
|
|
|
752
754
|
// PDP gallery markup — composed once per render call from the
|
|
@@ -968,6 +970,132 @@ function _reviewMessagePage(opts, heading, message, cta) {
|
|
|
968
970
|
});
|
|
969
971
|
}
|
|
970
972
|
|
|
973
|
+
// Builds the PDP Product Q&A block from the published questions + their
|
|
974
|
+
// approved answers. Renders the "no questions yet" empty state when the
|
|
975
|
+
// product has none; `ctaHtml` is the "Ask a question" call-to-action
|
|
976
|
+
// (resolved by the route). Reuses the reviews section's theme classes
|
|
977
|
+
// so no new CSS ships. Mirrors the edge renderer
|
|
978
|
+
// (`worker/render/product.js`) byte-for-byte so both render paths stay
|
|
979
|
+
// in sync.
|
|
980
|
+
function _buildProductQa(questions, ctaHtml) {
|
|
981
|
+
var esc = b.template.escapeHtml;
|
|
982
|
+
questions = questions || [];
|
|
983
|
+
|
|
984
|
+
var head;
|
|
985
|
+
if (questions.length > 0) {
|
|
986
|
+
head = "<p class=\"reviews__count\">" + questions.length +
|
|
987
|
+
(questions.length === 1 ? " question answered" : " questions answered") + "</p>";
|
|
988
|
+
} else {
|
|
989
|
+
head = "<p class=\"reviews__empty\">No questions yet. Be the first to ask about this product.</p>";
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
var list = "";
|
|
993
|
+
for (var i = 0; i < questions.length; i += 1) {
|
|
994
|
+
var q = questions[i];
|
|
995
|
+
var answers = q.answers || [];
|
|
996
|
+
var answerHtml = "";
|
|
997
|
+
for (var j = 0; j < answers.length; j += 1) {
|
|
998
|
+
var a = answers[j];
|
|
999
|
+
var who = Number(a.is_operator) === 1
|
|
1000
|
+
? "<span class=\"review__verified\">Answered by the seller</span>"
|
|
1001
|
+
: (a.author === "system"
|
|
1002
|
+
? "<span class=\"review__verified\">Automated answer</span>"
|
|
1003
|
+
: "<span class=\"review__verified\">Customer answer</span>");
|
|
1004
|
+
var pinned = Number(a.pinned) === 1
|
|
1005
|
+
? "<span class=\"review__verified\">Top answer</span>"
|
|
1006
|
+
: "";
|
|
1007
|
+
answerHtml +=
|
|
1008
|
+
"<li class=\"review qa__answer\">" +
|
|
1009
|
+
"<div class=\"review__meta\">" + who + pinned + "</div>" +
|
|
1010
|
+
"<p class=\"review__body\">" + esc(String(a.body)) + "</p>" +
|
|
1011
|
+
"</li>";
|
|
1012
|
+
}
|
|
1013
|
+
var answerList = answerHtml
|
|
1014
|
+
? "<ul class=\"reviews__list qa__answers\">" + answerHtml + "</ul>"
|
|
1015
|
+
: "<p class=\"reviews__empty\">Awaiting an answer.</p>";
|
|
1016
|
+
list +=
|
|
1017
|
+
"<li class=\"review qa__question\">" +
|
|
1018
|
+
"<div class=\"review__head\">" +
|
|
1019
|
+
"<h3 class=\"review__title\">" + esc(String(q.body)) + "</h3>" +
|
|
1020
|
+
"</div>" +
|
|
1021
|
+
answerList +
|
|
1022
|
+
"</li>";
|
|
1023
|
+
}
|
|
1024
|
+
var listHtml = list ? "<ul class=\"reviews__list\">" + list + "</ul>" : "";
|
|
1025
|
+
|
|
1026
|
+
return "<section class=\"reviews qa\" aria-labelledby=\"qa-title\">" +
|
|
1027
|
+
"<h2 id=\"qa-title\" class=\"reviews__heading\">Questions & answers</h2>" +
|
|
1028
|
+
head +
|
|
1029
|
+
listHtml +
|
|
1030
|
+
(ctaHtml || "") +
|
|
1031
|
+
"</section>";
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
var QA_FORM_PAGE =
|
|
1035
|
+
"<section class=\"review-form-page\">\n" +
|
|
1036
|
+
" <nav class=\"breadcrumb\" aria-label=\"Breadcrumb\">\n" +
|
|
1037
|
+
" <ol>\n" +
|
|
1038
|
+
" <li><a href=\"/\">Shop</a></li>\n" +
|
|
1039
|
+
" <li><a href=\"/products/{{slug}}\">{{title}}</a></li>\n" +
|
|
1040
|
+
" <li aria-current=\"page\">Ask a question</li>\n" +
|
|
1041
|
+
" </ol>\n" +
|
|
1042
|
+
" </nav>\n" +
|
|
1043
|
+
" <h1 class=\"review-form-page__title\">Ask about {{title}}</h1>\n" +
|
|
1044
|
+
" RAW_NOTICE_PLACEHOLDER\n" +
|
|
1045
|
+
" <form class=\"review-form\" method=\"post\" action=\"/products/{{slug}}/question\">\n" +
|
|
1046
|
+
" <label class=\"form-field\">\n" +
|
|
1047
|
+
" <span class=\"form-field__label\">Your question</span>\n" +
|
|
1048
|
+
" <textarea name=\"body\" maxlength=\"4000\" rows=\"6\" required></textarea>\n" +
|
|
1049
|
+
" </label>\n" +
|
|
1050
|
+
" <button type=\"submit\" class=\"btn-primary\">Submit question</button>\n" +
|
|
1051
|
+
" </form>\n" +
|
|
1052
|
+
"</section>\n";
|
|
1053
|
+
|
|
1054
|
+
// Auth-gated question form. `opts.product` carries { title, slug };
|
|
1055
|
+
// `opts.notice` is an optional error string rendered above the form
|
|
1056
|
+
// (e.g. a validation rejection bounced back from POST).
|
|
1057
|
+
function renderQuestionForm(opts) {
|
|
1058
|
+
var esc = b.template.escapeHtml;
|
|
1059
|
+
var notice = opts.notice
|
|
1060
|
+
? "<p class=\"form-notice form-notice--error\" role=\"alert\">" + esc(String(opts.notice)) + "</p>"
|
|
1061
|
+
: "";
|
|
1062
|
+
var body = _render(QA_FORM_PAGE, {
|
|
1063
|
+
title: opts.product.title,
|
|
1064
|
+
slug: opts.product.slug,
|
|
1065
|
+
})
|
|
1066
|
+
.replace("RAW_NOTICE_PLACEHOLDER", notice);
|
|
1067
|
+
return _wrap({
|
|
1068
|
+
title: "Ask about " + opts.product.title,
|
|
1069
|
+
shop_name: opts.shop_name || "blamejs.shop",
|
|
1070
|
+
cart_count: opts.cart_count == null ? 0 : opts.cart_count,
|
|
1071
|
+
theme_css: opts.theme_css,
|
|
1072
|
+
body: body,
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// Generic single-message page for the Q&A flow (submission thank-you).
|
|
1077
|
+
// `cta` is an optional { href, label }. Reuses the review-message
|
|
1078
|
+
// layout classes.
|
|
1079
|
+
function _qaMessagePage(opts, heading, message, cta) {
|
|
1080
|
+
var esc = b.template.escapeHtml;
|
|
1081
|
+
var ctaHtml = cta
|
|
1082
|
+
? "<a class=\"btn-primary\" href=\"" + esc(cta.href) + "\">" + esc(cta.label) + "</a>"
|
|
1083
|
+
: "";
|
|
1084
|
+
var body =
|
|
1085
|
+
"<section class=\"review-message\">" +
|
|
1086
|
+
"<h1 class=\"review-message__title\">" + esc(heading) + "</h1>" +
|
|
1087
|
+
"<p class=\"review-message__lede\">" + esc(message) + "</p>" +
|
|
1088
|
+
ctaHtml +
|
|
1089
|
+
"</section>";
|
|
1090
|
+
return _wrap({
|
|
1091
|
+
title: heading,
|
|
1092
|
+
shop_name: opts.shop_name || "blamejs.shop",
|
|
1093
|
+
cart_count: opts.cart_count == null ? 0 : opts.cart_count,
|
|
1094
|
+
theme_css: opts.theme_css,
|
|
1095
|
+
body: body,
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
|
|
971
1099
|
// Remove control for a wishlist entry — a form POST back through the
|
|
972
1100
|
// toggle route with `return_to` so the customer lands back on the
|
|
973
1101
|
// account page (not the product PDP the default toggle returns to).
|
|
@@ -1265,6 +1393,123 @@ function renderCollection(opts) {
|
|
|
1265
1393
|
});
|
|
1266
1394
|
}
|
|
1267
1395
|
|
|
1396
|
+
// Storefront category index — the top-level category tree. Each card
|
|
1397
|
+
// links into a category browse page. Reuses the collection-index card
|
|
1398
|
+
// shell (same visual contract) so no new CSS ships. `opts.categories`
|
|
1399
|
+
// is the hydrated [{ slug, title, description, hero_image_url }] list of
|
|
1400
|
+
// active top-level categories (archived rows are dropped by the lib).
|
|
1401
|
+
function renderCategoryIndex(opts) {
|
|
1402
|
+
var esc = b.template.escapeHtml;
|
|
1403
|
+
var cats = opts.categories || [];
|
|
1404
|
+
var cardsHtml = "";
|
|
1405
|
+
for (var i = 0; i < cats.length; i += 1) {
|
|
1406
|
+
var c = cats[i];
|
|
1407
|
+
var media = c.hero_image_url
|
|
1408
|
+
? "<figure class=\"collection-index-card__media\"><img src=\"" + esc(_categoryHeroSrc(c.hero_image_url, opts.asset_prefix)) + "\" alt=\"" + esc(c.title) + "\" loading=\"lazy\"></figure>"
|
|
1409
|
+
: "<figure class=\"collection-index-card__media collection-index-card__media--empty\" aria-hidden=\"true\"></figure>";
|
|
1410
|
+
cardsHtml +=
|
|
1411
|
+
"<a class=\"collection-index-card\" href=\"/categories/" + esc(c.slug) + "\">" +
|
|
1412
|
+
media +
|
|
1413
|
+
"<div class=\"collection-index-card__meta\">" +
|
|
1414
|
+
"<h2 class=\"collection-index-card__title\">" + esc(c.title) + "</h2>" +
|
|
1415
|
+
(c.description ? "<p class=\"collection-index-card__desc\">" + esc(c.description) + "</p>" : "") +
|
|
1416
|
+
"</div>" +
|
|
1417
|
+
"</a>";
|
|
1418
|
+
}
|
|
1419
|
+
var inner = cardsHtml
|
|
1420
|
+
? "<div class=\"collection-index-grid\">" + cardsHtml + "</div>"
|
|
1421
|
+
: "<p class=\"collection-empty\">No categories yet.</p>";
|
|
1422
|
+
var body =
|
|
1423
|
+
"<section class=\"collection-index\">" +
|
|
1424
|
+
"<header class=\"section-head\"><p class=\"eyebrow\">Browse</p>" +
|
|
1425
|
+
"<h1 class=\"section-head__title\">Categories</h1></header>" +
|
|
1426
|
+
inner +
|
|
1427
|
+
"</section>";
|
|
1428
|
+
return _wrap({
|
|
1429
|
+
title: "Categories", shop_name: opts.shop_name || "blamejs.shop",
|
|
1430
|
+
cart_count: opts.cart_count == null ? 0 : opts.cart_count, theme_css: opts.theme_css, body: body,
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
// A single category's page — breadcrumb (root -> current), title,
|
|
1435
|
+
// optional description + hero, and a grid of the category's direct
|
|
1436
|
+
// child sub-categories. `opts.breadcrumbs` is the root->current chain
|
|
1437
|
+
// (the last entry is the current category, rendered as plain text);
|
|
1438
|
+
// `opts.children` is the hydrated direct-child list (empty -> graceful
|
|
1439
|
+
// empty state). Reuses the collection-page + collection-index card
|
|
1440
|
+
// shells so no new CSS ships.
|
|
1441
|
+
function renderCategory(opts) {
|
|
1442
|
+
var esc = b.template.escapeHtml;
|
|
1443
|
+
var cat = opts.category;
|
|
1444
|
+
var crumbs = opts.breadcrumbs || [];
|
|
1445
|
+
var children = opts.children || [];
|
|
1446
|
+
|
|
1447
|
+
var crumbHtml = "<li><a href=\"/\">Shop</a></li><li><a href=\"/categories\">Categories</a></li>";
|
|
1448
|
+
for (var ci = 0; ci < crumbs.length; ci += 1) {
|
|
1449
|
+
var bc = crumbs[ci];
|
|
1450
|
+
if (ci === crumbs.length - 1) {
|
|
1451
|
+
crumbHtml += "<li aria-current=\"page\">" + esc(bc.title) + "</li>";
|
|
1452
|
+
} else {
|
|
1453
|
+
crumbHtml += "<li><a href=\"/categories/" + esc(bc.slug) + "\">" + esc(bc.title) + "</a></li>";
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
// Hero reuses the collection-index card media shell (aspect-ratio +
|
|
1458
|
+
// object-fit cover) so no new CSS ships; it's a standalone figure
|
|
1459
|
+
// rather than a card link here.
|
|
1460
|
+
var hero = cat.hero_image_url
|
|
1461
|
+
? "<figure class=\"collection-index-card__media\"><img src=\"" + esc(_categoryHeroSrc(cat.hero_image_url, opts.asset_prefix)) + "\" alt=\"" + esc(cat.title) + "\"></figure>"
|
|
1462
|
+
: "";
|
|
1463
|
+
|
|
1464
|
+
var cardsHtml = "";
|
|
1465
|
+
for (var i = 0; i < children.length; i += 1) {
|
|
1466
|
+
var ch = children[i];
|
|
1467
|
+
var media = ch.hero_image_url
|
|
1468
|
+
? "<figure class=\"collection-index-card__media\"><img src=\"" + esc(_categoryHeroSrc(ch.hero_image_url, opts.asset_prefix)) + "\" alt=\"" + esc(ch.title) + "\" loading=\"lazy\"></figure>"
|
|
1469
|
+
: "<figure class=\"collection-index-card__media collection-index-card__media--empty\" aria-hidden=\"true\"></figure>";
|
|
1470
|
+
cardsHtml +=
|
|
1471
|
+
"<a class=\"collection-index-card\" href=\"/categories/" + esc(ch.slug) + "\">" +
|
|
1472
|
+
media +
|
|
1473
|
+
"<div class=\"collection-index-card__meta\">" +
|
|
1474
|
+
"<h2 class=\"collection-index-card__title\">" + esc(ch.title) + "</h2>" +
|
|
1475
|
+
(ch.description ? "<p class=\"collection-index-card__desc\">" + esc(ch.description) + "</p>" : "") +
|
|
1476
|
+
"</div>" +
|
|
1477
|
+
"</a>";
|
|
1478
|
+
}
|
|
1479
|
+
var grid = cardsHtml
|
|
1480
|
+
? "<div class=\"collection-index-grid\">" + cardsHtml + "</div>"
|
|
1481
|
+
: "<p class=\"collection-empty\">No sub-categories here yet.</p>";
|
|
1482
|
+
|
|
1483
|
+
var body =
|
|
1484
|
+
"<section class=\"collection-page\">" +
|
|
1485
|
+
"<nav class=\"breadcrumb\" aria-label=\"Breadcrumb\"><ol>" +
|
|
1486
|
+
crumbHtml +
|
|
1487
|
+
"</ol></nav>" +
|
|
1488
|
+
"<header class=\"collection-page__head\">" +
|
|
1489
|
+
"<h1 class=\"collection-page__title\">" + esc(cat.title) + "</h1>" +
|
|
1490
|
+
(cat.description ? "<p class=\"collection-page__desc\">" + esc(cat.description) + "</p>" : "") +
|
|
1491
|
+
"</header>" +
|
|
1492
|
+
hero +
|
|
1493
|
+
grid +
|
|
1494
|
+
"</section>";
|
|
1495
|
+
return _wrap({
|
|
1496
|
+
title: cat.title, shop_name: opts.shop_name || "blamejs.shop",
|
|
1497
|
+
cart_count: opts.cart_count == null ? 0 : opts.cart_count, theme_css: opts.theme_css, body: body,
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
// Resolve a category hero_image_url into a renderable src. The lib gates
|
|
1502
|
+
// hero_image_url to https:// OR a /-rooted absolute path at write time,
|
|
1503
|
+
// so an absolute https URL or a /-rooted path is used as-is; any other
|
|
1504
|
+
// value (a bare R2 key) is prefixed with the card asset prefix the same
|
|
1505
|
+
// way collection hero media resolves.
|
|
1506
|
+
function _categoryHeroSrc(url, assetPrefix) {
|
|
1507
|
+
if (typeof url !== "string" || !url.length) return "";
|
|
1508
|
+
if (url.charCodeAt(0) === 47 /* "/" */) return url;
|
|
1509
|
+
if (/^https:\/\//i.test(url)) return url;
|
|
1510
|
+
return (assetPrefix || "/assets/") + url;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1268
1513
|
// Account "Recently viewed" page — a newest-first grid of products the
|
|
1269
1514
|
// signed-in customer has opened, reusing the standard product card.
|
|
1270
1515
|
// `opts.products` is a resolved [{ slug, title, price, image_url,
|
|
@@ -1881,6 +2126,7 @@ function renderProduct(opts) {
|
|
|
1881
2126
|
// theme's `{{{ reviews_html }}}` raw slot. The bundled themes
|
|
1882
2127
|
// include the slot; a custom theme opts in by adding it.
|
|
1883
2128
|
reviews_html: _buildReviews(opts.review_summary, opts.reviews, opts.review_cta),
|
|
2129
|
+
qa_html: _buildProductQa(opts.qa_questions, opts.qa_cta),
|
|
1884
2130
|
wishlist_html: _buildWishlist(opts.product.id, opts.wishlist_count),
|
|
1885
2131
|
asset_css_main: opts.theme.assetUrl("css/main.css"),
|
|
1886
2132
|
});
|
|
@@ -1891,6 +2137,7 @@ function renderProduct(opts) {
|
|
|
1891
2137
|
if (!rows) rows = "<tr><td colspan=\"4\" class=\"empty\">No variants available.</td></tr>";
|
|
1892
2138
|
var galleryHtml = _buildPdpGallery(opts.product, opts.media || [], opts.asset_prefix || "/assets/");
|
|
1893
2139
|
var reviewsHtml = _buildReviews(opts.review_summary, opts.reviews, opts.review_cta);
|
|
2140
|
+
var qaHtml = _buildProductQa(opts.qa_questions, opts.qa_cta);
|
|
1894
2141
|
var wishlistHtml = _buildWishlist(opts.product.id, opts.wishlist_count);
|
|
1895
2142
|
var body = _render(PRODUCT_PAGE, {
|
|
1896
2143
|
title: opts.product.title,
|
|
@@ -1900,7 +2147,8 @@ function renderProduct(opts) {
|
|
|
1900
2147
|
.replace("RAW_GALLERY_PLACEHOLDER", galleryHtml)
|
|
1901
2148
|
.replace("RAW_ROWS_PLACEHOLDER", rows)
|
|
1902
2149
|
.replace("RAW_WISHLIST_PLACEHOLDER", wishlistHtml)
|
|
1903
|
-
.replace("RAW_REVIEWS_PLACEHOLDER", reviewsHtml)
|
|
2150
|
+
.replace("RAW_REVIEWS_PLACEHOLDER", reviewsHtml)
|
|
2151
|
+
.replace("RAW_QA_PLACEHOLDER", qaHtml);
|
|
1904
2152
|
// Product-specific OpenGraph + Twitter Card values so shares
|
|
1905
2153
|
// unfurl as "Operator Tee — blamejs.shop" with the SVG hero, not
|
|
1906
2154
|
// the default shop-level description + brand logo.
|
|
@@ -3289,6 +3537,27 @@ function mount(router, deps) {
|
|
|
3289
3537
|
try { wishlistCount = await deps.wishlist.countForProduct(product.id); }
|
|
3290
3538
|
catch (_e) { wishlistCount = 0; }
|
|
3291
3539
|
}
|
|
3540
|
+
// Published Q&A — approved questions + their approved answers. A
|
|
3541
|
+
// failed read (e.g. the product_qa tables not yet migrated) degrades
|
|
3542
|
+
// to the empty state rather than 500-ing the PDP — Q&A is
|
|
3543
|
+
// supplementary to the buy path, like reviews. Mirrors the edge
|
|
3544
|
+
// renderer's missing-table resilience.
|
|
3545
|
+
var qaQuestions, qaCta;
|
|
3546
|
+
if (deps.productQa) {
|
|
3547
|
+
qaQuestions = [];
|
|
3548
|
+
try {
|
|
3549
|
+
var qList = (await deps.productQa.questionsForProduct({ product_id: product.id, limit: 20 })).rows;
|
|
3550
|
+
for (var qi = 0; qi < qList.length; qi += 1) {
|
|
3551
|
+
var qrow = qList[qi];
|
|
3552
|
+
qrow.answers = await deps.productQa.answersForQuestion(qrow.id, { limit: 20 });
|
|
3553
|
+
qaQuestions.push(qrow);
|
|
3554
|
+
}
|
|
3555
|
+
} catch (_e) { qaQuestions = []; }
|
|
3556
|
+
// The form route enforces auth, so the CTA links there
|
|
3557
|
+
// unconditionally; logged-out shoppers get redirected to login.
|
|
3558
|
+
qaCta = "<a class=\"btn-secondary reviews__cta\" href=\"/products/" +
|
|
3559
|
+
b.template.escapeHtml(product.slug) + "/question\">Ask a question</a>";
|
|
3560
|
+
}
|
|
3292
3561
|
// Log the view for a signed-in customer so it surfaces on their
|
|
3293
3562
|
// "Recently viewed" account page. Drop-silent — a recording failure
|
|
3294
3563
|
// (table not migrated, write contention) must never break the PDP
|
|
@@ -3310,6 +3579,8 @@ function mount(router, deps) {
|
|
|
3310
3579
|
review_summary: reviewSummary,
|
|
3311
3580
|
reviews: reviewRows,
|
|
3312
3581
|
review_cta: reviewCta,
|
|
3582
|
+
qa_questions: qaQuestions,
|
|
3583
|
+
qa_cta: qaCta,
|
|
3313
3584
|
wishlist_count: wishlistCount,
|
|
3314
3585
|
shop_name: shopName,
|
|
3315
3586
|
cart_count: cartCount,
|
|
@@ -3356,6 +3627,60 @@ function mount(router, deps) {
|
|
|
3356
3627
|
});
|
|
3357
3628
|
}
|
|
3358
3629
|
|
|
3630
|
+
// Category navigation — the hierarchical category tree surfaced as
|
|
3631
|
+
// public browse pages. The index lists the top-level categories; each
|
|
3632
|
+
// category page renders its breadcrumb chain + direct child sub-
|
|
3633
|
+
// categories. Mounted when the categoryNavigation primitive is wired.
|
|
3634
|
+
if (deps.categoryNavigation) {
|
|
3635
|
+
router.get("/categories", async function (req, res) {
|
|
3636
|
+
// Top-level categories only (parent_slug omitted). The lib drops
|
|
3637
|
+
// archived rows from every read, so the index is fresh against the
|
|
3638
|
+
// active tree.
|
|
3639
|
+
var cats = await deps.categoryNavigation.categoriesByParent({});
|
|
3640
|
+
var active = [];
|
|
3641
|
+
for (var i = 0; i < cats.length; i += 1) {
|
|
3642
|
+
if (cats[i].active) active.push(cats[i]);
|
|
3643
|
+
}
|
|
3644
|
+
var cartCount = await _cartCountForReq(req);
|
|
3645
|
+
_send(res, 200, renderCategoryIndex({
|
|
3646
|
+
categories: active, shop_name: shopName, cart_count: cartCount, asset_prefix: _cardAssetPrefix,
|
|
3647
|
+
}));
|
|
3648
|
+
});
|
|
3649
|
+
|
|
3650
|
+
router.get("/categories/:slug", async function (req, res) {
|
|
3651
|
+
var slug = req.params && req.params.slug;
|
|
3652
|
+
// getCategory() / breadcrumbsFor() / categoriesByParent() throw a
|
|
3653
|
+
// TypeError on a malformed slug shape (the primitive validates the
|
|
3654
|
+
// slug regex). A bad path segment, an unknown slug, or an archived
|
|
3655
|
+
// category is a 404, not a 500 — this is a defensive request-shape
|
|
3656
|
+
// reader.
|
|
3657
|
+
var cat, crumbs, children;
|
|
3658
|
+
try {
|
|
3659
|
+
cat = slug ? await deps.categoryNavigation.getCategory(slug) : null;
|
|
3660
|
+
if (!cat || !cat.active) return _send(res, 404, renderNotFound({ shop_name: shopName, theme: theme }));
|
|
3661
|
+
crumbs = await deps.categoryNavigation.breadcrumbsFor({ slug: slug });
|
|
3662
|
+
children = await deps.categoryNavigation.categoriesByParent({ parent_slug: slug });
|
|
3663
|
+
} catch (e) {
|
|
3664
|
+
if (e instanceof TypeError) return _send(res, 404, renderNotFound({ shop_name: shopName, theme: theme }));
|
|
3665
|
+
// A CATEGORY_NOT_FOUND from breadcrumbsFor / categoriesByParent
|
|
3666
|
+
// (e.g. the row was archived between reads) is also a 404.
|
|
3667
|
+
if (e && e.code === "CATEGORY_NOT_FOUND") return _send(res, 404, renderNotFound({ shop_name: shopName, theme: theme }));
|
|
3668
|
+
throw e;
|
|
3669
|
+
}
|
|
3670
|
+
// Only surface active sub-categories (the lib already drops archived
|
|
3671
|
+
// rows; this also hides operator-unpublished ones).
|
|
3672
|
+
var activeChildren = [];
|
|
3673
|
+
for (var i = 0; i < children.length; i += 1) {
|
|
3674
|
+
if (children[i].active) activeChildren.push(children[i]);
|
|
3675
|
+
}
|
|
3676
|
+
var cartCount = await _cartCountForReq(req);
|
|
3677
|
+
_send(res, 200, renderCategory({
|
|
3678
|
+
category: cat, breadcrumbs: crumbs, children: activeChildren,
|
|
3679
|
+
shop_name: shopName, cart_count: cartCount, asset_prefix: _cardAssetPrefix,
|
|
3680
|
+
}));
|
|
3681
|
+
});
|
|
3682
|
+
}
|
|
3683
|
+
|
|
3359
3684
|
router.get("/cart", async function (req, res) {
|
|
3360
3685
|
var sid = _readSidCookie(req);
|
|
3361
3686
|
if (!sid) {
|
|
@@ -5303,6 +5628,77 @@ function mount(router, deps) {
|
|
|
5303
5628
|
));
|
|
5304
5629
|
});
|
|
5305
5630
|
}
|
|
5631
|
+
|
|
5632
|
+
// Product Q&A — asking a question requires a logged-in customer (no
|
|
5633
|
+
// verified-purchase gate; any signed-in shopper can ask). The
|
|
5634
|
+
// question lands `pending` and surfaces on the PDP once an operator
|
|
5635
|
+
// approves it. Customer-authored answers aren't accepted from the
|
|
5636
|
+
// storefront — answering is an operator action in the admin console
|
|
5637
|
+
// (the lib models customer answers, but exposing a public answer
|
|
5638
|
+
// form invites an unmoderated reply surface we don't ship in v1;
|
|
5639
|
+
// operators post the authoritative answer). Only mounts when the
|
|
5640
|
+
// productQa primitive is wired.
|
|
5641
|
+
if (deps.productQa) {
|
|
5642
|
+
async function _qaGateContext(req, res) {
|
|
5643
|
+
var slug = req.params && req.params.slug;
|
|
5644
|
+
var product = slug ? await deps.catalog.products.bySlug(slug) : null;
|
|
5645
|
+
if (!product) { _send(res, 404, renderNotFound({ shop_name: shopName, theme: theme })); return null; }
|
|
5646
|
+
var auth;
|
|
5647
|
+
try { auth = _currentCustomer(req); }
|
|
5648
|
+
catch (e) {
|
|
5649
|
+
if (e && e.code === "vault/not-initialized") { _serviceUnavailable(res, "auth not configured"); return null; }
|
|
5650
|
+
throw e;
|
|
5651
|
+
}
|
|
5652
|
+
if (!auth) {
|
|
5653
|
+
res.status(303); res.setHeader && res.setHeader("location", "/account/login");
|
|
5654
|
+
res.end ? res.end() : res.send("");
|
|
5655
|
+
return null;
|
|
5656
|
+
}
|
|
5657
|
+
var cartCount = await _cartCountForReq(req);
|
|
5658
|
+
return { product: product, auth: auth, cartCount: cartCount };
|
|
5659
|
+
}
|
|
5660
|
+
|
|
5661
|
+
router.get("/products/:slug/question", async function (req, res) {
|
|
5662
|
+
var ctx = await _qaGateContext(req, res);
|
|
5663
|
+
if (!ctx) return;
|
|
5664
|
+
_send(res, 200, renderQuestionForm({
|
|
5665
|
+
product: { title: ctx.product.title, slug: ctx.product.slug },
|
|
5666
|
+
shop_name: shopName,
|
|
5667
|
+
cart_count: ctx.cartCount,
|
|
5668
|
+
}));
|
|
5669
|
+
});
|
|
5670
|
+
|
|
5671
|
+
router.post("/products/:slug/question", async function (req, res) {
|
|
5672
|
+
var ctx = await _qaGateContext(req, res);
|
|
5673
|
+
if (!ctx) return;
|
|
5674
|
+
var body = req.body || {};
|
|
5675
|
+
try {
|
|
5676
|
+
await deps.productQa.submitQuestion({
|
|
5677
|
+
product_id: ctx.product.id,
|
|
5678
|
+
customer_id: ctx.auth.customer_id,
|
|
5679
|
+
body: body.body,
|
|
5680
|
+
});
|
|
5681
|
+
} catch (e) {
|
|
5682
|
+
// Shape rejections bounce back to the form with the reason;
|
|
5683
|
+
// anything else is a real 500.
|
|
5684
|
+
if (e instanceof TypeError) {
|
|
5685
|
+
return _send(res, 400, renderQuestionForm({
|
|
5686
|
+
product: { title: ctx.product.title, slug: ctx.product.slug },
|
|
5687
|
+
notice: (e && e.message) || "Please check your question and try again.",
|
|
5688
|
+
shop_name: shopName,
|
|
5689
|
+
cart_count: ctx.cartCount,
|
|
5690
|
+
}));
|
|
5691
|
+
}
|
|
5692
|
+
throw e;
|
|
5693
|
+
}
|
|
5694
|
+
_send(res, 200, _qaMessagePage(
|
|
5695
|
+
{ shop_name: shopName, cart_count: ctx.cartCount },
|
|
5696
|
+
"Thanks for your question",
|
|
5697
|
+
"Your question has been submitted and is pending moderation. It will appear on the product page once an operator approves and answers it.",
|
|
5698
|
+
{ href: "/products/" + ctx.product.slug, label: "Back to product" },
|
|
5699
|
+
));
|
|
5700
|
+
});
|
|
5701
|
+
}
|
|
5306
5702
|
}
|
|
5307
5703
|
|
|
5308
5704
|
// POST /cart/lines — add a line. Reads variant_id + qty from the
|
package/lib/vendor/MANIFEST.json
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
"_about": "blamejs.shop vendors a single framework — blamejs — which itself bundles every server-side crypto/identity dependency. The transitive packages blamejs ships are surfaced in its own MANIFEST.json at lib/vendor/blamejs/lib/vendor/MANIFEST.json — Trivy / Grype rely on that nested data for CVE attribution.",
|
|
4
4
|
"packages": {
|
|
5
5
|
"blamejs": {
|
|
6
|
-
"version": "0.12.
|
|
7
|
-
"tag": "v0.12.
|
|
6
|
+
"version": "0.12.66",
|
|
7
|
+
"tag": "v0.12.66",
|
|
8
8
|
"license": "Apache-2.0",
|
|
9
9
|
"author": "blamejs contributors",
|
|
10
10
|
"source": "https://github.com/blamejs/blamejs",
|
|
@@ -8,6 +8,12 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.12.x
|
|
10
10
|
|
|
11
|
+
- v0.12.66 (2026-05-26) — **`b.uriTemplate` — RFC 6570 URI Template expansion.** Expand RFC 6570 URI Templates — the {var} syntax that OpenAPI links, HAL _links, and hypermedia API clients use to turn a template plus a set of variables into a concrete URI. The full Level 4 grammar is supported: every operator ({+var} reserved, {#var} fragment, {.var} label, {/var} path, {;var} path-style parameters, {?var} query, {&var} query continuation), the {var:3} prefix modifier, and the {var*} explode modifier for lists and associative arrays. b.uriTemplate.expand(template, vars) returns the expanded string; b.uriTemplate.compile(template) parses once for templates applied to many variable sets. A malformed template (unclosed expression, reserved operator, non-numeric prefix, unmatched brace) throws UriTemplateError. Verified against the official uritemplate-test conformance suite (all 135 spec, extended, and negative cases). **Added:** *`b.uriTemplate.expand` / `b.uriTemplate.compile`* — RFC 6570 URI Template expansion, full Level 4. `expand(template, vars)` substitutes variables into a template and returns the URI; `compile(template)` returns a reusable `{ expand }` for repeated use. Variable values may be strings, numbers, booleans, arrays (lists), or plain objects (associative arrays); undefined, null, and empty list/map variables are omitted. All eight operators, the `:N` prefix modifier, and the `*` explode modifier follow §3.2, including reserved-set encoding for `{+var}` / `{#var}`. Composes naturally with `b.hal`, `b.linkHeader`, and `b.openapi` link objects. A malformed template throws `UriTemplateError`.
|
|
12
|
+
|
|
13
|
+
- v0.12.65 (2026-05-26) — **`b.base32` — RFC 4648 Base32 encode / decode.** Encode and decode RFC 4648 Base32 — the case-insensitive alphabet behind TOTP / 2FA secrets, DNSSEC NSEC3 hashes, and human-transcribable identifiers. Both RFC 4648 variants are supported: the standard alphabet (the default) and the extended-hex alphabet. b.base32.encode pads to an 8-character boundary by default (pass padding: false for the bare form TOTP key URIs use); b.base32.decode is strict by default but accepts the real-world shapes humans produce — lower-case, embedded spaces and dashes, missing padding — under loose: true. Verified against the RFC 4648 §10 test vectors for both alphabets. The TOTP primitive now composes this codec instead of carrying its own Base32 implementation. **Added:** *`b.base32.encode` / `b.base32.decode`* — RFC 4648 Base32 codec. `encode(buf, opts)` takes a Buffer or Uint8Array and returns a Base32 string, padded to an 8-character boundary unless `padding: false`; `decode(str, opts)` returns a Buffer. The `variant` option selects the standard (`"rfc4648"`, default) or extended-hex (`"rfc4648-hex"`) alphabet. Decoding is strict by default — any character outside the alphabet throws `Base32Error` — and `loose: true` up-cases the input and ignores embedded spaces, dashes, and missing padding, which is how copied TOTP keys and hand-typed codes arrive. **Changed:** *TOTP composes `b.base32`* — `b.auth.totp` now encodes and decodes its secrets through `b.base32` rather than a private Base32 implementation. Behavior is unchanged — secrets are still emitted unpadded and parsed leniently (case-insensitive, ignoring spaces and dashes).
|
|
14
|
+
|
|
15
|
+
- v0.12.64 (2026-05-25) — **`b.jsonSchema` — JSON Schema 2020-12 validation.** Validate JSON against a JSON Schema 2020-12 document — the dialect OpenAPI 3.1 adopted and the most widely implemented schema language. b.jsonSchema.compile(schema) returns a reusable validator; b.jsonSchema.validate(schema, instance) compiles and runs in one call, returning { valid, errors } where each error names the failing instance location, keyword, and schema path. The full 2020-12 vocabulary is supported: every applicator (allOf / anyOf / oneOf / not / if-then-else, properties / patternProperties / additionalProperties / prefixItems / items / contains), the annotation-aware unevaluatedProperties / unevaluatedItems, every assertion keyword, and reference resolution ($ref / $anchor / $dynamicRef / $dynamicAnchor / $defs / $id base URIs). format is an annotation by default (opt in to assertion with assertFormat). External references resolve through an operator-supplied schema map — never a network fetch. Verified against the official JSON-Schema-Test-Suite (1292 of 1295 draft2020-12 cases; the remainder need the bundled dialect metaschema or $vocabulary selection, both opt-in). This is the standards-track counterpart to the fluent b.safeSchema builder and the portable b.jtd. **Added:** *`b.jsonSchema` — JSON Schema 2020-12* — `compile(schema, opts)` returns `{ validate, isValid }`; `validate(schema, instance, opts)` and `isValid(schema, instance, opts)` compile and run in one call. `validate` returns `{ valid, errors }`, each error a `{ instancePath, keyword, schemaPath, message }`. The full 2020-12 vocabulary is implemented — applicators, annotation-aware `unevaluatedProperties` / `unevaluatedItems`, every assertion keyword, and `$ref` / `$anchor` / `$dynamicRef` / `$dynamicAnchor` / `$defs` / `$id` resolution. `format` is an annotation by default (`assertFormat: true` to assert). External references resolve through `opts.schemas` (a URI→schema map), never a network fetch. Reach for it when the schema is an existing JSON Schema document (an API contract, OpenAPI component, or config schema); `b.safeSchema` remains the fluent in-process builder and `b.jtd` the portable codegen-friendly option. Validating a schema document against the dialect metaschema requires supplying that metaschema via `opts.schemas`, and `$vocabulary`-based keyword selection is not honored (every standard keyword always asserts).
|
|
16
|
+
|
|
11
17
|
- v0.12.63 (2026-05-25) — **`b.cloudEvents` gains the JSON event format, batch, and the HTTP binding.** b.cloudEvents grows beyond wrap / parse into a full CloudEvents 1.0.2 surface. b.cloudEvents.validate / isValid check an envelope against the spec without throwing (the non-throwing companion to parse). toJSON / fromJSON serialize and parse the JSON event format, and toJSONBatch / fromJSONBatch handle the JSON batch format; untrusted bodies parse through the framework's bounded, prototype-pollution-safe JSON reader. The new http.* binding speaks both content modes the spec defines — binary mode spreads context attributes across percent-encoded ce-* headers with the data in the body, structured mode carries the whole event as application/cloudevents+json — plus the batch mode, and http.decode auto-detects the incoming mode from Content-Type exactly as a conformant receiver does. Verified against the spec's normative example events. **Added:** *`b.cloudEvents` JSON event format, batch, and HTTP binding* — `validate` / `isValid` report spec violations without throwing (the non-throwing companion to `parse`). `toJSON` / `fromJSON` and `toJSONBatch` / `fromJSONBatch` serialize and parse the JSON event and batch formats over the existing envelope shape. `http.encodeBinary` / `http.encodeStructured` / `http.encodeBatch` render the three HTTP content modes — binary spreads attributes across percent-encoded `ce-*` headers, structured and batched carry the event(s) as `application/cloudevents+json` / `application/cloudevents-batch+json` — and `http.decode` parses a request back into an envelope (or array) by auto-detecting the mode from `Content-Type`. `b.jtd` or `b.safeSchema` still validate the event's `data` payload. **Fixed:** *`b.csp.build` accepts `fenced-frame-src` and `webrtc`* — The CSP3 `fenced-frame-src` directive — which the default security-headers policy emits to block `<fencedframe>` embeds — was missing from the builder's recognized-directive set, so the default policy could not round-trip through `b.csp.build` (it threw `csp/unknown-directive`). Both `fenced-frame-src` and the CSP3 `webrtc` directive are now recognized.
|
|
12
18
|
|
|
13
19
|
- v0.12.62 (2026-05-26) — **`b.jtd` — JSON Type Definition validation (RFC 8927).** Validate JSON against a JSON Type Definition schema (RFC 8927) — a small, portable, cross-implementation schema language, the interop-friendly companion to the framework's fluent b.safeSchema builder. b.jtd.validate(schema, instance) returns an array of { instancePath, schemaPath } errors (empty = valid); b.jtd.isValid is the boolean form. All eight schema forms are supported — empty, type, enum, elements, properties (with optional / additional properties and nullable), values, discriminator (with mapping), and ref (with definitions) — including the integer-range and RFC 3339 timestamp types. A malformed schema is rejected at compile time with jtd/bad-schema rather than silently mis-validating. Verified against the official json-typedef-spec suites: all 316 validation cases and all 49 invalid-schema cases. **Added:** *`b.jtd.validate(schema, instance)` / `b.jtd.isValid(schema, instance)`* — `validate` returns the RFC 8927 error list — each `{ instancePath, schemaPath }` naming the offending value and the broken schema rule — and `isValid` is the boolean convenience form. Supports every JTD form and type: the numeric types enforce their exact ranges (int8 … uint32, float32 / float64), `timestamp` requires an RFC 3339 date-time, `properties` honours `optionalProperties` / `additionalProperties` / `nullable`, and `discriminator` selects a `mapping` schema by a tag property. The schema is checked for well-formedness before validation, so unknown keywords, multiple forms, bad refs, or a discriminator over a non-properties mapping all throw `jtd/bad-schema`. Use JTD for schemas you share across implementations or generate code from; use `b.safeSchema` for in-process fluent validation.
|
|
@@ -99,7 +99,10 @@ The framework bundles the surface a typical Node app reaches for. Every primitiv
|
|
|
99
99
|
- **Signed webhooks + API encryption** — SLH-DSA-SHAKE-256f default; ML-DSA-65 opt-in; ECIES API encryption (`b.webhook`, `b.crypto`)
|
|
100
100
|
- **HPKE / HTTP signatures** — RFC 9180 HPKE with ML-KEM-1024 + HKDF-SHA3-512 + ChaCha20-Poly1305 (`b.crypto.hpke`); RFC 9421 HTTP Message Signatures with derived components and ed25519 / ML-DSA-65 (`b.crypto.httpSig`); RFC 9530 Content-Digest / Repr-Digest body-integrity fields (SHA-256 / SHA-512, legacy algorithms refused — `b.contentDigest`) to sign the digest rather than the whole body
|
|
101
101
|
- **Link header** — RFC 8288 Web Linking codec (`b.linkHeader.parse` / `serialize`): parse and build `Link: <uri>; rel="next"` relations, the standard REST pagination mechanism; quote-aware (a comma inside a quoted parameter never splits the list)
|
|
102
|
+
- **URI Templates** — RFC 6570 expansion (`b.uriTemplate.expand` / `compile`): full Level 4 — every operator, the `:N` prefix and `*` explode modifiers — turning `{/path}{?q*}` plus variables into a concrete URI; validated against the official uritemplate-test suite. The `{var}` syntax behind OpenAPI links and HAL `_links`
|
|
102
103
|
- **JSON Type Definition** — RFC 8927 validation (`b.jtd.validate` / `isValid`): portable, cross-implementation schema validation (all eight forms — type / enum / elements / properties / values / discriminator / ref / empty), returning instancePath / schemaPath errors; validated against the official 316-case suite. Interop companion to the fluent `b.safeSchema` builder
|
|
104
|
+
- **JSON Schema 2020-12** — the OpenAPI 3.1 dialect (`b.jsonSchema.compile` / `validate` / `isValid`): full vocabulary including every applicator, annotation-aware `unevaluatedProperties` / `unevaluatedItems`, and `$ref` / `$dynamicRef` / `$anchor` / `$id` resolution (external refs via an operator-supplied schema map, never a network fetch); `format` is an annotation unless `assertFormat` is set; returns located `{ valid, errors }`. Validated against the official JSON-Schema-Test-Suite. Standards-track counterpart to `b.safeSchema` and `b.jtd`
|
|
105
|
+
- **Base32** — RFC 4648 codec (`b.base32.encode` / `decode`): standard + extended-hex alphabets, padded or bare, strict or lenient decode (case-insensitive, ignoring spaces / dashes for copied TOTP keys); validated against the RFC 4648 §10 vectors. The codec behind `b.auth.totp` secrets
|
|
103
106
|
- **JSONPath** — full RFC 9535 query evaluator (`b.jsonPath.query` / `paths`): name / wildcard / index / slice / descendant selectors, `?filter` expressions, and the five standard functions, with compile-time well-typedness checks (validated against the official 703-case compliance suite); complements the JSONPath guards
|
|
104
107
|
- **JSON Pointer / Patch** — RFC 6901 `b.jsonPointer.get` (reference a value by `/foo/0/bar`) + RFC 6902 `b.jsonPatch.apply` (atomic add / remove / replace / move / copy / test for HTTP PATCH; the input document is never mutated, structural `test` comparison) + RFC 7396 `b.jsonMergePatch.merge` (the `merge-patch+json` partial-document format; both PATCH formats are prototype-pollution-safe)
|
|
105
108
|
- **Canonical JSON** — RFC 8785 JSON Canonicalization Scheme (`b.canonicalJson.stringifyJcs`): the deterministic, sorted-key byte form to hash or sign (custom credentials, receipts, deterministic request signing); UTF-16 key ordering + ECMAScript number formatting, with a lenient `stringify` variant for Buffers / Dates / BigInts
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"frameworkVersion": "0.12.
|
|
4
|
-
"createdAt": "2026-05-
|
|
3
|
+
"frameworkVersion": "0.12.66",
|
|
4
|
+
"createdAt": "2026-05-26T09:03:41.849Z",
|
|
5
5
|
"exports": {
|
|
6
6
|
"a2a": {
|
|
7
7
|
"type": "object",
|
|
@@ -4781,6 +4781,36 @@
|
|
|
4781
4781
|
}
|
|
4782
4782
|
}
|
|
4783
4783
|
},
|
|
4784
|
+
"base32": {
|
|
4785
|
+
"type": "object",
|
|
4786
|
+
"members": {
|
|
4787
|
+
"ALPHABETS": {
|
|
4788
|
+
"type": "object",
|
|
4789
|
+
"members": {
|
|
4790
|
+
"rfc4648": {
|
|
4791
|
+
"type": "primitive",
|
|
4792
|
+
"valueType": "string"
|
|
4793
|
+
},
|
|
4794
|
+
"rfc4648-hex": {
|
|
4795
|
+
"type": "primitive",
|
|
4796
|
+
"valueType": "string"
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
4799
|
+
},
|
|
4800
|
+
"Base32Error": {
|
|
4801
|
+
"type": "function",
|
|
4802
|
+
"arity": 4
|
|
4803
|
+
},
|
|
4804
|
+
"decode": {
|
|
4805
|
+
"type": "function",
|
|
4806
|
+
"arity": 2
|
|
4807
|
+
},
|
|
4808
|
+
"encode": {
|
|
4809
|
+
"type": "function",
|
|
4810
|
+
"arity": 2
|
|
4811
|
+
}
|
|
4812
|
+
}
|
|
4813
|
+
},
|
|
4784
4814
|
"bootGates": {
|
|
4785
4815
|
"type": "object",
|
|
4786
4816
|
"members": {
|
|
@@ -37108,6 +37138,31 @@
|
|
|
37108
37138
|
}
|
|
37109
37139
|
}
|
|
37110
37140
|
},
|
|
37141
|
+
"jsonSchema": {
|
|
37142
|
+
"type": "object",
|
|
37143
|
+
"members": {
|
|
37144
|
+
"DIALECT": {
|
|
37145
|
+
"type": "primitive",
|
|
37146
|
+
"valueType": "string"
|
|
37147
|
+
},
|
|
37148
|
+
"JsonSchemaError": {
|
|
37149
|
+
"type": "function",
|
|
37150
|
+
"arity": 4
|
|
37151
|
+
},
|
|
37152
|
+
"compile": {
|
|
37153
|
+
"type": "function",
|
|
37154
|
+
"arity": 2
|
|
37155
|
+
},
|
|
37156
|
+
"isValid": {
|
|
37157
|
+
"type": "function",
|
|
37158
|
+
"arity": 3
|
|
37159
|
+
},
|
|
37160
|
+
"validate": {
|
|
37161
|
+
"type": "function",
|
|
37162
|
+
"arity": 3
|
|
37163
|
+
}
|
|
37164
|
+
}
|
|
37165
|
+
},
|
|
37111
37166
|
"jtd": {
|
|
37112
37167
|
"type": "object",
|
|
37113
37168
|
"members": {
|
|
@@ -49338,6 +49393,23 @@
|
|
|
49338
49393
|
}
|
|
49339
49394
|
}
|
|
49340
49395
|
},
|
|
49396
|
+
"uriTemplate": {
|
|
49397
|
+
"type": "object",
|
|
49398
|
+
"members": {
|
|
49399
|
+
"UriTemplateError": {
|
|
49400
|
+
"type": "function",
|
|
49401
|
+
"arity": 4
|
|
49402
|
+
},
|
|
49403
|
+
"compile": {
|
|
49404
|
+
"type": "function",
|
|
49405
|
+
"arity": 1
|
|
49406
|
+
},
|
|
49407
|
+
"expand": {
|
|
49408
|
+
"type": "function",
|
|
49409
|
+
"arity": 2
|
|
49410
|
+
}
|
|
49411
|
+
}
|
|
49412
|
+
},
|
|
49341
49413
|
"uuid": {
|
|
49342
49414
|
"type": "object",
|
|
49343
49415
|
"members": {
|
|
@@ -403,6 +403,9 @@ var jsonPatch = require("./lib/json-patch");
|
|
|
403
403
|
var jsonMergePatch = require("./lib/json-merge-patch");
|
|
404
404
|
var jsonPath = require("./lib/json-path");
|
|
405
405
|
var jtd = require("./lib/jtd");
|
|
406
|
+
var jsonSchema = require("./lib/json-schema");
|
|
407
|
+
var base32 = require("./lib/base32");
|
|
408
|
+
var uriTemplate = require("./lib/uri-template");
|
|
406
409
|
var standardWebhooks = require("./lib/standard-webhooks");
|
|
407
410
|
var lro = require("./lib/lro");
|
|
408
411
|
var jsonApi = require("./lib/jsonapi");
|
|
@@ -427,6 +430,9 @@ module.exports = {
|
|
|
427
430
|
jsonMergePatch: jsonMergePatch,
|
|
428
431
|
jsonPath: jsonPath,
|
|
429
432
|
jtd: jtd,
|
|
433
|
+
jsonSchema: jsonSchema,
|
|
434
|
+
base32: base32,
|
|
435
|
+
uriTemplate: uriTemplate,
|
|
430
436
|
standardWebhooks: standardWebhooks,
|
|
431
437
|
lro: lro,
|
|
432
438
|
jsonApi: jsonApi,
|