Skip to content

Commit 53b4698

Browse files
committed
fix: avoid schema-dsl extension double registration
1 parent df120cc commit 53b4698

7 files changed

Lines changed: 51 additions & 5 deletions

File tree

changelogs/unreleased.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
- Fixed aggregate direct `.toArray()` to honor cache/meta execution paths, extended `find()` ObjectId auto-conversion to comparison operators, forwarded CountQueue abort signals into MongoDB count options, and added a warning when sync idempotency falls back to in-memory storage.
1818
- Upgraded `schema-dsl` to `2.1.1` and moved Model schema compilation/validation onto a MonSQLize runtime-scoped `schema-dsl/runtime` engine. `schemaDsl` now supports runtime options, extension registration, external runtime injection, and validation disablement.
19+
- Fixed `schemaDsl: { runtime, extensions }` lifecycle handling so injected runtimes register extensions once during `connect()` instead of registering the same factory during construction and reconnect setup.
20+
- Raised the default `mongodb-memory-server` launch timeout to 30 seconds for test, validation, examples, and `useMemoryServer` paths while keeping `MONSQLIZE_MEMORY_MONGO_LAUNCH_TIMEOUT_MS` as the override.
1921
- Added a short-lived read-cache dirty barrier around writes and transaction commits. Cached reads now bypass and avoid refilling query cache while a namespace is being invalidated, reducing stale-cache windows when a process exits between a database write and post-write invalidation.
2022
- Added optional Change Stream sync idempotency gates (`sync.idempotency`) with per-target keys and duplicate stats, so supervised restarts can skip targets already marked as applied before saving the shared resume token.
2123
- Added `writePathPolicy` with default `allow-both` behavior and optional `model-only` namespace enforcement across collection, db, legacy, raw, management, batch, and aggregate `$out` / `$merge` write paths.

scripts/validation/memory-server-policy.cjs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const fs = require('node:fs');
44
const path = require('node:path');
55

66
const DEFAULT_MEMORY_SERVER_VERSION = '7.0.14';
7+
const DEFAULT_MEMORY_SERVER_LAUNCH_TIMEOUT_MS = 30_000;
78
const MANAGED_DB_PATH_PREFIXES = ['single-', 'replset-', 'examples-single-', 'examples-replset-', 'probe-single-', 'probe-replset-'];
89

910
function isGeneratedPath(dir) {
@@ -159,7 +160,7 @@ async function seedMemoryServerBinaryCache(version) {
159160
function resolveMemoryServerLaunchTimeoutMs() {
160161
const raw = process.env.MONSQLIZE_MEMORY_MONGO_LAUNCH_TIMEOUT_MS;
161162
if (!raw) {
162-
return undefined;
163+
return DEFAULT_MEMORY_SERVER_LAUNCH_TIMEOUT_MS;
163164
}
164165

165166
const value = Number.parseInt(raw, 10);
@@ -225,6 +226,7 @@ async function stopMemoryServerWithCleanup(instance, dbPath) {
225226

226227
module.exports = {
227228
DEFAULT_MEMORY_SERVER_VERSION,
229+
DEFAULT_MEMORY_SERVER_LAUNCH_TIMEOUT_MS,
228230
configureMemoryServerEnv,
229231
createMemoryServerDbPath,
230232
memoryServerCleanupOptions,

src/adapters/mongodb/common/connect.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type { MongoConnectConfig, MongoConnectionState } from '../../../../types
1717
export type { MongoConnectConfig, MongoConnectionState } from '../../../../types/mongodb';
1818

1919
const DEFAULT_MEMORY_SERVER_VERSION = '7.0.14';
20+
const DEFAULT_MEMORY_SERVER_LAUNCH_TIMEOUT_MS = 30_000;
2021
const MANAGED_DB_PATH_PREFIXES = ['single-', 'replset-', 'examples-single-', 'examples-replset-', 'probe-single-', 'probe-replset-'];
2122

2223
// Singleton memory server (v1 compatible: reuses an already-started instance)
@@ -153,7 +154,7 @@ async function cleanupManagedDbPath(dbPath: string | null): Promise<boolean> {
153154
function resolveLaunchTimeout(): number | undefined {
154155
const raw = process.env.MONSQLIZE_MEMORY_MONGO_LAUNCH_TIMEOUT_MS;
155156
if (!raw) {
156-
return undefined;
157+
return DEFAULT_MEMORY_SERVER_LAUNCH_TIMEOUT_MS;
157158
}
158159

159160
const value = Number.parseInt(raw, 10);

src/entry/runtime-core.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ import {
148148
import { resolveScopedCollection } from './runtime-scoped-collection';
149149
import { normalizeRuntimeCacheWithLifecycle } from './runtime-cache-normalizer';
150150
import { prepareSshTunnelConnectConfig } from './runtime-ssh';
151-
import { createRuntimeSchemaDslEngine, disposeRuntimeSchemaDslEngine, replaceRuntimeSchemaDslEngine, type SchemaDslEngine } from './runtime-schema-dsl';
151+
import { createInitialRuntimeSchemaDslEngine, disposeRuntimeSchemaDslEngine, replaceRuntimeSchemaDslEngine, type SchemaDslEngine } from './runtime-schema-dsl';
152152

153153
// All public symbols are re-exported from the barrel file to keep the public API unchanged
154154
export * from './runtime-exports';
@@ -221,7 +221,7 @@ export class MonSQLizeRuntime {
221221
this._cache = normalizedCache.cache;
222222
this._cacheClose = normalizedCache.close;
223223
this._logger = Logger.create(options.logger ?? null);
224-
this._schemaDslEngine = createRuntimeSchemaDslEngine(options);
224+
this._schemaDslEngine = createInitialRuntimeSchemaDslEngine();
225225
if (shouldWarnUnsignedCursorSecret(options)) this._logger.warn?.('[MonSQLizeRuntime] cursorSecret is not configured; findPage cursor tokens are unsigned.');
226226
if (shouldWarnTransactionDistributedLock(options)) this._logger.warn?.('[MonSQLizeRuntime] transaction.distributedLock is a compatibility configuration and is not wired into the transaction cache lock in the v2 runtime. Transaction cache locks remain process-local; use explicit business locking with idempotency/fencing or disable cache when cross-instance strict consistency is required.');
227227
this._cacheLockManager = new CacheLockManager({

src/entry/runtime-schema-dsl.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import type { MonSQLizeOptions } from '../../types/monsqlize';
44

55
export type { SchemaDslEngine } from '../capabilities/model/schema-dsl';
66

7+
export function createInitialRuntimeSchemaDslEngine(): SchemaDslEngine {
8+
return createSchemaDslEngine(false);
9+
}
10+
711
export function createRuntimeSchemaDslEngine(options: MonSQLizeOptions): SchemaDslEngine {
812
return createSchemaDslEngine(options.schemaDsl);
913
}

test/bootstrap/memory-server-policy.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdir
22
import path from 'node:path';
33

44
export const DEFAULT_MEMORY_SERVER_VERSION = '7.0.14';
5+
export const DEFAULT_MEMORY_SERVER_LAUNCH_TIMEOUT_MS = 30_000;
56
const MANAGED_DB_PATH_PREFIXES = ['single-', 'replset-', 'examples-single-', 'examples-replset-', 'probe-single-', 'probe-replset-'];
67

78
type MemoryServerPolicy = {
@@ -171,7 +172,7 @@ export async function seedMemoryServerBinaryCache(version?: string): Promise<voi
171172
export function resolveMemoryServerLaunchTimeoutMs(): number | undefined {
172173
const raw = process.env.MONSQLIZE_MEMORY_MONGO_LAUNCH_TIMEOUT_MS;
173174
if (!raw) {
174-
return undefined;
175+
return DEFAULT_MEMORY_SERVER_LAUNCH_TIMEOUT_MS;
175176
}
176177

177178
const value = Number.parseInt(raw, 10);

test/integration/model/model-schema-validation-errors.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,42 @@ describe('model — schema-dsl runtime configuration', () => {
259259
assert.doesNotThrow(() => schemaRuntime.s({ tenantId: 'tenantId!' }));
260260
schemaRuntime.dispose();
261261
});
262+
263+
it('registers schemaDsl.extensions once when using an injected runtime', async () => {
264+
const schemaRuntime = createRuntime();
265+
const runtime = new MonSQLize({
266+
type: 'mongodb',
267+
databaseName: 'test_schema_runtime_injected_extensions',
268+
config: { uri },
269+
schemaDsl: {
270+
runtime: schemaRuntime,
271+
extensions: [{
272+
type: 'customType',
273+
literal: 'tenant-id',
274+
factoryName: 'tenantId',
275+
schema: { type: 'string', pattern: '^tenant_[a-z0-9]+$' },
276+
}],
277+
},
278+
});
279+
await runtime.connect();
280+
const modelName = 'schema_runtime_injected_extensions_' + Date.now();
281+
try {
282+
Model.define(modelName, {
283+
schema: (dsl: any) => dsl({
284+
tenantId: dsl.tenantId().require(),
285+
}),
286+
});
287+
const model = runtime.model(modelName);
288+
assert.equal(model.validate({ tenantId: 'tenant_demo' }).valid, true);
289+
assert.equal(model.validate({ tenantId: 'bad' }).valid, false);
290+
} finally {
291+
await runtime.close();
292+
Model.undefine(modelName);
293+
}
294+
295+
assert.doesNotThrow(() => schemaRuntime.s({ tenantId: 'tenant-id!' }));
296+
schemaRuntime.dispose();
297+
});
262298
});
263299

264300
describe('model — schema-dsl type delegation via redefine()', () => {

0 commit comments

Comments
 (0)