@cosmicdrift/kumiko-samples 0.365.1 → 0.367.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/package.json +1 -1
- package/packages/bundled-features/package.json +1 -1
- package/packages/bundled-features/src/admin-shell/feature.ts +9 -9
- package/packages/bundled-features/src/agent-tools/changes.json +6 -0
- package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +87 -5
- package/packages/bundled-features/src/audit/feature.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/changes.json +13 -0
- package/packages/bundled-features/src/auth-email-password/constants.ts +0 -2
- package/packages/bundled-features/src/auth-email-password/feature.ts +3 -3
- package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +1 -0
- package/packages/bundled-features/src/auth-mfa/feature.ts +4 -4
- package/packages/bundled-features/src/auth-mfa-user-data/index.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/feature.ts +1 -1
- package/packages/bundled-features/src/cap-counter/cap-reservation.ts +89 -28
- package/packages/bundled-features/src/cap-counter/changes.json +7 -0
- package/packages/bundled-features/src/cap-counter/constants.ts +3 -0
- package/packages/bundled-features/src/cap-counter/feature.ts +2 -2
- package/packages/bundled-features/src/cap-counter/tables.ts +5 -2
- package/packages/bundled-features/src/cap-overview/feature.ts +3 -3
- package/packages/bundled-features/src/channel-discord/feature.ts +2 -2
- package/packages/bundled-features/src/channel-email/changes.json +6 -0
- package/packages/bundled-features/src/channel-email/email-channel.ts +12 -2
- package/packages/bundled-features/src/channel-email/feature.ts +2 -2
- package/packages/bundled-features/src/channel-in-app/feature.ts +1 -1
- package/packages/bundled-features/src/channel-push/feature.ts +1 -1
- package/packages/bundled-features/src/channel-slack/feature.ts +2 -2
- package/packages/bundled-features/src/channel-teams/feature.ts +2 -2
- package/packages/bundled-features/src/channel-telegram/feature.ts +2 -2
- package/packages/bundled-features/src/compliance-profiles/feature.ts +1 -1
- package/packages/bundled-features/src/compliance-profiles-ops/index.ts +3 -3
- package/packages/bundled-features/src/config/feature.ts +1 -1
- package/packages/bundled-features/src/crypto-shredding/changes.json +6 -0
- package/packages/bundled-features/src/custom-fields/feature.ts +2 -2
- package/packages/bundled-features/src/custom-fields/wire-for-entity.ts +17 -13
- package/packages/bundled-features/src/data-retention/feature.ts +1 -1
- package/packages/bundled-features/src/data-retention/presets.ts +4 -4
- package/packages/bundled-features/src/delivery/changes.json +18 -0
- package/packages/bundled-features/src/delivery/channel-context.ts +1 -0
- package/packages/bundled-features/src/delivery/db/queries/address-opt-outs.ts +6 -2
- package/packages/bundled-features/src/delivery/db/queries/preferences.ts +4 -4
- package/packages/bundled-features/src/delivery/delivery-service.ts +71 -4
- package/packages/bundled-features/src/delivery/feature.ts +10 -6
- package/packages/bundled-features/src/delivery/handlers/resubscribe-address.write.ts +12 -8
- package/packages/bundled-features/src/delivery/handlers/resubscribe-user.write.ts +3 -1
- package/packages/bundled-features/src/delivery/handlers/set-preference.write.ts +3 -1
- package/packages/bundled-features/src/delivery/handlers/unsubscribe-address.write.ts +13 -6
- package/packages/bundled-features/src/delivery/handlers/unsubscribe-user.write.ts +3 -1
- package/packages/bundled-features/src/delivery/index.ts +6 -0
- package/packages/bundled-features/src/delivery/notification-type-aliases.ts +13 -0
- package/packages/bundled-features/src/delivery/testing.ts +2 -0
- package/packages/bundled-features/src/delivery/types.ts +4 -0
- package/packages/bundled-features/src/delivery/unsubscribe-link.ts +73 -0
- package/packages/bundled-features/src/delivery/unsubscribe-page-strings.ts +17 -0
- package/packages/bundled-features/src/delivery/unsubscribe.ts +94 -31
- package/packages/bundled-features/src/derivatives-sharp/feature.ts +1 -1
- package/packages/bundled-features/src/document-ingest-foundation/feature.ts +2 -2
- package/packages/bundled-features/src/file-derivatives/feature.ts +3 -3
- package/packages/bundled-features/src/file-foundation/feature.ts +1 -1
- package/packages/bundled-features/src/file-provider-inmemory/feature.ts +2 -2
- package/packages/bundled-features/src/file-provider-s3/feature.ts +3 -3
- package/packages/bundled-features/src/file-provider-s3-env/feature.ts +2 -2
- package/packages/bundled-features/src/files/README.md +1 -1
- package/packages/bundled-features/src/files-tenant-data/index.ts +1 -1
- package/packages/bundled-features/src/folders/feature.ts +2 -2
- package/packages/bundled-features/src/folders-user-data/index.ts +1 -1
- package/packages/bundled-features/src/form-draft/feature.ts +2 -2
- package/packages/bundled-features/src/form-draft-user-data/index.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/changes.json +13 -0
- package/packages/bundled-features/src/inbound-mail-foundation/connect-routes.ts +9 -1
- package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/types.ts +19 -7
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +21 -2
- package/packages/bundled-features/src/inbound-provider-imap/feature.ts +2 -2
- package/packages/bundled-features/src/inbound-provider-inmemory/feature.ts +1 -1
- package/packages/bundled-features/src/ledger/feature.ts +5 -5
- package/packages/bundled-features/src/legal-pages/feature.ts +1 -1
- package/packages/bundled-features/src/mail-foundation/feature.ts +51 -10
- package/packages/bundled-features/src/mail-foundation/index.ts +3 -0
- package/packages/bundled-features/src/mail-transport-inmemory/feature.ts +2 -2
- package/packages/bundled-features/src/mail-transport-smtp/feature.ts +3 -3
- package/packages/bundled-features/src/managed-pages/css-gate.ts +1 -1
- package/packages/bundled-features/src/managed-pages/feature.ts +2 -2
- package/packages/bundled-features/src/metrics/feature.ts +2 -2
- package/packages/bundled-features/src/notes-history/feature.ts +2 -2
- package/packages/bundled-features/src/notes-history-user-data/index.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/feature.ts +15 -11
- package/packages/bundled-features/src/readiness/feature.ts +2 -2
- package/packages/bundled-features/src/renderer-foundation/README.md +1 -1
- package/packages/bundled-features/src/renderer-foundation/feature.ts +1 -1
- package/packages/bundled-features/src/renderer-pdf/feature.ts +1 -1
- package/packages/bundled-features/src/secrets/feature.ts +1 -1
- package/packages/bundled-features/src/seo/feature.ts +1 -1
- package/packages/bundled-features/src/sessions/feature.ts +17 -13
- package/packages/bundled-features/src/step-dispatcher/feature.ts +1 -1
- package/packages/bundled-features/src/subscription-mollie/feature.ts +1 -1
- package/packages/bundled-features/src/subscription-stripe/feature.ts +3 -3
- package/packages/bundled-features/src/tags/feature.ts +2 -2
- package/packages/bundled-features/src/template-resolver/changes.json +25 -0
- package/packages/bundled-features/src/template-resolver/compile-document-body.ts +61 -9
- package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +19 -0
- package/packages/bundled-features/src/template-resolver/document-schema.ts +33 -0
- package/packages/bundled-features/src/template-resolver/feature.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +9 -1
- package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +8 -2
- package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +8 -1
- package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +6 -1
- package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +7 -2
- package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +21 -1
- package/packages/bundled-features/src/template-resolver/index.ts +4 -0
- package/packages/bundled-features/src/template-resolver/qualified-names.ts +1 -0
- package/packages/bundled-features/src/template-resolver/seeding.ts +54 -7
- package/packages/bundled-features/src/template-resolver/template-preview.ts +2 -0
- package/packages/bundled-features/src/template-resolver/template-system-only.ts +21 -0
- package/packages/bundled-features/src/template-resolver/template-validation.ts +48 -24
- package/packages/bundled-features/src/template-resolver/web/i18n.ts +2 -0
- package/packages/bundled-features/src/template-resolver-user-data/index.ts +1 -1
- package/packages/bundled-features/src/tenant/feature.ts +4 -4
- package/packages/bundled-features/src/tenant/seeding.ts +1 -1
- package/packages/bundled-features/src/tenant-lifecycle/feature.ts +2 -3
- package/packages/bundled-features/src/tenant-settings/feature.ts +1 -1
- package/packages/bundled-features/src/tier-engine/feature.ts +36 -27
- package/packages/bundled-features/src/user/feature.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/README.md +1 -1
- package/packages/bundled-features/src/user-data-rights/changes.json +6 -0
- package/packages/bundled-features/src/user-data-rights/feature.ts +4 -4
- package/packages/bundled-features/src/user-data-rights-defaults/feature.ts +2 -2
- package/packages/bundled-features/src/user-profile/feature.ts +4 -4
- package/packages/bundled-features/src/waitlist/feature.ts +2 -2
- package/samples/apps/cap-billing-demo/README.md +1 -1
- package/samples/apps/cap-billing-demo/src/feature.ts +4 -4
- package/samples/apps/cap-billing-demo/src/run-config.ts +1 -1
- package/samples/apps/config-demo/src/features/demo/feature.ts +2 -2
- package/samples/apps/styleguide/src/features/ai-settings/feature.ts +43 -28
- package/samples/apps/styleguide/src/features/demo/feature.ts +18 -18
- package/samples/apps/styleguide/src/features/widgets/ai-text-demo.ts +10 -12
- package/samples/apps/styleguide/src/features/widgets/feature.ts +90 -108
- package/samples/apps/ui-walkthrough/src/app/seed-templates.ts +2 -0
- package/samples/apps/ui-walkthrough/src/app/template-seed-data.ts +12 -0
- package/samples/apps/ui-walkthrough/src/features/billing/feature.ts +7 -0
- package/samples/apps/ui-walkthrough/src/features/billing/i18n.ts +2 -0
- package/samples/apps/ui-walkthrough/src/features/rental/feature.ts +36 -36
- package/samples/apps/ui-walkthrough/src/features/vehicles/feature.ts +6 -6
- package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +2 -2
- package/samples/apps/use-all-bundled/feature-manifest.json +4 -4
- package/samples/apps/use-all-bundled/package.json +1 -0
- package/samples/apps/use-all-bundled/scripts/gen-sample-index.ts +7 -7
- package/samples/apps/use-all-bundled/src/app/notes-feature.ts +3 -3
- package/samples/apps/use-all-bundled/src/app/screens-feature.ts +1 -1
- package/samples/apps/user-data-rights-demo/src/feature.ts +5 -5
- package/samples/recipes/access-control/src/feature.ts +21 -25
- package/samples/recipes/anonymous-access/src/feature.ts +36 -43
- package/samples/recipes/anonymous-access-multitenant/src/feature.ts +14 -16
- package/samples/recipes/auth-foundation-providers/src/feature.ts +1 -1
- package/samples/recipes/basic-entity/README.md +1 -1
- package/samples/recipes/boot-check/README.md +1 -1
- package/samples/recipes/boot-check/src/feature.ts +2 -2
- package/samples/recipes/chat-channels/src/feature.ts +1 -1
- package/samples/recipes/cross-feature-events/src/feature.ts +7 -7
- package/samples/recipes/currencies-global/src/feature.ts +3 -3
- package/samples/recipes/currencies-per-tenant/src/feature.ts +2 -2
- package/samples/recipes/custom-fields-basic/README.md +1 -1
- package/samples/recipes/custom-fields-basic/src/feature.ts +3 -2
- package/samples/recipes/custom-handlers/README.md +3 -3
- package/samples/recipes/custom-handlers/src/feature.ts +37 -41
- package/samples/recipes/dashboard-stats/src/feature.ts +6 -6
- package/samples/recipes/delivery-notifications/README.md +1 -1
- package/samples/recipes/delivery-notifications/src/feature.ts +8 -8
- package/samples/recipes/designer-demo/src/feature.ts +2 -1
- package/samples/recipes/embedded/src/feature.ts +1 -1
- package/samples/recipes/embedded-entity-form/src/feature.ts +13 -13
- package/samples/recipes/encrypted-tenant-config/src/feature.ts +1 -1
- package/samples/recipes/error-contract/src/feature.ts +25 -25
- package/samples/recipes/event-sourcing/src/feature.ts +70 -76
- package/samples/recipes/feature-toggles/src/feature.ts +28 -20
- package/samples/recipes/files-post-processing/src/feature.ts +22 -26
- package/samples/recipes/idempotency/src/feature.ts +16 -18
- package/samples/recipes/inbound-mail-triage/src/feature.ts +1 -1
- package/samples/recipes/lane-routing/src/feature.ts +10 -12
- package/samples/recipes/legal-pages/src/feature.ts +1 -1
- package/samples/recipes/lifecycle-hooks/README.md +4 -4
- package/samples/recipes/lifecycle-hooks/src/feature.ts +55 -35
- package/samples/recipes/managed-config/src/feature.ts +8 -8
- package/samples/recipes/managed-pages/src/feature.ts +1 -1
- package/samples/recipes/notes-history-basic/README.md +1 -1
- package/samples/recipes/notes-history-basic/src/feature.ts +2 -2
- package/samples/recipes/pdf-audit-export/src/feature.ts +7 -7
- package/samples/recipes/pipeline-basics/README.md +1 -1
- package/samples/recipes/pipeline-basics/src/feature.ts +7 -7
- package/samples/recipes/public-share-token/src/feature.ts +1 -1
- package/samples/recipes/rate-limiting/src/feature.ts +7 -9
- package/samples/recipes/record-detail-layout/src/feature.ts +18 -18
- package/samples/recipes/reference-data/src/feature.ts +1 -1
- package/samples/recipes/relations/src/feature.ts +2 -2
- package/samples/recipes/screens-nav/src/feature.ts +1 -1
- package/samples/recipes/search/src/feature.ts +17 -19
- package/samples/recipes/security-baseline/README.md +1 -1
- package/samples/recipes/state-machine/src/feature.ts +1 -1
- package/samples/recipes/tags-basic/README.md +1 -1
- package/samples/recipes/tags-basic/src/feature.ts +2 -2
- package/samples/recipes/tenant-settings/README.md +1 -1
- package/samples/recipes/tenant-settings/src/feature.ts +1 -1
- package/samples/recipes/timezones/src/feature.ts +12 -12
- package/samples/recipes/user-data-rights/README.md +2 -2
- package/samples/recipes/user-data-rights/src/feature.ts +1 -1
- package/samples/recipes/user-profile/src/feature.ts +1 -1
- package/samples/recipes/webhook-step/src/feature.ts +1 -1
- package/samples/recipes/writeform-section/src/feature.ts +12 -12
|
@@ -6,8 +6,12 @@ import type {
|
|
|
6
6
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
7
7
|
import type { Element as ScanElement } from "happy-dom";
|
|
8
8
|
import * as z from "zod";
|
|
9
|
-
import { withParsedDocument } from "./document-sanitizer.js";
|
|
10
|
-
import {
|
|
9
|
+
import { conditionScopeElement, withParsedDocument } from "./document-sanitizer.js";
|
|
10
|
+
import {
|
|
11
|
+
DOCUMENT_CONDITION_ATTRIBUTE,
|
|
12
|
+
isDocumentPlaceholderHref,
|
|
13
|
+
isDocumentReferenceName,
|
|
14
|
+
} from "./document-schema.js";
|
|
11
15
|
import type { TemplateMeta } from "./template-meta.js";
|
|
12
16
|
|
|
13
17
|
export const SYSTEM_PLACEHOLDERS: ReadonlySet<string> = new Set([
|
|
@@ -56,6 +60,7 @@ const scalarVariableSchema = z.discriminatedUnion("type", [
|
|
|
56
60
|
.optional(),
|
|
57
61
|
}),
|
|
58
62
|
z.object({ ...variableCommon, type: z.literal("date"), example: textLikeExample }),
|
|
63
|
+
z.object({ ...variableCommon, type: z.literal("percent"), example: z.number().optional() }),
|
|
59
64
|
]);
|
|
60
65
|
|
|
61
66
|
const variableDefSchema = z.union([
|
|
@@ -154,30 +159,49 @@ const NAME_ATTRIBUTE_BY_TAG: Readonly<Record<string, string>> = {
|
|
|
154
159
|
div: "data-steps",
|
|
155
160
|
};
|
|
156
161
|
|
|
162
|
+
function scanRepeatRows(root: ScanElement, scope: PlaceholderScope): void {
|
|
163
|
+
for (const element of Array.from(root.querySelectorAll("tr[data-each]"))) {
|
|
164
|
+
const listName = element.getAttribute("data-each") ?? "";
|
|
165
|
+
const def = Object.hasOwn(scope.variables, listName) ? scope.variables[listName] : undefined;
|
|
166
|
+
if (def?.type === "list") scope.used.add(listName);
|
|
167
|
+
else scope.unknown.add(listName);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function scanConditions(root: ScanElement, scope: PlaceholderScope): void {
|
|
172
|
+
for (const element of Array.from(root.querySelectorAll(`[${DOCUMENT_CONDITION_ATTRIBUTE}]`))) {
|
|
173
|
+
const itemFields = listItemFields(
|
|
174
|
+
scope.variables,
|
|
175
|
+
enclosingEachName(conditionScopeElement(element)),
|
|
176
|
+
);
|
|
177
|
+
recordName(element.getAttribute(DOCUMENT_CONDITION_ATTRIBUTE) ?? "", itemFields, scope);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function scanReferences(root: ScanElement, scope: PlaceholderScope): void {
|
|
182
|
+
for (const element of Array.from(
|
|
183
|
+
root.querySelectorAll("span[data-variable], div[data-steps], a[href], img[data-resource]"),
|
|
184
|
+
)) {
|
|
185
|
+
const itemFields = listItemFields(scope.variables, enclosingEachName(element));
|
|
186
|
+
const tag = element.tagName.toLowerCase();
|
|
187
|
+
const nameAttribute = NAME_ATTRIBUTE_BY_TAG[tag];
|
|
188
|
+
if (nameAttribute !== undefined) {
|
|
189
|
+
recordName(element.getAttribute(nameAttribute) ?? "", itemFields, scope);
|
|
190
|
+
} else if (tag === "a") {
|
|
191
|
+
const href = element.getAttribute("href") ?? "";
|
|
192
|
+
if (isDocumentPlaceholderHref(href)) recordName(href.slice(2, -2), itemFields, scope);
|
|
193
|
+
} else {
|
|
194
|
+
const key = element.getAttribute("data-resource") ?? "";
|
|
195
|
+
if (!scope.resourceKeys.has(key)) scope.unknownResources.add(key);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
157
200
|
function scanBodyPlaceholders(body: string, scope: PlaceholderScope): void {
|
|
158
201
|
withParsedDocument(body, (root) => {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (def?.type === "list") scope.used.add(listName);
|
|
163
|
-
else scope.unknown.add(listName);
|
|
164
|
-
}
|
|
165
|
-
for (const element of Array.from(
|
|
166
|
-
root.querySelectorAll("span[data-variable], div[data-steps], a[href], img[data-resource]"),
|
|
167
|
-
)) {
|
|
168
|
-
const itemFields = listItemFields(scope.variables, enclosingEachName(element));
|
|
169
|
-
const tag = element.tagName.toLowerCase();
|
|
170
|
-
const nameAttribute = NAME_ATTRIBUTE_BY_TAG[tag];
|
|
171
|
-
if (nameAttribute !== undefined) {
|
|
172
|
-
recordName(element.getAttribute(nameAttribute) ?? "", itemFields, scope);
|
|
173
|
-
} else if (tag === "a") {
|
|
174
|
-
const href = element.getAttribute("href") ?? "";
|
|
175
|
-
if (isDocumentPlaceholderHref(href)) recordName(href.slice(2, -2), itemFields, scope);
|
|
176
|
-
} else {
|
|
177
|
-
const key = element.getAttribute("data-resource") ?? "";
|
|
178
|
-
if (!scope.resourceKeys.has(key)) scope.unknownResources.add(key);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
202
|
+
scanRepeatRows(root, scope);
|
|
203
|
+
scanConditions(root, scope);
|
|
204
|
+
scanReferences(root, scope);
|
|
181
205
|
});
|
|
182
206
|
}
|
|
183
207
|
|
|
@@ -96,6 +96,8 @@ export const defaultTranslations: TranslationsByLocale = {
|
|
|
96
96
|
"“{detail}” templates are written through their own editor, not through a collection.",
|
|
97
97
|
"templateResolver.errors.noSystemTemplate": "There is no active system template to reset to.",
|
|
98
98
|
"templateResolver.errors.noDraft": "There is no draft to discard.",
|
|
99
|
+
"templateResolver.errors.systemOnlyTemplate":
|
|
100
|
+
"Only the platform maintains this template; it cannot be overridden per tenant.",
|
|
99
101
|
"templateResolver.errors.template.invalidMeta": "The template settings are invalid.",
|
|
100
102
|
"templateResolver.errors.template.meta.subject.required": "The subject is missing.",
|
|
101
103
|
"templateResolver.errors.template.meta.subject.invalid":
|
|
@@ -16,7 +16,7 @@ export const templateResolverUserDataFeature = defineFeature("template-resolver-
|
|
|
16
16
|
// template-resolver is optional for the same reason as in
|
|
17
17
|
// notes-history-user-data: a toggleable mount would otherwise trip the
|
|
18
18
|
// "effectively disabled" boot warning even though the entity is there.
|
|
19
|
-
r.requires("user-data-rights");
|
|
19
|
+
r.requires({ features: ["user-data-rights"] });
|
|
20
20
|
r.optionalRequires("template-resolver");
|
|
21
21
|
r.useExtension(EXT_USER_DATA, "user-content-entry", {
|
|
22
22
|
export: userContentExportHook,
|
|
@@ -80,7 +80,7 @@ export function createTenantFeature(options?: TenantFeatureOptions): FeatureDefi
|
|
|
80
80
|
recommended: true,
|
|
81
81
|
});
|
|
82
82
|
r.systemScope();
|
|
83
|
-
r.requires("config");
|
|
83
|
+
r.requires({ features: ["config"] });
|
|
84
84
|
r.extendsRegistrar(EXT_ASSIGNABLE_ROLE, {});
|
|
85
85
|
// The elevation guards read the registry, the screens only these options: a role the
|
|
86
86
|
// server accepts but the screen omits is silently stripped on the next member-roles save.
|
|
@@ -92,9 +92,9 @@ export function createTenantFeature(options?: TenantFeatureOptions): FeatureDefi
|
|
|
92
92
|
);
|
|
93
93
|
}
|
|
94
94
|
});
|
|
95
|
-
r.entity("tenant", tenantEntity);
|
|
96
|
-
r.entity("tenant-membership", tenantMembershipEntity);
|
|
97
|
-
r.entity("tenant-invitation", tenantInvitationEntity);
|
|
95
|
+
r.entity({ name: "tenant", ...tenantEntity });
|
|
96
|
+
r.entity({ name: "tenant-membership", ...tenantMembershipEntity });
|
|
97
|
+
r.entity({ name: "tenant-invitation", ...tenantInvitationEntity });
|
|
98
98
|
|
|
99
99
|
r.config({
|
|
100
100
|
keys: {
|
|
@@ -292,7 +292,7 @@ export async function seedTenant(
|
|
|
292
292
|
|
|
293
293
|
// "tenant:seed" matches no handler-scoped hook, only entity-scoped ones
|
|
294
294
|
// (keyed by result.entityName === "tenant") — that's exactly what
|
|
295
|
-
// tier-engine's `r.hook("postSave", { allOf: "tenant" }, ...)` needs.
|
|
295
|
+
// tier-engine's `r.hook({ type: "postSave", target: { allOf: "tenant" }, ... })` needs.
|
|
296
296
|
await fireEntityPostSave(hooks, "tenant:seed", result.data, { tenantId: options.id, db });
|
|
297
297
|
|
|
298
298
|
return { id: options.id };
|
|
@@ -63,7 +63,7 @@ export function createTenantLifecycleFeature(): FeatureDefinition {
|
|
|
63
63
|
category: "compliance",
|
|
64
64
|
recommended: false,
|
|
65
65
|
});
|
|
66
|
-
r.requires("tenant", "compliance-profiles");
|
|
66
|
+
r.requires({ features: ["tenant", "compliance-profiles"] });
|
|
67
67
|
r.optionalRequires("sessions");
|
|
68
68
|
r.usesApi("compliance.forTenant");
|
|
69
69
|
|
|
@@ -140,8 +140,7 @@ export function createTenantLifecycleFeature(): FeatureDefinition {
|
|
|
140
140
|
if (!ctx.registry) {
|
|
141
141
|
throw new Error("run-tenant-destruction: ctx.registry required (JobContext incomplete)");
|
|
142
142
|
}
|
|
143
|
-
const
|
|
144
|
-
const now = T.Now.instant();
|
|
143
|
+
const now = ctx.tz.now();
|
|
145
144
|
// Without crossTenantReads (undeclared hatch) fall through to the sweep.
|
|
146
145
|
if (ctx.crossTenantReads && !(await hasTenantDestructionWork(ctx.crossTenantReads, now))) {
|
|
147
146
|
// skip: no tenant is due, so the sweep would not touch anything
|
|
@@ -22,7 +22,7 @@ export function createTenantSettingsFeature(
|
|
|
22
22
|
opts: TenantSettingsFeatureOptions = {},
|
|
23
23
|
): FeatureDefinition {
|
|
24
24
|
return defineFeature(TENANT_SETTINGS_FEATURE_NAME, (r) => {
|
|
25
|
-
r.requires("config");
|
|
25
|
+
r.requires({ features: ["config"] });
|
|
26
26
|
r.describe(
|
|
27
27
|
"Per-tenant Currency + Locale defaults, exposed as two config keys (tenant-settings:config:currency, tenant-settings:config:locale) that surface in the self-populating Settings-Hub via `mask`. Pair with defineCreateWithTenantDefaults to auto-fill a money field's currency or a locale field on create instead of hard-coding a literal per entity.",
|
|
28
28
|
);
|
|
@@ -206,10 +206,10 @@ export function createTierEngineFeature<
|
|
|
206
206
|
category: "billing",
|
|
207
207
|
recommended: false,
|
|
208
208
|
});
|
|
209
|
-
r.requires("config");
|
|
210
|
-
r.requires("tenant");
|
|
209
|
+
r.requires({ features: ["config"] });
|
|
210
|
+
r.requires({ features: ["tenant"] });
|
|
211
211
|
|
|
212
|
-
r.entity("tier-assignment", tierAssignmentEntity);
|
|
212
|
+
r.entity({ name: "tier-assignment", ...tierAssignmentEntity });
|
|
213
213
|
|
|
214
214
|
// Standard-CRUD via Helper.
|
|
215
215
|
r.writeHandler(
|
|
@@ -369,25 +369,33 @@ export function createTierEngineFeature<
|
|
|
369
369
|
onTierAssigned.fn = applyAssignment;
|
|
370
370
|
|
|
371
371
|
// Invalidation: tier-assignment CRUD events (e.g. the billing webhook) update the cache.
|
|
372
|
-
r.hook(
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
372
|
+
r.hook({
|
|
373
|
+
type: "postSave",
|
|
374
|
+
target: { allOf: "tier-assignment" },
|
|
375
|
+
handler: async (result) => {
|
|
376
|
+
// result.data has tenantId + tier (after entity-update merge)
|
|
377
|
+
const data = result.data as { tenantId?: unknown; tier?: unknown }; // @cast-boundary engine-payload
|
|
378
|
+
// skip: defensive guard on the payload shape. Correct entity events
|
|
379
|
+
// always carry both fields as strings; a malformed payload (a custom
|
|
380
|
+
// handler bug) skips the cache update instead of throwing, because a
|
|
381
|
+
// hook error must not block the lifecycle pipeline (afterCommit,
|
|
382
|
+
// best-effort side effect).
|
|
383
|
+
if (typeof data.tenantId !== "string" || typeof data.tier !== "string") return;
|
|
384
|
+
applyAssignment(data.tenantId as TenantId, data.tier);
|
|
385
|
+
},
|
|
382
386
|
});
|
|
383
|
-
r.hook(
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
387
|
+
r.hook({
|
|
388
|
+
type: "postDelete",
|
|
389
|
+
target: { allOf: "tier-assignment" },
|
|
390
|
+
handler: async (payload) => {
|
|
391
|
+
const data = payload.data as { tenantId?: unknown }; // @cast-boundary engine-payload
|
|
392
|
+
// skip: gleiche type-guard semantik wie postSave-hook oben.
|
|
393
|
+
if (typeof data.tenantId !== "string") return;
|
|
394
|
+
const tenantId = data.tenantId as TenantId;
|
|
395
|
+
takeTicket(tenantId);
|
|
396
|
+
cache.delete(tenantId);
|
|
397
|
+
publishAssignmentChange(tenantId);
|
|
398
|
+
},
|
|
391
399
|
});
|
|
392
400
|
|
|
393
401
|
// Auto-default-tier-on-tenant-signup: hook fires inTransaction (atomic
|
|
@@ -406,10 +414,10 @@ export function createTierEngineFeature<
|
|
|
406
414
|
const defaultTier = opts.defaultTier;
|
|
407
415
|
const autoDefaultTierHookReason =
|
|
408
416
|
"seeds the default tier-assignment stream of the tenant just created";
|
|
409
|
-
r.hook(
|
|
410
|
-
"postSave",
|
|
411
|
-
{ allOf: "tenant" },
|
|
412
|
-
async (result, ctx) => {
|
|
417
|
+
r.hook({
|
|
418
|
+
type: "postSave",
|
|
419
|
+
target: { allOf: "tenant" },
|
|
420
|
+
handler: async (result, ctx) => {
|
|
413
421
|
// result-shape: kumiko-framework's SaveContext mit isNew + data
|
|
414
422
|
const saveResult = result as { isNew?: unknown; data?: unknown }; // @cast-boundary engine-payload
|
|
415
423
|
// skip: nur bei tenant-create (initial) — tenant-updates feuern
|
|
@@ -462,8 +470,9 @@ export function createTierEngineFeature<
|
|
|
462
470
|
tdb,
|
|
463
471
|
);
|
|
464
472
|
},
|
|
465
|
-
|
|
466
|
-
|
|
473
|
+
phase: HookPhases.inTransaction,
|
|
474
|
+
escapeHatch: { reason: autoDefaultTierHookReason },
|
|
475
|
+
});
|
|
467
476
|
}
|
|
468
477
|
|
|
469
478
|
// Extension-point declaration + self-registration. Pattern analog
|
|
@@ -28,7 +28,7 @@ export function createUserFeature(): FeatureDefinition {
|
|
|
28
28
|
recommended: true,
|
|
29
29
|
});
|
|
30
30
|
r.systemScope();
|
|
31
|
-
r.entity("user", userEntity);
|
|
31
|
+
r.entity({ name: "user", ...userEntity });
|
|
32
32
|
|
|
33
33
|
// Self-extension: `user` declares AND fulfils principalStatus (precedent:
|
|
34
34
|
// tier-engine/feature.ts) — wires the blocked-principal check into every stack that mounts `user`.
|
|
@@ -14,7 +14,7 @@ an. user-data-rights orchestriert Export- und Forget-Pipeline:
|
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
16
|
defineFeature("tasks", (r) => {
|
|
17
|
-
r.requires("user-data-rights");
|
|
17
|
+
r.requires({ features: ["user-data-rights"] });
|
|
18
18
|
r.useExtension(EXT_USER_DATA, "task", {
|
|
19
19
|
export: async (ctx) => ({
|
|
20
20
|
entity: "task",
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.367.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "Tests seed tenant memberships and events through the framework",
|
|
6
|
+
"detail": "Test-only change. Integration tests create read_tenant_memberships from tenantMembershipEntity and seed rows with seedRow instead of hand-written DDL and raw INSERTs. Fake events go through the event-store append, and backdated audit events are written with runSeedWritesAt instead of an UPDATE on created_at. No runtime behavior changes."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.365.0",
|
|
4
10
|
"type": "improvement",
|
|
@@ -197,7 +197,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
197
197
|
category: "compliance",
|
|
198
198
|
recommended: false,
|
|
199
199
|
});
|
|
200
|
-
r.requires("user", "data-retention", "compliance-profiles", "sessions");
|
|
200
|
+
r.requires({ features: ["user", "data-retention", "compliance-profiles", "sessions"] });
|
|
201
201
|
r.usesApi("compliance.forTenant");
|
|
202
202
|
r.usesApi("retention.policyFor");
|
|
203
203
|
// S2.U6 — restrict-account ruft sessions.revokeAllForUser cross-feature.
|
|
@@ -242,15 +242,15 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
242
242
|
);
|
|
243
243
|
|
|
244
244
|
// S2.U3 Atom 1b — ExportJob-Lifecycle-Entity.
|
|
245
|
-
r.entity("export-job", exportJobEntity);
|
|
245
|
+
r.entity({ name: "export-job", ...exportJobEntity });
|
|
246
246
|
|
|
247
247
|
// S2.U3 Atom 4a — Download-Token-Entity. Worker generiert Token beim
|
|
248
248
|
// Flip auf done (siehe run-export-jobs.ts). Hash in DB, plain im
|
|
249
249
|
// RunExportJobsResult fuer Atom 5 (Notification per Email).
|
|
250
|
-
r.entity("export-download-token", exportDownloadTokenEntity);
|
|
250
|
+
r.entity({ name: "export-download-token", ...exportDownloadTokenEntity });
|
|
251
251
|
|
|
252
252
|
// S2.U7 — Audit-Trail invalid Download-Attempts (DPO Brute-Force-Detection).
|
|
253
|
-
r.entity("download-attempt", downloadAttemptEntity);
|
|
253
|
+
r.entity({ name: "download-attempt", ...downloadAttemptEntity });
|
|
254
254
|
|
|
255
255
|
// S2.U6 — DSGVO Art. 18 Account-Freeze (Verarbeitungs-Pause).
|
|
256
256
|
// Endpoints fuer Restrict + Lift. Login-Block fuer Restricted Users
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
// `user` (S2.H1) und `fileRef` (S2.H2).
|
|
41
41
|
//
|
|
42
42
|
// Architektur-Entscheidung (S2.H1+H2): user-data-rights selbst kann
|
|
43
|
-
// nicht r.requires("user", "files") + r.useExtension(EXT_USER_DATA, ...)
|
|
43
|
+
// nicht r.requires({ features: ["user", "files"] }) + r.useExtension(EXT_USER_DATA, ...)
|
|
44
44
|
// machen weil es selbst Provider von EXT_USER_DATA ist (Boot-Validator
|
|
45
45
|
// lehnt self-extension ab). Lösung: drittes optional-mountbares Feature
|
|
46
46
|
// das requires beide Sources + die useExtension-Calls macht.
|
|
@@ -68,7 +68,7 @@ export function createUserDataRightsDefaultsFeature(): FeatureDefinition {
|
|
|
68
68
|
category: "compliance",
|
|
69
69
|
recommended: false,
|
|
70
70
|
});
|
|
71
|
-
r.requires("user", "files", "user-data-rights");
|
|
71
|
+
r.requires({ features: ["user", "files", "user-data-rights"] });
|
|
72
72
|
// Optional sources: hooks are registered unconditionally but each one
|
|
73
73
|
// no-ops at runtime when its source feature isn't mounted (see
|
|
74
74
|
// hooks/feature-mounted.ts) — the export runner has no per-hook
|
|
@@ -41,10 +41,10 @@ export function createUserProfileFeature(options?: UserProfileFeatureOptions): F
|
|
|
41
41
|
category: "identity",
|
|
42
42
|
recommended: true,
|
|
43
43
|
});
|
|
44
|
-
r.requires("user");
|
|
45
|
-
r.requires("auth-email-password");
|
|
46
|
-
r.requires("user-data-rights");
|
|
47
|
-
r.requires("user-data-rights-defaults");
|
|
44
|
+
r.requires({ features: ["user"] });
|
|
45
|
+
r.requires({ features: ["auth-email-password"] });
|
|
46
|
+
r.requires({ features: ["user-data-rights"] });
|
|
47
|
+
r.requires({ features: ["user-data-rights-defaults"] });
|
|
48
48
|
|
|
49
49
|
const handlers = allowEmailChange ? { changeEmail: r.writeHandler(changeEmailWrite) } : {};
|
|
50
50
|
|
|
@@ -30,7 +30,7 @@ export function createWaitlistFeature(opts: WaitlistOptions = {}): FeatureDefini
|
|
|
30
30
|
);
|
|
31
31
|
r.uiHints({ displayLabel: "Waitlist", category: "data", recommended: false });
|
|
32
32
|
r.systemScope();
|
|
33
|
-
r.requires("tenant", "auth-email-password", "user-data-rights");
|
|
33
|
+
r.requires({ features: ["tenant", "auth-email-password", "user-data-rights"] });
|
|
34
34
|
r.translations({ keys: WAITLIST_I18N });
|
|
35
35
|
|
|
36
36
|
r.notification("confirmation", {
|
|
@@ -43,7 +43,7 @@ export function createWaitlistFeature(opts: WaitlistOptions = {}): FeatureDefini
|
|
|
43
43
|
localizedTemplate: waitlistAdminNoticeTemplate,
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
r.entity("waitlistEntry", waitlistEntryEntity);
|
|
46
|
+
r.entity({ name: "waitlistEntry", ...waitlistEntryEntity });
|
|
47
47
|
r.writeHandler(createSubmitHandler(opts));
|
|
48
48
|
r.writeHandler(createInviteHandler(opts.invite));
|
|
49
49
|
r.writeHandler(rejectHandler);
|
|
@@ -102,7 +102,7 @@ In the browser, use the Designer/Admin UI to set the config key
|
|
|
102
102
|
there's no HTTP endpoint for it because the sample shows the
|
|
103
103
|
architecture, not an inbox UI.
|
|
104
104
|
|
|
105
|
-
If you want clickable: write a small `r.queryHandler("inbox:list")`
|
|
105
|
+
If you want clickable: write a small `r.queryHandler({ name: "inbox:list", … })`
|
|
106
106
|
that returns `getInbox(ctx.user.tenantId)`. ~20 LOC, deliberately
|
|
107
107
|
omitted to keep the focus on cap+tier.
|
|
108
108
|
|
|
@@ -183,10 +183,10 @@ export const newsletterFeature = defineFeature(FEATURE_NAME, (r) => {
|
|
|
183
183
|
// Hard-deps: mail-foundation für Plugin-API + config für tier-Wahl +
|
|
184
184
|
// cap-counter (transitiv via withCapEnforcement, aber explicit ist
|
|
185
185
|
// klarer für boot-Validator-Errors).
|
|
186
|
-
r.requires("config");
|
|
187
|
-
r.requires("cap-counter");
|
|
188
|
-
r.requires(mailFoundationFeature.name);
|
|
189
|
-
r.requires(BILLING_FOUNDATION_FEATURE);
|
|
186
|
+
r.requires({ features: ["config"] });
|
|
187
|
+
r.requires({ features: ["cap-counter"] });
|
|
188
|
+
r.requires({ features: [mailFoundationFeature.name] });
|
|
189
|
+
r.requires({ features: [BILLING_FOUNDATION_FEATURE] });
|
|
190
190
|
|
|
191
191
|
// Tier-config-key. Tenant-Admin setzt's; default "free".
|
|
192
192
|
r.config(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Single source of truth für die Feature-Komposition der Demo-App.
|
|
2
2
|
//
|
|
3
3
|
// **Mount-Order matters:** mail-foundation MUSS vor mail-transport-
|
|
4
|
-
// inmemory stehen (`r.requires("mail-foundation")` im Plugin); das
|
|
4
|
+
// inmemory stehen (`r.requires({ features: ["mail-foundation"] })` im Plugin); das
|
|
5
5
|
// newsletter-feature kommt danach (es nutzt mail-foundation +
|
|
6
6
|
// cap-counter). composeFeatures (auth-mode) ergänzt config + secrets +
|
|
7
7
|
// auth-email-password automatisch.
|
|
@@ -37,8 +37,8 @@ const REQUIRED_I18N = {
|
|
|
37
37
|
// mask.title ist der i18n-Key des Feld-Labels, mask.order die Reihenfolge.
|
|
38
38
|
export const configDemoFeature = defineFeature("config-demo", (r) => {
|
|
39
39
|
r.translations({ keys: REQUIRED_I18N });
|
|
40
|
-
r.requires("config");
|
|
41
|
-
r.requires("secrets");
|
|
40
|
+
r.requires({ features: ["config"] });
|
|
41
|
+
r.requires({ features: ["secrets"] });
|
|
42
42
|
|
|
43
43
|
r.config({
|
|
44
44
|
keys: {
|
|
@@ -53,14 +53,14 @@ function isProvider(value: unknown): value is Provider {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
export const aiSettingsFeature = defineFeature("ai-settings", (r) => {
|
|
56
|
-
r.requires("config");
|
|
56
|
+
r.requires({ features: ["config"] });
|
|
57
57
|
r.translations({ keys: AI_SETTINGS_I18N });
|
|
58
58
|
|
|
59
59
|
r.crud("connection", connectionEntity, { write: open, read: openRead });
|
|
60
|
-
r.queryHandler(
|
|
61
|
-
"connection-options",
|
|
62
|
-
noPayload,
|
|
63
|
-
async (query, ctx) => {
|
|
60
|
+
r.queryHandler({
|
|
61
|
+
name: "connection-options",
|
|
62
|
+
schema: noPayload,
|
|
63
|
+
handler: async (query, ctx) => {
|
|
64
64
|
const page = await executor.list({ limit: 100 }, query.user, ctx.db);
|
|
65
65
|
return {
|
|
66
66
|
rows: page.rows.map((row) => ({
|
|
@@ -69,28 +69,33 @@ export const aiSettingsFeature = defineFeature("ai-settings", (r) => {
|
|
|
69
69
|
})),
|
|
70
70
|
};
|
|
71
71
|
},
|
|
72
|
-
openRead,
|
|
73
|
-
);
|
|
72
|
+
...openRead,
|
|
73
|
+
});
|
|
74
74
|
|
|
75
|
-
r.queryHandler(
|
|
76
|
-
"model-options",
|
|
77
|
-
connectionIdPayload,
|
|
78
|
-
async (query, ctx) => {
|
|
75
|
+
r.queryHandler({
|
|
76
|
+
name: "model-options",
|
|
77
|
+
schema: connectionIdPayload,
|
|
78
|
+
handler: async (query, ctx) => {
|
|
79
79
|
const { connectionId } = query.payload;
|
|
80
80
|
if (connectionId === undefined) return { rows: [] };
|
|
81
81
|
const row = await executor.detail({ id: connectionId }, query.user, ctx.db);
|
|
82
82
|
const provider = row?.["provider"];
|
|
83
83
|
return { rows: isProvider(provider) ? MODELS_BY_PROVIDER[provider] : [] };
|
|
84
84
|
},
|
|
85
|
-
openRead,
|
|
86
|
-
);
|
|
85
|
+
...openRead,
|
|
86
|
+
});
|
|
87
87
|
|
|
88
|
-
r.queryHandler(
|
|
88
|
+
r.queryHandler({
|
|
89
|
+
name: "step-options",
|
|
90
|
+
schema: noPayload,
|
|
91
|
+
handler: async () => ({ rows: AI_STEPS }),
|
|
92
|
+
...openRead,
|
|
93
|
+
});
|
|
89
94
|
|
|
90
|
-
r.queryHandler(
|
|
91
|
-
"all-model-options",
|
|
92
|
-
noPayload,
|
|
93
|
-
async () => ({
|
|
95
|
+
r.queryHandler({
|
|
96
|
+
name: "all-model-options",
|
|
97
|
+
schema: noPayload,
|
|
98
|
+
handler: async () => ({
|
|
94
99
|
rows: PROVIDERS.flatMap((provider) =>
|
|
95
100
|
MODELS_BY_PROVIDER[provider].map((model) => ({
|
|
96
101
|
...model,
|
|
@@ -98,13 +103,13 @@ export const aiSettingsFeature = defineFeature("ai-settings", (r) => {
|
|
|
98
103
|
})),
|
|
99
104
|
),
|
|
100
105
|
}),
|
|
101
|
-
openRead,
|
|
102
|
-
);
|
|
106
|
+
...openRead,
|
|
107
|
+
});
|
|
103
108
|
|
|
104
|
-
r.queryHandler(
|
|
105
|
-
"connection:summary",
|
|
106
|
-
idPayload,
|
|
107
|
-
async (query, ctx) => {
|
|
109
|
+
r.queryHandler({
|
|
110
|
+
name: "connection:summary",
|
|
111
|
+
schema: idPayload,
|
|
112
|
+
handler: async (query, ctx) => {
|
|
108
113
|
const row = await executor.detail({ id: query.payload.id }, query.user, ctx.db);
|
|
109
114
|
return {
|
|
110
115
|
id: query.payload.id,
|
|
@@ -112,12 +117,22 @@ export const aiSettingsFeature = defineFeature("ai-settings", (r) => {
|
|
|
112
117
|
provider: String(row?.["provider"] ?? ""),
|
|
113
118
|
};
|
|
114
119
|
},
|
|
115
|
-
openRead,
|
|
116
|
-
);
|
|
120
|
+
...openRead,
|
|
121
|
+
});
|
|
117
122
|
|
|
118
123
|
const acknowledged = async () => ({ isSuccess: true as const, data: null });
|
|
119
|
-
r.writeHandler(
|
|
120
|
-
|
|
124
|
+
r.writeHandler({
|
|
125
|
+
name: "step:assign",
|
|
126
|
+
schema: z.object({}).passthrough(),
|
|
127
|
+
handler: acknowledged,
|
|
128
|
+
...open,
|
|
129
|
+
});
|
|
130
|
+
r.writeHandler({
|
|
131
|
+
name: "probe:run",
|
|
132
|
+
schema: z.object({}).passthrough(),
|
|
133
|
+
handler: acknowledged,
|
|
134
|
+
...open,
|
|
135
|
+
});
|
|
121
136
|
|
|
122
137
|
r.config({
|
|
123
138
|
keys: {
|
|
@@ -63,10 +63,10 @@ export const demoFeature = defineFeature("styleguide", (r) => {
|
|
|
63
63
|
// triggers shouldRenderActionsIconOnly) and need their own write handlers.
|
|
64
64
|
const { executor: itemExecutor } = createEntityExecutor("item", demoEntity);
|
|
65
65
|
|
|
66
|
-
r.writeHandler(
|
|
67
|
-
"item:archive",
|
|
68
|
-
z.object({ id: z.uuid() }),
|
|
69
|
-
async (event, ctx) =>
|
|
66
|
+
r.writeHandler({
|
|
67
|
+
name: "item:archive",
|
|
68
|
+
schema: z.object({ id: z.uuid() }),
|
|
69
|
+
handler: async (event, ctx) =>
|
|
70
70
|
// Admin-style status flip from a list row: last-writer-wins is fine,
|
|
71
71
|
// same as the tenant enable/disable toggle this mirrors.
|
|
72
72
|
itemExecutor.update(
|
|
@@ -75,13 +75,13 @@ export const demoFeature = defineFeature("styleguide", (r) => {
|
|
|
75
75
|
ctx.db,
|
|
76
76
|
{ skipOptimisticLock: true },
|
|
77
77
|
),
|
|
78
|
-
|
|
79
|
-
);
|
|
78
|
+
access: open.access,
|
|
79
|
+
});
|
|
80
80
|
|
|
81
|
-
r.writeHandler(
|
|
82
|
-
"item:publish",
|
|
83
|
-
z.object({ id: z.uuid() }),
|
|
84
|
-
async (event, ctx) =>
|
|
81
|
+
r.writeHandler({
|
|
82
|
+
name: "item:publish",
|
|
83
|
+
schema: z.object({ id: z.uuid() }),
|
|
84
|
+
handler: async (event, ctx) =>
|
|
85
85
|
itemExecutor.update(
|
|
86
86
|
{
|
|
87
87
|
id: event.payload.id,
|
|
@@ -91,13 +91,13 @@ export const demoFeature = defineFeature("styleguide", (r) => {
|
|
|
91
91
|
ctx.db,
|
|
92
92
|
{ skipOptimisticLock: true },
|
|
93
93
|
),
|
|
94
|
-
|
|
95
|
-
);
|
|
94
|
+
access: open.access,
|
|
95
|
+
});
|
|
96
96
|
|
|
97
|
-
r.writeHandler(
|
|
98
|
-
"item:duplicate",
|
|
99
|
-
z.object({ id: z.uuid() }),
|
|
100
|
-
async (event, ctx) => {
|
|
97
|
+
r.writeHandler({
|
|
98
|
+
name: "item:duplicate",
|
|
99
|
+
schema: z.object({ id: z.uuid() }),
|
|
100
|
+
handler: async (event, ctx) => {
|
|
101
101
|
const source = await itemExecutor.detail({ id: event.payload.id }, event.user, ctx.db);
|
|
102
102
|
if (!source) return failNotFound("item", event.payload.id);
|
|
103
103
|
return itemExecutor.create(
|
|
@@ -115,8 +115,8 @@ export const demoFeature = defineFeature("styleguide", (r) => {
|
|
|
115
115
|
ctx.db,
|
|
116
116
|
);
|
|
117
117
|
},
|
|
118
|
-
|
|
119
|
-
);
|
|
118
|
+
access: open.access,
|
|
119
|
+
});
|
|
120
120
|
|
|
121
121
|
r.screen({
|
|
122
122
|
id: "item-edit",
|