@brightchain/db 0.20.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/README.md +81 -0
- package/package.json +18 -0
- package/src/__tests__/helpers/mockBlockStore.d.ts +113 -0
- package/src/__tests__/helpers/mockBlockStore.js +380 -0
- package/src/__tests__/helpers/mockBlockStore.js.map +1 -0
- package/src/index.d.ts +31 -0
- package/src/index.js +78 -0
- package/src/index.js.map +1 -0
- package/src/lib/aggregation.d.ts +18 -0
- package/src/lib/aggregation.js +407 -0
- package/src/lib/aggregation.js.map +1 -0
- package/src/lib/cblIndex.d.ts +268 -0
- package/src/lib/cblIndex.js +856 -0
- package/src/lib/cblIndex.js.map +1 -0
- package/src/lib/collection.d.ts +305 -0
- package/src/lib/collection.js +991 -0
- package/src/lib/collection.js.map +1 -0
- package/src/lib/cursor.d.ts +8 -0
- package/src/lib/cursor.js +13 -0
- package/src/lib/cursor.js.map +1 -0
- package/src/lib/database.d.ts +158 -0
- package/src/lib/database.js +332 -0
- package/src/lib/database.js.map +1 -0
- package/src/lib/errors.d.ts +85 -0
- package/src/lib/errors.js +103 -0
- package/src/lib/errors.js.map +1 -0
- package/src/lib/expressMiddleware.d.ts +57 -0
- package/src/lib/expressMiddleware.js +488 -0
- package/src/lib/expressMiddleware.js.map +1 -0
- package/src/lib/headRegistry.d.ts +60 -0
- package/src/lib/headRegistry.js +216 -0
- package/src/lib/headRegistry.js.map +1 -0
- package/src/lib/indexing.d.ts +7 -0
- package/src/lib/indexing.js +14 -0
- package/src/lib/indexing.js.map +1 -0
- package/src/lib/model.d.ts +162 -0
- package/src/lib/model.js +260 -0
- package/src/lib/model.js.map +1 -0
- package/src/lib/pooledStoreAdapter.d.ts +44 -0
- package/src/lib/pooledStoreAdapter.js +109 -0
- package/src/lib/pooledStoreAdapter.js.map +1 -0
- package/src/lib/queryEngine.d.ts +48 -0
- package/src/lib/queryEngine.js +461 -0
- package/src/lib/queryEngine.js.map +1 -0
- package/src/lib/schemaValidation.d.ts +80 -0
- package/src/lib/schemaValidation.js +353 -0
- package/src/lib/schemaValidation.js.map +1 -0
- package/src/lib/transaction.d.ts +7 -0
- package/src/lib/transaction.js +12 -0
- package/src/lib/transaction.js.map +1 -0
- package/src/lib/types.d.ts +360 -0
- package/src/lib/types.js +6 -0
- package/src/lib/types.js.map +1 -0
- package/src/lib/updateEngine.d.ts +7 -0
- package/src/lib/updateEngine.js +13 -0
- package/src/lib/updateEngine.js.map +1 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PersistentHeadRegistry = exports.InMemoryHeadRegistry = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
// Re-export InMemoryHeadRegistry from brightchain-lib for backward compatibility
|
|
7
|
+
var inMemoryHeadRegistry_1 = require("@brightchain/brightchain-lib/lib/db/inMemoryHeadRegistry");
|
|
8
|
+
Object.defineProperty(exports, "InMemoryHeadRegistry", { enumerable: true, get: function () { return inMemoryHeadRegistry_1.InMemoryHeadRegistry; } });
|
|
9
|
+
/**
|
|
10
|
+
* Persistent HeadRegistry that writes through to a JSON file on disk.
|
|
11
|
+
*
|
|
12
|
+
* The in-memory Map is the primary read path; disk is the persistence layer.
|
|
13
|
+
* Every mutation (setHead, removeHead, clear) writes the full JSON to disk
|
|
14
|
+
* before returning, ensuring durability across process restarts.
|
|
15
|
+
*
|
|
16
|
+
* File-level locking via `fs.open` with exclusive create prevents concurrent
|
|
17
|
+
* write corruption when multiple processes share the same data directory.
|
|
18
|
+
*
|
|
19
|
+
* If the persisted file is missing or corrupt on load(), the registry starts
|
|
20
|
+
* empty and logs a warning rather than crashing.
|
|
21
|
+
*/
|
|
22
|
+
class PersistentHeadRegistry {
|
|
23
|
+
constructor(options) {
|
|
24
|
+
this.heads = new Map();
|
|
25
|
+
this.timestamps = new Map();
|
|
26
|
+
this.deferred = [];
|
|
27
|
+
this.filePath = (0, path_1.join)(options.dataDir, options.fileName ?? 'head-registry.json');
|
|
28
|
+
this.lockPath = this.filePath + '.lock';
|
|
29
|
+
}
|
|
30
|
+
makeKey(dbName, collectionName) {
|
|
31
|
+
return `${dbName}:${collectionName}`;
|
|
32
|
+
}
|
|
33
|
+
getHead(dbName, collectionName) {
|
|
34
|
+
return this.heads.get(this.makeKey(dbName, collectionName));
|
|
35
|
+
}
|
|
36
|
+
async setHead(dbName, collectionName, blockId) {
|
|
37
|
+
const key = this.makeKey(dbName, collectionName);
|
|
38
|
+
this.heads.set(key, blockId);
|
|
39
|
+
this.timestamps.set(key, new Date());
|
|
40
|
+
await this.writeToDisk();
|
|
41
|
+
}
|
|
42
|
+
async removeHead(dbName, collectionName) {
|
|
43
|
+
const key = this.makeKey(dbName, collectionName);
|
|
44
|
+
this.heads.delete(key);
|
|
45
|
+
this.timestamps.delete(key);
|
|
46
|
+
await this.writeToDisk();
|
|
47
|
+
}
|
|
48
|
+
async clear() {
|
|
49
|
+
this.heads.clear();
|
|
50
|
+
this.timestamps.clear();
|
|
51
|
+
this.deferred.length = 0;
|
|
52
|
+
await this.writeToDisk();
|
|
53
|
+
}
|
|
54
|
+
async load() {
|
|
55
|
+
try {
|
|
56
|
+
const content = await fs_1.promises.readFile(this.filePath, 'utf-8');
|
|
57
|
+
const parsed = JSON.parse(content);
|
|
58
|
+
if (parsed === null ||
|
|
59
|
+
typeof parsed !== 'object' ||
|
|
60
|
+
Array.isArray(parsed)) {
|
|
61
|
+
console.warn(`[PersistentHeadRegistry] Registry file is not a JSON object, starting empty: ${this.filePath}`);
|
|
62
|
+
this.heads.clear();
|
|
63
|
+
this.timestamps.clear();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
this.heads.clear();
|
|
67
|
+
this.timestamps.clear();
|
|
68
|
+
const record = parsed;
|
|
69
|
+
for (const [key, value] of Object.entries(record)) {
|
|
70
|
+
if (typeof value === 'string') {
|
|
71
|
+
// Legacy format: plain blockId string, no timestamp
|
|
72
|
+
this.heads.set(key, value);
|
|
73
|
+
}
|
|
74
|
+
else if (value !== null &&
|
|
75
|
+
typeof value === 'object' &&
|
|
76
|
+
'blockId' in value &&
|
|
77
|
+
typeof value['blockId'] === 'string') {
|
|
78
|
+
// New format: { blockId, timestamp }
|
|
79
|
+
const entry = value;
|
|
80
|
+
this.heads.set(key, entry['blockId']);
|
|
81
|
+
if (typeof entry['timestamp'] === 'string') {
|
|
82
|
+
this.timestamps.set(key, new Date(entry['timestamp']));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
const error = err;
|
|
89
|
+
if (error.code === 'ENOENT') {
|
|
90
|
+
// File doesn't exist yet — start empty (not a warning)
|
|
91
|
+
this.heads.clear();
|
|
92
|
+
this.timestamps.clear();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
console.warn(`[PersistentHeadRegistry] Failed to load registry file, starting empty: ${this.filePath}`, error.message ?? error);
|
|
96
|
+
this.heads.clear();
|
|
97
|
+
this.timestamps.clear();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
getAllHeads() {
|
|
101
|
+
return new Map(this.heads);
|
|
102
|
+
}
|
|
103
|
+
getHeadTimestamp(dbName, collectionName) {
|
|
104
|
+
return this.timestamps.get(this.makeKey(dbName, collectionName));
|
|
105
|
+
}
|
|
106
|
+
async mergeHeadUpdate(dbName, collectionName, blockId, timestamp) {
|
|
107
|
+
const key = this.makeKey(dbName, collectionName);
|
|
108
|
+
const localTimestamp = this.timestamps.get(key);
|
|
109
|
+
// If no local head exists, or the remote timestamp is strictly newer, apply
|
|
110
|
+
if (!localTimestamp || timestamp.getTime() > localTimestamp.getTime()) {
|
|
111
|
+
this.heads.set(key, blockId);
|
|
112
|
+
this.timestamps.set(key, timestamp);
|
|
113
|
+
await this.writeToDisk();
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
// Local is same age or newer — reject
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
async deferHeadUpdate(dbName, collectionName, blockId, timestamp) {
|
|
120
|
+
this.deferred.push({ dbName, collectionName, blockId, timestamp });
|
|
121
|
+
}
|
|
122
|
+
async applyDeferredUpdates(blockId) {
|
|
123
|
+
let applied = 0;
|
|
124
|
+
const remaining = [];
|
|
125
|
+
for (const update of this.deferred) {
|
|
126
|
+
if (update.blockId === blockId) {
|
|
127
|
+
await this.mergeHeadUpdate(update.dbName, update.collectionName, update.blockId, update.timestamp);
|
|
128
|
+
applied++;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
remaining.push(update);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
this.deferred.length = 0;
|
|
135
|
+
this.deferred.push(...remaining);
|
|
136
|
+
return applied;
|
|
137
|
+
}
|
|
138
|
+
getDeferredUpdates() {
|
|
139
|
+
return [...this.deferred];
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Write the current in-memory state to disk with file-level locking.
|
|
143
|
+
*
|
|
144
|
+
* Locking strategy: create a `.lock` file with exclusive flag (`wx`).
|
|
145
|
+
* If the lock file already exists, retry with a short delay.
|
|
146
|
+
* The lock file is always removed after writing, even on error.
|
|
147
|
+
*/
|
|
148
|
+
async writeToDisk() {
|
|
149
|
+
await this.acquireLock();
|
|
150
|
+
try {
|
|
151
|
+
const data = {};
|
|
152
|
+
for (const [key, value] of this.heads) {
|
|
153
|
+
const ts = this.timestamps.get(key);
|
|
154
|
+
data[key] = {
|
|
155
|
+
blockId: value,
|
|
156
|
+
...(ts ? { timestamp: ts.toISOString() } : {}),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const json = JSON.stringify(data, null, 2);
|
|
160
|
+
// Write to a temp file then rename for atomicity
|
|
161
|
+
const tmpPath = this.filePath + '.tmp';
|
|
162
|
+
await fs_1.promises.writeFile(tmpPath, json, 'utf-8');
|
|
163
|
+
await fs_1.promises.rename(tmpPath, this.filePath);
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
await this.releaseLock();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async acquireLock(maxRetries = 50, retryDelayMs = 20) {
|
|
170
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
171
|
+
try {
|
|
172
|
+
const handle = await fs_1.promises.open(this.lockPath, 'wx');
|
|
173
|
+
await handle.close();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
const error = err;
|
|
178
|
+
if (error.code === 'EEXIST') {
|
|
179
|
+
// Lock held by another writer — wait and retry
|
|
180
|
+
await this.delay(retryDelayMs);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// If we exhausted retries, force-remove stale lock and try once more
|
|
187
|
+
try {
|
|
188
|
+
await fs_1.promises.unlink(this.lockPath);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// Ignore — lock may have been released between check and unlink
|
|
192
|
+
}
|
|
193
|
+
const handle = await fs_1.promises.open(this.lockPath, 'wx');
|
|
194
|
+
await handle.close();
|
|
195
|
+
}
|
|
196
|
+
async releaseLock() {
|
|
197
|
+
try {
|
|
198
|
+
await fs_1.promises.unlink(this.lockPath);
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// Ignore — lock file may already be gone
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
delay(ms) {
|
|
205
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Factory method that creates a PersistentHeadRegistry for the given directory.
|
|
209
|
+
* Useful for tests that need an isolated persistent registry.
|
|
210
|
+
*/
|
|
211
|
+
static create(options) {
|
|
212
|
+
return new PersistentHeadRegistry(options);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
exports.PersistentHeadRegistry = PersistentHeadRegistry;
|
|
216
|
+
//# sourceMappingURL=headRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headRegistry.js","sourceRoot":"","sources":["../../../../brightchain-db/src/lib/headRegistry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory index engine for fast document lookups.
|
|
3
|
+
*
|
|
4
|
+
* This module has been relocated to @brightchain/brightchain-lib.
|
|
5
|
+
* Re-exported here for backward compatibility.
|
|
6
|
+
*/
|
|
7
|
+
export { CollectionIndex, DuplicateKeyError, IndexManager, } from '@brightchain/brightchain-lib/lib/db/indexing';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* In-memory index engine for fast document lookups.
|
|
4
|
+
*
|
|
5
|
+
* This module has been relocated to @brightchain/brightchain-lib.
|
|
6
|
+
* Re-exported here for backward compatibility.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.IndexManager = exports.DuplicateKeyError = exports.CollectionIndex = void 0;
|
|
10
|
+
var indexing_1 = require("@brightchain/brightchain-lib/lib/db/indexing");
|
|
11
|
+
Object.defineProperty(exports, "CollectionIndex", { enumerable: true, get: function () { return indexing_1.CollectionIndex; } });
|
|
12
|
+
Object.defineProperty(exports, "DuplicateKeyError", { enumerable: true, get: function () { return indexing_1.DuplicateKeyError; } });
|
|
13
|
+
Object.defineProperty(exports, "IndexManager", { enumerable: true, get: function () { return indexing_1.IndexManager; } });
|
|
14
|
+
//# sourceMappingURL=indexing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexing.js","sourceRoot":"","sources":["../../../../brightchain-db/src/lib/indexing.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEH,yEAIsD;AAHpD,2GAAA,eAAe,OAAA;AACf,6GAAA,iBAAiB,OAAA;AACjB,wGAAA,YAAY,OAAA"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Model — a typed wrapper around Collection that automatically
|
|
3
|
+
* serializes on writes and rehydrates on reads.
|
|
4
|
+
*
|
|
5
|
+
* Analogous to Mongoose's Model, but with explicit type boundaries:
|
|
6
|
+
* - TStored: the shape of documents in the database (all strings, extends BsonDocument)
|
|
7
|
+
* - TTyped: the shape your application code works with (typed IDs, Date objects, etc.)
|
|
8
|
+
*
|
|
9
|
+
* Conversion between the two is handled by an IHydrationSchema from brightchain-lib.
|
|
10
|
+
* Schema validation is applied automatically before every write.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* const roleModel = new Model(db.collection<IStoredRole>('roles'), {
|
|
14
|
+
* schema: ROLE_SCHEMA,
|
|
15
|
+
* hydration: { hydrate: rehydrateRole, dehydrate: serializeRole },
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* // Reads return TTyped
|
|
19
|
+
* const role = await roleModel.findOne({ name: 'Admin' });
|
|
20
|
+
*
|
|
21
|
+
* // Writes accept TTyped, serialize automatically
|
|
22
|
+
* await roleModel.insertOne(typedRoleObject);
|
|
23
|
+
*/
|
|
24
|
+
import type { IHydrationSchema } from '@brightchain/brightchain-lib';
|
|
25
|
+
import type { Collection } from './collection';
|
|
26
|
+
import { Cursor } from './cursor';
|
|
27
|
+
import type { ValidationFieldError } from './errors';
|
|
28
|
+
import type { CollectionSchema } from './schemaValidation';
|
|
29
|
+
import type { AggregationStage, BsonDocument, DeleteResult, DocumentId, FilterQuery, FindOptions, InsertManyResult, InsertOneResult, ReplaceResult, SortSpec, UpdateOptions, UpdateQuery, UpdateResult, WriteOptions } from './types';
|
|
30
|
+
/**
|
|
31
|
+
* A cursor that wraps a raw Cursor<TStored> and maps results through
|
|
32
|
+
* a hydration function, yielding TTyped documents.
|
|
33
|
+
*
|
|
34
|
+
* Preserves the chainable sort/skip/limit/project API of the underlying cursor.
|
|
35
|
+
*/
|
|
36
|
+
export declare class TypedCursor<TStored extends BsonDocument, TTyped> {
|
|
37
|
+
private readonly _inner;
|
|
38
|
+
private readonly _hydrate;
|
|
39
|
+
constructor(_inner: Cursor<TStored>, _hydrate: (stored: TStored) => TTyped);
|
|
40
|
+
sort(spec: SortSpec<TStored>): this;
|
|
41
|
+
skip(n: number): this;
|
|
42
|
+
limit(n: number): this;
|
|
43
|
+
toArray(): Promise<TTyped[]>;
|
|
44
|
+
count(): Promise<number>;
|
|
45
|
+
next(): Promise<TTyped | null>;
|
|
46
|
+
hasNext(): Promise<boolean>;
|
|
47
|
+
forEach(fn: (doc: TTyped) => void | Promise<void>): Promise<void>;
|
|
48
|
+
map<U>(fn: (doc: TTyped) => U): Promise<U[]>;
|
|
49
|
+
/** Make the cursor thenable so it can be awaited directly. */
|
|
50
|
+
then<TResult1 = TTyped[], TResult2 = never>(onfulfilled?: ((value: TTyped[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
51
|
+
}
|
|
52
|
+
export interface ModelOptions<TStored extends BsonDocument, TTyped> {
|
|
53
|
+
/** Collection schema for validation before writes. */
|
|
54
|
+
schema?: CollectionSchema;
|
|
55
|
+
/** Hydration schema for storage ↔ typed conversion. */
|
|
56
|
+
hydration: IHydrationSchema<TStored, TTyped>;
|
|
57
|
+
/**
|
|
58
|
+
* Collection name — used for validation error messages.
|
|
59
|
+
* Defaults to the schema name if a schema is provided.
|
|
60
|
+
*/
|
|
61
|
+
collectionName?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* A typed model wrapping a raw Collection<TStored>.
|
|
65
|
+
*
|
|
66
|
+
* - Reads (find, findOne, findById) return TTyped after hydration.
|
|
67
|
+
* - Writes (insertOne, insertMany, updateOne, etc.) accept TTyped,
|
|
68
|
+
* dehydrate to TStored, validate against the schema, then delegate
|
|
69
|
+
* to the underlying collection.
|
|
70
|
+
* - The raw collection is accessible via `.collection` for escape-hatch
|
|
71
|
+
* scenarios (bulk ops, aggregation, raw queries).
|
|
72
|
+
*/
|
|
73
|
+
export declare class Model<TStored extends BsonDocument, TTyped> {
|
|
74
|
+
/** The underlying raw collection. */
|
|
75
|
+
readonly collection: Collection<TStored>;
|
|
76
|
+
private readonly _hydrate;
|
|
77
|
+
private readonly _dehydrate;
|
|
78
|
+
private readonly _schema?;
|
|
79
|
+
private readonly _collectionName;
|
|
80
|
+
constructor(collection: Collection<TStored>, options: ModelOptions<TStored, TTyped>);
|
|
81
|
+
/**
|
|
82
|
+
* Validate a typed document against the schema.
|
|
83
|
+
* Returns an empty array if valid, or field errors if not.
|
|
84
|
+
*/
|
|
85
|
+
validate(typed: TTyped): ValidationFieldError[];
|
|
86
|
+
/**
|
|
87
|
+
* Validate and throw if invalid.
|
|
88
|
+
*/
|
|
89
|
+
private _validateOrThrow;
|
|
90
|
+
/**
|
|
91
|
+
* Find a single document matching the filter.
|
|
92
|
+
* The filter operates on stored field names/values.
|
|
93
|
+
*/
|
|
94
|
+
findOne(filter?: FilterQuery<TStored>, options?: FindOptions<TStored>): Promise<TTyped | null>;
|
|
95
|
+
/**
|
|
96
|
+
* Find all documents matching the filter.
|
|
97
|
+
* Returns a TypedCursor that hydrates results lazily.
|
|
98
|
+
*/
|
|
99
|
+
find(filter?: FilterQuery<TStored>, options?: FindOptions<TStored>): TypedCursor<TStored, TTyped>;
|
|
100
|
+
/**
|
|
101
|
+
* Find a document by its _id.
|
|
102
|
+
*/
|
|
103
|
+
findById(id: DocumentId): Promise<TTyped | null>;
|
|
104
|
+
/**
|
|
105
|
+
* Insert a single typed document.
|
|
106
|
+
* Dehydrates to stored form, validates, then inserts.
|
|
107
|
+
*/
|
|
108
|
+
insertOne(typed: TTyped, options?: WriteOptions): Promise<InsertOneResult>;
|
|
109
|
+
/**
|
|
110
|
+
* Insert multiple typed documents.
|
|
111
|
+
* Dehydrates and validates each before inserting.
|
|
112
|
+
*/
|
|
113
|
+
insertMany(docs: TTyped[], options?: WriteOptions): Promise<InsertManyResult>;
|
|
114
|
+
/**
|
|
115
|
+
* Update the first document matching the filter.
|
|
116
|
+
* The filter and update operate on stored field names/values.
|
|
117
|
+
*/
|
|
118
|
+
updateOne(filter: FilterQuery<TStored>, update: UpdateQuery<TStored>, options?: UpdateOptions): Promise<UpdateResult>;
|
|
119
|
+
/**
|
|
120
|
+
* Update all documents matching the filter.
|
|
121
|
+
*/
|
|
122
|
+
updateMany(filter: FilterQuery<TStored>, update: UpdateQuery<TStored>, options?: UpdateOptions): Promise<UpdateResult>;
|
|
123
|
+
/**
|
|
124
|
+
* Delete the first document matching the filter.
|
|
125
|
+
*/
|
|
126
|
+
deleteOne(filter: FilterQuery<TStored>, options?: WriteOptions): Promise<DeleteResult>;
|
|
127
|
+
/**
|
|
128
|
+
* Delete all documents matching the filter.
|
|
129
|
+
*/
|
|
130
|
+
deleteMany(filter: FilterQuery<TStored>, options?: WriteOptions): Promise<DeleteResult>;
|
|
131
|
+
/**
|
|
132
|
+
* Replace a single document matching the filter with a typed replacement.
|
|
133
|
+
* Dehydrates the replacement to stored form, validates, then delegates.
|
|
134
|
+
*/
|
|
135
|
+
replaceOne(filter: FilterQuery<TStored>, replacement: TTyped, options?: UpdateOptions): Promise<ReplaceResult>;
|
|
136
|
+
/**
|
|
137
|
+
* Run an aggregation pipeline on the underlying collection.
|
|
138
|
+
* Returns raw BsonDocument results (aggregation output shapes are
|
|
139
|
+
* typically different from the collection's document type).
|
|
140
|
+
*/
|
|
141
|
+
aggregate(pipeline: AggregationStage[]): Promise<BsonDocument[]>;
|
|
142
|
+
/**
|
|
143
|
+
* Return distinct values for a stored field.
|
|
144
|
+
*/
|
|
145
|
+
distinct<K extends keyof TStored>(field: K, filter?: FilterQuery<TStored>): Promise<Array<TStored[K]>>;
|
|
146
|
+
/**
|
|
147
|
+
* Count documents matching the filter.
|
|
148
|
+
*/
|
|
149
|
+
countDocuments(filter?: FilterQuery<TStored>): Promise<number>;
|
|
150
|
+
/**
|
|
151
|
+
* Estimated total document count.
|
|
152
|
+
*/
|
|
153
|
+
estimatedDocumentCount(): Promise<number>;
|
|
154
|
+
/**
|
|
155
|
+
* Check if a document matching the filter exists.
|
|
156
|
+
*/
|
|
157
|
+
exists(filter: FilterQuery<TStored>): Promise<boolean>;
|
|
158
|
+
/** Convert a stored document to its typed form. */
|
|
159
|
+
hydrate(stored: TStored): TTyped;
|
|
160
|
+
/** Convert a typed document to its stored form. */
|
|
161
|
+
dehydrate(typed: TTyped): TStored;
|
|
162
|
+
}
|
package/src/lib/model.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Model — a typed wrapper around Collection that automatically
|
|
4
|
+
* serializes on writes and rehydrates on reads.
|
|
5
|
+
*
|
|
6
|
+
* Analogous to Mongoose's Model, but with explicit type boundaries:
|
|
7
|
+
* - TStored: the shape of documents in the database (all strings, extends BsonDocument)
|
|
8
|
+
* - TTyped: the shape your application code works with (typed IDs, Date objects, etc.)
|
|
9
|
+
*
|
|
10
|
+
* Conversion between the two is handled by an IHydrationSchema from brightchain-lib.
|
|
11
|
+
* Schema validation is applied automatically before every write.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* const roleModel = new Model(db.collection<IStoredRole>('roles'), {
|
|
15
|
+
* schema: ROLE_SCHEMA,
|
|
16
|
+
* hydration: { hydrate: rehydrateRole, dehydrate: serializeRole },
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // Reads return TTyped
|
|
20
|
+
* const role = await roleModel.findOne({ name: 'Admin' });
|
|
21
|
+
*
|
|
22
|
+
* // Writes accept TTyped, serialize automatically
|
|
23
|
+
* await roleModel.insertOne(typedRoleObject);
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.Model = exports.TypedCursor = void 0;
|
|
27
|
+
const errors_1 = require("./errors");
|
|
28
|
+
const schemaValidation_1 = require("./schemaValidation");
|
|
29
|
+
// ─── TypedCursor ─────────────────────────────────────────────────────────────
|
|
30
|
+
/**
|
|
31
|
+
* A cursor that wraps a raw Cursor<TStored> and maps results through
|
|
32
|
+
* a hydration function, yielding TTyped documents.
|
|
33
|
+
*
|
|
34
|
+
* Preserves the chainable sort/skip/limit/project API of the underlying cursor.
|
|
35
|
+
*/
|
|
36
|
+
class TypedCursor {
|
|
37
|
+
constructor(_inner, _hydrate) {
|
|
38
|
+
this._inner = _inner;
|
|
39
|
+
this._hydrate = _hydrate;
|
|
40
|
+
}
|
|
41
|
+
sort(spec) {
|
|
42
|
+
this._inner.sort(spec);
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
skip(n) {
|
|
46
|
+
this._inner.skip(n);
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
limit(n) {
|
|
50
|
+
this._inner.limit(n);
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
async toArray() {
|
|
54
|
+
const docs = await this._inner.toArray();
|
|
55
|
+
return docs.map(this._hydrate);
|
|
56
|
+
}
|
|
57
|
+
async count() {
|
|
58
|
+
return this._inner.count();
|
|
59
|
+
}
|
|
60
|
+
async next() {
|
|
61
|
+
const doc = await this._inner.next();
|
|
62
|
+
return doc ? this._hydrate(doc) : null;
|
|
63
|
+
}
|
|
64
|
+
async hasNext() {
|
|
65
|
+
return this._inner.hasNext();
|
|
66
|
+
}
|
|
67
|
+
async forEach(fn) {
|
|
68
|
+
const docs = await this.toArray();
|
|
69
|
+
for (const doc of docs) {
|
|
70
|
+
await fn(doc);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async map(fn) {
|
|
74
|
+
const docs = await this.toArray();
|
|
75
|
+
return docs.map(fn);
|
|
76
|
+
}
|
|
77
|
+
/** Make the cursor thenable so it can be awaited directly. */
|
|
78
|
+
then(onfulfilled, onrejected) {
|
|
79
|
+
return this.toArray().then(onfulfilled, onrejected);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.TypedCursor = TypedCursor;
|
|
83
|
+
// ─── Model ───────────────────────────────────────────────────────────────────
|
|
84
|
+
/**
|
|
85
|
+
* A typed model wrapping a raw Collection<TStored>.
|
|
86
|
+
*
|
|
87
|
+
* - Reads (find, findOne, findById) return TTyped after hydration.
|
|
88
|
+
* - Writes (insertOne, insertMany, updateOne, etc.) accept TTyped,
|
|
89
|
+
* dehydrate to TStored, validate against the schema, then delegate
|
|
90
|
+
* to the underlying collection.
|
|
91
|
+
* - The raw collection is accessible via `.collection` for escape-hatch
|
|
92
|
+
* scenarios (bulk ops, aggregation, raw queries).
|
|
93
|
+
*/
|
|
94
|
+
class Model {
|
|
95
|
+
constructor(collection, options) {
|
|
96
|
+
this.collection = collection;
|
|
97
|
+
this._hydrate = options.hydration.hydrate;
|
|
98
|
+
this._dehydrate = options.hydration.dehydrate;
|
|
99
|
+
this._schema = options.schema;
|
|
100
|
+
this._collectionName =
|
|
101
|
+
options.collectionName ?? options.schema?.name ?? 'unknown';
|
|
102
|
+
// Wire the schema into the collection for its own validation
|
|
103
|
+
if (this._schema) {
|
|
104
|
+
collection.setSchema(this._schema);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// ── Validation ──────────────────────────────────────────────────────────
|
|
108
|
+
/**
|
|
109
|
+
* Validate a typed document against the schema.
|
|
110
|
+
* Returns an empty array if valid, or field errors if not.
|
|
111
|
+
*/
|
|
112
|
+
validate(typed) {
|
|
113
|
+
if (!this._schema)
|
|
114
|
+
return [];
|
|
115
|
+
const stored = this._dehydrate(typed);
|
|
116
|
+
try {
|
|
117
|
+
(0, schemaValidation_1.validateDocument)(stored, this._schema, this._collectionName);
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
if (err instanceof errors_1.ValidationError) {
|
|
122
|
+
return err.validationErrors;
|
|
123
|
+
}
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Validate and throw if invalid.
|
|
129
|
+
*/
|
|
130
|
+
_validateOrThrow(stored) {
|
|
131
|
+
if (!this._schema)
|
|
132
|
+
return;
|
|
133
|
+
(0, schemaValidation_1.validateDocument)(stored, this._schema, this._collectionName);
|
|
134
|
+
}
|
|
135
|
+
// ── Reads (return TTyped) ───────────────────────────────────────────────
|
|
136
|
+
/**
|
|
137
|
+
* Find a single document matching the filter.
|
|
138
|
+
* The filter operates on stored field names/values.
|
|
139
|
+
*/
|
|
140
|
+
async findOne(filter, options) {
|
|
141
|
+
const stored = await this.collection.findOne(filter, options);
|
|
142
|
+
return stored ? this._hydrate(stored) : null;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Find all documents matching the filter.
|
|
146
|
+
* Returns a TypedCursor that hydrates results lazily.
|
|
147
|
+
*/
|
|
148
|
+
find(filter, options) {
|
|
149
|
+
const cursor = this.collection.find(filter, options);
|
|
150
|
+
return new TypedCursor(cursor, this._hydrate);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Find a document by its _id.
|
|
154
|
+
*/
|
|
155
|
+
async findById(id) {
|
|
156
|
+
const stored = await this.collection.findById(id);
|
|
157
|
+
return stored ? this._hydrate(stored) : null;
|
|
158
|
+
}
|
|
159
|
+
// ── Writes (accept TTyped, serialize + validate automatically) ──────────
|
|
160
|
+
/**
|
|
161
|
+
* Insert a single typed document.
|
|
162
|
+
* Dehydrates to stored form, validates, then inserts.
|
|
163
|
+
*/
|
|
164
|
+
async insertOne(typed, options) {
|
|
165
|
+
const stored = this._dehydrate(typed);
|
|
166
|
+
this._validateOrThrow(stored);
|
|
167
|
+
return this.collection.insertOne(stored, options);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Insert multiple typed documents.
|
|
171
|
+
* Dehydrates and validates each before inserting.
|
|
172
|
+
*/
|
|
173
|
+
async insertMany(docs, options) {
|
|
174
|
+
const storedDocs = docs.map((d) => {
|
|
175
|
+
const stored = this._dehydrate(d);
|
|
176
|
+
this._validateOrThrow(stored);
|
|
177
|
+
return stored;
|
|
178
|
+
});
|
|
179
|
+
return this.collection.insertMany(storedDocs, options);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Update the first document matching the filter.
|
|
183
|
+
* The filter and update operate on stored field names/values.
|
|
184
|
+
*/
|
|
185
|
+
async updateOne(filter, update, options) {
|
|
186
|
+
return this.collection.updateOne(filter, update, options);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Update all documents matching the filter.
|
|
190
|
+
*/
|
|
191
|
+
async updateMany(filter, update, options) {
|
|
192
|
+
return this.collection.updateMany(filter, update, options);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Delete the first document matching the filter.
|
|
196
|
+
*/
|
|
197
|
+
async deleteOne(filter, options) {
|
|
198
|
+
return this.collection.deleteOne(filter, options);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Delete all documents matching the filter.
|
|
202
|
+
*/
|
|
203
|
+
async deleteMany(filter, options) {
|
|
204
|
+
return this.collection.deleteMany(filter, options);
|
|
205
|
+
}
|
|
206
|
+
// ── Convenience ─────────────────────────────────────────────────────────
|
|
207
|
+
/**
|
|
208
|
+
* Replace a single document matching the filter with a typed replacement.
|
|
209
|
+
* Dehydrates the replacement to stored form, validates, then delegates.
|
|
210
|
+
*/
|
|
211
|
+
async replaceOne(filter, replacement, options) {
|
|
212
|
+
const stored = this._dehydrate(replacement);
|
|
213
|
+
this._validateOrThrow(stored);
|
|
214
|
+
return this.collection.replaceOne(filter, stored, options);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Run an aggregation pipeline on the underlying collection.
|
|
218
|
+
* Returns raw BsonDocument results (aggregation output shapes are
|
|
219
|
+
* typically different from the collection's document type).
|
|
220
|
+
*/
|
|
221
|
+
async aggregate(pipeline) {
|
|
222
|
+
return this.collection.aggregate(pipeline);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Return distinct values for a stored field.
|
|
226
|
+
*/
|
|
227
|
+
async distinct(field, filter) {
|
|
228
|
+
return this.collection.distinct(field, filter);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Count documents matching the filter.
|
|
232
|
+
*/
|
|
233
|
+
async countDocuments(filter) {
|
|
234
|
+
return this.collection.countDocuments(filter);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Estimated total document count.
|
|
238
|
+
*/
|
|
239
|
+
async estimatedDocumentCount() {
|
|
240
|
+
return this.collection.estimatedDocumentCount();
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Check if a document matching the filter exists.
|
|
244
|
+
*/
|
|
245
|
+
async exists(filter) {
|
|
246
|
+
const doc = await this.collection.findOne(filter);
|
|
247
|
+
return doc !== null;
|
|
248
|
+
}
|
|
249
|
+
// ── Conversion helpers (public for escape-hatch scenarios) ──────────────
|
|
250
|
+
/** Convert a stored document to its typed form. */
|
|
251
|
+
hydrate(stored) {
|
|
252
|
+
return this._hydrate(stored);
|
|
253
|
+
}
|
|
254
|
+
/** Convert a typed document to its stored form. */
|
|
255
|
+
dehydrate(typed) {
|
|
256
|
+
return this._dehydrate(typed);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
exports.Model = Model;
|
|
260
|
+
//# sourceMappingURL=model.js.map
|