sluice-orm 0.1.1 → 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.
- package/README.md +40 -7
- package/dist/index.cjs +199 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +227 -28
- package/dist/index.d.ts +227 -28
- package/dist/index.js +194 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
**Type-safe MongoDB aggregation pipeline builder** where every stage's output type becomes the next stage's input — fully inferred, zero runtime overhead.
|
|
6
6
|
|
|
7
|
+
📚 **[Full Documentation](https://drttnk.github.io/sluice-orm/)** | 📖 **[Advanced Typings Showcase](https://drttnk.github.io/sluice-orm/docs/advanced-typings)**
|
|
8
|
+
|
|
7
9
|
```typescript
|
|
8
10
|
const result = await users
|
|
9
11
|
.aggregate(
|
|
10
|
-
$match(
|
|
12
|
+
$match($ => ({ age: { $gte: 18 } })),
|
|
11
13
|
$group($ => ({
|
|
12
14
|
_id: "$department",
|
|
13
15
|
avgAge: $.avg("$age"),
|
|
@@ -16,7 +18,7 @@ const result = await users
|
|
|
16
18
|
$sort({ headcount: -1 }),
|
|
17
19
|
)
|
|
18
20
|
.toList();
|
|
19
|
-
// result: { _id: string; avgAge: number; headcount: number }[]
|
|
21
|
+
// result: { _id: string; avgAge: number | null; headcount: number }[]
|
|
20
22
|
```
|
|
21
23
|
|
|
22
24
|
**What makes it different:** every `$field` reference, every expression operator, every accumulator is validated against your document schema at compile time. `$.multiply("$name", 2)` won't compile — it knows `name` is a `string`.
|
|
@@ -105,6 +107,37 @@ await users.bulkWrite([
|
|
|
105
107
|
]);
|
|
106
108
|
```
|
|
107
109
|
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Effect Support
|
|
113
|
+
|
|
114
|
+
For teams using [Effect](https://effect.website/), Sluice provides a dedicated `registryEffect` that wraps all operations in `Effect.Effect`.
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { Effect } from "effect";
|
|
118
|
+
import { registryEffect } from "sluice-orm";
|
|
119
|
+
|
|
120
|
+
const db = registryEffect("8.0", { users: UserSchema })(client.db("myapp"));
|
|
121
|
+
|
|
122
|
+
// All operations return Effect.Effect<T, Error>
|
|
123
|
+
const findEffect = db.users.find(() => ({ age: { $gte: 18 } })).toOne();
|
|
124
|
+
const user = await Effect.runPromise(findEffect);
|
|
125
|
+
|
|
126
|
+
// Compose multiple operations
|
|
127
|
+
const program = Effect.gen(function* () {
|
|
128
|
+
yield* db.users.insertOne({ _id: "u1", name: "Alice", age: 30 }).execute();
|
|
129
|
+
const user = yield* db.users.find(() => ({ _id: "u1" })).toOne();
|
|
130
|
+
yield* db.users.updateOne(() => ({ _id: "u1" }), { $inc: { age: 1 } }).execute();
|
|
131
|
+
const updated = yield* db.users.find(() => ({ _id: "u1" })).toOne();
|
|
132
|
+
return { before: user?.age, after: updated?.age };
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const result = await Effect.runPromise(program);
|
|
136
|
+
// result: { before: 30, after: 31 }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
108
141
|
### Update pipelines
|
|
109
142
|
|
|
110
143
|
```typescript
|
|
@@ -174,18 +207,18 @@ $project($ => ({
|
|
|
174
207
|
## Running Tests
|
|
175
208
|
|
|
176
209
|
```bash
|
|
177
|
-
#
|
|
178
|
-
npm test
|
|
210
|
+
# Type checks only (what CI runs)
|
|
211
|
+
npm run test:types
|
|
179
212
|
|
|
180
213
|
# Runtime tests only
|
|
181
214
|
npm run test:runtime
|
|
182
215
|
|
|
183
|
-
#
|
|
184
|
-
npm
|
|
216
|
+
# Full test suite (format + type checks + runtime tests — precommit)
|
|
217
|
+
npm test
|
|
185
218
|
```
|
|
186
219
|
|
|
187
220
|
## Build
|
|
188
221
|
|
|
189
222
|
```bash
|
|
190
|
-
npm run build
|
|
223
|
+
npm run build
|
|
191
224
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -57,12 +57,17 @@ __export(index_exports, {
|
|
|
57
57
|
$unwind: () => $unwind,
|
|
58
58
|
AccumulatorBuilder: () => AccumulatorBuilder,
|
|
59
59
|
ExprBuilder: () => ExprBuilder,
|
|
60
|
+
MongoDbClient: () => MongoDbClient,
|
|
61
|
+
MongoError: () => MongoError,
|
|
60
62
|
Ret: () => Ret,
|
|
61
63
|
WindowBuilder: () => WindowBuilder,
|
|
62
64
|
collection: () => collection,
|
|
65
|
+
collectionEffect: () => collectionEffect,
|
|
63
66
|
functionToString: () => functionToString,
|
|
67
|
+
makeMongoDbClientLayer: () => makeMongoDbClientLayer,
|
|
64
68
|
migrate: () => migrate,
|
|
65
69
|
registry: () => registry,
|
|
70
|
+
registryEffect: () => registryEffect,
|
|
66
71
|
resolveAccumulator: () => resolveAccumulator
|
|
67
72
|
});
|
|
68
73
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -929,6 +934,195 @@ var registry = (version, schemas) => (client) => {
|
|
|
929
934
|
Object.entries(schemas).map(([name, schema]) => [name, bind(name, schema)])
|
|
930
935
|
);
|
|
931
936
|
};
|
|
937
|
+
|
|
938
|
+
// src/registryEffect.ts
|
|
939
|
+
var import_effect = require("effect");
|
|
940
|
+
var MongoError = class extends import_effect.Data.TaggedError("MongoError") {
|
|
941
|
+
};
|
|
942
|
+
var MongoDbClient = class extends import_effect.Context.Tag("MongoDbClient")() {
|
|
943
|
+
};
|
|
944
|
+
var wrapMongoOperation = (operation, fn) => import_effect.Effect.tryPromise({
|
|
945
|
+
try: fn,
|
|
946
|
+
catch: (cause) => new MongoError({
|
|
947
|
+
operation,
|
|
948
|
+
cause,
|
|
949
|
+
message: cause instanceof Error ? cause.message : String(cause)
|
|
950
|
+
})
|
|
951
|
+
});
|
|
952
|
+
function createCrudMethodsEffect(mongoCol) {
|
|
953
|
+
const extractFilter = (filterFn) => {
|
|
954
|
+
if (!filterFn) return {};
|
|
955
|
+
return filterFn({});
|
|
956
|
+
};
|
|
957
|
+
return {
|
|
958
|
+
find: ((filterFn, options) => ({
|
|
959
|
+
toList: () => wrapMongoOperation("find.toList", () => {
|
|
960
|
+
const filter = filterFn ? extractFilter(filterFn) : {};
|
|
961
|
+
let cursor = mongoCol.find(filter);
|
|
962
|
+
if (options?.sort) cursor = cursor.sort(options.sort);
|
|
963
|
+
if (options?.skip) cursor = cursor.skip(options.skip);
|
|
964
|
+
if (options?.limit) cursor = cursor.limit(options.limit);
|
|
965
|
+
if (options?.projection) cursor = cursor.project(options.projection);
|
|
966
|
+
if (options?.hint) cursor = cursor.hint(options.hint);
|
|
967
|
+
if (options?.maxTimeMS) cursor = cursor.maxTimeMS(options.maxTimeMS);
|
|
968
|
+
if (options?.collation) cursor = cursor.collation(options.collation);
|
|
969
|
+
if (options?.comment) cursor = cursor.comment(options.comment);
|
|
970
|
+
return cursor.toArray();
|
|
971
|
+
}),
|
|
972
|
+
toOne: () => wrapMongoOperation("find.toOne", () => {
|
|
973
|
+
const filter = filterFn ? extractFilter(filterFn) : {};
|
|
974
|
+
return mongoCol.findOne(
|
|
975
|
+
filter,
|
|
976
|
+
options
|
|
977
|
+
);
|
|
978
|
+
})
|
|
979
|
+
})),
|
|
980
|
+
findOne: ((filterFn, options) => ({
|
|
981
|
+
toList: () => wrapMongoOperation("findOne.toList", async () => {
|
|
982
|
+
const doc = await mongoCol.findOne(
|
|
983
|
+
filterFn ? extractFilter(filterFn) : {},
|
|
984
|
+
options
|
|
985
|
+
);
|
|
986
|
+
return doc ? [doc] : [];
|
|
987
|
+
}),
|
|
988
|
+
toOne: () => wrapMongoOperation(
|
|
989
|
+
"findOne.toOne",
|
|
990
|
+
() => mongoCol.findOne(
|
|
991
|
+
filterFn ? extractFilter(filterFn) : {},
|
|
992
|
+
options
|
|
993
|
+
)
|
|
994
|
+
)
|
|
995
|
+
})),
|
|
996
|
+
insertOne: (doc) => ({
|
|
997
|
+
execute: () => wrapMongoOperation("insertOne", () => mongoCol.insertOne(doc))
|
|
998
|
+
}),
|
|
999
|
+
insertMany: (docs) => ({
|
|
1000
|
+
execute: () => wrapMongoOperation("insertMany", () => mongoCol.insertMany(docs))
|
|
1001
|
+
}),
|
|
1002
|
+
updateOne: ((filterFn, updateArg, options) => {
|
|
1003
|
+
const filter = extractFilter(filterFn);
|
|
1004
|
+
const updateDoc = typeof updateArg === "function" ? updateArg(update()).stages : updateArg;
|
|
1005
|
+
return {
|
|
1006
|
+
execute: () => wrapMongoOperation(
|
|
1007
|
+
"updateOne",
|
|
1008
|
+
() => mongoCol.updateOne(filter, updateDoc, options)
|
|
1009
|
+
)
|
|
1010
|
+
};
|
|
1011
|
+
}),
|
|
1012
|
+
updateMany: ((filterFn, updateArg, options) => {
|
|
1013
|
+
const filter = extractFilter(filterFn);
|
|
1014
|
+
const updateDoc = typeof updateArg === "function" ? updateArg(update()).stages : updateArg;
|
|
1015
|
+
return {
|
|
1016
|
+
execute: () => wrapMongoOperation(
|
|
1017
|
+
"updateMany",
|
|
1018
|
+
() => mongoCol.updateMany(filter, updateDoc, options)
|
|
1019
|
+
)
|
|
1020
|
+
};
|
|
1021
|
+
}),
|
|
1022
|
+
replaceOne: ((filterFn, replacement, options) => ({
|
|
1023
|
+
execute: () => wrapMongoOperation(
|
|
1024
|
+
"replaceOne",
|
|
1025
|
+
() => mongoCol.replaceOne(
|
|
1026
|
+
extractFilter(filterFn),
|
|
1027
|
+
replacement,
|
|
1028
|
+
options
|
|
1029
|
+
)
|
|
1030
|
+
)
|
|
1031
|
+
})),
|
|
1032
|
+
deleteOne: ((filterFn) => ({
|
|
1033
|
+
execute: () => wrapMongoOperation("deleteOne", () => mongoCol.deleteOne(extractFilter(filterFn)))
|
|
1034
|
+
})),
|
|
1035
|
+
deleteMany: ((filterFn) => ({
|
|
1036
|
+
execute: () => wrapMongoOperation("deleteMany", () => mongoCol.deleteMany(extractFilter(filterFn)))
|
|
1037
|
+
})),
|
|
1038
|
+
findOneAndDelete: ((filterFn, options) => ({
|
|
1039
|
+
execute: () => wrapMongoOperation(
|
|
1040
|
+
"findOneAndDelete",
|
|
1041
|
+
() => mongoCol.findOneAndDelete(extractFilter(filterFn), options).then((r) => r)
|
|
1042
|
+
)
|
|
1043
|
+
})),
|
|
1044
|
+
findOneAndReplace: ((filterFn, replacement, options) => ({
|
|
1045
|
+
execute: () => wrapMongoOperation(
|
|
1046
|
+
"findOneAndReplace",
|
|
1047
|
+
() => mongoCol.findOneAndReplace(
|
|
1048
|
+
extractFilter(filterFn),
|
|
1049
|
+
replacement,
|
|
1050
|
+
options
|
|
1051
|
+
).then((r) => r)
|
|
1052
|
+
)
|
|
1053
|
+
})),
|
|
1054
|
+
findOneAndUpdate: ((filterFn, updateArg, options) => ({
|
|
1055
|
+
execute: () => wrapMongoOperation(
|
|
1056
|
+
"findOneAndUpdate",
|
|
1057
|
+
() => mongoCol.findOneAndUpdate(
|
|
1058
|
+
extractFilter(filterFn),
|
|
1059
|
+
updateArg,
|
|
1060
|
+
options
|
|
1061
|
+
).then((r) => r)
|
|
1062
|
+
)
|
|
1063
|
+
})),
|
|
1064
|
+
countDocuments: ((filterFn, options) => ({
|
|
1065
|
+
execute: () => wrapMongoOperation(
|
|
1066
|
+
"countDocuments",
|
|
1067
|
+
() => mongoCol.countDocuments(
|
|
1068
|
+
filterFn ? extractFilter(filterFn) : {},
|
|
1069
|
+
options
|
|
1070
|
+
)
|
|
1071
|
+
)
|
|
1072
|
+
})),
|
|
1073
|
+
estimatedDocumentCount: () => ({
|
|
1074
|
+
execute: () => wrapMongoOperation("estimatedDocumentCount", () => mongoCol.estimatedDocumentCount())
|
|
1075
|
+
}),
|
|
1076
|
+
distinct: ((field, filterFn) => ({
|
|
1077
|
+
execute: () => wrapMongoOperation(
|
|
1078
|
+
"distinct",
|
|
1079
|
+
() => mongoCol.distinct(field, filterFn ? extractFilter(filterFn) : {})
|
|
1080
|
+
)
|
|
1081
|
+
})),
|
|
1082
|
+
bulkWrite: ((operations, options) => ({
|
|
1083
|
+
execute: () => wrapMongoOperation(
|
|
1084
|
+
"bulkWrite",
|
|
1085
|
+
() => mongoCol.bulkWrite(operations, options)
|
|
1086
|
+
)
|
|
1087
|
+
}))
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
var collectionEffect = (name, _schema, mongoCol) => {
|
|
1091
|
+
const col = {
|
|
1092
|
+
__collectionName: name,
|
|
1093
|
+
__collectionType: {}
|
|
1094
|
+
};
|
|
1095
|
+
const crud = createCrudMethodsEffect(mongoCol);
|
|
1096
|
+
return Object.assign(col, {
|
|
1097
|
+
aggregate: ((...stages) => {
|
|
1098
|
+
let agg = { collectionName: name, stages: [] };
|
|
1099
|
+
for (const s of stages) {
|
|
1100
|
+
agg = s(agg);
|
|
1101
|
+
}
|
|
1102
|
+
return {
|
|
1103
|
+
...agg,
|
|
1104
|
+
toList: () => wrapMongoOperation(
|
|
1105
|
+
"aggregate",
|
|
1106
|
+
() => mongoCol.aggregate(agg.stages).toArray()
|
|
1107
|
+
),
|
|
1108
|
+
toMQL: () => JSON.stringify(agg.stages, null, 2)
|
|
1109
|
+
};
|
|
1110
|
+
}),
|
|
1111
|
+
...crud
|
|
1112
|
+
});
|
|
1113
|
+
};
|
|
1114
|
+
var registryEffect = (_version, schemas) => import_effect.Effect.gen(function* () {
|
|
1115
|
+
const { db: client } = yield* MongoDbClient;
|
|
1116
|
+
const bind = (name, schema) => collectionEffect(
|
|
1117
|
+
name,
|
|
1118
|
+
schema,
|
|
1119
|
+
client.collection(name)
|
|
1120
|
+
);
|
|
1121
|
+
return Object.fromEntries(
|
|
1122
|
+
Object.entries(schemas).map(([name, schema]) => [name, bind(name, schema)])
|
|
1123
|
+
);
|
|
1124
|
+
});
|
|
1125
|
+
var makeMongoDbClientLayer = (db) => import_effect.Layer.succeed(MongoDbClient, { db });
|
|
932
1126
|
// Annotate the CommonJS export names for ESM import in node:
|
|
933
1127
|
0 && (module.exports = {
|
|
934
1128
|
$addFields,
|
|
@@ -966,12 +1160,17 @@ var registry = (version, schemas) => (client) => {
|
|
|
966
1160
|
$unwind,
|
|
967
1161
|
AccumulatorBuilder,
|
|
968
1162
|
ExprBuilder,
|
|
1163
|
+
MongoDbClient,
|
|
1164
|
+
MongoError,
|
|
969
1165
|
Ret,
|
|
970
1166
|
WindowBuilder,
|
|
971
1167
|
collection,
|
|
1168
|
+
collectionEffect,
|
|
972
1169
|
functionToString,
|
|
1170
|
+
makeMongoDbClientLayer,
|
|
973
1171
|
migrate,
|
|
974
1172
|
registry,
|
|
1173
|
+
registryEffect,
|
|
975
1174
|
resolveAccumulator
|
|
976
1175
|
});
|
|
977
1176
|
//# sourceMappingURL=index.cjs.map
|