@cosmicdrift/kumiko-framework 0.386.0 → 0.387.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/LICENSE +21 -57
- package/README.md +2 -2
- package/agents/widgets.md +39 -39
- package/dist/api/api-constants.js +2 -2
- package/dist/api/auth-middleware.js +7 -7
- package/dist/api/auth-rate-limiters.js +7 -3
- package/dist/api/auth-routes-mfa.js +1 -1
- package/dist/api/auth-routes-session.js +1 -1
- package/dist/api/auth-routes-shared.js +7 -3
- package/dist/api/auth-routes-signup.js +1 -1
- package/dist/api/client-ip.js +3 -1
- package/dist/api/extra-route.js +1 -1
- package/dist/api/pii-leak-guard.js +4 -2
- package/dist/api/readiness.js +5 -5
- package/dist/api/redis-sse-broker.js +8 -6
- package/dist/api/routes.js +2 -2
- package/dist/api/server.js +34 -33
- package/dist/api/sse-broker.js +1 -1
- package/dist/api/sse-route.d.ts +13 -13
- package/dist/api/sse-route.js +15 -15
- package/dist/auth/index.js +4 -4
- package/dist/auth/roles.d.ts +4 -4
- package/dist/auth/roles.js +24 -24
- package/dist/compliance/duration-spec.js +18 -20
- package/dist/compliance/index.js +2 -3
- package/dist/compliance/override-schema.d.ts +4 -4
- package/dist/compliance/override-schema.js +19 -22
- package/dist/compliance/profiles.d.ts +26 -29
- package/dist/compliance/profiles.js +62 -69
- package/dist/compliance/sub-processors.d.ts +28 -30
- package/dist/compliance/sub-processors.js +17 -21
- package/dist/consumer-cli.js +4 -4
- package/dist/crypto/blind-index.js +18 -17
- package/dist/crypto/ciphertext-pattern.js +1 -1
- package/dist/crypto/event-pii.js +3 -3
- package/dist/crypto/kek-source.d.ts +1 -1
- package/dist/crypto/kek-source.js +3 -2
- package/dist/crypto/kms-adapter.d.ts +1 -1
- package/dist/crypto/kms-adapter.js +4 -6
- package/dist/crypto/kms-wiring.d.ts +1 -2
- package/dist/crypto/kms-wiring.js +2 -2
- package/dist/crypto/pg-kms-adapter.js +2 -6
- package/dist/crypto/pii-field-encryption.js +4 -4
- package/dist/crypto/subject-resolver.d.ts +1 -1
- package/dist/crypto/subject-resolver.js +2 -2
- package/dist/db/apply-entity-event.d.ts +6 -6
- package/dist/db/apply-entity-event.js +55 -55
- package/dist/db/blind-index-cleanup.js +7 -7
- package/dist/db/collect-table-metas.js +10 -11
- package/dist/db/compound-types.d.ts +1 -1
- package/dist/db/compound-types.js +12 -12
- package/dist/db/cursor.js +3 -4
- package/dist/db/dialect.d.ts +2 -2
- package/dist/db/dialect.js +2 -2
- package/dist/db/eagerload.d.ts +3 -3
- package/dist/db/eagerload.js +21 -12
- package/dist/db/entity-table-meta.d.ts +2 -2
- package/dist/db/entity-table-meta.js +4 -4
- package/dist/db/event-store-executor-context.d.ts +1 -1
- package/dist/db/event-store-executor-context.js +13 -13
- package/dist/db/event-store-executor-read.js +27 -28
- package/dist/db/event-store-executor-write.js +65 -70
- package/dist/db/located-timestamp.d.ts +11 -11
- package/dist/db/located-timestamp.js +29 -30
- package/dist/db/migrate-generator.d.ts +1 -1
- package/dist/db/migrate-generator.js +26 -19
- package/dist/db/migrate-runner.js +25 -25
- package/dist/db/money.d.ts +6 -6
- package/dist/db/money.js +29 -27
- package/dist/db/queries/backfill-pii.js +5 -5
- package/dist/db/queries/ddl.js +1 -1
- package/dist/db/queries/es-ops.d.ts +1 -1
- package/dist/db/queries/es-ops.js +1 -1
- package/dist/db/queries/event-consumer.js +3 -3
- package/dist/db/queries/event-store.js +4 -2
- package/dist/db/queries/projection-rebuild.js +3 -3
- package/dist/db/queries/raw-sql.d.ts +2 -2
- package/dist/db/queries/raw-sql.js +2 -2
- package/dist/db/queries/shadow-swap.js +19 -19
- package/dist/db/query.d.ts +1 -1
- package/dist/db/query.js +6 -6
- package/dist/db/rebuild-marker.js +20 -21
- package/dist/db/replay-migration-sql.js +2 -2
- package/dist/db/schema-inspection.js +4 -5
- package/dist/db/table-builder.d.ts +1 -2
- package/dist/db/table-builder.js +82 -82
- package/dist/db/tenant-db.d.ts +2 -2
- package/dist/db/tenant-db.js +9 -2
- package/dist/derivatives/delete-stored-file.js +5 -2
- package/dist/derivatives/derivatives-context.js +1 -1
- package/dist/engine/boot-validator/access-roles.js +4 -3
- package/dist/engine/boot-validator/api-ext.js +14 -14
- package/dist/engine/boot-validator/config-deps.js +7 -6
- package/dist/engine/boot-validator/detail-screens.js +3 -3
- package/dist/engine/boot-validator/entity-handler.js +60 -61
- package/dist/engine/boot-validator/gdpr-storage.js +4 -4
- package/dist/engine/boot-validator/i18n-keys.js +1 -1
- package/dist/engine/boot-validator/index.d.ts +2 -2
- package/dist/engine/boot-validator/index.js +20 -20
- package/dist/engine/boot-validator/nav.js +5 -5
- package/dist/engine/boot-validator/ownership.js +12 -9
- package/dist/engine/boot-validator/parent-ref.js +1 -1
- package/dist/engine/boot-validator/pii-retention.js +50 -57
- package/dist/engine/boot-validator/projection-list-screens.js +7 -7
- package/dist/engine/boot-validator/query-refs.js +1 -1
- package/dist/engine/boot-validator/screens-actions.js +15 -16
- package/dist/engine/boot-validator/screens-forms.js +10 -11
- package/dist/engine/boot-validator/screens-lists.js +14 -15
- package/dist/engine/boot-validator/screens.js +34 -40
- package/dist/engine/boot-validator/security-baseline.js +3 -2
- package/dist/engine/boot-validator/transfer-graph.js +2 -2
- package/dist/engine/boot-validator/workspaces.js +3 -3
- package/dist/engine/bridge-stub.js +3 -4
- package/dist/engine/build-app-schema.js +75 -75
- package/dist/engine/build-config-feature-schema.js +50 -50
- package/dist/engine/build-target.js +20 -23
- package/dist/engine/codemod/pipeline-codemod.js +20 -13
- package/dist/engine/config-helpers.js +4 -4
- package/dist/engine/constants.js +2 -2
- package/dist/engine/create-app.js +2 -2
- package/dist/engine/define-feature.js +12 -11
- package/dist/engine/define-handler.js +12 -11
- package/dist/engine/define-step.js +1 -1
- package/dist/engine/define-workflow.d.ts +5 -5
- package/dist/engine/define-workflow.js +6 -6
- package/dist/engine/entity-handlers.js +13 -14
- package/dist/engine/event-def.js +2 -2
- package/dist/engine/extension-names.d.ts +16 -17
- package/dist/engine/extension-names.js +37 -38
- package/dist/engine/extensions/escape-hatch-usage.js +1 -1
- package/dist/engine/extensions/tenant-data.js +2 -2
- package/dist/engine/extensions/user-data.d.ts +23 -24
- package/dist/engine/factories.d.ts +14 -13
- package/dist/engine/factories.js +38 -37
- package/dist/engine/feature-ast/entity-field-types.js +1 -1
- package/dist/engine/feature-ast/extractors/handlers.js +1 -1
- package/dist/engine/feature-ast/extractors/hooks.js +1 -1
- package/dist/engine/feature-ast/extractors/shared.d.ts +2 -3
- package/dist/engine/feature-ast/extractors/shared.js +2 -3
- package/dist/engine/feature-ast/parse.js +13 -13
- package/dist/engine/feature-ast/patch.js +1 -1
- package/dist/engine/feature-ast/pattern-change-schema.js +1 -1
- package/dist/engine/feature-ast/patterns.d.ts +2 -2
- package/dist/engine/feature-ast/render.js +2 -2
- package/dist/engine/feature-config-events-jobs.js +15 -12
- package/dist/engine/feature-entity-handlers.js +9 -5
- package/dist/engine/feature-manifest.d.ts +9 -9
- package/dist/engine/feature-manifest.js +6 -7
- package/dist/engine/feature-ui-extensions.d.ts +3 -3
- package/dist/engine/feature-ui-extensions.js +30 -22
- package/dist/engine/field-access.js +5 -6
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.js +4 -4
- package/dist/engine/object-form.d.ts +1 -0
- package/dist/engine/object-form.js +14 -4
- package/dist/engine/ownership.js +3 -3
- package/dist/engine/parse-ref-target.js +5 -7
- package/dist/engine/pattern-library/index.js +2 -2
- package/dist/engine/pattern-library/library.js +2 -2
- package/dist/engine/pattern-library/types.js +3 -3
- package/dist/engine/pipeline.js +2 -2
- package/dist/engine/project-app-schema-for-roles.js +1 -1
- package/dist/engine/projection-helpers.js +9 -9
- package/dist/engine/qualified-name.js +2 -2
- package/dist/engine/registry-facade.js +1 -1
- package/dist/engine/registry-ingest.js +12 -12
- package/dist/engine/registry-state.js +17 -19
- package/dist/engine/registry-validate.js +12 -12
- package/dist/engine/run-in.js +2 -2
- package/dist/engine/run-pipeline.js +7 -0
- package/dist/engine/schema-builder.js +40 -38
- package/dist/engine/screen-filter-ops.js +17 -20
- package/dist/engine/screen-helpers.js +4 -3
- package/dist/engine/state-machine.d.ts +12 -12
- package/dist/engine/state-machine.js +7 -7
- package/dist/engine/steps/_resolver-utils.js +4 -3
- package/dist/engine/steps/for-each.js +4 -4
- package/dist/engine/steps/unsafe-projection-delete.js +1 -1
- package/dist/engine/steps/unsafe-projection-upsert.js +4 -4
- package/dist/engine/steps/wait-for-event.js +4 -4
- package/dist/engine/steps/workflow-system-events.js +3 -3
- package/dist/engine/tier-resolver-extension.d.ts +29 -27
- package/dist/engine/tier-resolver-extension.js +26 -26
- package/dist/engine/transfer-adjacency.js +2 -2
- package/dist/engine/validate-projection-allowlist.js +7 -27
- package/dist/engine/walk-step-instances.d.ts +2 -0
- package/dist/engine/walk-step-instances.js +38 -0
- package/dist/engine/where-rule-lint.js +1 -1
- package/dist/engine/write-handler-qn-extract.js +1 -1
- package/dist/entrypoint/index.js +3 -3
- package/dist/env/index.d.ts +7 -6
- package/dist/env/index.js +10 -9
- package/dist/errors/transition-details.js +7 -7
- package/dist/errors/write-error-info.d.ts +6 -6
- package/dist/errors/write-error-info.js +6 -6
- package/dist/es-ops/context.d.ts +9 -9
- package/dist/es-ops/context.js +28 -28
- package/dist/es-ops/index.js +7 -9
- package/dist/es-ops/operations-schema.js +4 -10
- package/dist/es-ops/runner.d.ts +11 -11
- package/dist/es-ops/runner.js +49 -53
- package/dist/es-ops/types.d.ts +55 -55
- package/dist/es-ops/types.js +11 -11
- package/dist/event-store/admin-api.js +6 -7
- package/dist/event-store/errors.js +1 -1
- package/dist/event-store/event-store.js +2 -2
- package/dist/event-store/rebuild-dead-letter.js +1 -1
- package/dist/files/feature.js +9 -10
- package/dist/files/file-handle.js +5 -6
- package/dist/files/file-ref-entity.js +15 -15
- package/dist/files/file-ref-table.js +7 -8
- package/dist/files/file-routes.d.ts +7 -0
- package/dist/files/file-routes.js +11 -5
- package/dist/files/in-memory-provider.js +8 -9
- package/dist/files/local-provider.js +18 -25
- package/dist/files/provider-resolver.d.ts +7 -7
- package/dist/files/storage-tracking.js +2 -2
- package/dist/files/zip-stream.d.ts +12 -19
- package/dist/files/zip-stream.js +44 -63
- package/dist/i18n/required-surface-keys.d.ts +1 -1
- package/dist/i18n/required-surface-keys.js +8 -8
- package/dist/jobs/job-runner-helpers.js +8 -2
- package/dist/jobs/job-runner-schedulers.d.ts +1 -1
- package/dist/jobs/job-runner-schedulers.js +3 -4
- package/dist/jobs/job-runner.js +13 -15
- package/dist/lifecycle/lifecycle.js +3 -5
- package/dist/lifecycle/signal-handlers.js +5 -4
- package/dist/logging/utils.d.ts +2 -1
- package/dist/logging/utils.js +38 -10
- package/dist/migrations/index.js +1 -1
- package/dist/migrations/kumiko-drift.js +14 -13
- package/dist/migrations/pending-rebuilds.d.ts +21 -20
- package/dist/migrations/pending-rebuilds.js +27 -27
- package/dist/migrations/projection-table-index.d.ts +3 -3
- package/dist/migrations/projection-table-index.js +8 -8
- package/dist/observability/metric-validator.js +1 -1
- package/dist/observability/metrics-handle.js +3 -3
- package/dist/pipeline/append-event-core.js +11 -2
- package/dist/pipeline/cascade-handler.d.ts +1 -1
- package/dist/pipeline/dispatch-batch.js +2 -2
- package/dist/pipeline/dispatch-query.js +10 -10
- package/dist/pipeline/dispatch-shared.d.ts +1 -0
- package/dist/pipeline/dispatch-shared.js +15 -17
- package/dist/pipeline/dispatch-stream.js +1 -1
- package/dist/pipeline/dispatch-write.js +3 -3
- package/dist/pipeline/dispatcher-utils.d.ts +1 -1
- package/dist/pipeline/dispatcher-utils.js +1 -1
- package/dist/pipeline/event-consumer-state.js +2 -2
- package/dist/pipeline/event-dispatcher-delivery.js +1 -1
- package/dist/pipeline/event-dispatcher.js +17 -28
- package/dist/pipeline/irreversible-operation-gate.js +1 -1
- package/dist/pipeline/lifecycle-pipeline.js +9 -9
- package/dist/pipeline/member-read-only-transaction.js +1 -3
- package/dist/pipeline/msp-rebuild.js +1 -1
- package/dist/pipeline/projection-rebuild.js +13 -12
- package/dist/pipeline/system-hooks.js +39 -41
- package/dist/pipeline/system-identity-switch.js +1 -3
- package/dist/pipeline/tenant-timezone-cache.js +1 -1
- package/dist/random/generate.d.ts +29 -29
- package/dist/random/generate.js +30 -30
- package/dist/random/words.js +20 -20
- package/dist/rate-limit/middleware.js +9 -5
- package/dist/redis/client.js +2 -2
- package/dist/redis/pubsub-signal.js +14 -17
- package/dist/schema-cli.js +116 -22
- package/dist/search/index.js +4 -4
- package/dist/search/purge-subject.js +4 -5
- package/dist/search/reindex-entity.js +6 -9
- package/dist/time/boot-tz-warning.d.ts +2 -2
- package/dist/time/boot-tz-warning.js +8 -8
- package/dist/time/iana.js +10 -10
- package/dist/time/index.js +9 -9
- package/dist/time/legacy-date.d.ts +2 -2
- package/dist/time/legacy-date.js +6 -8
- package/dist/time/polyfill.js +1 -1
- package/dist/time/tz-context.d.ts +1 -1
- package/dist/time/tz-context.js +1 -1
- package/dist/ui-types/app-schema.js +7 -7
- package/dist/ui-types/list-row-meta.js +4 -4
- package/dist/upgrade-cli.js +10 -9
- package/dist/utils/compare.js +1 -1
- package/package.json +4 -4
- package/src/changes.json +48 -0
- package/src/scripts/codemod/crypto-shredding-testing-move.ts +2 -2
- package/src/scripts/codemod/migrate-cross-tenant.ts +1 -1
- package/src/scripts/codemod/migrate-open-to-all.ts +1 -1
- package/src/scripts/codemod/pii-personal-migration.ts +17 -8
- package/src/scripts/codemod/unprocessable-error-details-reason.ts +2 -2
package/dist/es-ops/types.d.ts
CHANGED
|
@@ -3,49 +3,50 @@ import type { TenantId, WriteResult } from "../engine/index.ts";
|
|
|
3
3
|
export type EsOperationAppliedBy = "boot" | "cli" | "ci-pipeline";
|
|
4
4
|
/** Default-Export einer seed-Migration-File. */
|
|
5
5
|
export type SeedMigration = {
|
|
6
|
-
/**
|
|
7
|
-
*
|
|
6
|
+
/** A short description of what the migration does. Shown in
|
|
7
|
+
* kumiko_es_operations.notes and in the `ops seed:status` output. */
|
|
8
8
|
readonly description: string;
|
|
9
|
-
/** Optional:
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
/** Optional: skip this seed when the env var
|
|
10
|
+
* `KUMIKO_SKIP_ES_OPS_<sanitized-filename>=1` is set (for recovery or
|
|
11
|
+
* debug boots). Default false = always-run-pending. */
|
|
12
12
|
readonly skippable?: boolean;
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
13
|
+
/** Main migration work. ctx provides systemWriteAs (the Event Store-compliant
|
|
14
|
+
* path, bypassing access checks through the system user) plus read helpers.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* If this throws, the marker is NOT written, app boot aborts, and the
|
|
17
|
+
* migration is retried on the next boot. Each migration has its own
|
|
18
|
+
* transaction; a failure stops all subsequent pending migrations, preserving
|
|
19
|
+
* their order. */
|
|
19
20
|
readonly run: (ctx: SeedMigrationContext) => Promise<void>;
|
|
20
21
|
};
|
|
21
|
-
/** Read
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
/** Read shape of a user entry exposed to seed helpers.
|
|
23
|
+
* A narrow subset of AuthUserRow, since seeds typically need only
|
|
24
|
+
* these fields for identification. */
|
|
24
25
|
export type SeedUserRow = {
|
|
25
26
|
readonly id: string;
|
|
26
27
|
readonly email: string;
|
|
27
28
|
readonly tenantId: string;
|
|
28
29
|
};
|
|
29
|
-
/** Read
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
30
|
+
/** Read shape of a membership entry exposed to seed helpers.
|
|
31
|
+
* Distinguishes two tenantIds: the logical one from the read projection
|
|
32
|
+
* (`tenantId`) and the physical one from the aggregate stream
|
|
33
|
+
* (`streamTenantId`). They differ when the aggregate was created by an
|
|
34
|
+
* executor with a different tenantId (for example,
|
|
35
|
+
* seedTenantMembership-by=systemAdmin), which is a typical
|
|
36
|
+
* publicstatus driver use case. */
|
|
36
37
|
export type SeedMembershipRow = {
|
|
37
38
|
readonly userId: string;
|
|
38
|
-
/** Payload
|
|
39
|
-
*
|
|
39
|
+
/** Payload tenant from `read_tenant_memberships.tenant_id`; it is used
|
|
40
|
+
* as `tenantId` in the write payload. */
|
|
40
41
|
readonly tenantId: string;
|
|
41
|
-
/** Stream
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
42
|
+
/** Stream tenant from `kumiko_events.tenant_id` in the v1 row. MUST be
|
|
43
|
+
* passed as `tenantIdOverride` to `systemWriteAs`; otherwise the Event
|
|
44
|
+
* Store executor looks for the stream in the wrong tenant and returns
|
|
45
|
+
* `version_conflict`. */
|
|
45
46
|
readonly streamTenantId: string;
|
|
46
47
|
readonly roles: readonly string[];
|
|
47
48
|
};
|
|
48
|
-
/** Read
|
|
49
|
+
/** Read shape of a tenant entry exposed to seed helpers. */
|
|
49
50
|
export type SeedTenantRow = {
|
|
50
51
|
readonly id: string;
|
|
51
52
|
readonly name: string;
|
|
@@ -69,34 +70,32 @@ export type SeedTemplateResourceFilter = {
|
|
|
69
70
|
readonly locale?: string;
|
|
70
71
|
};
|
|
71
72
|
export type SeedMigrationContext = {
|
|
72
|
-
/** Event
|
|
73
|
-
*
|
|
74
|
-
*
|
|
73
|
+
/** Event Store-compliant write through the existing write handler. The system
|
|
74
|
+
* user executes it while bypassing access checks. Events have
|
|
75
|
+
* inserted_by_id = SYSTEM_TENANT_ID-user, making them auditable.
|
|
75
76
|
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
77
|
+
* The type signature follows existing ctx.writeAs (with payload as unknown).
|
|
78
|
+
* Type safety comes from handler-specific wrappers in the caller; do not
|
|
79
|
+
* attempt generic magic.
|
|
79
80
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* `feedback_event_store_tenant_consistency.md` + Driver-Use-Case
|
|
86
|
-
* publicstatus-admin-roles in `project_es_ops_phase1_retro.md`).
|
|
81
|
+
* **tenantIdOverride:** when the target aggregate lives in a specific
|
|
82
|
+
* tenant stream (rather than SYSTEM_TENANT_ID, the default), the caller
|
|
83
|
+
* MUST provide the stream tenantId. Otherwise the Event Store executor
|
|
84
|
+
* looks for the aggregate stream under `SYSTEM_TENANT_ID` and returns
|
|
85
|
+
* `version_conflict`.
|
|
87
86
|
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
87
|
+
* Typical patterns:
|
|
88
|
+
* - System-scope aggregates (config-values, system text-content): omit
|
|
89
|
+
* tenantIdOverride (default SYSTEM_TENANT_ID).
|
|
90
|
+
* - Tenant-scope aggregates (memberships, tenant-config, app-data): use
|
|
91
|
+
* `tenantIdOverride: m.tenantId` or the stream tenant from a find*
|
|
92
|
+
* helper.
|
|
94
93
|
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
94
|
+
* **extraRoles:** hasAccess has no system bypass. A handler with an
|
|
95
|
+
* explicit role gate (for example, `access: { roles: ["SystemAdmin"] }` or
|
|
96
|
+
* `["anonymous"]`) otherwise rejects the bare `system` actor with
|
|
97
|
+
* `access_denied`. Pass the required role(s) here; see
|
|
98
|
+
* `createSystemUser`'s `extraRoles` documentation. */
|
|
100
99
|
readonly systemWriteAs: (handlerQualifiedName: string, payload: unknown, tenantIdOverride?: TenantId, extraRoles?: readonly string[]) => Promise<WriteResult>;
|
|
101
100
|
readonly findUserByEmail: (email: string) => Promise<SeedUserRow | null>;
|
|
102
101
|
readonly findMembershipsOfUser: (userId: string) => Promise<readonly SeedMembershipRow[]>;
|
|
@@ -104,9 +103,10 @@ export type SeedMigrationContext = {
|
|
|
104
103
|
/** Template rows ordered by slug, locale. Empty list when the
|
|
105
104
|
* template-resolver feature (its table) is not present. */
|
|
106
105
|
readonly findTemplateResources: (filter?: SeedTemplateResourceFilter) => Promise<readonly SeedTemplateResourceRow[]>;
|
|
107
|
-
/** Escape
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
106
|
+
/** Escape hatch for direct database access. Use only for READ-only lookups
|
|
107
|
+
* that the context does not provide by default; WRITES must use
|
|
108
|
+
* systemWriteAs. The type is DbRunner (a connection or active transaction)
|
|
109
|
+
* because the runner creates the context within each migration's
|
|
110
|
+
* transaction scope. */
|
|
111
111
|
readonly db: DbRunner;
|
|
112
112
|
};
|
package/dist/es-ops/types.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
// Public API
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// kumiko_es_operations
|
|
1
|
+
// Public API for seed migrations. Files in /seeds/<date>-<slug>.ts
|
|
2
|
+
// export a default object of this type. The runner calls `run(ctx)` in
|
|
3
|
+
// chronological order, once per app boot, tracking runs in
|
|
4
|
+
// kumiko_es_operations.
|
|
5
5
|
//
|
|
6
|
-
// API
|
|
7
|
-
// (findUserByEmail, etc.)
|
|
8
|
-
//
|
|
9
|
-
//
|
|
6
|
+
// API stability: the initial API is intentionally minimal. Context helpers
|
|
7
|
+
// (findUserByEmail, etc.) grow on demand: when a real migration needs a
|
|
8
|
+
// lookup the context does not provide, add it there. This avoids speculative
|
|
9
|
+
// helper APIs.
|
|
10
10
|
//
|
|
11
|
-
// ctx.db
|
|
12
|
-
//
|
|
13
|
-
// (
|
|
11
|
+
// ctx.db is permitted as an escape hatch for READ-only lookups. WRITES must
|
|
12
|
+
// always go through ctx.systemWriteAs so Event Store invariants remain intact
|
|
13
|
+
// (source of truth plus automatic projection).
|
|
14
14
|
export {};
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/// <reference types="temporal-polyfill/global" preserve="true" />
|
|
2
|
-
// Event-
|
|
3
|
-
//
|
|
2
|
+
// Event-store admin API — bypasses Marten for legacy-data imports.
|
|
3
|
+
// Specification and behavior: docs/plans/features/event-store-admin-api.md
|
|
4
4
|
//
|
|
5
|
-
// Guard
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// selbst, das Guard-Script selbst.
|
|
5
|
+
// Guard rail: this module is NOT re-exported from event-store/index.ts. Import only via the deep
|
|
6
|
+
// path `@cosmicdrift/kumiko-framework/event-store/admin-api`. The guard script
|
|
7
|
+
// `scripts/guard-admin-api.ts` blocks calls from application code. Allowlist: samples/*/migration/,
|
|
8
|
+
// scripts/migrations/, this definition, and the guard script itself.
|
|
10
9
|
import { constraintOf, isUniqueViolation } from "../db/pg-error.js";
|
|
11
10
|
import { claimXactId, runAtomicEventWrite } from "../db/queries/event-store.js";
|
|
12
11
|
import { eventPredecessorExists, findExistingEventVersion, insertRawEventBatch, insertRawFirstEvent, insertRawSubsequentEvent, } from "../db/queries/event-store-admin.js";
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// errors so the executor layer can map them to the framework's
|
|
4
4
|
// WriteResult error contract (version_conflict).
|
|
5
5
|
//
|
|
6
|
-
// These live here and not in kumiko-types
|
|
6
|
+
// These live here and not in kumiko-types: `instanceof` needs a
|
|
7
7
|
// single copy of the class, which forced kumiko-types to be a peerDependency
|
|
8
8
|
// and made every minor changeset resolve to a major bump.
|
|
9
9
|
export class VersionConflictError extends Error {
|
|
@@ -35,7 +35,7 @@ import { currentSeedCreatedAt } from "./seed-clock.js";
|
|
|
35
35
|
// by TCP round-trip instead of pollIntervalMs.
|
|
36
36
|
export const EVENTS_PUBSUB_CHANNEL = "kumiko_events_new";
|
|
37
37
|
export async function append(db, event) {
|
|
38
|
-
// Event-PII
|
|
38
|
+
// Event-PII: stored payload AND returned echo carry ciphertext, so
|
|
39
39
|
// inline projections and rebuilds materialize identical rows.
|
|
40
40
|
const payload = await encryptEventPayloadPii(event.type, event.payload, {
|
|
41
41
|
tenantId: event.tenantId,
|
|
@@ -80,7 +80,7 @@ export async function append(db, event) {
|
|
|
80
80
|
throw e;
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
-
//
|
|
83
|
+
// Attribution is derived from the execution scope, never taken from
|
|
84
84
|
// the caller: a passed-in value can lie about who wrote the row, a derived
|
|
85
85
|
// one cannot. Hence overwrite rather than merge. appendRaw/appendRawBatch
|
|
86
86
|
// bypass this deliberately — they replay historical rows verbatim.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// whose (possibly years-old) payload makes its apply throw rolls the whole
|
|
6
6
|
// replay back — the projection stays permanently un-rebuildable until the
|
|
7
7
|
// event is repaired by hand. The upcaster dead-letter only quarantines
|
|
8
|
-
// upcast-TRANSFORM failures, not apply failures
|
|
8
|
+
// upcast-TRANSFORM failures, not apply failures.
|
|
9
9
|
//
|
|
10
10
|
// Quarantine mode (errorPolicy.skipApplyErrors on RebuildDeps, or
|
|
11
11
|
// MspErrorMode.rebuild.skipApplyErrors on the MSP definition) confines each
|
package/dist/files/feature.js
CHANGED
|
@@ -4,17 +4,16 @@ import { FILE_REF_ENTITY_NAME, fileRefEntity } from "./file-ref-entity.js";
|
|
|
4
4
|
import { DEFAULT_UPLOAD_SWEEP_GRACE_HOURS, MIN_UPLOAD_SWEEP_GRACE_HOURS, sweepOrphanedUploads, } from "./sweep-orphaned-uploads.js";
|
|
5
5
|
import { fileUploadLedgerTable } from "./upload-ledger.js";
|
|
6
6
|
export { fileRefEntity } from "./file-ref-entity.js";
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// `fileRef.
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// file-spezifische Lösch-Logik.
|
|
7
|
+
// `fileRef` is a regular ES entity. Uploads and deletes go through the standard
|
|
8
|
+
// entity executor (file-routes.ts: executor.create/delete → `fileRef.created` /
|
|
9
|
+
// `fileRef.deleted` → applyEntityEvent materializes `file_refs`), so soft-delete,
|
|
10
|
+
// anonymization, restore and retention come generically from the entity lifecycle
|
|
11
|
+
// and data-retention, with no file-specific deletion logic.
|
|
13
12
|
//
|
|
14
|
-
// `r.entity`
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
13
|
+
// `r.entity` exposes the table to PII boot validation and the userData/tenantData
|
|
14
|
+
// extensions, and registers the implicit entity projection used by
|
|
15
|
+
// rebuildProjection. It lives in the framework next to file-routes and
|
|
16
|
+
// fileRefsTable; bundled-features/files only re-exports it.
|
|
18
17
|
export const FILES_FEATURE = "files";
|
|
19
18
|
function assertValidSweepGraceHours(hours) {
|
|
20
19
|
if (!Number.isInteger(hours) || hours < MIN_UPLOAD_SWEEP_GRACE_HOURS) {
|
|
@@ -121,12 +121,11 @@ export function deriveKey(key, suffix) {
|
|
|
121
121
|
const { stem, ext } = splitKeyExtension(key);
|
|
122
122
|
return ext === "" ? `${key}.${suffix}` : `${stem}.${suffix}${ext}`;
|
|
123
123
|
}
|
|
124
|
-
// The prefix that covers a key AND every `derive()`d variant of it —
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
// none of which have their own `file_refs` row.
|
|
124
|
+
// The prefix that covers a key AND every `derive()`d variant of it — `foo/bar.jpg` → `foo/bar.`,
|
|
125
|
+
// matching `foo/bar.jpg` and `foo/bar.medium.jpg` alike, since derive() only ever inserts a suffix
|
|
126
|
+
// between the stem and the extension. Used by the tenant-handover destroy sweep to reach a moved
|
|
127
|
+
// fileRef's original plus every already-rendered derivative, none of which have their own
|
|
128
|
+
// `file_refs` row.
|
|
130
129
|
export function storageKeyStemPrefix(key) {
|
|
131
130
|
const { stem, ext } = splitKeyExtension(key);
|
|
132
131
|
return ext === "" ? `${key}.` : `${stem}.`;
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import { createBigIntField, createEntity, createTextField } from "../engine/index.js";
|
|
2
|
-
// fileRef —
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// fileRef — the file-metadata entity. A standard ES entity: uploads/deletes
|
|
3
|
+
// run through the standard executor (file-routes.ts), and the `file_refs` table
|
|
4
|
+
// is built from this definition via buildEntityTable.
|
|
5
5
|
//
|
|
6
|
-
// softDelete: true —
|
|
7
|
-
//
|
|
8
|
-
//
|
|
6
|
+
// softDelete: true — as with the `user` entity and the data-retention strategies.
|
|
7
|
+
// A delete sets `isDeleted=true` (recoverable, not "gone immediately"); actual
|
|
8
|
+
// erasure (Art. 17) runs through the Forget hook and retention cleanup.
|
|
9
9
|
//
|
|
10
|
-
// `insertedAt`/`insertedById`
|
|
11
|
-
// buildBaseColumns in table-builder.ts)
|
|
12
|
-
//
|
|
13
|
-
//
|
|
10
|
+
// `insertedAt`/`insertedById` are framework-managed base columns (see
|
|
11
|
+
// buildBaseColumns in table-builder.ts) and must NOT be duplicated as entity
|
|
12
|
+
// fields — fieldColumns win during the merge, and the field variant without
|
|
13
|
+
// `.default(now()).notNull()` silently makes inserted_at nullable.
|
|
14
14
|
//
|
|
15
|
-
// PII
|
|
16
|
-
// - fileName → personal: "self" (original filename often carries a
|
|
17
|
-
// reference: "Marc-Lebenslauf.pdf", "Krankheitsattest-Mai.pdf").
|
|
18
|
-
// fields (storageKey, mimeType, entityType, entityId, fieldName)
|
|
19
|
-
// technical references or system metadata, not subject content.
|
|
15
|
+
// PII annotations:
|
|
16
|
+
// - fileName → personal: "self" (the original filename often carries a
|
|
17
|
+
// personal reference: "Marc-Lebenslauf.pdf", "Krankheitsattest-Mai.pdf").
|
|
18
|
+
// The other fields (storageKey, mimeType, entityType, entityId, fieldName)
|
|
19
|
+
// are technical references or system metadata, not subject content.
|
|
20
20
|
export { FILE_REF_ENTITY_NAME } from "./types.js";
|
|
21
21
|
export const fileRefEntity = createEntity({
|
|
22
22
|
table: "file_refs",
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { buildEntityTable } from "../db/table-builder.js";
|
|
2
2
|
import { FILE_REF_ENTITY_NAME, fileRefEntity } from "./file-ref-entity.js";
|
|
3
|
-
// `file_refs`
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// `file_refs` is the read table for the `fileRef` entity. It is built from the entity
|
|
4
|
+
// definition rather than a manually maintained `pgTable`, so the implicit entity
|
|
5
|
+
// projection (rebuild) and live executor writes use the same columns and table—one
|
|
6
|
+
// source of truth, with no drift between duplicate definitions.
|
|
7
7
|
//
|
|
8
|
-
// `id`
|
|
9
|
-
//
|
|
10
|
-
// (
|
|
11
|
-
// auf /files/:id.
|
|
8
|
+
// `id` is a UUID and also serves as the aggregate ID for the `fileRef` event stream:
|
|
9
|
+
// each upload appends `fileRef.created`, and each deletion appends `fileRef.deleted`
|
|
10
|
+
// (standard entity auto-verbs). UUIDs prevent enumeration attacks against /files/:id.
|
|
12
11
|
export const fileRefsTable = buildEntityTable(FILE_REF_ENTITY_NAME, fileRefEntity);
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import type { WriteErrorInfo } from "@cosmicdrift/kumiko-types/write-error-info-types";
|
|
1
2
|
import { Hono } from "hono";
|
|
2
3
|
import type { DbConnection } from "../db/connection.ts";
|
|
3
4
|
import { type Registry, type SessionUser, type TenantId } from "../engine/types/index.ts";
|
|
4
5
|
import type { FileProviderResolver } from "./provider-resolver.ts";
|
|
5
6
|
import type { RecordReadCheck } from "./record-read-check.ts";
|
|
6
7
|
import { type RecordWriteCheck } from "./record-write-check.ts";
|
|
8
|
+
declare const ERROR_STATUSES: readonly [400, 401, 403, 404, 409, 422, 429, 500, 503, 504];
|
|
9
|
+
export declare function deleteFailureResponseFor(error: WriteErrorInfo): {
|
|
10
|
+
readonly status: (typeof ERROR_STATUSES)[number];
|
|
11
|
+
readonly code: string;
|
|
12
|
+
};
|
|
7
13
|
export type FileAccessDecision = "allow" | "deny";
|
|
8
14
|
export type FileRef = {
|
|
9
15
|
id: string;
|
|
@@ -36,3 +42,4 @@ export type FileRoutesOptions = {
|
|
|
36
42
|
export declare function resolveMaxUploadBodyBytes(options: Pick<FileRoutesOptions, "registry" | "maxUploadSize">): number;
|
|
37
43
|
export declare function readFilesRouteOptions(registry: Registry): Pick<FileRoutesOptions, "accessGuard" | "privilegedRoles" | "unclaimedUploadReaders" | "maxUploadSize">;
|
|
38
44
|
export declare function createFileRoutes(options: FileRoutesOptions): Hono;
|
|
45
|
+
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
1
2
|
import { Hono } from "hono";
|
|
2
3
|
import { bodyLimit } from "hono/body-limit";
|
|
3
4
|
import { getUser } from "../api/auth-middleware.js";
|
|
@@ -18,6 +19,12 @@ import { RecordWriteDecisions } from "./record-write-check.js";
|
|
|
18
19
|
import { neutralFileNameForMimeType } from "./resolve-content-type.js";
|
|
19
20
|
import { parseMaxSize, resolveServedContentType } from "./types.js";
|
|
20
21
|
import { createFileUploadLedger } from "./upload-ledger.js";
|
|
22
|
+
// Exactly the httpStatus values declared by KumikoError subclasses across packages/*.
|
|
23
|
+
const ERROR_STATUSES = [400, 401, 403, 404, 409, 422, 429, 500, 503, 504];
|
|
24
|
+
export function deleteFailureResponseFor(error) {
|
|
25
|
+
const status = isOneOf(ERROR_STATUSES, error.httpStatus) ? error.httpStatus : 500;
|
|
26
|
+
return { status, code: status === 404 ? "not_found" : error.code };
|
|
27
|
+
}
|
|
21
28
|
const DEFAULT_PRIVILEGED_ROLES = ["Admin", "SystemAdmin"];
|
|
22
29
|
// 15 minutes — long enough for a download to start, short enough that a
|
|
23
30
|
// leaked URL (e.g. from a browser history screenshot) isn't a long-lived
|
|
@@ -281,7 +288,7 @@ export function createFileRoutes(options) {
|
|
|
281
288
|
// GET /files/:id/variant/:name — a named derived version of the file.
|
|
282
289
|
// Same tenant + guard gate as the download above; the original stays
|
|
283
290
|
// reachable only through that route. The public, anonymous counterpart is
|
|
284
|
-
// a separate cut
|
|
291
|
+
// a separate cut and does not go through createFileRoutes.
|
|
285
292
|
api.get("/files/:id/variant/:name", async (c) => {
|
|
286
293
|
const user = getUser(c);
|
|
287
294
|
const id = c.req.param("id");
|
|
@@ -358,9 +365,8 @@ export function createFileRoutes(options) {
|
|
|
358
365
|
// pattern from the access-deny path above. Everything else (version
|
|
359
366
|
// conflict 409, ownership 403, validation 422, internal 500) gets the
|
|
360
367
|
// real status off the error so callers can distinguish recoverable
|
|
361
|
-
// from terminal —
|
|
362
|
-
const status = result.error
|
|
363
|
-
const code = status === 404 ? "not_found" : result.error.code;
|
|
368
|
+
// from terminal — a blanket 404 here had obscured exactly that.
|
|
369
|
+
const { status, code } = deleteFailureResponseFor(result.error);
|
|
364
370
|
return c.json({ error: code }, status);
|
|
365
371
|
}
|
|
366
372
|
return c.json({ ok: true });
|
|
@@ -411,7 +417,7 @@ export function createFileRoutes(options) {
|
|
|
411
417
|
return c.json({ url, expiresAt });
|
|
412
418
|
});
|
|
413
419
|
// Metadata without the bytes. Unlike the byte-serving routes, this returns
|
|
414
|
-
// fileName as JSON — and fileName is `personal: "self"
|
|
420
|
+
// fileName as JSON — and fileName is `personal: "self"`, so it must
|
|
415
421
|
// go through the entity executor's detail() to decrypt instead of
|
|
416
422
|
// loadFileForTenant's raw, still-encrypted read.
|
|
417
423
|
async function loadReadableFileMeta(id, user) {
|
|
@@ -12,9 +12,8 @@ export function createInMemoryFileProvider() {
|
|
|
12
12
|
store.set(key, { data: new Uint8Array(data), mimeType });
|
|
13
13
|
},
|
|
14
14
|
async writeStream(key, source, options) {
|
|
15
|
-
// In-
|
|
16
|
-
//
|
|
17
|
-
// Production-Pfad.
|
|
15
|
+
// In-memory storage does not provide real streaming; it collects chunks
|
|
16
|
+
// into a Uint8Array. Suitable for tests, not a production path.
|
|
18
17
|
const chunks = [];
|
|
19
18
|
let total = 0;
|
|
20
19
|
for await (const chunk of source) {
|
|
@@ -36,12 +35,12 @@ export function createInMemoryFileProvider() {
|
|
|
36
35
|
return new Uint8Array(entry.data);
|
|
37
36
|
},
|
|
38
37
|
readStream(key) {
|
|
39
|
-
// In-
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
38
|
+
// In-memory storage has no chunks, but the surface must match real
|
|
39
|
+
// streamers (Local/S3) so test code exercises the same path. Yield the
|
|
40
|
+
// bytes as a single chunk.
|
|
41
|
+
// The O(1) map lookup avoids a cost from eager resolution.
|
|
42
|
+
// Errors are thrown on the first chunk pull, matching S3's lazy pattern
|
|
43
|
+
// (the request starts on the first iterator step).
|
|
45
44
|
const entry = store.get(key);
|
|
46
45
|
const captured = entry ? new Uint8Array(entry.data) : null;
|
|
47
46
|
return {
|
|
@@ -33,16 +33,12 @@ export function createLocalProvider(basePath) {
|
|
|
33
33
|
await writeFile(filePath, data);
|
|
34
34
|
},
|
|
35
35
|
async writeStream(key, source, _options) {
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
// Problem (kein Reader sucht `*.tmp`-Patterns), nur Operations-
|
|
43
|
-
// Hygiene; ein periodischer cron-cleanup auf alten `.tmp`-Files
|
|
44
|
-
// ist die saubere Loesung wenn das in Production realistisch
|
|
45
|
-
// greift.
|
|
36
|
+
// Atomic via a temporary file and rename: readers see either the previous version (if any) or
|
|
37
|
+
// the complete new version, never a partially written stream. If the stream fails during
|
|
38
|
+
// writing, temporary-file cleanup is best-effort; an OS race in stream destruction can leave
|
|
39
|
+
// the `.tmp` file briefly. This affects operational hygiene only, because readers never
|
|
40
|
+
// search `*.tmp` patterns. A periodic cleanup of old `.tmp` files is appropriate if this runs
|
|
41
|
+
// in production.
|
|
46
42
|
const filePath = resolveContainedPath(key);
|
|
47
43
|
const tmpPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
48
44
|
await mkdir(dirname(filePath), { recursive: true });
|
|
@@ -51,8 +47,8 @@ export function createLocalProvider(basePath) {
|
|
|
51
47
|
await rename(tmpPath, filePath);
|
|
52
48
|
}
|
|
53
49
|
catch (e) {
|
|
54
|
-
// Best-effort
|
|
55
|
-
//
|
|
50
|
+
// Best-effort temporary-file cleanup; if that also fails, the filesystem problem is outside
|
|
51
|
+
// this component's scope.
|
|
56
52
|
await unlink(tmpPath).catch(() => { });
|
|
57
53
|
throw e;
|
|
58
54
|
}
|
|
@@ -62,24 +58,21 @@ export function createLocalProvider(basePath) {
|
|
|
62
58
|
return readFile(filePath);
|
|
63
59
|
},
|
|
64
60
|
readStream(key) {
|
|
65
|
-
// node:fs createReadStream
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
// (z.B. ENOENT bei Missing-File faellt erst beim ersten chunk-
|
|
72
|
-
// pull, nicht beim readStream-Aufruf — gleiches Lazy-Verhalten
|
|
73
|
-
// wie inmemory + S3).
|
|
61
|
+
// node:fs createReadStream is an AsyncIterable<Buffer>; Buffer extends Uint8Array. This
|
|
62
|
+
// adapter preserves a Uint8Array surface because @types/node types the async iterator as
|
|
63
|
+
// AsyncIterableIterator<any>. The default highWaterMark produces 64KB chunks, which suits ZIP
|
|
64
|
+
// stream consumption. Errors reach the consumer's for-await loop; for example, a missing file
|
|
65
|
+
// raises ENOENT on the first chunk pull rather than when readStream is called, matching the
|
|
66
|
+
// lazy behavior of in-memory and S3 providers.
|
|
74
67
|
const filePath = resolveContainedPath(key);
|
|
75
68
|
const stream = createReadStream(filePath);
|
|
76
69
|
return {
|
|
77
70
|
async *[Symbol.asyncIterator]() {
|
|
78
71
|
for await (const chunk of stream) {
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
72
|
+
// A stream without an encoding yields Buffer. Buffer extends Uint8Array, but
|
|
73
|
+
// @types/node types the async iterator as string|Buffer. Take a zero-copy view
|
|
74
|
+
// onto the same ArrayBuffer; the runtime check rules out the string branch
|
|
75
|
+
// because the stream was not opened with an encoding.
|
|
83
76
|
if (typeof chunk === "string") {
|
|
84
77
|
throw new Error("local-provider readStream: unexpected string chunk (encoding leaked)");
|
|
85
78
|
}
|
|
@@ -36,13 +36,13 @@ export type FileProviderContext = {
|
|
|
36
36
|
* file-provider-inmemory, ...) registers an implementation via
|
|
37
37
|
* `r.useExtension(EXT_FILE_PROVIDER, "<name>", { build })`.
|
|
38
38
|
*
|
|
39
|
-
* **Plugin-Author-
|
|
40
|
-
*
|
|
41
|
-
* `ctx.tx`, `ctx.actor`, `ctx.signal`)
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
39
|
+
* **Plugin-Author-Warning:** `ctx` is EXPLICITLY a FileProviderContext, not
|
|
40
|
+
* a full HandlerContext. Fields outside the narrow surface (e.g.
|
|
41
|
+
* `ctx.tx`, `ctx.actor`, `ctx.signal`) are NOT available on the worker path.
|
|
42
|
+
* Casting `ctx as unknown as HandlerContext` fails at runtime on the worker —
|
|
43
|
+
* the crash only appears in production with the first S3 tenant. If a plugin
|
|
44
|
+
* needs fields outside FileProviderContext, extend FileProviderContext
|
|
45
|
+
* explicitly (a visible breaking change) rather than casting `ctx`.
|
|
46
46
|
*/
|
|
47
47
|
export type FileProviderPlugin = {
|
|
48
48
|
readonly build: (ctx: FileProviderContext, tenantId: string) => Promise<FileStorageProvider>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Tenant storage usage — counts bytes + files per tenant from the event log.
|
|
2
2
|
//
|
|
3
|
-
// Tracking-only
|
|
4
|
-
//
|
|
3
|
+
// Tracking-only: no hard limit, no upload gatekeeping. Apps read the row to
|
|
4
|
+
// decide what to do (show a warning, soft-throttle, bill, …).
|
|
5
5
|
// Enforcement is a conscious deferred call — we want production numbers
|
|
6
6
|
// before picking thresholds (see "Storage tracking: counted now, enforced
|
|
7
7
|
// later" in core-files.md).
|