@cosmicdrift/kumiko-framework 0.364.0 → 0.365.1
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.md +36 -5
- package/agents/examples/anonymous-access-multitenant.md +9 -6
- package/agents/examples/anonymous-access.md +14 -26
- package/agents/examples/auth-mfa.md +5 -9
- package/agents/examples/custom-fields-basic.md +7 -6
- package/agents/examples/custom-handlers.md +72 -25
- package/agents/examples/dashboard-stats.md +229 -0
- package/agents/examples/encrypted-tenant-config.md +17 -8
- package/agents/examples/error-contract.md +1 -5
- package/agents/examples/feature-toggles.md +28 -17
- package/agents/examples/ledger-banking.md +7 -6
- package/agents/examples/ledger-invoicing.md +7 -6
- package/agents/examples/legal-pages.md +2 -2
- package/agents/examples/managed-pages.md +2 -2
- package/agents/examples/notes-history-basic.md +7 -6
- package/agents/examples/ownership.md +30 -22
- package/agents/examples/pdf-audit-export.md +87 -51
- package/agents/examples/session-revocation.md +1 -10
- package/agents/examples/tags-basic.md +7 -7
- package/agents/examples/user-data-rights.md +25 -17
- package/agents/examples/webhook-step.md +11 -1
- package/agents/examples/workflow-engine.md +2 -2
- package/agents/examples.md +2 -1
- package/agents/features.md +4 -4
- package/dist/api/auth-routes.d.ts +0 -1
- package/dist/api/auth-routes.js +44 -43
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.js +1 -1
- package/dist/api/server.d.ts +2 -2
- package/dist/api/server.js +29 -4
- package/dist/bun-db/connection.js +3 -16
- package/dist/bun-db/index.d.ts +1 -1
- package/dist/bun-db/index.js +1 -1
- package/dist/bun-db/query.d.ts +7 -0
- package/dist/bun-db/query.js +50 -18
- package/dist/crypto/in-memory-kms-adapter.js +4 -7
- package/dist/crypto/owner-transition-rekey.d.ts +1 -0
- package/dist/crypto/owner-transition-rekey.js +7 -3
- package/dist/crypto/pg-kms-adapter.js +10 -6
- package/dist/crypto/pii-field-encryption.d.ts +1 -0
- package/dist/crypto/pii-field-encryption.js +9 -2
- package/dist/crypto/subject-resolver.d.ts +1 -0
- package/dist/crypto/subject-resolver.js +7 -0
- package/dist/db/api.d.ts +4 -3
- package/dist/db/apply-entity-event.js +26 -2
- package/dist/db/blind-index-cleanup.js +2 -1
- package/dist/db/bun-provider.js +9 -18
- package/dist/db/dialect.d.ts +1 -1
- package/dist/db/dialect.js +2 -0
- package/dist/db/eagerload.js +2 -1
- package/dist/db/event-store-executor-context.d.ts +1 -0
- package/dist/db/event-store-executor-context.js +1 -0
- package/dist/db/event-store-executor-write.d.ts +1 -1
- package/dist/db/event-store-executor-write.js +85 -58
- package/dist/db/index.d.ts +1 -0
- package/dist/db/index.js +1 -0
- package/dist/db/migrate-runner.d.ts +1 -0
- package/dist/db/migrate-runner.js +56 -31
- package/dist/db/queries/backfill-pii.js +63 -25
- package/dist/db/queries/event-consumer.js +2 -1
- package/dist/db/queries/event-store-transfer.js +15 -4
- package/dist/db/queries/event-store.js +11 -10
- package/dist/db/queries/pii-backfill-state.js +3 -2
- package/dist/db/queries/postgres-array-literal.d.ts +2 -0
- package/dist/db/queries/postgres-array-literal.js +6 -0
- package/dist/db/queries/projection-rebuild.js +3 -2
- package/dist/db/queries/shadow-swap.d.ts +1 -1
- package/dist/db/queries/shadow-swap.js +12 -8
- package/dist/db/query.d.ts +1 -1
- package/dist/db/query.js +1 -1
- package/dist/db/retired-framework-tables.js +22 -4
- package/dist/db/table-builder.js +6 -2
- package/dist/db/tenant-db.d.ts +2 -0
- package/dist/db/tenant-db.js +20 -1
- package/dist/derivatives/delete-stored-file.d.ts +4 -0
- package/dist/derivatives/delete-stored-file.js +9 -0
- package/dist/derivatives/index.d.ts +1 -1
- package/dist/derivatives/index.js +1 -1
- package/dist/engine/boot-validator/entity-handler.js +3 -0
- package/dist/engine/boot-validator/query-refs.js +14 -2
- package/dist/engine/dedupe-features.js +31 -4
- package/dist/engine/entity-handlers.js +14 -7
- package/dist/engine/index.d.ts +2 -2
- package/dist/engine/index.js +1 -1
- package/dist/engine/registry-validate.d.ts +1 -0
- package/dist/engine/registry-validate.js +21 -0
- package/dist/engine/registry.js +2 -1
- package/dist/engine/screen-helpers.d.ts +1 -0
- package/dist/engine/screen-helpers.js +2 -2
- package/dist/engine/steps/_read-source.js +5 -2
- package/dist/engine/tier-resolver-extension.d.ts +1 -0
- package/dist/engine/tier-resolver-extension.js +6 -0
- package/dist/engine/types/index.d.ts +1 -1
- package/dist/entrypoint/index.d.ts +2 -2
- package/dist/errors/docs-url.js +6 -6
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/errors/subject-erased.d.ts +1 -0
- package/dist/errors/subject-erased.js +6 -0
- package/dist/event-store/admin-api.js +3 -2
- package/dist/event-store/errors.d.ts +9 -0
- package/dist/event-store/errors.js +20 -0
- package/dist/event-store/event-store.d.ts +6 -0
- package/dist/event-store/event-store.js +28 -6
- package/dist/event-store/index.d.ts +1 -1
- package/dist/event-store/index.js +1 -1
- package/dist/files/create-file-ref.d.ts +2 -1
- package/dist/files/create-file-ref.js +16 -2
- package/dist/files/dispose-removed-file-refs.js +21 -6
- package/dist/files/feature.d.ts +1 -0
- package/dist/files/feature.js +2 -1
- package/dist/files/file-handle.js +4 -0
- package/dist/files/file-routes.js +9 -5
- package/dist/files/index.d.ts +1 -1
- package/dist/files/index.js +1 -1
- package/dist/files/purge-expired-file-ref-bytes.js +2 -3
- package/dist/files/resolve-content-type.js +40 -4
- package/dist/files/storage-tracking.js +5 -4
- package/dist/files/types.js +5 -8
- package/dist/files/zip-entry-names.d.ts +1 -0
- package/dist/files/zip-entry-names.js +46 -0
- package/dist/jobs/job-runner.d.ts +2 -0
- package/dist/jobs/job-runner.js +183 -48
- package/dist/jobs/max-per-tenant.d.ts +8 -0
- package/dist/jobs/max-per-tenant.js +14 -0
- package/dist/observability/escape-hatch-report.d.ts +1 -0
- package/dist/observability/escape-hatch-report.js +31 -2
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.js +1 -1
- package/dist/observability/metrics-handle.d.ts +1 -0
- package/dist/observability/metrics-handle.js +7 -0
- package/dist/observability/standard-metrics.js +4 -4
- package/dist/pipeline/dispatch-query.d.ts +2 -1
- package/dist/pipeline/dispatch-query.js +6 -6
- package/dist/pipeline/dispatch-shared.d.ts +1 -1
- package/dist/pipeline/dispatch-shared.js +41 -30
- package/dist/pipeline/dispatch-stream.js +10 -4
- package/dist/pipeline/dispatcher.js +3 -1
- package/dist/pipeline/event-dispatcher.d.ts +2 -2
- package/dist/pipeline/event-dispatcher.js +11 -9
- package/dist/pipeline/event-retention.d.ts +1 -0
- package/dist/pipeline/event-retention.js +7 -1
- package/dist/pipeline/index.d.ts +2 -0
- package/dist/pipeline/index.js +1 -0
- package/dist/pipeline/multi-stream-apply-context.d.ts +1 -0
- package/dist/pipeline/multi-stream-apply-context.js +1 -0
- package/dist/pipeline/notify-scope.js +3 -1
- package/dist/pipeline/pre-transaction-reservations.js +2 -1
- package/dist/schema-cli.js +12 -1
- package/dist/stack/db.d.ts +2 -2
- package/dist/stack/test-stack.js +2 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +1 -0
- package/dist/testing/multipart-helper.d.ts +6 -0
- package/dist/testing/multipart-helper.js +1 -1
- package/dist/testing/zip-fixture.d.ts +1 -0
- package/dist/testing/zip-fixture.js +33 -0
- package/dist/ui-types/card-column-roles.js +4 -2
- package/dist/ui-types/index.d.ts +1 -1
- package/dist/ui-types/index.js +1 -1
- package/dist/upgrade-cli.js +46 -13
- package/package.json +4 -4
- package/src/changes.json +323 -0
|
@@ -9,13 +9,13 @@ import { isRateLimitDisabled, resolveAgentExposure } from "../engine/types/index
|
|
|
9
9
|
import { AccessDeniedError, FeatureDisabledError, FrameworkReasons, InternalError, memberResolutionReadOnlyDenied, RateLimitError, RateLimitUnavailableError, VersionConflictError, writeFailure, } from "../errors/index.js";
|
|
10
10
|
import { archiveStream as archiveStreamHelper, isStreamArchived, restoreStream as restoreStreamHelper, } from "../event-store/archive.js";
|
|
11
11
|
import { IdempotentAppendConflictError as EventStoreIdempotentAppendConflictError, VersionConflictError as EventStoreVersionConflictError, } from "../event-store/errors.js";
|
|
12
|
-
import {
|
|
12
|
+
import { loadAggregate, loadAggregateAsOf, loadAggregateForWriting, } from "../event-store/event-store.js";
|
|
13
13
|
import { effectiveSnapshotGeneration, loadAggregateWithSnapshot, saveSnapshot, } from "../event-store/snapshot.js";
|
|
14
14
|
import { upcastStoredEvent, upcastStoredEvents } from "../event-store/upcaster.js";
|
|
15
15
|
import { createFileContext } from "../files/file-handle.js";
|
|
16
16
|
import { DEFAULT_LOCALE, isValidLocaleTag } from "../i18n/request-locale.js";
|
|
17
17
|
import { createFallbackLogger } from "../logging/utils.js";
|
|
18
|
-
import { createEscapeHatchReporter, createMetricsHandle, createNoopMetricsHandle,
|
|
18
|
+
import { createEscapeHatchReporter, createMetricsForFactory, createMetricsHandle, createNoopMetricsHandle, emitDispatcherError, emitDispatcherHandler, getFallbackTracer, observabilityContext, } from "../observability/index.js";
|
|
19
19
|
import { buildBucketKey, buildPayloadBucketKey, normalizePayloadBucketValue, } from "../rate-limit/index.js";
|
|
20
20
|
import { createTzContext, isValidIanaTimeZone } from "../time/index.js";
|
|
21
21
|
import { isPlainObject } from "../utils/is-plain-object.js";
|
|
@@ -257,10 +257,14 @@ export function applyBeforeTransactionReadOnly(handlerContext) {
|
|
|
257
257
|
...denyTransactionBoundSurfaces(handlerContext),
|
|
258
258
|
dbOutsideTransaction: undefined,
|
|
259
259
|
outsideTransaction: undefined,
|
|
260
|
-
forTenant: undefined,
|
|
261
260
|
};
|
|
262
261
|
}
|
|
263
|
-
export async function buildHandlerContext(ctx, type, user, origin, tx, afterCommitHooks, includeDeleted, transactionScope = tx ? "inTransaction" : "withoutTransaction"
|
|
262
|
+
export async function buildHandlerContext(ctx, type, user, origin, tx, afterCommitHooks, includeDeleted, transactionScope = tx ? "inTransaction" : "withoutTransaction",
|
|
263
|
+
// A query bridge has no commit sink of its own, but ctx.notify inside it must still wait for
|
|
264
|
+
// the outer write's commit: the outer sink is lent to notify only, not to scheduleAfterCommit.
|
|
265
|
+
outerNotifySink,
|
|
266
|
+
// beforeTransaction phase: ctx.forTenant stays available but every target db denies writes.
|
|
267
|
+
readOnlyForTenant = false) {
|
|
264
268
|
const { registry, appContext: context, effectiveFeatures, jobRunner, lifecycle } = ctx;
|
|
265
269
|
const isSystem = registry.isHandlerSystemScoped(type);
|
|
266
270
|
// The outer dispatcher receives a DbConnection from the server/stack;
|
|
@@ -358,23 +362,30 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
|
|
|
358
362
|
const notifyInTransactionFactory = notifyFactory && tx !== undefined
|
|
359
363
|
? (notifyUser, notifyTenantId) => notifyFactory(notifyUser, notifyTenantId, notifyJobDispatcher, outsideTxSource, context._fileProviderResolver)
|
|
360
364
|
: undefined;
|
|
365
|
+
const notifyAfterCommitFactory = notifyFactory
|
|
366
|
+
? (notifyUser, notifyTenantId) => notifyFactory(notifyUser, notifyTenantId, notifyJobDispatcher, undefined, context._fileProviderResolver)
|
|
367
|
+
: undefined;
|
|
361
368
|
const notify = buildNotify();
|
|
362
369
|
function buildNotify() {
|
|
363
|
-
if (!
|
|
370
|
+
if (!notifyAfterCommitFactory)
|
|
364
371
|
return undefined;
|
|
365
|
-
const notifyAfterCommit =
|
|
372
|
+
const notifyAfterCommit = notifyAfterCommitFactory(user, user.tenantId);
|
|
366
373
|
if (!notifyInTransactionFactory)
|
|
367
374
|
return notifyAfterCommit;
|
|
368
375
|
const notifyInTransaction = notifyInTransactionFactory(user, user.tenantId);
|
|
369
|
-
// A rolled-back write must leave no notification behind.
|
|
370
|
-
|
|
376
|
+
// A rolled-back write must leave no notification behind. Without any commit to wait for (a
|
|
377
|
+
// post-commit context) delivery stays immediate.
|
|
378
|
+
const notifyCommitSink = hasLiveCommitSink ? bridgeSink : outerNotifySink;
|
|
379
|
+
if (notifyCommitSink === undefined)
|
|
371
380
|
return notifyInTransaction;
|
|
372
381
|
return createDeferringNotify({
|
|
373
382
|
registry,
|
|
374
383
|
tenantId: user.tenantId,
|
|
375
384
|
notifyAfterCommit,
|
|
376
385
|
notifyNow: notifyInTransaction,
|
|
377
|
-
scheduleAfterCommit
|
|
386
|
+
scheduleAfterCommit: (hook) => {
|
|
387
|
+
notifyCommitSink.push(hook);
|
|
388
|
+
},
|
|
378
389
|
});
|
|
379
390
|
}
|
|
380
391
|
// Mirror notify: only built when the config feature wired its factory.
|
|
@@ -417,7 +428,7 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
|
|
|
417
428
|
if (targetTenantId === user.tenantId && exposedDb) {
|
|
418
429
|
// Only perCommand handlers lose forTenant writes; reservations keep their pre-transaction contract.
|
|
419
430
|
const ownTenantDb = denyDbWrites && !isPerCommand
|
|
420
|
-
? buildTenantScopedDb(dbSource, reqCtx?.signal)
|
|
431
|
+
? buildTenantScopedDb(dbSource, reqCtx?.signal, undefined, readOnlyForTenant)
|
|
421
432
|
: exposedDb;
|
|
422
433
|
return { db: ownTenantDb, streamTenantId: undefined };
|
|
423
434
|
}
|
|
@@ -427,7 +438,7 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
|
|
|
427
438
|
});
|
|
428
439
|
}
|
|
429
440
|
return {
|
|
430
|
-
db: buildTenantScopedDb(dbSource, reqCtx?.signal, targetTenantId, denyDbWrites && isPerCommand),
|
|
441
|
+
db: buildTenantScopedDb(dbSource, reqCtx?.signal, targetTenantId, denyDbWrites && (isPerCommand || readOnlyForTenant)),
|
|
431
442
|
streamTenantId: targetTenantId,
|
|
432
443
|
};
|
|
433
444
|
}
|
|
@@ -455,16 +466,13 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
|
|
|
455
466
|
const metrics = meter && featureName ? createMetricsHandle(meter, featureName) : createNoopMetricsHandle();
|
|
456
467
|
// ctx.metricsFor(featureName) — shared/library code binds to a feature
|
|
457
468
|
// name of its own choosing instead of the dispatching handler's
|
|
458
|
-
// (framework#1844). Unregistered names no-op rather than throw
|
|
459
|
-
|
|
460
|
-
const metricsFor = meter
|
|
461
|
-
? (targetFeatureName) => createSafeMetricsHandle(meter, targetFeatureName)
|
|
462
|
-
: () => createNoopMetricsHandle();
|
|
469
|
+
// (framework#1844). Unregistered names no-op rather than throw.
|
|
470
|
+
const metricsFor = createMetricsForFactory(meter);
|
|
463
471
|
// A failed inner write has already appended its event and projections when a later
|
|
464
472
|
// step (e.g. an inTransaction hook) fails, and the outer handler may swallow the
|
|
465
473
|
// failure result. The savepoint makes the failure undo exactly the inner write; the
|
|
466
474
|
// inner afterCommit hooks only reach the outer sink if it succeeded.
|
|
467
|
-
const
|
|
475
|
+
const runNestedWriteInSavepoint = async (asUser, targetType, payload) => {
|
|
468
476
|
if (registry.getWriteHandler(targetType)?.transaction === "perCommand") {
|
|
469
477
|
return writeFailure(perCommandNestedDenied(targetType));
|
|
470
478
|
}
|
|
@@ -488,6 +496,15 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
|
|
|
488
496
|
throw e;
|
|
489
497
|
}
|
|
490
498
|
};
|
|
499
|
+
// Savepoints on one connection are not serialized by the driver: two parallel nested writes would
|
|
500
|
+
// interleave their statements and a ROLLBACK TO of the first would also discard the second's rows.
|
|
501
|
+
// Writes nested one level deeper run on their own savepoint handle and therefore their own chain.
|
|
502
|
+
let nestedWriteChain = Promise.resolve();
|
|
503
|
+
const executeNestedWriteInSavepoint = (asUser, targetType, payload) => {
|
|
504
|
+
const run = nestedWriteChain.then(() => runNestedWriteInSavepoint(asUser, targetType, payload));
|
|
505
|
+
nestedWriteChain = run.catch(() => undefined);
|
|
506
|
+
return run;
|
|
507
|
+
};
|
|
491
508
|
// Without an open transaction (post-commit hooks, stream/query handlers, perCommand handlers) a bare
|
|
492
509
|
// executeWrite would auto-commit event append, projections and inTransaction hooks separately and its
|
|
493
510
|
// afterCommit hooks would land in a sink nobody flushes. A batch-of-one gives the write its own
|
|
@@ -498,7 +515,7 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
|
|
|
498
515
|
const identitySwitchAudit = { reason: identitySwitchGrantReason, report: reportEscapeHatch };
|
|
499
516
|
const identitySwitch = createGatedIdentitySwitch(`handler "${type}"`, user, hasIdentitySwitchGrant, {
|
|
500
517
|
// Inherits the caller's origin, so switching to SYSTEM cannot shed an anonymous root.
|
|
501
|
-
queryAs: (asUser, targetType, payload) => executeQuery(ctx, targetType, payload, asUser, origin, tx), // @wrapper-known semantic-alias
|
|
518
|
+
queryAs: (asUser, targetType, payload) => executeQuery(ctx, targetType, payload, asUser, origin, tx, hasLiveCommitSink ? bridgeSink : outerNotifySink), // @wrapper-known semantic-alias
|
|
502
519
|
writeAs: writeAsUser,
|
|
503
520
|
}, identitySwitchAudit);
|
|
504
521
|
// Lazy — creates the reader (which fails closed on SYSTEM_TENANT_ID) only
|
|
@@ -542,7 +559,7 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
|
|
|
542
559
|
return projTable === undefined || hasTenantColumn(projTable);
|
|
543
560
|
});
|
|
544
561
|
const bridge = {
|
|
545
|
-
query: (targetType, payload) => executeQuery(ctx, targetType, payload, user, origin, tx), // @wrapper-known semantic-alias
|
|
562
|
+
query: (targetType, payload) => executeQuery(ctx, targetType, payload, user, origin, tx, hasLiveCommitSink ? bridgeSink : outerNotifySink), // @wrapper-known semantic-alias
|
|
546
563
|
queryAs: identitySwitch.queryAs,
|
|
547
564
|
write: (targetType, payload) => writeAsUser(user, targetType, payload),
|
|
548
565
|
writeAs: identitySwitch.writeAs,
|
|
@@ -597,16 +614,9 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
|
|
|
597
614
|
message: `ctx.fetchForWriting("${args.aggregateId}") requires a database connection — none is configured.`,
|
|
598
615
|
});
|
|
599
616
|
}
|
|
600
|
-
// Stream-version authoritative (same policy as CRUD executor + Block 0)
|
|
601
|
-
//
|
|
602
|
-
|
|
603
|
-
// the events (business-rule checks), so fetch both in parallel.
|
|
604
|
-
const [storedEvents, fetchedVersion] = await Promise.all([
|
|
605
|
-
loadAggregate(dbSource, args.aggregateId, user.tenantId, {
|
|
606
|
-
aggregateType: args.aggregateType,
|
|
607
|
-
}),
|
|
608
|
-
getStreamVersion(dbSource, args.aggregateId, user.tenantId),
|
|
609
|
-
]);
|
|
617
|
+
// Stream-version authoritative (same policy as CRUD executor + Block 0), read together
|
|
618
|
+
// with the events so the pinned version covers exactly what the handler's rules saw.
|
|
619
|
+
const { events: storedEvents, streamVersion: fetchedVersion } = await loadAggregateForWriting(dbSource, args.aggregateId, user.tenantId, { aggregateType: args.aggregateType });
|
|
610
620
|
const events = await upcastStoredEvents(storedEvents, registry.getEventUpcasters(), {
|
|
611
621
|
db: dbSource,
|
|
612
622
|
tenantId: user.tenantId,
|
|
@@ -831,11 +841,12 @@ export async function buildHandlerContext(ctx, type, user, origin, tx, afterComm
|
|
|
831
841
|
// able to compute bucket keys.
|
|
832
842
|
const { _rateLimitPayloadDigest: payloadDigest,
|
|
833
843
|
// Bound to this call's tx: an outer context's factory must not leak into a tx-less one.
|
|
834
|
-
_notifyInTransactionFactory: _omitOuterNotifyInTransactionFactory, ...contextForHandler } = context;
|
|
844
|
+
_notifyInTransactionFactory: _omitOuterNotifyInTransactionFactory, _notifyAfterCommitFactory: _omitOuterNotifyAfterCommitFactory, ...contextForHandler } = context;
|
|
835
845
|
const handlerContext = {
|
|
836
846
|
...contextForHandler,
|
|
837
847
|
...(payloadDigest && { rateLimitDigest: createHandlerBoundDigest(payloadDigest, type) }),
|
|
838
848
|
...(notifyInTransactionFactory && { _notifyInTransactionFactory: notifyInTransactionFactory }),
|
|
849
|
+
...(notifyAfterCommitFactory && { _notifyAfterCommitFactory: notifyAfterCommitFactory }),
|
|
839
850
|
registry,
|
|
840
851
|
db: exposedDb,
|
|
841
852
|
dbOutsideTransaction,
|
|
@@ -2,7 +2,7 @@ import { accessInvalidationCredentialFor } from "../api/sse-broker.js";
|
|
|
2
2
|
import { scheduleTokenExpiry } from "../api/token-expiry-timer.js";
|
|
3
3
|
import { hasAccess } from "../engine/access.js";
|
|
4
4
|
import { tenantOverrideDenied } from "../engine/cross-tenant.js";
|
|
5
|
-
import { AccessDeniedError, memberResolutionReadOnlyDenied, NotFoundError, validationErrorFromZod, } from "../errors/index.js";
|
|
5
|
+
import { AccessDeniedError, memberResolutionReadOnlyDenied, NotFoundError, UnauthenticatedError, validationErrorFromZod, } from "../errors/index.js";
|
|
6
6
|
import { assertNoSecretLeak } from "../secrets/index.js";
|
|
7
7
|
import { buildHandlerContext, enforceRateLimit, ensureFeatureEnabled, isMemberResolutionPrincipal, runStreamInstrumented, } from "./dispatch-shared.js";
|
|
8
8
|
import { handlerAccessError } from "./handler-access-error.js";
|
|
@@ -69,10 +69,16 @@ async function* executeStreamInner(ctx, type, payload, user, origin, options) {
|
|
|
69
69
|
abandonedForInvalidation = true;
|
|
70
70
|
void nextPull.catch(() => { });
|
|
71
71
|
void iterator.return?.(undefined)?.then(undefined, () => { });
|
|
72
|
+
// An expired token is a lapsed session (401, so clients show the
|
|
73
|
+
// session-end hint); revocation is a permission change (403).
|
|
74
|
+
if (outcome.reason === "expired") {
|
|
75
|
+
throw new UnauthenticatedError({
|
|
76
|
+
message: `token expired mid-stream for ${type}`,
|
|
77
|
+
details: { handler: type, reason: outcome.reason },
|
|
78
|
+
});
|
|
79
|
+
}
|
|
72
80
|
throw new AccessDeniedError({
|
|
73
|
-
message:
|
|
74
|
-
? `token expired mid-stream for ${type}`
|
|
75
|
-
: `access revoked mid-stream for ${type}`,
|
|
81
|
+
message: `access revoked mid-stream for ${type}`,
|
|
76
82
|
details: { handler: type },
|
|
77
83
|
});
|
|
78
84
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { runWithWriteOrigin } from "../api/request-context.js";
|
|
2
2
|
import { TENANT_MEMBERSHIPS_QUERY } from "../engine/extension-names.js";
|
|
3
3
|
import { InternalError, reraiseAsKumikoError } from "../errors/index.js";
|
|
4
|
+
import { withEscapeHatchAuditScopeStream } from "../observability/escape-hatch-report.js";
|
|
4
5
|
import { createEscapeHatchReportWindow, getFallbackMeter, getFallbackTracer, registerStandardMetrics, withEscapeHatchAuditScope, } from "../observability/index.js";
|
|
5
6
|
import { CACHE_SYNC_TOPICS, isTenantConfigSyncMessage } from "../redis/cache-sync-topics.js";
|
|
6
7
|
import { INTERACTIVE_SIGN_IN_POLICY, resolveActiveMembershipFn } from "./active-membership.js";
|
|
@@ -114,7 +115,8 @@ export function createDispatcher(registry, context, options = {}) {
|
|
|
114
115
|
},
|
|
115
116
|
stream: (typeOrRef, payload, user, options) => {
|
|
116
117
|
const type = resolveType(typeOrRef);
|
|
117
|
-
|
|
118
|
+
const origin = rootWriteOrigin(registry, type, user);
|
|
119
|
+
return withEscapeHatchAuditScopeStream(() => executeStream(ctx, type, payload, user, origin, options));
|
|
118
120
|
},
|
|
119
121
|
async command(typeOrRef, payload, user) {
|
|
120
122
|
const type = resolveType(typeOrRef);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PgListenClient } from "../db/connection.js";
|
|
2
2
|
import type { AppContext } from "../engine/types/index.js";
|
|
3
3
|
import { type StoredEvent } from "../event-store/index.js";
|
|
4
4
|
import { type Meter, type Tracer } from "../observability/index.js";
|
|
@@ -46,7 +46,7 @@ export type EventDispatcherOptions = {
|
|
|
46
46
|
readonly tracer?: Tracer;
|
|
47
47
|
readonly meter?: Meter;
|
|
48
48
|
readonly instanceId?: string;
|
|
49
|
-
readonly pgClient?:
|
|
49
|
+
readonly pgClient?: PgListenClient;
|
|
50
50
|
};
|
|
51
51
|
export declare function createEventDispatcher(options: EventDispatcherOptions): EventDispatcher;
|
|
52
52
|
export type { ConsumerProgress, ConsumerRecoveryState } from "./event-dispatcher-admin.js";
|
|
@@ -114,7 +114,7 @@ export function createEventDispatcher(options) {
|
|
|
114
114
|
// to EVENTS_PUBSUB_CHANNEL; cleared by .stop(). The timer remains active
|
|
115
115
|
// even with LISTEN attached — it's a cheap safety net against missed
|
|
116
116
|
// NOTIFYs (subscription drop, crash mid-commit).
|
|
117
|
-
let
|
|
117
|
+
let pgSubscription = null;
|
|
118
118
|
// Bounds how many consumer turns hold a DB transaction at once. Each turn's
|
|
119
119
|
// db.begin() checks out one pool connection, plus any writes the handler
|
|
120
120
|
// itself makes via context.db — the app db pool defaults to postgres-js's
|
|
@@ -498,15 +498,16 @@ export function createEventDispatcher(options) {
|
|
|
498
498
|
// succeeds (onlisten callback). It drops to 0 on the initial connect
|
|
499
499
|
// failure, on stop() and when the idle pre-check detects a DB outage.
|
|
500
500
|
// An outage shorter than one poll tick is not detected; the gauge then
|
|
501
|
-
// stays at 1 until
|
|
501
|
+
// stays at 1 until the driver's delayed re-LISTEN lands.
|
|
502
502
|
emitEventDispatcherListenConnected(meter, false);
|
|
503
503
|
if (options.pgClient) {
|
|
504
504
|
try {
|
|
505
505
|
const sub = await options.pgClient.listen(EVENTS_PUBSUB_CHANNEL, runBackgroundPass, () => {
|
|
506
|
-
// Fires on initial connect and again when
|
|
506
|
+
// Fires on initial connect and again when the driver's own
|
|
507
507
|
// re-LISTEN lands after a drop; during an outage that re-LISTEN
|
|
508
|
-
// waits in its connection backoff (measured 10-30s
|
|
509
|
-
// returns
|
|
508
|
+
// waits in its connection backoff (postgres-js: measured 10-30s
|
|
509
|
+
// after the DB returns, Bun.SQL: up to 32s), polling covers
|
|
510
|
+
// delivery meanwhile. Never unlisten()
|
|
510
511
|
// + listen() by hand: that leaves duplicate listeners.
|
|
511
512
|
// ponytail: no own LISTEN reconnect, the backoff gap is polled;
|
|
512
513
|
// add a dedicated LISTEN connection if prod wires a pgClient
|
|
@@ -514,7 +515,7 @@ export function createEventDispatcher(options) {
|
|
|
514
515
|
listenSubscribed = true;
|
|
515
516
|
emitEventDispatcherListenConnected(meter, true);
|
|
516
517
|
});
|
|
517
|
-
|
|
518
|
+
pgSubscription = sub;
|
|
518
519
|
}
|
|
519
520
|
catch (e) {
|
|
520
521
|
emitEventDispatcherListenConnected(meter, false);
|
|
@@ -532,12 +533,13 @@ export function createEventDispatcher(options) {
|
|
|
532
533
|
clearInterval(timer);
|
|
533
534
|
timer = null;
|
|
534
535
|
}
|
|
535
|
-
if (
|
|
536
|
-
|
|
536
|
+
if (pgSubscription) {
|
|
537
|
+
// Called on the subscription: Bun.SQL's unlisten is a prototype method.
|
|
538
|
+
await pgSubscription.unlisten().catch(() => {
|
|
537
539
|
// skip: unlisten failure only matters during shutdown — the
|
|
538
540
|
// subscription is being torn down anyway.
|
|
539
541
|
});
|
|
540
|
-
|
|
542
|
+
pgSubscription = null;
|
|
541
543
|
listenSubscribed = false;
|
|
542
544
|
emitEventDispatcherListenConnected(meter, false);
|
|
543
545
|
}
|
|
@@ -34,6 +34,10 @@ export async function pruneEvents(db, options) {
|
|
|
34
34
|
if (options.aggregateIds !== undefined && options.aggregateIds.length === 0) {
|
|
35
35
|
throw new Error("pruneEvents: aggregateIds must be non-empty when set.");
|
|
36
36
|
}
|
|
37
|
+
if (options.batchSize !== undefined &&
|
|
38
|
+
(!Number.isInteger(options.batchSize) || options.batchSize < 1)) {
|
|
39
|
+
throw new Error("pruneEvents: batchSize must be a positive integer.");
|
|
40
|
+
}
|
|
37
41
|
const aggregateTypes = options.aggregateTypes;
|
|
38
42
|
const aggregateIds = options.aggregateIds;
|
|
39
43
|
const dryRun = options.dryRun === true;
|
|
@@ -56,7 +60,9 @@ export async function pruneEvents(db, options) {
|
|
|
56
60
|
aggregateType: [...aggregateTypes],
|
|
57
61
|
...(aggregateIds ? { aggregateId: [...aggregateIds] } : {}),
|
|
58
62
|
createdAt: { lt: cutoff },
|
|
59
|
-
}
|
|
63
|
+
}, options.batchSize === undefined
|
|
64
|
+
? undefined
|
|
65
|
+
: { orderBy: { col: "id", direction: "asc" }, limit: options.batchSize });
|
|
60
66
|
if (candidates.length === 0) {
|
|
61
67
|
return { deletedCount: 0, cutoff, aggregateTypes, dryRun };
|
|
62
68
|
}
|
package/dist/pipeline/index.d.ts
CHANGED
|
@@ -31,6 +31,8 @@ export type { LifecycleHooks, SystemHookDef, SystemHooks } from "./lifecycle-pip
|
|
|
31
31
|
export { createLifecycleHooks } from "./lifecycle-pipeline.js";
|
|
32
32
|
export type { MspRebuildDeps } from "./msp-rebuild.js";
|
|
33
33
|
export { rebuildMultiStreamProjection } from "./msp-rebuild.js";
|
|
34
|
+
export type { MultiStreamApplyContext, MultiStreamApplyContextDeps, } from "./multi-stream-apply-context.js";
|
|
35
|
+
export { createMultiStreamApplyContext } from "./multi-stream-apply-context.js";
|
|
34
36
|
export { assertNotifyTargetsOwnTenant, createDeferringNotify } from "./notify-scope.js";
|
|
35
37
|
export type { ProjectionProgress, RebuildResult } from "./projection-rebuild.js";
|
|
36
38
|
export { getAllProjectionProgress, getProjectionState, listProjectionsWithState, rebuildProjection, } from "./projection-rebuild.js";
|
package/dist/pipeline/index.js
CHANGED
|
@@ -16,6 +16,7 @@ export { createIdempotencyGuard } from "./idempotency.js";
|
|
|
16
16
|
export { assertInstructionFieldWriteAllowed, assertIrreversibleOperationAllowed, isIrreversibleEntityVerb, } from "./irreversible-operation-gate.js";
|
|
17
17
|
export { createLifecycleHooks } from "./lifecycle-pipeline.js";
|
|
18
18
|
export { rebuildMultiStreamProjection } from "./msp-rebuild.js";
|
|
19
|
+
export { createMultiStreamApplyContext } from "./multi-stream-apply-context.js";
|
|
19
20
|
export { assertNotifyTargetsOwnTenant, createDeferringNotify } from "./notify-scope.js";
|
|
20
21
|
export { getAllProjectionProgress, getProjectionState, listProjectionsWithState, rebuildProjection, } from "./projection-rebuild.js";
|
|
21
22
|
export { createProjectionStateTable, ProjectionStatuses, projectionStateTable, } from "./projection-state.js";
|
|
@@ -14,5 +14,6 @@ export type MultiStreamApplyContextDeps = {
|
|
|
14
14
|
readonly files?: FileContext;
|
|
15
15
|
readonly derivatives?: DerivativesContext;
|
|
16
16
|
readonly secrets?: SecretsContext;
|
|
17
|
+
readonly jobs?: MultiStreamApplyContext["jobs"];
|
|
17
18
|
};
|
|
18
19
|
export declare function createMultiStreamApplyContext(deps: MultiStreamApplyContextDeps): MultiStreamApplyContext;
|
|
@@ -7,6 +7,7 @@ export function createMultiStreamApplyContext(deps) {
|
|
|
7
7
|
...(deps.files ? { files: deps.files } : {}),
|
|
8
8
|
...(deps.derivatives ? { derivatives: deps.derivatives } : {}),
|
|
9
9
|
...(deps.secrets ? { secrets: deps.secrets } : {}),
|
|
10
|
+
...(deps.jobs ? { jobs: deps.jobs } : {}),
|
|
10
11
|
appendEvent: (async (args) => {
|
|
11
12
|
await appendDomainEventCore({
|
|
12
13
|
registry: deps.registry,
|
|
@@ -23,8 +23,10 @@ export function createDeferringNotify(deps) {
|
|
|
23
23
|
assertNotificationVariables(registry, notificationType, options.data);
|
|
24
24
|
if (options.immediate === true)
|
|
25
25
|
return notifyNow(notificationType, options);
|
|
26
|
+
// The hook runs after the handler returned; a caller reusing `data` meanwhile must not change what is sent.
|
|
27
|
+
const optionsAtCallTime = structuredClone(options);
|
|
26
28
|
scheduleAfterCommit(async () => {
|
|
27
|
-
await notifyAfterCommit(notificationType,
|
|
29
|
+
await notifyAfterCommit(notificationType, optionsAtCallTime);
|
|
28
30
|
});
|
|
29
31
|
return { kind: "deferred" };
|
|
30
32
|
}
|
|
@@ -73,7 +73,8 @@ export async function reserveBeforeTransaction(ctx, commands, user, inheritedOri
|
|
|
73
73
|
}
|
|
74
74
|
let prepared;
|
|
75
75
|
if (handler.beforeTransaction) {
|
|
76
|
-
const
|
|
76
|
+
const phaseContext = await buildHandlerContext(ctx, command.type, user, origin, undefined, undefined, undefined, undefined, undefined, true);
|
|
77
|
+
const phase = await handler.beforeTransaction(event, applyBeforeTransactionReadOnly(phaseContext));
|
|
77
78
|
if (!phase.isSuccess) {
|
|
78
79
|
await releaseAll(ctx, releases);
|
|
79
80
|
return { isSuccess: false, error: phase.error, failedIndex: index };
|
package/dist/schema-cli.js
CHANGED
|
@@ -439,8 +439,19 @@ export async function runSchemaCli(argv, appCwd, out, options = {}) {
|
|
|
439
439
|
await reportLeftoverDroppedTables(db, migrationsDir, out);
|
|
440
440
|
return pending === 0 ? 0 : 1;
|
|
441
441
|
}
|
|
442
|
+
catch (e) {
|
|
443
|
+
// Message only, never dbUrl: it can carry the password.
|
|
444
|
+
out.err(` ✗ schema status failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
445
|
+
out.err(' Check that DATABASE_URL is reachable and the database exists (the scaffold\'s docker-compose creates "app").');
|
|
446
|
+
return 1;
|
|
447
|
+
}
|
|
442
448
|
finally {
|
|
443
|
-
|
|
449
|
+
try {
|
|
450
|
+
await close();
|
|
451
|
+
}
|
|
452
|
+
catch {
|
|
453
|
+
// The connection already failed and was reported above; a failing close must not throw again.
|
|
454
|
+
}
|
|
444
455
|
}
|
|
445
456
|
}
|
|
446
457
|
default: {
|
package/dist/stack/db.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { DbConnection } from "@cosmicdrift/kumiko-types/db-connection";
|
|
1
|
+
import type { DbConnection, PgListenClient } from "@cosmicdrift/kumiko-types/db-connection";
|
|
2
2
|
declare function requireEnv(name: string): string;
|
|
3
3
|
export type TestDb = {
|
|
4
4
|
db: DbConnection;
|
|
5
5
|
outsideTransactionDb: DbConnection;
|
|
6
|
-
client:
|
|
6
|
+
client: PgListenClient;
|
|
7
7
|
dbName: string;
|
|
8
8
|
cleanup: () => Promise<void>;
|
|
9
9
|
};
|
package/dist/stack/test-stack.js
CHANGED
|
@@ -275,7 +275,8 @@ export async function setupTestStack(options) {
|
|
|
275
275
|
(appContext.redis === undefined || options.rateLimitNowMs !== undefined);
|
|
276
276
|
const rateLimitFallback = needsOwnRateLimitResolver
|
|
277
277
|
? createRateLimitResolver({
|
|
278
|
-
redis
|
|
278
|
+
// An app-supplied redis keeps buckets and app redis on the same instance.
|
|
279
|
+
redis: appContext.redis ?? testRedis.redis,
|
|
279
280
|
...(options.rateLimitNowMs ? { nowMs: options.rateLimitNowMs } : {}),
|
|
280
281
|
})
|
|
281
282
|
: undefined;
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -22,3 +22,4 @@ export { sharedItemEntity, sharedItemTable, sharedUserEntity, sharedUserTable, s
|
|
|
22
22
|
export { sleep } from "./utils.js";
|
|
23
23
|
export { waitFor } from "./wait-for.js";
|
|
24
24
|
export { withoutAmbientTemporal } from "./without-ambient-temporal.js";
|
|
25
|
+
export { zipWithEntries } from "./zip-fixture.js";
|
package/dist/testing/index.js
CHANGED
|
@@ -30,3 +30,4 @@ export { sharedItemEntity, sharedItemTable, sharedUserEntity, sharedUserTable, s
|
|
|
30
30
|
export { sleep } from "./utils.js";
|
|
31
31
|
export { waitFor } from "./wait-for.js";
|
|
32
32
|
export { withoutAmbientTemporal } from "./without-ambient-temporal.js";
|
|
33
|
+
export { zipWithEntries } from "./zip-fixture.js";
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serializes a FormData instance to multipart/form-data bytes.
|
|
3
|
+
*
|
|
4
|
+
* Returns the encoded body and the Content-Type header value (including the
|
|
5
|
+
* generated boundary). Pass both directly to app.request or fetch.
|
|
6
|
+
*/
|
|
1
7
|
export declare function buildMultipartBody(fd: FormData): Promise<{
|
|
2
8
|
body: BodyInit;
|
|
3
9
|
contentType: string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
1
2
|
// Workarounds for Bun v1.3.x bun:test limitations with multipart/form-data.
|
|
2
3
|
//
|
|
3
4
|
// Two bugs combine to break file upload tests in bun:test:
|
|
@@ -19,7 +20,6 @@
|
|
|
19
20
|
* Returns the encoded body and the Content-Type header value (including the
|
|
20
21
|
* generated boundary). Pass both directly to app.request or fetch.
|
|
21
22
|
*/
|
|
22
|
-
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
23
23
|
export async function buildMultipartBody(fd) {
|
|
24
24
|
const boundary = `KumikoBnd${Date.now()}${Math.random().toString(36).slice(2, 8)}`;
|
|
25
25
|
const enc = new TextEncoder();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function zipWithEntries(...entryNames: readonly string[]): Uint8Array;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const LOCAL_HEADER_FIXED_LENGTH = 30;
|
|
2
|
+
const CENTRAL_HEADER_FIXED_LENGTH = 46;
|
|
3
|
+
const EOCD_LENGTH = 22;
|
|
4
|
+
// Minimal stored ZIP (no compression, zero CRC): enough structure for readers that
|
|
5
|
+
// walk the central directory. Not for extraction.
|
|
6
|
+
export function zipWithEntries(...entryNames) {
|
|
7
|
+
const encoder = new TextEncoder();
|
|
8
|
+
const names = entryNames.map((name) => encoder.encode(name));
|
|
9
|
+
const localSize = names.reduce((sum, name) => sum + LOCAL_HEADER_FIXED_LENGTH + name.length, 0);
|
|
10
|
+
const centralSize = names.reduce((sum, name) => sum + CENTRAL_HEADER_FIXED_LENGTH + name.length, 0);
|
|
11
|
+
const bytes = new Uint8Array(localSize + centralSize + EOCD_LENGTH);
|
|
12
|
+
const view = new DataView(bytes.buffer);
|
|
13
|
+
let localOffset = 0;
|
|
14
|
+
let centralOffset = localSize;
|
|
15
|
+
for (const name of names) {
|
|
16
|
+
view.setUint32(localOffset, 0x04034b50, true);
|
|
17
|
+
view.setUint16(localOffset + 26, name.length, true);
|
|
18
|
+
bytes.set(name, localOffset + LOCAL_HEADER_FIXED_LENGTH);
|
|
19
|
+
view.setUint32(centralOffset, 0x02014b50, true);
|
|
20
|
+
view.setUint16(centralOffset + 28, name.length, true);
|
|
21
|
+
view.setUint32(centralOffset + 42, localOffset, true);
|
|
22
|
+
bytes.set(name, centralOffset + CENTRAL_HEADER_FIXED_LENGTH);
|
|
23
|
+
localOffset += LOCAL_HEADER_FIXED_LENGTH + name.length;
|
|
24
|
+
centralOffset += CENTRAL_HEADER_FIXED_LENGTH + name.length;
|
|
25
|
+
}
|
|
26
|
+
const eocdOffset = localSize + centralSize;
|
|
27
|
+
view.setUint32(eocdOffset, 0x06054b50, true);
|
|
28
|
+
view.setUint16(eocdOffset + 8, names.length, true);
|
|
29
|
+
view.setUint16(eocdOffset + 10, names.length, true);
|
|
30
|
+
view.setUint32(eocdOffset + 12, centralSize, true);
|
|
31
|
+
view.setUint32(eocdOffset + 16, localSize, true);
|
|
32
|
+
return bytes;
|
|
33
|
+
}
|
|
@@ -18,8 +18,10 @@ export function cardFileDisplayKind(type, renderer) {
|
|
|
18
18
|
// boot validator so the validator counts exactly what the card would draw.
|
|
19
19
|
// The first image column is the card's leading preview, neither title nor meta.
|
|
20
20
|
export function cardColumnRoles(columns) {
|
|
21
|
-
const
|
|
22
|
-
const
|
|
21
|
+
const isImage = (col) => col.fileDisplay?.kind === "image";
|
|
22
|
+
const image = columns.find((col) => isImage(col) && col.hideOnNarrow !== true);
|
|
23
|
+
// A narrow-hidden image must not turn into the title just because it is not the preview.
|
|
24
|
+
const textColumns = columns.filter((col) => col !== image && !(isImage(col) && col.hideOnNarrow === true));
|
|
23
25
|
const title = textColumns.find((col) => col.highlighted === true) ?? textColumns[0];
|
|
24
26
|
const status = textColumns.find((col) => col !== title && col.type === "select" && col.renderer === undefined);
|
|
25
27
|
const meta = textColumns.filter((col) => col !== title && col !== status && col.hideOnNarrow !== true);
|
package/dist/ui-types/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { computeDerivedCellValue, roundDerivedCellValue } from "../engine/embedd
|
|
|
4
4
|
export type { ParsedRefTarget } from "../engine/parse-ref-target.js";
|
|
5
5
|
export { parseRefTarget } from "../engine/parse-ref-target.js";
|
|
6
6
|
export type { FieldsOrGroupsSection, UnavailableSelectOption } from "../engine/screen-helpers.js";
|
|
7
|
-
export { availableSelectOptions, evalFieldCondition, explicitListScreenId, findUnavailableSelectOptions, isExtensionEditSection, isFieldsEditSection, isFormatSpec, isRecordLocked, isWriteFormEditSection, normalizeEditField, normalizeListColumn, resolveNavParentScreen, sectionFieldSpecs, } from "../engine/screen-helpers.js";
|
|
7
|
+
export { availableSelectOptions, evalFieldCondition, explicitListScreenId, findUnavailableSelectOptions, isExtensionEditSection, isFieldsEditSection, isFormatSpec, isRecordLocked, isUnchangedValue, isWriteFormEditSection, normalizeEditField, normalizeListColumn, resolveNavParentScreen, sectionFieldSpecs, } from "../engine/screen-helpers.js";
|
|
8
8
|
export type { BooleanFieldDef, DateFieldDef, EmbeddedDerivedCellDef, EntityDefinition, FieldDefinition, FileFieldDef, FilesFieldDef, ImageFieldDef, ImagesFieldDef, NumberFieldDef, OptionsQueryPayload, OptionsQueryPayloadValue, SelectFieldDef, SelectOptionTone, TextFieldDef, } from "../engine/types/fields.js";
|
|
9
9
|
export type { AccessRule, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, RoleAccessPersonalData, RoleAccessRule, } from "../engine/types/handlers.js";
|
|
10
10
|
export { isOpenToAllGranted, isUiAccessGranted } from "../engine/types/handlers.js";
|
package/dist/ui-types/index.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
export { NO_WIDGET_FIELD_TYPES } from "@cosmicdrift/kumiko-types/fields";
|
|
23
23
|
export { computeDerivedCellValue, roundDerivedCellValue } from "../engine/embedded-derived.js";
|
|
24
24
|
export { parseRefTarget } from "../engine/parse-ref-target.js";
|
|
25
|
-
export { availableSelectOptions, evalFieldCondition, explicitListScreenId, findUnavailableSelectOptions, isExtensionEditSection, isFieldsEditSection, isFormatSpec, isRecordLocked, isWriteFormEditSection, normalizeEditField, normalizeListColumn, resolveNavParentScreen, sectionFieldSpecs, } from "../engine/screen-helpers.js";
|
|
25
|
+
export { availableSelectOptions, evalFieldCondition, explicitListScreenId, findUnavailableSelectOptions, isExtensionEditSection, isFieldsEditSection, isFormatSpec, isRecordLocked, isUnchangedValue, isWriteFormEditSection, normalizeEditField, normalizeListColumn, resolveNavParentScreen, sectionFieldSpecs, } from "../engine/screen-helpers.js";
|
|
26
26
|
export { isOpenToAllGranted, isUiAccessGranted } from "../engine/types/handlers.js";
|
|
27
27
|
export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "../engine/types/screen.js";
|
|
28
28
|
export { ACTION_FORM_ENTITY, handlerFieldLabelKey, handlerTitleKey, PROJECTION_DETAIL_ENTITY, WRITE_FORM_SECTION_ENTITY, } from "../i18n/required-surface-keys.js";
|