Skip to content

Commit 1c5fe3c

Browse files
fix(driver-memory)!: retire the legacy default export that CJS require() could not reach (#27323)
Fixes #26206 Clause-②: no (narrowing) Metadata-protocol: none Skills-docs: none — no `skills/**` file and no `content/docs/**` page names the retired `default` export or loads the package as a string plugin; the docs teach `InMemoryDriver` by name, which stays ## 维护者速读 - **改了什么**:`@objectstack/driver-memory` 的包入口不再提供 `default` 导出(一个旧式的 `{ id, version, onEnable }` 对象)。`InMemoryDriver` 等所有具名导出不变。这是本卡最后一项,前两项已由 #26321、#26352 落地。 - **为什么改**:这个默认导出不是内核插件(内核约定是 init/start/destroy),而且 CommonJS 用户 `require()` 时拿不到它,类型声明却说有(attw 报 `FalseExportDefault`)。退役后 attw 对该包为 0 问题。 - **风险与回滚**:本仓内默认导入者为 0,示例、脚手架模板、文档、技能里也没有人把它当字符串插件写进 `plugins`。外部若在配置里写了 `plugins: ['@objectstack/driver-memory']`,原来会注册一个内存驱动,现在会静默不注册(changeset 写明改用 `new DriverPlugin(new InMemoryDriver(), 'memory')`)。cloud / hotcrm 未能测到。回滚 = revert 本 PR。 - **先看**:`packages/drivers/driver-memory/src/index.ts`(删掉的默认导出);`.changeset/26206-driver-memory-default.md`(BREAKING 说明、ADR-0087 处置与 FROM → TO);`packages/drivers/driver-memory/src/default-export-retired.pin.test.ts`。 - **两分钟验证**:`node scripts/check-adr-0087-registration.mjs --base origin/main`,预期 exit 0 且打印 `verified: @objectstack/driver-memory#default`。 - **需要你做的**:无,席位落地(收窄已发布导出面,先过一次契约档复核)。 ## What changes - `packages/drivers/driver-memory/src/index.ts`: the `export default { id, version, onEnable }` object leaves the published entry. Every named export stays. A comment at the old site says why the entry has no default and which named door to use. - Choice, as the card asks: **drop the default** in favour of the named exports, not "make the CJS default reachable". Reasons: 1. A reachable CJS default means `require('@objectstack/driver-memory')` itself returns the default object. The entry also has 22 named exports, so that needs an `export =` shape with the named exports merged onto the object. That changes what every CommonJS named import reads today, and it keeps an object that is not a kernel `Plugin`. 2. Nothing reads it. In this tree: 0 default imports, 0 `require()` calls, 0 `.default` reads after a dynamic import, and 0 string `plugins` entries naming the package (evidence surface below). Control: 14 files (code, docs and skills) import named exports with a static `from`, and 3 code files destructure `InMemoryDriver` from a dynamic `import()`. - `packages/drivers/driver-memory/README.md`: the `default` row leaves the Key Exports table, and one line says the package has no default export. - `.changeset/26206-driver-memory-default.md`: `patch` (Metadata-protocol `none`), BREAKING banner, ADR-0087 `not-required (runtime-export-retired @objectstack/driver-memory#default)`, and one FROM → TO line. No staged window. - `packages/drivers/driver-memory/src/default-export-retired.pin.test.ts` (2 cases): the entry has no `default`, and the named exports stay and `InMemoryDriver` still constructs. ### What changes for a string `plugins` entry (PM assumption 3) `packages/cli/src/commands/serve.ts:2765` (`mod.default || mod`) loads the project's own config file, not a plugin. The string-plugin door is `materializeStackPlugin` in `packages/core/src/stack-plugins.ts:65`: it loads the module and takes `mod.default || mod`, and an object with no `init` is wrapped in `AppPlugin`, which runs a bundle's `onEnable` with a `drivers.register` that calls `ql.registerDriver`. Measured at that seam (real core `dist`, real driver-memory `dist`, a recording `wrapBundle`): - before: `wrapBundle` received `id, version, onEnable`, and calling `onEnable` with a driver registry registered `InMemoryDriver(com.objectstack.driver.memory)`. - after: `wrapBundle` receives the module namespace (`InMemoryDriver`, …, no `id`, no `onEnable`). `AppPlugin` then takes its no-id, no-payload branch (`packages/runtime/src/app-plugin.ts`) and becomes an empty no-op plugin, so no driver is registered and nothing says so. So a config that writes `plugins: ['@objectstack/driver-memory']` changes behaviour. No producer of that spelling was found (search below). The changeset names the replacement for that case. ## Tests <details><summary>attw 0.18.5 on an npm-pack staging of driver-memory: before and after</summary> Before (base `65b492081`, attw exit 1): ```text node10: ❗️ Incorrect default export node16 (from CJS): ❗️ Incorrect default export node16 (from ESM): 🟢 (ESM) bundler: 🟢 ``` After (`161653aed`, attw exit 0): ```text No problems found 🌟 node10: 🟢 node16 (from CJS): 🟢 (CJS) node16 (from ESM): 🟢 (ESM) bundler: 🟢 ``` The packed `dist/index.d.ts` has 0 `export default` lines after the change. </details> <details><summary>driver-memory build, typecheck and tests at 161653a</summary> `pnpm --filter @objectstack/driver-memory build && … typecheck && … test` under the verify lock: `VERDICT command-exit 0`; `Test Files 73 passed (73)`, `Tests 1760 passed (1760)`. `tsc --noEmit --listFiles` counts the new pin test once (it is compiled by the typecheck program). </details> <details><summary>Ablation (one-off, no permanent file)</summary> The fix was committed first. `node scripts/ablation-replace.mjs` replaced the comment line at the old site with `export default { id: 'ablated' };` (anchor 1 → 0, blob `a1f49a03fbd9` → `f5bf6cfac4ce`) and ran the pin test: `× the published entry has no default export`, `AssertionError: expected true to be false`, `Tests 1 failed | 1 passed (2)`. Restored: blob == HEAD (`a1f49a03fbd9`), `git diff HEAD` empty. The test imports `./index.js`, so it reads `src`, and no build was needed for this leg. A first attempt used a replacement that contained its own anchor. The tool refused it before running anything ("the anchor count moved 1 -> 1"), so that attempt measured nothing. </details> <details><summary>Full dogfood battery at 161653a (the title carries a bang)</summary> After `pnpm turbo run build --filter='./packages/**' --concurrency=2` (75/75 tasks, 0 cached, `VERDICT command-exit 0`), `pnpm --filter @objectstack/dogfood test` under the verify lock, no shard, no path: `VERDICT command-exit 0`, `Test Files 348 passed | 3 skipped (351)`, `Tests 2806 passed | 20 skipped (2826)`. No failures. </details> ## Acceptance notes - **Evidence surface for readers of the default** (tree `objectstack-ai/objectstack` at `65b492081`): `git grep` over the whole tree (`examples/**`, `packages/create-objectstack/**` templates, `content/docs/**`, `skills/**`, `ui/**`, every package's tests and `scripts/**`) for `import X from` / `import X, { … } from` / `import * as X from` of the package (0), `require('@objectstack/driver-memory')` (0), a dynamic `import()` whose result is read as `.default` or destructured as `default` (0; the 3 code hits all destructure `InMemoryDriver`, the other 2 hits are comments), a `plugins:` line naming the package (0), and the default's id `com.objectstack.driver.memory` (hits are the driver class's own `name`, tests and CLI banner text, none of which reads the default object). `content/docs/plugins/index.mdx` and `content/docs/plugins/packages.mdx` teach `InMemoryDriver` by name. - NOT MEASURED: `objectstack-ai/cloud` and `objectstack-ai/hotcrm` (this session's GitHub reads are bound to this repository; org code search answered 403), and npm dependants. - The four sibling drivers (`driver-sql`, `driver-mongodb`, `driver-sqlite-wasm`, `driver-turso`) export the same legacy `{ id, version, onEnable }` default. attw 0.18.5 on npm-pack stagings of all four at `161653aed` reports **no problems** (exit 0). So they are not part of this card's acceptance, and this PR does not touch them. Why attw flags driver-memory's build and not theirs (the CJS output has the same shape) was not established. - The silent no-op above is not specific to this package: a string `plugins` entry naming any module without a default export and without `init` gets the module namespace wrapped into an empty `AppPlugin`. That was read at the seam, not measured through `os serve`, so it is recorded here and not filed. - ADR-0087 gate (PM assumption 1): `check-adr-0087-registration --base origin/main` exit 0, `verified: @objectstack/driver-memory#default (export, packages/drivers/driver-memory/src/index.ts)`. The blocker fix `cdb949a288` is an ancestor of the base (`merge-base --is-ancestor` exit 0). ## Verification Local, at `161653aed`, derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` over this diff (58 commands). Every command ran, and each exit code was captured before any pipe. - 55 exited 0 on the first run. - 3 exited 3, PREREQUISITE NOT MET, so they measured nothing: `check-plugin-teardown-shape --self-test` (its pinned fixture commit was outside the shallow clone) and `check:dual-build-cjs-loads` / `check:lean-entry-closure` (they read built output). They were re-run after `git fetch --depth=1` of the fixture commit and the full packages build, and all 3 exited 0: `112 published require entry point(s) across 69 package(s) load`, `Admitted set held exactly (15 packages)`, `48 cases pass`. - `dispatch-gates --ran`: `58 derived, 58 run, 0 NOT-MEASURED, 0 UNRUN` (exit 0). - Outside the derived list: `check-platform-checklist --changed-from origin/main` exit 0 (0 anchored files), `check:published-readme-exports` exit 0, `check:published-readme-links` exit 0, `check-nul-bytes` exit 0. - `test:repo` and repo-wide lint were not run locally. They belong to CI (`Test Core`, `Lint & Repo Gates`) on this PR. --- _Generated by [Claude Code](https://claude.ai/code/session_017xRBcVyH9FzSkzAGmz8LNE)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent cf43b6c commit 1c5fe3c

4 files changed

Lines changed: 58 additions & 18 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/driver-memory': patch
3+
---
4+
5+
fix(driver-memory)!: retire the legacy `default` export, which a CommonJS `require()` could not reach as its types described
6+
7+
The package entry exported a legacy `{ id, version, onEnable }` object as its `default`. It is not a kernel plugin (the kernel contract is `init` / `start` / `destroy`), and the CommonJS build could only put it on `require('@objectstack/driver-memory').default`, while the published declarations said `export default` (`@arethetypeswrong/cli` reported `FalseExportDefault`). It leaves the published entry. Every named export stays. Nothing in this repository imported the default, and no example, scaffold template, doc page or skill names the package as a string entry of a stack's `plugins` array.
8+
9+
Clause-②: no (narrowing)
10+
Metadata-protocol: none
11+
12+
<!-- adr-0087: not-required (runtime-export-retired @objectstack/driver-memory#default) One runtime object leaves a published package entry; no key of any metadata schema, no object definition and no stored shape is added, removed, renamed or re-shaped, so there is nothing for `objectstack migrate meta` to rewrite and no tombstone. A TypeScript consumer meets the break at its own import, and the line below names the door to use instead. -->
13+
14+
**BREAKING** (a runtime export is removed). It ships as `patch` because the metadata protocol does not move. There is no staged window.
15+
16+
- FROM `default` TO `new DriverPlugin(new InMemoryDriver(), 'memory')` from `@objectstack/runtime`, or `new InMemoryDriver()` handed to whatever registers your drivers.
17+
18+
A stack config that names `'@objectstack/driver-memory'` as a string entry of `plugins` loaded that object and registered an `InMemoryDriver` through it. After this change that entry loads the module without a default and registers no driver, so put `new DriverPlugin(new InMemoryDriver(), 'memory')` in the `plugins` array instead.

‎packages/drivers/driver-memory/README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,15 @@ const driver = new InMemoryDriver({
6161

6262
| Export | Kind | Description |
6363
|:---|:---|:---|
64-
| `default` | legacy plugin object | Legacy `onEnable` shape — not a kernel `Plugin`; register through `DriverPlugin`. |
6564
| `InMemoryDriver` | class | Driver instance for direct use. |
6665
| `InMemoryStrategy` | class | Query execution strategy used by ObjectQL. |
6766
| `FileSystemPersistenceAdapter` | class | Node-only persistence. |
6867
| `LocalStoragePersistenceAdapter` | class | Browser-only persistence. |
6968
| `MemoryAnalyticsService` | class | Adds analytics aggregations backed by memory store. |
7069
| `InMemoryDriverConfig`, `PersistenceAdapterInterface`, `MemoryAnalyticsConfig` | types | Configuration shapes. |
7170

71+
The package has no default export. Register the driver with a named export, as in Quick Start above.
72+
7273
## Configuration
7374

7475
| Option | Type | Default | Notes |
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* The package entry carries no `default` export (ADR-0087
5+
* `runtime-export-retired @objectstack/driver-memory#default`).
6+
*
7+
* The retired object was the legacy `{ id, version, onEnable }` shape, which is
8+
* not a kernel `Plugin`, and the CJS build could only reach it as
9+
* `require(...).default` while the published declarations said `export default`
10+
* (attw `FalseExportDefault`). The named exports are the supported surface.
11+
*
12+
* Pinned both ways: the entry has no `default`, and the named door the
13+
* changeset points readers to still constructs a working driver.
14+
*/
15+
16+
import { describe, it, expect } from 'vitest';
17+
import * as entry from './index.js';
18+
19+
describe('driver-memory entry: the legacy default export is retired', () => {
20+
it('the published entry has no default export', () => {
21+
expect('default' in entry).toBe(false);
22+
});
23+
24+
it('the named exports stay, and InMemoryDriver still constructs a driver', () => {
25+
for (const name of ['InMemoryDriver', 'InMemoryStrategy', 'MemoryAnalyticsService', 'FileSystemPersistenceAdapter', 'LocalStoragePersistenceAdapter']) {
26+
expect(typeof (entry as Record<string, unknown>)[name]).toBe('function');
27+
}
28+
const driver = new entry.InMemoryDriver();
29+
expect(driver.name).toBe('com.objectstack.driver.memory');
30+
});
31+
});

‎packages/drivers/driver-memory/src/index.ts‎

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -61,20 +61,10 @@ export type {
6161
UniqueAwareSchema,
6262
} from './memory-unique-constraint.js';
6363

64-
export default {
65-
id: 'com.objectstack.driver.memory',
66-
version: '1.0.0',
67-
68-
onEnable: async (context: any) => {
69-
const { logger, config, drivers } = context;
70-
logger.info('[Memory Driver] Initializing...');
71-
72-
if (drivers) {
73-
const driver = new InMemoryDriver(config);
74-
drivers.register(driver);
75-
logger.info(`[Memory Driver] Registered driver: ${driver.name}`);
76-
} else {
77-
logger.warn('[Memory Driver] No driver registry found in context.');
78-
}
79-
}
80-
};
64+
// [ADR-0087 runtime-export-retired] This entry has NO default export, on
65+
// purpose. The legacy `{ id, version, onEnable }` object it once carried was not
66+
// a kernel `Plugin` (the contract is init/start/destroy), and the package's CJS
67+
// build could only place it on `require(...).default`, which the published
68+
// `.d.ts` misdescribed (attw `FalseExportDefault`). Register this driver through
69+
// a named export: `new DriverPlugin(new InMemoryDriver(), 'memory')`
70+
// (`@objectstack/runtime`), or construct `InMemoryDriver` directly.

0 commit comments

Comments
 (0)