@procedures/mongo 0.0.0-stage → 0.1.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/CHANGELOG.md +5 -0
- package/LICENSE +21 -0
- package/README.md +1322 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/connection.d.ts +93 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/connection.js +147 -0
- package/dist/connection.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +45 -0
- package/dist/index.js.map +1 -0
- package/dist/module.d.ts +20 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +58 -0
- package/dist/module.js.map +1 -0
- package/dist/procedures/collections.create.d.ts +20 -0
- package/dist/procedures/collections.create.d.ts.map +1 -0
- package/dist/procedures/collections.create.js +43 -0
- package/dist/procedures/collections.create.js.map +1 -0
- package/dist/procedures/collections.drop.d.ts +19 -0
- package/dist/procedures/collections.drop.d.ts.map +1 -0
- package/dist/procedures/collections.drop.js +36 -0
- package/dist/procedures/collections.drop.js.map +1 -0
- package/dist/procedures/collections.list.d.ts +16 -0
- package/dist/procedures/collections.list.d.ts.map +1 -0
- package/dist/procedures/collections.list.js +31 -0
- package/dist/procedures/collections.list.js.map +1 -0
- package/dist/procedures/collections.stats.d.ts +15 -0
- package/dist/procedures/collections.stats.d.ts.map +1 -0
- package/dist/procedures/collections.stats.js +38 -0
- package/dist/procedures/collections.stats.js.map +1 -0
- package/dist/procedures/database.info.d.ts +15 -0
- package/dist/procedures/database.info.d.ts.map +1 -0
- package/dist/procedures/database.info.js +38 -0
- package/dist/procedures/database.info.js.map +1 -0
- package/dist/procedures/database.ping.d.ts +17 -0
- package/dist/procedures/database.ping.d.ts.map +1 -0
- package/dist/procedures/database.ping.js +31 -0
- package/dist/procedures/database.ping.js.map +1 -0
- package/dist/procedures/documents.aggregate.d.ts +19 -0
- package/dist/procedures/documents.aggregate.d.ts.map +1 -0
- package/dist/procedures/documents.aggregate.js +39 -0
- package/dist/procedures/documents.aggregate.js.map +1 -0
- package/dist/procedures/documents.count.d.ts +18 -0
- package/dist/procedures/documents.count.d.ts.map +1 -0
- package/dist/procedures/documents.count.js +29 -0
- package/dist/procedures/documents.count.js.map +1 -0
- package/dist/procedures/documents.delete.d.ts +17 -0
- package/dist/procedures/documents.delete.d.ts.map +1 -0
- package/dist/procedures/documents.delete.js +31 -0
- package/dist/procedures/documents.delete.js.map +1 -0
- package/dist/procedures/documents.find.d.ts +23 -0
- package/dist/procedures/documents.find.d.ts.map +1 -0
- package/dist/procedures/documents.find.js +103 -0
- package/dist/procedures/documents.find.js.map +1 -0
- package/dist/procedures/documents.get.d.ts +20 -0
- package/dist/procedures/documents.get.d.ts.map +1 -0
- package/dist/procedures/documents.get.js +54 -0
- package/dist/procedures/documents.get.js.map +1 -0
- package/dist/procedures/documents.insert.d.ts +21 -0
- package/dist/procedures/documents.insert.d.ts.map +1 -0
- package/dist/procedures/documents.insert.js +55 -0
- package/dist/procedures/documents.insert.js.map +1 -0
- package/dist/procedures/documents.update.d.ts +26 -0
- package/dist/procedures/documents.update.d.ts.map +1 -0
- package/dist/procedures/documents.update.js +61 -0
- package/dist/procedures/documents.update.js.map +1 -0
- package/dist/procedures/index.d.ts +21 -0
- package/dist/procedures/index.d.ts.map +1 -0
- package/dist/procedures/index.js +26 -0
- package/dist/procedures/index.js.map +1 -0
- package/dist/procedures/indexes.create.d.ts +20 -0
- package/dist/procedures/indexes.create.d.ts.map +1 -0
- package/dist/procedures/indexes.create.js +44 -0
- package/dist/procedures/indexes.create.js.map +1 -0
- package/dist/procedures/indexes.drop.d.ts +17 -0
- package/dist/procedures/indexes.drop.d.ts.map +1 -0
- package/dist/procedures/indexes.drop.js +30 -0
- package/dist/procedures/indexes.drop.js.map +1 -0
- package/dist/procedures/indexes.list.d.ts +17 -0
- package/dist/procedures/indexes.list.d.ts.map +1 -0
- package/dist/procedures/indexes.list.js +40 -0
- package/dist/procedures/indexes.list.js.map +1 -0
- package/dist/procedures/input-schemas.d.ts +45 -0
- package/dist/procedures/input-schemas.d.ts.map +1 -0
- package/dist/procedures/input-schemas.js +52 -0
- package/dist/procedures/input-schemas.js.map +1 -0
- package/dist/procedures/schema.d.ts +2 -0
- package/dist/procedures/schema.d.ts.map +1 -0
- package/dist/procedures/schema.js +2 -0
- package/dist/procedures/schema.js.map +1 -0
- package/dist/procedures/target.d.ts +44 -0
- package/dist/procedures/target.d.ts.map +1 -0
- package/dist/procedures/target.js +98 -0
- package/dist/procedures/target.js.map +1 -0
- package/dist/procedures.types.d.ts +129 -0
- package/dist/procedures.types.d.ts.map +1 -0
- package/dist/procedures.types.js +24 -0
- package/dist/procedures.types.js.map +1 -0
- package/dist/register.d.ts +8 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +14 -0
- package/dist/register.js.map +1 -0
- package/dist/scope.d.ts +33 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +52 -0
- package/dist/scope.js.map +1 -0
- package/dist/storage/index.d.ts +6 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +5 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/mongo-storage.d.ts +50 -0
- package/dist/storage/mongo-storage.d.ts.map +1 -0
- package/dist/storage/mongo-storage.js +109 -0
- package/dist/storage/mongo-storage.js.map +1 -0
- package/dist/types.d.ts +239 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +58 -0
- package/dist/types.js.map +1 -0
- package/package.json +85 -3
- package/src/connection.ts +198 -0
- package/src/index.ts +159 -0
- package/src/module.ts +62 -0
- package/src/procedures/collections.create.ts +69 -0
- package/src/procedures/collections.drop.ts +62 -0
- package/src/procedures/collections.list.ts +53 -0
- package/src/procedures/collections.stats.ts +57 -0
- package/src/procedures/database.info.ts +58 -0
- package/src/procedures/database.ping.ts +54 -0
- package/src/procedures/documents.aggregate.ts +68 -0
- package/src/procedures/documents.count.ts +52 -0
- package/src/procedures/documents.delete.ts +54 -0
- package/src/procedures/documents.find.ts +147 -0
- package/src/procedures/documents.get.ts +98 -0
- package/src/procedures/documents.insert.ts +83 -0
- package/src/procedures/documents.update.ts +93 -0
- package/src/procedures/index.ts +30 -0
- package/src/procedures/indexes.create.ts +70 -0
- package/src/procedures/indexes.drop.ts +53 -0
- package/src/procedures/indexes.list.ts +64 -0
- package/src/procedures/input-schemas.ts +63 -0
- package/src/procedures/schema.ts +1 -0
- package/src/procedures/target.ts +123 -0
- package/src/procedures.types.ts +162 -0
- package/src/register.ts +17 -0
- package/src/scope.ts +69 -0
- package/src/storage/index.ts +6 -0
- package/src/storage/mongo-storage.ts +151 -0
- package/src/types.ts +337 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Procedure: mongo.documents.delete
|
|
3
|
+
* Delete documents matching a filter
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createProcedure, 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 }
|
|
37
|
+
> = createProcedure()
|
|
38
|
+
.path(["mongo", "documents", "delete"])
|
|
39
|
+
.input(deleteInputSchema)
|
|
40
|
+
.output(deleteOutputSchema)
|
|
41
|
+
.meta({ description: "Delete documents matching a filter" })
|
|
42
|
+
.handler(async (input: DeleteInput, ctx: ProcedureContext) => {
|
|
43
|
+
const { collection } = await collectionFor(input, ctx);
|
|
44
|
+
const filter = targetFilter(input);
|
|
45
|
+
|
|
46
|
+
const result = input.multi ? await collection.deleteMany(filter) : await collection.deleteOne(filter);
|
|
47
|
+
return {
|
|
48
|
+
acknowledged: result.acknowledged,
|
|
49
|
+
deletedCount: result.deletedCount,
|
|
50
|
+
};
|
|
51
|
+
})
|
|
52
|
+
.build();
|
|
53
|
+
|
|
54
|
+
export type { DeleteInput, DeleteOutput };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Procedure: mongo.documents.find
|
|
3
|
+
* Find documents with pagination and filtering
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
createProcedure,
|
|
8
|
+
type Procedure,
|
|
9
|
+
type ProcedureContext,
|
|
10
|
+
zodAdapter,
|
|
11
|
+
} from "@procedures/client";
|
|
12
|
+
import { collectionFor, copyScope, type ScopeInput } from "../scope.js";
|
|
13
|
+
import { described, documentObject, scopeFields } from "./input-schemas.js";
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import {
|
|
16
|
+
type PaginationOutput,
|
|
17
|
+
type DocumentQuery,
|
|
18
|
+
type SortSpec,
|
|
19
|
+
type MongoDocument,
|
|
20
|
+
} from "../types.js";
|
|
21
|
+
|
|
22
|
+
// Input/Output types
|
|
23
|
+
interface FindInput extends ScopeInput {
|
|
24
|
+
query?: DocumentQuery;
|
|
25
|
+
projection?: Record<string, 0 | 1>;
|
|
26
|
+
sort?: SortSpec;
|
|
27
|
+
page?: number;
|
|
28
|
+
limit?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface FindOutput {
|
|
32
|
+
documents: MongoDocument[];
|
|
33
|
+
pagination: PaginationOutput;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Validate find input. Enforces `page`/`limit` as integers >= 1 so a `page: 0`
|
|
38
|
+
* (which produced a negative `skip`) or a string `limit` (which produced a
|
|
39
|
+
* `NaN` limit) can no longer reach the driver. Other fields pass through.
|
|
40
|
+
*/
|
|
41
|
+
function parseFindInput(data: unknown): FindInput {
|
|
42
|
+
if (typeof data !== "object" || data === null) {
|
|
43
|
+
throw new Error("mongo.documents.find: input must be an object");
|
|
44
|
+
}
|
|
45
|
+
const raw = data as Record<string, unknown>;
|
|
46
|
+
const result: FindInput = {};
|
|
47
|
+
copyScope(raw, result);
|
|
48
|
+
|
|
49
|
+
if (raw["query"] !== undefined) {
|
|
50
|
+
result.query = raw["query"] as DocumentQuery;
|
|
51
|
+
}
|
|
52
|
+
if (raw["projection"] !== undefined) {
|
|
53
|
+
result.projection = raw["projection"] as Record<string, 0 | 1>;
|
|
54
|
+
}
|
|
55
|
+
if (raw["sort"] !== undefined) {
|
|
56
|
+
result.sort = raw["sort"] as SortSpec;
|
|
57
|
+
}
|
|
58
|
+
if (raw["page"] !== undefined) {
|
|
59
|
+
const page = raw["page"];
|
|
60
|
+
if (typeof page !== "number" || !Number.isInteger(page) || page < 1) {
|
|
61
|
+
throw new Error("mongo.documents.find: page must be an integer >= 1");
|
|
62
|
+
}
|
|
63
|
+
result.page = page;
|
|
64
|
+
}
|
|
65
|
+
if (raw["limit"] !== undefined) {
|
|
66
|
+
const limit = raw["limit"];
|
|
67
|
+
if (typeof limit !== "number" || !Number.isInteger(limit) || limit < 1) {
|
|
68
|
+
throw new Error("mongo.documents.find: limit must be an integer >= 1");
|
|
69
|
+
}
|
|
70
|
+
result.limit = limit;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Schemas
|
|
77
|
+
const findInputSchema = described<FindInput>(
|
|
78
|
+
parseFindInput,
|
|
79
|
+
z.object({
|
|
80
|
+
...scopeFields(),
|
|
81
|
+
query: documentObject().optional().describe("The filter of the documents (default: all documents)"),
|
|
82
|
+
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)"),
|
|
83
|
+
sort: z.record(z.string(), z.unknown()).optional().describe("The sort order: { field: 1 } ascending, { field: -1 } descending"),
|
|
84
|
+
page: z.number().int().min(1).optional().describe("The page to give, from 1"),
|
|
85
|
+
limit: z.number().int().min(1).optional().describe("The number of documents on a page"),
|
|
86
|
+
})
|
|
87
|
+
);
|
|
88
|
+
const findOutputSchema = zodAdapter<FindOutput>(
|
|
89
|
+
z.looseObject({
|
|
90
|
+
documents: z.array(z.record(z.string(), z.unknown())).describe("The documents of the page"),
|
|
91
|
+
pagination: z.looseObject({
|
|
92
|
+
page: z.number().describe("The page, from 1"),
|
|
93
|
+
limit: z.number().describe("The number of documents on a page"),
|
|
94
|
+
total: z.number().describe("The number of documents that match"),
|
|
95
|
+
totalPages: z.number().describe("The number of pages"),
|
|
96
|
+
hasNext: z.boolean().describe("True when a next page exists"),
|
|
97
|
+
hasPrev: z.boolean().describe("True when a previous page exists"),
|
|
98
|
+
}).describe("The page of the result"),
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
export const findProcedure: Procedure<
|
|
103
|
+
FindInput,
|
|
104
|
+
FindOutput,
|
|
105
|
+
{ description: string }
|
|
106
|
+
> = createProcedure()
|
|
107
|
+
.path(["mongo", "documents", "find"])
|
|
108
|
+
.input(findInputSchema)
|
|
109
|
+
.output(findOutputSchema)
|
|
110
|
+
.meta({ description: "Find documents with pagination and filtering" })
|
|
111
|
+
.handler(async (input: FindInput, ctx: ProcedureContext) => {
|
|
112
|
+
const { collection } = await collectionFor(input, ctx);
|
|
113
|
+
|
|
114
|
+
const query = input.query ?? {};
|
|
115
|
+
const page = input.page ?? 1;
|
|
116
|
+
const limit = Math.min(input.limit ?? 20, 100); // Cap at 100
|
|
117
|
+
const skip = (page - 1) * limit;
|
|
118
|
+
|
|
119
|
+
// Execute query and count in parallel
|
|
120
|
+
const [documents, total] = await Promise.all([
|
|
121
|
+
collection
|
|
122
|
+
.find(query)
|
|
123
|
+
.project(input.projection ?? {})
|
|
124
|
+
.sort(input.sort ?? {})
|
|
125
|
+
.skip(skip)
|
|
126
|
+
.limit(limit)
|
|
127
|
+
.toArray(),
|
|
128
|
+
collection.countDocuments(query),
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
const totalPages = Math.ceil(total / limit);
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
documents,
|
|
135
|
+
pagination: {
|
|
136
|
+
page,
|
|
137
|
+
limit,
|
|
138
|
+
total,
|
|
139
|
+
totalPages,
|
|
140
|
+
hasNext: page < totalPages,
|
|
141
|
+
hasPrev: page > 1,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
})
|
|
145
|
+
.build();
|
|
146
|
+
|
|
147
|
+
export type { FindInput, FindOutput };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Procedure: mongo.documents.get
|
|
3
|
+
* Get a single document by ID
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
createProcedure,
|
|
8
|
+
type Procedure,
|
|
9
|
+
type ProcedureContext,
|
|
10
|
+
zodAdapter,
|
|
11
|
+
} from "@procedures/client";
|
|
12
|
+
import { collectionFor, copyScope, type ScopeInput } from "../scope.js";
|
|
13
|
+
import { described, idTypeField, scopeFields } from "./input-schemas.js";
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import {
|
|
16
|
+
buildIdFilter,
|
|
17
|
+
type IdType,
|
|
18
|
+
type MongoDocument,
|
|
19
|
+
} from "../types.js";
|
|
20
|
+
|
|
21
|
+
// Input/Output types
|
|
22
|
+
interface GetInput extends ScopeInput {
|
|
23
|
+
id: string;
|
|
24
|
+
/** How to interpret the id (default: "auto" — matches ObjectId or string) */
|
|
25
|
+
idType?: IdType;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface GetOutput {
|
|
29
|
+
document: MongoDocument | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Validate get input: `id` must be a non-empty string, and `idType`, when
|
|
34
|
+
* present, must be one of the supported modes.
|
|
35
|
+
*/
|
|
36
|
+
function parseGetInput(data: unknown): GetInput {
|
|
37
|
+
if (typeof data !== "object" || data === null) {
|
|
38
|
+
throw new Error("mongo.documents.get: input must be an object");
|
|
39
|
+
}
|
|
40
|
+
const raw = data as Record<string, unknown>;
|
|
41
|
+
|
|
42
|
+
const id = raw["id"];
|
|
43
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
44
|
+
throw new Error("mongo.documents.get: id must be a non-empty string");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const result: GetInput = { id };
|
|
48
|
+
copyScope(raw, result);
|
|
49
|
+
|
|
50
|
+
const idType = raw["idType"];
|
|
51
|
+
if (idType !== undefined) {
|
|
52
|
+
if (idType !== "auto" && idType !== "objectId" && idType !== "string") {
|
|
53
|
+
throw new Error(
|
|
54
|
+
'mongo.documents.get: idType must be "auto", "objectId", or "string"'
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
result.idType = idType;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Schemas
|
|
64
|
+
const getInputSchema = described<GetInput>(
|
|
65
|
+
parseGetInput,
|
|
66
|
+
z.object({
|
|
67
|
+
...scopeFields(),
|
|
68
|
+
id: z.string().min(1).describe("The id of the document"),
|
|
69
|
+
idType: idTypeField(),
|
|
70
|
+
})
|
|
71
|
+
);
|
|
72
|
+
const getOutputSchema = zodAdapter<GetOutput>(
|
|
73
|
+
z.looseObject({
|
|
74
|
+
document: z.record(z.string(), z.unknown()).nullable().describe("The document, or null when no document has the id"),
|
|
75
|
+
})
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
export const getProcedure: Procedure<
|
|
79
|
+
GetInput,
|
|
80
|
+
GetOutput,
|
|
81
|
+
{ description: string }
|
|
82
|
+
> = createProcedure()
|
|
83
|
+
.path(["mongo", "documents", "get"])
|
|
84
|
+
.input(getInputSchema)
|
|
85
|
+
.output(getOutputSchema)
|
|
86
|
+
.meta({ description: "Get a single document by ID" })
|
|
87
|
+
.handler(async (input: GetInput, ctx: ProcedureContext) => {
|
|
88
|
+
const { collection } = await collectionFor(input, ctx);
|
|
89
|
+
|
|
90
|
+
const document = await collection.findOne(
|
|
91
|
+
buildIdFilter(input.id, input.idType)
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return { document };
|
|
95
|
+
})
|
|
96
|
+
.build();
|
|
97
|
+
|
|
98
|
+
export type { GetInput, GetOutput };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Procedure: mongo.documents.insert
|
|
3
|
+
* Insert one or more documents
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createProcedure, zodAdapter, 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 }
|
|
45
|
+
> = createProcedure()
|
|
46
|
+
.path(["mongo", "documents", "insert"])
|
|
47
|
+
.input(insertInputSchema)
|
|
48
|
+
.output(insertOutputSchema)
|
|
49
|
+
.meta({ description: "Insert one or more documents" })
|
|
50
|
+
.handler(async (input: InsertInput, ctx: ProcedureContext) => {
|
|
51
|
+
const { collection } = await collectionFor(input, ctx);
|
|
52
|
+
|
|
53
|
+
const docs = Array.isArray(input.documents)
|
|
54
|
+
? input.documents
|
|
55
|
+
: [input.documents];
|
|
56
|
+
|
|
57
|
+
if (docs.length === 0) {
|
|
58
|
+
return {
|
|
59
|
+
acknowledged: true,
|
|
60
|
+
insertedIds: [],
|
|
61
|
+
insertedCount: 0,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (docs.length === 1) {
|
|
66
|
+
const result = await collection.insertOne(docs[0]!);
|
|
67
|
+
return {
|
|
68
|
+
acknowledged: result.acknowledged,
|
|
69
|
+
insertedIds: [String(result.insertedId)],
|
|
70
|
+
insertedCount: result.acknowledged ? 1 : 0,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const result = await collection.insertMany(docs);
|
|
75
|
+
return {
|
|
76
|
+
acknowledged: result.acknowledged,
|
|
77
|
+
insertedIds: Object.values(result.insertedIds).map(String),
|
|
78
|
+
insertedCount: result.insertedCount,
|
|
79
|
+
};
|
|
80
|
+
})
|
|
81
|
+
.build();
|
|
82
|
+
|
|
83
|
+
export type { InsertInput, InsertOutput };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Procedure: mongo.documents.update
|
|
3
|
+
* Update documents matching a filter
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createProcedure, 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 }
|
|
70
|
+
> = createProcedure()
|
|
71
|
+
.path(["mongo", "documents", "update"])
|
|
72
|
+
.input(updateInputSchema)
|
|
73
|
+
.output(updateOutputSchema)
|
|
74
|
+
.meta({ description: "Update documents matching a filter" })
|
|
75
|
+
.handler(async (input: UpdateInput, ctx: ProcedureContext) => {
|
|
76
|
+
const { collection } = await collectionFor(input, ctx);
|
|
77
|
+
const upsert = input.upsert ?? false;
|
|
78
|
+
const filter = upsert ? await upsertFilter(collection, input) : targetFilter(input);
|
|
79
|
+
|
|
80
|
+
const result = input.multi
|
|
81
|
+
? await collection.updateMany(filter, input.update, { upsert })
|
|
82
|
+
: await collection.updateOne(filter, input.update, { upsert });
|
|
83
|
+
return {
|
|
84
|
+
acknowledged: result.acknowledged,
|
|
85
|
+
matchedCount: result.matchedCount,
|
|
86
|
+
modifiedCount: result.modifiedCount,
|
|
87
|
+
upsertedId: result.upsertedId ? String(result.upsertedId) : null,
|
|
88
|
+
upsertedCount: result.upsertedCount,
|
|
89
|
+
};
|
|
90
|
+
})
|
|
91
|
+
.build();
|
|
92
|
+
|
|
93
|
+
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,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Procedure: mongo.indexes.create
|
|
3
|
+
* Create an index on a collection
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createProcedure, zodAdapter, 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 }
|
|
52
|
+
> = createProcedure()
|
|
53
|
+
.path(["mongo", "indexes", "create"])
|
|
54
|
+
.input(createIndexInputSchema)
|
|
55
|
+
.output(createIndexOutputSchema)
|
|
56
|
+
.meta({ description: "Create an index on a collection" })
|
|
57
|
+
.handler(async (input: CreateIndexInput, ctx: ProcedureContext) => {
|
|
58
|
+
const { collection } = await collectionFor(input, ctx);
|
|
59
|
+
|
|
60
|
+
// The schema leaves out an option that the input does not give: no option is undefined at run time
|
|
61
|
+
const indexName = await collection.createIndex(input.keys, input.options as CreateIndexesOptions | undefined);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
acknowledged: true,
|
|
65
|
+
indexName,
|
|
66
|
+
};
|
|
67
|
+
})
|
|
68
|
+
.build();
|
|
69
|
+
|
|
70
|
+
export type { CreateIndexInput, CreateIndexOutput };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Procedure: mongo.indexes.drop
|
|
3
|
+
* Drop an index from a collection
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createProcedure, zodAdapter, 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 }
|
|
37
|
+
> = createProcedure()
|
|
38
|
+
.path(["mongo", "indexes", "drop"])
|
|
39
|
+
.input(dropIndexInputSchema)
|
|
40
|
+
.output(dropIndexOutputSchema)
|
|
41
|
+
.meta({ description: "Drop an index from a collection" })
|
|
42
|
+
.handler(async (input: DropIndexInput, ctx: ProcedureContext) => {
|
|
43
|
+
const { collection } = await collectionFor(input, ctx);
|
|
44
|
+
|
|
45
|
+
await collection.dropIndex(input.indexName);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
acknowledged: true,
|
|
49
|
+
};
|
|
50
|
+
})
|
|
51
|
+
.build();
|
|
52
|
+
|
|
53
|
+
export type { DropIndexInput, DropIndexOutput };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Procedure: mongo.indexes.list
|
|
3
|
+
* List indexes on a collection
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createProcedure, zodAdapter, 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 }
|
|
43
|
+
> = createProcedure()
|
|
44
|
+
.path(["mongo", "indexes", "list"])
|
|
45
|
+
.input(listIndexesInputSchema)
|
|
46
|
+
.output(listIndexesOutputSchema)
|
|
47
|
+
.meta({ description: "List indexes on a collection" })
|
|
48
|
+
.handler(async (input: ListIndexesInput, ctx: ProcedureContext) => {
|
|
49
|
+
const { collection } = await collectionFor(input, ctx);
|
|
50
|
+
|
|
51
|
+
const indexes = await collection.listIndexes().toArray();
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
indexes: indexes.map((idx) => ({
|
|
55
|
+
name: idx.name,
|
|
56
|
+
key: idx.key,
|
|
57
|
+
unique: idx.unique,
|
|
58
|
+
sparse: idx.sparse,
|
|
59
|
+
})),
|
|
60
|
+
};
|
|
61
|
+
})
|
|
62
|
+
.build();
|
|
63
|
+
|
|
64
|
+
export type { ListIndexesInput, ListIndexesOutput };
|