Skip to content

Commit 9ab2637

Browse files
committed
feat: add write path policy guard
1 parent 94d2650 commit 9ab2637

33 files changed

Lines changed: 1218 additions & 112 deletions

‎README.md‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,26 @@ The shared runtime direction is cache consistency, connection lifecycle, transac
6161

6262
monSQLize coordinates cache, transactions, sync, and queues inside the runtime, but it does not provide a global strict-consistency kernel. MongoDB transactions keep MongoDB session ACID semantics. Cache invalidation runs around writes or transaction commit and is best-effort; a short-lived dirty barrier makes cached reads bypass and avoid refilling cache while a namespace is being invalidated, but Redis/L2 cache and Pub/Sub invalidation still provide eventual cross-instance coherence rather than atomic cache/DB commits. Change Stream sync is at-least-once; custom targets should be idempotent by change event `_id`, and `sync.idempotency` can record per-target replay markers when a durable store is provided. Transaction cache locks are process-local in the v2 runtime; use explicit business coordination, idempotency/fencing, or cache bypassing for cross-instance strict flows.
6363

64+
## Write Path Policy
65+
66+
By default, both `collection()` and `model()` may write. Applications that want all writes for selected namespaces to pass through Model defaults, hooks, timestamps, versioning, and soft-delete rules can enable `writePathPolicy`.
67+
68+
```ts
69+
const msq = new MonSQLize({
70+
type: 'mongodb',
71+
databaseName: 'app',
72+
config: { uri: 'mongodb://localhost:27017' },
73+
writePathPolicy: {
74+
default: 'model-only',
75+
namespaces: {
76+
'app.audit_logs': 'allow-both'
77+
}
78+
}
79+
});
80+
```
81+
82+
`model-only` blocks direct collection/db/legacy write surfaces by default, including raw access and `$out` / `$merge` aggregate writes, while Model write and Model management methods remain available. See [Write Path Policy](./docs/en/write-path-policy.md).
83+
6484
## When to Use It
6585

6686
monSQLize is a good fit when you need:

‎changelogs/unreleased.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
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
- 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.
1919
- 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.
20+
- 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.
2021
- Added strict optimistic-locking support to Model `updateBatch(..., { versionMode: 'strict' })`; default `counter` behavior remains unchanged.
2122
- Clarified the runtime consistency contract across cache, transactions, Change Stream sync, and CountQueue; `transaction.distributedLock` now warns as a v1 compatibility placeholder because v2 transaction cache locks remain process-local.
2223
- Added an event-level barrier for Change Stream sync target failures, passed a cooperative `AbortSignal` through `CountQueue.execute()` timeouts, and unified ObjectId auto-conversion field matching across query/write paths including nested array path segments.

‎docs/en/README.md‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
| Quick start / installation / connection / basic queries | [`getting-started.md`](./getting-started.md) | ✅ | Aligned with the current runtime and full TypeScript types |
1010
| Common recipes | [`recipes.md`](./recipes.md) | ✅ | Copy-ready paths for basic connection, cache, Redis, SSH, pools, locks, and Model usage |
1111
| Cache / function cache | [`cache-and-function-cache.md`](./cache-and-function-cache.md) | ✅ | `MemoryCache` / `withCache()` / `FunctionCache` |
12+
| Write path policy | [`write-path-policy.md`](./write-path-policy.md) | ✅ | Optional runtime guard for Model-only write namespaces |
1213
| Examples mapping / gallery | [`examples.md`](./examples.md) | ✅ | Maps documentation topics to official examples |
1314
| Advanced capability index | [Capability index](./capability-index.md) | ✅ | Index of the current advanced capability surface |
1415
| Verification / architecture / engineering governance | [`verification-entrypoints.md`](./verification-entrypoints.md) / [`runtime-architecture.md`](./runtime-architecture.md) / [`support-matrix.md`](./support-matrix.md) / [`release-preflight.md`](./release-preflight.md) | ✅ | Unified entry points for verification, private real-env boundaries, runtime structure, and release constraints |
@@ -19,13 +20,14 @@
1920
2. Quick start: [`getting-started.md`](./getting-started.md)
2021
3. Recipes: [`recipes.md`](./recipes.md)
2122
4. Cache guide: [`cache-and-function-cache.md`](./cache-and-function-cache.md)
22-
5. Capability index: [Capability index](./capability-index.md)
23-
6. Engineering and boundaries:
23+
5. Write path policy: [`write-path-policy.md`](./write-path-policy.md)
24+
6. Capability index: [Capability index](./capability-index.md)
25+
7. Engineering and boundaries:
2426
- [`verification-entrypoints.md`](./verification-entrypoints.md)
2527
- [`support-matrix.md`](./support-matrix.md)
2628
- [`release-preflight.md`](./release-preflight.md)
2729
- [`roadmap-boundaries.md`](./roadmap-boundaries.md)
28-
7. Runnable examples:
30+
8. Runnable examples:
2931
- `examples/README.md`
3032
- `examples/quick-start/basic-connect.ts`
3133
- `examples/cache/with-cache.ts`

‎docs/en/api-index.md‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ This index documents the current stable MongoDB adapter APIs and shared runtime
2222
| [Cache system](cache.md) | Cache system (LRU + TTL) |
2323
| [Function cache](function-cache.md) | **Function cache: add caching to any async function (v1.1.4+)** |
2424
| [Transaction management](transaction.md) | Transaction management (automatic retry, cache locks) |
25+
| [Write path policy](write-path-policy.md) | Optional runtime policy for Model-only write namespaces |
2526
| [Change Stream sync](sync-backup.md) | **Change Stream data sync: real-time backup to multiple databases (v1.0.8+)** |
2627
| [Transaction optimizations](transaction-optimizations.md) | Transaction optimization strategies |
2728
| [Distributed deployment](distributed-deployment.md) | **Distributed deployment guide for multi-instance cache consistency** |
@@ -114,6 +115,7 @@ This index documents the current stable MongoDB adapter APIs and shared runtime
114115
| [Collection management](collection-management.md) | Collection management |
115116
| [Read preference](readPreference.md) | Read preference settings |
116117
| [Count queue](count-queue.md) | Count queue control for high-concurrency optimization |
118+
| [Write path policy](write-path-policy.md) | Configure whether writes may use collection APIs or must use Model APIs |
117119
| [Distributed deployment](distributed-deployment.md) | Distributed deployment configuration |
118120

119121
---
@@ -253,11 +255,12 @@ Recommended reading order for new users:
253255

254256
---
255257

256-
**Document count**: 97
257-
**Index coverage**: 97/97, including this index page
258-
**Last updated**: 2026-06-10
258+
**Document count**: 98
259+
**Index coverage**: 98/98, including this index page
260+
**Last updated**: 2026-06-30
259261

260262
Recent additions:
261263

264+
- [Write path policy](write-path-policy.md) - Optional Model-only write namespace guard
262265
- [ESM support](esm-support.md) - ES Module (`import`) support
263266
- [findOneAnd return values](findOneAnd-return-value-unified.md) - Return-value unification notes

‎docs/en/capability-index.md‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Principles:
2121
| Capability | Recommended entry | Notes |
2222
|------------|----------------------|-------|
2323
| Model | `msq.model()` / `MonSQLize.Model` | Supports model registration, relations, virtuals, populate, and common query entries |
24+
| write-path-policy | `writePathPolicy` | Optional runtime guard for Model-only write namespaces |
2425
| transaction | `startSession()` / `withTransaction()` | Supports MongoDB transaction wrappers and transaction statistics |
2526
| pool | `pool()` / `ConnectionPoolManager` | Supports multi-pool configuration, selection strategies, fallback, and status inspection |
2627
| sync | `startSync()` / `stopSync()` / `getSyncStats()` | Supports Change Stream sync start, stop, and status inspection |
@@ -40,7 +41,17 @@ Model documentation and examples:
4041
- Docs: `docs/model.md`, `docs/populate.md`, `docs/relations.md`
4142
- Example: `examples/docs/model.ts`
4243

43-
## 2. transaction
44+
## 2. write-path-policy
45+
46+
Available entries:
47+
48+
- `writePathPolicy.default`
49+
- `writePathPolicy.namespaces`
50+
- `WritePathPolicyOptions`
51+
52+
Use this when selected namespaces should be written through `msq.model()` instead of direct `collection()` writes. See `docs/en/write-path-policy.md`.
53+
54+
## 3. transaction
4455

4556
Available entries:
4657

@@ -52,7 +63,7 @@ Good first-use cases:
5263
- Identifying the transaction wrapper entry.
5364
- Inspecting transaction retry, timeout, and statistics behavior.
5465

55-
## 3. pool
66+
## 4. pool
5667

5768
Available entries:
5869

@@ -65,7 +76,7 @@ This is the current entry point for:
6576
- Multi-pool onboarding.
6677
- Configuration contract and runtime routing explanations.
6778

68-
## 4. sync / slow-query-log
79+
## 5. sync / slow-query-log
6980

7081
Available entries:
7182

@@ -79,14 +90,14 @@ These capabilities include topic pages and runnable examples:
7990

8091
Future documentation will continue to cover runtime fault injection, real deployment topology, and performance boundaries.
8192

82-
## 5. Recommended Entries in This Repository
93+
## 6. Recommended Entries in This Repository
8394

8495
1. Documentation home: `docs/index.md`
8596
2. API index: `docs/api-index.md`
8697
3. Example index: `docs/examples.md`
8798
4. Runnable examples: `examples/docs/*.ts`
8899

89-
## 6. Suggested Expansion Order
100+
## 7. Suggested Expansion Order
90101

91102
The most natural next expansion order is:
92103

‎docs/en/write-path-policy.md‎

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Write Path Policy
2+
3+
`writePathPolicy` lets a runtime decide whether writes may use both `collection()` and `model()`, or whether a namespace must be written through the Model layer.
4+
5+
The default is intentionally permissive: when `writePathPolicy` is omitted, collection APIs and Model APIs are both allowed. Enable this policy only for applications that want the runtime to enforce a stronger write boundary around schema defaults, hooks, timestamps, optimistic locking, soft delete, and other Model mutation rules.
6+
7+
## Configuration
8+
9+
```ts
10+
import MonSQLize from 'monsqlize';
11+
12+
const msq = new MonSQLize({
13+
type: 'mongodb',
14+
databaseName: 'app',
15+
config: { uri: 'mongodb://localhost:27017' },
16+
writePathPolicy: {
17+
default: 'model-only',
18+
namespaces: {
19+
'app.audit_logs': 'allow-both',
20+
'analytics:app.reports': {
21+
mode: 'allow-both',
22+
raw: 'block',
23+
management: 'allow'
24+
}
25+
}
26+
}
27+
});
28+
```
29+
30+
## Rule Shape
31+
32+
```ts
33+
type WritePathPolicyMode = 'allow-both' | 'model-only';
34+
35+
type WritePathPolicyRule = {
36+
mode?: WritePathPolicyMode;
37+
raw?: 'inherit' | 'allow' | 'block';
38+
management?: 'inherit' | 'allow' | 'block';
39+
onViolation?: 'throw' | 'warn';
40+
};
41+
42+
type WritePathPolicyOptions = {
43+
default?: WritePathPolicyMode | WritePathPolicyRule;
44+
namespaces?: Record<string, WritePathPolicyMode | WritePathPolicyRule>;
45+
};
46+
```
47+
48+
| Field | Default | Meaning |
49+
|-------|---------|---------|
50+
| `mode` | `allow-both` | `allow-both` allows collection and Model writes. `model-only` blocks direct collection, db, and legacy writes unless overridden. |
51+
| `raw` | `inherit` | Controls `collection.raw()`, `db.raw()`, and db command access. Inherits `block` from `model-only` and `allow` from `allow-both`. |
52+
| `management` | `inherit` | Controls index and collection management operations. In `model-only`, Model management methods are allowed while direct collection management is blocked. |
53+
| `onViolation` | `throw` | `throw` rejects the operation. `warn` logs a warning and allows the operation. |
54+
55+
## Namespace Matching
56+
57+
Namespace rules are matched from most specific to least specific:
58+
59+
1. Internal instance namespace, when present.
60+
2. Pool-scoped namespace: `poolName:dbName.collectionName`.
61+
3. Database namespace: `dbName.collectionName`.
62+
4. Collection name only.
63+
5. `default`.
64+
65+
Prefer `poolName:dbName.collectionName` or `dbName.collectionName` in user configuration. They are stable across runtime instance IDs.
66+
67+
## Governed Operations
68+
69+
`writePathPolicy` applies to write-capable paths:
70+
71+
- Collection writes: `insertOne`, `insertMany`, `updateOne`, `updateMany`, `replaceOne`, `findOneAndUpdate`, `findOneAndReplace`, `findOneAndDelete`, `upsertOne`, `deleteOne`, `deleteMany`.
72+
- Batch helpers: `insertBatch`, `updateBatch`, `deleteBatch`, `incrementOne`.
73+
- Collection management: index creation/drop, collection creation/drop, validators, `renameCollection`, `collMod`, and capped conversion.
74+
- Raw/db/legacy write surfaces: `collection.raw()`, `db.raw()`, `db.runCommand()`, `dropDatabase()`, and legacy adapter writes.
75+
- Aggregation pipelines whose final stage writes with `$out` or `$merge`; the policy is checked against the write target namespace.
76+
77+
Read-only queries are not governed by this policy.
78+
79+
## Model-Only Example
80+
81+
```ts
82+
const msq = new MonSQLize({
83+
type: 'mongodb',
84+
databaseName: 'app',
85+
config: { uri: 'mongodb://localhost:27017' },
86+
writePathPolicy: { default: 'model-only' }
87+
});
88+
89+
MonSQLize.Model.define('users', {
90+
schema: {},
91+
options: {
92+
timestamps: true,
93+
version: true,
94+
softDelete: true
95+
}
96+
});
97+
98+
await msq.connect();
99+
100+
await msq.model('users').insertOne({ name: 'Ada' }); // allowed
101+
await msq.collection('users').insertOne({ name: 'Ada' }); // throws
102+
```
103+
104+
Use namespace overrides for operational collections that intentionally remain native:
105+
106+
```ts
107+
const msq = new MonSQLize({
108+
type: 'mongodb',
109+
databaseName: 'app',
110+
config: { uri: 'mongodb://localhost:27017' },
111+
writePathPolicy: {
112+
default: 'model-only',
113+
namespaces: {
114+
'app.audit_logs': 'allow-both'
115+
}
116+
}
117+
});
118+
```
119+
120+
## Boundary
121+
122+
This policy controls the API path used to issue writes. It does not make cache invalidation transaction-atomic, does not make Change Stream sync exactly-once, and does not replace application-level idempotency or authorization.

‎docs/zh/README.md‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
| 快速开始 / 安装 / 连接 / 基础查询 | [`getting-started.md`](./getting-started.md) | ✅ | 对齐当前 runtime,TypeScript 完整类型 |
1010
| 常见场景配方 | [`recipes.md`](./recipes.md) | ✅ | 最小连接、缓存、Redis、SSH、连接池、锁、Model 的复制即用路径 |
1111
| 缓存 / 函数缓存 | [`cache-and-function-cache.md`](./cache-and-function-cache.md) | ✅ | `MemoryCache` / `withCache()` / `FunctionCache` |
12+
| 写路径策略 | [`write-path-policy.md`](./write-path-policy.md) | ✅ | 可选的 Model-only 写入命名空间运行时护栏 |
1213
| 示例映射 / Gallery | [`examples.md`](./examples.md) | ✅ | 文档主题到官方示例的映射页 |
1314
| 高级能力索引 | [高级能力索引页](./capability-index.md) | ✅ | 完整能力入口索引 |
1415
| 验证 / 架构 / 工程治理 | [`verification-entrypoints.md`](./verification-entrypoints.md) / [`runtime-architecture.md`](./runtime-architecture.md) / [`support-matrix.md`](./support-matrix.md) / [`release-preflight.md`](./release-preflight.md) | ✅ | 统一查看公开验证入口、私有 real-env 边界、运行时结构与发布约束 |
@@ -19,13 +20,14 @@
1920
2. 上手路径:[`getting-started.md`](./getting-started.md)
2021
3. 场景配方:[`recipes.md`](./recipes.md)
2122
4. 缓存专题:[`cache-and-function-cache.md`](./cache-and-function-cache.md)
22-
5. 能力索引:[高级能力索引页](./capability-index.md)
23-
6. 工程与边界:
23+
5. 写路径策略:[`write-path-policy.md`](./write-path-policy.md)
24+
6. 能力索引:[高级能力索引页](./capability-index.md)
25+
7. 工程与边界:
2426
- [`verification-entrypoints.md`](./verification-entrypoints.md)
2527
- [`support-matrix.md`](./support-matrix.md)
2628
- [`release-preflight.md`](./release-preflight.md)
2729
- [`roadmap-boundaries.md`](./roadmap-boundaries.md)
28-
7. 可执行示例:
30+
8. 可执行示例:
2931
- `examples/README.md`
3032
- `examples/quick-start/basic-connect.ts`
3133
- `examples/cache/with-cache.ts`

‎docs/zh/api-index.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
| [缓存系统](cache.md) | 缓存系统(LRU + TTL) |
2323
| [函数缓存](function-cache.md) | **🎉 函数缓存 - 为任意异步函数添加缓存能力(v1.1.4+)🆕** |
2424
| [事务管理](transaction.md) | 事务管理(自动重试、缓存锁) |
25+
| [写路径策略](write-path-policy.md) | 可选的 Model-only 写入命名空间策略 |
2526
| [Change Stream 同步](sync-backup.md) | **🎉 Change Stream 数据同步 - 实时备份到多个数据库(v1.0.8+)🆕** |
2627
| [事务优化策略](transaction-optimizations.md) | 事务优化策略 |
2728
| [分布式部署](distributed-deployment.md) | **分布式部署指南(多实例缓存一致性)⭐** |
@@ -114,6 +115,7 @@
114115
| [集合管理](collection-management.md) | 集合管理 |
115116
| [读偏好设置](readPreference.md) | 读偏好设置 |
116117
| [Count 队列控制](count-queue.md) | **Count 队列控制(高并发优化)⭐** |
118+
| [写路径策略](write-path-policy.md) | 配置写操作可走 collection API,或必须经过 Model API |
117119
| [分布式部署配置](distributed-deployment.md) | **分布式部署配置** |
118120

119121
---

‎docs/zh/capability-index.md‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ monSQLize 是数据库原生的生产数据运行时增强层。当前稳定适
2020
| 能力 | 推荐入口 | 说明 |
2121
|------|--------------|------|
2222
| Model | `msq.model()` / `MonSQLize.Model` | 支持 Model 注册、关系、虚拟字段、populate 与常用查询入口 |
23+
| write-path-policy | `writePathPolicy` | 可选的 Model-only 写入命名空间运行时护栏 |
2324
| transaction | `startSession()` / `withTransaction()` | 支持 MongoDB 事务封装与事务统计 |
2425
| pool | `pool()` / `ConnectionPoolManager` | 支持多连接池配置、选择策略、fallback 与状态查看 |
2526
| sync | `startSync()` / `stopSync()` / `getSyncStats()` | 支持 Change Stream 同步启动、停止和状态查询 |
@@ -39,7 +40,17 @@ Model 相关文档与示例:
3940
- 文档:`docs/model.md`、`docs/populate.md`、`docs/relations.md`
4041
- 示例:`examples/docs/model.ts`
4142

42-
## 2. transaction
43+
## 2. write-path-policy
44+
45+
可用入口:
46+
47+
- `writePathPolicy.default`
48+
- `writePathPolicy.namespaces`
49+
- `WritePathPolicyOptions`
50+
51+
当指定命名空间必须通过 `msq.model()` 写入,而不能直接通过 `collection()` 写入时使用。详见 `docs/zh/write-path-policy.md`。
52+
53+
## 3. transaction
4354

4455
可用入口:
4556

@@ -51,7 +62,7 @@ Model 相关文档与示例:
5162
- 事务封装入口识别
5263
- 事务重试、超时与统计行为确认
5364

54-
## 3. pool
65+
## 4. pool
5566

5667
可用入口:
5768

@@ -64,7 +75,7 @@ Model 相关文档与示例:
6475
- 多连接池入口识别
6576
- 配置契约与运行时路由说明
6677

67-
## 4. sync / slow-query-log
78+
## 5. sync / slow-query-log
6879

6980
可用入口:
7081

@@ -78,14 +89,14 @@ Model 相关文档与示例:
7889

7990
后续文档会继续补充运行时故障注入、真实部署拓扑与性能边界。
8091

81-
## 5. 当前仓库内的推荐入口
92+
## 6. 当前仓库内的推荐入口
8293

8394
1. 文档站首页:`docs/index.md`
8495
2. API 索引:`docs/api-index.md`
8596
3. 示例索引:`docs/examples.md`
8697
4. 可执行示例目录:`examples/docs/*.ts`
8798

88-
## 6. 后续扩展顺序建议
99+
## 7. 后续扩展顺序建议
89100

90101
当前更适合的扩展顺序是:
91102

0 commit comments

Comments
 (0)