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 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(() => ({ age: { $gte: 18 } })),
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
- # Full test suite (type checks + runtime tests)
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
- # Type checks only
184
- npm run test:types
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:ci
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