create-kumiko-app 0.366.0 → 0.368.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.
@@ -407,7 +407,7 @@
407
407
  },
408
408
  {
409
409
  "name": "channel-email",
410
- "description": "Wires an `EmailTransport` (typically `mail-transport-smtp` in production, `createInMemoryTransport()` in tests) into the delivery system as the `email` channel. Requires `delivery`; pass an `EmailChannelOptions` with a `transport` and a `resolveEmail` function that maps a user ID to their email address. Mails render from stored templates (`renderer-mail-html`): a notification routed to this channel needs `template` + `meta` on `r.notification`, otherwise its delivery fails.",
410
+ "description": "Wires an `EmailTransport` (typically `mail-transport-smtp` in production, `createInMemoryTransport()` in tests) into the delivery system as the `email` channel. Requires `delivery`; pass an `EmailChannelOptions` with a `transport` and a `resolveEmail` function that maps a user ID to their email address. Mails render from stored templates (`renderer-mail-html`): a notification routed to this channel needs `template` + `meta` on `r.notification`, otherwise its delivery fails. Pass `tenantTransport: tenantMailTransport` (from `mail-foundation`) to send each tenant's mail through its own configured provider: a broken provider fails the send, a tenant without one uses the app `transport`. Notifications declared with `unsubscribe: true` get a signed `data.unsubscribeUrl` from delivery, which turns into the `List-Unsubscribe` headers.",
411
411
  "toggleableDefault": null,
412
412
  "requires": [
413
413
  "delivery"
@@ -725,7 +725,7 @@
725
725
  },
726
726
  {
727
727
  "name": "delivery",
728
- "description": "The notification dispatch core: call `ctx.notify(notificationType, { to, route, data, priority, idempotencyKey })` from any handler to fan out a notification across all registered channels (email, in-app, push). Inside a write handler the delivery runs after the write has committed (the call returns `{ kind: \"deferred\" }`, a rolled-back write sends nothing); `immediate: true` delivers synchronously and returns `{ kind: \"dispatched\", deliveries }`. It stores per-user channel preferences in the `notification-preference` entity, opt-outs for no-account recipient addresses in `notification-address-opt-out` (keyed by a blind-index hash, never the plaintext address), logs every attempt to `store_delivery_attempts`, and enforces idempotency and rate-limiting — add `channel-email`, `channel-in-app`, or `channel-push` on top to actually send anything. Unsubscribe/resubscribe links are served by `createUnsubscribeRoutes({ secret })` mounted via the app's `extraRoutes` at `/api/delivery/unsubscribe` and `/api/delivery/resubscribe`: `GET /unsubscribe?token=` renders a confirmation page (no write), `POST /unsubscribe` performs the opt-out (RFC 8058 one-click, token from the form body or query), `POST /resubscribe` undoes it (JSON `{token}` or form body only, never the query — reachable by a mail-client link prefetcher) — sign links with `signUnsubscribeToken` / `signAddressUnsubscribeToken` using the same secret. `channel-email` sets `List-Unsubscribe` / `List-Unsubscribe-Post` automatically when a message's `data.unsubscribeUrl` points at the unsubscribe route.",
728
+ "description": "The notification dispatch core: call `ctx.notify(notificationType, { to, route, data, priority, idempotencyKey })` from any handler to fan out a notification across all registered channels (email, in-app, push). Inside a write handler the delivery runs after the write has committed (the call returns `{ kind: \"deferred\" }`, a rolled-back write sends nothing); `immediate: true` delivers synchronously and returns `{ kind: \"dispatched\", deliveries }`. It stores per-user channel preferences in the `notification-preference` entity, opt-outs for no-account recipient addresses in `notification-address-opt-out` (keyed by a blind-index hash, never the plaintext address), logs every attempt to `store_delivery_attempts`, and enforces idempotency and rate-limiting — add `channel-email`, `channel-in-app`, or `channel-push` on top to actually send anything. Unsubscribe/resubscribe links are served by `createUnsubscribeRoutes({ secret })` mounted via the app's `extraRoutes` at `/api/delivery/unsubscribe` and `/api/delivery/resubscribe`: `GET /unsubscribe?token=` renders a confirmation page (no write), `POST /unsubscribe` performs the opt-out (RFC 8058 one-click, token from the form body or query), `POST /resubscribe` undoes it (JSON `{token}` or form body only, never the query — reachable by a mail-client link prefetcher) — sign links with `signUnsubscribeToken` / `signAddressUnsubscribeToken` using the same secret. `channel-email` sets `List-Unsubscribe` / `List-Unsubscribe-Post` automatically when a message's `data.unsubscribeUrl` points at the unsubscribe route. A notification declared with `unsubscribe: true` gets that link from delivery itself when the service has `unsubscribeLinks` (`runProdApp` option `unsubscribe: { secret }` mounts the routes and wires it; base URL from `auth.publicOrigin`, `auth.mail.baseUrl` or `APP_BASE_URL`): user recipients get a user token, `route` recipients an address token (needs a blind-index key, otherwise no link); a `data.unsubscribeUrl` from the caller wins. `unsubscribe.tenantOrigin: (ctx) => origin | null` points the link at the tenant's own host (subdomain or custom domain, must serve this app's `/api`); null, a throw or a non-https value fall back to the app origin. The token carries the mail's locale, so the confirmation and result pages render in the mail's language. To react to a no-account opt-out (e.g. remove the app's own subscriber row), declare `r.job(name, { trigger: { on: DeliveryHandlers.unsubscribeAddress } }, handler)` on a lane the app consumes: it runs after each committed opt-out, also an idempotent repeat, with `unsubscribeAddressPayloadSchema` (`addressHash`, `notificationType`, `channel`) as payload and the tenant in `ctx.triggeredBy.tenantId`; match the subscriber via `hashUnsubscribeAddress(email)`. Declare `unsubscribeUrl` as an optional template variable if the mail body shows it.",
729
729
  "toggleableDefault": null,
730
730
  "requires": [
731
731
  "tenant"
@@ -1507,7 +1507,7 @@
1507
1507
  },
1508
1508
  {
1509
1509
  "name": "mail-foundation",
1510
- "description": "Defines the `mailTransport` extension point and a per-tenant `provider` config key that selects which registered transport plugin to use at runtime. Call `createTransportForTenant(ctx, tenantId)` to get an `EmailTransport` ready for sending — use this feature together with at least one `mail-transport-*` feature; use `delivery` + `channel-email` instead when you need the full notification pipeline with delivery attempts and user preferences.",
1510
+ "description": "Defines the `mailTransport` extension point and a per-tenant `provider` config key that selects which registered transport plugin to use at runtime. Call `createTransportForTenant(ctx, tenantId)` to get an `EmailTransport` ready for sending — use this feature together with at least one `mail-transport-*` feature; use `delivery` + `channel-email` instead when you need the full notification pipeline with delivery attempts and user preferences. Pass `tenantMailTransport` as `tenantTransport` to `createChannelEmailFeature` so a tenant with a provider configured sends delivery mails through its own transport and other tenants through the app transport.",
1511
1511
  "toggleableDefault": null,
1512
1512
  "requires": [
1513
1513
  "config"
@@ -2122,7 +2122,7 @@
2122
2122
  },
2123
2123
  {
2124
2124
  "name": "renderer-mail-html",
2125
- "description": "A `renderer-foundation` plugin that handles `RenderRequest { kind: \"mail\" }` and returns `{ subject, html, text, previewText? }` suitable for sending via `channel-email`. The mail template (`kind: \"mail\"`, document HTML, subject in `meta`) resolves through `template-resolver`'s DB fallback, its body is compiled to Handlebars by template-resolver and rendered with escaping, then placed into an MJML layout (`kind: \"mail-layout\"`, slug `default` unless `meta.layoutSlug` says otherwise, an in-code structured layout with the tenant branding logo and accent color as last resort) that `mjml()` compiles to email-safe table HTML, with `html-to-text` producing the plain-text alternative. An optional `footer` resolver adds the platform imprint, privacy link and footer text below every structured layout, and an optional `brandSuffix` resolver adds a word after the tenant name in the brand row of the in-code layout. Mail meta `headerChip`, `tone` and `footerNote` add a chip beside the brand, the tone of the top line and chip, and a note above the site and unsubscribe links of the card. Seed an editable system layout with `seedSystemMailLayout(db)`. Requires `renderer-foundation` + `template-resolver`.",
2125
+ "description": "A `renderer-foundation` plugin that handles `RenderRequest { kind: \"mail\" }` and returns `{ subject, html, text, previewText? }` suitable for sending via `channel-email`. The mail template (`kind: \"mail\"`, document HTML, subject in `meta`) resolves through `template-resolver`'s DB fallback, its body is compiled to Handlebars by template-resolver and rendered with escaping, then placed into an MJML layout (`kind: \"mail-layout\"`, slug `default` unless `meta.layoutSlug` says otherwise, an in-code structured layout with the tenant branding logo and accent color as last resort) that `mjml()` compiles to email-safe table HTML, with `html-to-text` producing the plain-text alternative. An optional `footer` resolver adds the platform imprint, privacy link and footer text below every structured layout, an optional `brandSuffix` resolver adds a word after the tenant name in the brand row of the in-code layout, and an optional `siteUrl` resolver supplies the site link of the card when the tenant branding has no site URL (without either, the link is left out). The brand row and `data-resource=\"logo\"` images use the branding logo upload, or the branding logo https address when no file is uploaded. Mail meta `headerChip`, `tone` and `footerNote` add a chip beside the brand, the tone of the top line and chip, and a note above the site and unsubscribe links of the card. Seed an editable system layout with `seedSystemMailLayout(db)`. Requires `renderer-foundation` + `template-resolver`.",
2126
2126
  "toggleableDefault": null,
2127
2127
  "requires": [
2128
2128
  "renderer-foundation",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-kumiko-app",
3
- "version": "0.366.0",
3
+ "version": "0.368.0",
4
4
  "description": "`bun create kumiko-app <name>` — scaffold a new Kumiko app with an interactive feature picker.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -27,10 +27,10 @@
27
27
  }
28
28
  },
29
29
  "dependencies": {
30
- "@cosmicdrift/kumiko-dev-server": "0.366.0",
31
- "@cosmicdrift/kumiko-framework": "0.366.0",
32
- "@cosmicdrift/kumiko-server-runtime": "0.366.0",
33
- "@cosmicdrift/kumiko-testing": "0.366.0",
30
+ "@cosmicdrift/kumiko-dev-server": "0.368.0",
31
+ "@cosmicdrift/kumiko-framework": "0.368.0",
32
+ "@cosmicdrift/kumiko-server-runtime": "0.368.0",
33
+ "@cosmicdrift/kumiko-testing": "0.368.0",
34
34
  "@inquirer/core": "^10.0.0",
35
35
  "@inquirer/prompts": "^7.4.0"
36
36
  },
@@ -7,11 +7,6 @@
7
7
  // subscription-mollie, file-provider-s3, managed-pages, tier-engine) are
8
8
  // intentionally absent — the picker hides them, the user wires them by
9
9
  // hand after scaffold.
10
- //
11
- // Naming inconsistency is intentional and historical: most features expose
12
- // `create<Pascal>Feature` factories, a handful export the FeatureDefinition
13
- // object directly. The callExpression captures that: `()` for factories,
14
- // bare name for objects.
15
10
 
16
11
  import type { ScaffoldFeatureEntry } from "@cosmicdrift/kumiko-dev-server";
17
12
 
@@ -32,8 +27,8 @@ export const FEATURE_CONSTRUCTORS: Readonly<Record<string, ScaffoldFeatureEntry>
32
27
  "auth-foundation": {
33
28
  name: "auth-foundation",
34
29
  importPath: "@cosmicdrift/kumiko-bundled-features/auth-foundation",
35
- exportName: "authFoundationFeature",
36
- callExpression: "authFoundationFeature",
30
+ exportName: "createAuthFoundationFeature",
31
+ callExpression: "createAuthFoundationFeature()",
37
32
  },
38
33
  "personal-access-tokens": {
39
34
  name: "personal-access-tokens",
@@ -85,8 +80,8 @@ export const FEATURE_CONSTRUCTORS: Readonly<Record<string, ScaffoldFeatureEntry>
85
80
  "cap-counter": {
86
81
  name: "cap-counter",
87
82
  importPath: "@cosmicdrift/kumiko-bundled-features/cap-counter",
88
- exportName: "capCounterFeature",
89
- callExpression: "capCounterFeature",
83
+ exportName: "createCapCounterFeature",
84
+ callExpression: "createCapCounterFeature()",
90
85
  },
91
86
  "step-dispatcher": {
92
87
  name: "step-dispatcher",
@@ -105,14 +100,14 @@ export const FEATURE_CONSTRUCTORS: Readonly<Record<string, ScaffoldFeatureEntry>
105
100
  "file-foundation": {
106
101
  name: "file-foundation",
107
102
  importPath: "@cosmicdrift/kumiko-bundled-features/file-foundation",
108
- exportName: "fileFoundationFeature",
109
- callExpression: "fileFoundationFeature",
103
+ exportName: "createFileFoundationFeature",
104
+ callExpression: "createFileFoundationFeature()",
110
105
  },
111
106
  "file-provider-inmemory": {
112
107
  name: "file-provider-inmemory",
113
108
  importPath: "@cosmicdrift/kumiko-bundled-features/file-provider-inmemory",
114
- exportName: "fileProviderInMemoryFeature",
115
- callExpression: "fileProviderInMemoryFeature",
109
+ exportName: "createFileProviderInMemoryFeature",
110
+ callExpression: "createFileProviderInMemoryFeature()",
116
111
  },
117
112
 
118
113
  // --- Notifications ---
@@ -125,20 +120,20 @@ export const FEATURE_CONSTRUCTORS: Readonly<Record<string, ScaffoldFeatureEntry>
125
120
  "mail-foundation": {
126
121
  name: "mail-foundation",
127
122
  importPath: "@cosmicdrift/kumiko-bundled-features/mail-foundation",
128
- exportName: "mailFoundationFeature",
129
- callExpression: "mailFoundationFeature",
123
+ exportName: "createMailFoundationFeature",
124
+ callExpression: "createMailFoundationFeature()",
130
125
  },
131
126
  "mail-transport-inmemory": {
132
127
  name: "mail-transport-inmemory",
133
128
  importPath: "@cosmicdrift/kumiko-bundled-features/mail-transport-inmemory",
134
- exportName: "mailTransportInMemoryFeature",
135
- callExpression: "mailTransportInMemoryFeature",
129
+ exportName: "createMailTransportInMemoryFeature",
130
+ callExpression: "createMailTransportInMemoryFeature()",
136
131
  },
137
132
  "mail-transport-smtp": {
138
133
  name: "mail-transport-smtp",
139
134
  importPath: "@cosmicdrift/kumiko-bundled-features/mail-transport-smtp",
140
- exportName: "mailTransportSmtpFeature",
141
- callExpression: "mailTransportSmtpFeature",
135
+ exportName: "createMailTransportSmtpFeature",
136
+ callExpression: "createMailTransportSmtpFeature()",
142
137
  },
143
138
  "channel-in-app": {
144
139
  name: "channel-in-app",
@@ -163,8 +158,8 @@ export const FEATURE_CONSTRUCTORS: Readonly<Record<string, ScaffoldFeatureEntry>
163
158
  "billing-foundation": {
164
159
  name: "billing-foundation",
165
160
  importPath: "@cosmicdrift/kumiko-bundled-features/billing-foundation",
166
- exportName: "billingFoundationFeature",
167
- callExpression: "billingFoundationFeature",
161
+ exportName: "createBillingFoundationFeature",
162
+ callExpression: "createBillingFoundationFeature()",
168
163
  },
169
164
 
170
165
  // --- Compliance ---
@@ -221,8 +216,8 @@ export const FEATURE_CONSTRUCTORS: Readonly<Record<string, ScaffoldFeatureEntry>
221
216
  readiness: {
222
217
  name: "readiness",
223
218
  importPath: "@cosmicdrift/kumiko-bundled-features/readiness",
224
- exportName: "readinessFeature",
225
- callExpression: "readinessFeature",
219
+ exportName: "createReadinessFeature",
220
+ callExpression: "createReadinessFeature()",
226
221
  },
227
222
 
228
223
  // --- Content ---
@@ -237,14 +232,14 @@ export const FEATURE_CONSTRUCTORS: Readonly<Record<string, ScaffoldFeatureEntry>
237
232
  tags: {
238
233
  name: "tags",
239
234
  importPath: "@cosmicdrift/kumiko-bundled-features/tags",
240
- exportName: "tagsFeature",
241
- callExpression: "tagsFeature",
235
+ exportName: "createTagsFeature",
236
+ callExpression: "createTagsFeature()",
242
237
  },
243
238
  "custom-fields": {
244
239
  name: "custom-fields",
245
240
  importPath: "@cosmicdrift/kumiko-bundled-features/custom-fields",
246
- exportName: "customFieldsFeature",
247
- callExpression: "customFieldsFeature",
241
+ exportName: "createCustomFieldsFeature",
242
+ callExpression: "createCustomFieldsFeature()",
248
243
  },
249
244
  };
250
245