@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,55 @@
1
+ /**
2
+ * Procedure: mongo.documents.count
3
+ * Count documents matching a filter
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, documentObject } from "./input-schemas.js";
9
+ import { z } from "zod";
10
+ import { type DocumentQuery } from "../types.js";
11
+
12
+ // Input/Output types
13
+ interface CountInput extends ScopeInput {
14
+ query?: DocumentQuery | undefined;
15
+ }
16
+
17
+ interface CountOutput {
18
+ count: number;
19
+ }
20
+
21
+ // Schemas
22
+ const countInputSchema = zodAdapter<CountInput>(
23
+ z.object({
24
+ ...scopeFields(),
25
+ query: documentObject().optional().describe("The filter of the documents to count (default: all documents)"),
26
+ })
27
+ );
28
+ const countOutputSchema = zodAdapter<CountOutput>(
29
+ z.looseObject({
30
+ count: z.number().describe("The number of documents that match"),
31
+ })
32
+ );
33
+
34
+ export const countProcedure: Procedure<
35
+ CountInput,
36
+ CountOutput,
37
+ { description: string; effects?: Effects }
38
+ > = createProcedure()
39
+ .path(["mongo", "documents", "count"])
40
+ .input(countInputSchema)
41
+ .output(countOutputSchema)
42
+ .meta({
43
+ description: "Count documents matching a filter",
44
+ effects: { reads: ["mongo", "network"], writes: [] },
45
+ })
46
+ .handler(async (input: CountInput, ctx: ProcedureContext) => {
47
+ const { collection } = await collectionFor(input, ctx);
48
+
49
+ const count = await collection.countDocuments(input.query ?? {});
50
+
51
+ return { count };
52
+ })
53
+ .build();
54
+
55
+ export type { CountInput, CountOutput };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Procedure: mongo.documents.delete
3
+ * Delete documents matching a filter
4
+ */
5
+
6
+ import { createProcedure, type Effects, type Procedure, type ProcedureContext, zodAdapter } from "@procedures/client";
7
+ import { collectionFor } from "../scope.js";
8
+ import { described, scopeFields, targetFields } from "./input-schemas.js";
9
+ import { z } from "zod";
10
+ import { parseTarget, targetFilter, type TargetInput } from "./target.js";
11
+
12
+ // Input/Output types
13
+ /** The input: exactly one of `id` or `filter`, and the scope fields. */
14
+ type DeleteInput = TargetInput;
15
+
16
+ interface DeleteOutput {
17
+ acknowledged: boolean;
18
+ deletedCount: number;
19
+ }
20
+
21
+ // Schemas
22
+ const deleteInputSchema = described<DeleteInput>(
23
+ (data: unknown) => parseTarget("mongo.documents.delete", data).target,
24
+ z.object({ ...scopeFields(), ...targetFields() })
25
+ );
26
+ const deleteOutputSchema = zodAdapter<DeleteOutput>(
27
+ z.looseObject({
28
+ acknowledged: z.boolean().describe("True when the server confirmed the write"),
29
+ deletedCount: z.number().describe("The number of deleted documents"),
30
+ })
31
+ );
32
+
33
+ export const deleteProcedure: Procedure<
34
+ DeleteInput,
35
+ DeleteOutput,
36
+ { description: string; effects?: Effects }
37
+ > = createProcedure()
38
+ .path(["mongo", "documents", "delete"])
39
+ .input(deleteInputSchema)
40
+ .output(deleteOutputSchema)
41
+ .meta({
42
+ description: "Delete documents matching a filter",
43
+ effects: { reads: ["mongo", "network"], writes: ["mongo", "network"] },
44
+ })
45
+ .handler(async (input: DeleteInput, ctx: ProcedureContext) => {
46
+ const { collection } = await collectionFor(input, ctx);
47
+ const filter = targetFilter(input);
48
+
49
+ const result = input.multi ? await collection.deleteMany(filter) : await collection.deleteOne(filter);
50
+ return {
51
+ acknowledged: result.acknowledged,
52
+ deletedCount: result.deletedCount,
53
+ };
54
+ })
55
+ .build();
56
+
57
+ export type { DeleteInput, DeleteOutput };
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Procedure: mongo.documents.find
3
+ * Find documents with pagination and filtering
4
+ */
5
+
6
+ import {
7
+ createProcedure,
8
+ type Effects,
9
+ type Procedure,
10
+ type ProcedureContext,
11
+ zodAdapter,
12
+ } from "@procedures/client";
13
+ import { collectionFor, copyScope, type ScopeInput } from "../scope.js";
14
+ import { described, documentObject, scopeFields } from "./input-schemas.js";
15
+ import { z } from "zod";
16
+ import {
17
+ type PaginationOutput,
18
+ type DocumentQuery,
19
+ type SortSpec,
20
+ type MongoDocument,
21
+ } from "../types.js";
22
+
23
+ // Input/Output types
24
+ interface FindInput extends ScopeInput {
25
+ query?: DocumentQuery;
26
+ projection?: Record<string, 0 | 1>;
27
+ sort?: SortSpec;
28
+ page?: number;
29
+ limit?: number;
30
+ }
31
+
32
+ interface FindOutput {
33
+ documents: MongoDocument[];
34
+ pagination: PaginationOutput;
35
+ }
36
+
37
+ /**
38
+ * Validate find input. Enforces `page`/`limit` as integers >= 1 so a `page: 0`
39
+ * (which produced a negative `skip`) or a string `limit` (which produced a
40
+ * `NaN` limit) can no longer reach the driver. Other fields pass through.
41
+ */
42
+ function parseFindInput(data: unknown): FindInput {
43
+ if (typeof data !== "object" || data === null) {
44
+ throw new Error("mongo.documents.find: input must be an object");
45
+ }
46
+ const raw = data as Record<string, unknown>;
47
+ const result: FindInput = {};
48
+ copyScope(raw, result);
49
+
50
+ if (raw["query"] !== undefined) {
51
+ result.query = raw["query"] as DocumentQuery;
52
+ }
53
+ if (raw["projection"] !== undefined) {
54
+ result.projection = raw["projection"] as Record<string, 0 | 1>;
55
+ }
56
+ if (raw["sort"] !== undefined) {
57
+ result.sort = raw["sort"] as SortSpec;
58
+ }
59
+ if (raw["page"] !== undefined) {
60
+ const page = raw["page"];
61
+ if (typeof page !== "number" || !Number.isInteger(page) || page < 1) {
62
+ throw new Error("mongo.documents.find: page must be an integer >= 1");
63
+ }
64
+ result.page = page;
65
+ }
66
+ if (raw["limit"] !== undefined) {
67
+ const limit = raw["limit"];
68
+ if (typeof limit !== "number" || !Number.isInteger(limit) || limit < 1) {
69
+ throw new Error("mongo.documents.find: limit must be an integer >= 1");
70
+ }
71
+ result.limit = limit;
72
+ }
73
+
74
+ return result;
75
+ }
76
+
77
+ // Schemas
78
+ const findInputSchema = described<FindInput>(
79
+ parseFindInput,
80
+ z.object({
81
+ ...scopeFields(),
82
+ query: documentObject().optional().describe("The filter of the documents (default: all documents)"),
83
+ projection: z.record(z.string(), z.union([z.literal(0), z.literal(1)])).optional().describe("The fields to give (1) or to leave out (0)"),
84
+ sort: z.record(z.string(), z.unknown()).optional().describe("The sort order: { field: 1 } ascending, { field: -1 } descending"),
85
+ page: z.number().int().min(1).optional().describe("The page to give, from 1"),
86
+ limit: z.number().int().min(1).optional().describe("The number of documents on a page"),
87
+ })
88
+ );
89
+ const findOutputSchema = zodAdapter<FindOutput>(
90
+ z.looseObject({
91
+ documents: z.array(z.record(z.string(), z.unknown())).describe("The documents of the page"),
92
+ pagination: z.looseObject({
93
+ page: z.number().describe("The page, from 1"),
94
+ limit: z.number().describe("The number of documents on a page"),
95
+ total: z.number().describe("The number of documents that match"),
96
+ totalPages: z.number().describe("The number of pages"),
97
+ hasNext: z.boolean().describe("True when a next page exists"),
98
+ hasPrev: z.boolean().describe("True when a previous page exists"),
99
+ }).describe("The page of the result"),
100
+ })
101
+ );
102
+
103
+ export const findProcedure: Procedure<
104
+ FindInput,
105
+ FindOutput,
106
+ { description: string; effects?: Effects }
107
+ > = createProcedure()
108
+ .path(["mongo", "documents", "find"])
109
+ .input(findInputSchema)
110
+ .output(findOutputSchema)
111
+ .meta({
112
+ description: "Find documents with pagination and filtering",
113
+ effects: { reads: ["mongo", "network"], writes: [] },
114
+ })
115
+ .handler(async (input: FindInput, ctx: ProcedureContext) => {
116
+ const { collection } = await collectionFor(input, ctx);
117
+
118
+ const query = input.query ?? {};
119
+ const page = input.page ?? 1;
120
+ const limit = Math.min(input.limit ?? 20, 100); // Cap at 100
121
+ const skip = (page - 1) * limit;
122
+
123
+ // Execute query and count in parallel
124
+ const [documents, total] = await Promise.all([
125
+ collection
126
+ .find(query)
127
+ .project(input.projection ?? {})
128
+ .sort(input.sort ?? {})
129
+ .skip(skip)
130
+ .limit(limit)
131
+ .toArray(),
132
+ collection.countDocuments(query),
133
+ ]);
134
+
135
+ const totalPages = Math.ceil(total / limit);
136
+
137
+ return {
138
+ documents,
139
+ pagination: {
140
+ page,
141
+ limit,
142
+ total,
143
+ totalPages,
144
+ hasNext: page < totalPages,
145
+ hasPrev: page > 1,
146
+ },
147
+ };
148
+ })
149
+ .build();
150
+
151
+ export type { FindInput, FindOutput };
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Procedure: mongo.documents.get
3
+ * Get a single document by ID
4
+ */
5
+
6
+ import {
7
+ createProcedure,
8
+ type Effects,
9
+ type Procedure,
10
+ type ProcedureContext,
11
+ zodAdapter,
12
+ } from "@procedures/client";
13
+ import { collectionFor, copyScope, type ScopeInput } from "../scope.js";
14
+ import { described, idTypeField, scopeFields } from "./input-schemas.js";
15
+ import { z } from "zod";
16
+ import {
17
+ buildIdFilter,
18
+ type IdType,
19
+ type MongoDocument,
20
+ } from "../types.js";
21
+
22
+ // Input/Output types
23
+ interface GetInput extends ScopeInput {
24
+ id: string;
25
+ /** How to interpret the id (default: "auto" — matches ObjectId or string) */
26
+ idType?: IdType;
27
+ }
28
+
29
+ interface GetOutput {
30
+ document: MongoDocument | null;
31
+ }
32
+
33
+ /**
34
+ * Validate get input: `id` must be a non-empty string, and `idType`, when
35
+ * present, must be one of the supported modes.
36
+ */
37
+ function parseGetInput(data: unknown): GetInput {
38
+ if (typeof data !== "object" || data === null) {
39
+ throw new Error("mongo.documents.get: input must be an object");
40
+ }
41
+ const raw = data as Record<string, unknown>;
42
+
43
+ const id = raw["id"];
44
+ if (typeof id !== "string" || id.length === 0) {
45
+ throw new Error("mongo.documents.get: id must be a non-empty string");
46
+ }
47
+
48
+ const result: GetInput = { id };
49
+ copyScope(raw, result);
50
+
51
+ const idType = raw["idType"];
52
+ if (idType !== undefined) {
53
+ if (idType !== "auto" && idType !== "objectId" && idType !== "string") {
54
+ throw new Error(
55
+ 'mongo.documents.get: idType must be "auto", "objectId", or "string"'
56
+ );
57
+ }
58
+ result.idType = idType;
59
+ }
60
+
61
+ return result;
62
+ }
63
+
64
+ // Schemas
65
+ const getInputSchema = described<GetInput>(
66
+ parseGetInput,
67
+ z.object({
68
+ ...scopeFields(),
69
+ id: z.string().min(1).describe("The id of the document"),
70
+ idType: idTypeField(),
71
+ })
72
+ );
73
+ const getOutputSchema = zodAdapter<GetOutput>(
74
+ z.looseObject({
75
+ document: z.record(z.string(), z.unknown()).nullable().describe("The document, or null when no document has the id"),
76
+ })
77
+ );
78
+
79
+ export const getProcedure: Procedure<
80
+ GetInput,
81
+ GetOutput,
82
+ { description: string; effects?: Effects }
83
+ > = createProcedure()
84
+ .path(["mongo", "documents", "get"])
85
+ .input(getInputSchema)
86
+ .output(getOutputSchema)
87
+ .meta({
88
+ description: "Get a single document by ID",
89
+ effects: { reads: ["mongo", "network"], writes: [] },
90
+ })
91
+ .handler(async (input: GetInput, ctx: ProcedureContext) => {
92
+ const { collection } = await collectionFor(input, ctx);
93
+
94
+ const document = await collection.findOne(
95
+ buildIdFilter(input.id, input.idType)
96
+ );
97
+
98
+ return { document };
99
+ })
100
+ .build();
101
+
102
+ export type { GetInput, GetOutput };
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Procedure: mongo.documents.insert
3
+ * Insert one or more documents
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, documentObject } from "./input-schemas.js";
9
+ import { z } from "zod";
10
+ import { type MongoDocument } from "../types.js";
11
+
12
+ // Input/Output types
13
+ interface InsertInput extends ScopeInput {
14
+ /** Single document or array of documents */
15
+ documents: MongoDocument | MongoDocument[];
16
+ }
17
+
18
+ interface InsertOutput {
19
+ acknowledged: boolean;
20
+ insertedIds: string[];
21
+ insertedCount: number;
22
+ }
23
+
24
+ // Schemas
25
+ const insertInputSchema = zodAdapter<InsertInput>(
26
+ z.object({
27
+ ...scopeFields(),
28
+ documents: z
29
+ .union([documentObject(), z.array(documentObject())])
30
+ .describe("One document, or a list of documents"),
31
+ })
32
+ );
33
+ const insertOutputSchema = zodAdapter<InsertOutput>(
34
+ z.looseObject({
35
+ acknowledged: z.boolean().describe("True when the server confirmed the write"),
36
+ insertedIds: z.array(z.string()).describe("The ids of the new documents, as text"),
37
+ insertedCount: z.number().describe("The number of new documents"),
38
+ })
39
+ );
40
+
41
+ export const insertProcedure: Procedure<
42
+ InsertInput,
43
+ InsertOutput,
44
+ { description: string; effects?: Effects }
45
+ > = createProcedure()
46
+ .path(["mongo", "documents", "insert"])
47
+ .input(insertInputSchema)
48
+ .output(insertOutputSchema)
49
+ .meta({
50
+ description: "Insert one or more documents",
51
+ effects: { reads: ["mongo", "network"], writes: ["mongo", "network"], destructive: false },
52
+ })
53
+ .handler(async (input: InsertInput, ctx: ProcedureContext) => {
54
+ const { collection } = await collectionFor(input, ctx);
55
+
56
+ const docs = Array.isArray(input.documents)
57
+ ? input.documents
58
+ : [input.documents];
59
+
60
+ if (docs.length === 0) {
61
+ return {
62
+ acknowledged: true,
63
+ insertedIds: [],
64
+ insertedCount: 0,
65
+ };
66
+ }
67
+
68
+ if (docs.length === 1) {
69
+ const result = await collection.insertOne(docs[0]!);
70
+ return {
71
+ acknowledged: result.acknowledged,
72
+ insertedIds: [String(result.insertedId)],
73
+ insertedCount: result.acknowledged ? 1 : 0,
74
+ };
75
+ }
76
+
77
+ const result = await collection.insertMany(docs);
78
+ return {
79
+ acknowledged: result.acknowledged,
80
+ insertedIds: Object.values(result.insertedIds).map(String),
81
+ insertedCount: result.insertedCount,
82
+ };
83
+ })
84
+ .build();
85
+
86
+ export type { InsertInput, InsertOutput };
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Procedure: mongo.documents.update
3
+ * Update documents matching a filter
4
+ */
5
+
6
+ import { createProcedure, type Effects, type Procedure, type ProcedureContext, zodAdapter } from "@procedures/client";
7
+ import { collectionFor } from "../scope.js";
8
+ import { described, documentObject, scopeFields, targetFields } from "./input-schemas.js";
9
+ import { z } from "zod";
10
+ import type { DocumentUpdate } from "../types.js";
11
+ import { parseTarget, targetFilter, upsertFilter, type TargetInput } from "./target.js";
12
+
13
+ // Input/Output types
14
+ /** The input: exactly one of `id` or `filter`, the update, and the scope fields. */
15
+ interface UpdateInput extends TargetInput {
16
+ /** Update operations */
17
+ update: DocumentUpdate;
18
+ /** Insert if not found */
19
+ upsert?: boolean;
20
+ }
21
+
22
+ interface UpdateOutput {
23
+ acknowledged: boolean;
24
+ matchedCount: number;
25
+ modifiedCount: number;
26
+ upsertedId: string | null;
27
+ upsertedCount: number;
28
+ }
29
+
30
+ function parseUpdateInput(data: unknown): UpdateInput {
31
+ const procedure = "mongo.documents.update";
32
+ const { target, raw } = parseTarget(procedure, data, ["update", "upsert"]);
33
+ const update = raw["update"];
34
+ if (typeof update !== "object" || update === null) {
35
+ throw new Error(`${procedure}: update must be an object (or a pipeline array)`);
36
+ }
37
+ const result: UpdateInput = { ...target, update: update as DocumentUpdate };
38
+ const upsert = raw["upsert"];
39
+ if (upsert !== undefined) {
40
+ if (typeof upsert !== "boolean") throw new Error(`${procedure}: upsert must be a boolean`);
41
+ result.upsert = upsert;
42
+ }
43
+ return result;
44
+ }
45
+
46
+ // Schemas
47
+ const updateInputSchema = described<UpdateInput>(
48
+ parseUpdateInput,
49
+ z.object({
50
+ ...scopeFields(),
51
+ ...targetFields(),
52
+ update: documentObject().describe("The update operations, for example { $set: { status: \"done\" } }"),
53
+ upsert: z.boolean().optional().describe("Insert a document when none matches"),
54
+ })
55
+ );
56
+ const updateOutputSchema = zodAdapter<UpdateOutput>(
57
+ z.looseObject({
58
+ acknowledged: z.boolean().describe("True when the server confirmed the write"),
59
+ matchedCount: z.number().describe("The number of documents that matched"),
60
+ modifiedCount: z.number().describe("The number of documents that changed"),
61
+ upsertedId: z.string().nullable().describe("The id of the new document of an upsert, as text, or null"),
62
+ upsertedCount: z.number().describe("The number of documents that an upsert made"),
63
+ })
64
+ );
65
+
66
+ export const updateProcedure: Procedure<
67
+ UpdateInput,
68
+ UpdateOutput,
69
+ { description: string; effects?: Effects }
70
+ > = createProcedure()
71
+ .path(["mongo", "documents", "update"])
72
+ .input(updateInputSchema)
73
+ .output(updateOutputSchema)
74
+ .meta({
75
+ description: "Update documents matching a filter",
76
+ effects: { reads: ["mongo", "network"], writes: ["mongo", "network"] },
77
+ })
78
+ .handler(async (input: UpdateInput, ctx: ProcedureContext) => {
79
+ const { collection } = await collectionFor(input, ctx);
80
+ const upsert = input.upsert ?? false;
81
+ const filter = upsert ? await upsertFilter(collection, input) : targetFilter(input);
82
+
83
+ const result = input.multi
84
+ ? await collection.updateMany(filter, input.update, { upsert })
85
+ : await collection.updateOne(filter, input.update, { upsert });
86
+ return {
87
+ acknowledged: result.acknowledged,
88
+ matchedCount: result.matchedCount,
89
+ modifiedCount: result.modifiedCount,
90
+ upsertedId: result.upsertedId ? String(result.upsertedId) : null,
91
+ upsertedCount: result.upsertedCount,
92
+ };
93
+ })
94
+ .build();
95
+
96
+ export type { UpdateInput, UpdateOutput };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Procedure Barrel Export
3
+ */
4
+
5
+ // Database procedures
6
+ export { pingProcedure } from "./database.ping.js";
7
+ export { infoProcedure } from "./database.info.js";
8
+
9
+ // Collection procedures
10
+ export { listProcedure } from "./collections.list.js";
11
+ export { createCollectionProcedure } from "./collections.create.js";
12
+ export { dropProcedure } from "./collections.drop.js";
13
+ export { statsProcedure } from "./collections.stats.js";
14
+
15
+ // Document procedures
16
+ export { findProcedure } from "./documents.find.js";
17
+ export { getProcedure } from "./documents.get.js";
18
+ export { insertProcedure } from "./documents.insert.js";
19
+ export { updateProcedure } from "./documents.update.js";
20
+ export { deleteProcedure } from "./documents.delete.js";
21
+ export { countProcedure } from "./documents.count.js";
22
+ export { aggregateProcedure } from "./documents.aggregate.js";
23
+
24
+ // Index procedures
25
+ export { listIndexesProcedure } from "./indexes.list.js";
26
+ export { createIndexProcedure } from "./indexes.create.js";
27
+ export { dropIndexProcedure } from "./indexes.drop.js";
28
+
29
+ // Schema helper
30
+ export { schema } from "./schema.js";
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Procedure: mongo.indexes.create
3
+ * Create an index 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 { CreateIndexesOptions } from "mongodb";
11
+ import { type IndexSpec, type IndexOptions } from "../types.js";
12
+
13
+ // Input/Output types
14
+ interface CreateIndexInput extends ScopeInput {
15
+ keys: IndexSpec;
16
+ options?: IndexOptions | undefined;
17
+ }
18
+
19
+ interface CreateIndexOutput {
20
+ acknowledged: boolean;
21
+ indexName: string;
22
+ }
23
+
24
+ // Schemas
25
+ const createIndexInputSchema = zodAdapter<CreateIndexInput>(
26
+ z.object({
27
+ ...scopeFields(),
28
+ keys: z
29
+ .record(z.string(), z.union([z.literal(1), z.literal(-1), z.literal("text"), z.literal("2dsphere")]))
30
+ .describe('The fields of the index and their order: 1, -1, "text" or "2dsphere"'),
31
+ options: z
32
+ .looseObject({
33
+ name: z.string().optional().describe("The name of the index"),
34
+ unique: z.boolean().optional().describe("A unique index"),
35
+ sparse: z.boolean().optional().describe("A sparse index"),
36
+ })
37
+ .optional()
38
+ .describe("The options of the index"),
39
+ })
40
+ );
41
+ const createIndexOutputSchema = zodAdapter<CreateIndexOutput>(
42
+ z.looseObject({
43
+ acknowledged: z.boolean().describe("True when the server confirmed the write"),
44
+ indexName: z.string().describe("The name of the new index"),
45
+ })
46
+ );
47
+
48
+ export const createIndexProcedure: Procedure<
49
+ CreateIndexInput,
50
+ CreateIndexOutput,
51
+ { description: string; effects?: Effects }
52
+ > = createProcedure()
53
+ .path(["mongo", "indexes", "create"])
54
+ .input(createIndexInputSchema)
55
+ .output(createIndexOutputSchema)
56
+ .meta({
57
+ description: "Create an index on a collection",
58
+ // A second call with the same keys and options keeps the existing index
59
+ effects: { reads: ["mongo", "network"], writes: ["mongo", "network"], destructive: false, idempotent: true },
60
+ })
61
+ .handler(async (input: CreateIndexInput, ctx: ProcedureContext) => {
62
+ const { collection } = await collectionFor(input, ctx);
63
+
64
+ // The schema leaves out an option that the input does not give: no option is undefined at run time
65
+ const indexName = await collection.createIndex(input.keys, input.options as CreateIndexesOptions | undefined);
66
+
67
+ return {
68
+ acknowledged: true,
69
+ indexName,
70
+ };
71
+ })
72
+ .build();
73
+
74
+ export type { CreateIndexInput, CreateIndexOutput };