@procedures/mongo 0.0.0-stage → 0.2.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.
Files changed (150) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1322 -2
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/connection.d.ts +93 -0
  6. package/dist/connection.d.ts.map +1 -0
  7. package/dist/connection.js +147 -0
  8. package/dist/connection.js.map +1 -0
  9. package/dist/index.d.ts +34 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +45 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/module.d.ts +26 -0
  14. package/dist/module.d.ts.map +1 -0
  15. package/dist/module.js +66 -0
  16. package/dist/module.js.map +1 -0
  17. package/dist/procedures/collections.create.d.ts +21 -0
  18. package/dist/procedures/collections.create.d.ts.map +1 -0
  19. package/dist/procedures/collections.create.js +46 -0
  20. package/dist/procedures/collections.create.js.map +1 -0
  21. package/dist/procedures/collections.drop.d.ts +20 -0
  22. package/dist/procedures/collections.drop.d.ts.map +1 -0
  23. package/dist/procedures/collections.drop.js +40 -0
  24. package/dist/procedures/collections.drop.js.map +1 -0
  25. package/dist/procedures/collections.list.d.ts +17 -0
  26. package/dist/procedures/collections.list.d.ts.map +1 -0
  27. package/dist/procedures/collections.list.js +34 -0
  28. package/dist/procedures/collections.list.js.map +1 -0
  29. package/dist/procedures/collections.stats.d.ts +16 -0
  30. package/dist/procedures/collections.stats.d.ts.map +1 -0
  31. package/dist/procedures/collections.stats.js +41 -0
  32. package/dist/procedures/collections.stats.js.map +1 -0
  33. package/dist/procedures/database.info.d.ts +16 -0
  34. package/dist/procedures/database.info.d.ts.map +1 -0
  35. package/dist/procedures/database.info.js +41 -0
  36. package/dist/procedures/database.info.js.map +1 -0
  37. package/dist/procedures/database.ping.d.ts +18 -0
  38. package/dist/procedures/database.ping.d.ts.map +1 -0
  39. package/dist/procedures/database.ping.js +34 -0
  40. package/dist/procedures/database.ping.js.map +1 -0
  41. package/dist/procedures/documents.aggregate.d.ts +20 -0
  42. package/dist/procedures/documents.aggregate.d.ts.map +1 -0
  43. package/dist/procedures/documents.aggregate.js +43 -0
  44. package/dist/procedures/documents.aggregate.js.map +1 -0
  45. package/dist/procedures/documents.count.d.ts +19 -0
  46. package/dist/procedures/documents.count.d.ts.map +1 -0
  47. package/dist/procedures/documents.count.js +32 -0
  48. package/dist/procedures/documents.count.js.map +1 -0
  49. package/dist/procedures/documents.delete.d.ts +18 -0
  50. package/dist/procedures/documents.delete.d.ts.map +1 -0
  51. package/dist/procedures/documents.delete.js +34 -0
  52. package/dist/procedures/documents.delete.js.map +1 -0
  53. package/dist/procedures/documents.find.d.ts +24 -0
  54. package/dist/procedures/documents.find.d.ts.map +1 -0
  55. package/dist/procedures/documents.find.js +106 -0
  56. package/dist/procedures/documents.find.js.map +1 -0
  57. package/dist/procedures/documents.get.d.ts +21 -0
  58. package/dist/procedures/documents.get.d.ts.map +1 -0
  59. package/dist/procedures/documents.get.js +57 -0
  60. package/dist/procedures/documents.get.js.map +1 -0
  61. package/dist/procedures/documents.insert.d.ts +22 -0
  62. package/dist/procedures/documents.insert.d.ts.map +1 -0
  63. package/dist/procedures/documents.insert.js +58 -0
  64. package/dist/procedures/documents.insert.js.map +1 -0
  65. package/dist/procedures/documents.update.d.ts +27 -0
  66. package/dist/procedures/documents.update.d.ts.map +1 -0
  67. package/dist/procedures/documents.update.js +64 -0
  68. package/dist/procedures/documents.update.js.map +1 -0
  69. package/dist/procedures/index.d.ts +21 -0
  70. package/dist/procedures/index.d.ts.map +1 -0
  71. package/dist/procedures/index.js +26 -0
  72. package/dist/procedures/index.js.map +1 -0
  73. package/dist/procedures/indexes.create.d.ts +21 -0
  74. package/dist/procedures/indexes.create.d.ts.map +1 -0
  75. package/dist/procedures/indexes.create.js +48 -0
  76. package/dist/procedures/indexes.create.js.map +1 -0
  77. package/dist/procedures/indexes.drop.d.ts +18 -0
  78. package/dist/procedures/indexes.drop.d.ts.map +1 -0
  79. package/dist/procedures/indexes.drop.js +33 -0
  80. package/dist/procedures/indexes.drop.js.map +1 -0
  81. package/dist/procedures/indexes.list.d.ts +18 -0
  82. package/dist/procedures/indexes.list.d.ts.map +1 -0
  83. package/dist/procedures/indexes.list.js +43 -0
  84. package/dist/procedures/indexes.list.js.map +1 -0
  85. package/dist/procedures/input-schemas.d.ts +45 -0
  86. package/dist/procedures/input-schemas.d.ts.map +1 -0
  87. package/dist/procedures/input-schemas.js +52 -0
  88. package/dist/procedures/input-schemas.js.map +1 -0
  89. package/dist/procedures/schema.d.ts +2 -0
  90. package/dist/procedures/schema.d.ts.map +1 -0
  91. package/dist/procedures/schema.js +2 -0
  92. package/dist/procedures/schema.js.map +1 -0
  93. package/dist/procedures/target.d.ts +44 -0
  94. package/dist/procedures/target.d.ts.map +1 -0
  95. package/dist/procedures/target.js +98 -0
  96. package/dist/procedures/target.js.map +1 -0
  97. package/dist/procedures.types.d.ts +129 -0
  98. package/dist/procedures.types.d.ts.map +1 -0
  99. package/dist/procedures.types.js +24 -0
  100. package/dist/procedures.types.js.map +1 -0
  101. package/dist/register.d.ts +7 -0
  102. package/dist/register.d.ts.map +1 -0
  103. package/dist/register.js +9 -0
  104. package/dist/register.js.map +1 -0
  105. package/dist/scope.d.ts +33 -0
  106. package/dist/scope.d.ts.map +1 -0
  107. package/dist/scope.js +52 -0
  108. package/dist/scope.js.map +1 -0
  109. package/dist/storage/index.d.ts +6 -0
  110. package/dist/storage/index.d.ts.map +1 -0
  111. package/dist/storage/index.js +5 -0
  112. package/dist/storage/index.js.map +1 -0
  113. package/dist/storage/mongo-storage.d.ts +50 -0
  114. package/dist/storage/mongo-storage.d.ts.map +1 -0
  115. package/dist/storage/mongo-storage.js +109 -0
  116. package/dist/storage/mongo-storage.js.map +1 -0
  117. package/dist/types.d.ts +239 -0
  118. package/dist/types.d.ts.map +1 -0
  119. package/dist/types.js +58 -0
  120. package/dist/types.js.map +1 -0
  121. package/package.json +84 -3
  122. package/src/connection.ts +198 -0
  123. package/src/index.ts +159 -0
  124. package/src/module.ts +71 -0
  125. package/src/procedures/collections.create.ts +72 -0
  126. package/src/procedures/collections.drop.ts +66 -0
  127. package/src/procedures/collections.list.ts +56 -0
  128. package/src/procedures/collections.stats.ts +60 -0
  129. package/src/procedures/database.info.ts +61 -0
  130. package/src/procedures/database.ping.ts +57 -0
  131. package/src/procedures/documents.aggregate.ts +72 -0
  132. package/src/procedures/documents.count.ts +55 -0
  133. package/src/procedures/documents.delete.ts +57 -0
  134. package/src/procedures/documents.find.ts +151 -0
  135. package/src/procedures/documents.get.ts +102 -0
  136. package/src/procedures/documents.insert.ts +86 -0
  137. package/src/procedures/documents.update.ts +96 -0
  138. package/src/procedures/index.ts +30 -0
  139. package/src/procedures/indexes.create.ts +74 -0
  140. package/src/procedures/indexes.drop.ts +56 -0
  141. package/src/procedures/indexes.list.ts +67 -0
  142. package/src/procedures/input-schemas.ts +63 -0
  143. package/src/procedures/schema.ts +1 -0
  144. package/src/procedures/target.ts +123 -0
  145. package/src/procedures.types.ts +162 -0
  146. package/src/register.ts +11 -0
  147. package/src/scope.ts +69 -0
  148. package/src/storage/index.ts +6 -0
  149. package/src/storage/mongo-storage.ts +151 -0
  150. package/src/types.ts +337 -0
@@ -0,0 +1,151 @@
1
+ /**
2
+ * MongoDB Storage Implementation
3
+ *
4
+ * Implements CollectionStorage<T> from client-collections, providing MongoDB
5
+ * as a storage backend for the collections framework.
6
+ */
7
+
8
+ import type { Collection, Document, Filter } from "mongodb";
9
+ import type { CollectionStorage, StorageMetadata } from "@procedures/collections";
10
+ import { ensureConnection } from "../connection.js";
11
+
12
+ /**
13
+ * Configuration options for MongoStorage.
14
+ */
15
+ export interface MongoStorageOptions {
16
+ /** MongoDB collection name */
17
+ collection: string;
18
+ }
19
+
20
+ /**
21
+ * MongoDB document with string ID.
22
+ */
23
+ interface MongoDoc<T> extends Document {
24
+ _id: string;
25
+ data: T;
26
+ }
27
+
28
+ /**
29
+ * MongoDB implementation of CollectionStorage.
30
+ *
31
+ * Stores items as documents with the structure:
32
+ * { _id: string, data: T }
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * const storage = new MongoStorage<User>({ collection: "users" });
37
+ * await storage.set("user-1", { name: "Alice", email: "alice@example.com" });
38
+ * const user = await storage.get("user-1");
39
+ * ```
40
+ */
41
+ export class MongoStorage<T> implements CollectionStorage<T> {
42
+ private readonly collectionName: string;
43
+
44
+ constructor(options: MongoStorageOptions) {
45
+ this.collectionName = options.collection;
46
+ }
47
+
48
+ /**
49
+ * The collection, from the current default connection. The storage does not keep it: after a
50
+ * reconnect the old collection belongs to a closed client (deep dive DATA-6).
51
+ */
52
+ private async getCollection(): Promise<Collection<MongoDoc<T>>> {
53
+ const connection = await ensureConnection();
54
+ return connection.getDb().collection<MongoDoc<T>>(this.collectionName);
55
+ }
56
+
57
+ async get(id: string): Promise<T | undefined> {
58
+ const doc = await (await this.getCollection()).findOne({ _id: id } as Filter<MongoDoc<T>>);
59
+ return doc?.data;
60
+ }
61
+
62
+ async getAll(): Promise<T[]> {
63
+ const docs = await (await this.getCollection()).find({}).toArray();
64
+ return docs.map((doc) => doc.data);
65
+ }
66
+
67
+ async find(predicate: (item: T) => boolean): Promise<T[]> {
68
+ const all = await this.getAll();
69
+ return all.filter(predicate);
70
+ }
71
+
72
+ async has(id: string): Promise<boolean> {
73
+ const count = await (await this.getCollection()).countDocuments({ _id: id } as Filter<MongoDoc<T>>, { limit: 1 });
74
+ return count > 0;
75
+ }
76
+
77
+ async size(): Promise<number> {
78
+ return (await this.getCollection()).countDocuments({});
79
+ }
80
+
81
+ async set(id: string, value: T): Promise<void> {
82
+ await (await this.getCollection()).updateOne(
83
+ { _id: id } as Filter<MongoDoc<T>>,
84
+ { $set: { _id: id, data: value } as unknown as Document },
85
+ { upsert: true }
86
+ );
87
+ }
88
+
89
+ async delete(id: string): Promise<boolean> {
90
+ const result = await (await this.getCollection()).deleteOne({ _id: id } as Filter<MongoDoc<T>>);
91
+ return result.deletedCount > 0;
92
+ }
93
+
94
+ async clear(): Promise<void> {
95
+ await (await this.getCollection()).deleteMany({});
96
+ }
97
+
98
+ async setBatch(items: Array<[string, T]>): Promise<void> {
99
+ if (items.length === 0) return;
100
+
101
+ const operations = items.map(([id, value]) => ({
102
+ updateOne: {
103
+ filter: { _id: id } as Filter<MongoDoc<T>>,
104
+ update: { $set: { _id: id, data: value } as unknown as Document },
105
+ upsert: true,
106
+ },
107
+ }));
108
+
109
+ await (await this.getCollection()).bulkWrite(operations);
110
+ }
111
+
112
+ async deleteBatch(ids: string[]): Promise<number> {
113
+ if (ids.length === 0) return 0;
114
+
115
+ const result = await (await this.getCollection()).deleteMany({
116
+ _id: { $in: ids },
117
+ } as Filter<MongoDoc<T>>);
118
+
119
+ return result.deletedCount;
120
+ }
121
+
122
+ async getBatch(ids: string[]): Promise<Map<string, T>> {
123
+ if (ids.length === 0) return new Map();
124
+
125
+ const docs = await (await this.getCollection())
126
+ .find({ _id: { $in: ids } } as Filter<MongoDoc<T>>)
127
+ .toArray();
128
+
129
+ const result = new Map<string, T>();
130
+ for (const doc of docs) {
131
+ result.set(String(doc._id), doc.data);
132
+ }
133
+ return result;
134
+ }
135
+
136
+ async close(): Promise<void> {
137
+ // The storage keeps no connection state: the connection belongs to the package
138
+ }
139
+
140
+ async getMetadata(): Promise<StorageMetadata> {
141
+ const size = await this.size();
142
+
143
+ return {
144
+ type: "custom",
145
+ size,
146
+ stats: {
147
+ collectionName: this.collectionName,
148
+ },
149
+ };
150
+ }
151
+ }
package/src/types.ts ADDED
@@ -0,0 +1,337 @@
1
+ /**
2
+ * TypeScript types for client-mongo procedures
3
+ */
4
+
5
+ import { ObjectId } from "mongodb";
6
+ import type { Document, Filter, UpdateFilter } from "mongodb";
7
+
8
+ // =============================================================================
9
+ // Common Types
10
+ // =============================================================================
11
+
12
+ /**
13
+ * Pagination input parameters
14
+ */
15
+ export interface PaginationInput {
16
+ /** Page number (1-indexed, default: 1) */
17
+ page?: number;
18
+ /** Items per page (default: 20, max: 100) */
19
+ limit?: number;
20
+ }
21
+
22
+ /**
23
+ * Pagination output metadata
24
+ */
25
+ export interface PaginationOutput {
26
+ /** Current page number */
27
+ page: number;
28
+ /** Items per page */
29
+ limit: number;
30
+ /** Total number of items */
31
+ total: number;
32
+ /** Total number of pages */
33
+ totalPages: number;
34
+ /** Whether there is a next page */
35
+ hasNext: boolean;
36
+ /** Whether there is a previous page */
37
+ hasPrev: boolean;
38
+ }
39
+
40
+ /**
41
+ * Sort order for queries
42
+ */
43
+ export type SortOrder = 1 | -1;
44
+
45
+ /**
46
+ * Sort specification
47
+ */
48
+ export type SortSpec = Record<string, SortOrder>;
49
+
50
+ // =============================================================================
51
+ // Document Types
52
+ // =============================================================================
53
+
54
+ /**
55
+ * Document ID - can be string or ObjectId.
56
+ * MongoDB supports any type for _id, but string and ObjectId are most common.
57
+ */
58
+ export type DocumentId = string | ObjectId;
59
+
60
+ /**
61
+ * Base document type.
62
+ * Uses MongoDB's Document type which allows any _id via index signature.
63
+ */
64
+ export type BaseDocument = Document;
65
+
66
+ /**
67
+ * Generic document type - use BaseDocument for proper _id typing
68
+ */
69
+ export type MongoDocument = BaseDocument;
70
+
71
+ /**
72
+ * Document query filter.
73
+ * Uses Document for compatibility with MongoDB driver methods.
74
+ */
75
+ export type DocumentQuery = Filter<Document>;
76
+
77
+ /**
78
+ * Document update specification
79
+ */
80
+ export type DocumentUpdate = UpdateFilter<Document>;
81
+
82
+ /**
83
+ * How to interpret a string id when building an `_id` filter.
84
+ *
85
+ * - `"objectId"` — coerce to an ObjectId (falls back to the raw string when the
86
+ * value is not a valid ObjectId).
87
+ * - `"string"` — match the raw string `_id` only.
88
+ * - `"auto"` (default) — when the id is a valid ObjectId, match EITHER an
89
+ * ObjectId `_id` or the raw string `_id`, so documents stored with a
90
+ * string `_id` that happens to be 24-hex (e.g. MongoStorage keys) stay
91
+ * reachable. Otherwise match the string.
92
+ */
93
+ export type IdType = "auto" | "objectId" | "string";
94
+
95
+ /**
96
+ * Build an `_id` filter from a string id.
97
+ *
98
+ * MongoDB supports any `_id` type at runtime. Previously any string that parsed
99
+ * as an ObjectId was silently coerced, making 24-hex *string* `_id`s
100
+ * unreachable. The default `"auto"` mode now queries both forms via `$or` when
101
+ * the id is ambiguous, and `idType` provides an explicit escape hatch.
102
+ */
103
+ export function buildIdFilter(id: string, idType: IdType = "auto"): Document {
104
+ if (idType === "string") {
105
+ return { _id: id };
106
+ }
107
+
108
+ let objectId: ObjectId | null = null;
109
+ try {
110
+ objectId = new ObjectId(id);
111
+ } catch {
112
+ objectId = null;
113
+ }
114
+
115
+ if (idType === "objectId") {
116
+ return { _id: objectId ?? id };
117
+ }
118
+
119
+ // "auto": match both the ObjectId and the raw string when ambiguous.
120
+ if (objectId) {
121
+ return { $or: [{ _id: objectId }, { _id: id }] };
122
+ }
123
+ return { _id: id };
124
+ }
125
+
126
+ // =============================================================================
127
+ // Procedure Metadata Types
128
+ // =============================================================================
129
+
130
+ /**
131
+ * Base metadata for all procedures
132
+ */
133
+ export interface BaseMeta extends Record<string, unknown> {
134
+ /** Override database name */
135
+ database?: string;
136
+ }
137
+
138
+ /**
139
+ * Metadata for collection-scoped procedures
140
+ */
141
+ export interface CollectionMeta extends BaseMeta {
142
+ /** Collection name (required) */
143
+ collection: string;
144
+ }
145
+
146
+ /**
147
+ * Type guard to check if metadata has collection
148
+ */
149
+ export function hasCollection(
150
+ metadata: Record<string, unknown>
151
+ ): metadata is CollectionMeta {
152
+ return typeof metadata["collection"] === "string";
153
+ }
154
+
155
+ /**
156
+ * Type guard to get collection metadata, throws if missing
157
+ */
158
+ export function requireCollection(
159
+ metadata: Record<string, unknown>
160
+ ): CollectionMeta {
161
+ if (!hasCollection(metadata)) {
162
+ throw new Error("collection is required in metadata");
163
+ }
164
+ if (metadata.collection.trim().length === 0) {
165
+ throw new Error("collection must be a non-empty string");
166
+ }
167
+ return metadata;
168
+ }
169
+
170
+ /**
171
+ * Get base metadata (database override).
172
+ * BaseMeta extends Record<string, unknown> so this is type-safe.
173
+ */
174
+ export function getBaseMeta(metadata: Record<string, unknown>): BaseMeta {
175
+ return metadata;
176
+ }
177
+
178
+ // =============================================================================
179
+ // Bulk Operation Types
180
+ // =============================================================================
181
+
182
+ /**
183
+ * Insert operation for bulk write
184
+ */
185
+ export interface BulkInsertOne {
186
+ insertOne: {
187
+ document: Document;
188
+ };
189
+ }
190
+
191
+ /**
192
+ * Update operation for bulk write
193
+ */
194
+ export interface BulkUpdateOne {
195
+ updateOne: {
196
+ filter: DocumentQuery;
197
+ update: DocumentUpdate;
198
+ upsert?: boolean;
199
+ };
200
+ }
201
+
202
+ /**
203
+ * Delete operation for bulk write
204
+ */
205
+ export interface BulkDeleteOne {
206
+ deleteOne: {
207
+ filter: DocumentQuery;
208
+ };
209
+ }
210
+
211
+ /**
212
+ * Bulk operation union type
213
+ */
214
+ export type BulkOperation = BulkInsertOne | BulkUpdateOne | BulkDeleteOne;
215
+
216
+ /**
217
+ * Result of a bulk write operation
218
+ */
219
+ export interface BulkWriteResult {
220
+ acknowledged: boolean;
221
+ insertedCount: number;
222
+ matchedCount: number;
223
+ modifiedCount: number;
224
+ deletedCount: number;
225
+ upsertedCount: number;
226
+ }
227
+
228
+ // =============================================================================
229
+ // Index Types
230
+ // =============================================================================
231
+
232
+ /**
233
+ * Index key specification
234
+ */
235
+ export type IndexSpec = Record<string, 1 | -1 | "text" | "2dsphere">;
236
+
237
+ /**
238
+ * Index creation options
239
+ */
240
+ export interface IndexOptions {
241
+ /** Index name */
242
+ name?: string | undefined;
243
+ /** Unique index */
244
+ unique?: boolean | undefined;
245
+ /** Sparse index */
246
+ sparse?: boolean | undefined;
247
+ /** Build in background */
248
+ background?: boolean | undefined;
249
+ /** TTL in seconds */
250
+ expireAfterSeconds?: number | undefined;
251
+ }
252
+
253
+ /**
254
+ * Index information returned by listIndexes
255
+ */
256
+ export interface IndexInfo {
257
+ name: string;
258
+ /** The fields of the index and their kind. The server also gives kinds that IndexSpec does not name ("hashed", "2d"). */
259
+ key: Record<string, number | string>;
260
+ unique?: boolean | undefined;
261
+ sparse?: boolean | undefined;
262
+ }
263
+
264
+ // =============================================================================
265
+ // Collection Types
266
+ // =============================================================================
267
+
268
+ /**
269
+ * Collection creation options
270
+ */
271
+ export interface CollectionOptions {
272
+ /** Create a capped collection */
273
+ capped?: boolean | undefined;
274
+ /** Max size in bytes (for capped) */
275
+ size?: number | undefined;
276
+ /** Max documents (for capped) */
277
+ max?: number | undefined;
278
+ /** JSON Schema validator */
279
+ validator?: Document | undefined;
280
+ }
281
+
282
+ /**
283
+ * Collection statistics
284
+ */
285
+ export interface CollectionStats {
286
+ /** Number of documents */
287
+ count: number;
288
+ /** Size of documents in bytes */
289
+ size: number;
290
+ /** Average document size */
291
+ avgObjSize: number;
292
+ /** Total storage size */
293
+ storageSize: number;
294
+ /** Number of indexes */
295
+ nindexes: number;
296
+ /** Total index size */
297
+ totalIndexSize: number;
298
+ }
299
+
300
+ // =============================================================================
301
+ // Database Types
302
+ // =============================================================================
303
+
304
+ /**
305
+ * Database information
306
+ */
307
+ export interface DatabaseInfo {
308
+ /** Database name */
309
+ name: string;
310
+ /** Number of collections */
311
+ collections: number;
312
+ /** Number of views */
313
+ views: number;
314
+ /** Size on disk in bytes */
315
+ sizeOnDisk: number;
316
+ /** Whether database is empty */
317
+ empty: boolean;
318
+ }
319
+
320
+ // =============================================================================
321
+ // Aggregation Types
322
+ // =============================================================================
323
+
324
+ /**
325
+ * Aggregation pipeline stage
326
+ */
327
+ export type AggregationStage = Document;
328
+
329
+ /**
330
+ * Aggregation options
331
+ */
332
+ export interface AggregationOptions {
333
+ /** Allow disk use for large operations */
334
+ allowDiskUse?: boolean | undefined;
335
+ /** Max execution time in ms */
336
+ maxTimeMS?: number | undefined;
337
+ }