@kywi-software/core 0.2.0 → 0.3.0
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/README.md +57 -2
- package/dist/admin/app.d.ts +10 -1
- package/dist/admin/app.d.ts.map +1 -1
- package/dist/admin/app.js +2 -2
- package/dist/admin/app.js.map +1 -1
- package/dist/admin/approval-queue.d.ts +3 -1
- package/dist/admin/approval-queue.d.ts.map +1 -1
- package/dist/admin/approval-queue.js +2 -2
- package/dist/admin/approval-queue.js.map +1 -1
- package/dist/admin/audience-list.d.ts.map +1 -1
- package/dist/admin/audience-list.js +9 -1
- package/dist/admin/audience-list.js.map +1 -1
- package/dist/admin/chrome.d.ts +7 -1
- package/dist/admin/chrome.d.ts.map +1 -1
- package/dist/admin/chrome.js +2 -1
- package/dist/admin/chrome.js.map +1 -1
- package/dist/admin/content-editor-api.d.ts +27 -1
- package/dist/admin/content-editor-api.d.ts.map +1 -1
- package/dist/admin/content-editor-api.js +60 -2
- package/dist/admin/content-editor-api.js.map +1 -1
- package/dist/admin/content-form.d.ts +62 -6
- package/dist/admin/content-form.d.ts.map +1 -1
- package/dist/admin/content-form.js +118 -13
- package/dist/admin/content-form.js.map +1 -1
- package/dist/admin/content-permissions-tab.d.ts +13 -0
- package/dist/admin/content-permissions-tab.d.ts.map +1 -0
- package/dist/admin/content-permissions-tab.js +90 -0
- package/dist/admin/content-permissions-tab.js.map +1 -0
- package/dist/admin/context.d.ts +8 -0
- package/dist/admin/context.d.ts.map +1 -1
- package/dist/admin/context.js.map +1 -1
- package/dist/admin/feed-builder.d.ts.map +1 -1
- package/dist/admin/feed-builder.js +20 -1
- package/dist/admin/feed-builder.js.map +1 -1
- package/dist/admin/field-components.d.ts.map +1 -1
- package/dist/admin/field-components.js +26 -1
- package/dist/admin/field-components.js.map +1 -1
- package/dist/admin/form-builder.js +6 -1
- package/dist/admin/form-builder.js.map +1 -1
- package/dist/admin/form-helpers.d.ts.map +1 -1
- package/dist/admin/form-helpers.js +2 -0
- package/dist/admin/form-helpers.js.map +1 -1
- package/dist/admin/kywi-logo.d.ts +21 -0
- package/dist/admin/kywi-logo.d.ts.map +1 -0
- package/dist/admin/kywi-logo.js +123 -0
- package/dist/admin/kywi-logo.js.map +1 -0
- package/dist/admin/layout-editor/component-actions.d.ts +18 -0
- package/dist/admin/layout-editor/component-actions.d.ts.map +1 -1
- package/dist/admin/layout-editor/component-actions.js +19 -0
- package/dist/admin/layout-editor/component-actions.js.map +1 -1
- package/dist/admin/layout-editor/component-picker-field.d.ts +20 -0
- package/dist/admin/layout-editor/component-picker-field.d.ts.map +1 -0
- package/dist/admin/layout-editor/component-picker-field.js +16 -0
- package/dist/admin/layout-editor/component-picker-field.js.map +1 -0
- package/dist/admin/layout-editor/drag-utils.d.ts +48 -0
- package/dist/admin/layout-editor/drag-utils.d.ts.map +1 -1
- package/dist/admin/layout-editor/drag-utils.js +43 -0
- package/dist/admin/layout-editor/drag-utils.js.map +1 -1
- package/dist/admin/layout-editor/editor-canvas.d.ts +9 -1
- package/dist/admin/layout-editor/editor-canvas.d.ts.map +1 -1
- package/dist/admin/layout-editor/editor-canvas.js +41 -15
- package/dist/admin/layout-editor/editor-canvas.js.map +1 -1
- package/dist/admin/layout-editor/editor-state.d.ts +9 -1
- package/dist/admin/layout-editor/editor-state.d.ts.map +1 -1
- package/dist/admin/layout-editor/editor-state.js +34 -0
- package/dist/admin/layout-editor/editor-state.js.map +1 -1
- package/dist/admin/layout-editor/editor.css +83 -0
- package/dist/admin/layout-editor/form-picker-field.d.ts +17 -0
- package/dist/admin/layout-editor/form-picker-field.d.ts.map +1 -0
- package/dist/admin/layout-editor/form-picker-field.js +35 -0
- package/dist/admin/layout-editor/form-picker-field.js.map +1 -0
- package/dist/admin/layout-editor/layout-editor.d.ts.map +1 -1
- package/dist/admin/layout-editor/layout-editor.js +95 -28
- package/dist/admin/layout-editor/layout-editor.js.map +1 -1
- package/dist/admin/layout-editor/mode-toggle.d.ts.map +1 -1
- package/dist/admin/layout-editor/mode-toggle.js +1 -1
- package/dist/admin/layout-editor/mode-toggle.js.map +1 -1
- package/dist/admin/layout-editor/module-overlay.d.ts +10 -1
- package/dist/admin/layout-editor/module-overlay.d.ts.map +1 -1
- package/dist/admin/layout-editor/module-overlay.js +3 -3
- package/dist/admin/layout-editor/module-overlay.js.map +1 -1
- package/dist/admin/layout-editor/props-panel.d.ts +10 -1
- package/dist/admin/layout-editor/props-panel.d.ts.map +1 -1
- package/dist/admin/layout-editor/props-panel.js +25 -2
- package/dist/admin/layout-editor/props-panel.js.map +1 -1
- package/dist/admin/layout-editor/section-insert-line.d.ts.map +1 -1
- package/dist/admin/layout-editor/section-insert-line.js +1 -1
- package/dist/admin/layout-editor/section-insert-line.js.map +1 -1
- package/dist/admin/layout-editor/section-overlay.d.ts.map +1 -1
- package/dist/admin/layout-editor/section-overlay.js +1 -1
- package/dist/admin/layout-editor/section-overlay.js.map +1 -1
- package/dist/admin/layout-editor/section-template-picker.d.ts +9 -1
- package/dist/admin/layout-editor/section-template-picker.d.ts.map +1 -1
- package/dist/admin/layout-editor/section-template-picker.js +45 -2
- package/dist/admin/layout-editor/section-template-picker.js.map +1 -1
- package/dist/admin/registry.d.ts.map +1 -1
- package/dist/admin/registry.js +11 -0
- package/dist/admin/registry.js.map +1 -1
- package/dist/admin/server.d.ts +15 -2
- package/dist/admin/server.d.ts.map +1 -1
- package/dist/admin/server.js +37 -1
- package/dist/admin/server.js.map +1 -1
- package/dist/admin/shell.d.ts.map +1 -1
- package/dist/admin/shell.js +62 -3
- package/dist/admin/shell.js.map +1 -1
- package/dist/admin/styles/admin.css +105 -27
- package/dist/admin/surface-matcher.d.ts +27 -0
- package/dist/admin/surface-matcher.d.ts.map +1 -1
- package/dist/admin/surface-matcher.js +11 -0
- package/dist/admin/surface-matcher.js.map +1 -1
- package/dist/admin/surfaces/approvals/approvals-data.d.ts +29 -9
- package/dist/admin/surfaces/approvals/approvals-data.d.ts.map +1 -1
- package/dist/admin/surfaces/approvals/approvals-data.js +65 -22
- package/dist/admin/surfaces/approvals/approvals-data.js.map +1 -1
- package/dist/admin/surfaces/approvals/approvals-index.d.ts.map +1 -1
- package/dist/admin/surfaces/approvals/approvals-index.js +47 -14
- package/dist/admin/surfaces/approvals/approvals-index.js.map +1 -1
- package/dist/admin/surfaces/content/content-edit.d.ts.map +1 -1
- package/dist/admin/surfaces/content/content-edit.js +34 -8
- package/dist/admin/surfaces/content/content-edit.js.map +1 -1
- package/dist/admin/surfaces/content/content-index.d.ts.map +1 -1
- package/dist/admin/surfaces/content/content-index.js +29 -2
- package/dist/admin/surfaces/content/content-index.js.map +1 -1
- package/dist/admin/surfaces/content/content-new.d.ts.map +1 -1
- package/dist/admin/surfaces/content/content-new.js +6 -4
- package/dist/admin/surfaces/content/content-new.js.map +1 -1
- package/dist/admin/surfaces/contentTree/content-tree.d.ts.map +1 -1
- package/dist/admin/surfaces/contentTree/content-tree.js +25 -2
- package/dist/admin/surfaces/contentTree/content-tree.js.map +1 -1
- package/dist/admin/surfaces/contentTypes/content-types-designer.d.ts.map +1 -1
- package/dist/admin/surfaces/contentTypes/content-types-designer.js +78 -47
- package/dist/admin/surfaces/contentTypes/content-types-designer.js.map +1 -1
- package/dist/admin/surfaces/feeds/feed-editor.d.ts.map +1 -1
- package/dist/admin/surfaces/feeds/feed-editor.js +21 -4
- package/dist/admin/surfaces/feeds/feed-editor.js.map +1 -1
- package/dist/admin/surfaces/layouts/layout-editor-client.d.ts.map +1 -1
- package/dist/admin/surfaces/layouts/layout-editor-client.js +7 -4
- package/dist/admin/surfaces/layouts/layout-editor-client.js.map +1 -1
- package/dist/admin/surfaces/login.d.ts.map +1 -1
- package/dist/admin/surfaces/login.js +2 -1
- package/dist/admin/surfaces/login.js.map +1 -1
- package/dist/admin/surfaces/media/media-client.d.ts.map +1 -1
- package/dist/admin/surfaces/media/media-client.js +10 -2
- package/dist/admin/surfaces/media/media-client.js.map +1 -1
- package/dist/admin/surfaces/settings/settings-site.d.ts.map +1 -1
- package/dist/admin/surfaces/settings/settings-site.js +39 -2
- package/dist/admin/surfaces/settings/settings-site.js.map +1 -1
- package/dist/admin/surfaces/settings/settings-sites.js +18 -0
- package/dist/admin/surfaces/settings/settings-sites.js.map +1 -1
- package/dist/admin/surfaces/trash/trash-index.d.ts.map +1 -1
- package/dist/admin/surfaces/trash/trash-index.js +45 -8
- package/dist/admin/surfaces/trash/trash-index.js.map +1 -1
- package/dist/admin/surfaces/users/users-data.d.ts +11 -0
- package/dist/admin/surfaces/users/users-data.d.ts.map +1 -1
- package/dist/admin/surfaces/users/users-data.js +20 -1
- package/dist/admin/surfaces/users/users-data.js.map +1 -1
- package/dist/admin/surfaces/webServices/web-services-data.d.ts +11 -1
- package/dist/admin/surfaces/webServices/web-services-data.d.ts.map +1 -1
- package/dist/admin/surfaces/webServices/web-services-data.js +13 -0
- package/dist/admin/surfaces/webServices/web-services-data.js.map +1 -1
- package/dist/admin/surfaces/webServices/webhooks.d.ts +3 -0
- package/dist/admin/surfaces/webServices/webhooks.d.ts.map +1 -0
- package/dist/admin/surfaces/webServices/webhooks.js +69 -0
- package/dist/admin/surfaces/webServices/webhooks.js.map +1 -0
- package/dist/admin/webhook-manager.d.ts +32 -0
- package/dist/admin/webhook-manager.d.ts.map +1 -0
- package/dist/admin/webhook-manager.js +62 -0
- package/dist/admin/webhook-manager.js.map +1 -0
- package/dist/api/admin-features-routes.d.ts.map +1 -1
- package/dist/api/admin-features-routes.js +30 -0
- package/dist/api/admin-features-routes.js.map +1 -1
- package/dist/api/api-key-routes.d.ts.map +1 -1
- package/dist/api/api-key-routes.js +6 -1
- package/dist/api/api-key-routes.js.map +1 -1
- package/dist/api/auth.d.ts +8 -0
- package/dist/api/auth.d.ts.map +1 -1
- package/dist/api/auth.js +11 -1
- package/dist/api/auth.js.map +1 -1
- package/dist/api/comment-routes.d.ts.map +1 -1
- package/dist/api/comment-routes.js +102 -47
- package/dist/api/comment-routes.js.map +1 -1
- package/dist/api/config-routes.d.ts +10 -2
- package/dist/api/config-routes.d.ts.map +1 -1
- package/dist/api/config-routes.js +46 -25
- package/dist/api/config-routes.js.map +1 -1
- package/dist/api/content-routes.d.ts.map +1 -1
- package/dist/api/content-routes.js +41 -0
- package/dist/api/content-routes.js.map +1 -1
- package/dist/api/content-schemas.d.ts.map +1 -1
- package/dist/api/content-schemas.js +34 -1
- package/dist/api/content-schemas.js.map +1 -1
- package/dist/api/content-type-routes.d.ts.map +1 -1
- package/dist/api/content-type-routes.js +58 -3
- package/dist/api/content-type-routes.js.map +1 -1
- package/dist/api/feed-routes.d.ts +180 -0
- package/dist/api/feed-routes.d.ts.map +1 -1
- package/dist/api/feed-routes.js +10 -2
- package/dist/api/feed-routes.js.map +1 -1
- package/dist/api/form-routes.d.ts +6 -0
- package/dist/api/form-routes.d.ts.map +1 -1
- package/dist/api/form-routes.js +93 -14
- package/dist/api/form-routes.js.map +1 -1
- package/dist/api/graphql/resolvers.d.ts +4 -4
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +12 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/media-routes.d.ts.map +1 -1
- package/dist/api/media-routes.js +18 -1
- package/dist/api/media-routes.js.map +1 -1
- package/dist/api/webhook-routes.d.ts.map +1 -1
- package/dist/api/webhook-routes.js +19 -2
- package/dist/api/webhook-routes.js.map +1 -1
- package/dist/api/workflow-routes.d.ts.map +1 -1
- package/dist/api/workflow-routes.js +163 -4
- package/dist/api/workflow-routes.js.map +1 -1
- package/dist/auth/roles.d.ts +28 -0
- package/dist/auth/roles.d.ts.map +1 -1
- package/dist/auth/roles.js +49 -0
- package/dist/auth/roles.js.map +1 -1
- package/dist/config/reserved-field-names.d.ts +29 -0
- package/dist/config/reserved-field-names.d.ts.map +1 -0
- package/dist/config/reserved-field-names.js +59 -0
- package/dist/config/reserved-field-names.js.map +1 -0
- package/dist/config/types.d.ts +31 -2
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js.map +1 -1
- package/dist/db/analytics-queries.d.ts +1 -1
- package/dist/db/api-key-queries.d.ts +7 -2
- package/dist/db/api-key-queries.d.ts.map +1 -1
- package/dist/db/api-key-queries.js +8 -2
- package/dist/db/api-key-queries.js.map +1 -1
- package/dist/db/apply-snapshot.d.ts +58 -0
- package/dist/db/apply-snapshot.d.ts.map +1 -0
- package/dist/db/apply-snapshot.js +142 -0
- package/dist/db/apply-snapshot.js.map +1 -0
- package/dist/db/audit-queries.d.ts +3 -3
- package/dist/db/category-queries.d.ts +3 -3
- package/dist/db/changeset-audit-queries.d.ts +2 -2
- package/dist/db/changeset-queries.d.ts +5 -5
- package/dist/db/changeset-queries.d.ts.map +1 -1
- package/dist/db/changeset-queries.js +24 -7
- package/dist/db/changeset-queries.js.map +1 -1
- package/dist/db/content-queries.d.ts +6 -0
- package/dist/db/content-queries.d.ts.map +1 -1
- package/dist/db/content-queries.js +8 -0
- package/dist/db/content-queries.js.map +1 -1
- package/dist/db/content-type-queries.d.ts +20 -0
- package/dist/db/content-type-queries.d.ts.map +1 -1
- package/dist/db/content-type-queries.js +32 -0
- package/dist/db/content-type-queries.js.map +1 -1
- package/dist/db/dashboard-queries.d.ts.map +1 -1
- package/dist/db/dashboard-queries.js +5 -1
- package/dist/db/dashboard-queries.js.map +1 -1
- package/dist/db/dynamic-schema.d.ts.map +1 -1
- package/dist/db/dynamic-schema.js +2 -0
- package/dist/db/dynamic-schema.js.map +1 -1
- package/dist/db/extension-queries.d.ts +8 -8
- package/dist/db/feed-queries.d.ts +3 -3
- package/dist/db/index.d.ts +3 -0
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +7 -0
- package/dist/db/index.js.map +1 -1
- package/dist/db/media-queries.d.ts +31 -7
- package/dist/db/media-queries.d.ts.map +1 -1
- package/dist/db/media-queries.js +13 -1
- package/dist/db/media-queries.js.map +1 -1
- package/dist/db/membership-dedupe.d.ts +26 -0
- package/dist/db/membership-dedupe.d.ts.map +1 -0
- package/dist/db/membership-dedupe.js +68 -0
- package/dist/db/membership-dedupe.js.map +1 -0
- package/dist/db/revision-queries.d.ts +122 -0
- package/dist/db/revision-queries.d.ts.map +1 -0
- package/dist/db/revision-queries.js +213 -0
- package/dist/db/revision-queries.js.map +1 -0
- package/dist/db/schema-apply.d.ts +8 -0
- package/dist/db/schema-apply.d.ts.map +1 -1
- package/dist/db/schema-apply.js +17 -0
- package/dist/db/schema-apply.js.map +1 -1
- package/dist/db/schema-field-queries.d.ts +2 -0
- package/dist/db/schema-field-queries.d.ts.map +1 -1
- package/dist/db/schema-field-queries.js +4 -0
- package/dist/db/schema-field-queries.js.map +1 -1
- package/dist/db/site-plugin-queries.d.ts +2 -2
- package/dist/db/user-queries.d.ts +12 -0
- package/dist/db/user-queries.d.ts.map +1 -1
- package/dist/db/user-queries.js +18 -4
- package/dist/db/user-queries.js.map +1 -1
- package/dist/db/version-queries.d.ts +37 -0
- package/dist/db/version-queries.d.ts.map +1 -1
- package/dist/db/version-queries.js +36 -0
- package/dist/db/version-queries.js.map +1 -1
- package/dist/db/webhook-queries.d.ts +27 -5
- package/dist/db/webhook-queries.d.ts.map +1 -1
- package/dist/db/webhook-queries.js +17 -0
- package/dist/db/webhook-queries.js.map +1 -1
- package/dist/db/workflow-queries.d.ts +2 -2
- package/dist/experiments/schema.d.ts +1 -1
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/webhook-retry.d.ts +22 -1
- package/dist/hooks/webhook-retry.d.ts.map +1 -1
- package/dist/hooks/webhook-retry.js +38 -1
- package/dist/hooks/webhook-retry.js.map +1 -1
- package/dist/host/index.d.ts +28 -0
- package/dist/host/index.d.ts.map +1 -1
- package/dist/host/index.js +56 -1
- package/dist/host/index.js.map +1 -1
- package/dist/layout/comments-module.d.ts +18 -0
- package/dist/layout/comments-module.d.ts.map +1 -0
- package/dist/layout/comments-module.js +130 -0
- package/dist/layout/comments-module.js.map +1 -0
- package/dist/layout/feed-hydration.d.ts +75 -0
- package/dist/layout/feed-hydration.d.ts.map +1 -0
- package/dist/layout/feed-hydration.js +158 -0
- package/dist/layout/feed-hydration.js.map +1 -0
- package/dist/layout/index.d.ts +2 -1
- package/dist/layout/index.d.ts.map +1 -1
- package/dist/layout/index.js +2 -1
- package/dist/layout/index.js.map +1 -1
- package/dist/layout/module-render-context.d.ts +13 -0
- package/dist/layout/module-render-context.d.ts.map +1 -0
- package/dist/layout/module-render-context.js +15 -0
- package/dist/layout/module-render-context.js.map +1 -0
- package/dist/layout/modules-extended.d.ts.map +1 -1
- package/dist/layout/modules-extended.js +92 -13
- package/dist/layout/modules-extended.js.map +1 -1
- package/dist/layout/registry.d.ts.map +1 -1
- package/dist/layout/registry.js +22 -6
- package/dist/layout/registry.js.map +1 -1
- package/dist/layout/renderer.d.ts +17 -1
- package/dist/layout/renderer.d.ts.map +1 -1
- package/dist/layout/renderer.js +11 -4
- package/dist/layout/renderer.js.map +1 -1
- package/dist/nav/index.d.ts +1 -0
- package/dist/nav/index.d.ts.map +1 -1
- package/dist/nav/index.js +4 -0
- package/dist/nav/index.js.map +1 -1
- package/dist/nav/url-resolver.d.ts +10 -2
- package/dist/nav/url-resolver.d.ts.map +1 -1
- package/dist/nav/url-resolver.js +17 -3
- package/dist/nav/url-resolver.js.map +1 -1
- package/dist/schema/base.d.ts +37 -1
- package/dist/schema/base.d.ts.map +1 -1
- package/dist/schema/base.js +20 -2
- package/dist/schema/base.js.map +1 -1
- package/dist/schema/generate.d.ts +8 -0
- package/dist/schema/generate.d.ts.map +1 -1
- package/dist/schema/generate.js +7 -4
- package/dist/schema/generate.js.map +1 -1
- package/dist/schema/mapper.d.ts.map +1 -1
- package/dist/schema/mapper.js +2 -0
- package/dist/schema/mapper.js.map +1 -1
- package/dist/schema/mysql-tables.d.ts +34 -0
- package/dist/schema/mysql-tables.d.ts.map +1 -1
- package/dist/schema/mysql-tables.js +14 -2
- package/dist/schema/mysql-tables.js.map +1 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -0
- package/dist/server.js.map +1 -1
- package/dist/site/styles.css +384 -0
- package/package.json +2 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHmac } from 'node:crypto';
|
|
2
|
-
import { updateDeliveryStatus } from '../db/webhook-queries.js';
|
|
2
|
+
import { updateDeliveryStatus, listPendingRetries, getEndpointById } from '../db/webhook-queries.js';
|
|
3
3
|
// Exponential backoff: 1min, 5min, 15min, 60min, ...
|
|
4
4
|
function computeNextRetryDelay(attempts) {
|
|
5
5
|
const base = 60_000; // 1 minute
|
|
@@ -22,6 +22,43 @@ export function createWebhookRetryWorker(db, deps) {
|
|
|
22
22
|
},
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Start an in-process interval runner that re-dispatches due webhook retries
|
|
27
|
+
* every `intervalSeconds`, wiring the real DB deps (`listPendingRetries` /
|
|
28
|
+
* `getEndpointById`) into {@link createWebhookRetryWorker}. Mirrors
|
|
29
|
+
* `startScheduler`: overlapping ticks are skipped (a slow sweep never piles up),
|
|
30
|
+
* the timer is `unref()`d so it never keeps the process alive on its own, and
|
|
31
|
+
* one failed sweep routes to `onError` instead of crashing the host. Returns a
|
|
32
|
+
* handle whose `stop()` clears the interval.
|
|
33
|
+
*
|
|
34
|
+
* This is the missing runtime call site for the retry model (#57): before this,
|
|
35
|
+
* `createWebhookRetryWorker` was dead code and failed deliveries never retried.
|
|
36
|
+
*/
|
|
37
|
+
export function startWebhookRetryWorker(db, opts) {
|
|
38
|
+
const worker = createWebhookRetryWorker(db, {
|
|
39
|
+
listPendingRetries: (d) => listPendingRetries(d, opts.maxAttempts !== undefined ? { maxAttempts: opts.maxAttempts } : {}),
|
|
40
|
+
getEndpointById: (d, id) => getEndpointById(d, id),
|
|
41
|
+
});
|
|
42
|
+
const ms = Math.max(1, opts.intervalSeconds) * 1000;
|
|
43
|
+
const onError = opts.onError ?? ((err) => console.warn('[kywi:webhooks] retry sweep failed:', err));
|
|
44
|
+
let running = false;
|
|
45
|
+
const timer = setInterval(() => {
|
|
46
|
+
if (running)
|
|
47
|
+
return;
|
|
48
|
+
running = true;
|
|
49
|
+
void worker.tick()
|
|
50
|
+
.catch(onError)
|
|
51
|
+
.finally(() => {
|
|
52
|
+
running = false;
|
|
53
|
+
});
|
|
54
|
+
}, ms);
|
|
55
|
+
// Do not hold the event loop open for the retry worker alone.
|
|
56
|
+
if (typeof timer.unref === 'function') {
|
|
57
|
+
;
|
|
58
|
+
timer.unref();
|
|
59
|
+
}
|
|
60
|
+
return { stop: () => clearInterval(timer) };
|
|
61
|
+
}
|
|
25
62
|
async function retryDeliver(url, secret, event, payload, deliveryId, previousAttempts, db) {
|
|
26
63
|
const body = JSON.stringify({ event, data: payload, timestamp: Date.now() });
|
|
27
64
|
const signature = createHmac('sha256', secret).update(body).digest('hex');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-retry.js","sourceRoot":"","sources":["../../src/hooks/webhook-retry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"webhook-retry.js","sourceRoot":"","sources":["../../src/hooks/webhook-retry.ts"],"names":[],"mappings":"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"}
|
package/dist/host/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { assertProductionAuthSecret, isDevFallbackSecret, DEV_FALLBACK_SECRETS, } from '../auth/auth-secret.js';
|
|
2
|
+
export { canAccessContent, hasMinRole, type ContentAction, type KywiRole, } from '../auth/roles.js';
|
|
2
3
|
/** httpOnly cookie holding the short-lived access JWT (type=access). */
|
|
3
4
|
export declare const ACCESS_COOKIE = "kywi_access_token";
|
|
4
5
|
/** httpOnly cookie holding the long-lived refresh JWT (type=refresh). */
|
|
@@ -31,6 +32,33 @@ export type AccessTokenStatus =
|
|
|
31
32
|
* because it expired is still refreshable ('stale'); anything else is 'invalid'.
|
|
32
33
|
*/
|
|
33
34
|
export declare function classifyAccessToken(token: string | undefined, secret: string): Promise<AccessTokenStatus>;
|
|
35
|
+
/** The non-sensitive claims carried by a signature-verified access token. */
|
|
36
|
+
export interface SessionClaims {
|
|
37
|
+
/** The authenticated user's id (token `sub`). */
|
|
38
|
+
sub: string;
|
|
39
|
+
/** The user's role — feeds the content-permission gates (see canAccessContent). */
|
|
40
|
+
role: string;
|
|
41
|
+
/** The user's group ids (empty when the token carries none). */
|
|
42
|
+
groups: string[];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Verify an access-token cookie against the host's auth secret and return its
|
|
46
|
+
* non-sensitive claims (sub / role / groups), or null when the token is missing,
|
|
47
|
+
* malformed, the wrong type, or its signature does not match `secret`.
|
|
48
|
+
*
|
|
49
|
+
* A token whose signature verified but has merely EXPIRED still yields its
|
|
50
|
+
* claims: a public (site) route is NOT in the middleware's refresh matcher, so
|
|
51
|
+
* an actively-signed-in admin's short-lived access cookie may have lapsed by the
|
|
52
|
+
* time they open a page with `?kywi-edit=1`. These claims only gate a client-side
|
|
53
|
+
* edit AFFORDANCE (the front-of-site overlay); every real mutation still flows
|
|
54
|
+
* through /api/v1, which the middleware refreshes and which re-checks auth. So
|
|
55
|
+
* surfacing the overlay for a signature-valid-but-stale admin is safe and correct
|
|
56
|
+
* UX. Anything whose signature does not verify returns null.
|
|
57
|
+
*
|
|
58
|
+
* Intended for a host's public render to decide whether to mount the overlay for
|
|
59
|
+
* the current visitor — pair it with the re-exported `canAccessContent`.
|
|
60
|
+
*/
|
|
61
|
+
export declare function readSessionClaims(token: string | undefined, secret: string): Promise<SessionClaims | null>;
|
|
34
62
|
/**
|
|
35
63
|
* Pull the access/refresh tokens out of a token-issuing endpoint's JSON body
|
|
36
64
|
* (`{ data: { accessToken, refreshToken } }`). Missing/non-string fields and
|
package/dist/host/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/host/index.ts"],"names":[],"mappings":"AA8BA,OAAO,EACL,0BAA0B,EAC1B,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/host/index.ts"],"names":[],"mappings":"AA8BA,OAAO,EACL,0BAA0B,EAC1B,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,wBAAwB,CAAA;AAM/B,OAAO,EACL,gBAAgB,EAChB,UAAU,EACV,KAAK,aAAa,EAClB,KAAK,QAAQ,GACd,MAAM,kBAAkB,CAAA;AAEzB,wEAAwE;AACxE,eAAO,MAAM,aAAa,sBAAsB,CAAA;AAChD,yEAAyE;AACzE,eAAO,MAAM,cAAc,uBAAuB,CAAA;AAGlD,eAAO,MAAM,kBAAkB,QAAU,CAAA;AACzC,eAAO,MAAM,mBAAmB,QAAmB,CAAA;AAEnD;;;GAGG;AACH,eAAO,MAAM,mBAAmB,MAAM,CAAA;AAEtC;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAMlD,CAAA;AAEF,MAAM,MAAM,iBAAiB;AAC3B,wEAAwE;AACtE,OAAO;AACT,2EAA2E;GACzE,OAAO;AACT,yEAAyE;GACvE,SAAS,CAAA;AAcb;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,iBAAiB,CAAC,CAa5B;AAED,6EAA6E;AAC7E,MAAM,WAAW,aAAa;IAC5B,iDAAiD;IACjD,GAAG,EAAE,MAAM,CAAA;IACX,mFAAmF;IACnF,IAAI,EAAE,MAAM,CAAA;IACZ,gEAAgE;IAChE,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB;AAaD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAiB/B;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG;IACpD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAUA;AAED;oCACoC;AACpC,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,OAAO,CAAA;IACjB,QAAQ,EAAE,KAAK,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,OAAO,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;CACf;AAED,6EAA6E;AAC7E,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAA;KAAE,CAAA;CACvF;AAkBD,+DAA+D;AAC/D,wBAAgB,mBAAmB,CAAC,MAAM,SAAqB,GAAG,oBAAoB,CAErF;AAED,gEAAgE;AAChE,wBAAgB,oBAAoB,CAAC,MAAM,SAAsB,GAAG,oBAAoB,CAEvF;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAExE;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAEzE;AAED,6EAA6E;AAC7E,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,cAAc,GAAG,IAAI,CAG7D"}
|
package/dist/host/index.js
CHANGED
|
@@ -22,12 +22,17 @@
|
|
|
22
22
|
* them. The security-critical crypto (JWT verify, token classification, cookie
|
|
23
23
|
* options) lives here once; the two host files carry only framework wiring.
|
|
24
24
|
*/
|
|
25
|
-
import { jwtVerify } from 'jose';
|
|
25
|
+
import { jwtVerify, decodeJwt } from 'jose';
|
|
26
26
|
// Re-export the AUTH_SECRET production guard so a generated app can call it at
|
|
27
27
|
// module scope (in middleware.ts / kywi.config.ts) and refuse to build/start in
|
|
28
28
|
// production with a missing or dev-fallback secret. Shares its policy with the
|
|
29
29
|
// CLI/app env-guard, so both agree on what an insecure secret is.
|
|
30
30
|
export { assertProductionAuthSecret, isDevFallbackSecret, DEV_FALLBACK_SECRETS, } from '../auth/auth-secret.js';
|
|
31
|
+
// Re-export the pure role → content-permission helpers so a host's public (site)
|
|
32
|
+
// render can decide, from a verified token's role, whether to surface the
|
|
33
|
+
// front-of-site edit overlay (see readSessionClaims). Both are dependency-free,
|
|
34
|
+
// so this adds nothing to the edge bundle beyond the constants they close over.
|
|
35
|
+
export { canAccessContent, hasMinRole, } from '../auth/roles.js';
|
|
31
36
|
/** httpOnly cookie holding the short-lived access JWT (type=access). */
|
|
32
37
|
export const ACCESS_COOKIE = 'kywi_access_token';
|
|
33
38
|
/** httpOnly cookie holding the long-lived refresh JWT (type=refresh). */
|
|
@@ -88,6 +93,56 @@ export async function classifyAccessToken(token, secret) {
|
|
|
88
93
|
return 'invalid';
|
|
89
94
|
}
|
|
90
95
|
}
|
|
96
|
+
function claimsFromPayload(payload) {
|
|
97
|
+
if (payload['type'] !== 'access')
|
|
98
|
+
return null;
|
|
99
|
+
const sub = typeof payload['sub'] === 'string' ? payload['sub'] : null;
|
|
100
|
+
const role = typeof payload['role'] === 'string' ? payload['role'] : null;
|
|
101
|
+
if (!sub || !role)
|
|
102
|
+
return null;
|
|
103
|
+
const groups = Array.isArray(payload['groups'])
|
|
104
|
+
? payload['groups'].filter((g) => typeof g === 'string')
|
|
105
|
+
: [];
|
|
106
|
+
return { sub, role, groups };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Verify an access-token cookie against the host's auth secret and return its
|
|
110
|
+
* non-sensitive claims (sub / role / groups), or null when the token is missing,
|
|
111
|
+
* malformed, the wrong type, or its signature does not match `secret`.
|
|
112
|
+
*
|
|
113
|
+
* A token whose signature verified but has merely EXPIRED still yields its
|
|
114
|
+
* claims: a public (site) route is NOT in the middleware's refresh matcher, so
|
|
115
|
+
* an actively-signed-in admin's short-lived access cookie may have lapsed by the
|
|
116
|
+
* time they open a page with `?kywi-edit=1`. These claims only gate a client-side
|
|
117
|
+
* edit AFFORDANCE (the front-of-site overlay); every real mutation still flows
|
|
118
|
+
* through /api/v1, which the middleware refreshes and which re-checks auth. So
|
|
119
|
+
* surfacing the overlay for a signature-valid-but-stale admin is safe and correct
|
|
120
|
+
* UX. Anything whose signature does not verify returns null.
|
|
121
|
+
*
|
|
122
|
+
* Intended for a host's public render to decide whether to mount the overlay for
|
|
123
|
+
* the current visitor — pair it with the re-exported `canAccessContent`.
|
|
124
|
+
*/
|
|
125
|
+
export async function readSessionClaims(token, secret) {
|
|
126
|
+
if (!token)
|
|
127
|
+
return null;
|
|
128
|
+
try {
|
|
129
|
+
const { payload } = await jwtVerify(token, keyFor(secret), { algorithms: ['HS256'] });
|
|
130
|
+
return claimsFromPayload(payload);
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
// jose verifies the signature BEFORE the exp claim, so ERR_JWT_EXPIRED means
|
|
134
|
+
// the signature was valid — decode the (unverified) claims to read the role.
|
|
135
|
+
if (err.code === 'ERR_JWT_EXPIRED') {
|
|
136
|
+
try {
|
|
137
|
+
return claimsFromPayload(decodeJwt(token));
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
91
146
|
/**
|
|
92
147
|
* Pull the access/refresh tokens out of a token-issuing endpoint's JSON body
|
|
93
148
|
* (`{ data: { accessToken, refreshToken } }`). Missing/non-string fields and
|
package/dist/host/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/host/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/host/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,MAAM,CAAA;AAE3C,+EAA+E;AAC/E,gFAAgF;AAChF,+EAA+E;AAC/E,kEAAkE;AAClE,OAAO,EACL,0BAA0B,EAC1B,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,wBAAwB,CAAA;AAE/B,iFAAiF;AACjF,0EAA0E;AAC1E,gFAAgF;AAChF,gFAAgF;AAChF,OAAO,EACL,gBAAgB,EAChB,UAAU,GAGX,MAAM,kBAAkB,CAAA;AAEzB,wEAAwE;AACxE,MAAM,CAAC,MAAM,aAAa,GAAG,mBAAmB,CAAA;AAChD,yEAAyE;AACzE,MAAM,CAAC,MAAM,cAAc,GAAG,oBAAoB,CAAA;AAElD,wEAAwE;AACxE,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,GAAG,EAAE,CAAA;AACzC,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAA;AAEnD;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,CAAA;AAEtC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAwB,IAAI,GAAG,CAAC;IAC9D,YAAY;IACZ,eAAe;IACf,cAAc;IACd,iBAAiB;IACjB,wBAAwB;CACzB,CAAC,CAAA;AAUF,iFAAiF;AACjF,oBAAoB;AACpB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAsB,CAAA;AAC9C,SAAS,MAAM,CAAC,MAAc;IAC5B,IAAI,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAC9B,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,GAAG,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACtC,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC3B,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,KAAyB,EACzB,MAAc;IAEd,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAA;IAC5B,IAAI,CAAC;QACH,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACrF,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAA;QAClD,MAAM,GAAG,GAAG,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;QAC7D,MAAM,WAAW,GAAG,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAA;QACvD,OAAO,WAAW,IAAI,mBAAmB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAA;IAC/D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,4EAA4E;QAC5E,IAAK,GAAyB,CAAC,IAAI,KAAK,iBAAiB;YAAE,OAAO,OAAO,CAAA;QACzE,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAYD,SAAS,iBAAiB,CAAC,OAAgC;IACzD,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAC7C,MAAM,GAAG,GAAG,OAAO,OAAO,CAAC,KAAK,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IACtE,MAAM,IAAI,GAAG,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IACzE,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IAC9B,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC7C,CAAC,CAAE,OAAO,CAAC,QAAQ,CAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;QACpF,CAAC,CAAC,EAAE,CAAA;IACN,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;AAC9B,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,KAAyB,EACzB,MAAc;IAEd,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAA;IACvB,IAAI,CAAC;QACH,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACrF,OAAO,iBAAiB,CAAC,OAAkC,CAAC,CAAA;IAC9D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,6EAA6E;QAC7E,6EAA6E;QAC7E,IAAK,GAAyB,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;YAC1D,IAAI,CAAC;gBACH,OAAO,iBAAiB,CAAC,SAAS,CAAC,KAAK,CAA4B,CAAC,CAAA;YACvE,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAA;YACb,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAgB;IAIjD,IAAI,CAAC;QACH,MAAM,IAAI,GAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAA+C,EAAE,IAAI,CAAA;QACtF,MAAM,GAAG,GAAoD,EAAE,CAAA;QAC/D,IAAI,OAAO,IAAI,EAAE,WAAW,KAAK,QAAQ,IAAI,IAAI,CAAC,WAAW;YAAE,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAA;QACjG,IAAI,OAAO,IAAI,EAAE,YAAY,KAAK,QAAQ,IAAI,IAAI,CAAC,YAAY;YAAE,GAAG,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAA;QACrG,OAAO,GAAG,CAAA;IACZ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAA;IACX,CAAC;AACH,CAAC;AAiBD,SAAS,iBAAiB;IACxB,OAAO;QACL,QAAQ,EAAE,IAAI;QACd,QAAQ,EAAE,KAAc;QACxB,2EAA2E;QAC3E,6EAA6E;QAC7E,kEAAkE;QAClE,sEAAsE;QACtE,yCAAyC;QACzC,IAAI,EAAE,GAAG;QACT,wEAAwE;QACxE,2DAA2D;QAC3D,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,YAAY;KACjD,CAAA;AACH,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,mBAAmB,CAAC,MAAM,GAAG,kBAAkB;IAC7D,OAAO,EAAE,GAAG,iBAAiB,EAAE,EAAE,MAAM,EAAE,CAAA;AAC3C,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,oBAAoB,CAAC,MAAM,GAAG,mBAAmB;IAC/D,OAAO,EAAE,GAAG,iBAAiB,EAAE,EAAE,MAAM,EAAE,CAAA;AAC3C,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,GAAmB,EAAE,KAAa;IAChE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,CAAA;AAC9D,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,GAAmB,EAAE,KAAa;IACjE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAA;AAChE,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,mBAAmB,CAAC,GAAmB;IACrD,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,EAAE,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAA;IAC1D,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,EAAE,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAA;AAC9D,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comments layout module (#29). Renders the approved-comment list for the
|
|
3
|
+
* current content plus a public submission form. New comments POST to the
|
|
4
|
+
* collection endpoint `POST /api/v1/comments` and always land in the moderation
|
|
5
|
+
* queue as `pending`; they only appear here once an editor approves them (the
|
|
6
|
+
* list reads `GET /api/v1/comments?contentId=…&status=approved`, which the API
|
|
7
|
+
* only ever returns approved rows for).
|
|
8
|
+
*
|
|
9
|
+
* Inside the layout-editor canvas it renders a non-interactive preview (no live
|
|
10
|
+
* `<form>`), matching the Form module's editor behaviour (kywi-cms#36).
|
|
11
|
+
*/
|
|
12
|
+
interface ModuleProps {
|
|
13
|
+
props: Record<string, unknown>;
|
|
14
|
+
style?: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
export declare function CommentsModule({ props }: ModuleProps): import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=comments-module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comments-module.d.ts","sourceRoot":"","sources":["../../src/layout/comments-module.tsx"],"names":[],"mappings":"AAKA;;;;;;;;;;GAUG;AAEH,UAAU,WAAW;IACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC/B;AA2OD,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,EAAE,WAAW,2CAmBpD"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useContext, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { ModuleEditorContext } from './module-render-context.js';
|
|
5
|
+
const COMMENTS_ENDPOINT = '/api/v1/comments';
|
|
6
|
+
const HONEYPOT_FIELD = '_kywi_hp';
|
|
7
|
+
/**
|
|
8
|
+
* Resolve the content id this module is attached to, in priority order:
|
|
9
|
+
* 1. an explicit `contentId` prop (advanced binding / preview),
|
|
10
|
+
* 2. a `<meta name="kywi:content-id">` emitted on the public content page,
|
|
11
|
+
* 3. the nearest `[data-kywi-content-id]` ancestor (shared with inline editing).
|
|
12
|
+
* Resolution beyond the explicit prop happens client-side only, so SSR stays
|
|
13
|
+
* deterministic.
|
|
14
|
+
*/
|
|
15
|
+
function useResolvedContentId(explicit, ref) {
|
|
16
|
+
const [resolved, setResolved] = useState(explicit ?? null);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (explicit) {
|
|
19
|
+
setResolved(explicit);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (typeof document === 'undefined')
|
|
23
|
+
return;
|
|
24
|
+
const meta = document
|
|
25
|
+
.querySelector('meta[name="kywi:content-id"]')
|
|
26
|
+
?.getAttribute('content');
|
|
27
|
+
if (meta) {
|
|
28
|
+
setResolved(meta);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const el = ref.current?.closest('[data-kywi-content-id]');
|
|
32
|
+
const attr = el?.getAttribute('data-kywi-content-id');
|
|
33
|
+
if (attr)
|
|
34
|
+
setResolved(attr);
|
|
35
|
+
}, [explicit, ref]);
|
|
36
|
+
return resolved;
|
|
37
|
+
}
|
|
38
|
+
function authorLabel(c) {
|
|
39
|
+
return c.authorName && c.authorName.trim() ? c.authorName : 'Anonymous';
|
|
40
|
+
}
|
|
41
|
+
// ─── Editor preview (non-interactive) ────────────────────────────────────────
|
|
42
|
+
function CommentsModulePreview({ title, submitLabel }) {
|
|
43
|
+
return (_jsxs("div", { className: "kywi-module kywi-module-comments kywi-module-comments-preview", "aria-hidden": "true", children: [_jsx("h2", { className: "kywi-comments-title", children: title }), _jsx("p", { className: "kywi-comments-preview-note", children: "Approved comments for this content appear here; visitors can post new ones (held for moderation)." }), _jsxs("div", { className: "kywi-comments-form kywi-comments-form-preview", children: [_jsx("input", { className: "kywi-form-input", disabled: true, placeholder: "Name (optional)" }), _jsx("textarea", { className: "kywi-form-textarea", disabled: true, placeholder: "Add a comment\u2026" }), _jsx("button", { type: "button", className: "kywi-form-submit", disabled: true, children: submitLabel })] })] }));
|
|
44
|
+
}
|
|
45
|
+
// ─── Public view (interactive) ───────────────────────────────────────────────
|
|
46
|
+
function CommentsModuleView({ title, submitLabel, allowAnonymous, explicitId, }) {
|
|
47
|
+
const wrapperRef = useRef(null);
|
|
48
|
+
const contentId = useResolvedContentId(explicitId, wrapperRef);
|
|
49
|
+
const [comments, setComments] = useState([]);
|
|
50
|
+
const [loaded, setLoaded] = useState(false);
|
|
51
|
+
const [authorName, setAuthorName] = useState('');
|
|
52
|
+
const [authorEmail, setAuthorEmail] = useState('');
|
|
53
|
+
const [body, setBody] = useState('');
|
|
54
|
+
const [honeypot, setHoneypot] = useState('');
|
|
55
|
+
const [submitting, setSubmitting] = useState(false);
|
|
56
|
+
const [submitted, setSubmitted] = useState(false);
|
|
57
|
+
const [error, setError] = useState(null);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (!contentId)
|
|
60
|
+
return;
|
|
61
|
+
let cancelled = false;
|
|
62
|
+
fetch(`${COMMENTS_ENDPOINT}?contentId=${encodeURIComponent(contentId)}&status=approved`, {
|
|
63
|
+
credentials: 'include',
|
|
64
|
+
})
|
|
65
|
+
.then(r => (r.ok ? r.json() : null))
|
|
66
|
+
.then((payload) => {
|
|
67
|
+
if (!cancelled && Array.isArray(payload?.data)) {
|
|
68
|
+
// API returns newest-first; show oldest-first for readable threads.
|
|
69
|
+
setComments([...payload.data].reverse());
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
.catch(() => { })
|
|
73
|
+
.finally(() => { if (!cancelled)
|
|
74
|
+
setLoaded(true); });
|
|
75
|
+
return () => { cancelled = true; };
|
|
76
|
+
}, [contentId]);
|
|
77
|
+
const handleSubmit = async (e) => {
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
setError(null);
|
|
80
|
+
if (!contentId) {
|
|
81
|
+
setError('This comments block is not connected to any content yet.');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
setSubmitting(true);
|
|
85
|
+
try {
|
|
86
|
+
const payload = { contentId, body };
|
|
87
|
+
if (authorName)
|
|
88
|
+
payload.authorName = authorName;
|
|
89
|
+
if (authorEmail)
|
|
90
|
+
payload.authorEmail = authorEmail;
|
|
91
|
+
if (honeypot)
|
|
92
|
+
payload[HONEYPOT_FIELD] = honeypot;
|
|
93
|
+
const res = await fetch(COMMENTS_ENDPOINT, {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: { 'Content-Type': 'application/json' },
|
|
96
|
+
credentials: 'include',
|
|
97
|
+
body: JSON.stringify(payload),
|
|
98
|
+
});
|
|
99
|
+
const result = (await res.json().catch(() => null));
|
|
100
|
+
if (!res.ok) {
|
|
101
|
+
setError(result?.error?.message ?? 'Could not submit your comment. Please try again.');
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
setSubmitted(true);
|
|
105
|
+
setAuthorName('');
|
|
106
|
+
setAuthorEmail('');
|
|
107
|
+
setBody('');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
setError('Network error. Please try again.');
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
setSubmitting(false);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
return (_jsxs("div", { ref: wrapperRef, className: "kywi-module kywi-module-comments", "data-content-id": contentId ?? '', children: [_jsx("h2", { className: "kywi-comments-title", children: title }), _jsx("ul", { className: "kywi-comments-list", children: comments.map(c => (_jsxs("li", { className: "kywi-comment", children: [_jsxs("div", { className: "kywi-comment-meta", children: [_jsx("span", { className: "kywi-comment-author", children: authorLabel(c) }), c.createdAt && (_jsx("time", { className: "kywi-comment-date", dateTime: c.createdAt, children: new Date(c.createdAt).toLocaleDateString() }))] }), _jsx("p", { className: "kywi-comment-body", children: c.body })] }, c.id))) }), loaded && comments.length === 0 && (_jsx("p", { className: "kywi-comments-empty", children: "Be the first to comment." })), submitted ? (_jsx("div", { className: "kywi-comments-success", role: "status", children: _jsx("p", { children: "Thanks \u2014 your comment has been submitted and is awaiting moderation." }) })) : (_jsxs("form", { className: "kywi-comments-form", onSubmit: handleSubmit, children: [_jsx("div", { style: { position: 'absolute', left: '-9999px' }, "aria-hidden": "true", children: _jsx("input", { type: "text", name: HONEYPOT_FIELD, tabIndex: -1, autoComplete: "off", value: honeypot, onChange: e => setHoneypot(e.target.value) }) }), allowAnonymous && (_jsxs("div", { className: "kywi-comments-fields", children: [_jsx("label", { className: "kywi-form-label", htmlFor: "kywi-comment-name", children: "Name" }), _jsx("input", { id: "kywi-comment-name", className: "kywi-form-input", type: "text", value: authorName, placeholder: "Your name (optional)", onChange: e => setAuthorName(e.target.value) }), _jsx("label", { className: "kywi-form-label", htmlFor: "kywi-comment-email", children: "Email" }), _jsx("input", { id: "kywi-comment-email", className: "kywi-form-input", type: "email", value: authorEmail, placeholder: "Your email (optional, not published)", onChange: e => setAuthorEmail(e.target.value) })] })), _jsx("label", { className: "kywi-form-label", htmlFor: "kywi-comment-body", children: "Comment" }), _jsx("textarea", { id: "kywi-comment-body", className: "kywi-form-textarea", required: true, value: body, placeholder: "Add a comment\u2026", onChange: e => setBody(e.target.value) }), error && _jsx("div", { className: "kywi-form-error", role: "alert", children: error }), _jsx("div", { className: "kywi-form-actions", children: _jsx("button", { type: "submit", className: "kywi-form-submit", disabled: submitting || !body.trim(), children: submitting ? 'Submitting…' : submitLabel }) })] }))] }));
|
|
118
|
+
}
|
|
119
|
+
export function CommentsModule({ props }) {
|
|
120
|
+
const inEditor = useContext(ModuleEditorContext);
|
|
121
|
+
const title = String(props.title ?? 'Comments');
|
|
122
|
+
const submitLabel = String(props.submitLabel ?? 'Post Comment');
|
|
123
|
+
const allowAnonymous = props.allowAnonymous !== false;
|
|
124
|
+
const explicitId = props.contentId ? String(props.contentId) : undefined;
|
|
125
|
+
if (inEditor) {
|
|
126
|
+
return _jsx(CommentsModulePreview, { title: title, submitLabel: submitLabel });
|
|
127
|
+
}
|
|
128
|
+
return (_jsx(CommentsModuleView, { title: title, submitLabel: submitLabel, allowAnonymous: allowAnonymous, explicitId: explicitId }));
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=comments-module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comments-module.js","sourceRoot":"","sources":["../../src/layout/comments-module.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* feed-hydration.ts
|
|
3
|
+
*
|
|
4
|
+
* Server-side data resolution for the public layout render path.
|
|
5
|
+
*
|
|
6
|
+
* The `feedDisplay` module (see modules.tsx) is a `'use client'` component: it can
|
|
7
|
+
* only render items it is *given* via `props.items` — it cannot run a database
|
|
8
|
+
* query itself. On the public site, nothing ran that query, so every Feed Display
|
|
9
|
+
* module rendered an empty `<div class="kywi-feed-items">` (kywi-cms#28).
|
|
10
|
+
*
|
|
11
|
+
* This module closes that gap without coupling the renderer to a database: the
|
|
12
|
+
* host supplies a {@link FeedItemsResolver} (it has DB + feed-engine access), and
|
|
13
|
+
* {@link hydrateLayoutFeeds} walks a {@link LayoutDocument}, runs each feed once,
|
|
14
|
+
* maps rows to the display shape the module expects, and injects `props.items`
|
|
15
|
+
* before the document is handed to `<KywiRegion>`. This mirrors the existing
|
|
16
|
+
* `ComponentResolver` seam in renderer.tsx: core owns the walk + shape, the host
|
|
17
|
+
* owns the data access.
|
|
18
|
+
*/
|
|
19
|
+
import type { LayoutDocument } from './types.js';
|
|
20
|
+
/**
|
|
21
|
+
* The display-ready item shape `FeedDisplayModule` renders. Every field is a
|
|
22
|
+
* plain serializable value so the hydrated document can cross the
|
|
23
|
+
* server → client boundary as props.
|
|
24
|
+
*/
|
|
25
|
+
export interface FeedDisplayItem {
|
|
26
|
+
id: string;
|
|
27
|
+
title: string;
|
|
28
|
+
/** Absolute-or-root-relative link to the item, or null to render an unlinked title. */
|
|
29
|
+
href: string | null;
|
|
30
|
+
/** ISO-8601 timestamp string, or null when the row has no publish/created date. */
|
|
31
|
+
date: string | null;
|
|
32
|
+
summary: string | null;
|
|
33
|
+
/** Resolved image URL, or null. Media-id → URL resolution is a host concern. */
|
|
34
|
+
image: string | null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Resolve a feed slug to its raw content rows, server-side. The host implements
|
|
38
|
+
* this over the feed engine, e.g.
|
|
39
|
+
*
|
|
40
|
+
* ```ts
|
|
41
|
+
* const resolveFeedItems: FeedItemsResolver = async (slug, { limit }) => {
|
|
42
|
+
* const feed = await getFeedBySlug(db, slug, siteId)
|
|
43
|
+
* if (!feed) return []
|
|
44
|
+
* const result = await executeFeedQuery(
|
|
45
|
+
* db,
|
|
46
|
+
* { ...feed.query, siteId, status: 'published', limit },
|
|
47
|
+
* contentTypeMap,
|
|
48
|
+
* customTableMap,
|
|
49
|
+
* )
|
|
50
|
+
* return result.items
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* Rows are mapped to {@link FeedDisplayItem} by {@link contentRowsToFeedItems}.
|
|
55
|
+
*/
|
|
56
|
+
export type FeedItemsResolver = (feedSlug: string, opts: {
|
|
57
|
+
limit: number;
|
|
58
|
+
}) => Promise<Array<Record<string, unknown>>>;
|
|
59
|
+
/**
|
|
60
|
+
* Map raw feed-engine content rows to the {@link FeedDisplayItem} shape the
|
|
61
|
+
* Feed Display module renders. Unknown/missing fields degrade to null (the
|
|
62
|
+
* module hides those pieces) rather than throwing. `image` is only populated
|
|
63
|
+
* when the row already carries a URL string — resolving a media id to a URL is
|
|
64
|
+
* left to the host (it can pre-map rows before returning them from the resolver).
|
|
65
|
+
*/
|
|
66
|
+
export declare function contentRowsToFeedItems(rows: Array<Record<string, unknown>>): FeedDisplayItem[];
|
|
67
|
+
/**
|
|
68
|
+
* Resolve every `feedDisplay` module in `layout` and inject its items, returning
|
|
69
|
+
* a new document (the input is not mutated). Each distinct (slug, limit) pair is
|
|
70
|
+
* queried once and shared. Modules that already carry inline items are left
|
|
71
|
+
* untouched; a resolver error or an empty result yields an empty list (the module
|
|
72
|
+
* renders no items rather than crashing the page).
|
|
73
|
+
*/
|
|
74
|
+
export declare function hydrateLayoutFeeds(layout: LayoutDocument, resolveFeedItems: FeedItemsResolver): Promise<LayoutDocument>;
|
|
75
|
+
//# sourceMappingURL=feed-hydration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feed-hydration.d.ts","sourceRoot":"","sources":["../../src/layout/feed-hydration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAyC,MAAM,YAAY,CAAA;AAKvF;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,uFAAuF;IACvF,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,mFAAmF;IACnF,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,gFAAgF;IAChF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAC9B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,KACpB,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;AAqC5C;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACnC,eAAe,EAAE,CASnB;AA8CD;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,cAAc,EACtB,gBAAgB,EAAE,iBAAiB,GAClC,OAAO,CAAC,cAAc,CAAC,CAmCzB"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* feed-hydration.ts
|
|
3
|
+
*
|
|
4
|
+
* Server-side data resolution for the public layout render path.
|
|
5
|
+
*
|
|
6
|
+
* The `feedDisplay` module (see modules.tsx) is a `'use client'` component: it can
|
|
7
|
+
* only render items it is *given* via `props.items` — it cannot run a database
|
|
8
|
+
* query itself. On the public site, nothing ran that query, so every Feed Display
|
|
9
|
+
* module rendered an empty `<div class="kywi-feed-items">` (kywi-cms#28).
|
|
10
|
+
*
|
|
11
|
+
* This module closes that gap without coupling the renderer to a database: the
|
|
12
|
+
* host supplies a {@link FeedItemsResolver} (it has DB + feed-engine access), and
|
|
13
|
+
* {@link hydrateLayoutFeeds} walks a {@link LayoutDocument}, runs each feed once,
|
|
14
|
+
* maps rows to the display shape the module expects, and injects `props.items`
|
|
15
|
+
* before the document is handed to `<KywiRegion>`. This mirrors the existing
|
|
16
|
+
* `ComponentResolver` seam in renderer.tsx: core owns the walk + shape, the host
|
|
17
|
+
* owns the data access.
|
|
18
|
+
*/
|
|
19
|
+
import { isLayoutSection } from './types.js';
|
|
20
|
+
// ── Row → display-item mapping ────────────────────────────────────────────────
|
|
21
|
+
const DEFAULT_LIMIT = 10;
|
|
22
|
+
function toLimit(raw) {
|
|
23
|
+
const n = Number(raw);
|
|
24
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : DEFAULT_LIMIT;
|
|
25
|
+
}
|
|
26
|
+
function firstString(...values) {
|
|
27
|
+
for (const value of values) {
|
|
28
|
+
if (typeof value === 'string' && value.trim() !== '')
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
/** Derive a root-relative link from a content row's `path`/`slug`, or null. */
|
|
34
|
+
function rowHref(row) {
|
|
35
|
+
const explicit = firstString(row.href, row.url);
|
|
36
|
+
if (explicit)
|
|
37
|
+
return explicit;
|
|
38
|
+
const path = row.path;
|
|
39
|
+
if (typeof path === 'string' && path !== '' && path !== '/')
|
|
40
|
+
return path;
|
|
41
|
+
const slug = row.slug;
|
|
42
|
+
if (typeof slug === 'string' && slug.trim() !== '')
|
|
43
|
+
return `/${slug.replace(/^\/+/, '')}`;
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
/** Derive an ISO date string from a content row's publish/created columns, or null. */
|
|
47
|
+
function rowDate(row) {
|
|
48
|
+
const raw = row.publishedAt ?? row.date ?? row.createdAt;
|
|
49
|
+
if (raw == null)
|
|
50
|
+
return null;
|
|
51
|
+
const d = raw instanceof Date ? raw : new Date(String(raw));
|
|
52
|
+
return Number.isNaN(d.getTime()) ? null : d.toISOString();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Map raw feed-engine content rows to the {@link FeedDisplayItem} shape the
|
|
56
|
+
* Feed Display module renders. Unknown/missing fields degrade to null (the
|
|
57
|
+
* module hides those pieces) rather than throwing. `image` is only populated
|
|
58
|
+
* when the row already carries a URL string — resolving a media id to a URL is
|
|
59
|
+
* left to the host (it can pre-map rows before returning them from the resolver).
|
|
60
|
+
*/
|
|
61
|
+
export function contentRowsToFeedItems(rows) {
|
|
62
|
+
return rows.map((row, i) => ({
|
|
63
|
+
id: firstString(row.id) ?? String(i),
|
|
64
|
+
title: firstString(row.title, row.name, row.menuTitle) ?? '',
|
|
65
|
+
href: rowHref(row),
|
|
66
|
+
date: rowDate(row),
|
|
67
|
+
summary: firstString(row.summary, row.metaDescription, row.excerpt),
|
|
68
|
+
image: firstString(row.image),
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
// ── Layout walk + injection ───────────────────────────────────────────────────
|
|
72
|
+
function* modulesInSection(section) {
|
|
73
|
+
for (const column of section.columns) {
|
|
74
|
+
for (const node of column.nodes)
|
|
75
|
+
yield node;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Yield every ModuleNode reachable from a layout document — across the default
|
|
80
|
+
* regions, every page variant, every A/B experiment arm, and the sections nested
|
|
81
|
+
* inside variantContainers — so no feed module is missed regardless of where the
|
|
82
|
+
* author placed it.
|
|
83
|
+
*/
|
|
84
|
+
function* iterateModuleNodes(layout) {
|
|
85
|
+
const regionMaps = [layout.regions];
|
|
86
|
+
for (const pv of layout.pageVariants ?? [])
|
|
87
|
+
regionMaps.push(pv.regions);
|
|
88
|
+
for (const exp of layout.abExperiments ?? []) {
|
|
89
|
+
for (const variant of exp.variants)
|
|
90
|
+
regionMaps.push(variant.regions);
|
|
91
|
+
}
|
|
92
|
+
for (const regions of regionMaps) {
|
|
93
|
+
for (const nodes of Object.values(regions)) {
|
|
94
|
+
for (const node of nodes) {
|
|
95
|
+
if (isLayoutSection(node)) {
|
|
96
|
+
yield* modulesInSection(node);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
for (const section of node.defaultSections)
|
|
100
|
+
yield* modulesInSection(section);
|
|
101
|
+
for (const variant of node.variants) {
|
|
102
|
+
for (const section of variant.sections)
|
|
103
|
+
yield* modulesInSection(section);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function hasResolvedItems(node) {
|
|
111
|
+
const items = node.props.items;
|
|
112
|
+
if (Array.isArray(items))
|
|
113
|
+
return items.length > 0;
|
|
114
|
+
// A non-empty JSON string of items counts as author-provided too.
|
|
115
|
+
return typeof items === 'string' && items.trim() !== '' && items.trim() !== '[]';
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Resolve every `feedDisplay` module in `layout` and inject its items, returning
|
|
119
|
+
* a new document (the input is not mutated). Each distinct (slug, limit) pair is
|
|
120
|
+
* queried once and shared. Modules that already carry inline items are left
|
|
121
|
+
* untouched; a resolver error or an empty result yields an empty list (the module
|
|
122
|
+
* renders no items rather than crashing the page).
|
|
123
|
+
*/
|
|
124
|
+
export async function hydrateLayoutFeeds(layout, resolveFeedItems) {
|
|
125
|
+
const cloned = structuredClone(layout);
|
|
126
|
+
const feedNodes = [];
|
|
127
|
+
for (const node of iterateModuleNodes(cloned)) {
|
|
128
|
+
if (node.type === 'feedDisplay')
|
|
129
|
+
feedNodes.push(node);
|
|
130
|
+
}
|
|
131
|
+
if (feedNodes.length === 0)
|
|
132
|
+
return cloned;
|
|
133
|
+
const cache = new Map();
|
|
134
|
+
for (const node of feedNodes) {
|
|
135
|
+
if (hasResolvedItems(node))
|
|
136
|
+
continue;
|
|
137
|
+
const feedSlug = typeof node.props.feedSlug === 'string' ? node.props.feedSlug.trim() : '';
|
|
138
|
+
if (feedSlug === '')
|
|
139
|
+
continue;
|
|
140
|
+
const limit = toLimit(node.props.limit);
|
|
141
|
+
const key = `${feedSlug}\u0000${limit}`;
|
|
142
|
+
let items = cache.get(key);
|
|
143
|
+
if (!items) {
|
|
144
|
+
let rows = [];
|
|
145
|
+
try {
|
|
146
|
+
rows = await resolveFeedItems(feedSlug, { limit });
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
rows = [];
|
|
150
|
+
}
|
|
151
|
+
items = contentRowsToFeedItems(Array.isArray(rows) ? rows : []);
|
|
152
|
+
cache.set(key, items);
|
|
153
|
+
}
|
|
154
|
+
node.props.items = items;
|
|
155
|
+
}
|
|
156
|
+
return cloned;
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=feed-hydration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feed-hydration.js","sourceRoot":"","sources":["../../src/layout/feed-hydration.ts"],"names":[],"mappings":"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"}
|