@cosmicdrift/kumiko-framework 0.383.1 → 0.385.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/agents/examples/custom-fields-basic.md +13 -3
- package/agents/examples/designer-demo.md +6 -2
- package/agents/examples/i18n.md +36 -1
- package/agents/features.md +4 -0
- package/dist/api/auth-rate-limiters.d.ts +8 -0
- package/dist/api/auth-rate-limiters.js +105 -0
- package/dist/api/auth-routes-context.d.ts +34 -0
- package/dist/api/auth-routes-context.js +192 -0
- package/dist/api/auth-routes-invite.d.ts +2 -0
- package/dist/api/auth-routes-invite.js +171 -0
- package/dist/api/auth-routes-login.d.ts +2 -0
- package/dist/api/auth-routes-login.js +103 -0
- package/dist/api/auth-routes-mfa.d.ts +4 -0
- package/dist/api/auth-routes-mfa.js +211 -0
- package/dist/api/auth-routes-session.d.ts +2 -0
- package/dist/api/auth-routes-session.js +144 -0
- package/dist/api/auth-routes-shared.d.ts +33 -0
- package/dist/api/auth-routes-shared.js +81 -0
- package/dist/api/auth-routes-signup.d.ts +2 -0
- package/dist/api/auth-routes-signup.js +75 -0
- package/dist/api/auth-routes-token-flows.d.ts +4 -0
- package/dist/api/auth-routes-token-flows.js +91 -0
- package/dist/api/auth-routes-types.d.ts +76 -0
- package/dist/api/auth-routes-types.js +1 -0
- package/dist/api/auth-routes.d.ts +5 -85
- package/dist/api/auth-routes.js +20 -1095
- package/dist/config/index.d.ts +1 -1
- package/dist/crypto/pii-ciphertext-format.js +1 -1
- package/dist/db/blind-index-cleanup.js +1 -1
- package/dist/db/dialect.js +1 -1
- package/dist/db/distinct-on.d.ts +9 -0
- package/dist/db/distinct-on.js +28 -0
- package/dist/db/event-store-executor-write.js +2 -2
- package/dist/db/migrate-runner.js +4 -4
- package/dist/db/pg-error.js +0 -12
- package/dist/db/queries/event-consumer.js +3 -3
- package/dist/db/queries/postgres-array-literal.js +2 -2
- package/dist/db/query.js +48 -20
- package/dist/db/tenant-db.js +1 -1
- package/dist/engine/boot-validator/i18n-keys.d.ts +2 -0
- package/dist/engine/boot-validator/i18n-keys.js +15 -1
- package/dist/engine/boot-validator/index.d.ts +4 -0
- package/dist/engine/boot-validator/index.js +3 -1
- package/dist/engine/boot-validator/pii-retention.js +6 -7
- package/dist/engine/boot-validator/screens-actions.d.ts +67 -0
- package/dist/engine/boot-validator/screens-actions.js +488 -0
- package/dist/engine/boot-validator/screens-dashboard.d.ts +15 -0
- package/dist/engine/boot-validator/screens-dashboard.js +467 -0
- package/dist/engine/boot-validator/screens-forms.d.ts +24 -0
- package/dist/engine/boot-validator/screens-forms.js +516 -0
- package/dist/engine/boot-validator/screens-lists.d.ts +47 -0
- package/dist/engine/boot-validator/screens-lists.js +319 -0
- package/dist/engine/boot-validator/screens.d.ts +4 -21
- package/dist/engine/boot-validator/screens.js +26 -1799
- package/dist/engine/build-app-schema.js +1 -1
- package/dist/engine/factories.d.ts +3 -3
- package/dist/engine/steps/read-find-one.js +1 -1
- package/dist/engine/types/index.d.ts +1 -1
- package/dist/event-store/provenance-append.js +1 -1
- package/dist/files/file-ref-entity.d.ts +6 -126
- package/dist/files/file-ref-table.d.ts +6 -126
- package/dist/files/file-routes.js +39 -16
- package/dist/i18n/required-surface-keys.d.ts +22 -2
- package/dist/i18n/required-surface-keys.js +91 -3
- package/dist/jobs/job-runner-helpers.d.ts +45 -0
- package/dist/jobs/job-runner-helpers.js +281 -0
- package/dist/jobs/job-runner-schedulers.d.ts +23 -0
- package/dist/jobs/job-runner-schedulers.js +106 -0
- package/dist/jobs/job-runner-types.d.ts +65 -0
- package/dist/jobs/job-runner-types.js +2 -0
- package/dist/jobs/job-runner.d.ts +4 -75
- package/dist/jobs/job-runner.js +7 -384
- package/dist/pipeline/dispatch-shared.js +2 -2
- package/dist/pipeline/event-dispatcher-admin.d.ts +15 -8
- package/dist/pipeline/event-dispatcher-admin.js +28 -7
- package/dist/pipeline/event-dispatcher.d.ts +2 -2
- package/dist/pipeline/event-dispatcher.js +3 -4
- package/dist/pipeline/index.d.ts +2 -2
- package/dist/pipeline/index.js +1 -1
- package/dist/schema-cli.js +5 -0
- package/package.json +3 -3
- package/src/changes.json +39 -0
|
@@ -456,7 +456,7 @@ function assertValidScreenMultilineRows(featureName, shortId, screen) {
|
|
|
456
456
|
// `derived`, `totals`, `totalsMatch`) — a top-level `Array.isArray`/plain-
|
|
457
457
|
// object check alone would let a function survive one level deep.
|
|
458
458
|
//
|
|
459
|
-
// Cast at the exit `as
|
|
459
|
+
// Cast at the exit `as EntityFieldDefinition`: type-system-wise our out-map only
|
|
460
460
|
// satisfies the discriminated union with an unnarrowed `type` string — the
|
|
461
461
|
// cast bridges the variant inference TS can't recompute from a generic
|
|
462
462
|
// Record.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { EntityTenancy } from "@cosmicdrift/kumiko-types/tenancy-brand";
|
|
2
|
-
import type { BigIntFieldDef, BooleanFieldDef, DateFieldDef, DecimalFieldDef, DerivedFieldDef, EmbeddedFieldDef, EntityDefinition, EntityIndexDef, FieldsMap, FileFieldDef, FilesFieldDef, ImageFieldDef, ImagesFieldDef, JsonbFieldDef, LocatedTimestampFieldDef, LongTextFieldDef, MoneyFieldDef, MultiSelectFieldDef, NumberFieldDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, ResolvedPiiFlags, RetentionDef, SelectFieldDef, TextFieldDef, TimestampFieldDef, TzFieldDef } from "./types/index.ts";
|
|
2
|
+
import type { BigIntFieldDef, BooleanFieldDef, DateFieldDef, DecimalFieldDef, DerivedFieldDef, EmbeddedFieldDef, EntityDefinition, EntityIndexDef, EntityLongTextFieldDef, EntityTextFieldDef, FieldsMap, FileFieldDef, FilesFieldDef, ImageFieldDef, ImagesFieldDef, JsonbFieldDef, LocatedTimestampFieldDef, LongTextFieldDef, MoneyFieldDef, MultiSelectFieldDef, NumberFieldDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, ResolvedPiiFlags, RetentionDef, SelectFieldDef, TextFieldDef, TimestampFieldDef, TzFieldDef } from "./types/index.ts";
|
|
3
3
|
export declare function createTextField<R extends true | false = false>(overrides: Partial<Omit<TextFieldDef, "type" | "required" | keyof ResolvedPiiFlags>> & PersonalAnnotations & {
|
|
4
4
|
required?: R;
|
|
5
|
-
}):
|
|
5
|
+
}): EntityTextFieldDef & {
|
|
6
6
|
required: R;
|
|
7
7
|
};
|
|
8
8
|
/**
|
|
@@ -25,7 +25,7 @@ export declare function createTextField<R extends true | false = false>(override
|
|
|
25
25
|
export declare function createDerivedField(spec: DerivedFieldDef): DerivedFieldDef;
|
|
26
26
|
export declare function createLongTextField<R extends true | false = false>(overrides: Partial<Omit<LongTextFieldDef, "type" | "required" | keyof ResolvedPiiFlags>> & PersonalAnnotationsLongText & {
|
|
27
27
|
required?: R;
|
|
28
|
-
}):
|
|
28
|
+
}): EntityLongTextFieldDef & {
|
|
29
29
|
required: R;
|
|
30
30
|
};
|
|
31
31
|
export declare function createBooleanField<R extends true | false = false>(overrides?: Partial<Omit<BooleanFieldDef, "type" | "required">> & {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// r.step.read.findOne — load a single row from a projection table.
|
|
2
2
|
//
|
|
3
|
-
// Thin wrapper on selectMany(db, table, where, { limit: 1 }) (
|
|
3
|
+
// Thin wrapper on selectMany(db, table, where, { limit: 1 }) (db/query.ts).
|
|
4
4
|
// Resolves to the first row or null. Tenant-filtered like ctx.db
|
|
5
5
|
// method-form reads (own tenant + SYSTEM_TENANT_ID reference rows); a
|
|
6
6
|
// foreign `where.tenantId` is narrowed to the caller's own scope.
|
|
@@ -6,7 +6,7 @@ export type { KumikoEntityTypeMap, KumikoEventTypeMap, KumikoHandlerPayloadMap,
|
|
|
6
6
|
export type { ExtensionOptionsArgs, ExtensionOptionsFor, KumikoExtensionOptionsMap, } from "@cosmicdrift/kumiko-types/extension-options-map";
|
|
7
7
|
export type { BootCheckContext, BootCheckFn, BootOptionName, EntityObjectDefinition, FeatureDefinition, FeatureMetricDef, FeatureMetricType, FeatureRegistrar, HookDefinition, MetricOptions, Registry, SecretKeyDefinition, SecretKeyHandle, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretOptions, StackBootRequirement, StoreTableDef, StoreTableEntry, StoreTableOptions, UiHintOption, UiHints, } from "@cosmicdrift/kumiko-types/feature";
|
|
8
8
|
export type { FieldIconKey } from "@cosmicdrift/kumiko-types/field-icon";
|
|
9
|
-
export type { AnyFileFieldDef, BigIntFieldDef, BooleanFieldDef, ClientDerivedFieldDef, DateFieldDef, DecimalFieldDef, DefaultCurrency, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EmbeddedDerivedCellDef, EmbeddedFieldDef, EmbeddedSubFieldDef, EntityDefinition, EntityEventMigration, EntityIndexDef, FieldAccess, FieldDefinition, FieldsMap, FileFieldDef, FilesFieldDef, Findability, ImageFieldDef, ImagesFieldDef, JsonbFieldDef, LocatedTimestampFieldDef, LongTextFieldDef, LongTextFindability, MoneyCurrencySource, MoneyFieldDef, MultiSelectFieldDef, NumberFieldDef, OptionsQueryPayload, OptionsQueryPayloadValue, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, ReferenceFieldDef, ResolvedPiiFlags, RetentionDef, SelectFieldDef, SelectOptionTone, TextFieldDef, TimestampFieldDef, TransitionMap, TzFieldDef, } from "@cosmicdrift/kumiko-types/fields";
|
|
9
|
+
export type { AnyFileFieldDef, BigIntFieldDef, BooleanFieldDef, ClientDerivedFieldDef, DateFieldDef, DecimalFieldDef, DefaultCurrency, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EmbeddedDerivedCellDef, EmbeddedFieldDef, EmbeddedSubFieldDef, EntityDefinition, EntityEventMigration, EntityFieldDefinition, EntityIndexDef, EntityLongTextFieldDef, EntityTextFieldDef, FieldAccess, FieldDefinition, FieldsMap, FileFieldDef, FilesFieldDef, Findability, ImageFieldDef, ImagesFieldDef, JsonbFieldDef, LocatedTimestampFieldDef, LongTextFieldDef, LongTextFindability, MoneyCurrencySource, MoneyFieldDef, MultiSelectFieldDef, NumberFieldDef, OptionsQueryPayload, OptionsQueryPayloadValue, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, ReferenceFieldDef, ResolvedPiiFlags, ResolvedPiiStance, RetentionDef, SelectFieldDef, SelectOptionTone, TextFieldDef, TimestampFieldDef, TransitionMap, TzFieldDef, } from "@cosmicdrift/kumiko-types/fields";
|
|
10
10
|
export type { AccessRule, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AggregateStreamHandle, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BeforeTransactionPhase, BeforeTransactionResult, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, DeclarativeEventMigration, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DispatchWriteRef, EntityRef, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, EventMigrationDef, EventPiiFields, EventPiiStance, EventUpcastCtx, EventUpcastFn, FetchForWritingArgs, HandlerContext, HandlerRef, JobContext, JobDeferral, JobRunnerRef, JobTriggerEvent, MemberReader, NameOrRef, NotifyDeferred, NotifyDelivery, NotifyDeliveryStatus, NotifyDispatched, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, PayloadBucketNormalization, PayloadRateLimitOption, PreparedWriteEvent, ProviderPluginContext, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitDeclaration, RateLimitDisabled, RateLimitOption, RateLimitOptions, RateLimitPer, RoleAccessPersonalData, RoleAccessRule, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, TenantWriteTarget, TryAppendEventFn, TryAppendEventResult, UnsafeAppendEventFn, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "@cosmicdrift/kumiko-types/handlers";
|
|
11
11
|
export { DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, declaredRateLimitOptions, HTTP_ERROR_CODE_PATTERN, isDeliveryErrorCode, isOpenToAllGranted, isRateLimitDisabled, isRateLimitOption, isUiAccessGranted, } from "@cosmicdrift/kumiko-types/handlers";
|
|
12
12
|
export type { DeleteContext, EntityHookMap, HookMap, HookOwner, HookPhase, LifecycleHookFn, LifecycleResult, OwnedFn, PhasedHook, PostDeleteBatchHookFn, PostDeleteHookFn, PostQueryHookFn, PostSaveBatchHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, SaveContext, SearchPayloadContributorFn, ValidationError, ValidationHookFn, } from "@cosmicdrift/kumiko-types/hooks";
|
|
@@ -32,7 +32,7 @@ export async function appendProvenanceEvent(db, event) {
|
|
|
32
32
|
message: `appendProvenanceEvent: event tenant "${event.tenantId}" does not match the TenantDb's tenant "${db.tenantId}".`,
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
|
-
//
|
|
35
|
+
// postgres-js aborts the whole surrounding begin() on a statement
|
|
36
36
|
// error (25P02) even when the caller's catch swallows it — the savepoint
|
|
37
37
|
// confines that to this scope. Pool connections have no ambient tx to poison.
|
|
38
38
|
await runInSavepointIfSupported(runner, async (sp) => {
|
|
@@ -1,72 +1,12 @@
|
|
|
1
1
|
export { FILE_REF_ENTITY_NAME } from "./types.ts";
|
|
2
2
|
export declare const fileRefEntity: import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
|
|
3
|
-
readonly storageKey: {
|
|
4
|
-
readonly type: "text";
|
|
5
|
-
readonly description?: string;
|
|
6
|
-
readonly maxLength?: number;
|
|
7
|
-
readonly minLength?: number;
|
|
8
|
-
readonly autoComplete?: string;
|
|
9
|
-
readonly required?: boolean;
|
|
10
|
-
readonly searchable?: boolean;
|
|
11
|
-
readonly sortable?: boolean;
|
|
12
|
-
readonly filterable?: boolean;
|
|
13
|
-
readonly encrypted?: boolean;
|
|
14
|
-
readonly sensitive?: boolean;
|
|
15
|
-
readonly writeOnly?: boolean;
|
|
16
|
-
readonly readAsInstruction?: boolean;
|
|
17
|
-
readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
|
|
18
|
-
readonly default?: string;
|
|
19
|
-
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
20
|
-
readonly multiline?: boolean | {
|
|
21
|
-
readonly rows?: number;
|
|
22
|
-
};
|
|
23
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
3
|
+
readonly storageKey: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
24
4
|
required: true;
|
|
25
5
|
};
|
|
26
|
-
readonly fileName: {
|
|
27
|
-
readonly type: "text";
|
|
28
|
-
readonly description?: string;
|
|
29
|
-
readonly maxLength?: number;
|
|
30
|
-
readonly minLength?: number;
|
|
31
|
-
readonly autoComplete?: string;
|
|
32
|
-
readonly required?: boolean;
|
|
33
|
-
readonly searchable?: boolean;
|
|
34
|
-
readonly sortable?: boolean;
|
|
35
|
-
readonly filterable?: boolean;
|
|
36
|
-
readonly encrypted?: boolean;
|
|
37
|
-
readonly sensitive?: boolean;
|
|
38
|
-
readonly writeOnly?: boolean;
|
|
39
|
-
readonly readAsInstruction?: boolean;
|
|
40
|
-
readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
|
|
41
|
-
readonly default?: string;
|
|
42
|
-
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
43
|
-
readonly multiline?: boolean | {
|
|
44
|
-
readonly rows?: number;
|
|
45
|
-
};
|
|
46
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
6
|
+
readonly fileName: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
47
7
|
required: true;
|
|
48
8
|
};
|
|
49
|
-
readonly mimeType: {
|
|
50
|
-
readonly type: "text";
|
|
51
|
-
readonly description?: string;
|
|
52
|
-
readonly maxLength?: number;
|
|
53
|
-
readonly minLength?: number;
|
|
54
|
-
readonly autoComplete?: string;
|
|
55
|
-
readonly required?: boolean;
|
|
56
|
-
readonly searchable?: boolean;
|
|
57
|
-
readonly sortable?: boolean;
|
|
58
|
-
readonly filterable?: boolean;
|
|
59
|
-
readonly encrypted?: boolean;
|
|
60
|
-
readonly sensitive?: boolean;
|
|
61
|
-
readonly writeOnly?: boolean;
|
|
62
|
-
readonly readAsInstruction?: boolean;
|
|
63
|
-
readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
|
|
64
|
-
readonly default?: string;
|
|
65
|
-
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
66
|
-
readonly multiline?: boolean | {
|
|
67
|
-
readonly rows?: number;
|
|
68
|
-
};
|
|
69
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
9
|
+
readonly mimeType: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
70
10
|
required: true;
|
|
71
11
|
};
|
|
72
12
|
readonly size: {
|
|
@@ -81,73 +21,13 @@ export declare const fileRefEntity: import("@cosmicdrift/kumiko-types/fields").E
|
|
|
81
21
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
82
22
|
required: true;
|
|
83
23
|
};
|
|
84
|
-
readonly entityType: {
|
|
85
|
-
readonly type: "text";
|
|
86
|
-
readonly description?: string;
|
|
87
|
-
readonly maxLength?: number;
|
|
88
|
-
readonly minLength?: number;
|
|
89
|
-
readonly autoComplete?: string;
|
|
90
|
-
readonly required?: boolean;
|
|
91
|
-
readonly searchable?: boolean;
|
|
92
|
-
readonly sortable?: boolean;
|
|
93
|
-
readonly filterable?: boolean;
|
|
94
|
-
readonly encrypted?: boolean;
|
|
95
|
-
readonly sensitive?: boolean;
|
|
96
|
-
readonly writeOnly?: boolean;
|
|
97
|
-
readonly readAsInstruction?: boolean;
|
|
98
|
-
readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
|
|
99
|
-
readonly default?: string;
|
|
100
|
-
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
101
|
-
readonly multiline?: boolean | {
|
|
102
|
-
readonly rows?: number;
|
|
103
|
-
};
|
|
104
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
24
|
+
readonly entityType: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
105
25
|
required: false;
|
|
106
26
|
};
|
|
107
|
-
readonly entityId: {
|
|
108
|
-
readonly type: "text";
|
|
109
|
-
readonly description?: string;
|
|
110
|
-
readonly maxLength?: number;
|
|
111
|
-
readonly minLength?: number;
|
|
112
|
-
readonly autoComplete?: string;
|
|
113
|
-
readonly required?: boolean;
|
|
114
|
-
readonly searchable?: boolean;
|
|
115
|
-
readonly sortable?: boolean;
|
|
116
|
-
readonly filterable?: boolean;
|
|
117
|
-
readonly encrypted?: boolean;
|
|
118
|
-
readonly sensitive?: boolean;
|
|
119
|
-
readonly writeOnly?: boolean;
|
|
120
|
-
readonly readAsInstruction?: boolean;
|
|
121
|
-
readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
|
|
122
|
-
readonly default?: string;
|
|
123
|
-
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
124
|
-
readonly multiline?: boolean | {
|
|
125
|
-
readonly rows?: number;
|
|
126
|
-
};
|
|
127
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
27
|
+
readonly entityId: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
128
28
|
required: false;
|
|
129
29
|
};
|
|
130
|
-
readonly fieldName: {
|
|
131
|
-
readonly type: "text";
|
|
132
|
-
readonly description?: string;
|
|
133
|
-
readonly maxLength?: number;
|
|
134
|
-
readonly minLength?: number;
|
|
135
|
-
readonly autoComplete?: string;
|
|
136
|
-
readonly required?: boolean;
|
|
137
|
-
readonly searchable?: boolean;
|
|
138
|
-
readonly sortable?: boolean;
|
|
139
|
-
readonly filterable?: boolean;
|
|
140
|
-
readonly encrypted?: boolean;
|
|
141
|
-
readonly sensitive?: boolean;
|
|
142
|
-
readonly writeOnly?: boolean;
|
|
143
|
-
readonly readAsInstruction?: boolean;
|
|
144
|
-
readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
|
|
145
|
-
readonly default?: string;
|
|
146
|
-
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
147
|
-
readonly multiline?: boolean | {
|
|
148
|
-
readonly rows?: number;
|
|
149
|
-
};
|
|
150
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
30
|
+
readonly fieldName: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
151
31
|
required: false;
|
|
152
32
|
};
|
|
153
33
|
}, "tenant">;
|
|
@@ -1,71 +1,11 @@
|
|
|
1
1
|
export declare const fileRefsTable: import("../db/table-builder.ts").EntityTable<import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
|
|
2
|
-
readonly storageKey: {
|
|
3
|
-
readonly type: "text";
|
|
4
|
-
readonly description?: string;
|
|
5
|
-
readonly maxLength?: number;
|
|
6
|
-
readonly minLength?: number;
|
|
7
|
-
readonly autoComplete?: string;
|
|
8
|
-
readonly required?: boolean;
|
|
9
|
-
readonly searchable?: boolean;
|
|
10
|
-
readonly sortable?: boolean;
|
|
11
|
-
readonly filterable?: boolean;
|
|
12
|
-
readonly encrypted?: boolean;
|
|
13
|
-
readonly sensitive?: boolean;
|
|
14
|
-
readonly writeOnly?: boolean;
|
|
15
|
-
readonly readAsInstruction?: boolean;
|
|
16
|
-
readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
|
|
17
|
-
readonly default?: string;
|
|
18
|
-
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
19
|
-
readonly multiline?: boolean | {
|
|
20
|
-
readonly rows?: number;
|
|
21
|
-
};
|
|
22
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
2
|
+
readonly storageKey: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
23
3
|
required: true;
|
|
24
4
|
};
|
|
25
|
-
readonly fileName: {
|
|
26
|
-
readonly type: "text";
|
|
27
|
-
readonly description?: string;
|
|
28
|
-
readonly maxLength?: number;
|
|
29
|
-
readonly minLength?: number;
|
|
30
|
-
readonly autoComplete?: string;
|
|
31
|
-
readonly required?: boolean;
|
|
32
|
-
readonly searchable?: boolean;
|
|
33
|
-
readonly sortable?: boolean;
|
|
34
|
-
readonly filterable?: boolean;
|
|
35
|
-
readonly encrypted?: boolean;
|
|
36
|
-
readonly sensitive?: boolean;
|
|
37
|
-
readonly writeOnly?: boolean;
|
|
38
|
-
readonly readAsInstruction?: boolean;
|
|
39
|
-
readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
|
|
40
|
-
readonly default?: string;
|
|
41
|
-
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
42
|
-
readonly multiline?: boolean | {
|
|
43
|
-
readonly rows?: number;
|
|
44
|
-
};
|
|
45
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
5
|
+
readonly fileName: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
46
6
|
required: true;
|
|
47
7
|
};
|
|
48
|
-
readonly mimeType: {
|
|
49
|
-
readonly type: "text";
|
|
50
|
-
readonly description?: string;
|
|
51
|
-
readonly maxLength?: number;
|
|
52
|
-
readonly minLength?: number;
|
|
53
|
-
readonly autoComplete?: string;
|
|
54
|
-
readonly required?: boolean;
|
|
55
|
-
readonly searchable?: boolean;
|
|
56
|
-
readonly sortable?: boolean;
|
|
57
|
-
readonly filterable?: boolean;
|
|
58
|
-
readonly encrypted?: boolean;
|
|
59
|
-
readonly sensitive?: boolean;
|
|
60
|
-
readonly writeOnly?: boolean;
|
|
61
|
-
readonly readAsInstruction?: boolean;
|
|
62
|
-
readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
|
|
63
|
-
readonly default?: string;
|
|
64
|
-
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
65
|
-
readonly multiline?: boolean | {
|
|
66
|
-
readonly rows?: number;
|
|
67
|
-
};
|
|
68
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
8
|
+
readonly mimeType: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
69
9
|
required: true;
|
|
70
10
|
};
|
|
71
11
|
readonly size: {
|
|
@@ -80,73 +20,13 @@ export declare const fileRefsTable: import("../db/table-builder.ts").EntityTable
|
|
|
80
20
|
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
81
21
|
required: true;
|
|
82
22
|
};
|
|
83
|
-
readonly entityType: {
|
|
84
|
-
readonly type: "text";
|
|
85
|
-
readonly description?: string;
|
|
86
|
-
readonly maxLength?: number;
|
|
87
|
-
readonly minLength?: number;
|
|
88
|
-
readonly autoComplete?: string;
|
|
89
|
-
readonly required?: boolean;
|
|
90
|
-
readonly searchable?: boolean;
|
|
91
|
-
readonly sortable?: boolean;
|
|
92
|
-
readonly filterable?: boolean;
|
|
93
|
-
readonly encrypted?: boolean;
|
|
94
|
-
readonly sensitive?: boolean;
|
|
95
|
-
readonly writeOnly?: boolean;
|
|
96
|
-
readonly readAsInstruction?: boolean;
|
|
97
|
-
readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
|
|
98
|
-
readonly default?: string;
|
|
99
|
-
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
100
|
-
readonly multiline?: boolean | {
|
|
101
|
-
readonly rows?: number;
|
|
102
|
-
};
|
|
103
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
23
|
+
readonly entityType: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
104
24
|
required: false;
|
|
105
25
|
};
|
|
106
|
-
readonly entityId: {
|
|
107
|
-
readonly type: "text";
|
|
108
|
-
readonly description?: string;
|
|
109
|
-
readonly maxLength?: number;
|
|
110
|
-
readonly minLength?: number;
|
|
111
|
-
readonly autoComplete?: string;
|
|
112
|
-
readonly required?: boolean;
|
|
113
|
-
readonly searchable?: boolean;
|
|
114
|
-
readonly sortable?: boolean;
|
|
115
|
-
readonly filterable?: boolean;
|
|
116
|
-
readonly encrypted?: boolean;
|
|
117
|
-
readonly sensitive?: boolean;
|
|
118
|
-
readonly writeOnly?: boolean;
|
|
119
|
-
readonly readAsInstruction?: boolean;
|
|
120
|
-
readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
|
|
121
|
-
readonly default?: string;
|
|
122
|
-
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
123
|
-
readonly multiline?: boolean | {
|
|
124
|
-
readonly rows?: number;
|
|
125
|
-
};
|
|
126
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
26
|
+
readonly entityId: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
127
27
|
required: false;
|
|
128
28
|
};
|
|
129
|
-
readonly fieldName: {
|
|
130
|
-
readonly type: "text";
|
|
131
|
-
readonly description?: string;
|
|
132
|
-
readonly maxLength?: number;
|
|
133
|
-
readonly minLength?: number;
|
|
134
|
-
readonly autoComplete?: string;
|
|
135
|
-
readonly required?: boolean;
|
|
136
|
-
readonly searchable?: boolean;
|
|
137
|
-
readonly sortable?: boolean;
|
|
138
|
-
readonly filterable?: boolean;
|
|
139
|
-
readonly encrypted?: boolean;
|
|
140
|
-
readonly sensitive?: boolean;
|
|
141
|
-
readonly writeOnly?: boolean;
|
|
142
|
-
readonly readAsInstruction?: boolean;
|
|
143
|
-
readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
|
|
144
|
-
readonly default?: string;
|
|
145
|
-
readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
|
|
146
|
-
readonly multiline?: boolean | {
|
|
147
|
-
readonly rows?: number;
|
|
148
|
-
};
|
|
149
|
-
} & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
|
|
29
|
+
readonly fieldName: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
150
30
|
required: false;
|
|
151
31
|
};
|
|
152
32
|
}, "tenant">>;
|
|
@@ -23,6 +23,9 @@ const DEFAULT_PRIVILEGED_ROLES = ["Admin", "SystemAdmin"];
|
|
|
23
23
|
// leaked URL (e.g. from a browser history screenshot) isn't a long-lived
|
|
24
24
|
// credential (see "Signed-URL default expiry" in core-files.md).
|
|
25
25
|
const SIGNED_URL_DEFAULT_EXPIRY_SECONDS = 15 * 60;
|
|
26
|
+
const MAX_BATCH_META_IDS = 100;
|
|
27
|
+
// Each detail() read decrypts through the KMS pool; a bounded chunk keeps one batch from hogging it.
|
|
28
|
+
const BATCH_META_CONCURRENCY = 10;
|
|
26
29
|
// Default guard: on attached files, allow the uploader or a privileged role,
|
|
27
30
|
// and for reads also everyone who may read the record. Delete stays with the
|
|
28
31
|
// uploader and privileged roles: reading a record is no right to remove its
|
|
@@ -218,6 +221,25 @@ export function createFileRoutes(options) {
|
|
|
218
221
|
return c.json({ error: "upload_failed" }, 500);
|
|
219
222
|
return c.json(outcome.fileRef, 201);
|
|
220
223
|
});
|
|
224
|
+
// GET /files/meta?ids=a,b,c — meta of many files in one request (list/card
|
|
225
|
+
// cells otherwise fire one request per row). Unreadable and unknown ids are
|
|
226
|
+
// left out of the answer, same as /files/:id/meta answering 404 for them.
|
|
227
|
+
// Registered before /files/:id so "meta" is never taken for an id.
|
|
228
|
+
api.get("/files/meta", async (c) => {
|
|
229
|
+
const user = getUser(c);
|
|
230
|
+
const ids = [...new Set((c.req.query("ids") ?? "").split(",").filter((id) => isUuid(id)))];
|
|
231
|
+
if (ids.length > MAX_BATCH_META_IDS)
|
|
232
|
+
return c.json({ error: "too_many_ids" }, 400);
|
|
233
|
+
const files = [];
|
|
234
|
+
for (let start = 0; start < ids.length; start += BATCH_META_CONCURRENCY) {
|
|
235
|
+
const chunk = ids.slice(start, start + BATCH_META_CONCURRENCY);
|
|
236
|
+
const metas = await Promise.all(chunk.map((id) => loadReadableFileMeta(id, user)));
|
|
237
|
+
for (const meta of metas)
|
|
238
|
+
if (meta)
|
|
239
|
+
files.push(meta);
|
|
240
|
+
}
|
|
241
|
+
return c.json({ files });
|
|
242
|
+
});
|
|
221
243
|
// GET /files/:id — download.
|
|
222
244
|
//
|
|
223
245
|
// Authorization stack:
|
|
@@ -388,31 +410,28 @@ export function createFileRoutes(options) {
|
|
|
388
410
|
const expiresAt = new Date(Date.now() + expiresInSeconds * 1000).toISOString();
|
|
389
411
|
return c.json({ url, expiresAt });
|
|
390
412
|
});
|
|
391
|
-
//
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
-
|
|
396
|
-
const user = getUser(c);
|
|
397
|
-
const id = c.req.param("id");
|
|
413
|
+
// Metadata without the bytes. Unlike the byte-serving routes, this returns
|
|
414
|
+
// fileName as JSON — and fileName is `personal: "self"` (fw#2423), so it must
|
|
415
|
+
// go through the entity executor's detail() to decrypt instead of
|
|
416
|
+
// loadFileForTenant's raw, still-encrypted read.
|
|
417
|
+
async function loadReadableFileMeta(id, user) {
|
|
398
418
|
// Same 22P02-avoidance as loadFileForTenant — detail() doesn't validate
|
|
399
419
|
// id shape before it hits the UUID column.
|
|
400
420
|
if (!isUuid(id))
|
|
401
|
-
return
|
|
421
|
+
return null;
|
|
402
422
|
const row = await executor.detail({ id }, user, tenantDbFor(user.tenantId));
|
|
403
423
|
if (!row)
|
|
404
|
-
return
|
|
424
|
+
return null;
|
|
405
425
|
const fileRef = row; // @cast-boundary db-row (decrypted via executor.detail)
|
|
406
426
|
// detail()'s "pass"-ownership read widens to tenantId IN (self, SYSTEM),
|
|
407
427
|
// narrower in loadFileForTenant's selectMany. Restore that here so meta
|
|
408
428
|
// doesn't leak reference-tenant rows.
|
|
409
|
-
if (fileRef.tenantId !== user.tenantId)
|
|
410
|
-
return
|
|
411
|
-
}
|
|
429
|
+
if (fileRef.tenantId !== user.tenantId)
|
|
430
|
+
return null;
|
|
412
431
|
const decision = await guard({ fileRef, user, operation: "read" });
|
|
413
432
|
if (decision === "deny")
|
|
414
|
-
return
|
|
415
|
-
return
|
|
433
|
+
return null;
|
|
434
|
+
return {
|
|
416
435
|
id: fileRef.id,
|
|
417
436
|
fileName: fileRef.fileName,
|
|
418
437
|
mimeType: fileRef.mimeType,
|
|
@@ -420,11 +439,15 @@ export function createFileRoutes(options) {
|
|
|
420
439
|
entityType: fileRef.entityType,
|
|
421
440
|
entityId: fileRef.entityId,
|
|
422
441
|
fieldName: fileRef.fieldName,
|
|
423
|
-
}
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
api.get("/files/:id/meta", async (c) => {
|
|
445
|
+
const meta = await loadReadableFileMeta(c.req.param("id"), getUser(c));
|
|
446
|
+
return meta ? c.json(meta) : c.json({ error: "not_found" }, 404);
|
|
424
447
|
});
|
|
425
448
|
async function loadFileForTenant(id, tenantId) {
|
|
426
449
|
// fileRefsTable.id is a UUID column — a malformed id must fail here, not
|
|
427
|
-
// at Postgres (22P02 poisons the pooled
|
|
450
|
+
// at Postgres (22P02 poisons the pooled connection). 404, not
|
|
428
451
|
// 400: an authenticated caller sending a bad id must see the same
|
|
429
452
|
// response as a nonexistent one, no existence-oracle.
|
|
430
453
|
if (!isUuid(id))
|
|
@@ -31,5 +31,25 @@ export type TranslationLocaleGap = {
|
|
|
31
31
|
readonly key: string;
|
|
32
32
|
readonly missingLocales: readonly string[];
|
|
33
33
|
};
|
|
34
|
-
/**
|
|
35
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Per feature, every translation key must carry every locale that any key of the
|
|
36
|
+
* same feature carries. A feature with a single locale across all keys is
|
|
37
|
+
* consistent by definition (locale packages, en-only framework features).
|
|
38
|
+
*/
|
|
39
|
+
export declare function findTranslationLocaleGaps(features: readonly FeatureDefinition[]): readonly TranslationLocaleGap[];
|
|
40
|
+
export type RequiredLocaleGap = {
|
|
41
|
+
readonly featureName: string;
|
|
42
|
+
readonly key: string;
|
|
43
|
+
readonly missingLocales: readonly string[];
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Per translation key, the union of locales over ALL features must cover
|
|
47
|
+
* `requiredLocales`. Locale packages translate other features' keys under the
|
|
48
|
+
* full `feature:key` name, so the lookup unions by effective name. Raw names
|
|
49
|
+
* only come from locale packages and already-prefixed keys. Entries that only translate another
|
|
50
|
+
* mounted feature's key are not reported separately; the defining feature is.
|
|
51
|
+
* Locale packages (`locale-*`) only contribute locales: their keys often belong to
|
|
52
|
+
* client bundles (e.g. `auth.errors.*`) that no feature defines, so they are never
|
|
53
|
+
* reported as gap owners.
|
|
54
|
+
*/
|
|
55
|
+
export declare function findRequiredLocaleGaps(features: readonly FeatureDefinition[], requiredLocales: readonly string[]): readonly RequiredLocaleGap[];
|
|
@@ -519,7 +519,95 @@ export function buildEffectiveTranslationKeys(features) {
|
|
|
519
519
|
}
|
|
520
520
|
return out;
|
|
521
521
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
522
|
+
function hasLocaleValue(value) {
|
|
523
|
+
if (value === undefined)
|
|
524
|
+
return false;
|
|
525
|
+
return typeof value === "string" ? value !== "" : true;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Per feature, every translation key must carry every locale that any key of the
|
|
529
|
+
* same feature carries. A feature with a single locale across all keys is
|
|
530
|
+
* consistent by definition (locale packages, en-only framework features).
|
|
531
|
+
*/
|
|
532
|
+
export function findTranslationLocaleGaps(features) {
|
|
533
|
+
const gaps = [];
|
|
534
|
+
for (const feature of features) {
|
|
535
|
+
const entries = Object.entries(feature.translations ?? {});
|
|
536
|
+
const featureLocales = new Set();
|
|
537
|
+
for (const [, byLocale] of entries) {
|
|
538
|
+
for (const locale of Object.keys(byLocale))
|
|
539
|
+
featureLocales.add(locale);
|
|
540
|
+
}
|
|
541
|
+
if (featureLocales.size < 2)
|
|
542
|
+
continue;
|
|
543
|
+
const sortedLocales = [...featureLocales].sort();
|
|
544
|
+
for (const [key, byLocale] of entries) {
|
|
545
|
+
const missingLocales = sortedLocales.filter((locale) => !hasLocaleValue(byLocale[locale]));
|
|
546
|
+
if (missingLocales.length > 0)
|
|
547
|
+
gaps.push({ featureName: feature.name, key, missingLocales });
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return gaps;
|
|
551
|
+
}
|
|
552
|
+
const LOCALE_PACKAGE_PREFIX = "locale-";
|
|
553
|
+
function collectLocalesByName(features) {
|
|
554
|
+
// A plain key of a regular feature is only its own; registering it raw would let two
|
|
555
|
+
// features with the same plain key cover each other's missing locales.
|
|
556
|
+
const namesOf = (featureName, key) => featureName.startsWith(LOCALE_PACKAGE_PREFIX) || key.includes(":")
|
|
557
|
+
? [`${featureName}:${key}`, key]
|
|
558
|
+
: [`${featureName}:${key}`];
|
|
559
|
+
const localesByName = new Map();
|
|
560
|
+
for (const feature of features) {
|
|
561
|
+
for (const [key, byLocale] of Object.entries(feature.translations ?? {})) {
|
|
562
|
+
const present = Object.entries(byLocale)
|
|
563
|
+
.filter(([, value]) => hasLocaleValue(value))
|
|
564
|
+
.map(([locale]) => locale);
|
|
565
|
+
for (const name of namesOf(feature.name, key)) {
|
|
566
|
+
const locales = localesByName.get(name) ?? new Set();
|
|
567
|
+
for (const locale of present)
|
|
568
|
+
locales.add(locale);
|
|
569
|
+
localesByName.set(name, locales);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return localesByName;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Per translation key, the union of locales over ALL features must cover
|
|
577
|
+
* `requiredLocales`. Locale packages translate other features' keys under the
|
|
578
|
+
* full `feature:key` name, so the lookup unions by effective name. Raw names
|
|
579
|
+
* only come from locale packages and already-prefixed keys. Entries that only translate another
|
|
580
|
+
* mounted feature's key are not reported separately; the defining feature is.
|
|
581
|
+
* Locale packages (`locale-*`) only contribute locales: their keys often belong to
|
|
582
|
+
* client bundles (e.g. `auth.errors.*`) that no feature defines, so they are never
|
|
583
|
+
* reported as gap owners.
|
|
584
|
+
*/
|
|
585
|
+
export function findRequiredLocaleGaps(features, requiredLocales) {
|
|
586
|
+
const featureNames = new Set(features.map((feature) => feature.name));
|
|
587
|
+
const localesByName = collectLocalesByName(features);
|
|
588
|
+
const gaps = [];
|
|
589
|
+
const reported = new Set();
|
|
590
|
+
for (const feature of features) {
|
|
591
|
+
if (feature.name.startsWith(LOCALE_PACKAGE_PREFIX))
|
|
592
|
+
continue;
|
|
593
|
+
for (const key of Object.keys(feature.translations ?? {})) {
|
|
594
|
+
const prefix = key.includes(":") ? key.slice(0, key.indexOf(":")) : undefined;
|
|
595
|
+
const translatesOtherFeature = prefix !== undefined && prefix !== feature.name && featureNames.has(prefix);
|
|
596
|
+
if (translatesOtherFeature)
|
|
597
|
+
continue;
|
|
598
|
+
const effective = key.includes(":") ? key : `${feature.name}:${key}`;
|
|
599
|
+
if (reported.has(effective))
|
|
600
|
+
continue;
|
|
601
|
+
// Locale packages spell plurals as flat `.one`/`.other` keys; `other` is the form every locale has.
|
|
602
|
+
const present = new Set([effective, key, `${effective}.other`, `${key}.other`].flatMap((name) => [
|
|
603
|
+
...(localesByName.get(name) ?? []),
|
|
604
|
+
]));
|
|
605
|
+
const missingLocales = requiredLocales.filter((locale) => !present.has(locale));
|
|
606
|
+
if (missingLocales.length > 0) {
|
|
607
|
+
reported.add(effective);
|
|
608
|
+
gaps.push({ featureName: feature.name, key, missingLocales });
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return gaps;
|
|
525
613
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/// <reference types="temporal-polyfill/global" preserve="true" />
|
|
2
|
+
import type { ConfigAccessor } from "@cosmicdrift/kumiko-types/config";
|
|
3
|
+
import type { TzContext } from "@cosmicdrift/kumiko-types/tz-context";
|
|
4
|
+
import type { JobsOptions, Queue } from "bullmq";
|
|
5
|
+
import type { TenantDb } from "../db/tenant-db.ts";
|
|
6
|
+
import type { AppContext, EscapeHatchReporter, JobContext, JobDefinition, JobRunIn, JobTriggerEvent } from "../engine/types/index.ts";
|
|
7
|
+
import type { Logger } from "../logging/types.ts";
|
|
8
|
+
import type { Meter, SerializedTraceContext, Tracer } from "../observability/index.ts";
|
|
9
|
+
import type { JobLogEntry, JobSubjectValue } from "./job-runner-types.ts";
|
|
10
|
+
export declare function isJobTriggerEvent(value: unknown): value is JobTriggerEvent;
|
|
11
|
+
export declare function stampGatedWriteOrigin(data: Record<string, unknown>): void;
|
|
12
|
+
export declare function createJobLogger(logs: JobLogEntry[], baseContext?: Record<string, unknown>): Logger;
|
|
13
|
+
export declare function serializeJobSubject(subject: Readonly<Record<string, JobSubjectValue>>): string;
|
|
14
|
+
export declare function jobSubjectKey(jobName: string, payload: Record<string, unknown>, fields: readonly string[] | undefined, declaration: "tenantVisibleFailure" | "tenantVisibleRun"): string | null;
|
|
15
|
+
export declare function resolveJobTz(config: ConfigAccessor | undefined, context: Pick<AppContext, "geoTzProvider" | "clock">): Promise<TzContext>;
|
|
16
|
+
export declare function missingDbProxy(jobName: string): TenantDb;
|
|
17
|
+
export declare function createCrossTenantReads(systemModeDb: TenantDb, reason: string, report: EscapeHatchReporter): NonNullable<JobContext["crossTenantReads"]>;
|
|
18
|
+
export declare function priorAttemptsOf(data: Record<string, unknown>): number;
|
|
19
|
+
export declare function tenantFailureMessageKey(err: unknown, declaration: JobDefinition["tenantVisibleFailure"]): string | null;
|
|
20
|
+
export declare const TRACE_CONTEXT_KEY = "_traceContext";
|
|
21
|
+
export declare const DEFER_COUNT_KEY = "_deferCount";
|
|
22
|
+
export declare const HANDLER_DEFER_COUNT_KEY = "_handlerDeferCount";
|
|
23
|
+
export declare const DEFAULT_MAX_DEFERS = 100;
|
|
24
|
+
export declare const DEFER_DELAY_CAP_MS = 30000;
|
|
25
|
+
export declare const DEFER_BACKOFF_MAX_DOUBLINGS = 5;
|
|
26
|
+
export declare function readTraceContext(data: Record<string, unknown>): SerializedTraceContext | undefined;
|
|
27
|
+
export declare function captureTraceContext(tracer: Tracer): SerializedTraceContext | undefined;
|
|
28
|
+
export declare function startQueueDepthPolling(queue: Queue, lane: JobRunIn, meter: Meter): Promise<ReturnType<typeof setInterval>>;
|
|
29
|
+
export declare function parseRedisOpts(url: string): {
|
|
30
|
+
host: string;
|
|
31
|
+
port: number;
|
|
32
|
+
db?: number | undefined;
|
|
33
|
+
};
|
|
34
|
+
export declare const BOOT_REDIS_TIMEOUT_MS = 10000;
|
|
35
|
+
export declare const STOP_QUEUE_READY_TIMEOUT_MS = 1000;
|
|
36
|
+
export declare const BOOT_GATE_SLOW_WARN_MS = 60000;
|
|
37
|
+
export declare function timeoutReject(ms: number, message: string): {
|
|
38
|
+
promise: Promise<never>;
|
|
39
|
+
cancel: () => void;
|
|
40
|
+
};
|
|
41
|
+
export declare function buildRetryBullOpts(jobDef: JobDefinition, priorAttempts?: number): Pick<JobsOptions, "attempts" | "backoff">;
|
|
42
|
+
export declare function payloadMatchesTriggerFilter(where: Readonly<Record<string, string | number | boolean>> | undefined, payload: Record<string, unknown>): boolean;
|
|
43
|
+
export declare const COMPLETED_JOB_RETENTION_AGE_SEC = 86400;
|
|
44
|
+
export declare const FAILED_JOB_RETENTION_AGE_SEC = 604800;
|
|
45
|
+
export declare function maxRetryWindowMs(jobDef: JobDefinition): number;
|