@cosmicdrift/kumiko-bundled-features 0.334.0 → 0.335.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/dist/admin-shell/overview-panels.js +2 -2
- package/dist/agent-tools/agent-doc-lint.d.ts +1 -0
- package/dist/agent-tools/agent-doc-lint.js +33 -3
- package/dist/agent-tools/agent-manifest.js +47 -6
- package/dist/agent-tools/filter-ops.d.ts +2 -0
- package/dist/agent-tools/filter-ops.js +4 -0
- package/dist/agent-tools/tool-catalog.js +1 -4
- package/dist/agent-tools/tool-dispatch.js +1 -4
- package/dist/audit/constants.d.ts +1 -1
- package/dist/audit/constants.js +1 -1
- package/dist/auth-email-password/handlers/change-password.write.js +2 -0
- package/dist/auth-email-password/handlers/invite-create.write.js +12 -2
- package/dist/auth-email-password/handlers/login.write.js +2 -0
- package/dist/auth-email-password/handlers/self-registration-status.query.js +1 -1
- package/dist/auth-email-password/i18n.js +2 -0
- package/dist/auth-email-password/web/auth-client.d.ts +1 -0
- package/dist/auth-email-password/web/auth-client.js +1 -1
- package/dist/auth-email-password/web/auth-gate.js +6 -8
- package/dist/auth-email-password/web/index.d.ts +1 -1
- package/dist/auth-email-password/web/index.js +1 -1
- package/dist/auth-email-password/web/invite-accept-screen.js +16 -12
- package/dist/auth-email-password/web/login-screen.js +1 -1
- package/dist/auth-email-password/web/session-bootstrap-error.d.ts +5 -1
- package/dist/auth-email-password/web/session-bootstrap-error.js +2 -2
- package/dist/auth-email-password/web/session.d.ts +4 -0
- package/dist/auth-email-password/web/session.js +15 -0
- package/dist/auth-mfa/feature.js +3 -2
- package/dist/auth-mfa/handlers/enable-confirm-preauth.write.js +2 -2
- package/dist/auth-mfa/handlers/verify.write.js +2 -0
- package/dist/auth-mfa/hmac-token-codec.js +3 -0
- package/dist/auth-mfa/mfa-challenge-token.js +4 -0
- package/dist/auth-mfa/mfa-preauth-setup-token.js +3 -0
- package/dist/auth-mfa/web/mfa-client.d.ts +1 -1
- package/dist/auth-mfa/web/mfa-client.js +4 -10
- package/dist/billing-foundation/billing-info-query.js +1 -1
- package/dist/billing-foundation/checkout-core.d.ts +5 -5
- package/dist/billing-foundation/checkout-core.js +10 -6
- package/dist/billing-foundation/handlers/switch-plan.write.js +5 -8
- package/dist/cap-counter/book-cap-usage.js +15 -5
- package/dist/cap-counter/enforce-cap.js +9 -3
- package/dist/cap-counter/feature.js +3 -2
- package/dist/cap-counter/i18n.d.ts +1 -0
- package/dist/cap-counter/i18n.js +6 -5
- package/dist/cap-overview/handlers/caps-usage.query.js +9 -16
- package/dist/cap-overview/handlers/tenant-caps-list.query.js +41 -15
- package/dist/cap-overview/i18n.js +5 -0
- package/dist/cap-overview/web/cap-cards-panel.js +4 -1
- package/dist/cap-overview/web/cap-usage-bar.js +1 -1
- package/dist/channel-email/email-channel.js +12 -1
- package/dist/channel-in-app/tables.js +2 -6
- package/dist/compliance-profiles/handlers/sub-processors.query.js +1 -1
- package/dist/config/db/queries/resolver.js +3 -1
- package/dist/config/handlers/set.write.js +8 -4
- package/dist/config/web/i18n.js +1 -0
- package/dist/config/write-helpers.d.ts +1 -1
- package/dist/config/write-helpers.js +11 -5
- package/dist/crypto-shredding/handlers/forget-subject.write.js +15 -8
- package/dist/custom-fields/constants.js +1 -4
- package/dist/data-retention/resolve-for-tenant.d.ts +2 -3
- package/dist/data-retention/run-retention-cleanup.js +100 -44
- package/dist/delivery/types.d.ts +2 -1
- package/dist/delivery/types.js +7 -0
- package/dist/delivery/unsubscribe.js +5 -11
- package/dist/derivatives-sharp/render.js +27 -9
- package/dist/document-ingest-foundation/feature.js +5 -5
- package/dist/document-ingest-foundation/providers.d.ts +1 -0
- package/dist/document-ingest-foundation/providers.js +6 -0
- package/dist/document-ingest-foundation/write-document-extract.d.ts +2 -2
- package/dist/document-ingest-foundation/write-document-extract.js +10 -0
- package/dist/feature-toggles/global-feature-state-table.js +2 -7
- package/dist/feature-toggles/toggle-sync-signal.d.ts +2 -1
- package/dist/feature-toggles/toggle-sync-signal.js +3 -2
- package/dist/files-tenant-data/index.js +3 -2
- package/dist/folders/handlers/clear-folder.write.js +4 -11
- package/dist/folders/handlers/set-folder.write.js +4 -10
- package/dist/form-draft/feature.js +3 -2
- package/dist/form-draft/handlers/discard.write.js +7 -8
- package/dist/form-draft/handlers/get.query.js +7 -1
- package/dist/foundation-shared/host-allowlist.d.ts +4 -0
- package/dist/foundation-shared/host-allowlist.js +14 -0
- package/dist/foundation-shared/index.d.ts +1 -0
- package/dist/foundation-shared/index.js +1 -0
- package/dist/foundation-shared/mail-host-policy.d.ts +0 -2
- package/dist/foundation-shared/mail-host-policy.js +3 -13
- package/dist/inbound-mail-foundation/connect-routes.js +7 -5
- package/dist/inbound-mail-foundation/watch-supervisor.js +47 -43
- package/dist/jobs/handlers/list.query.d.ts +7 -2
- package/dist/jobs/handlers/list.query.js +9 -2
- package/dist/jobs/handlers/retry.write.js +3 -2
- package/dist/jobs/handlers/tenant-failures.query.js +3 -9
- package/dist/jobs/job-run-logger.js +18 -2
- package/dist/jobs/tenant-job-failure-table.js +2 -6
- package/dist/ledger/handlers/confirm-schedule-period.write.js +2 -2
- package/dist/ledger/handlers/reverse-transaction.write.js +2 -2
- package/dist/ledger/subject-reference.d.ts +4 -0
- package/dist/ledger/subject-reference.js +7 -0
- package/dist/legal-pages/feature.js +8 -1
- package/dist/managed-pages/handlers/branding.query.js +1 -1
- package/dist/managed-pages/handlers/by-slug.query.js +1 -1
- package/dist/managed-pages/handlers/by-tenant-published.query.js +1 -1
- package/dist/metrics/compute.js +7 -1
- package/dist/metrics/window.js +5 -0
- package/dist/notes-history/handlers/add-note.write.js +4 -9
- package/dist/notes-history-user-data/hooks.js +8 -1
- package/dist/personal-access-tokens/constants.d.ts +1 -0
- package/dist/personal-access-tokens/constants.js +1 -0
- package/dist/personal-access-tokens/feature.js +2 -2
- package/dist/personal-access-tokens/handlers/create.write.js +2 -2
- package/dist/personal-access-tokens/handlers/revoke.write.js +2 -2
- package/dist/personal-access-tokens/i18n.js +12 -13
- package/dist/personal-access-tokens/schema/api-token.js +2 -1
- package/dist/personal-access-tokens/screens.js +5 -1
- package/dist/rate-limiting/feature.js +2 -0
- package/dist/rate-limiting/handlers/bucket-access.js +15 -7
- package/dist/rate-limiting/i18n.d.ts +10 -0
- package/dist/rate-limiting/i18n.js +10 -0
- package/dist/secrets/feature.js +3 -2
- package/dist/seo/handlers/seo-config.query.js +1 -1
- package/dist/sessions/constants.d.ts +1 -0
- package/dist/sessions/constants.js +1 -0
- package/dist/sessions/feature.js +5 -3
- package/dist/sessions/handlers/revoke-all-for-user.write.d.ts +1 -0
- package/dist/sessions/handlers/revoke-all-for-user.write.js +12 -2
- package/dist/sessions/handlers/revoke.write.js +2 -2
- package/dist/sessions/schema/user-session.js +2 -1
- package/dist/sessions/screens.js +5 -1
- package/dist/sessions/session-callbacks.js +2 -2
- package/dist/shared/index.d.ts +1 -1
- package/dist/shared/index.js +1 -1
- package/dist/shared/lockout-counter.js +1 -1
- package/dist/shared/parent-visibility.d.ts +5 -0
- package/dist/shared/parent-visibility.js +17 -1
- package/dist/shared/row-bound-grant.js +20 -9
- package/dist/shared/single-use-token-store.js +5 -13
- package/dist/step-dispatcher/webhook-runner.d.ts +0 -2
- package/dist/step-dispatcher/webhook-runner.js +18 -17
- package/dist/subscription-mollie/plugin-methods.d.ts +3 -2
- package/dist/subscription-mollie/plugin-methods.js +7 -1
- package/dist/subscription-mollie/verify-webhook.js +6 -0
- package/dist/subscription-stripe/plugin-methods.js +25 -6
- package/dist/subscription-stripe/verify-webhook.js +13 -6
- package/dist/tags/handlers/assign-tag.write.js +4 -10
- package/dist/tags/handlers/remove-tag.write.js +4 -11
- package/dist/template-resolver/api.d.ts +2 -1
- package/dist/template-resolver/api.js +1 -1
- package/dist/template-resolver/handlers/by-slug.query.d.ts +1 -1
- package/dist/template-resolver/handlers/by-slug.query.js +2 -2
- package/dist/template-resolver/handlers/by-tenant.query.d.ts +3 -2
- package/dist/template-resolver/handlers/by-tenant.query.js +2 -2
- package/dist/template-resolver/handlers/collection-item.query.d.ts +1 -1
- package/dist/template-resolver/handlers/collection-list.query.d.ts +1 -1
- package/dist/template-resolver/handlers/collection-shared.d.ts +3 -2
- package/dist/template-resolver/handlers/collection-shared.js +1 -1
- package/dist/template-resolver/handlers/find-by-id.query.d.ts +1 -1
- package/dist/template-resolver/handlers/find-by-id.query.js +1 -1
- package/dist/template-resolver/handlers/list.query.d.ts +1 -1
- package/dist/template-resolver/handlers/list.query.js +1 -1
- package/dist/template-resolver/seeding.js +41 -39
- package/dist/template-resolver/table.d.ts +8 -5
- package/dist/template-resolver/testing.js +2 -2
- package/dist/template-resolver/user-content-table.d.ts +2 -5
- package/dist/template-resolver/web/client-plugin.d.ts +1 -1
- package/dist/template-resolver-user-data/hooks.js +1 -1
- package/dist/tenant/cancel-pending-invitation.d.ts +8 -0
- package/dist/tenant/cancel-pending-invitation.js +22 -0
- package/dist/tenant/feature.js +4 -2
- package/dist/tenant/handlers/cancel-invitation.write.js +6 -21
- package/dist/tenant/handlers/member-directory.query.d.ts +1 -0
- package/dist/tenant/handlers/member-directory.query.js +17 -4
- package/dist/tenant/handlers/remove-member.write.js +9 -14
- package/dist/tenant/handlers/team-list.query.js +2 -2
- package/dist/tenant/handlers/tenant-directory.query.d.ts +1 -0
- package/dist/tenant/handlers/tenant-directory.query.js +13 -3
- package/dist/tenant/handlers/update-member-roles.write.d.ts +1 -1
- package/dist/tenant/handlers/update-member-roles.write.js +28 -12
- package/dist/tenant/i18n.js +4 -0
- package/dist/tenant/index.d.ts +1 -1
- package/dist/tenant/index.js +1 -1
- package/dist/tenant/last-tenant-admin.d.ts +1 -1
- package/dist/tenant/last-tenant-admin.js +5 -3
- package/dist/tenant/seeding.d.ts +4 -0
- package/dist/tenant/seeding.js +9 -2
- package/dist/tenant-lifecycle/boot-checks.d.ts +1 -0
- package/dist/tenant-lifecycle/boot-checks.js +27 -1
- package/dist/tenant-lifecycle/feature.js +2 -1
- package/dist/tenant-lifecycle/stages.js +2 -2
- package/dist/tier-engine/feature.js +3 -2
- package/dist/user/command-schemas.d.ts +2 -2
- package/dist/user/handlers/create.write.d.ts +1 -1
- package/dist/user/handlers/create.write.js +8 -2
- package/dist/user/handlers/update.write.d.ts +1 -1
- package/dist/user/handlers/update.write.js +8 -2
- package/dist/user-data-rights/db/queries/export-jobs.d.ts +2 -0
- package/dist/user-data-rights/feature.js +22 -9
- package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.js +4 -7
- package/dist/user-data-rights/handlers/deletion-grace-period.d.ts +3 -2
- package/dist/user-data-rights/handlers/deletion-grace-period.js +30 -8
- package/dist/user-data-rights/handlers/download-by-token.query.js +1 -1
- package/dist/user-data-rights/handlers/request-deletion-by-email.write.js +1 -1
- package/dist/user-data-rights/handlers/request-deletion.write.js +3 -6
- package/dist/user-data-rights/lib/update-user-lifecycle.js +7 -14
- package/dist/user-data-rights/run-export-jobs.d.ts +1 -0
- package/dist/user-data-rights/run-export-jobs.js +74 -41
- package/dist/user-data-rights/run-forget-cleanup.js +2 -2
- package/dist/user-data-rights/run-user-export.js +2 -2
- package/dist/user-data-rights-defaults/hooks/file-ref.userdata-hook.js +4 -1
- package/dist/user-profile/feature.js +6 -1
- package/dist/user-profile/handlers/change-email.write.js +2 -0
- package/dist/workflow-runner/db/queries/due-runs.js +6 -9
- package/dist/workflow-runner/event-subscriber.js +29 -9
- package/dist/workflow-runner/handlers/resume-run.write.js +8 -5
- package/dist/workflow-runner/pending-projection.js +8 -1
- package/package.json +11 -9
- package/src/agent-tools/changes.json +12 -0
- package/src/auth-email-password/changes.json +18 -0
- package/src/auth-mfa/changes.json +6 -0
- package/src/billing-foundation/changes.json +7 -0
- package/src/cap-counter/changes.json +7 -0
- package/src/cap-overview/changes.json +12 -0
- package/src/channel-email/changes.json +6 -0
- package/src/config/changes.json +6 -0
- package/src/crypto-shredding/changes.json +6 -0
- package/src/data-retention/changes.json +12 -0
- package/src/delivery/changes.json +7 -0
- package/src/derivatives-sharp/changes.json +11 -0
- package/src/document-ingest-foundation/changes.json +12 -0
- package/src/inbound-mail-foundation/changes.json +12 -0
- package/src/jobs/changes.json +13 -0
- package/src/legal-pages/changes.json +8 -1
- package/src/managed-pages/changes.json +5 -0
- package/src/metrics/changes.json +6 -0
- package/src/rate-limiting/changes.json +5 -0
- package/src/secrets/changes.json +7 -0
- package/src/sessions/changes.json +5 -0
- package/src/shared/changes.json +6 -0
- package/src/step-dispatcher/changes.json +5 -0
- package/src/subscription-mollie/changes.json +7 -0
- package/src/subscription-stripe/changes.json +11 -0
- package/src/template-resolver/changes.json +7 -0
- package/src/tenant/changes.json +29 -0
- package/src/user-data-rights/changes.json +22 -0
- package/src/workflow-runner/changes.json +6 -0
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
// user-data-rights' confirm-deletion-by-token and any "you just created this
|
|
4
4
|
// row, now enrich it" flow.
|
|
5
5
|
//
|
|
6
|
-
// The row's own anchor (a value that moves on when the row is consumed
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
6
|
+
// The row's own anchor (a value that moves on when the row is consumed) is
|
|
7
|
+
// mixed INTO the HMAC purpose rather than carried in the token. It must be
|
|
8
|
+
// unique per cycle and never recur (request id, UUID, monotonic version): a
|
|
9
|
+
// status can return to an earlier value and revive an old grant (ABA replay).
|
|
10
|
+
// Verification recomputes it from the row's CURRENT anchor, so a replayed
|
|
11
|
+
// token dies the moment the row moves on — single-use semantics without a
|
|
12
|
+
// burn key or Redis. Minting and redeeming
|
|
11
13
|
// share one purpose-building function so the two can't drift apart; that
|
|
12
14
|
// coupling is the whole point of this module, since an unanchored purpose
|
|
13
15
|
// silently degrades to a bearer token valid for the full TTL.
|
|
@@ -27,7 +29,9 @@
|
|
|
27
29
|
// clear. A grant on a row therefore exposes that row's id to whoever holds
|
|
28
30
|
// the link. Where the id itself must stay hidden, use an opaque handle
|
|
29
31
|
// (single-use-token-store) instead.
|
|
32
|
+
import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
|
|
30
33
|
import { peekTokenSubject, signToken, verifyToken } from "./signed-token.js";
|
|
34
|
+
const log = createFallbackLogger("row-bound-grant");
|
|
31
35
|
const FAILED = { ok: false };
|
|
32
36
|
function anchoredPurpose(purpose, anchor) {
|
|
33
37
|
return `${purpose}:${anchor}`;
|
|
@@ -40,6 +44,10 @@ export function signRowBoundGrant(args) {
|
|
|
40
44
|
// hand an attacker a row-existence oracle on an endpoint that is open to
|
|
41
45
|
// anonymous callers.
|
|
42
46
|
export async function redeemRowBoundGrant(args) {
|
|
47
|
+
// Misconfiguration must throw on every call, not only on the first valid redemption.
|
|
48
|
+
if (typeof args.commitAnchor !== "function" && !args.commitAnchor.unsafeSkip.reason.trim()) {
|
|
49
|
+
throw new Error("row-bound grant: unsafeSkip needs a non-empty reason");
|
|
50
|
+
}
|
|
43
51
|
if (!args.secret)
|
|
44
52
|
return FAILED;
|
|
45
53
|
const subject = peekTokenSubject(args.token);
|
|
@@ -52,7 +60,13 @@ export async function redeemRowBoundGrant(args) {
|
|
|
52
60
|
try {
|
|
53
61
|
anchor = await args.loadAnchor(subject);
|
|
54
62
|
}
|
|
55
|
-
catch {
|
|
63
|
+
catch (error) {
|
|
64
|
+
// Name/code only: driver messages can echo the attacker-supplied subject.
|
|
65
|
+
// Logged so an infrastructure outage doesn't look like an expired link.
|
|
66
|
+
log.warn("row-bound grant anchor lookup failed", {
|
|
67
|
+
errorName: error instanceof Error ? error.name : typeof error,
|
|
68
|
+
code: error instanceof Error && "code" in error ? String(error.code) : undefined,
|
|
69
|
+
});
|
|
56
70
|
return FAILED;
|
|
57
71
|
}
|
|
58
72
|
if (!anchor)
|
|
@@ -67,8 +81,5 @@ export async function redeemRowBoundGrant(args) {
|
|
|
67
81
|
if (!spent)
|
|
68
82
|
return FAILED;
|
|
69
83
|
}
|
|
70
|
-
else if (!args.commitAnchor.unsafeSkip.reason.trim()) {
|
|
71
|
-
throw new Error("row-bound grant: unsafeSkip needs a non-empty reason");
|
|
72
|
-
}
|
|
73
84
|
return { ok: true, subject, expiresAtMs: verified.expiresAtMs };
|
|
74
85
|
}
|
|
@@ -1,21 +1,13 @@
|
|
|
1
1
|
// Generic Redis-backed pre-activation token store: bidirectional
|
|
2
2
|
// token↔subject mapping plus single-use burn/unburn semantics. Extracted
|
|
3
3
|
// from auth-email-password/signup-token-store.ts and
|
|
4
|
-
// auth-email-password/invite-token-store.ts
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// auth-email-password/invite-token-store.ts — both were the same Redis
|
|
5
|
+
// layout, differing only in their key prefixes and which field (email vs.
|
|
6
|
+
// invitationId) plays the "subject" role.
|
|
7
7
|
//
|
|
8
8
|
// Public subpath export (./shared/single-use-token-store in package.json):
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// with a comment noting it "must stay byte-compatible with
|
|
12
|
-
// auth-email-password/signup-token-store" because signup-confirm resolves
|
|
13
|
-
// tokens via the same Redis key layout. offlot-app#418 (separate issue,
|
|
14
|
-
// after this ships) will replace that copy with
|
|
15
|
-
// `createSingleUseTokenStore({ tokenPrefix: "signup:by-token:", subjectPrefix:
|
|
16
|
-
// "signup:by-email:", burnPrefix: "signup:burn:" })` — i.e. the exact same
|
|
17
|
-
// prefix strings the framework's own signup store below uses, so both stay
|
|
18
|
-
// byte-compatible by construction instead of by hand-copied logic.
|
|
9
|
+
// external consumers share the signup key layout, so the prefixes must stay
|
|
10
|
+
// byte-identical.
|
|
19
11
|
//
|
|
20
12
|
// Token material: opaque random 256-bit (e.g. crypto.randomBytes,
|
|
21
13
|
// base64url-encoded). Not designed for human typing — the subject clicks a
|
|
@@ -3,8 +3,6 @@ import type { SecretsContext } from "@cosmicdrift/kumiko-framework/secrets";
|
|
|
3
3
|
import type { TenantId } from "@cosmicdrift/kumiko-types/identifiers";
|
|
4
4
|
import * as z from "zod";
|
|
5
5
|
export declare const WEBHOOK_ALLOWED_PRIVATE_HOSTS_ENV_VAR = "KUMIKO_WEBHOOK_ALLOWED_PRIVATE_HOSTS";
|
|
6
|
-
/** Parses the comma-separated operator allowlist env var. Never throws —
|
|
7
|
-
* an unset or empty value just means no bypass. */
|
|
8
6
|
export declare function readAllowedPrivateWebhookHostsFromEnv(env?: Readonly<Record<string, string | undefined>>): readonly string[];
|
|
9
7
|
export declare function setWebhookHostLookup(fn: typeof lookup | undefined): void;
|
|
10
8
|
export declare const WEBHOOK_AUTH_SECRET_KEY_PREFIX = "step-dispatcher:webhook-auth.";
|
|
@@ -22,18 +22,11 @@
|
|
|
22
22
|
import { BlockedHostError, buildPinnedRequest, HostResolutionError, resolvePublicHostname, } from "@cosmicdrift/kumiko-framework/http";
|
|
23
23
|
import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
|
|
24
24
|
import * as z from "zod";
|
|
25
|
+
import { isHostAllowlisted, readHostAllowlistFromEnv } from "../foundation-shared/index.js";
|
|
25
26
|
const log = createFallbackLogger("step-dispatcher");
|
|
26
27
|
export const WEBHOOK_ALLOWED_PRIVATE_HOSTS_ENV_VAR = "KUMIKO_WEBHOOK_ALLOWED_PRIVATE_HOSTS";
|
|
27
|
-
/** Parses the comma-separated operator allowlist env var. Never throws —
|
|
28
|
-
* an unset or empty value just means no bypass. */
|
|
29
28
|
export function readAllowedPrivateWebhookHostsFromEnv(env = process.env) {
|
|
30
|
-
|
|
31
|
-
if (!raw)
|
|
32
|
-
return [];
|
|
33
|
-
return raw
|
|
34
|
-
.split(",")
|
|
35
|
-
.map((host) => host.trim())
|
|
36
|
-
.filter((host) => host.length > 0);
|
|
29
|
+
return readHostAllowlistFromEnv(WEBHOOK_ALLOWED_PRIVATE_HOSTS_ENV_VAR, env);
|
|
37
30
|
}
|
|
38
31
|
// Test-only DNS seam — production never calls this, resolvePublicHostname
|
|
39
32
|
// defaults to the real resolver. Reset it in afterEach/afterAll — this is
|
|
@@ -87,14 +80,23 @@ async function buildWebhookHeaders(spec, deps) {
|
|
|
87
80
|
return { ok: true, headers };
|
|
88
81
|
if (!deps.secrets)
|
|
89
82
|
return { ok: false, error: WEBHOOK_AUTH_SECRET_UNAVAILABLE_ERROR };
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
83
|
+
// A throwing get (corrupt envelope, rotated key, KMS error) must become a
|
|
84
|
+
// dispatch-failed event; rethrowing would retry and dead-letter the whole
|
|
85
|
+
// step-dispatcher consumer for every tenant.
|
|
86
|
+
let secret;
|
|
87
|
+
try {
|
|
88
|
+
const revealed = await deps.secrets.get(deps.tenantId, webhookAuthSecretKey(spec.auth.secret), {
|
|
89
|
+
userId: deps.userId,
|
|
90
|
+
handlerName: "step-dispatcher:webhook.send",
|
|
91
|
+
});
|
|
92
|
+
if (!revealed)
|
|
93
|
+
return { ok: false, error: WEBHOOK_AUTH_SECRET_UNAVAILABLE_ERROR };
|
|
94
|
+
secret = revealed.reveal();
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
log.warn("webhook auth secret lookup failed", { tenantId: deps.tenantId });
|
|
95
98
|
return { ok: false, error: WEBHOOK_AUTH_SECRET_UNAVAILABLE_ERROR };
|
|
96
99
|
}
|
|
97
|
-
const secret = revealed.reveal();
|
|
98
100
|
if (spec.auth.kind === "bearer") {
|
|
99
101
|
headers["authorization"] = `Bearer ${secret}`;
|
|
100
102
|
}
|
|
@@ -104,8 +106,7 @@ async function buildWebhookHeaders(spec, deps) {
|
|
|
104
106
|
return { ok: true, headers };
|
|
105
107
|
}
|
|
106
108
|
async function resolveWebhookFetchTarget(rawUrl, url, headers) {
|
|
107
|
-
|
|
108
|
-
if (isAllowedPrivateHost)
|
|
109
|
+
if (isHostAllowlisted(url.hostname, readAllowedPrivateWebhookHostsFromEnv()))
|
|
109
110
|
return { ok: true, fetchUrl: rawUrl, requestInit: { headers } };
|
|
110
111
|
try {
|
|
111
112
|
const resolved = await resolvePublicHostname(url.hostname, webhookHostLookup);
|
|
@@ -8,8 +8,9 @@ export type MolliePriceConfig = {
|
|
|
8
8
|
/** Mollie-format: 2-decimal string, z.B. `"9.99"`. */
|
|
9
9
|
readonly amountValue: string;
|
|
10
10
|
readonly amountCurrency: string;
|
|
11
|
-
/** Mollie-format: `1 month` / `1 year` / `14 days`.
|
|
12
|
-
|
|
11
|
+
/** Mollie-format: `1 month` / `1 year` / `14 days`. Required for
|
|
12
|
+
* subscription prices; omit for one-off prices (`mode: "payment"`). */
|
|
13
|
+
readonly interval?: string;
|
|
13
14
|
readonly description: string;
|
|
14
15
|
};
|
|
15
16
|
export declare function createMollieCheckoutSession(client: MollieClient, priceToConfig: Readonly<Record<string, MolliePriceConfig>>, appWebhookUrl: string): (_ctx: HandlerContext, options: MollieCheckoutOptions) => Promise<{
|
|
@@ -19,6 +19,11 @@ export function createMollieCheckoutSession(client, priceToConfig, appWebhookUrl
|
|
|
19
19
|
if (!priceCfg) {
|
|
20
20
|
throw new Error(`subscription-mollie: priceId "${options.priceId}" not in priceToConfig-Map`);
|
|
21
21
|
}
|
|
22
|
+
// A first payment without an interval would set up a mandate that
|
|
23
|
+
// verify-webhook can't turn into a subscription — fail before charging.
|
|
24
|
+
if (options.mode !== "payment" && !priceCfg.interval) {
|
|
25
|
+
throw new Error(`subscription-mollie: priceId "${options.priceId}" has no interval but is booked as a subscription (mode is not "payment")`);
|
|
26
|
+
}
|
|
22
27
|
let customerId = options.providerCustomerId;
|
|
23
28
|
if (!customerId) {
|
|
24
29
|
const customer = await client.customers.create({
|
|
@@ -50,7 +55,8 @@ export function createMollieCheckoutSession(client, priceToConfig, appWebhookUrl
|
|
|
50
55
|
})); // @cast-boundary engine-bridge
|
|
51
56
|
const checkoutHref = payment.getCheckoutUrl();
|
|
52
57
|
if (!checkoutHref) {
|
|
53
|
-
|
|
58
|
+
const mandateHint = sequenceType === SequenceType.first ? " — first-payment-mandates ggf. nicht aktiviert" : "";
|
|
59
|
+
throw new Error(`subscription-mollie: payment.getCheckoutUrl() returned null (sequenceType=${sequenceType})${mandateHint}`);
|
|
54
60
|
}
|
|
55
61
|
return { url: checkoutHref };
|
|
56
62
|
};
|
|
@@ -14,8 +14,10 @@
|
|
|
14
14
|
// das im Plugin (idempotent via list-check), damit Foundation einen
|
|
15
15
|
// Created-Event mit subscription-id bekommt.
|
|
16
16
|
import { SubscriptionEventTypes, SubscriptionStatuses, } from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
|
|
17
|
+
import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
|
|
17
18
|
import { Temporal } from "temporal-polyfill";
|
|
18
19
|
import { MOLLIE_PROVIDER_NAME } from "./constants.js";
|
|
20
|
+
const log = createFallbackLogger("subscription-mollie");
|
|
19
21
|
export function verifyAndParseMollieWebhook(client, options) {
|
|
20
22
|
return async (rawBody, headers) => {
|
|
21
23
|
const id = extractMollieId(rawBody, headers);
|
|
@@ -115,6 +117,10 @@ async function ensureSubscriptionForMandate(client, options, payment) {
|
|
|
115
117
|
const priceCfg = options.priceToConfig[priceId];
|
|
116
118
|
if (!priceCfg)
|
|
117
119
|
return null;
|
|
120
|
+
if (!priceCfg.interval) {
|
|
121
|
+
log.warn("price has no interval, no subscription created", { priceId });
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
118
124
|
const existing = await client.customerSubscriptions.list(customerId);
|
|
119
125
|
const matchingExisting = existing.find((sub) => sub.metadata?.["priceId"] === priceId && // @cast-boundary engine-bridge
|
|
120
126
|
(sub.status === "active" || sub.status === "pending"));
|
|
@@ -45,6 +45,10 @@ export function createStripeCheckoutSession(runtime, { paymentInvoiceCreation =
|
|
|
45
45
|
: {
|
|
46
46
|
payment_intent_data: { metadata: { tenantId: options.tenantId } },
|
|
47
47
|
invoice_creation: { enabled: paymentInvoiceCreation },
|
|
48
|
+
// Without a customer, Stripe's default "if_required" makes a guest
|
|
49
|
+
// checkout when invoice_creation is off; the webhook then has no
|
|
50
|
+
// customer id and drops the paid payment as ignored.
|
|
51
|
+
...(!options.providerCustomerId && { customer_creation: "always" }),
|
|
48
52
|
}),
|
|
49
53
|
...(options.providerCustomerId && { customer: options.providerCustomerId }),
|
|
50
54
|
});
|
|
@@ -113,12 +117,14 @@ function isKnownRecurringInterval(interval) {
|
|
|
113
117
|
}
|
|
114
118
|
function mapStripePrice(price) {
|
|
115
119
|
const interval = price.recurring?.interval;
|
|
120
|
+
const knownInterval = isKnownRecurringInterval(interval) ? interval : null;
|
|
116
121
|
return {
|
|
117
122
|
priceId: price.id,
|
|
118
123
|
unitAmount: price.unit_amount,
|
|
119
124
|
currency: price.currency,
|
|
120
|
-
interval:
|
|
121
|
-
|
|
125
|
+
interval: knownInterval,
|
|
126
|
+
// Kept consistent with `interval`: a count without an interval would read as a half-recurring price.
|
|
127
|
+
intervalCount: knownInterval ? (price.recurring?.interval_count ?? null) : null,
|
|
122
128
|
active: price.active,
|
|
123
129
|
metadata: price.metadata ?? {},
|
|
124
130
|
};
|
|
@@ -162,11 +168,23 @@ async function resolvePricesViaCache(runtime, cache, ctx, priceIds) {
|
|
|
162
168
|
}
|
|
163
169
|
}
|
|
164
170
|
export function createStripeRetrievePrices(runtime, cache) {
|
|
171
|
+
const warnedUnknownIntervalPriceIds = new Set();
|
|
165
172
|
return async (ctx, priceIds) => {
|
|
166
173
|
await resolvePricesViaCache(runtime, cache, ctx, priceIds);
|
|
167
174
|
return priceIds.flatMap((priceId) => {
|
|
168
175
|
const price = cache.get(priceId);
|
|
169
|
-
|
|
176
|
+
if (!price)
|
|
177
|
+
return [];
|
|
178
|
+
const mapped = mapStripePrice(price);
|
|
179
|
+
const rawInterval = price.recurring?.interval;
|
|
180
|
+
if (rawInterval && mapped.interval === null && !warnedUnknownIntervalPriceIds.has(priceId)) {
|
|
181
|
+
warnedUnknownIntervalPriceIds.add(priceId);
|
|
182
|
+
ctx.log?.warn("subscription-stripe: price has an unknown recurring interval", {
|
|
183
|
+
priceId,
|
|
184
|
+
interval: rawInterval,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
return [mapped];
|
|
170
188
|
});
|
|
171
189
|
};
|
|
172
190
|
}
|
|
@@ -175,7 +193,7 @@ function resolveProductId(product) {
|
|
|
175
193
|
return typeof product === "string" ? product : product.id;
|
|
176
194
|
}
|
|
177
195
|
/** Both our own pre-Stripe-call product+interval collision check and a live
|
|
178
|
-
* `StripeInvalidRequestError` from `configurations.create()
|
|
196
|
+
* `StripeInvalidRequestError` from `configurations.create()`
|
|
179
197
|
* surface the same underlying misconfiguration (two plan tiers sharing one
|
|
180
198
|
* Stripe product) — mapped to the same `UnprocessableError` so the panel
|
|
181
199
|
* only needs one i18nKey to translate. */
|
|
@@ -336,8 +354,9 @@ export function createStripePlanSwitchSession(runtime, cache, portalConfigCache)
|
|
|
336
354
|
// out-of-band) must not poison every subsequent switch for the same
|
|
337
355
|
// price-set — evict it so the next call re-searches/re-creates.
|
|
338
356
|
portalConfigCache.delete(hash);
|
|
339
|
-
|
|
340
|
-
|
|
357
|
+
// Not mapped to plan_tiers_share_product: a sessions.create error under
|
|
358
|
+
// flow_data.subscription_update_confirm.* is a different problem (inactive
|
|
359
|
+
// target price, wrong item) and mapping it would hide the real cause.
|
|
341
360
|
throw error;
|
|
342
361
|
}
|
|
343
362
|
};
|
|
@@ -33,9 +33,11 @@
|
|
|
33
33
|
// redelivers.
|
|
34
34
|
import { BillingEventKinds, SubscriptionEventTypes, SubscriptionStatuses, } from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
|
|
35
35
|
import { ExtraRouteRejection } from "@cosmicdrift/kumiko-framework/api";
|
|
36
|
+
import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
|
|
36
37
|
import Stripe from "stripe";
|
|
37
38
|
import { Temporal } from "temporal-polyfill";
|
|
38
39
|
import { STRIPE_PROVIDER_NAME, StripeEventTypes } from "./constants.js";
|
|
40
|
+
const log = createFallbackLogger("subscription-stripe");
|
|
39
41
|
export function isResourceMissingStripeError(error) {
|
|
40
42
|
return (error instanceof Stripe.errors.StripeInvalidRequestError && error.code === "resource_missing");
|
|
41
43
|
}
|
|
@@ -259,6 +261,12 @@ function isCheckoutSessionEventType(stripeType) {
|
|
|
259
261
|
return (stripeType === StripeEventTypes.checkoutSessionCompleted ||
|
|
260
262
|
stripeType === StripeEventTypes.checkoutSessionAsyncPaymentSucceeded);
|
|
261
263
|
}
|
|
264
|
+
// The session is already paid, so a silent drop means money collected with no
|
|
265
|
+
// credit — warn (ids and reason only, no PII) so operators can spot it.
|
|
266
|
+
function dropPaidSession(event, session, reason) {
|
|
267
|
+
log.warn("paid checkout session dropped", { eventId: event.id, sessionId: session.id, reason });
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
262
270
|
/** Parses a checkout.session.completed / .async_payment_succeeded event into
|
|
263
271
|
* a PaymentEvent, or null if it isn't a paid one-off-payment session. */
|
|
264
272
|
async function parsePaymentEvent(event, stripe) {
|
|
@@ -296,17 +304,16 @@ async function parsePaymentEvent(event, stripe) {
|
|
|
296
304
|
const paymentIntent = expanded.payment_intent;
|
|
297
305
|
if (!paymentIntent || typeof paymentIntent === "string") {
|
|
298
306
|
// Not expanded (Stripe-API-drift) or absent — no verified tenant-claim
|
|
299
|
-
// to trust.
|
|
300
|
-
|
|
301
|
-
return null;
|
|
307
|
+
// to trust.
|
|
308
|
+
return dropPaidSession(event, session, "payment_intent_not_expanded");
|
|
302
309
|
}
|
|
303
310
|
const tenantId = paymentIntent.metadata?.["tenantId"];
|
|
304
311
|
if (!tenantId || tenantId.length === 0) {
|
|
305
|
-
return
|
|
312
|
+
return dropPaidSession(event, session, "missing_tenant_id");
|
|
306
313
|
}
|
|
307
314
|
const priceId = expanded.line_items?.data[0]?.price?.id;
|
|
308
315
|
if (!priceId) {
|
|
309
|
-
return
|
|
316
|
+
return dropPaidSession(event, session, "missing_price_id");
|
|
310
317
|
}
|
|
311
318
|
// providerCustomerId: session.customer is null for guest checkouts (no
|
|
312
319
|
// customer_creation configured) — fall back to the PaymentIntent's own
|
|
@@ -317,7 +324,7 @@ async function parsePaymentEvent(event, stripe) {
|
|
|
317
324
|
? paymentIntent.customer
|
|
318
325
|
: paymentIntent.customer?.id);
|
|
319
326
|
if (!providerCustomerId) {
|
|
320
|
-
return
|
|
327
|
+
return dropPaidSession(event, session, "missing_customer");
|
|
321
328
|
}
|
|
322
329
|
return {
|
|
323
330
|
kind: BillingEventKinds.payment,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NotFoundError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
2
|
-
import {
|
|
2
|
+
import { denyUnlessJoinRowParentVisible } from "../../shared/index.js";
|
|
3
3
|
import { tagAssignmentAggregateId } from "../aggregate-id.js";
|
|
4
4
|
import { DEFAULT_TAG_ACCESS } from "../constants.js";
|
|
5
5
|
import { tagAssignmentExecutor, tagExecutor } from "../executor.js";
|
|
@@ -33,15 +33,9 @@ export function createAssignTagHandler(access = DEFAULT_TAG_ACCESS) {
|
|
|
33
33
|
description: "Attaches an existing catalog tag to one host entity addressed by its type and id, reporting success when the tag was already attached; use it to tag a record, not to create the tag.",
|
|
34
34
|
handler: async (event, ctx) => {
|
|
35
35
|
const payload = event.payload; // @cast-boundary engine-payload
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
// Checked FIRST, ahead of every lookup below, so a denied caller can't tell
|
|
40
|
-
// an invisible parent apart from a missing assignment or an unknown tag —
|
|
41
|
-
// every path answers with the same NotFoundError.
|
|
42
|
-
if (!(await joinRowParentIsVisible(ctx.registry, "tag-assignment", payload, event.user, ctx.db))) {
|
|
43
|
-
return writeFailure(new NotFoundError(payload.entityType, payload.entityId));
|
|
44
|
-
}
|
|
36
|
+
const denied = await denyUnlessJoinRowParentVisible(ctx.registry, "tag-assignment", payload, event.user, ctx.db);
|
|
37
|
+
if (denied)
|
|
38
|
+
return denied;
|
|
45
39
|
const id = tagAssignmentAggregateId(event.user.tenantId, payload.tagId, payload.entityType, payload.entityId);
|
|
46
40
|
const existing = await tagAssignmentExecutor.detail({ id }, event.user, ctx.db);
|
|
47
41
|
if (existing) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { joinRowParentIsVisible } from "../../shared/index.js";
|
|
1
|
+
import { denyUnlessJoinRowParentVisible } from "../../shared/index.js";
|
|
3
2
|
import { tagAssignmentAggregateId } from "../aggregate-id.js";
|
|
4
3
|
import { DEFAULT_TAG_ACCESS } from "../constants.js";
|
|
5
4
|
import { tagAssignmentExecutor } from "../executor.js";
|
|
@@ -19,15 +18,9 @@ export function createRemoveTagHandler(access = DEFAULT_TAG_ACCESS) {
|
|
|
19
18
|
description: "Detaches one catalog tag from one host entity, reporting success when it was not attached; use it to untag a single record while the tag itself stays in the catalog.",
|
|
20
19
|
handler: async (event, ctx) => {
|
|
21
20
|
const payload = event.payload; // @cast-boundary engine-payload
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// Checked FIRST, ahead of every lookup below, so a denied caller can't tell
|
|
26
|
-
// an invisible parent apart from a missing assignment or an unknown tag —
|
|
27
|
-
// every path answers with the same NotFoundError.
|
|
28
|
-
if (!(await joinRowParentIsVisible(ctx.registry, "tag-assignment", payload, event.user, ctx.db))) {
|
|
29
|
-
return writeFailure(new NotFoundError(payload.entityType, payload.entityId));
|
|
30
|
-
}
|
|
21
|
+
const denied = await denyUnlessJoinRowParentVisible(ctx.registry, "tag-assignment", payload, event.user, ctx.db);
|
|
22
|
+
if (denied)
|
|
23
|
+
return denied;
|
|
31
24
|
const id = tagAssignmentAggregateId(event.user.tenantId, payload.tagId, payload.entityType, payload.entityId);
|
|
32
25
|
const existing = await tagAssignmentExecutor.detail({ id }, event.user, ctx.db);
|
|
33
26
|
if (!existing) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import type { SessionUser, TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import type { Temporal } from "temporal-polyfill";
|
|
3
4
|
import type { ContentFormat, TemplateKind } from "./constants.js";
|
|
4
5
|
export type TemplateResource = {
|
|
5
6
|
readonly id: string;
|
|
@@ -17,7 +18,7 @@ export type TemplateResource = {
|
|
|
17
18
|
readonly scope: "system" | "tenant";
|
|
18
19
|
readonly parentTemplateId: string | null;
|
|
19
20
|
readonly status: "draft" | "active" | "archived";
|
|
20
|
-
readonly
|
|
21
|
+
readonly modifiedAt: Temporal.Instant | null;
|
|
21
22
|
};
|
|
22
23
|
export type ResolveRequest = {
|
|
23
24
|
readonly tenantId: TenantId;
|
|
@@ -10,5 +10,5 @@ export declare const bySlugQuery: import("@cosmicdrift/kumiko-types/define-handl
|
|
|
10
10
|
content: string | null;
|
|
11
11
|
contentFormat: string;
|
|
12
12
|
folder: string | null;
|
|
13
|
-
|
|
13
|
+
modifiedAt: import("temporal-spec").Temporal.Instant | null;
|
|
14
14
|
} | null, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
@@ -24,7 +24,7 @@ export const bySlugQuery = defineQueryHandler({
|
|
|
24
24
|
tenantIdOverride: z.string().min(1).optional(),
|
|
25
25
|
}),
|
|
26
26
|
access: { roles: ["anonymous", "User", "TenantAdmin", "SystemAdmin"] },
|
|
27
|
-
rateLimit: { per: "ip", limit: 60, windowSeconds: 60 },
|
|
27
|
+
rateLimit: { per: "ip+handler", limit: 60, windowSeconds: 60 },
|
|
28
28
|
description: "Reads one text-block of a tenant by slug and locale together with its body and format; it is pinned to the text-block kind so mail templates and AI prompts in the same table stay unreachable through this anonymous-capable path.",
|
|
29
29
|
handler: async (query, ctx) => {
|
|
30
30
|
const override = query.payload.tenantIdOverride;
|
|
@@ -47,7 +47,7 @@ export const bySlugQuery = defineQueryHandler({
|
|
|
47
47
|
content: row.content,
|
|
48
48
|
contentFormat: row.contentFormat,
|
|
49
49
|
folder: row.folder,
|
|
50
|
-
|
|
50
|
+
modifiedAt: row.modifiedAt,
|
|
51
51
|
};
|
|
52
52
|
},
|
|
53
53
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Temporal } from "temporal-polyfill";
|
|
1
2
|
import * as z from "zod";
|
|
2
3
|
export type TextBlockSummary = {
|
|
3
4
|
readonly slug: string;
|
|
@@ -5,7 +6,7 @@ export type TextBlockSummary = {
|
|
|
5
6
|
readonly title: string | null;
|
|
6
7
|
readonly content: string | null;
|
|
7
8
|
readonly folder: string | null;
|
|
8
|
-
readonly
|
|
9
|
+
readonly modifiedAt: Temporal.Instant | null;
|
|
9
10
|
};
|
|
10
11
|
export declare const byTenantQuery: import("@cosmicdrift/kumiko-types/define-handler").QueryHandlerDefinition<"by-tenant", z.ZodObject<{
|
|
11
12
|
tenantIdOverride: z.ZodOptional<z.ZodString>;
|
|
@@ -16,6 +17,6 @@ export declare const byTenantQuery: import("@cosmicdrift/kumiko-types/define-han
|
|
|
16
17
|
title: string | null;
|
|
17
18
|
content: string | null;
|
|
18
19
|
folder: string | null;
|
|
19
|
-
|
|
20
|
+
modifiedAt: Temporal.Instant | null;
|
|
20
21
|
}[];
|
|
21
22
|
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
@@ -11,7 +11,7 @@ export const byTenantQuery = defineQueryHandler({
|
|
|
11
11
|
tenantIdOverride: z.string().min(1).optional(),
|
|
12
12
|
}),
|
|
13
13
|
access: { roles: ["anonymous", "User", "TenantAdmin", "SystemAdmin"] },
|
|
14
|
-
rateLimit: { per: "ip", limit: 60, windowSeconds: 60 },
|
|
14
|
+
rateLimit: { per: "ip+handler", limit: 60, windowSeconds: 60 },
|
|
15
15
|
description: "Lists every text-block of a tenant with slug, locale, title and body so a public content tree can be rendered in one call; use it for the whole sidebar, and by-slug when only one block is needed.",
|
|
16
16
|
handler: async (query, ctx) => {
|
|
17
17
|
const override = query.payload.tenantIdOverride;
|
|
@@ -27,7 +27,7 @@ export const byTenantQuery = defineQueryHandler({
|
|
|
27
27
|
title: row.title,
|
|
28
28
|
content: row.content,
|
|
29
29
|
folder: row.folder,
|
|
30
|
-
|
|
30
|
+
modifiedAt: row.modifiedAt,
|
|
31
31
|
})),
|
|
32
32
|
};
|
|
33
33
|
},
|
|
@@ -11,5 +11,5 @@ export declare function makeCollectionItemQuery(collection: ContentCollectionDef
|
|
|
11
11
|
content: string | null;
|
|
12
12
|
contentFormat: string;
|
|
13
13
|
folder: string | null;
|
|
14
|
-
|
|
14
|
+
modifiedAt: import("temporal-spec").Temporal.Instant | null;
|
|
15
15
|
} | null, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
@@ -10,6 +10,6 @@ export declare function makeCollectionListQuery(collection: ContentCollectionDef
|
|
|
10
10
|
content: string | null;
|
|
11
11
|
contentFormat: string;
|
|
12
12
|
folder: string | null;
|
|
13
|
-
|
|
13
|
+
modifiedAt: import("temporal-spec").Temporal.Instant | null;
|
|
14
14
|
}[];
|
|
15
15
|
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { EventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import type { AccessRule, ContentCollectionDefinition, SessionUser, TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import type { Temporal } from "temporal-polyfill";
|
|
3
4
|
import { templateResourcesTable } from "../table.js";
|
|
4
5
|
import { userContentEntriesTable } from "../user-content-table.js";
|
|
5
6
|
export declare const DEFAULT_COLLECTION_ACCESS: AccessRule;
|
|
@@ -12,7 +13,7 @@ export type CollectionEntryRow = {
|
|
|
12
13
|
readonly content: string | null;
|
|
13
14
|
readonly contentFormat: string;
|
|
14
15
|
readonly folder: string | null;
|
|
15
|
-
readonly
|
|
16
|
+
readonly modifiedAt: Temporal.Instant | null;
|
|
16
17
|
};
|
|
17
18
|
export type CollectionStore = {
|
|
18
19
|
readonly table: typeof templateResourcesTable | typeof userContentEntriesTable;
|
|
@@ -30,5 +31,5 @@ export declare function toCollectionEntry(row: CollectionEntryRow): {
|
|
|
30
31
|
content: string | null;
|
|
31
32
|
contentFormat: string;
|
|
32
33
|
folder: string | null;
|
|
33
|
-
|
|
34
|
+
modifiedAt: Temporal.Instant | null;
|
|
34
35
|
};
|
|
@@ -15,5 +15,5 @@ export declare const findByIdQuery: import("@cosmicdrift/kumiko-types/define-han
|
|
|
15
15
|
scope: string;
|
|
16
16
|
parentTemplateId: string | null;
|
|
17
17
|
status: string;
|
|
18
|
-
|
|
18
|
+
modifiedAt: import("temporal-spec").Temporal.Instant | null;
|
|
19
19
|
} | null, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
@@ -22,5 +22,5 @@ export declare const listQuery: import("@cosmicdrift/kumiko-types/define-handler
|
|
|
22
22
|
scope: string;
|
|
23
23
|
status: string;
|
|
24
24
|
contentFormat: string;
|
|
25
|
-
|
|
25
|
+
modifiedAt: import("temporal-spec").Temporal.Instant | null;
|
|
26
26
|
}[], import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|