@cosmicdrift/kumiko-bundled-features 0.339.0 → 0.341.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/auth-email-password/bootstrap.d.ts +74 -0
- package/dist/auth-email-password/bootstrap.js +165 -0
- package/dist/auth-email-password/constants.d.ts +2 -0
- package/dist/auth-email-password/constants.js +5 -0
- package/dist/auth-email-password/errors.d.ts +1 -0
- package/dist/auth-email-password/errors.js +6 -0
- package/dist/auth-email-password/feature.js +2 -1
- package/dist/auth-email-password/handlers/invite-accept-with-login.write.js +9 -1
- package/dist/auth-email-password/handlers/invite-accept.write.js +8 -0
- package/dist/auth-email-password/handlers/invite-create.write.d.ts +11 -0
- package/dist/auth-email-password/handlers/invite-create.write.js +134 -87
- package/dist/auth-email-password/handlers/invite-signup-complete.write.js +5 -1
- package/dist/auth-email-password/handlers/signup-request.write.js +1 -0
- package/dist/auth-email-password/handlers/token-request-handler.js +1 -0
- package/dist/auth-email-password/index.d.ts +2 -0
- package/dist/auth-email-password/index.js +1 -0
- package/dist/auth-email-password/invite-token-store.d.ts +3 -0
- package/dist/auth-email-password/invite-token-store.js +5 -0
- package/dist/auth-email-password/magic-link-mail.js +1 -0
- package/dist/auth-mfa/config.d.ts +1 -1
- package/dist/auth-mfa/config.js +8 -3
- package/dist/auth-mfa/feature.d.ts +2 -0
- package/dist/auth-mfa/feature.js +2 -1
- package/dist/auth-mfa/mfa-status-checker.d.ts +2 -0
- package/dist/auth-mfa/mfa-status-checker.js +1 -1
- package/dist/billing-foundation/consumer-protection/issue-confirmation.js +1 -0
- package/dist/billing-foundation/consumer-protection/termination-mail.js +1 -0
- package/dist/billing-foundation/consumer-protection/termination-notify.js +1 -0
- package/dist/billing-foundation/consumer-protection/termination-pages.d.ts +8 -1
- package/dist/billing-foundation/consumer-protection/termination-pages.js +59 -32
- package/dist/billing-foundation/consumer-protection/termination-request.js +2 -0
- package/dist/billing-foundation/consumer-protection/termination-texts.d.ts +1 -0
- package/dist/billing-foundation/consumer-protection/termination-texts.js +3 -1
- package/dist/billing-foundation/projection.js +23 -40
- package/dist/channel-discord/discord-channel.d.ts +4 -0
- package/dist/channel-discord/discord-channel.js +4 -0
- package/dist/channel-discord/feature.js +7 -2
- package/dist/channel-discord/index.d.ts +1 -1
- package/dist/channel-discord/index.js +1 -1
- package/dist/channel-email/email-channel.js +1 -0
- package/dist/channel-slack/feature.js +4 -2
- package/dist/channel-slack/index.d.ts +1 -1
- package/dist/channel-slack/index.js +1 -1
- package/dist/channel-slack/slack-channel.d.ts +4 -0
- package/dist/channel-slack/slack-channel.js +4 -0
- package/dist/channel-teams/feature.js +4 -2
- package/dist/channel-teams/index.d.ts +1 -1
- package/dist/channel-teams/index.js +1 -1
- package/dist/channel-teams/teams-channel.d.ts +4 -0
- package/dist/channel-teams/teams-channel.js +4 -0
- package/dist/channel-telegram/feature.js +4 -2
- package/dist/channel-telegram/index.d.ts +1 -1
- package/dist/channel-telegram/index.js +1 -1
- package/dist/channel-telegram/telegram-channel.d.ts +8 -0
- package/dist/channel-telegram/telegram-channel.js +28 -12
- package/dist/delivery/chat-webhook-channel.d.ts +5 -1
- package/dist/delivery/chat-webhook-channel.js +10 -5
- package/dist/delivery/chat-webhook-sender.d.ts +6 -2
- package/dist/delivery/chat-webhook-sender.js +13 -8
- package/dist/delivery/delivery-service.d.ts +4 -3
- package/dist/delivery/delivery-service.js +53 -33
- package/dist/delivery/index.d.ts +2 -2
- package/dist/delivery/index.js +2 -2
- package/dist/delivery/jobs.js +3 -1
- package/dist/delivery/types.d.ts +5 -3
- package/dist/inbound-mail-foundation/projection.js +37 -45
- package/dist/legal-pages/feature.d.ts +3 -15
- package/dist/legal-pages/feature.js +20 -50
- package/dist/legal-pages/lib/boot-check.d.ts +10 -0
- package/dist/legal-pages/lib/boot-check.js +33 -0
- package/dist/legal-pages/lib/validate-routes.d.ts +2 -0
- package/dist/legal-pages/lib/validate-routes.js +17 -0
- package/dist/page-render/index.d.ts +2 -1
- package/dist/page-render/index.js +1 -0
- package/dist/page-render/layout.d.ts +7 -0
- package/dist/page-render/layout.js +5 -0
- package/dist/page-render/trailing-slash.d.ts +2 -0
- package/dist/page-render/trailing-slash.js +15 -0
- package/dist/renderer-simple/feature.js +1 -0
- package/dist/renderer-simple/index.d.ts +1 -1
- package/dist/renderer-simple/index.js +1 -1
- package/dist/renderer-simple/simple-renderer.d.ts +12 -5
- package/dist/renderer-simple/simple-renderer.js +66 -12
- package/dist/secrets/announce-secret-change.d.ts +8 -0
- package/dist/secrets/announce-secret-change.js +18 -0
- package/dist/secrets/feature.js +1 -1
- package/dist/secrets/handlers/delete.write.js +2 -0
- package/dist/secrets/handlers/set.write.js +6 -1
- package/dist/secrets/i18n.d.ts +4 -0
- package/dist/secrets/i18n.js +4 -0
- package/dist/secrets/secrets-context.d.ts +2 -0
- package/dist/secrets/secrets-context.js +3 -0
- package/dist/secrets/write-gate.d.ts +5 -1
- package/dist/secrets/write-gate.js +24 -1
- package/dist/tenant/constants.d.ts +1 -0
- package/dist/tenant/constants.js +1 -0
- package/dist/tenant/handlers/add-member.write.js +14 -3
- package/dist/tenant/handlers/invitations.query.d.ts +8 -0
- package/dist/tenant/handlers/invitations.query.js +15 -1
- package/dist/tenant/index.d.ts +1 -0
- package/dist/tenant/index.js +1 -0
- package/dist/tenant/invitation-table.d.ts +2 -0
- package/dist/tenant/invitation-table.js +10 -1
- package/dist/tenant-lifecycle/lifecycle-gate.d.ts +1 -0
- package/dist/tenant-lifecycle/lifecycle-gate.js +2 -0
- package/dist/tenant-lifecycle/run-tenant-destroy.js +2 -2
- package/dist/tier-engine/feature.js +83 -14
- package/dist/tier-engine/handlers/set-tenant-tier.write.d.ts +3 -3
- package/dist/tier-engine/handlers/set-tenant-tier.write.js +17 -10
- package/dist/user/handlers/update-roles.js +7 -18
- package/dist/user/invited-global-roles.d.ts +14 -0
- package/dist/user/invited-global-roles.js +35 -0
- package/dist/user-data-rights/feature.js +3 -0
- package/dist/waitlist/constants.d.ts +33 -0
- package/dist/waitlist/constants.js +35 -0
- package/dist/waitlist/entity.d.ts +433 -0
- package/dist/waitlist/entity.js +64 -0
- package/dist/waitlist/feature.d.ts +3 -0
- package/dist/waitlist/feature.js +38 -0
- package/dist/waitlist/handlers/invite.write.d.ts +9 -0
- package/dist/waitlist/handlers/invite.write.js +119 -0
- package/dist/waitlist/handlers/list.query.d.ts +1 -0
- package/dist/waitlist/handlers/list.query.js +10 -0
- package/dist/waitlist/handlers/reject.write.d.ts +7 -0
- package/dist/waitlist/handlers/reject.write.js +80 -0
- package/dist/waitlist/handlers/submit.write.d.ts +13 -0
- package/dist/waitlist/handlers/submit.write.js +112 -0
- package/dist/waitlist/i18n.d.ts +6 -0
- package/dist/waitlist/i18n.js +31 -0
- package/dist/waitlist/index.d.ts +5 -0
- package/dist/waitlist/index.js +4 -0
- package/dist/waitlist/lib.d.ts +10 -0
- package/dist/waitlist/lib.js +24 -0
- package/dist/waitlist/mail.d.ts +15 -0
- package/dist/waitlist/mail.js +73 -0
- package/dist/waitlist/options.d.ts +29 -0
- package/dist/waitlist/options.js +8 -0
- package/dist/waitlist/payloads.d.ts +14 -0
- package/dist/waitlist/payloads.js +47 -0
- package/dist/waitlist/screens.d.ts +2 -0
- package/dist/waitlist/screens.js +42 -0
- package/dist/waitlist/user-data-hooks.d.ts +3 -0
- package/dist/waitlist/user-data-hooks.js +71 -0
- package/dist/workflow-runner/db/queries/due-runs.d.ts +4 -5
- package/dist/workflow-runner/db/queries/due-runs.js +5 -4
- package/dist/workflow-runner/feature.js +4 -6
- package/package.json +17 -9
- package/src/auth-email-password/changes.json +18 -0
- package/src/auth-mfa/changes.json +6 -0
- package/src/billing-foundation/changes.json +13 -0
- package/src/channel-telegram/changes.json +9 -0
- package/src/delivery/changes.json +7 -0
- package/src/files/changes.json +13 -1
- package/src/inbound-mail-foundation/changes.json +7 -0
- package/src/renderer-simple/changes.json +6 -0
- package/src/secrets/changes.json +12 -0
- package/src/tenant/changes.json +6 -0
- package/src/user/changes.json +13 -6
- package/src/waitlist/changes.json +17 -0
- package/src/workflow-runner/changes.json +7 -0
- package/dist/billing-foundation/db/queries/payment-projection.d.ts +0 -2
- package/dist/billing-foundation/db/queries/payment-projection.js +0 -13
- package/dist/billing-foundation/db/queries/subscription-projection.d.ts +0 -2
- package/dist/billing-foundation/db/queries/subscription-projection.js +0 -7
- package/dist/inbound-mail-foundation/db/queries/inbound-projections.d.ts +0 -7
- package/dist/inbound-mail-foundation/db/queries/inbound-projections.js +0 -24
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sql } from "@cosmicdrift/kumiko-framework/db";
|
|
2
|
+
import { workflowRunPendingTable } from "../../tables.js";
|
|
2
3
|
// Oldest first and capped, so a backlog after an outage drains over several
|
|
3
4
|
// ticks instead of holding one tick's `concurrency: "skip"` slot for minutes.
|
|
4
5
|
export const DUE_RUNS_BATCH_SIZE = 100;
|
|
@@ -6,7 +7,7 @@ export const DUE_RUNS_BATCH_SIZE = 100;
|
|
|
6
7
|
// (ctx.tryAppendEvent on WORKFLOW_RESUMED) — a second dispatch for the same
|
|
7
8
|
// row loses that race and no-ops. No row locks: in an autocommit statement
|
|
8
9
|
// they would be released right after the SELECT and guard nothing.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return
|
|
10
|
+
// `now()` stays the DB clock so job-host clock skew cannot mark rows due early.
|
|
11
|
+
export function selectDueWorkflowRunPending(db) {
|
|
12
|
+
return db.selectMany(workflowRunPendingTable, { wakeAt: { lt: sql `now()` } }, { orderBy: { col: "wakeAt", direction: "asc" }, limit: DUE_RUNS_BATCH_SIZE });
|
|
12
13
|
}
|
|
@@ -57,21 +57,19 @@ export const workflowRunnerFeature = defineFeature(FEATURE_NAME, (r) => {
|
|
|
57
57
|
// skip: cron fired without a perTenant fan-out tenant — nothing scoped
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
60
|
-
ctx.systemDb.assertTenantMatch(tenantId);
|
|
61
|
-
const dueRows = await selectDueWorkflowRunPending(ctx.systemDb.unsafeRaw("due-row pickup needs FOR UPDATE SKIP LOCKED raw SQL; the query filters by this job's tenantId"), // @cast-boundary db-operator — DbRunner narrows to DbConnection, jobs never run inside a DbTx
|
|
62
|
-
tenantId);
|
|
60
|
+
const dueRows = await selectDueWorkflowRunPending(ctx.systemDb.assertTenantMatch(tenantId));
|
|
63
61
|
for (const row of dueRows) {
|
|
64
62
|
try {
|
|
65
63
|
await ctx.write("workflow-runner:write:resume-run", {
|
|
66
|
-
runId: row.
|
|
67
|
-
stepIndex: row.
|
|
64
|
+
runId: row.runId,
|
|
65
|
+
stepIndex: row.stepIndex,
|
|
68
66
|
});
|
|
69
67
|
}
|
|
70
68
|
catch (error) {
|
|
71
69
|
// skip: one row's dispatch failing (infra hiccup, not a business
|
|
72
70
|
// outcome — resume-run itself never throws for expected cases)
|
|
73
71
|
// must not stop the rest of this tenant's due rows from resuming.
|
|
74
|
-
ctx.log.warn(`[workflow-runner:resume-due-runs] runId=${row.
|
|
72
|
+
ctx.log.warn(`[workflow-runner:resume-due-runs] runId=${row.runId} stepIndex=${row.stepIndex} dispatch failed: ${String(error)}`);
|
|
75
73
|
}
|
|
76
74
|
}
|
|
77
75
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-bundled-features",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.341.0",
|
|
4
4
|
"description": "Built-in features — tenant, user, auth, delivery. The stuff you'd rewrite anyway, already typed.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -465,6 +465,10 @@
|
|
|
465
465
|
"types": "./dist/legal-pages/web/index.d.ts",
|
|
466
466
|
"default": "./dist/legal-pages/web/index.js"
|
|
467
467
|
},
|
|
468
|
+
"./waitlist": {
|
|
469
|
+
"types": "./dist/waitlist/index.d.ts",
|
|
470
|
+
"default": "./dist/waitlist/index.js"
|
|
471
|
+
},
|
|
468
472
|
"./page-render": {
|
|
469
473
|
"types": "./dist/page-render/index.d.ts",
|
|
470
474
|
"default": "./dist/page-render/index.js"
|
|
@@ -499,12 +503,12 @@
|
|
|
499
503
|
}
|
|
500
504
|
},
|
|
501
505
|
"dependencies": {
|
|
502
|
-
"@cosmicdrift/kumiko-dispatcher-live": "0.
|
|
503
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
504
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
505
|
-
"@cosmicdrift/kumiko-renderer": "0.
|
|
506
|
-
"@cosmicdrift/kumiko-renderer-web": "0.
|
|
507
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
506
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.341.0",
|
|
507
|
+
"@cosmicdrift/kumiko-framework": "0.341.0",
|
|
508
|
+
"@cosmicdrift/kumiko-headless": "0.341.0",
|
|
509
|
+
"@cosmicdrift/kumiko-renderer": "0.341.0",
|
|
510
|
+
"@cosmicdrift/kumiko-renderer-web": "0.341.0",
|
|
511
|
+
"@cosmicdrift/kumiko-types": "0.341.0",
|
|
508
512
|
"@mollie/api-client": "^4.5.0",
|
|
509
513
|
"@node-rs/argon2": "^2.0.2",
|
|
510
514
|
"@types/mailparser": "^3.4.6",
|
|
@@ -975,6 +979,10 @@
|
|
|
975
979
|
"types": "./dist/legal-pages/web/index.d.ts",
|
|
976
980
|
"default": "./dist/legal-pages/web/index.js"
|
|
977
981
|
},
|
|
982
|
+
"./waitlist": {
|
|
983
|
+
"types": "./dist/waitlist/index.d.ts",
|
|
984
|
+
"default": "./dist/waitlist/index.js"
|
|
985
|
+
},
|
|
978
986
|
"./page-render": {
|
|
979
987
|
"types": "./dist/page-render/index.d.ts",
|
|
980
988
|
"default": "./dist/page-render/index.js"
|
|
@@ -1017,8 +1025,8 @@
|
|
|
1017
1025
|
],
|
|
1018
1026
|
"devDependencies": {
|
|
1019
1027
|
"@testing-library/user-event": "^14.6.1",
|
|
1020
|
-
"@cosmicdrift/kumiko-locale-de": "0.
|
|
1021
|
-
"@cosmicdrift/kumiko-locale-es": "0.
|
|
1028
|
+
"@cosmicdrift/kumiko-locale-de": "0.341.0",
|
|
1029
|
+
"@cosmicdrift/kumiko-locale-es": "0.341.0",
|
|
1022
1030
|
"jsqr": "^1.4.0"
|
|
1023
1031
|
}
|
|
1024
1032
|
}
|
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.341.0",
|
|
4
|
+
"type": "breaking",
|
|
5
|
+
"title": "Invitations can carry a global role (SystemAdmin), granted only on accept",
|
|
6
|
+
"detail": "New system-only handler auth-email-password:write:system-invite-create and bootstrapTenants() for idempotent passwordless provisioning. Tenant admins cannot set global roles; a re-invite clears a pending one.",
|
|
7
|
+
"migration": "New column read_tenant_invitations.global_roles: run `kumiko schema generate` and apply the migration before deploying."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"version": "0.341.0",
|
|
11
|
+
"type": "improvement",
|
|
12
|
+
"title": "runBootstrap applies per-tenant config",
|
|
13
|
+
"detail": "BootstrapTenant.config sets tenant-scope config keys as system writes on every run; a value that already matches is not rewritten."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"version": "0.341.0",
|
|
17
|
+
"type": "fix",
|
|
18
|
+
"title": "System invite refuses to reset an accepted invitation (invite_already_accepted)"
|
|
19
|
+
},
|
|
2
20
|
{
|
|
3
21
|
"version": "0.339.0",
|
|
4
22
|
"type": "improvement",
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.341.0",
|
|
4
|
+
"type": "breaking",
|
|
5
|
+
"title": "MFA required policy is writable by the system only",
|
|
6
|
+
"migration": "Tenant admins can no longer write auth-mfa:config:required (write access is now system-only). Set the app-wide default with createAuthMfaFeature({ requiredPolicy }) / the mfa options and per-tenant overrides with runBootstrap tenants[].config or a system write (createSystemUser). Code that wrote the key as TenantAdmin/Admin/SystemAdmin over HTTP must switch to one of these."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.335.0",
|
|
4
10
|
"type": "fix",
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.341.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "App layout around termination pages, trailing-slash redirects, receipt greeting",
|
|
6
|
+
"detail": "`createContractTerminationRoutes` accepts a `wrapLayout`, the same function `createLegalPagesFeature` takes. It wraps every termination page (form, review, result, 429, error); the security headers stay with the framework. The layout also receives `alternates`, a map from locale to the path of the same page, so it can render a language switch from plain links. `page-render` exports the shared type `PublicPageWrapLayout`; `LegalPagesWrapLayout` is now an alias of it. The page body sits in `<div data-kumiko-page=\"contract-termination\">` with `data-kumiko-*` hooks on the form, field groups, buttons and review table. The page CSP keeps `script-src 'none'`, so a layout must not rely on JavaScript.\nThe default `wrapInLayout` stylesheet gains base rules for input, select, textarea and button, so unstyled forms stay readable.\nThe termination pages and every legal page now answer the other trailing-slash form of their path (`/legal/kuendigen/`, `/legal/impressum/`) with a 301 to the configured path for GET and HEAD, query string kept. POST has no alias.\nThe termination receipt mail text changed: it now starts with a greeting line (\"Hallo,\" / \"Hello,\") and the English sentence starts capitalized (\"We confirm receipt ...\"). The receipt content still depends only on the declarant's input."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.340.0",
|
|
10
|
+
"type": "improvement",
|
|
11
|
+
"title": "Payment and subscription projections use typed bun-db helpers",
|
|
12
|
+
"detail": "`payment-received` inserts through `insertOnConflictDoNothing`, subscription events upsert through `upsertOnConflict`. The raw `db/queries/payment-projection.ts` and `subscription-projection.ts` modules are gone.",
|
|
13
|
+
"migration": "keine"
|
|
14
|
+
},
|
|
2
15
|
{
|
|
3
16
|
"version": "0.339.0",
|
|
4
17
|
"type": "improvement",
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.341.0",
|
|
4
|
+
"type": "breaking",
|
|
5
|
+
"title": "Telegram apiBaseUrl must match allowedHosts and use https",
|
|
6
|
+
"detail": "`ctx.secrets.set` now runs the same `valueSchema` check as `secrets:write:set` and throws the same validation error (`secrets.errors.invalidValue`, never carrying the value) before anything is encrypted or stored. `createSecretsContext` takes a required `registry` option that supplies the declared schemas; `runProdApp`, `runDevApp` and `setupTestStack` setups pass the booted registry.\n`createChannelTelegramFeature` takes `allowedHosts` (default `[\"api.telegram.org\"]`) and `requireHttps` (default `true`), like the other chat channels. Boot fails when the host of `apiBaseUrl` is not in `allowedHosts` or the URL is not https while `requireHttps` is on. Before, the host of `apiBaseUrl` was its own allowlist and https was only required when the URL already started with `https:`.",
|
|
7
|
+
"migration": "An app that sets a custom apiBaseUrl (self-hosted Bot API server, local test server) must also pass allowedHosts: [\"<host>\"], plus requireHttps: false for plain http. Without it the app fails at boot."
|
|
8
|
+
}
|
|
9
|
+
]
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.341.0",
|
|
4
|
+
"type": "breaking",
|
|
5
|
+
"title": "Chat channels deliver in production boot, ctx.notify returns a NotifyResult",
|
|
6
|
+
"detail": "Chat channels (slack, discord, teams, telegram) now deliver in production boot. `runProdApp`, `runDevApp` and `runWorkerApp` pass the tenant secrets to the delivery service and hand queued channels to the `delivery.render`/`delivery.send` jobs of the calling context's job runner. `delivery.render` now receives `ctx.secrets` as well. Without a job runner, queued channels still deliver inline, now with secrets. `runBootstrap` is a one-shot process whose queue nobody drains after it exits, so it keeps delivering queued channels inline (the SystemAdmin invitation goes out before the process ends).\n`NotifyFn` returns a `NotifyResult` (`{ deliveries }` with channel, recipientId, status `queued | sent | failed | skipped`, error and `deliveryAttemptId` per delivery) instead of `void`. `NotifyOptions.immediate` delivers queued channels inline for one call and bypasses job retry, for \"send test message\" handlers. `NotifyFactory` takes an optional job dispatcher as third argument, and `DeliveryService.notify` an optional per-call dispatcher.\nConsumers: `NotifyFn` implementations in tests and mocks must now return a `NotifyResult`, e.g. `async () => ({ deliveries: [] })`. When running the API without a worker (`runSingleInstance: false`), a dedicated worker must process the delivery jobs.",
|
|
7
|
+
"migration": "NotifyFn implementations in tests and mocks must return a NotifyResult, e.g. async () => ({ deliveries: [] }). Queued channels now run through the delivery jobs in production; an API-only deployment (runSingleInstance: false) needs a worker that mounts delivery and the channel features."
|
|
8
|
+
},
|
|
2
9
|
{
|
|
3
10
|
"version": "0.339.0",
|
|
4
11
|
"type": "improvement",
|
package/src/files/changes.json
CHANGED
|
@@ -1 +1,13 @@
|
|
|
1
|
-
[
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.341.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "Uploads resolve a generic content type from the file content",
|
|
6
|
+
"detail": "POST /api/files now resolves application/octet-stream or a missing type from the bytes (PDF, XML, PNG, JPEG, GIF, WebP, ZIP) and falls back to the file extension. A concrete declared type is never overridden. The shared helper resolveContentType is exported from the files module."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.341.0",
|
|
10
|
+
"type": "fix",
|
|
11
|
+
"title": "Uploads without entityId no longer produce an empty storage key segment"
|
|
12
|
+
}
|
|
13
|
+
]
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.340.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "Inbound mail projections use typed bun-db helpers",
|
|
6
|
+
"detail": "Mail-account and mail-thread events upsert through `upsertOnConflict` (`connectedAt` is only written on insert), inbound messages insert through `insertOnConflictDoNothing`. The raw `db/queries/inbound-projections.ts` module is gone.",
|
|
7
|
+
"migration": "keine"
|
|
8
|
+
},
|
|
2
9
|
{
|
|
3
10
|
"version": "0.339.0",
|
|
4
11
|
"type": "improvement",
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.341.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "Localized mail branding, locale through delivery, logoPath",
|
|
6
|
+
"detail": "`ctx.notify` accepts `locale`, and the locale now travels through the delivery jobs (`delivery.render`, `delivery.send`) to `NotificationRenderer.render`, where `RendererInput.locale` is available. Jobs queued before this release have no locale and keep working. The email channel and `renderer-simple` pass it on; the auth mails and the consumer-protection mails set it to the language they already translate their content into. This lands in the same release as the `NotifyOptions` change from #3508.\n`MailBranding` for `renderer-simple` can now be localized: `footerText`, `footerLinks[].label` and `footerLinks[].url` take a plain string or a map of locale to string. The renderer picks the exact locale, then the language part (`de-AT` falls back to `de`), then the new optional `MailBranding.defaultLocale`, then the first entry. Every URL in a map must be an absolute http(s) URL, otherwise `createSimpleRenderer` throws at boot. Without a locale and with plain strings the HTML is unchanged.\n`MailBranding` also gets `logoPath` and `baseUrl`, so an app can point at a raster logo on its own origin (`logoPath: \"/logo.png\"`, `baseUrl` the same base as `auth.mail.baseUrl`). `logoPath` and `logoUrl` exclude each other, `logoPath` needs a valid `baseUrl`, must start with a single `/` and must stay on the base origin; violations throw at boot. Use PNG or JPEG, because mail clients block SVG."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.339.0",
|
|
4
10
|
"type": "improvement",
|
package/src/secrets/changes.json
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.341.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "Chat channel secret values are validated on write",
|
|
6
|
+
"detail": "`r.secret` and `r.secretNamespace` accept a `valueSchema`. `secrets:write:set` checks the value against it after the key and role checks and answers a failure with a 400 validation error on field `value` (`secrets.errors.invalidValue`); the response never contains the value or the schema's issues. `secrets:write:delete` is unaffected.\nThe chat channels use it: Slack, Discord and Teams webhook secrets must be URLs that pass the channel's host allowlist (including the Discord `/api/webhooks/` path), and the Telegram bot token must have the `<bot id>:<secret>` shape. The allowlist comes from the same options as the channel, so `createChannelSlackFeature({ allowedHosts })` also governs what can be stored.\n`channel-slack`, `channel-discord`, `channel-teams` and `channel-telegram` now export their secret keys (`SLACK_SECRET_KEYS`, `DISCORD_SECRET_KEYS`, `TEAMS_SECRET_KEYS`, `TELEGRAM_SECRET_KEYS`), allowlist constants and `isTelegramChatId` / `isTelegramBotToken`. `delivery` exports `checkChatWebhookTarget`, `chatWebhookUrlSchema` and `resolveChatWebhookTarget`, so apps can validate an address or URL when a user creates a channel.\nExisting invalid secrets stay stored; only new writes are checked. Writes through `ctx.secrets.set` in feature code run through the same check."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.341.0",
|
|
10
|
+
"type": "breaking",
|
|
11
|
+
"title": "ctx.secrets.set validates against the declared valueSchema",
|
|
12
|
+
"migration": "Code that calls createSecretsContext({ db, masterKeyProvider }) must also pass registry (the booted Registry). Programmatic ctx.secrets.set calls with a value that fails the key's valueSchema now throw a ValidationError instead of storing it."
|
|
13
|
+
},
|
|
2
14
|
{
|
|
3
15
|
"version": "0.337.0",
|
|
4
16
|
"type": "breaking",
|
package/src/tenant/changes.json
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.341.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "Harden system role, invitations query, tenant teardown and addMember",
|
|
6
|
+
"detail": "The system role can no longer be minted into a session through a user's global roles. invitations.query returns an explicit field allowlist and exposes globalRoles only to a SystemAdmin. The tenant destruction sweep waits 60 seconds after the grace period ends so the teardown gate can settle. Behaviour change: tenant:write:addMember now adds members to a foreign tenant only from the framework system context; a SystemAdmin request user is limited to their own tenant and gets 403 otherwise."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.336.1",
|
|
4
10
|
"type": "fix",
|
package/src/user/changes.json
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.340.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "The last-SystemAdmin check uses jsonb containment through selectMany",
|
|
6
|
+
"detail": "Removing the SystemAdmin role still refuses to drop the last active SystemAdmin; the count now comes from `selectMany` with a roles containment filter instead of raw SQL.",
|
|
7
|
+
"migration": "keine"
|
|
8
|
+
},
|
|
2
9
|
{
|
|
3
10
|
"version": "0.271.0",
|
|
4
11
|
"type": "improvement",
|
|
@@ -8,7 +15,7 @@
|
|
|
8
15
|
{
|
|
9
16
|
"version": "0.218.1",
|
|
10
17
|
"type": "improvement",
|
|
11
|
-
"title": "user.email searchable
|
|
18
|
+
"title": "user.email searchable — reindex required after upgrade",
|
|
12
19
|
"detail": "email gained searchable for roster lookup. buildSearchDocument only runs on write; existing rows need reindexEntity(\"user\") (or the reindex job) plus Meili configure for the new attribute.",
|
|
13
20
|
"migration": "After deploying, run reindexEntity(\"user\") (or the existing reindex job) so pre-upgrade user rows appear in roster search."
|
|
14
21
|
},
|
|
@@ -16,28 +23,28 @@
|
|
|
16
23
|
"version": "0.218.0",
|
|
17
24
|
"type": "fix",
|
|
18
25
|
"title": "user-list: system-scope search; membership roles on tenants; emailVerified sort",
|
|
19
|
-
"detail": "systemScope entityList search uses SYSTEM_TENANT_ID (was session tenant
|
|
26
|
+
"detail": "systemScope entityList search uses SYSTEM_TENANT_ID (was session tenant → Meili miss/500). Tenants column shows membership roles (Offlot Demo (TenantAdmin)). emailVerified sortable; email also searchable for roster lookup. Global roles stay SystemAdmin-only — TenantAdmin/Admin live on membership (Team → Members)."
|
|
20
27
|
},
|
|
21
28
|
{
|
|
22
29
|
"version": "0.217.0",
|
|
23
30
|
"type": "improvement",
|
|
24
31
|
"title": "user-list: roles multiSelect + tenants column; roles editable as SystemAdmin (fw#2384 follow-up)",
|
|
25
|
-
"detail": "user.roles is now createMultiSelectField (jsonb string[], options=[SystemAdmin]) so entityEdit shows a real multi-select instead of a raw JSON text box
|
|
26
|
-
"migration": "Run `kumiko-schema generate` and apply
|
|
32
|
+
"detail": "user.roles is now createMultiSelectField (jsonb string[], options=[SystemAdmin]) so entityEdit shows a real multi-select instead of a raw JSON text box — SystemAdmins can promote another SystemAdmin from the UI. user-list columns gain roles + tenants; tenants is a derived field filled by the list handler from memberships (platform roster was unusable without tenant context). switch-tenant + auth-client parseRoles accept jsonb arrays (string-only deserialize dropped SystemAdmin on tenant switch). Soft-deleted users stay hidden by default.",
|
|
33
|
+
"migration": "Run `kumiko-schema generate` and apply — migrates read_users.roles from text to jsonb USING roles::jsonb. Existing JSON array strings (`[]`, `[\"SystemAdmin\"]`) convert cleanly. Consumers that wrote roles as a JSON-encoded string via user:create/update should pass string[] instead (parseRoles still accepts both on read)."
|
|
27
34
|
},
|
|
28
35
|
{
|
|
29
36
|
"version": "0.167.1",
|
|
30
37
|
"type": "breaking",
|
|
31
38
|
"title": "user.locale no longer defaults to \"de\" (fw#1637).",
|
|
32
39
|
"detail": "The entity-level default contradicted tenant-settings' own \"en\" default and silently overrode any app or tenant locale configuration for every new user. locale now stays unset until the client or a resolution chain provides one. Consumers that already fall back with `user.locale ?? \"en\"` are unaffected in shape but now see null instead of \"de\".",
|
|
33
|
-
"migration": "Run `kumiko-schema generate` and apply
|
|
40
|
+
"migration": "Run `kumiko-schema generate` and apply — the migration emits `ALTER TABLE read_users ALTER COLUMN locale DROP DEFAULT`. Note that DROP DEFAULT only affects future inserts: existing rows keep the \"de\" the old default wrote, which no user ever chose. Your app therefore splits into pre-bump users on \"de\" and post-bump users on null, and each group resolves to a different language. Decide deliberately: either keep the old values (and state that pre-bump users stay German), or run `UPDATE read_users SET locale = NULL WHERE locale = 'de'` once so every user follows the same chain — the latter only if no UI ever let users edit the field, otherwise it erases real choices. If your app relied on the implicit German default, set an explicit fallback instead (fw#1653)."
|
|
34
41
|
},
|
|
35
42
|
{
|
|
36
43
|
"version": "0.167.1",
|
|
37
44
|
"type": "improvement",
|
|
38
45
|
"title": "user.timezone field, wired into ctx.tz (fw#1636).",
|
|
39
46
|
"detail": "The user entity gains a `timezone` field (createTzField, no default). buildHandlerContext passes it to ctx.tz.user; the fallback chain stays tenant.timezone, then \"UTC\". Before this, ctx.tz.tenant and ctx.tz.user both hardcoded \"UTC\" while the plumbing already accepted per-request values.",
|
|
40
|
-
"migration": "Run `kumiko-schema generate` and apply
|
|
47
|
+
"migration": "Run `kumiko-schema generate` and apply — the migration adds `read_users.timezone`. No backfill: an unset timezone falls through to the tenant's, exactly as before the field existed."
|
|
41
48
|
},
|
|
42
49
|
{
|
|
43
50
|
"version": "0.165.3",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.341.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "Waitlist submit is limited per email address"
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"version": "0.341.0",
|
|
9
|
+
"type": "improvement",
|
|
10
|
+
"title": "Add waitlist bundled feature: public signup intake with admin invite and reject, GDPR export and erasure"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"version": "0.341.0",
|
|
14
|
+
"type": "fix",
|
|
15
|
+
"title": "Waitlist re-invite and reject no longer touch an invitee who already accepted"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.340.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "The resume job reads due runs through the job's TenantDb",
|
|
6
|
+
"detail": "`selectDueWorkflowRunPending` takes a `TenantDb` and returns `{ runId, stepIndex }` rows, so the tenant filter comes from the TenantDb instead of a hand-written `WHERE tenant_id`.",
|
|
7
|
+
"migration": "keine"
|
|
8
|
+
},
|
|
2
9
|
{
|
|
3
10
|
"version": "0.339.0",
|
|
4
11
|
"type": "breaking",
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
|
-
// INSERT-once, not UPSERT: unlike subscriptionsProjectionTable (one row per
|
|
3
|
-
// tenant, state overwritten on every event), a payment row is an immutable
|
|
4
|
-
// fact keyed by paymentRowId(tenantId, providerName, providerEventId) — ON
|
|
5
|
-
// CONFLICT DO NOTHING makes a projection rebuild/replay idempotent without
|
|
6
|
-
// ever mutating an existing row.
|
|
7
|
-
export async function insertPaymentProjectionRow(tx, tableName, insertCols) {
|
|
8
|
-
const insertKeys = Object.keys(insertCols);
|
|
9
|
-
const insertPlaceholders = insertKeys.map((_, i) => `$${i + 1}`);
|
|
10
|
-
const sqlText = `INSERT INTO "${tableName}" (${insertKeys.map((k) => `"${k}"`).join(", ")}) VALUES (${insertPlaceholders.join(", ")}) ON CONFLICT ("id") DO NOTHING`;
|
|
11
|
-
// kumiko-lint-ignore raw-sql tableName + column keys are code-controlled, not user input: tableName is buildEntityTable()'s static table name and insertKeys come from applyPaymentReceived's hardcoded object-literal columns in projection.ts — only values flow through $1..$n
|
|
12
|
-
await asRawClient(tx).unsafe(sqlText, Object.values(insertCols));
|
|
13
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
|
-
export async function upsertSubscriptionProjectionRow(tx, tableName, insertCols, setClauses, params) {
|
|
3
|
-
const insertKeys = Object.keys(insertCols);
|
|
4
|
-
const insertPlaceholders = insertKeys.map((_, i) => `$${i + 1}`);
|
|
5
|
-
const sqlText = `INSERT INTO "${tableName}" (${insertKeys.map((k) => `"${k}"`).join(", ")}) VALUES (${insertPlaceholders.join(", ")}) ON CONFLICT ("id") DO UPDATE SET ${setClauses.join(", ")}`;
|
|
6
|
-
await asRawClient(tx).unsafe(sqlText, params);
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { DbRunner } from "@cosmicdrift/kumiko-framework/db";
|
|
2
|
-
/** INSERT ... ON CONFLICT ("id") DO UPDATE SET <setClauses>. */
|
|
3
|
-
export declare function upsertProjectionRow(tx: DbRunner, tableName: string, insertCols: Record<string, unknown>, setClauses: readonly string[], params: readonly unknown[]): Promise<void>;
|
|
4
|
-
/** INSERT ... ON CONFLICT ("id") DO NOTHING — für append-only-Rows
|
|
5
|
-
* (inbound-message: genau EIN received-event pro Stream, Replays
|
|
6
|
-
* no-op'en auf der PK). */
|
|
7
|
-
export declare function insertIgnoreProjectionRow(tx: DbRunner, tableName: string, insertCols: Record<string, unknown>): Promise<void>;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
// Raw-SQL-Helper für die inline projections — Muster
|
|
2
|
-
// billing-foundation/db/queries/subscription-projection.ts.
|
|
3
|
-
//
|
|
4
|
-
// Warum raw statt drizzle-insert: die apply-fns laufen in der event-TX
|
|
5
|
-
// mit einem DbRunner, und die ON-CONFLICT-Semantik (UPSERT vs.
|
|
6
|
-
// DO NOTHING) ist der fachliche Kern — explizit hingeschrieben statt
|
|
7
|
-
// hinter einem ORM-Builder versteckt.
|
|
8
|
-
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
9
|
-
/** INSERT ... ON CONFLICT ("id") DO UPDATE SET <setClauses>. */
|
|
10
|
-
export async function upsertProjectionRow(tx, tableName, insertCols, setClauses, params) {
|
|
11
|
-
const insertKeys = Object.keys(insertCols);
|
|
12
|
-
const insertPlaceholders = insertKeys.map((_, i) => `$${i + 1}`);
|
|
13
|
-
const sqlText = `INSERT INTO "${tableName}" (${insertKeys.map((k) => `"${k}"`).join(", ")}) VALUES (${insertPlaceholders.join(", ")}) ON CONFLICT ("id") DO UPDATE SET ${setClauses.join(", ")}`;
|
|
14
|
-
await asRawClient(tx).unsafe(sqlText, params);
|
|
15
|
-
}
|
|
16
|
-
/** INSERT ... ON CONFLICT ("id") DO NOTHING — für append-only-Rows
|
|
17
|
-
* (inbound-message: genau EIN received-event pro Stream, Replays
|
|
18
|
-
* no-op'en auf der PK). */
|
|
19
|
-
export async function insertIgnoreProjectionRow(tx, tableName, insertCols) {
|
|
20
|
-
const insertKeys = Object.keys(insertCols);
|
|
21
|
-
const insertPlaceholders = insertKeys.map((_, i) => `$${i + 1}`);
|
|
22
|
-
const sqlText = `INSERT INTO "${tableName}" (${insertKeys.map((k) => `"${k}"`).join(", ")}) VALUES (${insertPlaceholders.join(", ")}) ON CONFLICT ("id") DO NOTHING`;
|
|
23
|
-
await asRawClient(tx).unsafe(sqlText, Object.values(insertCols));
|
|
24
|
-
}
|