@cosmicdrift/kumiko-framework 0.387.0 → 0.388.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.md +6 -1
- package/agents/examples/anonymous-access.md +1 -1
- package/agents/examples/apex-landing.md +204 -0
- package/agents/examples/api-worker-split.md +64 -0
- package/agents/examples/app-config.md +14 -0
- package/agents/examples/auth-mfa.md +115 -0
- package/agents/examples/env-composition.md +28 -1
- package/agents/examples/legal-pages.md +1 -1
- package/agents/examples/prod-boot-options.md +16 -0
- package/agents/examples/rate-limiting.md +48 -0
- package/agents/examples/record-detail-layout.md +692 -0
- package/agents/examples/seed-migration.md +212 -0
- package/agents/examples/wizard-form.md +941 -0
- package/agents/examples/writeform-section.md +548 -0
- package/agents/features.md +19 -2
- package/agents/widgets.md +1 -0
- package/dist/api/client-error-route.js +8 -2
- package/dist/api/index.d.ts +3 -1
- package/dist/api/index.js +3 -1
- package/dist/api/request-context.d.ts +1 -1
- package/dist/api/request-context.js +1 -1
- package/dist/api/server-boot-checks.d.ts +10 -0
- package/dist/api/server-boot-checks.js +157 -0
- package/dist/api/server-event-dispatcher.d.ts +26 -0
- package/dist/api/server-event-dispatcher.js +208 -0
- package/dist/api/server-extra-routes.d.ts +11 -0
- package/dist/api/server-extra-routes.js +203 -0
- package/dist/api/server-middleware.d.ts +20 -0
- package/dist/api/server-middleware.js +211 -0
- package/dist/api/server-routes.d.ts +19 -0
- package/dist/api/server-routes.js +293 -0
- package/dist/api/server-websocket-routes.d.ts +4 -0
- package/dist/api/server-websocket-routes.js +108 -0
- package/dist/api/server-wiring.d.ts +32 -0
- package/dist/api/server-wiring.js +236 -0
- package/dist/api/server.d.ts +10 -15
- package/dist/api/server.js +48 -1251
- package/dist/api/session-cookies.js +4 -4
- package/dist/api/sse-route.js +16 -12
- package/dist/crypto/owner-transition-rekey.d.ts +5 -1
- package/dist/crypto/owner-transition-rekey.js +1 -1
- package/dist/crypto/pii-field-encryption.d.ts +5 -1
- package/dist/crypto/pii-field-encryption.js +3 -1
- package/dist/db/aggregate-sql.d.ts +21 -0
- package/dist/db/aggregate-sql.js +155 -0
- package/dist/db/closed-connection.d.ts +9 -0
- package/dist/db/closed-connection.js +95 -0
- package/dist/db/dialect.js +1 -1
- package/dist/db/distinct-on.d.ts +1 -1
- package/dist/db/driver-row-coercion.d.ts +2 -0
- package/dist/db/driver-row-coercion.js +186 -0
- package/dist/db/driver-value-preparation.d.ts +16 -0
- package/dist/db/driver-value-preparation.js +99 -0
- package/dist/db/entity-field-encryption.d.ts +1 -0
- package/dist/db/entity-field-encryption.js +1 -1
- package/dist/db/entity-table-meta.js +103 -110
- package/dist/db/event-store-executor-context.d.ts +2 -1
- package/dist/db/event-store-executor-read-aggregate.d.ts +8 -0
- package/dist/db/event-store-executor-read-aggregate.js +112 -0
- package/dist/db/event-store-executor-read-filter.d.ts +31 -0
- package/dist/db/event-store-executor-read-filter.js +203 -0
- package/dist/db/event-store-executor-read-paging.d.ts +14 -0
- package/dist/db/event-store-executor-read-paging.js +99 -0
- package/dist/db/event-store-executor-read-rows.d.ts +6 -0
- package/dist/db/event-store-executor-read-rows.js +32 -0
- package/dist/db/event-store-executor-read-search.d.ts +28 -0
- package/dist/db/event-store-executor-read-search.js +221 -0
- package/dist/db/event-store-executor-read-sort.d.ts +41 -0
- package/dist/db/event-store-executor-read-sort.js +130 -0
- package/dist/db/event-store-executor-read-where.d.ts +24 -0
- package/dist/db/event-store-executor-read-where.js +81 -0
- package/dist/db/event-store-executor-read.d.ts +2 -9
- package/dist/db/event-store-executor-read.js +48 -701
- package/dist/db/event-store-executor-write-steps.d.ts +20 -0
- package/dist/db/event-store-executor-write-steps.js +100 -0
- package/dist/db/event-store-executor-write.js +395 -499
- package/dist/db/index.d.ts +6 -2
- package/dist/db/index.js +5 -1
- package/dist/db/list-aggregate-labels.d.ts +14 -0
- package/dist/db/list-aggregate-labels.js +26 -0
- package/dist/db/migrate-generator.js +71 -86
- package/dist/db/migrate-runner.js +4 -1
- package/dist/db/query.d.ts +2 -15
- package/dist/db/query.js +8 -761
- package/dist/db/reference-data.js +6 -1
- package/dist/db/table-builder.js +113 -166
- package/dist/db/table-info.d.ts +10 -0
- package/dist/db/table-info.js +45 -0
- package/dist/db/temporal-value-guards.d.ts +6 -0
- package/dist/db/temporal-value-guards.js +15 -0
- package/dist/db/tenant-db-runner.d.ts +1 -0
- package/dist/db/tenant-db-runner.js +3 -0
- package/dist/db/tenant-db.js +39 -9
- package/dist/db/where-clause.d.ts +6 -0
- package/dist/db/where-clause.js +236 -0
- package/dist/derivatives/delete-stored-file.js +2 -1
- package/dist/engine/boot-validator/boot-check.js +1 -1
- package/dist/engine/boot-validator/detail-screens.js +2 -2
- package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
- package/dist/engine/boot-validator/entity-handler.js +17 -0
- package/dist/engine/boot-validator/form-derivations.js +35 -1
- package/dist/engine/boot-validator/i18n-keys.js +5 -3
- package/dist/engine/boot-validator/index.js +2 -0
- package/dist/engine/boot-validator/pii-retention.js +233 -155
- package/dist/engine/boot-validator/screens-actions.d.ts +1 -0
- package/dist/engine/boot-validator/screens-actions.js +8 -1
- package/dist/engine/boot-validator/screens-dashboard.js +2 -5
- package/dist/engine/boot-validator/screens-forms.js +2 -6
- package/dist/engine/boot-validator/screens-kind-config-edit.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-config-edit.js +88 -0
- package/dist/engine/boot-validator/screens-kind-entity-edit.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-entity-edit.js +55 -0
- package/dist/engine/boot-validator/screens-kind-entity-list.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-entity-list.js +228 -0
- package/dist/engine/boot-validator/screens-kind-projection-detail.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-projection-detail.js +235 -0
- package/dist/engine/boot-validator/screens-kind-projection-list.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-projection-list.js +52 -0
- package/dist/engine/boot-validator/screens-kind-secret-mint.d.ts +3 -0
- package/dist/engine/boot-validator/screens-kind-secret-mint.js +28 -0
- package/dist/engine/boot-validator/screens-kind-shared.d.ts +37 -0
- package/dist/engine/boot-validator/screens-kind-shared.js +142 -0
- package/dist/engine/boot-validator/screens-lists.d.ts +2 -1
- package/dist/engine/boot-validator/screens-lists.js +12 -9
- package/dist/engine/boot-validator/screens.d.ts +0 -2
- package/dist/engine/boot-validator/screens.js +99 -860
- package/dist/engine/build-app-schema.js +56 -96
- package/dist/engine/define-feature.js +9 -2
- package/dist/engine/define-handler.js +1 -0
- package/dist/engine/entity-handlers.d.ts +13 -0
- package/dist/engine/entity-handlers.js +10 -21
- package/dist/engine/entity-list-runtime-options.d.ts +8 -0
- package/dist/engine/entity-list-runtime-options.js +18 -0
- package/dist/engine/feature-ast/extractors/handlers.js +1 -0
- package/dist/engine/feature-ast/extractors/round2.js +25 -6
- package/dist/engine/feature-ast/patch.js +4 -182
- package/dist/engine/feature-ast/pattern-change-schema.d.ts +3 -0
- package/dist/engine/feature-ast/pattern-change-schema.js +1 -0
- package/dist/engine/feature-ast/pattern-id-matchers.d.ts +7 -0
- package/dist/engine/feature-ast/pattern-id-matchers.js +210 -0
- package/dist/engine/feature-ast/patterns.d.ts +9 -0
- package/dist/engine/feature-ast/patterns.js +43 -51
- package/dist/engine/feature-ast/render.js +48 -87
- package/dist/engine/feature-entity-handlers.js +3 -0
- package/dist/engine/form-derivations.js +18 -7
- package/dist/engine/handler-field-references.js +1 -1
- package/dist/engine/project-app-schema-for-roles.d.ts +2 -0
- package/dist/engine/project-app-schema-for-roles.js +6 -4
- package/dist/engine/registry-ingest.js +1 -1
- package/dist/engine/schema-builder.js +228 -212
- package/dist/engine/screen-helpers.js +1 -1
- package/dist/engine/screen-kind.d.ts +7 -0
- package/dist/engine/screen-kind.js +1 -0
- package/dist/engine/strip-disabled-features-from-app-schema.js +51 -14
- package/dist/engine/validate-projection-allowlist.js +2 -1
- package/dist/entrypoint/index.d.ts +4 -1
- package/dist/entrypoint/index.js +7 -1
- package/dist/errors/docs-url.d.ts +1 -1
- package/dist/errors/docs-url.js +22 -2
- package/dist/es-ops/context.js +1 -1
- package/dist/event-store/admin-api.js +1 -1
- package/dist/event-store/event-store.js +10 -4
- package/dist/files/bind-pending-file-refs.js +3 -3
- package/dist/files/create-file-ref-from-bytes.js +4 -1
- package/dist/files/file-handle.js +2 -3
- package/dist/files/file-routes.js +11 -8
- package/dist/files/resolve-content-type.js +43 -0
- package/dist/files/upload-ledger.js +7 -0
- package/dist/i18n/required-surface-keys.js +182 -206
- package/dist/internal/stack-support.d.ts +2 -1
- package/dist/internal/stack-support.js +1 -1
- package/dist/jobs/job-runner-context.d.ts +32 -0
- package/dist/jobs/job-runner-context.js +305 -0
- package/dist/jobs/job-runner-execution.d.ts +7 -0
- package/dist/jobs/job-runner-execution.js +160 -0
- package/dist/jobs/job-runner-gates.d.ts +19 -0
- package/dist/jobs/job-runner-gates.js +198 -0
- package/dist/jobs/job-runner-helpers.d.ts +17 -3
- package/dist/jobs/job-runner-helpers.js +61 -2
- package/dist/jobs/job-runner-scope.d.ts +44 -0
- package/dist/jobs/job-runner-scope.js +102 -0
- package/dist/jobs/job-runner-types.d.ts +12 -0
- package/dist/jobs/job-runner.d.ts +0 -1
- package/dist/jobs/job-runner.js +47 -657
- package/dist/observability/escape-hatch-report.d.ts +3 -0
- package/dist/observability/escape-hatch-report.js +12 -1
- package/dist/pipeline/cascade-handler.js +165 -138
- package/dist/pipeline/dispatch-query.js +10 -3
- package/dist/pipeline/dispatch-shared.d.ts +13 -3
- package/dist/pipeline/dispatch-shared.js +58 -710
- package/dist/pipeline/dispatch-write.js +296 -189
- package/dist/pipeline/event-dispatcher-admin.js +2 -1
- package/dist/pipeline/event-dispatcher-delivery.js +3 -5
- package/dist/pipeline/handler-context-auth.d.ts +9 -0
- package/dist/pipeline/handler-context-auth.js +54 -0
- package/dist/pipeline/handler-context-config.d.ts +14 -0
- package/dist/pipeline/handler-context-config.js +113 -0
- package/dist/pipeline/handler-context-db.d.ts +24 -0
- package/dist/pipeline/handler-context-db.js +149 -0
- package/dist/pipeline/handler-context-dispatch.d.ts +21 -0
- package/dist/pipeline/handler-context-dispatch.js +89 -0
- package/dist/pipeline/handler-context-events.d.ts +43 -0
- package/dist/pipeline/handler-context-events.js +237 -0
- package/dist/pipeline/handler-context-notify.d.ts +12 -0
- package/dist/pipeline/handler-context-notify.js +55 -0
- package/dist/pipeline/handler-context-observability.d.ts +7 -0
- package/dist/pipeline/handler-context-observability.js +21 -0
- package/dist/pipeline/handler-context-scope.d.ts +48 -0
- package/dist/pipeline/handler-context-scope.js +88 -0
- package/dist/pipeline/notify-scope.js +12 -1
- package/dist/pipeline/pre-handler-gates.js +20 -4
- package/dist/pipeline/pre-transaction-reservations.d.ts +1 -0
- package/dist/pipeline/pre-transaction-reservations.js +9 -3
- package/dist/pipeline/projection-rebuild.js +2 -1
- package/dist/schema-cli/apply-command.d.ts +2 -0
- package/dist/schema-cli/apply-command.js +97 -0
- package/dist/schema-cli/baseline-command.d.ts +2 -0
- package/dist/schema-cli/baseline-command.js +33 -0
- package/dist/schema-cli/generate-command.d.ts +2 -0
- package/dist/schema-cli/generate-command.js +90 -0
- package/dist/schema-cli/shared.d.ts +39 -0
- package/dist/schema-cli/shared.js +95 -0
- package/dist/schema-cli/status-command.d.ts +2 -0
- package/dist/schema-cli/status-command.js +73 -0
- package/dist/schema-cli/validate-boot-input.d.ts +5 -0
- package/dist/schema-cli/validate-boot-input.js +130 -0
- package/dist/schema-cli/validate-command.d.ts +2 -0
- package/dist/schema-cli/validate-command.js +130 -0
- package/dist/schema-cli.d.ts +2 -18
- package/dist/schema-cli.js +47 -604
- package/dist/search/meilisearch-adapter.js +2 -1
- package/dist/upgrade-cli.js +149 -121
- package/package.json +4 -4
- package/src/changes.json +310 -0
- package/src/scripts/codemod/feature-layout.ts +10 -2
- package/src/scripts/codemod/object-form.ts +41 -4
- package/src/scripts/codemod/package-merge.ts +58 -8
- package/src/scripts/codemod/pii-personal-migration.ts +175 -145
- package/src/scripts/codemod/testing-injection.ts +122 -42
- package/src/scripts/codemod/testing-move.ts +29 -7
|
@@ -111,7 +111,7 @@ export function buildAppSchema(registry, options = {}) {
|
|
|
111
111
|
// instead so a leaked function renderer is actually caught.
|
|
112
112
|
const offender = findNonJsonSafePath(schema, "schema");
|
|
113
113
|
if (offender !== null) {
|
|
114
|
-
log.error(`buildAppSchema:
|
|
114
|
+
log.error(`buildAppSchema: output is not JSON-safe, non-serializable value at "${offender}" (a function renderer or class instance in the schema?).`, { offender });
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
return schema;
|
|
@@ -446,136 +446,96 @@ function assertValidScreenMultilineRows(featureName, shortId, screen) {
|
|
|
446
446
|
check(screen.confirm.fields);
|
|
447
447
|
}
|
|
448
448
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
//
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
// cast bridges the variant inference TS can't recompute from a generic
|
|
462
|
-
// Record.
|
|
463
|
-
// kumiko-lint-ignore complexity-budget flat per-property whitelist walk, one independent `if` per FieldDefinition property — same shape as schema-builder.ts's field→zod switch
|
|
464
|
-
function projectField(fieldDef) {
|
|
465
|
-
const def = fieldDef; // @cast-boundary schema-walk
|
|
466
|
-
const out = {};
|
|
467
|
-
if (typeof def["type"] === "string")
|
|
468
|
-
out["type"] = def["type"];
|
|
469
|
-
if (typeof def["required"] === "boolean")
|
|
470
|
-
out["required"] = def["required"];
|
|
471
|
-
if (typeof def["sortable"] === "boolean")
|
|
472
|
-
out["sortable"] = def["sortable"];
|
|
449
|
+
const isStringValue = (value) => typeof value === "string";
|
|
450
|
+
const isBooleanValue = (value) => typeof value === "boolean";
|
|
451
|
+
const isNumberValue = (value) => typeof value === "number";
|
|
452
|
+
const isTrueValue = (value) => value === true;
|
|
453
|
+
const isNumberOrStringValue = (value) => typeof value === "number" || typeof value === "string";
|
|
454
|
+
const isBooleanOrJsonSafeObject = (value) => typeof value === "boolean" || isJsonSafeObject(value);
|
|
455
|
+
const isUnitSpec = (value) => typeof value === "string" || (isPlainObject(value) && typeof value["field"] === "string");
|
|
456
|
+
// Forwarded in this order, so the client schema keeps its key order.
|
|
457
|
+
const FORWARDED_FIELD_PROPERTIES = [
|
|
458
|
+
["type", isStringValue],
|
|
459
|
+
["required", isBooleanValue],
|
|
460
|
+
["sortable", isBooleanValue],
|
|
473
461
|
// filterable controls the faceted filter dropdowns in the renderer (select/
|
|
474
462
|
// boolean), so it must be included in the client schema.
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
out["searchable"] = def["searchable"];
|
|
479
|
-
if (isJsonSafeValue(def["default"]))
|
|
480
|
-
out["default"] = def["default"];
|
|
463
|
+
["filterable", isBooleanValue],
|
|
464
|
+
["searchable", isBooleanValue],
|
|
465
|
+
["default", isJsonSafeValue],
|
|
481
466
|
// Select: the options list is plain JSON, so forward it.
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
if (isJsonSafeObject(def["optionTones"]))
|
|
485
|
-
out["optionTones"] = def["optionTones"];
|
|
467
|
+
["options", Array.isArray],
|
|
468
|
+
["optionTones", isJsonSafeObject],
|
|
486
469
|
// Reference: entity target, labelField, and multiple are required by the
|
|
487
470
|
// renderer. ReferenceInput builds the options query from refEntity/refFeature
|
|
488
471
|
// and resolves the label through labelField; without these properties, the
|
|
489
472
|
// dropdown is empty (the QN becomes `<feature>:query::list` → 404).
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
if (typeof def["labelField"] === "string")
|
|
493
|
-
out["labelField"] = def["labelField"];
|
|
473
|
+
["entity", isStringValue],
|
|
474
|
+
["labelField", isStringValue],
|
|
494
475
|
// Author-supplied picker source — without it the client falls back to the list handler.
|
|
495
|
-
|
|
496
|
-
out["optionsQuery"] = def["optionsQuery"];
|
|
476
|
+
["optionsQuery", isStringValue],
|
|
497
477
|
// Select: tier/tenant availability of the static options, loaded by the renderer.
|
|
498
|
-
|
|
499
|
-
out["optionsAvailabilityQuery"] = def["optionsAvailabilityQuery"];
|
|
478
|
+
["optionsAvailabilityQuery", isStringValue],
|
|
500
479
|
// Select: options only offered while a sibling-field condition holds; the renderer filters, the write path enforces.
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
if (typeof def["multiple"] === "boolean")
|
|
504
|
-
out["multiple"] = def["multiple"];
|
|
480
|
+
["conditionalOptions", Array.isArray],
|
|
481
|
+
["multiple", isBooleanValue],
|
|
505
482
|
// MultiSelect: display picks checkboxes vs. combobox in the renderer,
|
|
506
483
|
// columns/maxRows size the checkbox grid — without these the renderer
|
|
507
484
|
// always falls back to the combobox. Select: display picks
|
|
508
485
|
// "radio" | "dropdown" the same way.
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
out["columns"] = def["columns"];
|
|
513
|
-
if (typeof def["maxRows"] === "number")
|
|
514
|
-
out["maxRows"] = def["maxRows"];
|
|
486
|
+
["display", isStringValue],
|
|
487
|
+
["columns", isNumberValue],
|
|
488
|
+
["maxRows", isNumberValue],
|
|
515
489
|
// text/longText: textarea row count — DefaultInput renders a single-line
|
|
516
490
|
// input otherwise.
|
|
517
|
-
|
|
518
|
-
out["multiline"] = def["multiline"];
|
|
491
|
+
["multiline", isBooleanOrJsonSafeObject],
|
|
519
492
|
// number: Zod write-boundary bounds. date/timestamp/locatedTimestamp:
|
|
520
493
|
// ISO-string picker bounds — same keys, different literal type.
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
if (typeof def["max"] === "number" || typeof def["max"] === "string")
|
|
524
|
-
out["max"] = def["max"];
|
|
494
|
+
["min", isNumberOrStringValue],
|
|
495
|
+
["max", isNumberOrStringValue],
|
|
525
496
|
// date/timestamp/locatedTimestamp: display/parsing locale override.
|
|
526
|
-
|
|
527
|
-
out["locale"] = def["locale"];
|
|
497
|
+
["locale", isStringValue],
|
|
528
498
|
// image/images: which camera a mobile capture opens.
|
|
529
|
-
|
|
530
|
-
out["capture"] = def["capture"];
|
|
499
|
+
["capture", isStringValue],
|
|
531
500
|
// number: display-only suffix, static or read from a sibling field of the row.
|
|
532
|
-
|
|
533
|
-
if (typeof unit === "string" || (isPlainObject(unit) && typeof unit["field"] === "string"))
|
|
534
|
-
out["unit"] = unit;
|
|
501
|
+
["unit", isUnitSpec],
|
|
535
502
|
// number/decimal/bigInt: `false` drops thousands separators (e.g. a year).
|
|
536
|
-
|
|
537
|
-
out["grouping"] = def["grouping"];
|
|
503
|
+
["grouping", isBooleanValue],
|
|
538
504
|
// text: "password" masks the input and blocks URL prefill in the renderer.
|
|
539
|
-
|
|
540
|
-
out["format"] = def["format"];
|
|
505
|
+
["format", isStringValue],
|
|
541
506
|
// Write-response redaction stays server-side; the renderer needs the flag to
|
|
542
507
|
// refuse any prefill (URL or handoff) into the field.
|
|
543
|
-
|
|
544
|
-
out["sensitive"] = true;
|
|
508
|
+
["sensitive", isTrueValue],
|
|
545
509
|
// Without it the renderer shows a plain text input for a field whose value never leaves the server.
|
|
546
|
-
|
|
547
|
-
out["writeOnly"] = true;
|
|
510
|
+
["writeOnly", isTrueValue],
|
|
548
511
|
// timestamp: the edit view-model derives its wall-clock input mode from this.
|
|
549
|
-
|
|
550
|
-
out["locatedBy"] = def["locatedBy"];
|
|
512
|
+
["locatedBy", isStringValue],
|
|
551
513
|
// file/image/images: upload picker constraints; the first variant key picks
|
|
552
514
|
// the preview variant.
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
out["maxSize"] = def["maxSize"];
|
|
557
|
-
if (isJsonSafeObject(def["variants"]))
|
|
558
|
-
out["variants"] = def["variants"];
|
|
515
|
+
["accept", isJsonSafeArray],
|
|
516
|
+
["maxSize", isStringValue],
|
|
517
|
+
["variants", isJsonSafeObject],
|
|
559
518
|
// decimal, incl. embedded sub-fields: rounding of derived embedded-list cells.
|
|
560
|
-
|
|
561
|
-
out["scale"] = def["scale"];
|
|
519
|
+
["scale", isNumberValue],
|
|
562
520
|
// money: declared currency source (MoneyCurrencySource) — without
|
|
563
521
|
// it the renderer can't tell a "currency: { kind: 'tenant' }" field apart
|
|
564
522
|
// from an undeclared one and always falls back to entity.defaultCurrency.
|
|
565
|
-
|
|
566
|
-
out["currency"] = def["currency"];
|
|
523
|
+
["currency", isJsonSafeObject],
|
|
567
524
|
// embedded lists: row-count bounds, computed cells, totals row, and the
|
|
568
525
|
// sibling-money-field totals check.
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
526
|
+
["minItems", isNumberValue],
|
|
527
|
+
["maxItems", isNumberValue],
|
|
528
|
+
["derived", isJsonSafeObject],
|
|
529
|
+
["totals", isJsonSafeArray],
|
|
530
|
+
["totalsMatch", isJsonSafeObject],
|
|
531
|
+
];
|
|
532
|
+
function projectField(fieldDef) {
|
|
533
|
+
const def = fieldDef; // @cast-boundary schema-walk
|
|
534
|
+
const out = {};
|
|
535
|
+
for (const [key, isForwarded] of FORWARDED_FIELD_PROPERTIES) {
|
|
536
|
+
if (isForwarded(def[key]))
|
|
537
|
+
out[key] = def[key];
|
|
538
|
+
}
|
|
579
539
|
// embedded lists: the sub-field map itself — without it the client-side
|
|
580
540
|
// edit view-model iterates `undefined` and the screen crashes.
|
|
581
541
|
// Recursive per sub-field, NOT a blanket isJsonSafeValue gate: a single
|
|
@@ -48,9 +48,16 @@ export function defineFeature(name, setup, settings) {
|
|
|
48
48
|
},
|
|
49
49
|
requires: (() => {
|
|
50
50
|
const fn = (...args) => {
|
|
51
|
-
|
|
51
|
+
const [first, ...rest] = args;
|
|
52
|
+
if (rest.length > 0 || !isObjectArgument(first)) {
|
|
52
53
|
throwObjectArgumentRequired(name, "requires");
|
|
53
|
-
|
|
54
|
+
}
|
|
55
|
+
// Untyped callers can pass `{ feature: [...] }`; without this the object would be
|
|
56
|
+
// registered as a dependency name.
|
|
57
|
+
if (!Array.isArray(first.features) || !first.features.every((f) => typeof f === "string")) {
|
|
58
|
+
throw new Error(`[Feature ${name}] r.requires() needs { features: string[] }`);
|
|
59
|
+
}
|
|
60
|
+
state.requires.push(...first.features);
|
|
54
61
|
};
|
|
55
62
|
return Object.assign(fn, {
|
|
56
63
|
projection: (tableName) => {
|
|
@@ -57,6 +57,7 @@ export function defineWriteHandler(def,
|
|
|
57
57
|
...(def.unsafeSkipTransitionGuard && { unsafeSkipTransitionGuard: true }),
|
|
58
58
|
...(def.rateLimit && { rateLimit: def.rateLimit }),
|
|
59
59
|
...(def.additionalRateLimits && { additionalRateLimits: def.additionalRateLimits }),
|
|
60
|
+
...(def.rateLimitCost && { rateLimitCost: def.rateLimitCost }),
|
|
60
61
|
...(def.escapeHatch && { escapeHatch: def.escapeHatch }),
|
|
61
62
|
...pickPreTransactionPhases(def),
|
|
62
63
|
...(def.tenantlessAnonymous && { tenantlessAnonymous: true }),
|
|
@@ -44,6 +44,19 @@ export declare const entityListSchema: z.ZodObject<{
|
|
|
44
44
|
}>;
|
|
45
45
|
value: z.ZodUnknown;
|
|
46
46
|
}, z.core.$strip>>>;
|
|
47
|
+
aggregate: z.ZodOptional<z.ZodObject<{
|
|
48
|
+
groupBy: z.ZodOptional<z.ZodString>;
|
|
49
|
+
measures: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
50
|
+
field: z.ZodString;
|
|
51
|
+
kind: z.ZodEnum<{
|
|
52
|
+
avg: "avg";
|
|
53
|
+
count: "count";
|
|
54
|
+
max: "max";
|
|
55
|
+
min: "min";
|
|
56
|
+
sum: "sum";
|
|
57
|
+
}>;
|
|
58
|
+
}, z.core.$strip>>>;
|
|
59
|
+
}, z.core.$strip>>;
|
|
47
60
|
}, z.core.$strip>;
|
|
48
61
|
export declare function defineEntityWriteHandler(name: string, entity: EntityDefinition, options: EntityWriteHandlerOptions): WriteHandlerDef;
|
|
49
62
|
export declare function defineEntityQueryHandler(name: string, entity: EntityDefinition, options: EntityQueryHandlerOptions): QueryHandlerDef;
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { ENTITY_CONVENTION_QUERY_BRAND } from "@cosmicdrift/kumiko-types/handlers";
|
|
2
|
+
import { listAggregatePayloadSchema, } from "@cosmicdrift/kumiko-types/list-aggregate-types";
|
|
2
3
|
import * as z from "zod";
|
|
3
4
|
import { collectReferenceFields, enrichRowWithReferences, enrichWithReferences, } from "../db/eagerload.js";
|
|
4
5
|
import { createEventStoreExecutor } from "../db/event-store-executor.js";
|
|
6
|
+
import { answerListAggregate } from "../db/list-aggregate-labels.js";
|
|
5
7
|
import { buildEntityTable } from "../db/table-builder.js";
|
|
6
8
|
import { acknowledgeConventionCrossTenant, reportConventionCrossTenantUse, } from "../db/tenant-db.js";
|
|
7
9
|
import { InternalError } from "../errors/index.js";
|
|
8
10
|
import { isSystemIdentity } from "../pipeline/system-identity-switch.js";
|
|
9
11
|
import { assertUnreachable } from "../utils/index.js";
|
|
10
12
|
import { PAGED_QUERY_HANDLER_BRAND } from "./define-handler.js";
|
|
13
|
+
import { entityListRuntime } from "./entity-list-runtime-options.js";
|
|
11
14
|
import { assertReason } from "./escape-hatch-reason.js";
|
|
12
15
|
import { dropEmptyWriteOnlyValues } from "./field-access.js";
|
|
13
16
|
import { instructionFieldNames } from "./instruction-fields.js";
|
|
@@ -105,6 +108,7 @@ export const entityListSchema = z.object({
|
|
|
105
108
|
value: z.unknown(),
|
|
106
109
|
}))
|
|
107
110
|
.optional(),
|
|
111
|
+
aggregate: listAggregatePayloadSchema.optional(),
|
|
108
112
|
});
|
|
109
113
|
function parseHandlerName(name, validVerbs) {
|
|
110
114
|
const colonIdx = name.indexOf(":");
|
|
@@ -390,28 +394,13 @@ export function defineEntityQueryHandler(name, entity, options) {
|
|
|
390
394
|
// runtime override supplies it.
|
|
391
395
|
const listPayload = query.payload; // @cast-boundary engine-payload
|
|
392
396
|
const db = dbFor(ctx);
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
...
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
// same runtime-override extension point as searchAdapter above.
|
|
399
|
-
referenceSearch: {
|
|
400
|
-
fields: ctx.registry.getSearchableReferences(entityName),
|
|
401
|
-
resolveEntity: (name) => ctx.registry.getEntity(name),
|
|
402
|
-
},
|
|
403
|
-
referenceSort: {
|
|
404
|
-
fields: ctx.registry.getSortableReferences(entityName),
|
|
405
|
-
resolveEntity: (name) => ctx.registry.getEntity(name),
|
|
406
|
-
},
|
|
407
|
-
parentVisibility: { entities: ctx.registry.getAllEntities() },
|
|
408
|
-
});
|
|
409
|
-
const eagerLoadViewer = {
|
|
410
|
-
user: query.user,
|
|
411
|
-
parentVisibility: { entities: ctx.registry.getAllEntities() },
|
|
412
|
-
};
|
|
397
|
+
const { runtimeOptions, eagerLoadViewer, resolveEntity } = entityListRuntime(ctx, entityName, query.user);
|
|
398
|
+
if (listPayload.aggregate !== undefined) {
|
|
399
|
+
return answerListAggregate(executor, { ...listPayload, aggregate: listPayload.aggregate }, query.user, db, runtimeOptions, { entity, resolveEntity, viewer: eagerLoadViewer });
|
|
400
|
+
}
|
|
401
|
+
const result = await executor.list(listPayload, query.user, db, runtimeOptions);
|
|
413
402
|
const enrichedRows = hasRefFields
|
|
414
|
-
? await enrichWithReferences(result.rows, entity,
|
|
403
|
+
? await enrichWithReferences(result.rows, entity, resolveEntity, db, eagerLoadViewer)
|
|
415
404
|
: result.rows;
|
|
416
405
|
return { ...result, rows: augmentDerivedFields(enrichedRows, entity) };
|
|
417
406
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ListRuntimeOptions } from "@cosmicdrift/kumiko-types/event-store-executor-types";
|
|
2
|
+
import type { EagerLoadEntityResolver, EagerLoadViewer } from "../db/eagerload.ts";
|
|
3
|
+
import type { HandlerContext, SessionUser } from "./types/index.ts";
|
|
4
|
+
export declare function entityListRuntime(ctx: HandlerContext, entityName: string, user: SessionUser): {
|
|
5
|
+
readonly runtimeOptions: ListRuntimeOptions;
|
|
6
|
+
readonly eagerLoadViewer: EagerLoadViewer;
|
|
7
|
+
readonly resolveEntity: EagerLoadEntityResolver;
|
|
8
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Registry access only exists at request time, so the searchable/sortable-reference
|
|
2
|
+
// caches, the entity resolver and the join-row read gate travel through the
|
|
3
|
+
// runtime-override extension point of the executor verbs.
|
|
4
|
+
export function entityListRuntime(ctx, entityName, user) {
|
|
5
|
+
const parentVisibility = { entities: ctx.registry.getAllEntities() };
|
|
6
|
+
const resolveEntity = (name) => ctx.registry.getEntity(name);
|
|
7
|
+
return {
|
|
8
|
+
runtimeOptions: {
|
|
9
|
+
...(ctx.searchAdapter !== undefined && { searchAdapter: ctx.searchAdapter }),
|
|
10
|
+
...(ctx.includeDeleted === true && { includeDeleted: true }),
|
|
11
|
+
referenceSearch: { fields: ctx.registry.getSearchableReferences(entityName), resolveEntity },
|
|
12
|
+
referenceSort: { fields: ctx.registry.getSortableReferences(entityName), resolveEntity },
|
|
13
|
+
parentVisibility,
|
|
14
|
+
},
|
|
15
|
+
eagerLoadViewer: { user, parentVisibility },
|
|
16
|
+
resolveEntity,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -33,6 +33,7 @@ const WRITE_HANDLER_KEY_KINDS = {
|
|
|
33
33
|
unsafeSkipTransitionGuard: "modeled",
|
|
34
34
|
rateLimit: "modeled",
|
|
35
35
|
additionalRateLimits: "modeled",
|
|
36
|
+
rateLimitCost: "opaque",
|
|
36
37
|
escapeHatch: "modeled",
|
|
37
38
|
tenantlessAnonymous: "opaque",
|
|
38
39
|
reserveBeforeTransaction: "opaque",
|
|
@@ -2,19 +2,36 @@ import { SyntaxKind } from "ts-morph";
|
|
|
2
2
|
import { describeUnknownFieldType, findUnknownEntityFieldTypes } from "../entity-field-types.js";
|
|
3
3
|
import { sourceLocationFromNode } from "../source-location.js";
|
|
4
4
|
import { fail, isPlainObject, isRawRefSentinel, ok, readDataLiteralNode, readNameLiteral, readNameOrRef, } from "./shared.js";
|
|
5
|
+
function readBackingTableRef(property) {
|
|
6
|
+
const initializer = property.asKind(SyntaxKind.PropertyAssignment)?.getInitializer() ??
|
|
7
|
+
property.asKind(SyntaxKind.ShorthandPropertyAssignment)?.getNameNode();
|
|
8
|
+
const reference = initializer ? readDataLiteralNode(initializer) : undefined;
|
|
9
|
+
return isRawRefSentinel(reference) ? reference : undefined;
|
|
10
|
+
}
|
|
5
11
|
// `{ name: "x", ...xEntity }` — the definition is a reference the parser cannot
|
|
6
12
|
// inline, so it is kept as a raw-ref sentinel and re-rendered as the same spread.
|
|
7
13
|
// `backingTable` is runtime wiring (a table identifier), not part of the
|
|
8
|
-
// definition data, so it
|
|
14
|
+
// definition data, so it is carried next to the definition and skipped by the shape check.
|
|
9
15
|
function readSpreadOnlyDefinition(obj) {
|
|
10
|
-
const
|
|
11
|
-
.
|
|
12
|
-
|
|
16
|
+
const isBackingTable = (property) => {
|
|
17
|
+
const name = property.asKind(SyntaxKind.PropertyAssignment)?.getName() ??
|
|
18
|
+
property.asKind(SyntaxKind.ShorthandPropertyAssignment)?.getName();
|
|
19
|
+
return name === "backingTable";
|
|
20
|
+
};
|
|
21
|
+
const allProperties = obj.getProperties();
|
|
22
|
+
const properties = allProperties.filter((property) => !isBackingTable(property));
|
|
13
23
|
const spread = properties.length === 2 ? properties[1]?.asKind(SyntaxKind.SpreadAssignment) : undefined;
|
|
14
24
|
if (!spread)
|
|
15
25
|
return undefined;
|
|
16
26
|
const reference = readDataLiteralNode(spread.getExpression());
|
|
17
|
-
|
|
27
|
+
if (!isRawRefSentinel(reference))
|
|
28
|
+
return undefined;
|
|
29
|
+
const backingTableProperty = allProperties.find(isBackingTable);
|
|
30
|
+
const backingTable = backingTableProperty ? readBackingTableRef(backingTableProperty) : undefined;
|
|
31
|
+
// A backingTable that is not a plain reference cannot be re-rendered; fall back to the data reader.
|
|
32
|
+
if (backingTableProperty && !backingTable)
|
|
33
|
+
return undefined;
|
|
34
|
+
return { definition: reference, backingTable };
|
|
18
35
|
}
|
|
19
36
|
export function extractEntity(call, sourceFile) {
|
|
20
37
|
const args = call.getArguments();
|
|
@@ -32,7 +49,8 @@ export function extractEntity(call, sourceFile) {
|
|
|
32
49
|
if (!nameInit) {
|
|
33
50
|
return fail("entity", sourceLocationFromNode(call, sourceFile), "object form requires a string-literal `name` property");
|
|
34
51
|
}
|
|
35
|
-
const
|
|
52
|
+
const spreadOnly = readSpreadOnlyDefinition(obj);
|
|
53
|
+
const definition = spreadOnly?.definition ?? readDataLiteralNode(obj);
|
|
36
54
|
if (!isPlainObject(definition)) {
|
|
37
55
|
return fail("entity", sourceLocationFromNode(call, sourceFile), "definition could not be read as a plain object (contains functions or identifiers)");
|
|
38
56
|
}
|
|
@@ -49,6 +67,7 @@ export function extractEntity(call, sourceFile) {
|
|
|
49
67
|
// Field-type membership is checked above; the cast is the extractor's
|
|
50
68
|
// existing narrowing contract (readDataLiteralNode returns unknown).
|
|
51
69
|
definition: defWithoutName,
|
|
70
|
+
...(spreadOnly?.backingTable && { backingTable: spreadOnly.backingTable }),
|
|
52
71
|
});
|
|
53
72
|
}
|
|
54
73
|
const entityName = readNameLiteral(first);
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
// roadmap C-Notes for the canonical-comment-attach Pattern that would
|
|
34
34
|
// preserve prefixed `// kumiko-comment:` markers across roundtrips.
|
|
35
35
|
import { SyntaxKind, } from "ts-morph";
|
|
36
|
-
import {
|
|
36
|
+
import { readPropertyKey } from "./extractors/shared.js";
|
|
37
|
+
import { callMatchesId } from "./pattern-id-matchers.js";
|
|
37
38
|
import { indent, PATTERN_INDENT, renderPattern, renderValue } from "./render.js";
|
|
38
39
|
/**
|
|
39
40
|
* Apply a sequence of changes to the source file in-place. The list is
|
|
@@ -523,7 +524,7 @@ function findCallForId(sourceFile, id) {
|
|
|
523
524
|
continue;
|
|
524
525
|
if (propAccess.getName() !== id.kind)
|
|
525
526
|
continue;
|
|
526
|
-
if (callMatchesId(call, id))
|
|
527
|
+
if (callMatchesId(call, id.kind, id))
|
|
527
528
|
matches.push(call);
|
|
528
529
|
}
|
|
529
530
|
if (SINGLETON_KINDS.has(id.kind) && matches.length > 1) {
|
|
@@ -536,28 +537,6 @@ const AI_STEP_FACTORY = {
|
|
|
536
537
|
"ai.extract": "aiExtractStep",
|
|
537
538
|
"ai.classify": "aiClassifyStep",
|
|
538
539
|
};
|
|
539
|
-
function readAiStepKey(call) {
|
|
540
|
-
const arg = call.getArguments()[0];
|
|
541
|
-
if (!arg)
|
|
542
|
-
return undefined;
|
|
543
|
-
const obj = resolveSameFileObjectLiteral(arg);
|
|
544
|
-
if (!obj)
|
|
545
|
-
return undefined;
|
|
546
|
-
const prop = obj.getProperty("stepKey");
|
|
547
|
-
if (!prop)
|
|
548
|
-
return undefined;
|
|
549
|
-
const assign = prop.asKind(SyntaxKind.PropertyAssignment);
|
|
550
|
-
if (assign) {
|
|
551
|
-
const init = assign.getInitializer();
|
|
552
|
-
if (!init)
|
|
553
|
-
return undefined;
|
|
554
|
-
return readNameLiteral(init);
|
|
555
|
-
}
|
|
556
|
-
const shorthand = prop.asKind(SyntaxKind.ShorthandPropertyAssignment);
|
|
557
|
-
if (shorthand)
|
|
558
|
-
return readNameLiteral(shorthand.getNameNode());
|
|
559
|
-
return undefined;
|
|
560
|
-
}
|
|
561
540
|
function findAiStepCall(sourceFile, id) {
|
|
562
541
|
if (!isAiStepId(id))
|
|
563
542
|
return undefined;
|
|
@@ -566,7 +545,7 @@ function findAiStepCall(sourceFile, id) {
|
|
|
566
545
|
for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
567
546
|
if (call.getExpression().getText() !== factory)
|
|
568
547
|
continue;
|
|
569
|
-
if (callMatchesId(call, id))
|
|
548
|
+
if (callMatchesId(call, id.kind, id))
|
|
570
549
|
matches.push(call);
|
|
571
550
|
}
|
|
572
551
|
if (matches.length > 1) {
|
|
@@ -574,163 +553,6 @@ function findAiStepCall(sourceFile, id) {
|
|
|
574
553
|
}
|
|
575
554
|
return matches[0];
|
|
576
555
|
}
|
|
577
|
-
function callMatchesId(call, id) {
|
|
578
|
-
switch (id.kind) {
|
|
579
|
-
// Singletons: kind alone identifies the call.
|
|
580
|
-
case "requires":
|
|
581
|
-
case "optionalRequires":
|
|
582
|
-
case "readsConfig":
|
|
583
|
-
case "systemScope":
|
|
584
|
-
case "toggleable":
|
|
585
|
-
case "describe":
|
|
586
|
-
case "uiHints":
|
|
587
|
-
case "config":
|
|
588
|
-
case "translations":
|
|
589
|
-
case "authClaims":
|
|
590
|
-
case "treeActions":
|
|
591
|
-
return true;
|
|
592
|
-
case "entity":
|
|
593
|
-
return (matchFirstArgString(call, id.entityName) || matchObjectProperty(call, "name", id.entityName));
|
|
594
|
-
case "relation":
|
|
595
|
-
// Positional: r.relation(entity, name, def) | Object: { entity, name, ... }
|
|
596
|
-
if (matchFirstArgString(call, id.entityName)) {
|
|
597
|
-
return matchArgString(call, 1, id.relationName);
|
|
598
|
-
}
|
|
599
|
-
return (matchObjectProperty(call, "entity", id.entityName) &&
|
|
600
|
-
matchObjectProperty(call, "name", id.relationName));
|
|
601
|
-
case "nav":
|
|
602
|
-
case "workspace":
|
|
603
|
-
case "screen":
|
|
604
|
-
return matchObjectProperty(call, "id", id.id);
|
|
605
|
-
case "writeHandler":
|
|
606
|
-
case "queryHandler":
|
|
607
|
-
case "streamHandler":
|
|
608
|
-
return (matchFirstArgString(call, id.handlerName) ||
|
|
609
|
-
matchObjectProperty(call, "name", id.handlerName));
|
|
610
|
-
case "hook":
|
|
611
|
-
// Positional: r.hook(type, target, fn) | Object: { type, target }
|
|
612
|
-
if (typeof id.target === "object") {
|
|
613
|
-
// Entity-wide { allOf } target — only ever appears as the object
|
|
614
|
-
// literal { allOf: entity } (positional or nested in the object
|
|
615
|
-
// form), never as a bare string, so check both call shapes.
|
|
616
|
-
if (matchFirstArgString(call, id.hookType)) {
|
|
617
|
-
return matchAllOfArg(call.getArguments()[1], id.target.allOf);
|
|
618
|
-
}
|
|
619
|
-
return (matchObjectProperty(call, "type", id.hookType) &&
|
|
620
|
-
matchObjectAllOfProperty(call, "target", id.target.allOf));
|
|
621
|
-
}
|
|
622
|
-
if (matchFirstArgString(call, id.hookType)) {
|
|
623
|
-
return matchArgNameOrRef(call, 1, id.target);
|
|
624
|
-
}
|
|
625
|
-
return (matchObjectProperty(call, "type", id.hookType) &&
|
|
626
|
-
matchObjectProperty(call, "target", id.target));
|
|
627
|
-
case "metric":
|
|
628
|
-
case "secret":
|
|
629
|
-
case "secretNamespace":
|
|
630
|
-
case "claimKey":
|
|
631
|
-
return (matchFirstArgString(call, id.shortName) || matchObjectProperty(call, "name", id.shortName));
|
|
632
|
-
case "referenceData":
|
|
633
|
-
return (matchFirstArgString(call, id.entityName) ||
|
|
634
|
-
matchObjectProperty(call, "entity", id.entityName));
|
|
635
|
-
case "useExtension":
|
|
636
|
-
// Positional: r.useExtension(name, entity) | Object: { name, entity }
|
|
637
|
-
if (matchFirstArgString(call, id.extensionName)) {
|
|
638
|
-
return matchArgNameOrRef(call, 1, id.entityName);
|
|
639
|
-
}
|
|
640
|
-
return (matchObjectProperty(call, "name", id.extensionName) &&
|
|
641
|
-
matchObjectProperty(call, "entity", id.entityName));
|
|
642
|
-
case "job":
|
|
643
|
-
return matchFirstArgString(call, id.jobName) || matchObjectProperty(call, "name", id.jobName);
|
|
644
|
-
case "notification":
|
|
645
|
-
return (matchFirstArgString(call, id.notificationName) ||
|
|
646
|
-
matchObjectProperty(call, "name", id.notificationName));
|
|
647
|
-
case "httpRoute":
|
|
648
|
-
// Object form only; positional doesn't apply.
|
|
649
|
-
return (matchObjectProperty(call, "method", id.method) && matchObjectProperty(call, "path", id.path));
|
|
650
|
-
case "projection":
|
|
651
|
-
case "multiStreamProjection":
|
|
652
|
-
return matchObjectProperty(call, "name", id.name);
|
|
653
|
-
case "defineEvent":
|
|
654
|
-
return (matchFirstArgString(call, id.eventName) || matchObjectProperty(call, "name", id.eventName));
|
|
655
|
-
case "extendsRegistrar":
|
|
656
|
-
return matchFirstArgString(call, id.extensionName);
|
|
657
|
-
case "ai.generate":
|
|
658
|
-
case "ai.extract":
|
|
659
|
-
case "ai.classify":
|
|
660
|
-
return readAiStepKey(call) === id.stepKey;
|
|
661
|
-
default: {
|
|
662
|
-
const _exhaustive = id;
|
|
663
|
-
return _exhaustive;
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
// Resolves an argument the same way the extractors do (readNameLiteral):
|
|
668
|
-
// a string literal directly, or a bare identifier following its
|
|
669
|
-
// declaration (same-file or imported) to a string-literal initializer —
|
|
670
|
-
// the dominant naming style in the framework's own bundled-features
|
|
671
|
-
// (`r.entity(ENTITY, ...)`, `r.useExtension(EXT_X, ...)`).
|
|
672
|
-
//
|
|
673
|
-
// Narrow on purpose: every kind's arg-0 (and relation's arg-1) is parsed
|
|
674
|
-
// via readNameLiteral, never readNameOrRef (see round2.ts/round3.ts) —
|
|
675
|
-
// widening this shared helper to readNameOrRef would let an object-form
|
|
676
|
-
// call's first argument (an ObjectLiteralExpression) match here too,
|
|
677
|
-
// short-circuiting the object-form branch in callMatchesId. Positions
|
|
678
|
-
// where the parser itself accepts an inline `{ name: "..." }` ref use
|
|
679
|
-
// `matchArgNameOrRef` below instead.
|
|
680
|
-
function matchArgString(call, index, expected) {
|
|
681
|
-
const arg = call.getArguments()[index];
|
|
682
|
-
if (!arg)
|
|
683
|
-
return false;
|
|
684
|
-
return readNameLiteral(arg) === expected;
|
|
685
|
-
}
|
|
686
|
-
// @wrapper-known semantic-alias
|
|
687
|
-
function matchFirstArgString(call, expected) {
|
|
688
|
-
return matchArgString(call, 0, expected);
|
|
689
|
-
}
|
|
690
|
-
// Like matchArgString, but via readNameOrRef — for the specific positional
|
|
691
|
-
// slots where the parser accepts an inline `{ name: "..." }` object ref in
|
|
692
|
-
// addition to a literal/identifier (useExtension's entity arg, hook's
|
|
693
|
-
// target arg; see round3.ts:445 / hooks.ts:75). Not a drop-in replacement
|
|
694
|
-
// for matchArgString: applying it to an arg-0 position would match an
|
|
695
|
-
// object-form call's first (and only) argument, see the comment above.
|
|
696
|
-
function matchArgNameOrRef(call, index, expected) {
|
|
697
|
-
const arg = call.getArguments()[index];
|
|
698
|
-
if (!arg)
|
|
699
|
-
return false;
|
|
700
|
-
return readNameOrRef(arg) === expected;
|
|
701
|
-
}
|
|
702
|
-
// Object-form property values are resolved via readNameOrRef, not the
|
|
703
|
-
// narrower readNameLiteral — some properties (useExtension's `entity`,
|
|
704
|
-
// hook's `target`/`allOf`) accept an inline `{ name: "..." }` ref in the
|
|
705
|
-
// parser (see extractors/round3.ts, extractors/hooks.ts), not just a
|
|
706
|
-
// literal or identifier. readNameOrRef is a superset of readNameLiteral,
|
|
707
|
-
// so this is safe for every other property too.
|
|
708
|
-
function matchObjectProperty(call, propName, expected) {
|
|
709
|
-
const obj = call.getArguments()[0]?.asKind(SyntaxKind.ObjectLiteralExpression);
|
|
710
|
-
if (!obj)
|
|
711
|
-
return false;
|
|
712
|
-
const init = obj.getProperty(propName)?.asKind(SyntaxKind.PropertyAssignment)?.getInitializer();
|
|
713
|
-
if (!init)
|
|
714
|
-
return false;
|
|
715
|
-
return readNameOrRef(init) === expected;
|
|
716
|
-
}
|
|
717
|
-
// Matches an r.hook `{ allOf: entity }` target — as the arg node directly
|
|
718
|
-
// (positional call form) or as a nested object property (object call form).
|
|
719
|
-
function matchAllOfArg(node, expectedEntity) {
|
|
720
|
-
const obj = node?.asKind(SyntaxKind.ObjectLiteralExpression);
|
|
721
|
-
const init = obj?.getProperty("allOf")?.asKind(SyntaxKind.PropertyAssignment)?.getInitializer();
|
|
722
|
-
if (!init)
|
|
723
|
-
return false;
|
|
724
|
-
return readNameOrRef(init) === expectedEntity;
|
|
725
|
-
}
|
|
726
|
-
function matchObjectAllOfProperty(call, propName, expectedEntity) {
|
|
727
|
-
const obj = call.getArguments()[0]?.asKind(SyntaxKind.ObjectLiteralExpression);
|
|
728
|
-
const propInit = obj
|
|
729
|
-
?.getProperty(propName)
|
|
730
|
-
?.asKind(SyntaxKind.PropertyAssignment)
|
|
731
|
-
?.getInitializer();
|
|
732
|
-
return matchAllOfArg(propInit, expectedEntity);
|
|
733
|
-
}
|
|
734
556
|
// =============================================================================
|
|
735
557
|
// Format helpers — line boundaries / blank-line collapse
|
|
736
558
|
// (indent / PATTERN_INDENT live in render.ts and are imported above.)
|