@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,56 @@
1
+ /**
2
+ * Procedure: mongo.indexes.drop
3
+ * Drop an index from a collection
4
+ */
5
+
6
+ import { createProcedure, zodAdapter, type Effects, type Procedure, type ProcedureContext } from "@procedures/client";
7
+ import { collectionFor, type ScopeInput } from "../scope.js";
8
+ import { scopeFields } from "./input-schemas.js";
9
+ import { z } from "zod";
10
+
11
+ // Input/Output types
12
+ interface DropIndexInput extends ScopeInput {
13
+ indexName: string;
14
+ }
15
+
16
+ interface DropIndexOutput {
17
+ acknowledged: boolean;
18
+ }
19
+
20
+ // Schemas
21
+ const dropIndexInputSchema = zodAdapter<DropIndexInput>(
22
+ z.object({
23
+ ...scopeFields(),
24
+ indexName: z.string().min(1).describe("The name of the index to drop"),
25
+ })
26
+ );
27
+ const dropIndexOutputSchema = zodAdapter<DropIndexOutput>(
28
+ z.looseObject({
29
+ acknowledged: z.boolean().describe("True when the server confirmed the write"),
30
+ })
31
+ );
32
+
33
+ export const dropIndexProcedure: Procedure<
34
+ DropIndexInput,
35
+ DropIndexOutput,
36
+ { description: string; effects?: Effects }
37
+ > = createProcedure()
38
+ .path(["mongo", "indexes", "drop"])
39
+ .input(dropIndexInputSchema)
40
+ .output(dropIndexOutputSchema)
41
+ .meta({
42
+ description: "Drop an index from a collection",
43
+ effects: { reads: ["mongo", "network"], writes: ["mongo", "network"] },
44
+ })
45
+ .handler(async (input: DropIndexInput, ctx: ProcedureContext) => {
46
+ const { collection } = await collectionFor(input, ctx);
47
+
48
+ await collection.dropIndex(input.indexName);
49
+
50
+ return {
51
+ acknowledged: true,
52
+ };
53
+ })
54
+ .build();
55
+
56
+ export type { DropIndexInput, DropIndexOutput };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Procedure: mongo.indexes.list
3
+ * List indexes on a collection
4
+ */
5
+
6
+ import { createProcedure, zodAdapter, type Effects, type Procedure, type ProcedureContext } from "@procedures/client";
7
+ import { collectionFor, type ScopeInput } from "../scope.js";
8
+ import { scopeFields } from "./input-schemas.js";
9
+ import { z } from "zod";
10
+ import { type IndexInfo } from "../types.js";
11
+
12
+ // Input/Output types
13
+ /** The input: only the scope fields. */
14
+ type ListIndexesInput = ScopeInput;
15
+
16
+ interface ListIndexesOutput {
17
+ indexes: IndexInfo[];
18
+ }
19
+
20
+ // Schemas
21
+ const listIndexesInputSchema = zodAdapter<ListIndexesInput>(
22
+ z.object({ ...scopeFields() })
23
+ );
24
+ const listIndexesOutputSchema = zodAdapter<ListIndexesOutput>(
25
+ z.looseObject({
26
+ indexes: z
27
+ .array(
28
+ z.looseObject({
29
+ name: z.string().describe("The index name"),
30
+ key: z.record(z.string(), z.union([z.number(), z.string()])).describe("The fields of the index and their kind (1, -1, \"text\", \"hashed\")"),
31
+ unique: z.boolean().optional().describe("True for a unique index"),
32
+ sparse: z.boolean().optional().describe("True for a sparse index"),
33
+ })
34
+ )
35
+ .describe("The indexes of the collection"),
36
+ })
37
+ );
38
+
39
+ export const listIndexesProcedure: Procedure<
40
+ ListIndexesInput,
41
+ ListIndexesOutput,
42
+ { description: string; effects?: Effects }
43
+ > = createProcedure()
44
+ .path(["mongo", "indexes", "list"])
45
+ .input(listIndexesInputSchema)
46
+ .output(listIndexesOutputSchema)
47
+ .meta({
48
+ description: "List indexes on a collection",
49
+ effects: { reads: ["mongo", "network"], writes: [] },
50
+ })
51
+ .handler(async (input: ListIndexesInput, ctx: ProcedureContext) => {
52
+ const { collection } = await collectionFor(input, ctx);
53
+
54
+ const indexes = await collection.listIndexes().toArray();
55
+
56
+ return {
57
+ indexes: indexes.map((idx) => ({
58
+ name: idx.name,
59
+ key: idx.key,
60
+ unique: idx.unique,
61
+ sparse: idx.sparse,
62
+ })),
63
+ };
64
+ })
65
+ .build();
66
+
67
+ export type { ListIndexesInput, ListIndexesOutput };
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The shared fields of the mongo input schemas (roadmap 2.1).
3
+ *
4
+ * Each `mongo.*` tool describes its fields, so Claude and `mark --help` know them. Before, most
5
+ * tools had a pass-through schema: the tool list showed no fields, and a field name with a
6
+ * typing error reached the handler.
7
+ *
8
+ * A write (`update`, `delete`) and `find`/`get` keep their own parse functions: they check the
9
+ * target rules of deep dive DATA-4 (exactly one of `id` or `filter`, an unknown field is an
10
+ * error, an empty filter needs `confirm`). `described()` gives such a function the JSON Schema of
11
+ * a zod object that names the same fields.
12
+ */
13
+
14
+ import { z } from "zod";
15
+ import { jsonSchemaOf, zodAdapter, type ZodLikeSchema } from "@procedures/client";
16
+
17
+ /** The fields that select the database and the collection. */
18
+ export function scopeFields(): { collection: z.ZodOptional<z.ZodString>; database: z.ZodOptional<z.ZodString> } {
19
+ return {
20
+ collection: z.string().min(1).optional().describe("The collection name. It takes the place of metadata.collection."),
21
+ database: z.string().min(1).optional().describe("The database name (default: the database of the connection)"),
22
+ };
23
+ }
24
+
25
+ /** A MongoDB document or a filter: an object with any fields. */
26
+ export function documentObject(): z.ZodRecord<z.ZodString, z.ZodUnknown> {
27
+ return z.record(z.string(), z.unknown());
28
+ }
29
+
30
+ /** The fields that select the documents of a write: one `id`, or a `filter`. */
31
+ export function targetFields(): {
32
+ filter: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
33
+ id: z.ZodOptional<z.ZodString>;
34
+ idType: z.ZodOptional<z.ZodEnum<{ auto: "auto"; objectId: "objectId"; string: "string" }>>;
35
+ multi: z.ZodOptional<z.ZodBoolean>;
36
+ confirm: z.ZodOptional<z.ZodBoolean>;
37
+ } {
38
+ return {
39
+ filter: documentObject()
40
+ .optional()
41
+ .describe("The filter of the documents (give exactly one of id or filter). An empty filter needs confirm: true."),
42
+ id: z.string().min(1).optional().describe("The id of one document (the alternative to filter)"),
43
+ idType: idTypeField(),
44
+ multi: z.boolean().optional().describe("Change every matching document, not only the first one"),
45
+ confirm: z.boolean().optional().describe("Set to true to use an empty filter, which matches every document"),
46
+ };
47
+ }
48
+
49
+ /** How to read a string id. */
50
+ export function idTypeField(): z.ZodOptional<z.ZodEnum<{ auto: "auto"; objectId: "objectId"; string: "string" }>> {
51
+ return z
52
+ .enum(["auto", "objectId", "string"])
53
+ .optional()
54
+ .describe('How to read the id: "auto" (the default: an ObjectId or a string), "objectId" or "string"');
55
+ }
56
+
57
+ /**
58
+ * A schema whose validation is a parse function, and whose JSON Schema is the one of a zod object
59
+ * that names the same fields.
60
+ */
61
+ export function described<T>(parse: (data: unknown) => T, fields: z.ZodObject): ZodLikeSchema<T> {
62
+ return Object.assign(zodAdapter<T>({ parse }), { "~jsonSchema": jsonSchemaOf(fields) });
63
+ }
@@ -0,0 +1 @@
1
+ export { outputSchema as schema } from "@procedures/client";
@@ -0,0 +1,123 @@
1
+ /**
2
+ * The target of a write: one document by `id`, or the documents that match a `filter`.
3
+ *
4
+ * Before, a call without `id` and `filter` used the filter `{}`, and the field name of
5
+ * `find`/`count` (`query`) was ignored: `delete { query, multi: true }` emptied the
6
+ * collection (deep dive DATA-4). Now a write needs exactly one target, an unknown field is an
7
+ * error, and an empty filter needs `confirm: true`.
8
+ */
9
+
10
+ import { ObjectId, type Collection, type Document } from "mongodb";
11
+ import { buildIdFilter, type DocumentQuery, type IdType } from "../types.js";
12
+ import { SCOPE_FIELDS, copyScope, type ScopeInput } from "../scope.js";
13
+
14
+ /** The fields of a write target. */
15
+ export interface TargetInput extends ScopeInput {
16
+ /** The filter of the documents to change. An empty filter needs `confirm: true`. */
17
+ filter?: DocumentQuery;
18
+ /** The ID of one document (the alternative to `filter`). */
19
+ id?: string;
20
+ /** How to read the ID (default: "auto", which matches an ObjectId or a string). */
21
+ idType?: IdType;
22
+ /** Change all matching documents, not only the first one. */
23
+ multi?: boolean;
24
+ /** Set to true to use an empty filter (it matches every document). */
25
+ confirm?: boolean;
26
+ }
27
+
28
+ const TARGET_FIELDS = ["filter", "id", "idType", "multi", "confirm", ...SCOPE_FIELDS] as const;
29
+
30
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
31
+ return typeof value === "object" && value !== null && !Array.isArray(value);
32
+ }
33
+
34
+ function optionalBoolean(procedure: string, raw: Record<string, unknown>, field: string): boolean | undefined {
35
+ const value = raw[field];
36
+ if (value === undefined) return undefined;
37
+ if (typeof value !== "boolean") throw new Error(`${procedure}: ${field} must be a boolean`);
38
+ return value;
39
+ }
40
+
41
+ /**
42
+ * This function reads and checks the target fields of a write input.
43
+ * `extraFields` are the other fields that the procedure accepts.
44
+ */
45
+ export function parseTarget(
46
+ procedure: string,
47
+ data: unknown,
48
+ extraFields: readonly string[] = [],
49
+ ): { target: TargetInput; raw: Record<string, unknown> } {
50
+ if (!isPlainObject(data)) {
51
+ throw new Error(`${procedure}: input must be an object`);
52
+ }
53
+ const raw = data;
54
+ const allowed = new Set<string>([...TARGET_FIELDS, ...extraFields]);
55
+ for (const key of Object.keys(raw)) {
56
+ if (!allowed.has(key)) {
57
+ const hint = key === "query" ? ' Use "filter".' : "";
58
+ throw new Error(`${procedure}: unknown field "${key}".${hint} The fields are: ${[...allowed].join(", ")}`);
59
+ }
60
+ }
61
+
62
+ const hasId = raw["id"] !== undefined;
63
+ const hasFilter = raw["filter"] !== undefined;
64
+ if (hasId === hasFilter) {
65
+ throw new Error(`${procedure}: give exactly one of id or filter`);
66
+ }
67
+
68
+ const target: TargetInput = {};
69
+ copyScope(raw, target);
70
+
71
+ if (hasId) {
72
+ const id = raw["id"];
73
+ if (typeof id !== "string" || id.length === 0) {
74
+ throw new Error(`${procedure}: id must be a non-empty string`);
75
+ }
76
+ target.id = id;
77
+ } else {
78
+ const filter = raw["filter"];
79
+ if (!isPlainObject(filter)) {
80
+ throw new Error(`${procedure}: filter must be an object`);
81
+ }
82
+ target.filter = filter as DocumentQuery;
83
+ }
84
+
85
+ const idType = raw["idType"];
86
+ if (idType !== undefined) {
87
+ if (idType !== "auto" && idType !== "objectId" && idType !== "string") {
88
+ throw new Error(`${procedure}: idType must be "auto", "objectId" or "string"`);
89
+ }
90
+ target.idType = idType;
91
+ }
92
+
93
+ const multi = optionalBoolean(procedure, raw, "multi");
94
+ const confirm = optionalBoolean(procedure, raw, "confirm");
95
+ if (multi !== undefined) target.multi = multi;
96
+ if (confirm !== undefined) target.confirm = confirm;
97
+
98
+ if (target.filter && Object.keys(target.filter).length === 0 && confirm !== true) {
99
+ throw new Error(`${procedure}: an empty filter matches every document. Set confirm: true to use it.`);
100
+ }
101
+
102
+ return { target, raw };
103
+ }
104
+
105
+ /** The filter of a target. */
106
+ export function targetFilter(target: TargetInput): Document {
107
+ return target.id !== undefined ? buildIdFilter(target.id, target.idType) : (target.filter as Document);
108
+ }
109
+
110
+ /**
111
+ * The filter of an upsert by ID. It names one exact `_id`.
112
+ *
113
+ * In the "auto" mode the ID filter is `$or` of the ObjectId and the string form. MongoDB cannot
114
+ * take an `_id` from `$or`, so each upsert inserted a new document with a new ID (deep dive
115
+ * DATA-15). This function uses the `_id` of the existing document, or the ObjectId form when no
116
+ * document matches.
117
+ */
118
+ export async function upsertFilter(collection: Collection<Document>, target: TargetInput): Promise<Document> {
119
+ const filter = targetFilter(target);
120
+ if (target.id === undefined || !("$or" in filter)) return filter;
121
+ const existing = await collection.findOne(filter, { projection: { _id: 1 } });
122
+ return { _id: existing ? existing["_id"] : new ObjectId(target.id) };
123
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Typed Procedures Interface
3
+ *
4
+ * Provides compile-time autocomplete for client-mongo procedures.
5
+ * Import this type and use with a typed caller for full type safety.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import type { MongoProcedures } from "@procedures/mongo";
10
+ * import { createTypedCaller } from "@procedures/client";
11
+ *
12
+ * const call = createTypedCaller<MongoProcedures>(client.call);
13
+ *
14
+ * // Full autocomplete on path and input!
15
+ * const result = await call(
16
+ * ["mongo", "documents", "find"],
17
+ * { query: { status: "active" } },
18
+ * { metadata: { collection: "users" } }
19
+ * );
20
+ * // result is typed as FindOutput
21
+ * ```
22
+ */
23
+
24
+ import type { CollectionStats, DatabaseInfo } from "./types.js";
25
+
26
+ // =============================================================================
27
+ // Input and output types
28
+ // =============================================================================
29
+ // These are the types the handlers use: each procedure file exports them, so this
30
+ // interface cannot drift from the handlers. (BUGS-2026-07 M17: the copies kept here before
31
+ // had drifted from what the handlers accept and return.)
32
+
33
+ import type { PingInput, PingOutput } from "./procedures/database.ping.js";
34
+ import type { InfoInput } from "./procedures/database.info.js";
35
+ import type { ListInput as ListCollectionsInput, ListOutput as ListCollectionsOutput } from "./procedures/collections.list.js";
36
+ import type { CreateInput as CreateCollectionInput, CreateOutput as CreateCollectionOutput } from "./procedures/collections.create.js";
37
+ import type { DropInput as DropCollectionInput, DropOutput as DropCollectionOutput } from "./procedures/collections.drop.js";
38
+ import type { StatsInput } from "./procedures/collections.stats.js";
39
+ import type { FindInput, FindOutput } from "./procedures/documents.find.js";
40
+ import type { GetInput, GetOutput } from "./procedures/documents.get.js";
41
+ import type { InsertInput, InsertOutput } from "./procedures/documents.insert.js";
42
+ import type { UpdateInput, UpdateOutput } from "./procedures/documents.update.js";
43
+ import type { DeleteInput, DeleteOutput } from "./procedures/documents.delete.js";
44
+ import type { CountInput, CountOutput } from "./procedures/documents.count.js";
45
+ import type { AggregateInput, AggregateOutput } from "./procedures/documents.aggregate.js";
46
+ import type { ListIndexesInput, ListIndexesOutput } from "./procedures/indexes.list.js";
47
+ import type { CreateIndexInput, CreateIndexOutput } from "./procedures/indexes.create.js";
48
+ import type { DropIndexInput, DropIndexOutput } from "./procedures/indexes.drop.js";
49
+
50
+ export type {
51
+ PingInput,
52
+ PingOutput,
53
+ InfoInput,
54
+ ListCollectionsInput,
55
+ ListCollectionsOutput,
56
+ CreateCollectionInput,
57
+ CreateCollectionOutput,
58
+ DropCollectionInput,
59
+ DropCollectionOutput,
60
+ StatsInput,
61
+ FindInput,
62
+ FindOutput,
63
+ GetInput,
64
+ GetOutput,
65
+ InsertInput,
66
+ InsertOutput,
67
+ UpdateInput,
68
+ UpdateOutput,
69
+ DeleteInput,
70
+ DeleteOutput,
71
+ CountInput,
72
+ CountOutput,
73
+ AggregateInput,
74
+ AggregateOutput,
75
+ ListIndexesInput,
76
+ ListIndexesOutput,
77
+ CreateIndexInput,
78
+ CreateIndexOutput,
79
+ DropIndexInput,
80
+ DropIndexOutput,
81
+ };
82
+
83
+ // =============================================================================
84
+ // Combined Procedures Interface
85
+ // =============================================================================
86
+
87
+ /**
88
+ * Typed interface for all MongoDB procedures.
89
+ * Use with createTypedCaller for compile-time autocomplete.
90
+ */
91
+ export interface MongoProcedures {
92
+ mongo: {
93
+ database: {
94
+ ping: { input: PingInput; output: PingOutput };
95
+ info: { input: InfoInput; output: DatabaseInfo };
96
+ };
97
+ collections: {
98
+ list: { input: ListCollectionsInput; output: ListCollectionsOutput };
99
+ create: { input: CreateCollectionInput; output: CreateCollectionOutput };
100
+ drop: { input: DropCollectionInput; output: DropCollectionOutput };
101
+ stats: { input: StatsInput; output: CollectionStats };
102
+ };
103
+ documents: {
104
+ find: { input: FindInput; output: FindOutput };
105
+ get: { input: GetInput; output: GetOutput };
106
+ insert: { input: InsertInput; output: InsertOutput };
107
+ update: { input: UpdateInput; output: UpdateOutput };
108
+ delete: { input: DeleteInput; output: DeleteOutput };
109
+ count: { input: CountInput; output: CountOutput };
110
+ aggregate: { input: AggregateInput; output: AggregateOutput };
111
+ };
112
+ indexes: {
113
+ list: { input: ListIndexesInput; output: ListIndexesOutput };
114
+ create: { input: CreateIndexInput; output: CreateIndexOutput };
115
+ drop: { input: DropIndexInput; output: DropIndexOutput };
116
+ };
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Helper type to extract input type from a procedure path
122
+ */
123
+ export type ProcedureInput<P extends readonly string[]> =
124
+ P extends ["mongo", "database", "ping"] ? PingInput :
125
+ P extends ["mongo", "database", "info"] ? InfoInput :
126
+ P extends ["mongo", "collections", "list"] ? ListCollectionsInput :
127
+ P extends ["mongo", "collections", "create"] ? CreateCollectionInput :
128
+ P extends ["mongo", "collections", "drop"] ? DropCollectionInput :
129
+ P extends ["mongo", "collections", "stats"] ? StatsInput :
130
+ P extends ["mongo", "documents", "find"] ? FindInput :
131
+ P extends ["mongo", "documents", "get"] ? GetInput :
132
+ P extends ["mongo", "documents", "insert"] ? InsertInput :
133
+ P extends ["mongo", "documents", "update"] ? UpdateInput :
134
+ P extends ["mongo", "documents", "delete"] ? DeleteInput :
135
+ P extends ["mongo", "documents", "count"] ? CountInput :
136
+ P extends ["mongo", "documents", "aggregate"] ? AggregateInput :
137
+ P extends ["mongo", "indexes", "list"] ? ListIndexesInput :
138
+ P extends ["mongo", "indexes", "create"] ? CreateIndexInput :
139
+ P extends ["mongo", "indexes", "drop"] ? DropIndexInput :
140
+ never;
141
+
142
+ /**
143
+ * Helper type to extract output type from a procedure path
144
+ */
145
+ export type ProcedureOutput<P extends readonly string[]> =
146
+ P extends ["mongo", "database", "ping"] ? PingOutput :
147
+ P extends ["mongo", "database", "info"] ? DatabaseInfo :
148
+ P extends ["mongo", "collections", "list"] ? ListCollectionsOutput :
149
+ P extends ["mongo", "collections", "create"] ? CreateCollectionOutput :
150
+ P extends ["mongo", "collections", "drop"] ? DropCollectionOutput :
151
+ P extends ["mongo", "collections", "stats"] ? CollectionStats :
152
+ P extends ["mongo", "documents", "find"] ? FindOutput :
153
+ P extends ["mongo", "documents", "get"] ? GetOutput :
154
+ P extends ["mongo", "documents", "insert"] ? InsertOutput :
155
+ P extends ["mongo", "documents", "update"] ? UpdateOutput :
156
+ P extends ["mongo", "documents", "delete"] ? DeleteOutput :
157
+ P extends ["mongo", "documents", "count"] ? CountOutput :
158
+ P extends ["mongo", "documents", "aggregate"] ? AggregateOutput :
159
+ P extends ["mongo", "indexes", "list"] ? ListIndexesOutput :
160
+ P extends ["mongo", "indexes", "create"] ? CreateIndexOutput :
161
+ P extends ["mongo", "indexes", "drop"] ? DropIndexOutput :
162
+ never;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Importing this file registers the procedures of @procedures/mongo, and of the modules that it
3
+ * requires, in the global registry: `import "@procedures/mongo/register"`. The package root and the
4
+ * `./module` entry register nothing (ARCHITECTURE-PROPOSALS-2026-10, P9).
5
+ */
6
+
7
+ import { registerMongoProcedures } from "./module.js";
8
+
9
+ export * from "./module.js";
10
+
11
+ registerMongoProcedures();
package/src/scope.ts ADDED
@@ -0,0 +1,69 @@
1
+ /**
2
+ * The database and the collection of a call.
3
+ *
4
+ * Each procedure reads `collection` and `database` from its input. The call metadata stays a
5
+ * fallback for the clients that set it. (Before, only the metadata gave them, and an MCP tool
6
+ * call cannot set metadata: deep dive DATA-5.)
7
+ */
8
+
9
+ import type { Collection, Db, Document } from "mongodb";
10
+ import type { ProcedureContext } from "@procedures/client";
11
+ import { ensureConnection } from "./connection.js";
12
+
13
+ /** The input fields that select the database and the collection. */
14
+ export interface ScopeInput {
15
+ /** The collection name. It takes the place of `metadata.collection`. */
16
+ collection?: string | undefined;
17
+ /** The database name. It takes the place of `metadata.database` and the connection default. */
18
+ database?: string | undefined;
19
+ }
20
+
21
+ /** The names of the scope fields, for the input parsers. */
22
+ export const SCOPE_FIELDS = ["collection", "database"] as const;
23
+
24
+ function optionalName(value: unknown, field: string): string | undefined {
25
+ if (value === undefined) return undefined;
26
+ if (typeof value !== "string" || value.trim().length === 0) {
27
+ throw new Error(`${field} must be a non-empty string`);
28
+ }
29
+ return value;
30
+ }
31
+
32
+ /** The database name of a call: the input, then the metadata. Undefined means the connection default. */
33
+ export function databaseName(input: ScopeInput, ctx: Pick<ProcedureContext, "metadata">): string | undefined {
34
+ return optionalName(input.database, "database") ?? optionalName(ctx.metadata["database"], "metadata.database");
35
+ }
36
+
37
+ /** The collection name of a call: the input, then the metadata. */
38
+ export function collectionName(input: ScopeInput, ctx: Pick<ProcedureContext, "metadata">): string {
39
+ const name = optionalName(input.collection, "collection") ?? optionalName(ctx.metadata["collection"], "metadata.collection");
40
+ if (name === undefined) {
41
+ throw new Error("collection is required: give it in the input (or in the call metadata)");
42
+ }
43
+ return name;
44
+ }
45
+
46
+ /** The database of a call. This function connects first when there is no open connection. */
47
+ export async function databaseFor(input: ScopeInput, ctx: Pick<ProcedureContext, "metadata">): Promise<Db> {
48
+ const name = databaseName(input, ctx);
49
+ const connection = await ensureConnection();
50
+ return name ? connection.getClient().db(name) : connection.getDb();
51
+ }
52
+
53
+ /** The collection of a call. This function connects first when there is no open connection. */
54
+ export async function collectionFor(
55
+ input: ScopeInput,
56
+ ctx: Pick<ProcedureContext, "metadata">,
57
+ ): Promise<{ collection: Collection<Document>; name: string; db: Db }> {
58
+ const name = collectionName(input, ctx);
59
+ const db = await databaseFor(input, ctx);
60
+ return { collection: db.collection(name), name, db };
61
+ }
62
+
63
+ /** This function copies the scope fields of a raw input into a parsed input. */
64
+ export function copyScope(raw: Record<string, unknown>, parsed: ScopeInput): void {
65
+ const collection = optionalName(raw["collection"], "collection");
66
+ const database = optionalName(raw["database"], "database");
67
+ if (collection !== undefined) parsed.collection = collection;
68
+ if (database !== undefined) parsed.database = database;
69
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * MongoDB Storage Exports
3
+ */
4
+
5
+ export { MongoStorage } from "./mongo-storage.js";
6
+ export type { MongoStorageOptions } from "./mongo-storage.js";