create-kumiko-app 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -0
- package/bin/cli.ts +4 -0
- package/feature-manifest.json +1676 -0
- package/package.json +48 -0
- package/src/__tests__/cli.test.ts +78 -0
- package/src/__tests__/dep-resolver.test.ts +46 -0
- package/src/__tests__/manifest-drift.test.ts +31 -0
- package/src/__tests__/picker.test.ts +35 -0
- package/src/dep-resolver.ts +44 -0
- package/src/feature-constructors.ts +238 -0
- package/src/index.ts +92 -0
- package/src/manifest.ts +59 -0
- package/src/picker.ts +76 -0
|
@@ -0,0 +1,1676 @@
|
|
|
1
|
+
{
|
|
2
|
+
"source": "samples/apps/use-all-bundled APP_FEATURES (composeFeatures includeBundled)",
|
|
3
|
+
"featureCount": 41,
|
|
4
|
+
"features": [
|
|
5
|
+
{
|
|
6
|
+
"name": "audit",
|
|
7
|
+
"description": "Exposes the framework's event store as a paginated, filterable audit log via the `audit:query:list` handler (accessible to `Admin` and `SystemAdmin` roles). No separate table or projection — the event store is the audit trail by construction: every entity write already records who, when, what entity, and the event payload with PII stripped. Filter by `aggregateType`, `aggregateId`, `eventType`, `userId`, or time range.",
|
|
8
|
+
"toggleableDefault": null,
|
|
9
|
+
"requires": [],
|
|
10
|
+
"optionalRequires": [],
|
|
11
|
+
"configReads": [],
|
|
12
|
+
"exposesApis": [],
|
|
13
|
+
"usesApis": [],
|
|
14
|
+
"extensionsUsed": [],
|
|
15
|
+
"configKeys": [],
|
|
16
|
+
"secrets": [],
|
|
17
|
+
"writeHandlers": [],
|
|
18
|
+
"uiHints": {
|
|
19
|
+
"displayLabel": "Audit Log",
|
|
20
|
+
"category": "compliance",
|
|
21
|
+
"recommended": false
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "auth-email-password",
|
|
26
|
+
"description": "Provides email+password authentication: the always-on handlers are `login`, `changePassword`, and `logout`; optional flows — password reset, email verification, magic-link self-signup, and tenant invite — are registered only when you pass their respective option objects (`passwordReset`, `emailVerification`, `signup`, `invite`) to `createAuthEmailPasswordFeature(opts)`. Each opt-in flow uses HMAC-signed or opaque-random tokens delivered via callback (e.g. `sendResetEmail`) so the feature stays transport-agnostic. Requires the `user` and `tenant` features, and declares `JWT_SECRET` (≥ 32 chars) in `authEmailPasswordEnvSchema` so a missing secret surfaces at boot validation rather than on the first login attempt.",
|
|
27
|
+
"toggleableDefault": null,
|
|
28
|
+
"requires": [
|
|
29
|
+
"user",
|
|
30
|
+
"tenant"
|
|
31
|
+
],
|
|
32
|
+
"optionalRequires": [],
|
|
33
|
+
"configReads": [],
|
|
34
|
+
"exposesApis": [],
|
|
35
|
+
"usesApis": [],
|
|
36
|
+
"extensionsUsed": [],
|
|
37
|
+
"configKeys": [],
|
|
38
|
+
"secrets": [],
|
|
39
|
+
"writeHandlers": [
|
|
40
|
+
"auth-email-password:write:change-password",
|
|
41
|
+
"auth-email-password:write:login",
|
|
42
|
+
"auth-email-password:write:logout"
|
|
43
|
+
],
|
|
44
|
+
"uiHints": {
|
|
45
|
+
"displayLabel": "Auth · Email + Password",
|
|
46
|
+
"category": "identity",
|
|
47
|
+
"recommended": true,
|
|
48
|
+
"configurableOptions": [
|
|
49
|
+
{
|
|
50
|
+
"key": "passwordReset",
|
|
51
|
+
"label": "Password-Reset-Flow",
|
|
52
|
+
"type": "boolean",
|
|
53
|
+
"default": true
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"key": "emailVerification",
|
|
57
|
+
"label": "Email-Verification-Flow",
|
|
58
|
+
"type": "boolean",
|
|
59
|
+
"default": true
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"key": "signup",
|
|
63
|
+
"label": "Self-Signup-Flow",
|
|
64
|
+
"type": "boolean",
|
|
65
|
+
"default": false
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"key": "invite",
|
|
69
|
+
"label": "Tenant-Invite-Flow",
|
|
70
|
+
"type": "boolean",
|
|
71
|
+
"default": false
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"name": "billing-foundation",
|
|
78
|
+
"description": "Plugin host for subscription billing — manages the `read_subscriptions` projection table and exposes 5 domain events (subscription created/updated/canceled, invoice paid/failed) appended by the foundation's own `billing-foundation:write:process-event` write-handler after provider plugins verify and normalize each webhook. Also ships `billing-foundation:write:create-checkout-session` and `billing-foundation:write:create-portal-session` write-handlers, a `billing-foundation:query:subscription:list` query handler, and a `createSubscriptionWebhookHandler` factory for the `/api/subscription/webhook/:providerName` route. Low-level building block — use `subscription-stripe` or `subscription-mollie` unless you are writing a new payment provider.",
|
|
79
|
+
"toggleableDefault": null,
|
|
80
|
+
"requires": [],
|
|
81
|
+
"optionalRequires": [],
|
|
82
|
+
"configReads": [],
|
|
83
|
+
"exposesApis": [],
|
|
84
|
+
"usesApis": [],
|
|
85
|
+
"extensionsUsed": [],
|
|
86
|
+
"configKeys": [],
|
|
87
|
+
"secrets": [],
|
|
88
|
+
"writeHandlers": [
|
|
89
|
+
"billing-foundation:write:create-checkout-session",
|
|
90
|
+
"billing-foundation:write:create-portal-session",
|
|
91
|
+
"billing-foundation:write:process-event"
|
|
92
|
+
],
|
|
93
|
+
"uiHints": {
|
|
94
|
+
"displayLabel": "Billing · Foundation",
|
|
95
|
+
"category": "billing",
|
|
96
|
+
"recommended": false
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"name": "cap-counter",
|
|
101
|
+
"description": "Tracks per-tenant usage against configurable limits using two complementary storage models: calendar-period counters (one projection row per tenant/capName/period, reset implicitly by period rollover) and rolling-window counters (append-only event stream, no projection). Use `enforceCap` / `enforceRollingCap` (or the `withCapEnforcement` / `withRollingCapEnforcement` handler wrappers) in your write-handlers to check limits with soft-warn and hard-block tolerances; call `enforceCapAndMaybeNotify` when you also want to trigger a delivery notification on soft-threshold hits.",
|
|
102
|
+
"toggleableDefault": null,
|
|
103
|
+
"requires": [],
|
|
104
|
+
"optionalRequires": [],
|
|
105
|
+
"configReads": [],
|
|
106
|
+
"exposesApis": [],
|
|
107
|
+
"usesApis": [],
|
|
108
|
+
"extensionsUsed": [],
|
|
109
|
+
"configKeys": [],
|
|
110
|
+
"secrets": [],
|
|
111
|
+
"writeHandlers": [
|
|
112
|
+
"cap-counter:write:increment",
|
|
113
|
+
"cap-counter:write:increment-rolling",
|
|
114
|
+
"cap-counter:write:mark-soft-warned"
|
|
115
|
+
],
|
|
116
|
+
"uiHints": {
|
|
117
|
+
"displayLabel": "Cap Counter · Usage Limits",
|
|
118
|
+
"category": "operations",
|
|
119
|
+
"recommended": false
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"name": "channel-email",
|
|
124
|
+
"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`, a `renderer: NotificationRenderer` (e.g. backed by `renderer-simple`), and a `resolveEmail` function that maps a user ID to their email address.",
|
|
125
|
+
"toggleableDefault": null,
|
|
126
|
+
"requires": [
|
|
127
|
+
"delivery"
|
|
128
|
+
],
|
|
129
|
+
"optionalRequires": [],
|
|
130
|
+
"configReads": [],
|
|
131
|
+
"exposesApis": [],
|
|
132
|
+
"usesApis": [],
|
|
133
|
+
"extensionsUsed": [
|
|
134
|
+
{
|
|
135
|
+
"extensionName": "deliveryChannel",
|
|
136
|
+
"entityName": "email"
|
|
137
|
+
}
|
|
138
|
+
],
|
|
139
|
+
"configKeys": [],
|
|
140
|
+
"secrets": [],
|
|
141
|
+
"writeHandlers": [],
|
|
142
|
+
"uiHints": {
|
|
143
|
+
"displayLabel": "Email Channel",
|
|
144
|
+
"category": "notifications",
|
|
145
|
+
"recommended": false
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"name": "channel-in-app",
|
|
150
|
+
"description": "Persists notifications to an in-app inbox table so users can retrieve them via `handlers.inbox` and track unread state with `handlers.markRead` / `handlers.markAllRead` and `queries.unreadCount`. Requires `delivery`; no external service needed — messages are stored in the app's own database.",
|
|
151
|
+
"toggleableDefault": null,
|
|
152
|
+
"requires": [
|
|
153
|
+
"delivery"
|
|
154
|
+
],
|
|
155
|
+
"optionalRequires": [],
|
|
156
|
+
"configReads": [],
|
|
157
|
+
"exposesApis": [],
|
|
158
|
+
"usesApis": [],
|
|
159
|
+
"extensionsUsed": [
|
|
160
|
+
{
|
|
161
|
+
"extensionName": "deliveryChannel",
|
|
162
|
+
"entityName": "inApp"
|
|
163
|
+
}
|
|
164
|
+
],
|
|
165
|
+
"configKeys": [],
|
|
166
|
+
"secrets": [],
|
|
167
|
+
"writeHandlers": [
|
|
168
|
+
"channel-in-app:write:mark-all-read",
|
|
169
|
+
"channel-in-app:write:mark-read"
|
|
170
|
+
],
|
|
171
|
+
"uiHints": {
|
|
172
|
+
"displayLabel": "In-App Inbox",
|
|
173
|
+
"category": "notifications",
|
|
174
|
+
"recommended": false
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"name": "channel-push",
|
|
179
|
+
"description": "Delivers push notifications through a `PushTransport` (bring your own FCM/APNs adapter or use `createInMemoryPushTransport()` for tests) registered as the `push` channel in the delivery system. Requires `delivery`; supply a `PushChannelOptions` with a transport and a resolver that maps a user ID to their device token.",
|
|
180
|
+
"toggleableDefault": null,
|
|
181
|
+
"requires": [
|
|
182
|
+
"delivery"
|
|
183
|
+
],
|
|
184
|
+
"optionalRequires": [],
|
|
185
|
+
"configReads": [],
|
|
186
|
+
"exposesApis": [],
|
|
187
|
+
"usesApis": [],
|
|
188
|
+
"extensionsUsed": [
|
|
189
|
+
{
|
|
190
|
+
"extensionName": "deliveryChannel",
|
|
191
|
+
"entityName": "push"
|
|
192
|
+
}
|
|
193
|
+
],
|
|
194
|
+
"configKeys": [],
|
|
195
|
+
"secrets": [],
|
|
196
|
+
"writeHandlers": [],
|
|
197
|
+
"uiHints": {
|
|
198
|
+
"displayLabel": "Push Channel",
|
|
199
|
+
"category": "notifications",
|
|
200
|
+
"recommended": false
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"name": "compliance-profiles",
|
|
205
|
+
"description": "Lets each tenant select a compliance regime (e.g. `eu-dsgvo`, `swiss-dsg`, `de-hr-dsgvo-hgb`) that bundles user-rights grace periods, breach-disclosure deadlines, sub-processor requirements, and audit-retention rules into a single named profile. Tenant admins call `compliance-profiles:write:set-profile` to choose a profile (with optional JSON override for edge cases); other features resolve the effective profile via the `compliance.forTenant` cross-feature API. Required by `user-data-rights` — mount this feature before it.",
|
|
206
|
+
"toggleableDefault": null,
|
|
207
|
+
"requires": [],
|
|
208
|
+
"optionalRequires": [],
|
|
209
|
+
"configReads": [],
|
|
210
|
+
"exposesApis": [
|
|
211
|
+
"compliance.forTenant"
|
|
212
|
+
],
|
|
213
|
+
"usesApis": [],
|
|
214
|
+
"extensionsUsed": [],
|
|
215
|
+
"configKeys": [],
|
|
216
|
+
"secrets": [],
|
|
217
|
+
"writeHandlers": [
|
|
218
|
+
"compliance-profiles:write:set-profile"
|
|
219
|
+
],
|
|
220
|
+
"uiHints": {
|
|
221
|
+
"displayLabel": "Compliance Profiles",
|
|
222
|
+
"category": "compliance",
|
|
223
|
+
"recommended": false
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"name": "config",
|
|
228
|
+
"description": "Stores per-tenant (and optionally per-user) configuration values with a multi-layer cascade: user-row → tenant-row → system-row → app-override (deploy-time `AppConfigOverrides`) → computed → feature default. Access a value in handlers via `ctx.config(handle)`, declare keys with `r.config({ keys: { ... } })` inside a feature's registry callback, and optionally mark them `encrypted: true` to route storage through an `EncryptionProvider`. Use this feature whenever a tenant admin needs to customise behaviour at runtime without a code deploy.",
|
|
229
|
+
"toggleableDefault": null,
|
|
230
|
+
"requires": [],
|
|
231
|
+
"optionalRequires": [],
|
|
232
|
+
"configReads": [],
|
|
233
|
+
"exposesApis": [],
|
|
234
|
+
"usesApis": [],
|
|
235
|
+
"extensionsUsed": [],
|
|
236
|
+
"configKeys": [],
|
|
237
|
+
"secrets": [],
|
|
238
|
+
"writeHandlers": [
|
|
239
|
+
"config:write:reset",
|
|
240
|
+
"config:write:set"
|
|
241
|
+
],
|
|
242
|
+
"uiHints": {
|
|
243
|
+
"displayLabel": "Tenant Config Store",
|
|
244
|
+
"category": "infrastructure",
|
|
245
|
+
"recommended": true
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"name": "custom-fields",
|
|
250
|
+
"description": "Tenant- and system-scoped custom field definitions with generic value storage on any host entity. Registers the `field-definition` entity (event-sourced CRUD via `define-tenant-field`, `define-system-field`, `update-tenant-field`, `delete-tenant-field`, `delete-system-field`) and two value write-handlers (`set-custom-field`, `clear-custom-field`) that emit `custom-fields:event:custom-field-set` / `custom-fields:event:custom-field-cleared` events on the host aggregate's stream. To attach custom fields to your own entity, call `wireCustomFieldsFor(r, entityName, entityTable)` in the host feature — this wires the JSONB projection, `postQuery` flattening hook, and search-payload extension. The host entity must declare a `customFieldsField()` JSONB column.",
|
|
251
|
+
"toggleableDefault": null,
|
|
252
|
+
"requires": [],
|
|
253
|
+
"optionalRequires": [],
|
|
254
|
+
"configReads": [],
|
|
255
|
+
"exposesApis": [],
|
|
256
|
+
"usesApis": [],
|
|
257
|
+
"extensionsUsed": [],
|
|
258
|
+
"configKeys": [],
|
|
259
|
+
"secrets": [],
|
|
260
|
+
"writeHandlers": [
|
|
261
|
+
"custom-fields:write:clear-custom-field",
|
|
262
|
+
"custom-fields:write:define-system-field",
|
|
263
|
+
"custom-fields:write:define-tenant-field",
|
|
264
|
+
"custom-fields:write:delete-system-field",
|
|
265
|
+
"custom-fields:write:delete-tenant-field",
|
|
266
|
+
"custom-fields:write:set-custom-field",
|
|
267
|
+
"custom-fields:write:update-tenant-field"
|
|
268
|
+
],
|
|
269
|
+
"uiHints": {
|
|
270
|
+
"displayLabel": "Custom Fields",
|
|
271
|
+
"category": "data",
|
|
272
|
+
"recommended": false
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
"name": "data-retention",
|
|
277
|
+
"description": "Resolves the effective retention policy for any entity using a 3-layer stack: entity-level default → tenant preset (`dsgvo-basic`, `dsgvo-hgb`, `swiss-dsg`) → per-tenant override stored in `tenantRetentionOverride`. Other features query the resolved policy via the `retention.policyFor` cross-feature API — most notably `user-data-rights`, which uses it to decide whether to anonymize instead of hard-delete a record that is still within a mandatory retention window.",
|
|
278
|
+
"toggleableDefault": null,
|
|
279
|
+
"requires": [],
|
|
280
|
+
"optionalRequires": [],
|
|
281
|
+
"configReads": [],
|
|
282
|
+
"exposesApis": [
|
|
283
|
+
"retention.policyFor"
|
|
284
|
+
],
|
|
285
|
+
"usesApis": [],
|
|
286
|
+
"extensionsUsed": [],
|
|
287
|
+
"configKeys": [],
|
|
288
|
+
"secrets": [],
|
|
289
|
+
"writeHandlers": [],
|
|
290
|
+
"uiHints": {
|
|
291
|
+
"displayLabel": "Data Retention Policy",
|
|
292
|
+
"category": "compliance",
|
|
293
|
+
"recommended": false
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
"name": "delivery",
|
|
298
|
+
"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). It stores per-user channel preferences in the `notification-preference` entity, logs every attempt to `read_delivery_attempts`, and enforces idempotency and rate-limiting — add `channel-email`, `channel-in-app`, or `channel-push` on top to actually send anything.",
|
|
299
|
+
"toggleableDefault": null,
|
|
300
|
+
"requires": [],
|
|
301
|
+
"optionalRequires": [],
|
|
302
|
+
"configReads": [],
|
|
303
|
+
"exposesApis": [],
|
|
304
|
+
"usesApis": [],
|
|
305
|
+
"extensionsUsed": [],
|
|
306
|
+
"configKeys": [],
|
|
307
|
+
"secrets": [],
|
|
308
|
+
"writeHandlers": [
|
|
309
|
+
"delivery:write:set-preference"
|
|
310
|
+
],
|
|
311
|
+
"uiHints": {
|
|
312
|
+
"displayLabel": "Notifications · Dispatch Core",
|
|
313
|
+
"category": "notifications",
|
|
314
|
+
"recommended": true
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
"name": "feature-toggles",
|
|
319
|
+
"description": "Persists per-feature enabled/disabled state in the `read_global_feature_state` table and exposes a `set` write-handler plus `list`/`registered` query-handlers so operators can flip features at runtime without redeploying. Each API instance keeps an in-memory `GlobalFeatureToggleRuntime` snapshot (initialize it via `createFeatureToggleRuntime`, pass a `() => runtime` accessor to `createFeatureTogglesFeature`) that the dispatcher gate reads on every request; a `toggle-cache-sync` multi-stream projection with `delivery: \"per-instance\"` syncs the snapshot across instances whenever a `toggle-set` event is appended.",
|
|
320
|
+
"toggleableDefault": null,
|
|
321
|
+
"requires": [],
|
|
322
|
+
"optionalRequires": [],
|
|
323
|
+
"configReads": [],
|
|
324
|
+
"exposesApis": [],
|
|
325
|
+
"usesApis": [],
|
|
326
|
+
"extensionsUsed": [],
|
|
327
|
+
"configKeys": [],
|
|
328
|
+
"secrets": [],
|
|
329
|
+
"writeHandlers": [
|
|
330
|
+
"feature-toggles:write:set"
|
|
331
|
+
],
|
|
332
|
+
"uiHints": {
|
|
333
|
+
"displayLabel": "Feature Toggles · Operator Switches",
|
|
334
|
+
"category": "operations",
|
|
335
|
+
"recommended": false
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
"name": "file-foundation",
|
|
340
|
+
"description": "Defines the `fileProvider` extension point and a per-tenant `provider` config key that selects which registered storage plugin to use at runtime. Call `createFileProviderForTenant(ctx, tenantId)` to get a `FileStorageProvider` — use this feature together with at least one `file-provider-*` feature; the `files` feature builds on top of it for tracked `FileRef` entities with GDPR hooks.",
|
|
341
|
+
"toggleableDefault": null,
|
|
342
|
+
"requires": [
|
|
343
|
+
"config"
|
|
344
|
+
],
|
|
345
|
+
"optionalRequires": [],
|
|
346
|
+
"configReads": [],
|
|
347
|
+
"exposesApis": [],
|
|
348
|
+
"usesApis": [],
|
|
349
|
+
"extensionsUsed": [],
|
|
350
|
+
"configKeys": [
|
|
351
|
+
{
|
|
352
|
+
"key": "provider",
|
|
353
|
+
"qualifiedName": "file-foundation:config:provider",
|
|
354
|
+
"type": "text",
|
|
355
|
+
"scope": "tenant",
|
|
356
|
+
"default": "",
|
|
357
|
+
"encrypted": false,
|
|
358
|
+
"computed": false,
|
|
359
|
+
"options": null,
|
|
360
|
+
"bounds": null,
|
|
361
|
+
"pattern": null,
|
|
362
|
+
"writeRoles": [
|
|
363
|
+
"TenantAdmin",
|
|
364
|
+
"SystemAdmin"
|
|
365
|
+
],
|
|
366
|
+
"readRoles": [
|
|
367
|
+
"TenantAdmin",
|
|
368
|
+
"SystemAdmin",
|
|
369
|
+
"User"
|
|
370
|
+
]
|
|
371
|
+
}
|
|
372
|
+
],
|
|
373
|
+
"secrets": [],
|
|
374
|
+
"writeHandlers": [],
|
|
375
|
+
"uiHints": {
|
|
376
|
+
"displayLabel": "File Provider Foundation",
|
|
377
|
+
"category": "storage",
|
|
378
|
+
"recommended": false
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
"name": "file-provider-inmemory",
|
|
383
|
+
"description": "Registers an in-process `\"inmemory\"` provider for `file-foundation` that stores file bytes per tenant in a module-level Map. Use `listKeys(tenantId)` and `clearStorage(tenantId)` in demo apps and tests; not for production (data is lost on restart and grows without bound).",
|
|
384
|
+
"toggleableDefault": null,
|
|
385
|
+
"requires": [
|
|
386
|
+
"file-foundation"
|
|
387
|
+
],
|
|
388
|
+
"optionalRequires": [],
|
|
389
|
+
"configReads": [],
|
|
390
|
+
"exposesApis": [],
|
|
391
|
+
"usesApis": [],
|
|
392
|
+
"extensionsUsed": [
|
|
393
|
+
{
|
|
394
|
+
"extensionName": "fileProvider",
|
|
395
|
+
"entityName": "inmemory"
|
|
396
|
+
}
|
|
397
|
+
],
|
|
398
|
+
"configKeys": [],
|
|
399
|
+
"secrets": [],
|
|
400
|
+
"writeHandlers": [],
|
|
401
|
+
"uiHints": {
|
|
402
|
+
"displayLabel": "File Provider · In-Memory",
|
|
403
|
+
"category": "storage",
|
|
404
|
+
"recommended": false
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
"name": "file-provider-s3",
|
|
409
|
+
"description": "Registers itself as the `\"s3\"` provider for `file-foundation` and owns the per-tenant config keys (`bucket`, `region`, `endpoint`, `forcePathStyle`, `accessKeyId`) and the encrypted `s3.secretAccessKey` secret. Compatible with any S3-compatible object store (AWS S3, Hetzner Object Storage); set credentials via the admin UI or a seed handler before the first file operation.",
|
|
410
|
+
"toggleableDefault": null,
|
|
411
|
+
"requires": [
|
|
412
|
+
"config",
|
|
413
|
+
"secrets",
|
|
414
|
+
"file-foundation"
|
|
415
|
+
],
|
|
416
|
+
"optionalRequires": [],
|
|
417
|
+
"configReads": [],
|
|
418
|
+
"exposesApis": [],
|
|
419
|
+
"usesApis": [],
|
|
420
|
+
"extensionsUsed": [
|
|
421
|
+
{
|
|
422
|
+
"extensionName": "fileProvider",
|
|
423
|
+
"entityName": "s3"
|
|
424
|
+
}
|
|
425
|
+
],
|
|
426
|
+
"configKeys": [
|
|
427
|
+
{
|
|
428
|
+
"key": "access-key-id",
|
|
429
|
+
"qualifiedName": "file-provider-s3:config:access-key-id",
|
|
430
|
+
"type": "text",
|
|
431
|
+
"scope": "tenant",
|
|
432
|
+
"default": "",
|
|
433
|
+
"encrypted": false,
|
|
434
|
+
"computed": false,
|
|
435
|
+
"options": null,
|
|
436
|
+
"bounds": null,
|
|
437
|
+
"pattern": null,
|
|
438
|
+
"writeRoles": [
|
|
439
|
+
"TenantAdmin",
|
|
440
|
+
"SystemAdmin"
|
|
441
|
+
],
|
|
442
|
+
"readRoles": [
|
|
443
|
+
"TenantAdmin",
|
|
444
|
+
"SystemAdmin"
|
|
445
|
+
]
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
"key": "bucket",
|
|
449
|
+
"qualifiedName": "file-provider-s3:config:bucket",
|
|
450
|
+
"type": "text",
|
|
451
|
+
"scope": "tenant",
|
|
452
|
+
"default": "",
|
|
453
|
+
"encrypted": false,
|
|
454
|
+
"computed": false,
|
|
455
|
+
"options": null,
|
|
456
|
+
"bounds": null,
|
|
457
|
+
"pattern": null,
|
|
458
|
+
"writeRoles": [
|
|
459
|
+
"TenantAdmin",
|
|
460
|
+
"SystemAdmin"
|
|
461
|
+
],
|
|
462
|
+
"readRoles": [
|
|
463
|
+
"TenantAdmin",
|
|
464
|
+
"SystemAdmin"
|
|
465
|
+
]
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
"key": "endpoint",
|
|
469
|
+
"qualifiedName": "file-provider-s3:config:endpoint",
|
|
470
|
+
"type": "text",
|
|
471
|
+
"scope": "tenant",
|
|
472
|
+
"default": "",
|
|
473
|
+
"encrypted": false,
|
|
474
|
+
"computed": false,
|
|
475
|
+
"options": null,
|
|
476
|
+
"bounds": null,
|
|
477
|
+
"pattern": null,
|
|
478
|
+
"writeRoles": [
|
|
479
|
+
"TenantAdmin",
|
|
480
|
+
"SystemAdmin"
|
|
481
|
+
],
|
|
482
|
+
"readRoles": [
|
|
483
|
+
"TenantAdmin",
|
|
484
|
+
"SystemAdmin"
|
|
485
|
+
]
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
"key": "force-path-style",
|
|
489
|
+
"qualifiedName": "file-provider-s3:config:force-path-style",
|
|
490
|
+
"type": "boolean",
|
|
491
|
+
"scope": "tenant",
|
|
492
|
+
"default": false,
|
|
493
|
+
"encrypted": false,
|
|
494
|
+
"computed": false,
|
|
495
|
+
"options": null,
|
|
496
|
+
"bounds": null,
|
|
497
|
+
"pattern": null,
|
|
498
|
+
"writeRoles": [
|
|
499
|
+
"TenantAdmin",
|
|
500
|
+
"SystemAdmin"
|
|
501
|
+
],
|
|
502
|
+
"readRoles": [
|
|
503
|
+
"all"
|
|
504
|
+
]
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
"key": "region",
|
|
508
|
+
"qualifiedName": "file-provider-s3:config:region",
|
|
509
|
+
"type": "text",
|
|
510
|
+
"scope": "tenant",
|
|
511
|
+
"default": "",
|
|
512
|
+
"encrypted": false,
|
|
513
|
+
"computed": false,
|
|
514
|
+
"options": null,
|
|
515
|
+
"bounds": null,
|
|
516
|
+
"pattern": null,
|
|
517
|
+
"writeRoles": [
|
|
518
|
+
"TenantAdmin",
|
|
519
|
+
"SystemAdmin"
|
|
520
|
+
],
|
|
521
|
+
"readRoles": [
|
|
522
|
+
"TenantAdmin",
|
|
523
|
+
"SystemAdmin"
|
|
524
|
+
]
|
|
525
|
+
}
|
|
526
|
+
],
|
|
527
|
+
"secrets": [
|
|
528
|
+
{
|
|
529
|
+
"qualifiedName": "file-provider-s3:secret:s3-secret-access-key",
|
|
530
|
+
"scope": "tenant",
|
|
531
|
+
"label": "S3 Secret Access Key",
|
|
532
|
+
"hint": "Private half of the S3 key pair. Hetzner calls it 'Secret Key', AWS calls it 'Secret Access Key'."
|
|
533
|
+
}
|
|
534
|
+
],
|
|
535
|
+
"writeHandlers": [],
|
|
536
|
+
"uiHints": {
|
|
537
|
+
"displayLabel": "File Provider · S3",
|
|
538
|
+
"category": "storage",
|
|
539
|
+
"recommended": false
|
|
540
|
+
}
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
"name": "files",
|
|
544
|
+
"description": "Exposes the `fileRef` entity and `createFilesFeature` from the framework core so that uploaded files — tracked in the `file_refs` table by `createFileRoutes` — participate in cross-feature hooks: `user-data-rights-defaults` automatically includes file blobs in GDPR exports and forget flows, and future tenant-lifecycle cleanup will delete all refs on tenant destroy. This feature does not add upload or download routes; those remain in the server bootstrap via the `options.files` parameter.",
|
|
545
|
+
"toggleableDefault": null,
|
|
546
|
+
"requires": [],
|
|
547
|
+
"optionalRequires": [],
|
|
548
|
+
"configReads": [],
|
|
549
|
+
"exposesApis": [],
|
|
550
|
+
"usesApis": [],
|
|
551
|
+
"extensionsUsed": [],
|
|
552
|
+
"configKeys": [],
|
|
553
|
+
"secrets": [],
|
|
554
|
+
"writeHandlers": [],
|
|
555
|
+
"uiHints": {
|
|
556
|
+
"displayLabel": "Files · Metadata",
|
|
557
|
+
"category": "storage",
|
|
558
|
+
"recommended": false
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
"name": "jobs",
|
|
563
|
+
"description": "Persistence and operator tooling for background jobs registered via `r.job(...)`. Every job execution appends `run-started`, `run-completed`, and `run-failed` events to the `jobRun` aggregate stream, which two inline projections materialize into `read_job_runs` (current status + duration) and `read_job_run_logs` (per-line log rows). Exposes `jobs:write:trigger` (manual run) and `jobs:write:retry` (operator retry of a failed run), plus `jobs:query:list` and `jobs:query:details` for the operator UI.",
|
|
564
|
+
"toggleableDefault": null,
|
|
565
|
+
"requires": [],
|
|
566
|
+
"optionalRequires": [],
|
|
567
|
+
"configReads": [],
|
|
568
|
+
"exposesApis": [],
|
|
569
|
+
"usesApis": [],
|
|
570
|
+
"extensionsUsed": [],
|
|
571
|
+
"configKeys": [],
|
|
572
|
+
"secrets": [],
|
|
573
|
+
"writeHandlers": [
|
|
574
|
+
"jobs:write:retry",
|
|
575
|
+
"jobs:write:trigger"
|
|
576
|
+
],
|
|
577
|
+
"uiHints": {
|
|
578
|
+
"displayLabel": "Jobs · Audit & Operator UI",
|
|
579
|
+
"category": "operations",
|
|
580
|
+
"recommended": false
|
|
581
|
+
}
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
"name": "legal-pages",
|
|
585
|
+
"description": "Opt-in wrapper around `text-content` that registers four public HTML routes (`/legal/impressum`, `/legal/datenschutz`, `/legal/imprint`, `/legal/privacy`) with Markdown-to-HTML rendering and a boot-time job that hard-fails in production when the required DE blocks (`imprint/de`, `privacy/de`) are not seeded in `SYSTEM_TENANT`. Requires `anonymousAccess: { defaultTenantId: SYSTEM_TENANT_ID }` and `extraContext.textContent` to be wired at app bootstrap; for per-tenant imprints or a custom layout call `text-content:query:by-slug` directly.",
|
|
586
|
+
"toggleableDefault": null,
|
|
587
|
+
"requires": [
|
|
588
|
+
"text-content"
|
|
589
|
+
],
|
|
590
|
+
"optionalRequires": [],
|
|
591
|
+
"configReads": [],
|
|
592
|
+
"exposesApis": [],
|
|
593
|
+
"usesApis": [],
|
|
594
|
+
"extensionsUsed": [],
|
|
595
|
+
"configKeys": [],
|
|
596
|
+
"secrets": [],
|
|
597
|
+
"writeHandlers": [],
|
|
598
|
+
"uiHints": {
|
|
599
|
+
"displayLabel": "Legal Pages",
|
|
600
|
+
"category": "content",
|
|
601
|
+
"recommended": false
|
|
602
|
+
}
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
"name": "mail-foundation",
|
|
606
|
+
"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.",
|
|
607
|
+
"toggleableDefault": null,
|
|
608
|
+
"requires": [
|
|
609
|
+
"config"
|
|
610
|
+
],
|
|
611
|
+
"optionalRequires": [],
|
|
612
|
+
"configReads": [],
|
|
613
|
+
"exposesApis": [],
|
|
614
|
+
"usesApis": [],
|
|
615
|
+
"extensionsUsed": [],
|
|
616
|
+
"configKeys": [
|
|
617
|
+
{
|
|
618
|
+
"key": "provider",
|
|
619
|
+
"qualifiedName": "mail-foundation:config:provider",
|
|
620
|
+
"type": "text",
|
|
621
|
+
"scope": "tenant",
|
|
622
|
+
"default": "",
|
|
623
|
+
"encrypted": false,
|
|
624
|
+
"computed": false,
|
|
625
|
+
"options": null,
|
|
626
|
+
"bounds": null,
|
|
627
|
+
"pattern": null,
|
|
628
|
+
"writeRoles": [
|
|
629
|
+
"TenantAdmin",
|
|
630
|
+
"SystemAdmin"
|
|
631
|
+
],
|
|
632
|
+
"readRoles": [
|
|
633
|
+
"TenantAdmin",
|
|
634
|
+
"SystemAdmin",
|
|
635
|
+
"User"
|
|
636
|
+
]
|
|
637
|
+
}
|
|
638
|
+
],
|
|
639
|
+
"secrets": [],
|
|
640
|
+
"writeHandlers": [],
|
|
641
|
+
"uiHints": {
|
|
642
|
+
"displayLabel": "Mail Transport Foundation",
|
|
643
|
+
"category": "notifications",
|
|
644
|
+
"recommended": false
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
"name": "mail-transport-inmemory",
|
|
649
|
+
"description": "Registers an in-process `\"inmemory\"` provider for `mail-foundation` that buffers sent mails per tenant instead of contacting an SMTP server. Use `getInbox(tenantId)` and `clearInbox(tenantId)` in demo apps and tests; not for production (buffer is process-memory, lost on restart).",
|
|
650
|
+
"toggleableDefault": null,
|
|
651
|
+
"requires": [
|
|
652
|
+
"mail-foundation"
|
|
653
|
+
],
|
|
654
|
+
"optionalRequires": [],
|
|
655
|
+
"configReads": [],
|
|
656
|
+
"exposesApis": [],
|
|
657
|
+
"usesApis": [],
|
|
658
|
+
"extensionsUsed": [
|
|
659
|
+
{
|
|
660
|
+
"extensionName": "mailTransport",
|
|
661
|
+
"entityName": "inmemory"
|
|
662
|
+
}
|
|
663
|
+
],
|
|
664
|
+
"configKeys": [],
|
|
665
|
+
"secrets": [],
|
|
666
|
+
"writeHandlers": []
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
"name": "mail-transport-smtp",
|
|
670
|
+
"description": "Registers itself as the `\"smtp\"` provider for `mail-foundation` and owns the per-tenant config keys (`host`, `port`, `secure`, `from`, `authUser`) and the encrypted `smtp.password` secret. Tenants set `mail-foundation`'s `provider` config key to `\"smtp\"` to activate it; set the SMTP credentials via the admin UI or a seed handler before sending the first mail.",
|
|
671
|
+
"toggleableDefault": null,
|
|
672
|
+
"requires": [
|
|
673
|
+
"config",
|
|
674
|
+
"secrets",
|
|
675
|
+
"mail-foundation"
|
|
676
|
+
],
|
|
677
|
+
"optionalRequires": [],
|
|
678
|
+
"configReads": [],
|
|
679
|
+
"exposesApis": [],
|
|
680
|
+
"usesApis": [],
|
|
681
|
+
"extensionsUsed": [
|
|
682
|
+
{
|
|
683
|
+
"extensionName": "mailTransport",
|
|
684
|
+
"entityName": "smtp"
|
|
685
|
+
}
|
|
686
|
+
],
|
|
687
|
+
"configKeys": [
|
|
688
|
+
{
|
|
689
|
+
"key": "auth-user",
|
|
690
|
+
"qualifiedName": "mail-transport-smtp:config:auth-user",
|
|
691
|
+
"type": "text",
|
|
692
|
+
"scope": "tenant",
|
|
693
|
+
"default": "",
|
|
694
|
+
"encrypted": false,
|
|
695
|
+
"computed": false,
|
|
696
|
+
"options": null,
|
|
697
|
+
"bounds": null,
|
|
698
|
+
"pattern": null,
|
|
699
|
+
"writeRoles": [
|
|
700
|
+
"TenantAdmin",
|
|
701
|
+
"SystemAdmin"
|
|
702
|
+
],
|
|
703
|
+
"readRoles": [
|
|
704
|
+
"TenantAdmin",
|
|
705
|
+
"SystemAdmin"
|
|
706
|
+
]
|
|
707
|
+
},
|
|
708
|
+
{
|
|
709
|
+
"key": "from",
|
|
710
|
+
"qualifiedName": "mail-transport-smtp:config:from",
|
|
711
|
+
"type": "text",
|
|
712
|
+
"scope": "tenant",
|
|
713
|
+
"default": "",
|
|
714
|
+
"encrypted": false,
|
|
715
|
+
"computed": false,
|
|
716
|
+
"options": null,
|
|
717
|
+
"bounds": null,
|
|
718
|
+
"pattern": null,
|
|
719
|
+
"writeRoles": [
|
|
720
|
+
"TenantAdmin",
|
|
721
|
+
"SystemAdmin"
|
|
722
|
+
],
|
|
723
|
+
"readRoles": [
|
|
724
|
+
"TenantAdmin",
|
|
725
|
+
"SystemAdmin"
|
|
726
|
+
]
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
"key": "host",
|
|
730
|
+
"qualifiedName": "mail-transport-smtp:config:host",
|
|
731
|
+
"type": "text",
|
|
732
|
+
"scope": "tenant",
|
|
733
|
+
"default": "",
|
|
734
|
+
"encrypted": false,
|
|
735
|
+
"computed": false,
|
|
736
|
+
"options": null,
|
|
737
|
+
"bounds": null,
|
|
738
|
+
"pattern": null,
|
|
739
|
+
"writeRoles": [
|
|
740
|
+
"TenantAdmin",
|
|
741
|
+
"SystemAdmin"
|
|
742
|
+
],
|
|
743
|
+
"readRoles": [
|
|
744
|
+
"TenantAdmin",
|
|
745
|
+
"SystemAdmin"
|
|
746
|
+
]
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
"key": "port",
|
|
750
|
+
"qualifiedName": "mail-transport-smtp:config:port",
|
|
751
|
+
"type": "number",
|
|
752
|
+
"scope": "tenant",
|
|
753
|
+
"default": 587,
|
|
754
|
+
"encrypted": false,
|
|
755
|
+
"computed": false,
|
|
756
|
+
"options": null,
|
|
757
|
+
"bounds": {
|
|
758
|
+
"min": 1,
|
|
759
|
+
"max": 65535
|
|
760
|
+
},
|
|
761
|
+
"pattern": null,
|
|
762
|
+
"writeRoles": [
|
|
763
|
+
"TenantAdmin",
|
|
764
|
+
"SystemAdmin"
|
|
765
|
+
],
|
|
766
|
+
"readRoles": [
|
|
767
|
+
"all"
|
|
768
|
+
]
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
"key": "secure",
|
|
772
|
+
"qualifiedName": "mail-transport-smtp:config:secure",
|
|
773
|
+
"type": "boolean",
|
|
774
|
+
"scope": "tenant",
|
|
775
|
+
"default": false,
|
|
776
|
+
"encrypted": false,
|
|
777
|
+
"computed": false,
|
|
778
|
+
"options": null,
|
|
779
|
+
"bounds": null,
|
|
780
|
+
"pattern": null,
|
|
781
|
+
"writeRoles": [
|
|
782
|
+
"TenantAdmin",
|
|
783
|
+
"SystemAdmin"
|
|
784
|
+
],
|
|
785
|
+
"readRoles": [
|
|
786
|
+
"all"
|
|
787
|
+
]
|
|
788
|
+
}
|
|
789
|
+
],
|
|
790
|
+
"secrets": [
|
|
791
|
+
{
|
|
792
|
+
"qualifiedName": "mail-transport-smtp:secret:smtp-password",
|
|
793
|
+
"scope": "tenant",
|
|
794
|
+
"label": "SMTP password",
|
|
795
|
+
"hint": "Login password at the SMTP server. Brevo/Postmark/SES call it 'API key' or 'SMTP credentials'."
|
|
796
|
+
}
|
|
797
|
+
],
|
|
798
|
+
"writeHandlers": [],
|
|
799
|
+
"uiHints": {
|
|
800
|
+
"displayLabel": "Mail Transport · SMTP",
|
|
801
|
+
"category": "notifications",
|
|
802
|
+
"recommended": false
|
|
803
|
+
}
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
"name": "managed-pages",
|
|
807
|
+
"description": "Tenant-editable, server-rendered public pages with per-tenant branding. Stores one Markdown `page` per `(tenantId, slug, lang)` in the `read_pages` entity table with a `published` gate plus `description`/`ogImage` SEO meta. Registers an anonymous `GET {basePath}/:slug` route that resolves the tenant from the request Host via the app-supplied `resolveApexTenant`, serves only published pages (drafts → 404), renders Markdown through the hardened `page-render` core, and isolates per-tenant content with `Vary: Host`. Ships TenantAdmin/SystemAdmin admin screens (`entityList` + `entityEdit`) backed by convention CRUD handlers (`managed-pages:write:page:{create,update,delete}`, `managed-pages:query:page:{list,detail}`); the app wires nav/workspace onto `managed-pages:screen:page-list`. Branding (via `config`, scope tenant): `branding-{title,description,site-url,accent-color,logo-url,layout-preset}` keys with write-time validation (hex color, https URLs), a `configEdit` self-service screen (`managed-pages:screen:branding-settings`), and a `managed-pages:query:branding` read that the render path applies as scoped `:root` CSS vars + a logo/title header. Also exposes `managed-pages:write:set` (idempotent slug-keyed upsert, SystemAdmin cross-tenant via `tenantIdOverride`) as a provisioning API. Requires `config` + `anonymousAccess` wired at app bootstrap.",
|
|
808
|
+
"toggleableDefault": null,
|
|
809
|
+
"requires": [
|
|
810
|
+
"config"
|
|
811
|
+
],
|
|
812
|
+
"optionalRequires": [],
|
|
813
|
+
"configReads": [],
|
|
814
|
+
"exposesApis": [],
|
|
815
|
+
"usesApis": [],
|
|
816
|
+
"extensionsUsed": [],
|
|
817
|
+
"configKeys": [
|
|
818
|
+
{
|
|
819
|
+
"key": "branding-accent-color",
|
|
820
|
+
"qualifiedName": "managed-pages:config:branding-accent-color",
|
|
821
|
+
"type": "text",
|
|
822
|
+
"scope": "tenant",
|
|
823
|
+
"default": "",
|
|
824
|
+
"encrypted": false,
|
|
825
|
+
"computed": false,
|
|
826
|
+
"options": null,
|
|
827
|
+
"bounds": null,
|
|
828
|
+
"pattern": {
|
|
829
|
+
"regex": "^$|^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
|
|
830
|
+
},
|
|
831
|
+
"writeRoles": [
|
|
832
|
+
"system",
|
|
833
|
+
"TenantAdmin",
|
|
834
|
+
"Admin",
|
|
835
|
+
"SystemAdmin"
|
|
836
|
+
],
|
|
837
|
+
"readRoles": [
|
|
838
|
+
"all"
|
|
839
|
+
]
|
|
840
|
+
},
|
|
841
|
+
{
|
|
842
|
+
"key": "branding-custom-css",
|
|
843
|
+
"qualifiedName": "managed-pages:config:branding-custom-css",
|
|
844
|
+
"type": "text",
|
|
845
|
+
"scope": "tenant",
|
|
846
|
+
"default": "",
|
|
847
|
+
"encrypted": false,
|
|
848
|
+
"computed": false,
|
|
849
|
+
"options": null,
|
|
850
|
+
"bounds": null,
|
|
851
|
+
"pattern": {
|
|
852
|
+
"regex": "^[\\s\\S]{0,8000}$"
|
|
853
|
+
},
|
|
854
|
+
"writeRoles": [
|
|
855
|
+
"TenantAdmin",
|
|
856
|
+
"Admin",
|
|
857
|
+
"SystemAdmin"
|
|
858
|
+
],
|
|
859
|
+
"readRoles": [
|
|
860
|
+
"all"
|
|
861
|
+
]
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
"key": "branding-description",
|
|
865
|
+
"qualifiedName": "managed-pages:config:branding-description",
|
|
866
|
+
"type": "text",
|
|
867
|
+
"scope": "tenant",
|
|
868
|
+
"default": "",
|
|
869
|
+
"encrypted": false,
|
|
870
|
+
"computed": false,
|
|
871
|
+
"options": null,
|
|
872
|
+
"bounds": null,
|
|
873
|
+
"pattern": {
|
|
874
|
+
"regex": "^[\\s\\S]{0,500}$"
|
|
875
|
+
},
|
|
876
|
+
"writeRoles": [
|
|
877
|
+
"system",
|
|
878
|
+
"TenantAdmin",
|
|
879
|
+
"Admin",
|
|
880
|
+
"SystemAdmin"
|
|
881
|
+
],
|
|
882
|
+
"readRoles": [
|
|
883
|
+
"all"
|
|
884
|
+
]
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
"key": "branding-layout-preset",
|
|
888
|
+
"qualifiedName": "managed-pages:config:branding-layout-preset",
|
|
889
|
+
"type": "select",
|
|
890
|
+
"scope": "tenant",
|
|
891
|
+
"default": "centered",
|
|
892
|
+
"encrypted": false,
|
|
893
|
+
"computed": false,
|
|
894
|
+
"options": [
|
|
895
|
+
"minimal",
|
|
896
|
+
"centered",
|
|
897
|
+
"wide"
|
|
898
|
+
],
|
|
899
|
+
"bounds": null,
|
|
900
|
+
"pattern": null,
|
|
901
|
+
"writeRoles": [
|
|
902
|
+
"system",
|
|
903
|
+
"TenantAdmin",
|
|
904
|
+
"Admin",
|
|
905
|
+
"SystemAdmin"
|
|
906
|
+
],
|
|
907
|
+
"readRoles": [
|
|
908
|
+
"all"
|
|
909
|
+
]
|
|
910
|
+
},
|
|
911
|
+
{
|
|
912
|
+
"key": "branding-logo-url",
|
|
913
|
+
"qualifiedName": "managed-pages:config:branding-logo-url",
|
|
914
|
+
"type": "text",
|
|
915
|
+
"scope": "tenant",
|
|
916
|
+
"default": "",
|
|
917
|
+
"encrypted": false,
|
|
918
|
+
"computed": false,
|
|
919
|
+
"options": null,
|
|
920
|
+
"bounds": null,
|
|
921
|
+
"pattern": {
|
|
922
|
+
"regex": "^$|^https://[^\\s\"'<>]{1,2000}$"
|
|
923
|
+
},
|
|
924
|
+
"writeRoles": [
|
|
925
|
+
"system",
|
|
926
|
+
"TenantAdmin",
|
|
927
|
+
"Admin",
|
|
928
|
+
"SystemAdmin"
|
|
929
|
+
],
|
|
930
|
+
"readRoles": [
|
|
931
|
+
"all"
|
|
932
|
+
]
|
|
933
|
+
},
|
|
934
|
+
{
|
|
935
|
+
"key": "branding-site-url",
|
|
936
|
+
"qualifiedName": "managed-pages:config:branding-site-url",
|
|
937
|
+
"type": "text",
|
|
938
|
+
"scope": "tenant",
|
|
939
|
+
"default": "",
|
|
940
|
+
"encrypted": false,
|
|
941
|
+
"computed": false,
|
|
942
|
+
"options": null,
|
|
943
|
+
"bounds": null,
|
|
944
|
+
"pattern": {
|
|
945
|
+
"regex": "^$|^https://[^\\s\"'<>]{1,2000}$"
|
|
946
|
+
},
|
|
947
|
+
"writeRoles": [
|
|
948
|
+
"system",
|
|
949
|
+
"TenantAdmin",
|
|
950
|
+
"Admin",
|
|
951
|
+
"SystemAdmin"
|
|
952
|
+
],
|
|
953
|
+
"readRoles": [
|
|
954
|
+
"all"
|
|
955
|
+
]
|
|
956
|
+
},
|
|
957
|
+
{
|
|
958
|
+
"key": "branding-title",
|
|
959
|
+
"qualifiedName": "managed-pages:config:branding-title",
|
|
960
|
+
"type": "text",
|
|
961
|
+
"scope": "tenant",
|
|
962
|
+
"default": "",
|
|
963
|
+
"encrypted": false,
|
|
964
|
+
"computed": false,
|
|
965
|
+
"options": null,
|
|
966
|
+
"bounds": null,
|
|
967
|
+
"pattern": {
|
|
968
|
+
"regex": "^[\\s\\S]{0,200}$"
|
|
969
|
+
},
|
|
970
|
+
"writeRoles": [
|
|
971
|
+
"system",
|
|
972
|
+
"TenantAdmin",
|
|
973
|
+
"Admin",
|
|
974
|
+
"SystemAdmin"
|
|
975
|
+
],
|
|
976
|
+
"readRoles": [
|
|
977
|
+
"all"
|
|
978
|
+
]
|
|
979
|
+
}
|
|
980
|
+
],
|
|
981
|
+
"secrets": [],
|
|
982
|
+
"writeHandlers": [
|
|
983
|
+
"managed-pages:write:page:create",
|
|
984
|
+
"managed-pages:write:page:delete",
|
|
985
|
+
"managed-pages:write:page:update",
|
|
986
|
+
"managed-pages:write:set"
|
|
987
|
+
],
|
|
988
|
+
"uiHints": {
|
|
989
|
+
"displayLabel": "Managed Pages · Public CMS",
|
|
990
|
+
"category": "content",
|
|
991
|
+
"recommended": false
|
|
992
|
+
}
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
"name": "rate-limiting",
|
|
996
|
+
"description": "Adds an ops-side `rate-limiting:query:status` query handler for inspecting current bucket state; the actual request throttling is wired automatically by the dispatcher when any handler declares a `rateLimit` option (e.g. `{ per: 'user', limit: 3, windowSeconds: 60 }`) or when you pass `context.rateLimit` to `buildServer`. Loading this feature is optional if you only need L3 per-handler rate limits and have no need for ops introspection.",
|
|
997
|
+
"toggleableDefault": null,
|
|
998
|
+
"requires": [],
|
|
999
|
+
"optionalRequires": [],
|
|
1000
|
+
"configReads": [],
|
|
1001
|
+
"exposesApis": [],
|
|
1002
|
+
"usesApis": [],
|
|
1003
|
+
"extensionsUsed": [],
|
|
1004
|
+
"configKeys": [],
|
|
1005
|
+
"secrets": [],
|
|
1006
|
+
"writeHandlers": [],
|
|
1007
|
+
"uiHints": {
|
|
1008
|
+
"displayLabel": "Rate Limiting · Ops Query",
|
|
1009
|
+
"category": "operations",
|
|
1010
|
+
"recommended": false
|
|
1011
|
+
}
|
|
1012
|
+
},
|
|
1013
|
+
{
|
|
1014
|
+
"name": "readiness",
|
|
1015
|
+
"description": "One-call tenant-onboarding probe: `readiness:query:status` rolls up every config key and secret declared `required: true` across all mounted features and reports which still lack a usable value for the calling tenant, plus a single `ready` boolean. Provider-features under an `r.extensionSelector`-declared extension point count only while their provider is the selected one — a tenant on the inmemory mail transport is not blocked by unset SMTP keys. Mount it (together with `config` and `secrets`) when an admin UI needs a settings checklist before the first mail-send or file-write; the per-concern lists stay available via `config:query:readiness` and `secrets:query:list`.",
|
|
1016
|
+
"toggleableDefault": null,
|
|
1017
|
+
"requires": [
|
|
1018
|
+
"config",
|
|
1019
|
+
"secrets"
|
|
1020
|
+
],
|
|
1021
|
+
"optionalRequires": [],
|
|
1022
|
+
"configReads": [],
|
|
1023
|
+
"exposesApis": [],
|
|
1024
|
+
"usesApis": [],
|
|
1025
|
+
"extensionsUsed": [],
|
|
1026
|
+
"configKeys": [],
|
|
1027
|
+
"secrets": [],
|
|
1028
|
+
"writeHandlers": [],
|
|
1029
|
+
"uiHints": {
|
|
1030
|
+
"displayLabel": "Readiness · Onboarding Probe",
|
|
1031
|
+
"category": "operations",
|
|
1032
|
+
"recommended": false
|
|
1033
|
+
}
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
"name": "renderer-foundation",
|
|
1037
|
+
"description": "Plugin registry for content rendering (notification HTML, mail HTML, PDF, images): call `foundation.createRendererForTenant({ tenantId, kind })` at render time to get the right renderer plugin selected by kind, with tenant-level overrides via the `rendererPluginByKind` config key. Requires `template-resolver` (declared via `r.requires`). Low-level building block — add `renderer-simple` (or write a custom plugin via `r.useExtension(\"renderer\", name, { kinds, render })`) rather than using this feature alone.",
|
|
1038
|
+
"toggleableDefault": null,
|
|
1039
|
+
"requires": [
|
|
1040
|
+
"template-resolver"
|
|
1041
|
+
],
|
|
1042
|
+
"optionalRequires": [],
|
|
1043
|
+
"configReads": [],
|
|
1044
|
+
"exposesApis": [],
|
|
1045
|
+
"usesApis": [],
|
|
1046
|
+
"extensionsUsed": [],
|
|
1047
|
+
"configKeys": [],
|
|
1048
|
+
"secrets": [],
|
|
1049
|
+
"writeHandlers": [],
|
|
1050
|
+
"uiHints": {
|
|
1051
|
+
"displayLabel": "Renderer Foundation",
|
|
1052
|
+
"category": "notifications",
|
|
1053
|
+
"recommended": false
|
|
1054
|
+
}
|
|
1055
|
+
},
|
|
1056
|
+
{
|
|
1057
|
+
"name": "renderer-simple",
|
|
1058
|
+
"description": "Default renderer plugin for `kind=\"notification\"`: takes a structured `EmailTemplateData` variable map (with `header`, `sections[]` of text/button objects, and optional `footer`; falls back to `title`/`body` if no structured fields are present) and returns rendered HTML with inline CSS. Requires `renderer-foundation`; sufficient for plain notification emails — swap it for `renderer-mail-html` if you need MJML/Markdown layouts.",
|
|
1059
|
+
"toggleableDefault": null,
|
|
1060
|
+
"requires": [
|
|
1061
|
+
"renderer-foundation"
|
|
1062
|
+
],
|
|
1063
|
+
"optionalRequires": [],
|
|
1064
|
+
"configReads": [],
|
|
1065
|
+
"exposesApis": [],
|
|
1066
|
+
"usesApis": [],
|
|
1067
|
+
"extensionsUsed": [
|
|
1068
|
+
{
|
|
1069
|
+
"extensionName": "renderer",
|
|
1070
|
+
"entityName": "simple"
|
|
1071
|
+
}
|
|
1072
|
+
],
|
|
1073
|
+
"configKeys": [],
|
|
1074
|
+
"secrets": [],
|
|
1075
|
+
"writeHandlers": [],
|
|
1076
|
+
"uiHints": {
|
|
1077
|
+
"displayLabel": "Renderer · Simple",
|
|
1078
|
+
"category": "notifications",
|
|
1079
|
+
"recommended": false
|
|
1080
|
+
}
|
|
1081
|
+
},
|
|
1082
|
+
{
|
|
1083
|
+
"name": "secrets",
|
|
1084
|
+
"description": "Stores arbitrary per-tenant secrets (API keys, tokens, credentials) encrypted at rest using AES-256 with a KEK loaded from `KUMIKO_SECRETS_MASTER_KEY_V1` (and successive versions for rotation). Read a secret in handlers via `ctx.secrets.get(tenantId, handle)`, which automatically appends a `tenantSecretRead` audit event so every access is traceable. A `rotate` job re-encrypts all envelopes after a KEK version bump.",
|
|
1085
|
+
"toggleableDefault": null,
|
|
1086
|
+
"requires": [],
|
|
1087
|
+
"optionalRequires": [],
|
|
1088
|
+
"configReads": [],
|
|
1089
|
+
"exposesApis": [],
|
|
1090
|
+
"usesApis": [],
|
|
1091
|
+
"extensionsUsed": [],
|
|
1092
|
+
"configKeys": [],
|
|
1093
|
+
"secrets": [],
|
|
1094
|
+
"writeHandlers": [
|
|
1095
|
+
"secrets:write:delete",
|
|
1096
|
+
"secrets:write:set"
|
|
1097
|
+
],
|
|
1098
|
+
"uiHints": {
|
|
1099
|
+
"displayLabel": "Tenant Secrets",
|
|
1100
|
+
"category": "infrastructure",
|
|
1101
|
+
"recommended": true
|
|
1102
|
+
}
|
|
1103
|
+
},
|
|
1104
|
+
{
|
|
1105
|
+
"name": "sessions",
|
|
1106
|
+
"description": "Tracks signed-in clients in the `read_user_sessions` table (one row per JWT, keyed by the `sid`/`jti` claim) and exposes handlers for `mine` (list your sessions), `revoke`, and `revokeAllOthers`. Session creation and revocation on the hot auth path are handled by `createSessionCallbacks()`, wired into `buildServer({ auth: { ... } })` outside the dispatcher; the feature also ships a manual-trigger cleanup job for pruning expired rows and an optional `autoRevokeOnPasswordChange` hook that mass-revokes all sessions for a user whenever their `passwordHash` changes.",
|
|
1107
|
+
"toggleableDefault": null,
|
|
1108
|
+
"requires": [
|
|
1109
|
+
"user"
|
|
1110
|
+
],
|
|
1111
|
+
"optionalRequires": [],
|
|
1112
|
+
"configReads": [],
|
|
1113
|
+
"exposesApis": [
|
|
1114
|
+
"sessions.revokeAllForUser"
|
|
1115
|
+
],
|
|
1116
|
+
"usesApis": [],
|
|
1117
|
+
"extensionsUsed": [],
|
|
1118
|
+
"configKeys": [],
|
|
1119
|
+
"secrets": [],
|
|
1120
|
+
"writeHandlers": [
|
|
1121
|
+
"sessions:write:user-session:revoke",
|
|
1122
|
+
"sessions:write:user-session:revoke-all-for-user",
|
|
1123
|
+
"sessions:write:user-session:revoke-all-others"
|
|
1124
|
+
],
|
|
1125
|
+
"uiHints": {
|
|
1126
|
+
"displayLabel": "Sessions · Server-side Logout",
|
|
1127
|
+
"category": "identity",
|
|
1128
|
+
"recommended": false
|
|
1129
|
+
}
|
|
1130
|
+
},
|
|
1131
|
+
{
|
|
1132
|
+
"name": "step-dispatcher",
|
|
1133
|
+
"description": "Internal system feature that drains deferred Tier-2 side-effects (currently `webhook.send` and `mail.send`) after their originating transaction commits. Listens via `r.multiStreamProjection` on the `kumiko:system:step.dispatch-requested` system event, performs the actual HTTP or mail delivery, then appends `kumiko:system:step.dispatched` or `kumiko:system:step.dispatch-failed` back onto the same stream so the outcome is recorded in the event log without a separate status table. Mount this feature explicitly via `createStepDispatcherFeature()` in your app's feature list alongside any features that use `r.step.webhook.send` or `r.step.mail.send`.",
|
|
1134
|
+
"toggleableDefault": null,
|
|
1135
|
+
"requires": [],
|
|
1136
|
+
"optionalRequires": [],
|
|
1137
|
+
"configReads": [],
|
|
1138
|
+
"exposesApis": [],
|
|
1139
|
+
"usesApis": [],
|
|
1140
|
+
"extensionsUsed": [],
|
|
1141
|
+
"configKeys": [],
|
|
1142
|
+
"secrets": [],
|
|
1143
|
+
"writeHandlers": [],
|
|
1144
|
+
"uiHints": {
|
|
1145
|
+
"displayLabel": "Step Dispatcher · Deferred Side-Effects",
|
|
1146
|
+
"category": "infrastructure",
|
|
1147
|
+
"recommended": false
|
|
1148
|
+
}
|
|
1149
|
+
},
|
|
1150
|
+
{
|
|
1151
|
+
"name": "subscription-mollie",
|
|
1152
|
+
"description": "Mollie payment provider plugin for `billing-foundation`, covering the DACH/EU mid-market use case. Mount via `createSubscriptionMollieFeature({ apiKey, webhookUrl, priceToTier, priceToConfig })` — the factory validates that `priceToTier` and `priceToConfig` keys are identical at boot time. Implements `verifyAndParseWebhook` (lazy Mollie-API fetch + heuristic event-type mapping) and `createCheckoutSession` (customer + first-payment with `sequenceType=\"first\"`); `createPortalSession` and `cancelSubscription` are not available because Mollie has no customer portal and requires a `customerId` that the plugin contract does not carry.",
|
|
1153
|
+
"toggleableDefault": null,
|
|
1154
|
+
"requires": [
|
|
1155
|
+
"billing-foundation"
|
|
1156
|
+
],
|
|
1157
|
+
"optionalRequires": [],
|
|
1158
|
+
"configReads": [],
|
|
1159
|
+
"exposesApis": [],
|
|
1160
|
+
"usesApis": [],
|
|
1161
|
+
"extensionsUsed": [
|
|
1162
|
+
{
|
|
1163
|
+
"extensionName": "subscriptionProvider",
|
|
1164
|
+
"entityName": "mollie"
|
|
1165
|
+
}
|
|
1166
|
+
],
|
|
1167
|
+
"configKeys": [],
|
|
1168
|
+
"secrets": [],
|
|
1169
|
+
"writeHandlers": [],
|
|
1170
|
+
"uiHints": {
|
|
1171
|
+
"displayLabel": "Billing · Mollie",
|
|
1172
|
+
"category": "billing",
|
|
1173
|
+
"recommended": false
|
|
1174
|
+
}
|
|
1175
|
+
},
|
|
1176
|
+
{
|
|
1177
|
+
"name": "subscription-stripe",
|
|
1178
|
+
"description": "Stripe payment provider plugin for `billing-foundation`. Reads its Stripe API key + webhook secret from system config keys with `backing:\"secrets\"` (envelope-encrypted in the secrets store under the system tenant) and a `billingLive` **system config** flag — all at runtime, so keys rotate and prod goes live without a redeploy. The `mask` on each key derives the sysadmin settings screen + nav, so no app wires a hand-written config UI. Mount via `createSubscriptionStripeFeature({ priceToTier })`; the optional `apiKey`/`webhookSecret` options are env→secrets bridge fallbacks. The plugin always mounts — `createCheckoutSession` throws `feature_disabled` unless `billingLive` is true, so sk_test_ keys in prod never produce a live checkout. Implements all four provider methods (webhook verify, checkout, portal, cancel).",
|
|
1179
|
+
"toggleableDefault": null,
|
|
1180
|
+
"requires": [
|
|
1181
|
+
"billing-foundation",
|
|
1182
|
+
"config",
|
|
1183
|
+
"secrets"
|
|
1184
|
+
],
|
|
1185
|
+
"optionalRequires": [],
|
|
1186
|
+
"configReads": [],
|
|
1187
|
+
"exposesApis": [],
|
|
1188
|
+
"usesApis": [],
|
|
1189
|
+
"extensionsUsed": [
|
|
1190
|
+
{
|
|
1191
|
+
"extensionName": "subscriptionProvider",
|
|
1192
|
+
"entityName": "stripe"
|
|
1193
|
+
}
|
|
1194
|
+
],
|
|
1195
|
+
"configKeys": [
|
|
1196
|
+
{
|
|
1197
|
+
"key": "api-key",
|
|
1198
|
+
"qualifiedName": "subscription-stripe:config:api-key",
|
|
1199
|
+
"type": "text",
|
|
1200
|
+
"scope": "system",
|
|
1201
|
+
"default": null,
|
|
1202
|
+
"encrypted": false,
|
|
1203
|
+
"computed": false,
|
|
1204
|
+
"options": null,
|
|
1205
|
+
"bounds": null,
|
|
1206
|
+
"pattern": {
|
|
1207
|
+
"regex": "^(sk|rk)_(test|live)_"
|
|
1208
|
+
},
|
|
1209
|
+
"writeRoles": [
|
|
1210
|
+
"SystemAdmin"
|
|
1211
|
+
],
|
|
1212
|
+
"readRoles": [
|
|
1213
|
+
"TenantAdmin",
|
|
1214
|
+
"Admin",
|
|
1215
|
+
"SystemAdmin"
|
|
1216
|
+
]
|
|
1217
|
+
},
|
|
1218
|
+
{
|
|
1219
|
+
"key": "billing-live",
|
|
1220
|
+
"qualifiedName": "subscription-stripe:config:billing-live",
|
|
1221
|
+
"type": "boolean",
|
|
1222
|
+
"scope": "system",
|
|
1223
|
+
"default": false,
|
|
1224
|
+
"encrypted": false,
|
|
1225
|
+
"computed": false,
|
|
1226
|
+
"options": null,
|
|
1227
|
+
"bounds": null,
|
|
1228
|
+
"pattern": null,
|
|
1229
|
+
"writeRoles": [
|
|
1230
|
+
"system",
|
|
1231
|
+
"SystemAdmin"
|
|
1232
|
+
],
|
|
1233
|
+
"readRoles": [
|
|
1234
|
+
"TenantAdmin",
|
|
1235
|
+
"Admin",
|
|
1236
|
+
"SystemAdmin"
|
|
1237
|
+
]
|
|
1238
|
+
},
|
|
1239
|
+
{
|
|
1240
|
+
"key": "webhook-secret",
|
|
1241
|
+
"qualifiedName": "subscription-stripe:config:webhook-secret",
|
|
1242
|
+
"type": "text",
|
|
1243
|
+
"scope": "system",
|
|
1244
|
+
"default": null,
|
|
1245
|
+
"encrypted": false,
|
|
1246
|
+
"computed": false,
|
|
1247
|
+
"options": null,
|
|
1248
|
+
"bounds": null,
|
|
1249
|
+
"pattern": {
|
|
1250
|
+
"regex": "^whsec_"
|
|
1251
|
+
},
|
|
1252
|
+
"writeRoles": [
|
|
1253
|
+
"SystemAdmin"
|
|
1254
|
+
],
|
|
1255
|
+
"readRoles": [
|
|
1256
|
+
"TenantAdmin",
|
|
1257
|
+
"Admin",
|
|
1258
|
+
"SystemAdmin"
|
|
1259
|
+
]
|
|
1260
|
+
}
|
|
1261
|
+
],
|
|
1262
|
+
"secrets": [],
|
|
1263
|
+
"writeHandlers": [],
|
|
1264
|
+
"uiHints": {
|
|
1265
|
+
"displayLabel": "Billing · Stripe",
|
|
1266
|
+
"category": "billing",
|
|
1267
|
+
"recommended": false
|
|
1268
|
+
}
|
|
1269
|
+
},
|
|
1270
|
+
{
|
|
1271
|
+
"name": "tags",
|
|
1272
|
+
"description": "Generic, host-agnostic tagging for any entity. Owns two event-sourced entities — the per-tenant `tag` catalog (`read_tags`) and `tag-assignment` join rows keyed by (entityType, entityId) (`read_tag_assignments`) — so tagging adds NO column to the host entity and needs no relational pivot or JOIN. Provides write-handlers `create-tag`, `assign-tag` (idempotent), `remove-tag` (idempotent) and list queries for the catalog and the assignments. Read which tags an entity has, or which entities carry a tag, by listing `tag-assignment` filtered on `entityId` or `tagId` and composing in the read-layer. Every path uses one access rule — adopt the host's model with createTagsFeature({ access: { openToAll: true } }) or pin roles with createTagsFeature({ roles }). Pass { toggleable: { default: false } } to make the whole feature tier-gatable via the tier-engine (no host hook).",
|
|
1273
|
+
"toggleableDefault": null,
|
|
1274
|
+
"requires": [],
|
|
1275
|
+
"optionalRequires": [],
|
|
1276
|
+
"configReads": [],
|
|
1277
|
+
"exposesApis": [],
|
|
1278
|
+
"usesApis": [],
|
|
1279
|
+
"extensionsUsed": [],
|
|
1280
|
+
"configKeys": [],
|
|
1281
|
+
"secrets": [],
|
|
1282
|
+
"writeHandlers": [
|
|
1283
|
+
"tags:write:assign-tag",
|
|
1284
|
+
"tags:write:create-tag",
|
|
1285
|
+
"tags:write:remove-tag"
|
|
1286
|
+
],
|
|
1287
|
+
"uiHints": {
|
|
1288
|
+
"displayLabel": "Tags",
|
|
1289
|
+
"category": "data",
|
|
1290
|
+
"recommended": false
|
|
1291
|
+
}
|
|
1292
|
+
},
|
|
1293
|
+
{
|
|
1294
|
+
"name": "template-resolver",
|
|
1295
|
+
"description": "Stores notification and mail templates in the database with a 4-level fallback: tenant+locale → system+locale → tenant+fallback-locale → system+fallback-locale. Call `ctx.templateResolver.resolveTemplate({ tenantId, slug, kind, locale })` at render time; manage templates via the `upsertSystem`, `upsertTenant`, `publish`, and `archive` write handlers. Tenants can override system-default templates without touching application code.",
|
|
1296
|
+
"toggleableDefault": null,
|
|
1297
|
+
"requires": [],
|
|
1298
|
+
"optionalRequires": [],
|
|
1299
|
+
"configReads": [],
|
|
1300
|
+
"exposesApis": [],
|
|
1301
|
+
"usesApis": [],
|
|
1302
|
+
"extensionsUsed": [],
|
|
1303
|
+
"configKeys": [],
|
|
1304
|
+
"secrets": [],
|
|
1305
|
+
"writeHandlers": [
|
|
1306
|
+
"template-resolver:write:archive",
|
|
1307
|
+
"template-resolver:write:publish",
|
|
1308
|
+
"template-resolver:write:upsert-system",
|
|
1309
|
+
"template-resolver:write:upsert-tenant"
|
|
1310
|
+
],
|
|
1311
|
+
"uiHints": {
|
|
1312
|
+
"displayLabel": "Template Resolver",
|
|
1313
|
+
"category": "notifications",
|
|
1314
|
+
"recommended": false
|
|
1315
|
+
}
|
|
1316
|
+
},
|
|
1317
|
+
{
|
|
1318
|
+
"name": "tenant",
|
|
1319
|
+
"description": "Registers the three core multi-tenancy entities — `tenant`, `tenant-membership`, and `tenant-invitation` (DB tables `read_tenants`, `read_tenant_memberships`, and `read_tenant_invitations`) — along with write handlers for create/update/disable/enable/addMember/removeMember/updateMemberRoles and the matching queries. It also declares a set of per-tenant config keys (companyName, timezone, locale, SMTP credentials) and system-only keys (priceModel, maxUsers) via `r.config({ keys: { ... } })`. Use this feature in every multi-tenant app; membership resolution and invitation flows depend on it, and `auth-email-password` requires it.",
|
|
1320
|
+
"toggleableDefault": null,
|
|
1321
|
+
"requires": [
|
|
1322
|
+
"config"
|
|
1323
|
+
],
|
|
1324
|
+
"optionalRequires": [],
|
|
1325
|
+
"configReads": [],
|
|
1326
|
+
"exposesApis": [],
|
|
1327
|
+
"usesApis": [],
|
|
1328
|
+
"extensionsUsed": [],
|
|
1329
|
+
"configKeys": [
|
|
1330
|
+
{
|
|
1331
|
+
"key": "company-name",
|
|
1332
|
+
"qualifiedName": "tenant:config:company-name",
|
|
1333
|
+
"type": "text",
|
|
1334
|
+
"scope": "tenant",
|
|
1335
|
+
"default": "",
|
|
1336
|
+
"encrypted": false,
|
|
1337
|
+
"computed": false,
|
|
1338
|
+
"options": null,
|
|
1339
|
+
"bounds": null,
|
|
1340
|
+
"pattern": null,
|
|
1341
|
+
"writeRoles": [
|
|
1342
|
+
"TenantAdmin",
|
|
1343
|
+
"Admin",
|
|
1344
|
+
"SystemAdmin"
|
|
1345
|
+
],
|
|
1346
|
+
"readRoles": [
|
|
1347
|
+
"all"
|
|
1348
|
+
]
|
|
1349
|
+
},
|
|
1350
|
+
{
|
|
1351
|
+
"key": "locale",
|
|
1352
|
+
"qualifiedName": "tenant:config:locale",
|
|
1353
|
+
"type": "select",
|
|
1354
|
+
"scope": "tenant",
|
|
1355
|
+
"default": "de",
|
|
1356
|
+
"encrypted": false,
|
|
1357
|
+
"computed": false,
|
|
1358
|
+
"options": [
|
|
1359
|
+
"de",
|
|
1360
|
+
"en",
|
|
1361
|
+
"fr",
|
|
1362
|
+
"es"
|
|
1363
|
+
],
|
|
1364
|
+
"bounds": null,
|
|
1365
|
+
"pattern": null,
|
|
1366
|
+
"writeRoles": [
|
|
1367
|
+
"TenantAdmin",
|
|
1368
|
+
"Admin",
|
|
1369
|
+
"SystemAdmin"
|
|
1370
|
+
],
|
|
1371
|
+
"readRoles": [
|
|
1372
|
+
"all"
|
|
1373
|
+
]
|
|
1374
|
+
},
|
|
1375
|
+
{
|
|
1376
|
+
"key": "max-users",
|
|
1377
|
+
"qualifiedName": "tenant:config:max-users",
|
|
1378
|
+
"type": "number",
|
|
1379
|
+
"scope": "system",
|
|
1380
|
+
"default": 50,
|
|
1381
|
+
"encrypted": false,
|
|
1382
|
+
"computed": false,
|
|
1383
|
+
"options": null,
|
|
1384
|
+
"bounds": null,
|
|
1385
|
+
"pattern": null,
|
|
1386
|
+
"writeRoles": [
|
|
1387
|
+
"system"
|
|
1388
|
+
],
|
|
1389
|
+
"readRoles": [
|
|
1390
|
+
"TenantAdmin",
|
|
1391
|
+
"Admin",
|
|
1392
|
+
"SystemAdmin"
|
|
1393
|
+
]
|
|
1394
|
+
},
|
|
1395
|
+
{
|
|
1396
|
+
"key": "price-model",
|
|
1397
|
+
"qualifiedName": "tenant:config:price-model",
|
|
1398
|
+
"type": "select",
|
|
1399
|
+
"scope": "system",
|
|
1400
|
+
"default": "basic",
|
|
1401
|
+
"encrypted": false,
|
|
1402
|
+
"computed": false,
|
|
1403
|
+
"options": [
|
|
1404
|
+
"basic",
|
|
1405
|
+
"pro",
|
|
1406
|
+
"enterprise"
|
|
1407
|
+
],
|
|
1408
|
+
"bounds": null,
|
|
1409
|
+
"pattern": null,
|
|
1410
|
+
"writeRoles": [
|
|
1411
|
+
"system"
|
|
1412
|
+
],
|
|
1413
|
+
"readRoles": [
|
|
1414
|
+
"TenantAdmin",
|
|
1415
|
+
"Admin",
|
|
1416
|
+
"SystemAdmin"
|
|
1417
|
+
]
|
|
1418
|
+
},
|
|
1419
|
+
{
|
|
1420
|
+
"key": "smtp-host",
|
|
1421
|
+
"qualifiedName": "tenant:config:smtp-host",
|
|
1422
|
+
"type": "text",
|
|
1423
|
+
"scope": "tenant",
|
|
1424
|
+
"default": null,
|
|
1425
|
+
"encrypted": false,
|
|
1426
|
+
"computed": false,
|
|
1427
|
+
"options": null,
|
|
1428
|
+
"bounds": null,
|
|
1429
|
+
"pattern": null,
|
|
1430
|
+
"writeRoles": [
|
|
1431
|
+
"SystemAdmin"
|
|
1432
|
+
],
|
|
1433
|
+
"readRoles": [
|
|
1434
|
+
"TenantAdmin",
|
|
1435
|
+
"Admin",
|
|
1436
|
+
"SystemAdmin"
|
|
1437
|
+
]
|
|
1438
|
+
},
|
|
1439
|
+
{
|
|
1440
|
+
"key": "smtp-pass",
|
|
1441
|
+
"qualifiedName": "tenant:config:smtp-pass",
|
|
1442
|
+
"type": "text",
|
|
1443
|
+
"scope": "tenant",
|
|
1444
|
+
"default": null,
|
|
1445
|
+
"encrypted": true,
|
|
1446
|
+
"computed": false,
|
|
1447
|
+
"options": null,
|
|
1448
|
+
"bounds": null,
|
|
1449
|
+
"pattern": null,
|
|
1450
|
+
"writeRoles": [
|
|
1451
|
+
"SystemAdmin"
|
|
1452
|
+
],
|
|
1453
|
+
"readRoles": [
|
|
1454
|
+
"SystemAdmin"
|
|
1455
|
+
]
|
|
1456
|
+
},
|
|
1457
|
+
{
|
|
1458
|
+
"key": "timezone",
|
|
1459
|
+
"qualifiedName": "tenant:config:timezone",
|
|
1460
|
+
"type": "select",
|
|
1461
|
+
"scope": "tenant",
|
|
1462
|
+
"default": "Europe/Berlin",
|
|
1463
|
+
"encrypted": false,
|
|
1464
|
+
"computed": false,
|
|
1465
|
+
"options": [
|
|
1466
|
+
"UTC",
|
|
1467
|
+
"Europe/Berlin",
|
|
1468
|
+
"Europe/London",
|
|
1469
|
+
"Europe/Paris",
|
|
1470
|
+
"Europe/Madrid",
|
|
1471
|
+
"Europe/Rome",
|
|
1472
|
+
"America/New_York",
|
|
1473
|
+
"America/Los_Angeles",
|
|
1474
|
+
"America/Sao_Paulo",
|
|
1475
|
+
"Asia/Tokyo",
|
|
1476
|
+
"Asia/Singapore",
|
|
1477
|
+
"Australia/Sydney"
|
|
1478
|
+
],
|
|
1479
|
+
"bounds": null,
|
|
1480
|
+
"pattern": null,
|
|
1481
|
+
"writeRoles": [
|
|
1482
|
+
"TenantAdmin",
|
|
1483
|
+
"Admin",
|
|
1484
|
+
"SystemAdmin"
|
|
1485
|
+
],
|
|
1486
|
+
"readRoles": [
|
|
1487
|
+
"all"
|
|
1488
|
+
]
|
|
1489
|
+
}
|
|
1490
|
+
],
|
|
1491
|
+
"secrets": [],
|
|
1492
|
+
"writeHandlers": [
|
|
1493
|
+
"tenant:write:add-member",
|
|
1494
|
+
"tenant:write:cancel-invitation",
|
|
1495
|
+
"tenant:write:create",
|
|
1496
|
+
"tenant:write:disable",
|
|
1497
|
+
"tenant:write:enable",
|
|
1498
|
+
"tenant:write:remove-member",
|
|
1499
|
+
"tenant:write:tenant:update",
|
|
1500
|
+
"tenant:write:update",
|
|
1501
|
+
"tenant:write:update-member-roles"
|
|
1502
|
+
],
|
|
1503
|
+
"uiHints": {
|
|
1504
|
+
"displayLabel": "Multi-Tenant Core",
|
|
1505
|
+
"category": "identity",
|
|
1506
|
+
"recommended": true
|
|
1507
|
+
}
|
|
1508
|
+
},
|
|
1509
|
+
{
|
|
1510
|
+
"name": "text-content",
|
|
1511
|
+
"description": "Generic Markdown text store keyed by `(tenantId, slug, lang)` — one row per combination in the `read_text_blocks` entity table. Provides `text-content:write:set` (TenantAdmin upsert) and `text-content:query:by-slug` (anonymous-capable read); use `SYSTEM_TENANT_ID` as the tenant for app-wide texts such as imprint, privacy policy, or FAQ. Other features (e.g. `legal-pages`) read blocks without a direct code import via the `createTextContentApi` / `requireTextContent` extraContext pattern.",
|
|
1512
|
+
"toggleableDefault": null,
|
|
1513
|
+
"requires": [],
|
|
1514
|
+
"optionalRequires": [],
|
|
1515
|
+
"configReads": [],
|
|
1516
|
+
"exposesApis": [],
|
|
1517
|
+
"usesApis": [],
|
|
1518
|
+
"extensionsUsed": [],
|
|
1519
|
+
"configKeys": [],
|
|
1520
|
+
"secrets": [],
|
|
1521
|
+
"writeHandlers": [
|
|
1522
|
+
"text-content:write:set"
|
|
1523
|
+
],
|
|
1524
|
+
"uiHints": {
|
|
1525
|
+
"displayLabel": "Text Content · Markdown Store",
|
|
1526
|
+
"category": "content",
|
|
1527
|
+
"recommended": false
|
|
1528
|
+
}
|
|
1529
|
+
},
|
|
1530
|
+
{
|
|
1531
|
+
"name": "tier-engine",
|
|
1532
|
+
"description": "Stores a `tier-assignment` entity per tenant (which pricing tier is active) and, when configured with a `TierMap`, registers itself as the `tenantTierResolver` extension so the dispatcher automatically gates `r.toggleable()` features per tenant based on their assigned tier. Call `createTierEngineFeature({ defaultTier, tierMap })` to get full tier composition — including an `inTransaction` entity hook that atomically writes the default tier when a new tenant is created — or use `createTierEngineFeature()` without options for storage-only mode when you manage tier assignment yourself via `composeApp`. A SystemAdmin-only `set-tenant-tier` write plus `get-tenant-tier`/`tier-options` reads let an operator assign a tier to ANY tenant manually — without a billing purchase — stamping `source: \"manual\"` so a future Stripe→tier sync won't overwrite the grant. Apps surface this via the `tier-admin` screen.",
|
|
1533
|
+
"toggleableDefault": null,
|
|
1534
|
+
"requires": [
|
|
1535
|
+
"config",
|
|
1536
|
+
"tenant"
|
|
1537
|
+
],
|
|
1538
|
+
"optionalRequires": [],
|
|
1539
|
+
"configReads": [],
|
|
1540
|
+
"exposesApis": [],
|
|
1541
|
+
"usesApis": [],
|
|
1542
|
+
"extensionsUsed": [],
|
|
1543
|
+
"configKeys": [],
|
|
1544
|
+
"secrets": [],
|
|
1545
|
+
"writeHandlers": [
|
|
1546
|
+
"tier-engine:write:set-tenant-tier",
|
|
1547
|
+
"tier-engine:write:tier-assignment:create",
|
|
1548
|
+
"tier-engine:write:tier-assignment:update"
|
|
1549
|
+
],
|
|
1550
|
+
"uiHints": {
|
|
1551
|
+
"displayLabel": "Tier Engine · Plan Composition",
|
|
1552
|
+
"category": "billing",
|
|
1553
|
+
"recommended": false
|
|
1554
|
+
}
|
|
1555
|
+
},
|
|
1556
|
+
{
|
|
1557
|
+
"name": "user",
|
|
1558
|
+
"description": "Manages the cross-tenant user identity: the `read_users` table holds each user's email, `displayName`, global `roles`, `emailVerified` flag, and lifecycle `status` (active / restricted / deletionRequested / deleted). Because users exist above any individual tenant, the feature runs with `r.systemScope()` — membership and tenant-specific roles live in the `tenant` feature instead. Add this feature whenever your app needs a persistent, tenant-agnostic user record that auth and GDPR pipelines can reference.",
|
|
1559
|
+
"toggleableDefault": null,
|
|
1560
|
+
"requires": [],
|
|
1561
|
+
"optionalRequires": [],
|
|
1562
|
+
"configReads": [],
|
|
1563
|
+
"exposesApis": [],
|
|
1564
|
+
"usesApis": [],
|
|
1565
|
+
"extensionsUsed": [],
|
|
1566
|
+
"configKeys": [],
|
|
1567
|
+
"secrets": [],
|
|
1568
|
+
"writeHandlers": [
|
|
1569
|
+
"user:write:user:create",
|
|
1570
|
+
"user:write:user:update"
|
|
1571
|
+
],
|
|
1572
|
+
"uiHints": {
|
|
1573
|
+
"displayLabel": "User Identity",
|
|
1574
|
+
"category": "identity",
|
|
1575
|
+
"recommended": true
|
|
1576
|
+
}
|
|
1577
|
+
},
|
|
1578
|
+
{
|
|
1579
|
+
"name": "user-data-rights",
|
|
1580
|
+
"description": "Implements GDPR Art. 15 (access / `my-audit-log` query), Art. 17 (erasure / `request-deletion` + `cancel-deletion`, plus the anonymous email-verified `request-deletion-by-email` + `confirm-deletion-by-token` flow for lockout-safe self-service, + cron cleanup with grace period), Art. 18 (restriction / `restrict-account` + `lift-restriction`), and Art. 20 (portability / async `request-export` → ZIP via `file-foundation`, Magic-Link download) as first-class HTTP handlers and cron jobs. Each domain feature opts in by calling `r.useExtension(EXT_USER_DATA, \"<entity>\", { export, delete })` — the feature then orchestrates the export and forget pipelines across all registered hooks automatically. Requires `user`, `data-retention`, `compliance-profiles`, and `sessions`.",
|
|
1581
|
+
"toggleableDefault": null,
|
|
1582
|
+
"requires": [
|
|
1583
|
+
"user",
|
|
1584
|
+
"data-retention",
|
|
1585
|
+
"compliance-profiles",
|
|
1586
|
+
"sessions"
|
|
1587
|
+
],
|
|
1588
|
+
"optionalRequires": [],
|
|
1589
|
+
"configReads": [],
|
|
1590
|
+
"exposesApis": [
|
|
1591
|
+
"userDataRights.runForget",
|
|
1592
|
+
"userDataRights.runExport"
|
|
1593
|
+
],
|
|
1594
|
+
"usesApis": [
|
|
1595
|
+
"compliance.forTenant",
|
|
1596
|
+
"retention.policyFor",
|
|
1597
|
+
"sessions.revokeAllForUser"
|
|
1598
|
+
],
|
|
1599
|
+
"extensionsUsed": [],
|
|
1600
|
+
"configKeys": [],
|
|
1601
|
+
"secrets": [],
|
|
1602
|
+
"writeHandlers": [
|
|
1603
|
+
"user-data-rights:write:cancel-deletion",
|
|
1604
|
+
"user-data-rights:write:confirm-deletion-by-token",
|
|
1605
|
+
"user-data-rights:write:lift-restriction",
|
|
1606
|
+
"user-data-rights:write:request-deletion",
|
|
1607
|
+
"user-data-rights:write:request-deletion-by-email",
|
|
1608
|
+
"user-data-rights:write:request-export",
|
|
1609
|
+
"user-data-rights:write:restrict-account",
|
|
1610
|
+
"user-data-rights:write:run-forget-cleanup"
|
|
1611
|
+
],
|
|
1612
|
+
"uiHints": {
|
|
1613
|
+
"displayLabel": "User Data Rights · GDPR",
|
|
1614
|
+
"category": "compliance",
|
|
1615
|
+
"recommended": false
|
|
1616
|
+
}
|
|
1617
|
+
},
|
|
1618
|
+
{
|
|
1619
|
+
"name": "user-data-rights-defaults",
|
|
1620
|
+
"description": "Registers ready-made `EXT_USER_DATA` export and delete hooks for the two core entities: `user` (delete strategy sets email to `deleted-<id>@anonymized.invalid`, nulls `passwordHash`, sets status to `Deleted`; anonymize strategy sets email to `anonymized-<id>@anonymized.invalid` without touching `passwordHash`) and `fileRef` (delete removes both the DB row and the storage binary). Mount this alongside `user-data-rights` for standard GDPR compliance; omit it only if your app needs custom anonymization logic for these entities.",
|
|
1621
|
+
"toggleableDefault": null,
|
|
1622
|
+
"requires": [
|
|
1623
|
+
"user",
|
|
1624
|
+
"files",
|
|
1625
|
+
"user-data-rights"
|
|
1626
|
+
],
|
|
1627
|
+
"optionalRequires": [],
|
|
1628
|
+
"configReads": [],
|
|
1629
|
+
"exposesApis": [],
|
|
1630
|
+
"usesApis": [],
|
|
1631
|
+
"extensionsUsed": [
|
|
1632
|
+
{
|
|
1633
|
+
"extensionName": "userData",
|
|
1634
|
+
"entityName": "user"
|
|
1635
|
+
},
|
|
1636
|
+
{
|
|
1637
|
+
"extensionName": "userData",
|
|
1638
|
+
"entityName": "fileRef"
|
|
1639
|
+
}
|
|
1640
|
+
],
|
|
1641
|
+
"configKeys": [],
|
|
1642
|
+
"secrets": [],
|
|
1643
|
+
"writeHandlers": [],
|
|
1644
|
+
"uiHints": {
|
|
1645
|
+
"displayLabel": "User Data Rights · Default Hooks",
|
|
1646
|
+
"category": "compliance",
|
|
1647
|
+
"recommended": false
|
|
1648
|
+
}
|
|
1649
|
+
},
|
|
1650
|
+
{
|
|
1651
|
+
"name": "user-profile",
|
|
1652
|
+
"description": "Self-service account page building blocks: a `change-email` write handler (re-auth via current password, uniqueness check, resets emailVerified and expects the app to trigger the verification flow) plus the ProfileScreen web component that composes change-password (auth-email-password), change-email and account deletion (user-data-rights request/cancel with grace period) into one screen. Apps register the screen as `type: \"custom\"` with `__component: \"UserProfileScreen\"`. Requires `user`, `auth-email-password`, and `user-data-rights`.",
|
|
1653
|
+
"toggleableDefault": null,
|
|
1654
|
+
"requires": [
|
|
1655
|
+
"user",
|
|
1656
|
+
"auth-email-password",
|
|
1657
|
+
"user-data-rights"
|
|
1658
|
+
],
|
|
1659
|
+
"optionalRequires": [],
|
|
1660
|
+
"configReads": [],
|
|
1661
|
+
"exposesApis": [],
|
|
1662
|
+
"usesApis": [],
|
|
1663
|
+
"extensionsUsed": [],
|
|
1664
|
+
"configKeys": [],
|
|
1665
|
+
"secrets": [],
|
|
1666
|
+
"writeHandlers": [
|
|
1667
|
+
"user-profile:write:change-email"
|
|
1668
|
+
],
|
|
1669
|
+
"uiHints": {
|
|
1670
|
+
"displayLabel": "User Profile · Self-Service",
|
|
1671
|
+
"category": "identity",
|
|
1672
|
+
"recommended": true
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
]
|
|
1676
|
+
}
|