Skip to content

Commit 237f513

Browse files
refactor(objectql): generate the engine.ts section split with a deterministic codemod (#30012)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} --> _Requested by **Yibo Wang** · [project thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBYCcuvo7uSguGzFDstLzarK)_ Part of #29986 ## 维护者速读 - 改了什么:codemod `scripts/codemod-split-engine.mjs` 按裁决 6624345766 的门面形态拆分 `engine.ts`。`ObjectQL` 的 161/278 个方法体(未修改)移进 16 个无状态 facet 类(每个分节模块一个类,类里是静态方法)。146/146 个顶层声明移进叶子模块 `engine-top-level.ts`。类上留下签名和一个调用 facet 的 stub。私有方法的 docblock 随方法体移走,公开方法和受保护方法的 docblock 留在类上。方法体读私有静态成员时经 `ObjectQLStatics` 转发。状态仍在 `ObjectQL` 上。生成的文件没有手改。按路径读 `engine.ts` 的读者跟着代码读新模块。 - 为什么改:#29465 大文件拆分;裁决 6624345766 把 13,000 行门槛换成结构门禁(门面文件里不留方法体,例外具名列出,见 #30019)。 - 风险与回滚:只是搬移。161/161 个方法体、146/146 个顶层声明逐字节相同。撤销拆分会逐字节还原 base 的 `engine.ts`。objectql 测试 base 10,829 = head 10,829,typecheck 0 错。打包后的 `.d.ts` 公开面相等(裁决 6624345766 第 1 条)。回滚方式是 revert。 - 先看:① 下方「例外」表(31 个方法体留在类上,以及原因);② `ObjectQLStatics` 和类末尾的 static 块;③ 下方「按路径读的读者」表。 - 两分钟验证:`pnpm install --frozen-lockfile` 之后运行 `node scripts/codemod-split-engine.mjs --verify --base 0b99c2b`,应输出 `161/161 moved bodies identical`、`146/146 moved top-level declarations identical`、`23245 -> 14140 lines`,退出码 0;`node scripts/codemod-split-engine.mjs --self-test` 应通过 62 例。 - 需要你做的:改动超过 3,000 行,需要您批准,并在您定的窗口里落地;本 PR 保持草稿。 Clause-②: no Metadata-protocol: none ## What changes - **`scripts/codemod-split-engine.mjs`** (new). It plans the split, writes it and proves it. It follows the pilot's form (#29598) and these rulings: 6591753301 A (levers (a) and (b)), 6597555122 A (by-path readers widen in the same PR) and 6624345766 (the facade shape, below). - **Stateless facets.** Each section module holds one class, `<Section>Facet`, whose static methods are the moved bodies, unedited (`static name(this: Pick<SectionHost, …>, …)`). A class member can take any name, so a reserved word (`delete`) or a name an import already takes no longer keeps a body on the class. - **Host pattern.** The stub hands the body one literal holding the members it reads. Methods go as closures called on the engine at call time, so spies and overrides still work. Fields and accessors go as getters. A field a body assigns also gets a setter (31 stubs). - **Docblock ownership.** A private method's docblock moves with its body (87 moved). A public or protected method keeps its docblock on the class, and the facet member carries a one-line placeholder that points there. - **Private-static-through-host.** `engine.ts` declares `ObjectQLStaticsHost` (each member typed `(typeof ObjectQL)['x']`) and `ObjectQLStatics`. A static block at the end of the class fills the object with forwarders: a method is called on the class, a field is read when read. A facet module whose bodies read a private static imports `ObjectQLStatics` AS `ObjectQL`, so the body text does not change (4 modules, 12 statics). `index.ts` re-exports neither. - Generic methods call their body instantiated with their own type parameters, `(Facet.name<T>).call(host, …)`. A defaulted parameter forwards the value the stub's default produced. A static method whose body reads no `this` moves too. - The no-yield rule is dropped, because the structural gate counts bodies, not lines. - `PATH_READER_PINS` keeps six methods that carry a #4918 `opts as any` site on the class. The guard grandfathers by file, and its baseline may only shrink. - `--self-test`: 62 cases in 6 batteries (stay rules 20; facets, statics and docblocks 11; top-level moves 11; byte-identity proof 10; import prune 4; refusals 6). Each proof is fed an input it must refuse. - **The generated split.** `--write` on main `9d1a465f` (its `engine.ts` is the base `0b99c2b1` one), unedited. New files: `engine-compatibility-section.ts`, `engine-migration-state.ts`, `engine-transactions-section.ts`. Changed: the 13 earlier section modules and `engine.ts`. ### Facets | Module | Facet | Bodies | |:--|:--|--:| | `engine-hooks-and-functions.ts` | `HooksAndFunctionsFacet` | 14 | | `engine-actions.ts` | `ActionsFacet` | 11 | | `engine-security-resolvers.ts` | `SecurityResolversFacet` | 13 | | `engine-middleware-and-context.ts` | `MiddlewareAndContextFacet` | 11 | | `engine-defaults-and-autonumbers.ts` | `DefaultsAndAutonumbersFacet` | 10 | | `engine-registration.ts` | `RegistrationFacet` | 12 | | `engine-references-and-master-detail.ts` | `ReferencesAndMasterDetailFacet` | 11 | | `engine-secrets-and-routing.ts` | `SecretsAndRoutingFacet` | 11 | | `engine-migration-state.ts` | `MigrationStateFacet` | 12 | | `engine-summaries-and-expansion.ts` | `SummariesAndExpansionFacet` | 8 | | `engine-find.ts` | `FindFacet` | 7 | | `engine-insert.ts` | `InsertFacet` | 5 | | `engine-cascade-planning.ts` | `CascadePlanningFacet` | 20 | | `engine-delete-and-aggregate.ts` | `DeleteAndAggregateFacet` | 2 | | `engine-transactions-section.ts` | `TransactionsSectionFacet` | 5 | | `engine-compatibility-section.ts` | `CompatibilitySectionFacet` | 9 | Another 86 methods were already stubs from the hand series (#25522). Their docblocks stay on the class, because this codemod does not edit the hand-series modules. ### Exceptions: bodies that stay on the class (31, for the #30019 allow-list) | Reason | Methods (body lines) | |:--|:--| | bare `this` (16) | `update` 2099, `insert` 1384, `delete` 690, `find` 225, `findOne` 191, `recomputeSummaries` 99, `resolveReadableIdsSecurity` 35, `bindHooks` 32, `readMigrationFlagRowMemoized` 27, `createContext` 18, `ownershipQueueService` 14, `buildHookApi` 10, `setHookMetricsRecorder` 9, `setStrictHookBinding` 8, `setWarnLegacyHandler` 4, `getHookMetricsRecorder` 4 | | inferred public return type (8) | `registerApp` 149, `registerHook` 89, `registerDriver` 82, `init` 68, `triggerHooks` 52, `destroy` 22, `setDatasourceMapping` 17, `getStatus` 12 | | #4918 erasure pin (6) | `seedAutonumber` 152, `probeReferencingRows` 116, `resolveMasterDetailParent` 110, `resolveMasterDetailParents` 75, `inspectDanglingReferences` 67, `referenceExists` 35 | | field call (1) | `homeAmountServing` 54 (it calls the field `homeLocalizationResolver`, and the host would change the receiver) | `ObjectRepository` and `ScopedContext` (696 lines) are not facaded by this codemod. ## Tests | Reading | Base | Head | |:--|:--|:--| | objectql `test` (local) | 547 files, 10,829 passed | 547 files, 10,829 passed | | objectql `test:repo` | 5 passed | 5 passed | | objectql `typecheck` (`tsc` + `check:test-typecheck`) | exit 0 | exit 0 | | core `test:repo` | | 94 passed | ## Readers of `engine.ts` by path | Reader | Now | |:--|:--| | `engine-middleware-operation-vocabulary.test.ts` (#7809 weld) | reads `engine.ts` and `engine-top-level.ts` | | `federated-injected-column-readers.test.ts` (#21918) | rows keyed to the facet module holding each site; `ENGINE_SPLIT_FILES` names all 16 modules and the leaf, and a `via` resolves across them | | `scripts/check-stack-collection-maps.mjs` | the `METADATA_ARRAY_KEYS` row reads `engine-top-level.ts` | | core `second-object-read-exposure.pin.test.ts` | the `executes` row names `engine-summaries-and-expansion.ts`; two `not-a-served-read` rows for `engine-security-resolvers.ts` and `engine-top-level.ts` | | `check:liveness` | `field/summaryOperations`, `field/autonumberFormat` and `datasource/external.rowCap` cite the facet modules | | `check-system-context-census` | site register regenerated with `--write`; no row changed | | `check-adr-anchors`, `functional-completeness.ts` | anchors name `engine-top-level.ts` | | eslint `query-options/no-any-erasure`, `check:query-options-erasure` | unchanged: the six sites stay in `engine.ts` | Ablations, each red and then restored: a federated row re-keyed back to `engine.ts` (3 tests red); the core `executes` row put back on `engine.ts` (2 red); `'purge'` added to `OperationContext` in the leaf (both weld tests red); `'ablatedCollection'` added to `METADATA_ARRAY_KEYS` (`check-stack-collection-maps` red). The liveness and census reds were observed before their fixes. ## `.d.ts`: public surface (ruling 6624345766 item 1) The reading is `/mnt/project-files/issue-29986/pubsurface.mjs`. It applies dtsnorm's second normalisation (ruling 6441032442 B) after removing each private class member, with its leading docblock. Built dist: main 1 = main 2 and head 1 = head 2, byte for byte. | File | statements | private members | public surface | exported names | |:--|:--|:--|:--|:--| | `core.d.ts`, `core.d.mts` | 16 / 16 | 0 / 0 | equal | equal | | `index.d.ts`, `index.d.mts` | 168 / 168 | 94 / 94 | equal | equal | | `util-HASH.d.ts`, `util-HASH.d.mts` | 190 / 190 | 332 / 332 | equal | equal | Controls: a private member made public is caught; a public member's printed type changed is caught; a private docblock edit is not caught. Strict 6441032442 B, for reference: `util-HASH` differs in one statement (the `ObjectQL` class, by its moved private docblocks), and its surface digest is equal. `ObjectQLStatics` and the facets appear in no `.d.ts`. ## Verification | Reading | Result | |:--|:--| | Byte-identity (`--verify --base 0b99c2b`) | exit 0: 161/161 bodies and 146/146 declarations identical; the moved docblocks and the placeholders are checked, the statics block is rebuilt from the base class, and the undo rebuilds the base `engine.ts` once the 107 pruned bindings are re-derived | | Determinism | two generations from the same input are identical; tree sha256 `2723df98712b1831ad01067458bcfaa3695ff9a3eb8a7e0c46e1566f007c8e73` (input `dc5677f3ef529f5272d95855361202d9948cb802a89fc3df80dca16dea3e1384`), the same as the tree on disk | | `pnpm build`, `pnpm lint`, `check-nul-bytes` | exit 0 | | `dispatch-gates --commands` | 126 derived, 126 run, all exit 0; `--ran`: 0 NOT-MEASURED, 0 UNRUN | No changeset: the pilot #29598 shipped none (`skip-changeset`), and this PR follows it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WGufzhfKpcY1cmybduYBcp <!-- signed: domain:server#6 · os-support-ai · session_01WGufzhfKpcY1cmybduYBcp --> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent c2d353d commit 237f513

28 files changed

Lines changed: 15876 additions & 10510 deletions

‎content/docs/permissions/system-context.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,7 @@ and CI refuses one that differs from what the tree produces.
532532
| `packages/metadata-core/src/meta-write-capability.ts#walledCatalogWriteVerdict` | 1 | 55b |
533533
| `packages/metadata-core/src/object-schema-fls.ts#isObjectSchemaMaskExempt` | 1 | 8 |
534534
| `packages/objectql/src/credential-evaluation-guard.ts#rejectCredentialEvaluation` | 1 | 18c |
535+
| `packages/objectql/src/engine-cascade-planning.ts#dependentCountIsDisclosable` | 1 | 28 |
535536
| `packages/objectql/src/engine-delete-cascade.ts#recordReferenceCheckElevation` | 1 | 29 |
536537
| `packages/objectql/src/engine-driver-options.ts#buildDriverOptions` | 1 | 24 |
537538
| `packages/objectql/src/engine-execution-plumbing.ts#buildSession` | 1 | 63 |
@@ -541,7 +542,6 @@ and CI refuses one that differs from what the tree produces.
541542
| `packages/objectql/src/engine-secret-internal-fields.ts#encryptSecretFields` | 1 | 23e |
542543
| `packages/objectql/src/engine-secret-internal-fields.ts#stripSearchCompanionFromRead` | 1 | 27 |
543544
| `packages/objectql/src/engine-validate.ts#validate` | 1 | 21b |
544-
| `packages/objectql/src/engine.ts#dependentCountIsDisclosable` | 1 | 28 |
545545
| `packages/objectql/src/engine.ts#inheritsElevation` | 1 | 1b |
546546
| `packages/objectql/src/engine.ts#insert` | 3 | 21, 22 |
547547
| `packages/objectql/src/engine.ts#isSystem` | 1 | 64 |

‎docs/adr/0072-reference-scope-and-resolvability.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ for (const [k, v] of Object.entries(context.record))
5151
| Surface | Evaluator (file:line) | In-scope references | Token shape |
5252
| :-- | :-- | :-- | :-- |
5353
| Flow / edge / decision | `packages/services/service-automation/src/engine.ts#variables` | `record`,`previous`, **flattened record fields**, flow `variables`, node outputs, `$runId`/`$flowName` | bare **and** `record.x` |
54-
| Formula field (`Field.expression`) | `packages/objectql/src/engine.ts#expression` | `{ now, timezone, user, org, record }` | `record.x` only |
54+
| Formula field (`Field.expression`) | `packages/objectql/src/engine-top-level.ts#expression` | `{ now, timezone, user, org, record }` | `record.x` only |
5555
| Validation (`script`/`cross_field`/`when`) | `packages/objectql/src/validation/rule-validator.ts#cross_field` | `{ record: {...previous,...patch}, previous }` | `record.x` only |
5656
| Field `visibleWhen`/`requiredWhen`/`readonlyWhen` | `packages/objectql/src/validation/rule-validator.ts#readonlyWhen` | merged `record`, `previous`, (`parent` for master-detail — server-bound for `readonlyWhen` since #4889; the client grid binds it for all three) | `record.x` only |
5757
| Hook lifecycle `condition` | `packages/objectql/src/hook-wrappers.ts#condition` | `{ record }` | `record.x` only |

‎packages/core/src/security/second-object-read-exposure.pin.test.ts‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,8 @@ const CALLERS: Record<string, Classification> = {
133133
wiring: { file: 'packages/metadata-protocol/src/protocol.ts', call: 'this.servedExpand(' },
134134
pin: 'packages/metadata-protocol/src/protocol.expand-target-exposure.test.ts',
135135
},
136-
'packages/objectql/src/engine.ts': {
136+
// [#29986] The codemod split moved the body of `ObjectQL.expandRelatedRecords` out of engine.ts.
137+
'packages/objectql/src/engine-summaries-and-expansion.ts': {
137138
kind: 'executes',
138139
read: "the expansion sub-read (`ObjectQL.expandRelatedRecords`), for the data door's `$expand`",
139140
decidedIn: 'packages/metadata-protocol/src/protocol-query-validation.ts',
@@ -228,6 +229,19 @@ const CALLERS: Record<string, Classification> = {
228229
'`beforeRelationTarget` hook (the data door hands it `refuseUnservedRelationTarget`). Pinned by ' +
229230
'`packages/objectql/src/internal-field-evaluation-guard.test.ts` (the refusals assert the driver was never asked)',
230231
},
232+
// [#29986] The codemod split of engine.ts moved two arbiter callers out of it.
233+
'packages/objectql/src/engine-security-resolvers.ts': {
234+
kind: 'not-a-served-read',
235+
reason:
236+
"a formula field's relation hop (`resolveFormulaRelated`): it judges the formula's own path against the " +
237+
"target's DECLARED field map and loads through `resolveOneHopRelated` (its row), serving nothing of the target here",
238+
},
239+
'packages/objectql/src/engine-top-level.ts': {
240+
kind: 'not-a-served-read',
241+
reason:
242+
'the delete walk names the object a relation points at (`deleteWalkTargetOf`: a `user` field answers `sys_user`) ' +
243+
'to plan a cascade or a restrict; it reads no record and serves nothing of the target',
244+
},
231245
// [#25522 cut 10] The write-path reference check, moved out of engine.ts.
232246
'packages/objectql/src/engine-referential-integrity.ts': {
233247
kind: 'not-a-served-read',
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* [#29986 codemod] The "Action system and packaged-action activation" section
5+
* of `ObjectQL`: the bodies of `registerAction`, `executeAction`,
6+
* `listRegisteredActions`, `removeActionsByPackage`,
7+
* `setActionActivationStore`, `hydrateActionActivations`, `isActionEnabled`,
8+
* `listDisabledActions`, `getDisabledActions`, `describeDisabledAction`,
9+
* `setActionActive` moved out of `engine.ts` whole by
10+
* `scripts/codemod-split-engine.mjs`.
11+
*
12+
* A MOVE, not a redesign. {@link ActionsFacet} is a stateless facet of `ObjectQL`
13+
* (ruling 6624345766): each static method of it is the body of the `ObjectQL`
14+
* method of the same name exactly as it stood: not re-indented, not
15+
* formatted, not edited. The method stays on the class as a one-call stub with
16+
* its signature, so the published surface, an instance spy and a subclass
17+
* override are what they were. A public or protected method keeps its
18+
* docblock on the class, the contract; a private method's docblock moved here
19+
* with its body. The state stays on `ObjectQL`.
20+
*
21+
* The bodies reach the class through {@link ActionsHost}: exactly the
22+
* `ObjectQL` members they read, private ones included. Each stub hands its
23+
* body one literal of the members that body reads, plus a `.call`: methods go
24+
* as closures that call them on the engine at call time, so a subclass
25+
* override or an instance spy is still the one reached; fields and accessors
26+
* go as GETTERS, read when the body reads them, as they were on the class,
27+
* and a field a body assigns goes with a SETTER that assigns it on the engine.
28+
*
29+
* The module imports `engine.ts` for types only, so the two modules form no
30+
* evaluation cycle. `index.ts` re-exports nothing from this module.
31+
*/
32+
33+
import type { ActionActivationRow, ActionActivationStore, ActionActivationWriteOptions } from './action-activation.js';
34+
import type { ObjectQL } from './engine.js';
35+
36+
/**
37+
* The `ObjectQL` members the bodies of {@link ActionsFacet} read — every one of
38+
* them, and no other. Each is typed AS the class member (an indexed access on
39+
* the class), so a change to a member's signature on the class is a change
40+
* here too. `readonly` members are the getters; the others are fields a body
41+
* assigns.
42+
*/
43+
export interface ActionsHost {
44+
readonly actionActivation: ObjectQL['actionActivation'];
45+
readonly actions: ObjectQL['actions'];
46+
readonly logger: ObjectQL['logger'];
47+
}
48+
49+
/** The bodies of the "Action system and packaged-action activation" section of `ObjectQL`: a stateless facet, called by the class's stubs. */
50+
export class ActionsFacet {
51+
/** The body of `ObjectQL#registerAction`; the method's docblock there is its contract. */
52+
static registerAction(this: Pick<ActionsHost, 'actions' | 'logger'>, objectName: string, actionName: string, handler: (ctx: any) => Promise<any> | any, packageName?: string): void {
53+
const key = `${objectName}:${actionName}`;
54+
const replaced = this.actions.get(key);
55+
if (replaced !== undefined && replaced.package !== packageName) {
56+
const by = (name: string | undefined) => (name ? `by package '${name}'` : 'with no package');
57+
this.logger.warn(
58+
`Action handler '${key}' registered ${by(packageName)} replaces the one registered ${by(replaced.package)}: `
59+
+ `the action door now runs the handler registered ${by(packageName)}, whichever declaration object `
60+
+ `'${objectName}' lists${packageName ? `, and withdrawing package '${packageName}' leaves the action with no handler` : ''}. `
61+
+ `Two owners share one handler key only when both declare action '${actionName}' on object `
62+
+ `'${objectName}' — rename one of them.`,
63+
{ objectName, actionName, package: packageName, replacedPackage: replaced.package },
64+
);
65+
}
66+
this.actions.set(key, { handler, package: packageName });
67+
this.logger.debug('Registered action', { objectName, actionName, package: packageName });
68+
}
69+
70+
/** The body of `ObjectQL#executeAction`; the method's docblock there is its contract. */
71+
static async executeAction(this: Pick<ActionsHost, 'actions'>, objectName: string, actionName: string, ctx: any): Promise<any> {
72+
const entry = this.actions.get(`${objectName}:${actionName}`);
73+
if (!entry) {
74+
throw new Error(`Action '${actionName}' on object '${objectName}' not found`);
75+
}
76+
return entry.handler(ctx);
77+
}
78+
79+
/** The body of `ObjectQL#listRegisteredActions`; the method's docblock there is its contract. */
80+
static listRegisteredActions(this: Pick<ActionsHost, 'actions'>): Array<{ objectName: string; actionName: string; package?: string }> {
81+
const out: Array<{ objectName: string; actionName: string; package?: string }> = [];
82+
for (const [key, entry] of this.actions.entries()) {
83+
const sep = key.indexOf(':');
84+
if (sep < 0) continue;
85+
out.push({
86+
objectName: key.slice(0, sep),
87+
actionName: key.slice(sep + 1),
88+
...(entry.package ? { package: entry.package } : {}),
89+
});
90+
}
91+
return out;
92+
}
93+
94+
/** The body of `ObjectQL#removeActionsByPackage`; the method's docblock there is its contract. */
95+
static removeActionsByPackage(this: Pick<ActionsHost, 'actions'>, packageName: string): void {
96+
for (const [key, entry] of this.actions.entries()) {
97+
if (entry.package === packageName) {
98+
this.actions.delete(key);
99+
}
100+
}
101+
}
102+
103+
/** The body of `ObjectQL#setActionActivationStore`; the method's docblock there is its contract. */
104+
static setActionActivationStore(this: Pick<ActionsHost, 'actionActivation'>, store: ActionActivationStore): void {
105+
this.actionActivation.attach(store);
106+
}
107+
108+
/** The body of `ObjectQL#hydrateActionActivations`; the method's docblock there is its contract. */
109+
static async hydrateActionActivations(this: Pick<ActionsHost, 'actionActivation'>): Promise<string[]> {
110+
return this.actionActivation.hydrate();
111+
}
112+
113+
/** The body of `ObjectQL#isActionEnabled`; the method's docblock there is its contract. */
114+
static isActionEnabled(this: Pick<ActionsHost, 'actionActivation'>, actionName: string): boolean {
115+
return this.actionActivation.isEnabled(actionName);
116+
}
117+
118+
/** The body of `ObjectQL#listDisabledActions`; the method's docblock there is its contract. */
119+
static listDisabledActions(this: Pick<ActionsHost, 'actionActivation'>): string[] {
120+
return this.actionActivation.disabledNames();
121+
}
122+
123+
/** The body of `ObjectQL#getDisabledActions`; the method's docblock there is its contract. */
124+
static getDisabledActions(this: Pick<ActionsHost, 'actionActivation'>): string[] | undefined {
125+
return this.actionActivation.durable ? this.actionActivation.disabledNames() : undefined;
126+
}
127+
128+
/** The body of `ObjectQL#describeDisabledAction`; the method's docblock there is its contract. */
129+
static describeDisabledAction(this: Pick<ActionsHost, 'actionActivation' | 'logger'>, actionName: string): string {
130+
this.logger.info(
131+
`ACTION_DISABLED: packaged action '${actionName}' refused — it is switched off in the activation ledger ` +
132+
'(sys_metadata_activation, ADR-0126 §8 item 2).',
133+
{ action: actionName, refusal: 'ACTION_DISABLED' },
134+
);
135+
return this.actionActivation.describeDisabled(actionName);
136+
}
137+
138+
/** The body of `ObjectQL#setActionActive`; the method's docblock there is its contract. */
139+
static async setActionActive(this: Pick<ActionsHost, 'actionActivation'>, row: ActionActivationRow, options?: ActionActivationWriteOptions): Promise<void> {
140+
await this.actionActivation.setActive(row, options);
141+
}
142+
}

0 commit comments

Comments
 (0)