@cosmicdrift/kumiko-framework 0.305.0 → 0.306.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 +4 -4
- package/src/api/__tests__/server-boot-guards.test.ts +1 -0
- package/src/api/__tests__/server-error-logging.test.ts +71 -0
- package/src/api/request-context.ts +5 -4
- package/src/api/routes.ts +26 -1
- package/src/bun-db/__tests__/closed-connection-retry.integration.test.ts +159 -0
- package/src/bun-db/__tests__/select-many-retry.integration.test.ts +138 -0
- package/src/bun-db/query.ts +42 -18
- package/src/changes.json +66 -0
- package/src/db/__tests__/pg-error.test.ts +14 -0
- package/src/db/__tests__/system-db-view-export.test.ts +107 -0
- package/src/db/__tests__/tenant-db-no-raw.test.ts +10 -0
- package/src/db/__tests__/with-systemdb-unsafe-raw-grant.test.ts +71 -0
- package/src/db/index.ts +1 -1
- package/src/db/pg-error.ts +13 -0
- package/src/db/queries/__tests__/{unsafe-read-retrying.test.ts → unsafe-read-retrying.integration.test.ts} +28 -28
- package/src/db/tenant-db.ts +90 -13
- package/src/engine/__tests__/boot-validator-gdpr-storage.test.ts +6 -1
- package/src/engine/__tests__/boot-validator-s0-integration.test.ts +2 -2
- package/src/engine/__tests__/boot-validator.test.ts +1 -1
- package/src/engine/__tests__/tier-resolver-extension.test.ts +1 -1
- package/src/engine/extension-names.ts +55 -25
- package/src/engine/extensions/storage-provider.ts +14 -41
- package/src/engine/extensions/tenant-data.ts +4 -0
- package/src/engine/extensions/tenant-resource.ts +40 -0
- package/src/engine/extensions/user-data.ts +8 -7
- package/src/engine/feature-ast/__tests__/handler-header-roundtrip.test.ts +669 -0
- package/src/engine/feature-ast/__tests__/parse.test.ts +5 -3
- package/src/engine/feature-ast/__tests__/patch-update.test.ts +718 -0
- package/src/engine/feature-ast/__tests__/pattern-change-schema.test.ts +819 -0
- package/src/engine/feature-ast/entity-field-types.ts +41 -0
- package/src/engine/feature-ast/extractors/handlers.ts +217 -84
- package/src/engine/feature-ast/extractors/hooks.ts +72 -15
- package/src/engine/feature-ast/extractors/round2.ts +21 -0
- package/src/engine/feature-ast/extractors/shared.ts +9 -0
- package/src/engine/feature-ast/index.ts +11 -1
- package/src/engine/feature-ast/patch.ts +338 -5
- package/src/engine/feature-ast/patcher.ts +2 -2
- package/src/engine/feature-ast/pattern-change-schema.ts +1411 -0
- package/src/engine/feature-ast/patterns.ts +22 -15
- package/src/engine/feature-ast/render.ts +1 -0
- package/src/engine/feature-ui-extensions.ts +8 -7
- package/src/engine/index.ts +21 -5
- package/src/engine/types/extension-options-map.ts +1 -0
- package/src/engine/types/index.ts +6 -0
- package/src/jobs/__tests__/job-retention.integration.test.ts +316 -0
- package/src/jobs/__tests__/job-retry-enqueue-paths.integration.test.ts +309 -0
- package/src/jobs/__tests__/jobs.integration.test.ts +3 -3
- package/src/jobs/job-runner.ts +151 -14
- package/src/pipeline/__tests__/ctx-bridge.integration.test.ts +113 -26
- package/src/pipeline/__tests__/dispatcher.test.ts +5 -0
- package/src/pipeline/__tests__/hook-systemdb-escape-hatch.integration.test.ts +246 -0
- package/src/pipeline/__tests__/idempotency-transient-failure.integration.test.ts +198 -0
- package/src/pipeline/__tests__/redis-pipeline.integration.test.ts +59 -0
- package/src/pipeline/dispatch-batch.ts +56 -13
- package/src/pipeline/idempotency.ts +16 -0
- package/src/pipeline/system-identity-switch.ts +22 -4
- package/src/testing/closed-connection-error.ts +62 -0
- package/src/testing/index.ts +1 -0
- package/src/bun-db/__tests__/select-many-retry.test.ts +0 -79
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import type { DerivativeRendererPlugin } from "@cosmicdrift/kumiko-types/derivatives-types";
|
|
2
|
+
import type { OverlayResolverPlugin } from "../derivatives/derivatives-context";
|
|
3
|
+
import type { FileProviderPlugin } from "../files/provider-resolver";
|
|
4
|
+
import type { PrincipalStatusPlugin, TenantLifecycleStatusPlugin } from "./active-membership";
|
|
5
|
+
import type { TenantDataExtensionHooks } from "./extensions/tenant-data";
|
|
6
|
+
import type { TenantResourceExtensionHooks } from "./extensions/tenant-resource";
|
|
7
|
+
import type { UserDataExtensionOptions } from "./extensions/user-data";
|
|
8
|
+
import { TENANT_TIER_RESOLVER_EXT, type TierResolverPlugin } from "./tier-resolver-extension";
|
|
9
|
+
|
|
1
10
|
// Standardisierte Extension-Namen fuer Datenschutz-Hook-Achsen.
|
|
2
11
|
//
|
|
3
12
|
// Features registrieren Extensions via:
|
|
@@ -55,14 +64,12 @@ export const EXT_USER_DATA_ORDER = {
|
|
|
55
64
|
export const EXT_TENANT_DATA = "tenantData" as const;
|
|
56
65
|
|
|
57
66
|
/**
|
|
58
|
-
* `storageProvider` —
|
|
67
|
+
* `storageProvider` — file-storage-plugin tenant-destroy hook.
|
|
59
68
|
*
|
|
60
|
-
*
|
|
61
|
-
* - `destroyTenant(tenantId, ctx) => Promise<void>`
|
|
62
|
-
* - `destroySubject(subject, ctx) => Promise<{ deleted: number }>`
|
|
69
|
+
* Expected hook method: `destroyTenant(tenantId, ctx) => Promise<void>`.
|
|
63
70
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
71
|
+
* Registered by: `files-tenant-data`.
|
|
72
|
+
* Consumed by: pluggable providers (Local, MinIO, S3, R2).
|
|
66
73
|
*/
|
|
67
74
|
export const EXT_STORAGE_PROVIDER = "storageProvider" as const;
|
|
68
75
|
|
|
@@ -148,37 +155,28 @@ export const EXT_DERIVATIVE_PUBLIC_PREDICATE = "derivativePublicPredicate" as co
|
|
|
148
155
|
export const EXT_DERIVATIVE_OVERLAY_RESOLVER = "derivativeOverlayResolver" as const;
|
|
149
156
|
|
|
150
157
|
/**
|
|
151
|
-
* `searchAdapter` —
|
|
152
|
-
*
|
|
158
|
+
* `searchAdapter` — search-index tenant-destroy hook (Meilisearch index
|
|
159
|
+
* cleanup on tenant-destroy).
|
|
153
160
|
*
|
|
154
|
-
*
|
|
155
|
-
* - `destroyTenant(tenantId, ctx) => Promise<void>`
|
|
156
|
-
* - `eraseSubject(subject, ctx) => Promise<void>`
|
|
161
|
+
* Expected hook method: `destroyTenant(tenantId, ctx) => Promise<void>`.
|
|
157
162
|
*
|
|
158
|
-
*
|
|
159
|
-
* Genutzt von: Meilisearch- und andere Search-Adapter-Implementierungen.
|
|
163
|
+
* Consumed by: Meilisearch and other search-adapter implementations.
|
|
160
164
|
*/
|
|
161
165
|
export const EXT_SEARCH_ADAPTER = "searchAdapter" as const;
|
|
162
166
|
|
|
163
167
|
/**
|
|
164
|
-
* `externalResource` —
|
|
165
|
-
*
|
|
168
|
+
* `externalResource` — external-service tenant-destroy hook (webhook
|
|
169
|
+
* subscriptions, Brevo recipient lists, provider customer accounts).
|
|
166
170
|
*
|
|
167
|
-
*
|
|
168
|
-
* - `destroyTenant(tenantId, ctx) => Promise<void>`
|
|
169
|
-
*
|
|
170
|
-
* Registriert von: `tenant-lifecycle` (Sprint 5).
|
|
171
|
+
* Expected hook method: `destroyTenant(tenantId, ctx) => Promise<void>`.
|
|
171
172
|
*/
|
|
172
173
|
export const EXT_EXTERNAL_RESOURCE = "externalResource" as const;
|
|
173
174
|
|
|
174
175
|
/**
|
|
175
|
-
* `infraResource` — Pulumi-managed
|
|
176
|
-
* (
|
|
176
|
+
* `infraResource` — Pulumi-managed per-tenant resource tenant-destroy hook
|
|
177
|
+
* (custom domain, cert-manager issuer, dedicated pod/volume).
|
|
177
178
|
*
|
|
178
|
-
*
|
|
179
|
-
* - `destroyTenant(tenantId, ctx) => Promise<void>`
|
|
180
|
-
*
|
|
181
|
-
* Registriert von: `tenant-lifecycle` (Sprint 5).
|
|
179
|
+
* Expected hook method: `destroyTenant(tenantId, ctx) => Promise<void>`.
|
|
182
180
|
*/
|
|
183
181
|
export const EXT_INFRA_RESOURCE = "infraResource" as const;
|
|
184
182
|
|
|
@@ -206,7 +204,39 @@ export const TENANT_MEMBERSHIPS_QUERY = "tenant:query:memberships" as const;
|
|
|
206
204
|
export type KumikoExtensionName =
|
|
207
205
|
| typeof EXT_USER_DATA
|
|
208
206
|
| typeof EXT_TENANT_DATA
|
|
207
|
+
| typeof EXT_STORAGE_PROVIDER
|
|
208
|
+
| typeof EXT_SEARCH_ADAPTER
|
|
209
|
+
| typeof EXT_EXTERNAL_RESOURCE
|
|
210
|
+
| typeof EXT_INFRA_RESOURCE
|
|
211
|
+
| typeof EXT_FILE_PROVIDER
|
|
212
|
+
| typeof EXT_DERIVATIVE_RENDERER
|
|
213
|
+
| typeof EXT_DERIVATIVE_PUBLIC_PREDICATE
|
|
214
|
+
| typeof EXT_DERIVATIVE_OVERLAY_RESOLVER
|
|
215
|
+
| typeof EXT_PRINCIPAL_STATUS
|
|
216
|
+
| typeof EXT_TENANT_LIFECYCLE_STATUS;
|
|
217
|
+
|
|
218
|
+
/** The four `destroyTenant(tenantId, ctx)`-only resource-cleanup extension points. */
|
|
219
|
+
export type TenantResourceExtensionName =
|
|
209
220
|
| typeof EXT_STORAGE_PROVIDER
|
|
210
221
|
| typeof EXT_SEARCH_ADAPTER
|
|
211
222
|
| typeof EXT_EXTERNAL_RESOURCE
|
|
212
223
|
| typeof EXT_INFRA_RESOURCE;
|
|
224
|
+
|
|
225
|
+
// r.useExtension options-shape for framework-owned extension points; app/bundled-features-owned
|
|
226
|
+
// points augment co-located with their owner, since the framework never imports upward.
|
|
227
|
+
declare module "@cosmicdrift/kumiko-framework/engine" {
|
|
228
|
+
interface KumikoExtensionOptionsMap {
|
|
229
|
+
[EXT_TENANT_DATA]: TenantDataExtensionHooks;
|
|
230
|
+
[EXT_USER_DATA]: UserDataExtensionOptions;
|
|
231
|
+
[EXT_FILE_PROVIDER]: FileProviderPlugin;
|
|
232
|
+
[EXT_DERIVATIVE_RENDERER]: DerivativeRendererPlugin;
|
|
233
|
+
[EXT_DERIVATIVE_OVERLAY_RESOLVER]: OverlayResolverPlugin;
|
|
234
|
+
[EXT_PRINCIPAL_STATUS]: PrincipalStatusPlugin;
|
|
235
|
+
[EXT_TENANT_LIFECYCLE_STATUS]: TenantLifecycleStatusPlugin;
|
|
236
|
+
[EXT_STORAGE_PROVIDER]: TenantResourceExtensionHooks;
|
|
237
|
+
[EXT_SEARCH_ADAPTER]: TenantResourceExtensionHooks;
|
|
238
|
+
[EXT_EXTERNAL_RESOURCE]: TenantResourceExtensionHooks;
|
|
239
|
+
[EXT_INFRA_RESOURCE]: TenantResourceExtensionHooks;
|
|
240
|
+
[TENANT_TIER_RESOLVER_EXT]: TierResolverPlugin;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -1,43 +1,16 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// extension point, not just this one. ctx here guarantees tenantId, db, plus
|
|
7
|
-
// the optional fileProviderResolver/log; the richer stage-runner ctx
|
|
8
|
-
// (tenant-lifecycle's DestructionStageCtx) is a structural superset, so a hook
|
|
9
|
-
// typed against this minimal ctx is safely assignable wherever that richer ctx
|
|
10
|
-
// is passed.
|
|
11
|
-
//
|
|
12
|
-
// `db` is a raw DbRunner, NOT a tenant-scoped TenantDb — unlike
|
|
13
|
-
// TenantDataHookCtx.db (EXT_TENANT_DATA), runExtensionDestroyHooks hands every
|
|
14
|
-
// EXT_*_RESOURCE-style hook (this one included) the stage runner's own
|
|
15
|
-
// DestructionStageCtx.db straight through, with no tenant filter and no
|
|
16
|
-
// escapeHatch/unsafeRaw gate — there is nothing to declare, because nothing
|
|
17
|
-
// ever wraps it. That's deliberate for this stage family: wiping a whole
|
|
18
|
-
// search index, an S3 prefix, or infra resources is inherently a cross-tenant,
|
|
19
|
-
// bulk operation, not a row-scoped one. A hook that needs a plain read across
|
|
20
|
-
// tenants (kumiko-framework#3035: does a foreign tenant's file_refs row still
|
|
1
|
+
// EXT_STORAGE_PROVIDER (tenant-destroy binary cleanup) is one of the four
|
|
2
|
+
// EXT_*_RESOURCE-style extension points — see tenant-resource.ts for the
|
|
3
|
+
// shared hook-signature contract. Kept as aliases so the existing public
|
|
4
|
+
// exports/imports stay valid; a hook that needs a plain cross-tenant read
|
|
5
|
+
// (kumiko-framework#3035: does a foreign tenant's file_refs row still
|
|
21
6
|
// reference a key under THIS tenant's storage prefix) uses `db` directly via
|
|
22
|
-
// executeRawQueryRead — there is no `ctx.systemDb` here
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
7
|
+
// executeRawQueryRead — there is no `ctx.systemDb` here.
|
|
8
|
+
import type {
|
|
9
|
+
TenantResourceDestroyHook,
|
|
10
|
+
TenantResourceExtensionHooks,
|
|
11
|
+
TenantResourceHookCtx,
|
|
12
|
+
} from "./tenant-resource";
|
|
28
13
|
|
|
29
|
-
export
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
readonly fileProviderResolver?: FileProviderResolver;
|
|
33
|
-
readonly log?: (message: string) => void;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export type StorageProviderDestroyTenantHook = (
|
|
37
|
-
tenantId: TenantId,
|
|
38
|
-
ctx: StorageProviderHookCtx,
|
|
39
|
-
) => Promise<void>;
|
|
40
|
-
|
|
41
|
-
export interface StorageProviderExtensionHooks {
|
|
42
|
-
readonly destroyTenant: StorageProviderDestroyTenantHook;
|
|
43
|
-
}
|
|
14
|
+
export type StorageProviderHookCtx = TenantResourceHookCtx;
|
|
15
|
+
export type StorageProviderDestroyTenantHook = TenantResourceDestroyHook;
|
|
16
|
+
export type StorageProviderExtensionHooks = TenantResourceExtensionHooks;
|
|
@@ -28,3 +28,7 @@ export type TenantDataDestroyHook = (ctx: TenantDataHookCtx) => Promise<void>;
|
|
|
28
28
|
export interface TenantDataExtensionHooks {
|
|
29
29
|
readonly destroy: TenantDataDestroyHook;
|
|
30
30
|
}
|
|
31
|
+
|
|
32
|
+
export function isTenantDataExtensionHooks(o: unknown): o is TenantDataExtensionHooks {
|
|
33
|
+
return typeof o === "object" && o !== null && "destroy" in o && typeof o.destroy === "function";
|
|
34
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Hook signature types shared by the four EXT_*_RESOURCE-style extension
|
|
2
|
+
// points (storageProvider, searchAdapter, externalResource, infraResource).
|
|
3
|
+
// runExtensionDestroyHooks (tenant-lifecycle/stages.ts) passes tenantId as
|
|
4
|
+
// its own positional arg for all of them, not just storageProvider — the
|
|
5
|
+
// stage runner's DestructionStageCtx is a structural superset of the ctx
|
|
6
|
+
// below, so a hook typed against it is safely assignable wherever the
|
|
7
|
+
// richer ctx is passed.
|
|
8
|
+
//
|
|
9
|
+
// `db` is a raw DbRunner, not a tenant-scoped TenantDb: wiping a whole
|
|
10
|
+
// search index, an S3 prefix, or infra resources is inherently a
|
|
11
|
+
// cross-tenant, bulk operation, not a row-scoped one — there is no
|
|
12
|
+
// escapeHatch/unsafeRaw gate here because nothing ever wraps `db`.
|
|
13
|
+
import type { FileProviderResolver } from "@cosmicdrift/kumiko-types/file-provider-resolver-types";
|
|
14
|
+
import type { DbRunner } from "../../db/connection";
|
|
15
|
+
import type { TenantId } from "../types";
|
|
16
|
+
|
|
17
|
+
export interface TenantResourceHookCtx {
|
|
18
|
+
readonly tenantId: TenantId;
|
|
19
|
+
readonly db: DbRunner;
|
|
20
|
+
readonly fileProviderResolver?: FileProviderResolver;
|
|
21
|
+
readonly log?: (message: string) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type TenantResourceDestroyHook = (
|
|
25
|
+
tenantId: TenantId,
|
|
26
|
+
ctx: TenantResourceHookCtx,
|
|
27
|
+
) => Promise<void>;
|
|
28
|
+
|
|
29
|
+
export interface TenantResourceExtensionHooks {
|
|
30
|
+
readonly destroyTenant: TenantResourceDestroyHook;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function isTenantResourceExtensionHooks(o: unknown): o is TenantResourceExtensionHooks {
|
|
34
|
+
return (
|
|
35
|
+
typeof o === "object" &&
|
|
36
|
+
o !== null &&
|
|
37
|
+
"destroyTenant" in o &&
|
|
38
|
+
typeof o.destroyTenant === "function"
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -166,15 +166,16 @@ export type UserDataDeleteHook = (
|
|
|
166
166
|
) => Promise<UserDataDeleteHookResult>;
|
|
167
167
|
|
|
168
168
|
/**
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
* konsumierende Features liefern beide Hooks via
|
|
172
|
-
* `r.useExtension(EXT_USER_DATA, "<entity>", { export, delete })`.
|
|
173
|
-
*
|
|
174
|
-
* Kein Hook ist optional — beide MÜSSEN registriert sein. Boot-Check
|
|
175
|
-
* (Sprint 2) prüft das.
|
|
169
|
+
* Full hook table for EXT_USER_DATA. Export-only is a legitimate partial
|
|
170
|
+
* contract, so registrations are typed by `UserDataExtensionOptions`.
|
|
176
171
|
*/
|
|
177
172
|
export interface UserDataExtensionHooks {
|
|
178
173
|
readonly export: UserDataExportHook;
|
|
179
174
|
readonly delete: UserDataDeleteHook;
|
|
180
175
|
}
|
|
176
|
+
|
|
177
|
+
// At least one hook is required: an empty bag would silently register neither export nor forget.
|
|
178
|
+
export type UserDataExtensionOptions = (
|
|
179
|
+
| { readonly export: UserDataExportHook; readonly delete?: UserDataDeleteHook }
|
|
180
|
+
| { readonly export?: UserDataExportHook; readonly delete: UserDataDeleteHook }
|
|
181
|
+
) & { readonly order?: number };
|