equipped 5.2.9 → 5.3.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 +95 -0
- package/README.md +362 -34
- package/dist/cjs/cache/adapters/in-memory/index.cjs +824 -0
- package/dist/cjs/cache/adapters/in-memory/index.cjs.map +1 -0
- package/dist/cjs/cache/adapters/in-memory/index.min.cjs +4 -0
- package/dist/cjs/cache/adapters/in-memory/index.min.cjs.map +1 -0
- package/dist/cjs/cache/adapters/redis/index.cjs +844 -0
- package/dist/cjs/cache/adapters/redis/index.cjs.map +1 -0
- package/dist/cjs/cache/adapters/redis/index.min.cjs +4 -0
- package/dist/cjs/cache/adapters/redis/index.min.cjs.map +1 -0
- package/dist/cjs/cache/index.cjs +602 -4
- package/dist/cjs/cache/index.cjs.map +1 -1
- package/dist/cjs/cache/index.min.cjs +3 -1
- package/dist/cjs/cache/index.min.cjs.map +1 -1
- package/dist/cjs/dbs/adapters/mongodb/index.cjs +1314 -0
- package/dist/cjs/dbs/adapters/mongodb/index.cjs.map +1 -0
- package/dist/cjs/dbs/adapters/mongodb/index.min.cjs +4 -0
- package/dist/cjs/dbs/adapters/mongodb/index.min.cjs.map +1 -0
- package/dist/cjs/dbs/index.cjs +965 -6
- package/dist/cjs/dbs/index.cjs.map +1 -1
- package/dist/cjs/dbs/index.min.cjs +3 -1
- package/dist/cjs/dbs/index.min.cjs.map +1 -1
- package/dist/cjs/errors/index.cjs +127 -8
- package/dist/cjs/errors/index.cjs.map +1 -1
- package/dist/cjs/errors/index.min.cjs +1 -1
- package/dist/cjs/errors/index.min.cjs.map +1 -1
- package/dist/cjs/events/adapters/kafka/index.cjs +845 -0
- package/dist/cjs/events/adapters/kafka/index.cjs.map +1 -0
- package/dist/cjs/events/adapters/kafka/index.min.cjs +4 -0
- package/dist/cjs/events/adapters/kafka/index.min.cjs.map +1 -0
- package/dist/cjs/events/adapters/rabbitmq/index.cjs +836 -0
- package/dist/cjs/events/adapters/rabbitmq/index.cjs.map +1 -0
- package/dist/cjs/events/adapters/rabbitmq/index.min.cjs +4 -0
- package/dist/cjs/events/adapters/rabbitmq/index.min.cjs.map +1 -0
- package/dist/cjs/events/index.cjs +33 -4
- package/dist/cjs/events/index.cjs.map +1 -1
- package/dist/cjs/events/index.min.cjs +1 -1
- package/dist/cjs/events/index.min.cjs.map +1 -1
- package/dist/cjs/index.cjs +588 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.min.cjs +3 -1
- package/dist/cjs/index.min.cjs.map +1 -1
- package/dist/cjs/instance/index.cjs +514 -64
- package/dist/cjs/instance/index.cjs.map +1 -1
- package/dist/cjs/instance/index.min.cjs +3 -3
- package/dist/cjs/instance/index.min.cjs.map +1 -1
- package/dist/cjs/jobs/adapters/redis/index.cjs +979 -0
- package/dist/cjs/jobs/adapters/redis/index.cjs.map +1 -0
- package/dist/cjs/jobs/adapters/redis/index.min.cjs +4 -0
- package/dist/cjs/jobs/adapters/redis/index.min.cjs.map +1 -0
- package/dist/cjs/jobs/index.cjs +55 -2
- package/dist/cjs/jobs/index.cjs.map +1 -1
- package/dist/cjs/jobs/index.min.cjs +1 -1
- package/dist/cjs/jobs/index.min.cjs.map +1 -1
- package/dist/cjs/orm/adapters/in-memory/index.cjs +2551 -0
- package/dist/cjs/orm/adapters/in-memory/index.cjs.map +1 -0
- package/dist/cjs/orm/adapters/in-memory/index.min.cjs +4 -0
- package/dist/cjs/orm/adapters/in-memory/index.min.cjs.map +1 -0
- package/dist/cjs/orm/adapters/json/index.cjs +3518 -0
- package/dist/cjs/orm/adapters/json/index.cjs.map +1 -0
- package/dist/cjs/orm/adapters/json/index.min.cjs +4 -0
- package/dist/cjs/orm/adapters/json/index.min.cjs.map +1 -0
- package/dist/cjs/orm/adapters/mongodb/index.cjs +3299 -0
- package/dist/cjs/orm/adapters/mongodb/index.cjs.map +1 -0
- package/dist/cjs/orm/adapters/mongodb/index.min.cjs +4 -0
- package/dist/cjs/orm/adapters/mongodb/index.min.cjs.map +1 -0
- package/dist/cjs/orm/adapters/postgresql/index.cjs +4114 -0
- package/dist/cjs/orm/adapters/postgresql/index.cjs.map +1 -0
- package/dist/cjs/orm/adapters/postgresql/index.min.cjs +49 -0
- package/dist/cjs/orm/adapters/postgresql/index.min.cjs.map +1 -0
- package/dist/cjs/orm/index.cjs +9216 -0
- package/dist/cjs/orm/index.cjs.map +1 -0
- package/dist/cjs/orm/index.min.cjs +4 -0
- package/dist/cjs/orm/index.min.cjs.map +1 -0
- package/dist/cjs/server/adapters/express/index.cjs +1967 -0
- package/dist/cjs/server/adapters/express/index.cjs.map +1 -0
- package/dist/cjs/server/adapters/express/index.min.cjs +32 -0
- package/dist/cjs/server/adapters/express/index.min.cjs.map +1 -0
- package/dist/cjs/server/adapters/fastify/index.cjs +1974 -0
- package/dist/cjs/server/adapters/fastify/index.cjs.map +1 -0
- package/dist/cjs/server/adapters/fastify/index.min.cjs +32 -0
- package/dist/cjs/server/adapters/fastify/index.min.cjs.map +1 -0
- package/dist/cjs/server/index.cjs +1811 -9
- package/dist/cjs/server/index.cjs.map +1 -1
- package/dist/cjs/server/index.min.cjs +31 -1
- package/dist/cjs/server/index.min.cjs.map +1 -1
- package/dist/cjs/types/index.cjs +18 -1
- package/dist/cjs/types/index.cjs.map +1 -1
- package/dist/cjs/types/index.min.cjs +1 -1
- package/dist/cjs/types/index.min.cjs.map +1 -1
- package/dist/cjs/utilities/index.cjs +862 -7
- package/dist/cjs/utilities/index.cjs.map +1 -1
- package/dist/cjs/utilities/index.min.cjs +3 -1
- package/dist/cjs/utilities/index.min.cjs.map +1 -1
- package/dist/cjs/validations/index.cjs +679 -10
- package/dist/cjs/validations/index.cjs.map +1 -1
- package/dist/cjs/validations/index.min.cjs +3 -1
- package/dist/cjs/validations/index.min.cjs.map +1 -1
- package/dist/esm/cache/adapters/in-memory/index.min.mjs +2 -0
- package/dist/esm/cache/adapters/in-memory/index.min.mjs.map +1 -0
- package/dist/esm/cache/adapters/in-memory/index.mjs +93 -0
- package/dist/esm/cache/adapters/in-memory/index.mjs.map +1 -0
- package/dist/esm/cache/adapters/redis/index.min.mjs +2 -0
- package/dist/esm/cache/adapters/redis/index.mjs +13 -0
- package/dist/esm/cache/index.min.mjs +1 -1
- package/dist/esm/cache/index.min.mjs.map +1 -1
- package/dist/esm/cache/index.mjs +9 -4
- package/dist/esm/cache/index.mjs.map +1 -1
- package/dist/esm/chunk-4JLDXF7T.min.mjs +2 -0
- package/dist/esm/chunk-4JLDXF7T.min.mjs.map +1 -0
- package/dist/{types/events/types/kafka.js → esm/chunk-4MXYZS5R.mjs} +60 -37
- package/dist/esm/chunk-4MXYZS5R.mjs.map +1 -0
- package/dist/esm/chunk-5VW3JAFR.min.mjs +2 -0
- package/dist/esm/chunk-5VW3JAFR.min.mjs.map +1 -0
- package/dist/esm/chunk-AKQBDYUO.mjs +126 -0
- package/dist/esm/chunk-AKQBDYUO.mjs.map +1 -0
- package/dist/esm/chunk-AQY7QVYI.min.mjs +2 -0
- package/dist/esm/chunk-B5QQ32YY.min.mjs +2 -0
- package/dist/esm/chunk-B5QQ32YY.min.mjs.map +1 -0
- package/dist/esm/chunk-BJNIUCPT.min.mjs +2 -0
- package/dist/esm/chunk-BJNIUCPT.min.mjs.map +1 -0
- package/dist/esm/chunk-DKPZJIIR.mjs +29 -0
- package/dist/esm/chunk-DKPZJIIR.mjs.map +1 -0
- package/dist/esm/chunk-DSUJ5FUL.min.mjs +2 -0
- package/dist/esm/chunk-DSUJ5FUL.min.mjs.map +1 -0
- package/dist/esm/chunk-FLAPYQUT.min.mjs +32 -0
- package/dist/esm/chunk-FLAPYQUT.min.mjs.map +1 -0
- package/dist/esm/chunk-J5LGTIGS.mjs +10 -0
- package/dist/esm/chunk-JH6EBXY4.mjs +53 -0
- package/dist/esm/chunk-JH6EBXY4.mjs.map +1 -0
- package/dist/esm/chunk-MDQKODYQ.mjs +2035 -0
- package/dist/esm/chunk-MDQKODYQ.mjs.map +1 -0
- package/dist/esm/chunk-PCAUXBAG.min.mjs +2 -0
- package/dist/esm/chunk-PCAUXBAG.min.mjs.map +1 -0
- package/dist/esm/chunk-PNZNNUMN.mjs +113 -0
- package/dist/esm/chunk-PNZNNUMN.mjs.map +1 -0
- package/dist/esm/chunk-PQCBVQL6.mjs +1280 -0
- package/dist/esm/chunk-PQCBVQL6.mjs.map +1 -0
- package/dist/esm/chunk-QHLKB47N.min.mjs +2 -0
- package/dist/esm/chunk-QHLKB47N.min.mjs.map +1 -0
- package/dist/esm/chunk-SGZNOYC3.min.mjs +2 -0
- package/dist/esm/chunk-SGZNOYC3.min.mjs.map +1 -0
- package/dist/esm/chunk-TVTYDU2D.mjs +795 -0
- package/dist/esm/chunk-TVTYDU2D.mjs.map +1 -0
- package/dist/esm/chunk-VCCS7CAI.mjs +8 -0
- package/dist/esm/chunk-VCCS7CAI.mjs.map +1 -0
- package/dist/esm/chunk-Z2TZ3KR7.mjs +22 -0
- package/dist/esm/chunk-Z2TZ3KR7.mjs.map +1 -0
- package/dist/esm/chunk-ZGMFFK6Y.min.mjs +2 -0
- package/dist/esm/chunk-ZGMFFK6Y.min.mjs.map +1 -0
- package/dist/esm/dbs/adapters/mongodb/index.min.mjs +2 -0
- package/dist/esm/dbs/adapters/mongodb/index.min.mjs.map +1 -0
- package/dist/esm/dbs/adapters/mongodb/index.mjs +419 -0
- package/dist/esm/dbs/adapters/mongodb/index.mjs.map +1 -0
- package/dist/esm/dbs/index.min.mjs +1 -1
- package/dist/esm/dbs/index.min.mjs.map +1 -1
- package/dist/esm/dbs/index.mjs +22 -6
- package/dist/esm/dbs/index.mjs.map +1 -1
- package/dist/esm/errors/index.min.mjs +1 -1
- package/dist/esm/errors/index.min.mjs.map +1 -1
- package/dist/esm/errors/index.mjs +22 -8
- package/dist/esm/errors/index.mjs.map +1 -1
- package/dist/esm/events/adapters/kafka/index.min.mjs +2 -0
- package/dist/esm/events/adapters/kafka/index.min.mjs.map +1 -0
- package/dist/esm/events/adapters/kafka/index.mjs +12 -0
- package/dist/esm/events/adapters/kafka/index.mjs.map +1 -0
- package/dist/esm/events/adapters/rabbitmq/index.min.mjs +2 -0
- package/dist/esm/events/adapters/rabbitmq/index.min.mjs.map +1 -0
- package/dist/esm/events/adapters/rabbitmq/index.mjs +92 -0
- package/dist/esm/events/adapters/rabbitmq/index.mjs.map +1 -0
- package/dist/esm/events/index.min.mjs +1 -1
- package/dist/esm/events/index.min.mjs.map +1 -1
- package/dist/esm/events/index.mjs +7 -4
- package/dist/esm/events/index.mjs.map +1 -1
- package/dist/esm/index.min.mjs +1 -1
- package/dist/esm/index.min.mjs.map +1 -1
- package/dist/esm/index.mjs +8 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/instance/index.min.mjs +1 -3
- package/dist/esm/instance/index.min.mjs.map +1 -1
- package/dist/esm/instance/index.mjs +4 -132
- package/dist/esm/instance/index.mjs.map +1 -1
- package/dist/esm/introspection-32BRYKYY.mjs +10 -0
- package/dist/esm/introspection-32BRYKYY.mjs.map +1 -0
- package/dist/esm/introspection-A72WMWE3.min.mjs +2 -0
- package/dist/esm/introspection-A72WMWE3.min.mjs.map +1 -0
- package/dist/esm/jobs/adapters/redis/index.min.mjs +2 -0
- package/dist/esm/jobs/adapters/redis/index.min.mjs.map +1 -0
- package/dist/esm/jobs/{types/redis.mjs → adapters/redis/index.mjs} +71 -56
- package/dist/esm/jobs/adapters/redis/index.mjs.map +1 -0
- package/dist/esm/jobs/index.min.mjs +1 -1
- package/dist/esm/jobs/index.min.mjs.map +1 -1
- package/dist/esm/jobs/index.mjs +9 -2
- package/dist/esm/jobs/index.mjs.map +1 -1
- package/dist/esm/orm/adapters/in-memory/index.min.mjs +2 -0
- package/dist/esm/orm/adapters/in-memory/index.min.mjs.map +1 -0
- package/dist/esm/orm/adapters/in-memory/index.mjs +11 -0
- package/dist/esm/orm/adapters/in-memory/index.mjs.map +1 -0
- package/dist/esm/orm/adapters/json/index.min.mjs +2 -0
- package/dist/esm/orm/adapters/json/index.min.mjs.map +1 -0
- package/dist/esm/orm/adapters/json/index.mjs +980 -0
- package/dist/esm/orm/adapters/json/index.mjs.map +1 -0
- package/dist/esm/orm/adapters/mongodb/index.min.mjs +2 -0
- package/dist/esm/orm/adapters/mongodb/index.min.mjs.map +1 -0
- package/dist/esm/orm/adapters/mongodb/index.mjs +1539 -0
- package/dist/esm/orm/adapters/mongodb/index.mjs.map +1 -0
- package/dist/esm/orm/adapters/postgresql/index.min.mjs +47 -0
- package/dist/esm/orm/adapters/postgresql/index.min.mjs.map +1 -0
- package/dist/esm/orm/adapters/postgresql/index.mjs +2070 -0
- package/dist/esm/orm/adapters/postgresql/index.mjs.map +1 -0
- package/dist/esm/orm/index.min.mjs +2 -0
- package/dist/esm/orm/index.min.mjs.map +1 -0
- package/dist/esm/orm/index.mjs +7326 -0
- package/dist/esm/orm/index.mjs.map +1 -0
- package/dist/esm/server/adapters/express/index.min.mjs +2 -0
- package/dist/esm/server/adapters/express/index.min.mjs.map +1 -0
- package/dist/esm/server/adapters/express/index.mjs +177 -0
- package/dist/esm/server/adapters/express/index.mjs.map +1 -0
- package/dist/esm/server/adapters/fastify/index.min.mjs +2 -0
- package/dist/esm/server/adapters/fastify/index.min.mjs.map +1 -0
- package/dist/esm/server/adapters/fastify/index.mjs +185 -0
- package/dist/esm/server/adapters/fastify/index.mjs.map +1 -0
- package/dist/esm/server/index.min.mjs +1 -1
- package/dist/esm/server/index.min.mjs.map +1 -1
- package/dist/esm/server/index.mjs +38 -9
- package/dist/esm/server/index.mjs.map +1 -1
- package/dist/esm/types/index.min.mjs +0 -1
- package/dist/esm/types/index.min.mjs.map +1 -1
- package/dist/esm/types/index.mjs +0 -1
- package/dist/esm/types/index.mjs.map +1 -1
- package/dist/esm/utilities/index.min.mjs +1 -1
- package/dist/esm/utilities/index.min.mjs.map +1 -1
- package/dist/esm/utilities/index.mjs +22 -9
- package/dist/esm/utilities/index.mjs.map +1 -1
- package/dist/esm/validations/index.min.mjs +1 -1
- package/dist/esm/validations/index.min.mjs.map +1 -1
- package/dist/esm/validations/index.mjs +8 -18
- package/dist/esm/validations/index.mjs.map +1 -1
- package/dist/types/adapter-Coap9ISX.d.ts +1435 -0
- package/dist/types/{fastify-DLPUK3vD.d.ts → base-DVr0fHil.d.ts} +76 -143
- package/dist/types/cache/adapters/in-memory/index.d.ts +21 -0
- package/dist/types/cache/adapters/in-memory/index.js +92 -0
- package/dist/types/cache/adapters/redis/index.d.ts +73 -0
- package/dist/types/cache/adapters/redis/index.js +12 -0
- package/dist/types/cache/index.d.ts +2 -42
- package/dist/types/cache/index.js +9 -4
- package/dist/types/chunk-3M4PBZAQ.js +125 -0
- package/dist/types/chunk-3TKJGSCZ.js +21 -0
- package/dist/types/chunk-65EHKCDZ.js +112 -0
- package/dist/types/chunk-JQ2FWX4W.js +1279 -0
- package/dist/types/chunk-KZKFYO4V.js +7 -0
- package/dist/types/chunk-LN3A6R4Y.js +28 -0
- package/dist/types/chunk-MLKGABMK.js +9 -0
- package/dist/types/chunk-NJ2FHI3U.js +794 -0
- package/dist/types/chunk-OHFTQGQG.js +52 -0
- package/dist/{esm/events/types/kafka.mjs → types/chunk-XGGGAEET.js} +59 -38
- package/dist/types/chunk-YTY3EFNB.js +2034 -0
- package/dist/types/{core-BrkNSwvc.d.ts → core-tNlIMH9Y.d.ts} +48 -38
- package/dist/types/dbs/adapters/mongodb/index.d.ts +43 -0
- package/dist/types/dbs/adapters/mongodb/index.js +418 -0
- package/dist/types/dbs/index.d.ts +14 -12
- package/dist/types/dbs/index.js +22 -6
- package/dist/types/equippedError-Cmo60Kn-.d.ts +8 -0
- package/dist/types/errors/index.d.ts +3 -3
- package/dist/types/errors/index.js +22 -8
- package/dist/types/events/adapters/kafka/index.d.ts +83 -0
- package/dist/types/events/adapters/kafka/index.js +11 -0
- package/dist/types/events/adapters/rabbitmq/index.d.ts +41 -0
- package/dist/types/events/adapters/rabbitmq/index.js +91 -0
- package/dist/types/events/index.d.ts +11 -11
- package/dist/types/events/index.js +7 -4
- package/dist/types/index.d.ts +2 -25
- package/dist/types/index.js +8 -1
- package/dist/types/instance/index.d.ts +12 -179
- package/dist/types/instance/index.js +4 -132
- package/dist/types/introspection-VOIU3XE6.js +9 -0
- package/dist/types/jobs/adapters/redis/index.d.ts +93 -0
- package/dist/types/jobs/{types/redis.js → adapters/redis/index.js} +70 -55
- package/dist/types/jobs/index.d.ts +19 -35
- package/dist/types/jobs/index.js +9 -2
- package/dist/types/orm/adapters/in-memory/index.d.ts +92 -0
- package/dist/types/orm/adapters/in-memory/index.js +10 -0
- package/dist/types/orm/adapters/json/index.d.ts +72 -0
- package/dist/types/orm/adapters/json/index.js +979 -0
- package/dist/types/orm/adapters/mongodb/index.d.ts +74 -0
- package/dist/types/orm/adapters/mongodb/index.js +1538 -0
- package/dist/types/orm/adapters/postgresql/index.d.ts +112 -0
- package/dist/types/orm/adapters/postgresql/index.js +2069 -0
- package/dist/types/orm/index.d.ts +160 -0
- package/dist/types/orm/index.js +7325 -0
- package/dist/types/{requestError-C2PNWUrt.d.ts → requestError-DdaL8hc3.d.ts} +2 -8
- package/dist/types/server/adapters/express/index.d.ts +196 -0
- package/dist/types/server/adapters/express/index.js +176 -0
- package/dist/types/server/adapters/fastify/index.d.ts +221 -0
- package/dist/types/server/adapters/fastify/index.js +184 -0
- package/dist/types/server/index.d.ts +10 -11
- package/dist/types/server/index.js +38 -9
- package/dist/types/types/index.js +0 -1
- package/dist/types/utilities/index.d.ts +22 -9
- package/dist/types/utilities/index.js +22 -9
- package/dist/types/{validationError-Bw8OYv5s.d.ts → validationError-oigtHqBi.d.ts} +1 -1
- package/dist/types/validations/index.d.ts +3 -2
- package/dist/types/validations/index.js +8 -18
- package/package.json +155 -18
- package/dist/cjs/audit/events.cjs +0 -101
- package/dist/cjs/audit/events.cjs.map +0 -1
- package/dist/cjs/audit/events.min.cjs +0 -2
- package/dist/cjs/audit/events.min.cjs.map +0 -1
- package/dist/cjs/audit/index.cjs +0 -2
- package/dist/cjs/audit/index.cjs.map +0 -1
- package/dist/cjs/audit/index.min.cjs +0 -2
- package/dist/cjs/audit/index.min.cjs.map +0 -1
- package/dist/cjs/cache/base.cjs +0 -6
- package/dist/cjs/cache/base.cjs.map +0 -1
- package/dist/cjs/cache/base.min.cjs +0 -2
- package/dist/cjs/cache/base.min.cjs.map +0 -1
- package/dist/cjs/cache/pipes.cjs +0 -16
- package/dist/cjs/cache/pipes.cjs.map +0 -1
- package/dist/cjs/cache/pipes.min.cjs +0 -2
- package/dist/cjs/cache/pipes.min.cjs.map +0 -1
- package/dist/cjs/cache/types/in-memory.cjs +0 -47
- package/dist/cjs/cache/types/in-memory.cjs.map +0 -1
- package/dist/cjs/cache/types/in-memory.min.cjs +0 -2
- package/dist/cjs/cache/types/in-memory.min.cjs.map +0 -1
- package/dist/cjs/cache/types/redis.cjs +0 -55
- package/dist/cjs/cache/types/redis.cjs.map +0 -1
- package/dist/cjs/cache/types/redis.min.cjs +0 -2
- package/dist/cjs/cache/types/redis.min.cjs.map +0 -1
- package/dist/cjs/dbs/base/changes.cjs +0 -36
- package/dist/cjs/dbs/base/changes.cjs.map +0 -1
- package/dist/cjs/dbs/base/changes.min.cjs +0 -2
- package/dist/cjs/dbs/base/changes.min.cjs.map +0 -1
- package/dist/cjs/dbs/base/core.cjs +0 -1
- package/dist/cjs/dbs/base/core.cjs.map +0 -1
- package/dist/cjs/dbs/base/core.min.cjs +0 -1
- package/dist/cjs/dbs/base/core.min.cjs.map +0 -1
- package/dist/cjs/dbs/base/db.cjs +0 -14
- package/dist/cjs/dbs/base/db.cjs.map +0 -1
- package/dist/cjs/dbs/base/db.min.cjs +0 -2
- package/dist/cjs/dbs/base/db.min.cjs.map +0 -1
- package/dist/cjs/dbs/base/types.cjs +0 -2
- package/dist/cjs/dbs/base/types.cjs.map +0 -1
- package/dist/cjs/dbs/base/types.min.cjs +0 -2
- package/dist/cjs/dbs/base/types.min.cjs.map +0 -1
- package/dist/cjs/dbs/mongo/changes.cjs +0 -81
- package/dist/cjs/dbs/mongo/changes.cjs.map +0 -1
- package/dist/cjs/dbs/mongo/changes.min.cjs +0 -2
- package/dist/cjs/dbs/mongo/changes.min.cjs.map +0 -1
- package/dist/cjs/dbs/mongo/db.cjs +0 -225
- package/dist/cjs/dbs/mongo/db.cjs.map +0 -1
- package/dist/cjs/dbs/mongo/db.min.cjs +0 -2
- package/dist/cjs/dbs/mongo/db.min.cjs.map +0 -1
- package/dist/cjs/dbs/mongo/query.cjs +0 -66
- package/dist/cjs/dbs/mongo/query.cjs.map +0 -1
- package/dist/cjs/dbs/mongo/query.min.cjs +0 -2
- package/dist/cjs/dbs/mongo/query.min.cjs.map +0 -1
- package/dist/cjs/dbs/pipes.cjs +0 -104
- package/dist/cjs/dbs/pipes.cjs.map +0 -1
- package/dist/cjs/dbs/pipes.min.cjs +0 -2
- package/dist/cjs/dbs/pipes.min.cjs.map +0 -1
- package/dist/cjs/errors/equippedError.cjs +0 -12
- package/dist/cjs/errors/equippedError.cjs.map +0 -1
- package/dist/cjs/errors/equippedError.min.cjs +0 -2
- package/dist/cjs/errors/equippedError.min.cjs.map +0 -1
- package/dist/cjs/errors/requestError.cjs +0 -12
- package/dist/cjs/errors/requestError.cjs.map +0 -1
- package/dist/cjs/errors/requestError.min.cjs +0 -2
- package/dist/cjs/errors/requestError.min.cjs.map +0 -1
- package/dist/cjs/errors/types/badRequestError.cjs +0 -12
- package/dist/cjs/errors/types/badRequestError.cjs.map +0 -1
- package/dist/cjs/errors/types/badRequestError.min.cjs +0 -2
- package/dist/cjs/errors/types/badRequestError.min.cjs.map +0 -1
- package/dist/cjs/errors/types/notAuthenticatedError.cjs +0 -12
- package/dist/cjs/errors/types/notAuthenticatedError.cjs.map +0 -1
- package/dist/cjs/errors/types/notAuthenticatedError.min.cjs +0 -2
- package/dist/cjs/errors/types/notAuthenticatedError.min.cjs.map +0 -1
- package/dist/cjs/errors/types/notAuthorizedError.cjs +0 -12
- package/dist/cjs/errors/types/notAuthorizedError.cjs.map +0 -1
- package/dist/cjs/errors/types/notAuthorizedError.min.cjs +0 -2
- package/dist/cjs/errors/types/notAuthorizedError.min.cjs.map +0 -1
- package/dist/cjs/errors/types/notFoundError.cjs +0 -12
- package/dist/cjs/errors/types/notFoundError.cjs.map +0 -1
- package/dist/cjs/errors/types/notFoundError.min.cjs +0 -2
- package/dist/cjs/errors/types/notFoundError.min.cjs.map +0 -1
- package/dist/cjs/errors/types/tokenExpired.cjs +0 -12
- package/dist/cjs/errors/types/tokenExpired.cjs.map +0 -1
- package/dist/cjs/errors/types/tokenExpired.min.cjs +0 -2
- package/dist/cjs/errors/types/tokenExpired.min.cjs.map +0 -1
- package/dist/cjs/errors/types/validationError.cjs +0 -16
- package/dist/cjs/errors/types/validationError.cjs.map +0 -1
- package/dist/cjs/errors/types/validationError.min.cjs +0 -2
- package/dist/cjs/errors/types/validationError.min.cjs.map +0 -1
- package/dist/cjs/events/base.cjs +0 -6
- package/dist/cjs/events/base.cjs.map +0 -1
- package/dist/cjs/events/base.min.cjs +0 -2
- package/dist/cjs/events/base.min.cjs.map +0 -1
- package/dist/cjs/events/pipes.cjs +0 -28
- package/dist/cjs/events/pipes.cjs.map +0 -1
- package/dist/cjs/events/pipes.min.cjs +0 -2
- package/dist/cjs/events/pipes.min.cjs.map +0 -1
- package/dist/cjs/events/types/kafka.cjs +0 -80
- package/dist/cjs/events/types/kafka.cjs.map +0 -1
- package/dist/cjs/events/types/kafka.min.cjs +0 -2
- package/dist/cjs/events/types/kafka.min.cjs.map +0 -1
- package/dist/cjs/events/types/rabbitmq.cjs +0 -54
- package/dist/cjs/events/types/rabbitmq.cjs.map +0 -1
- package/dist/cjs/events/types/rabbitmq.min.cjs +0 -2
- package/dist/cjs/events/types/rabbitmq.min.cjs.map +0 -1
- package/dist/cjs/instance/hooks.cjs +0 -26
- package/dist/cjs/instance/hooks.cjs.map +0 -1
- package/dist/cjs/instance/hooks.min.cjs +0 -2
- package/dist/cjs/instance/hooks.min.cjs.map +0 -1
- package/dist/cjs/instance/settings.cjs +0 -57
- package/dist/cjs/instance/settings.cjs.map +0 -1
- package/dist/cjs/instance/settings.min.cjs +0 -2
- package/dist/cjs/instance/settings.min.cjs.map +0 -1
- package/dist/cjs/jobs/pipes.cjs +0 -13
- package/dist/cjs/jobs/pipes.cjs.map +0 -1
- package/dist/cjs/jobs/pipes.min.cjs +0 -2
- package/dist/cjs/jobs/pipes.min.cjs.map +0 -1
- package/dist/cjs/jobs/types/redis.cjs +0 -106
- package/dist/cjs/jobs/types/redis.cjs.map +0 -1
- package/dist/cjs/jobs/types/redis.min.cjs +0 -2
- package/dist/cjs/jobs/types/redis.min.cjs.map +0 -1
- package/dist/cjs/server/impls/base.cjs +0 -211
- package/dist/cjs/server/impls/base.cjs.map +0 -1
- package/dist/cjs/server/impls/base.min.cjs +0 -2
- package/dist/cjs/server/impls/base.min.cjs.map +0 -1
- package/dist/cjs/server/impls/express.cjs +0 -111
- package/dist/cjs/server/impls/express.cjs.map +0 -1
- package/dist/cjs/server/impls/express.min.cjs +0 -2
- package/dist/cjs/server/impls/express.min.cjs.map +0 -1
- package/dist/cjs/server/impls/fastify.cjs +0 -121
- package/dist/cjs/server/impls/fastify.cjs.map +0 -1
- package/dist/cjs/server/impls/fastify.min.cjs +0 -2
- package/dist/cjs/server/impls/fastify.min.cjs.map +0 -1
- package/dist/cjs/server/middlewares/index.cjs +0 -2
- package/dist/cjs/server/middlewares/index.cjs.map +0 -1
- package/dist/cjs/server/middlewares/index.min.cjs +0 -2
- package/dist/cjs/server/middlewares/index.min.cjs.map +0 -1
- package/dist/cjs/server/middlewares/requireAuthUser.cjs +0 -22
- package/dist/cjs/server/middlewares/requireAuthUser.cjs.map +0 -1
- package/dist/cjs/server/middlewares/requireAuthUser.min.cjs +0 -2
- package/dist/cjs/server/middlewares/requireAuthUser.min.cjs.map +0 -1
- package/dist/cjs/server/openapi.cjs +0 -191
- package/dist/cjs/server/openapi.cjs.map +0 -1
- package/dist/cjs/server/openapi.min.cjs +0 -30
- package/dist/cjs/server/openapi.min.cjs.map +0 -1
- package/dist/cjs/server/pipes.cjs +0 -67
- package/dist/cjs/server/pipes.cjs.map +0 -1
- package/dist/cjs/server/pipes.min.cjs +0 -2
- package/dist/cjs/server/pipes.min.cjs.map +0 -1
- package/dist/cjs/server/requests-auth-methods/apiKeys.cjs +0 -21
- package/dist/cjs/server/requests-auth-methods/apiKeys.cjs.map +0 -1
- package/dist/cjs/server/requests-auth-methods/apiKeys.min.cjs +0 -2
- package/dist/cjs/server/requests-auth-methods/apiKeys.min.cjs.map +0 -1
- package/dist/cjs/server/requests-auth-methods/base.cjs +0 -20
- package/dist/cjs/server/requests-auth-methods/base.cjs.map +0 -1
- package/dist/cjs/server/requests-auth-methods/base.min.cjs +0 -2
- package/dist/cjs/server/requests-auth-methods/base.min.cjs.map +0 -1
- package/dist/cjs/server/requests-auth-methods/index.cjs +0 -4
- package/dist/cjs/server/requests-auth-methods/index.cjs.map +0 -1
- package/dist/cjs/server/requests-auth-methods/index.min.cjs +0 -2
- package/dist/cjs/server/requests-auth-methods/index.min.cjs.map +0 -1
- package/dist/cjs/server/requests-auth-methods/jwt.cjs +0 -78
- package/dist/cjs/server/requests-auth-methods/jwt.cjs.map +0 -1
- package/dist/cjs/server/requests-auth-methods/jwt.min.cjs +0 -2
- package/dist/cjs/server/requests-auth-methods/jwt.min.cjs.map +0 -1
- package/dist/cjs/server/requests.cjs +0 -80
- package/dist/cjs/server/requests.cjs.map +0 -1
- package/dist/cjs/server/requests.min.cjs +0 -2
- package/dist/cjs/server/requests.min.cjs.map +0 -1
- package/dist/cjs/server/routes.cjs +0 -74
- package/dist/cjs/server/routes.cjs.map +0 -1
- package/dist/cjs/server/routes.min.cjs +0 -2
- package/dist/cjs/server/routes.min.cjs.map +0 -1
- package/dist/cjs/server/sockets.cjs +0 -126
- package/dist/cjs/server/sockets.cjs.map +0 -1
- package/dist/cjs/server/sockets.min.cjs +0 -2
- package/dist/cjs/server/sockets.min.cjs.map +0 -1
- package/dist/cjs/server/types.cjs +0 -34
- package/dist/cjs/server/types.cjs.map +0 -1
- package/dist/cjs/server/types.min.cjs +0 -2
- package/dist/cjs/server/types.min.cjs.map +0 -1
- package/dist/cjs/types/overrides.cjs +0 -1
- package/dist/cjs/types/overrides.cjs.map +0 -1
- package/dist/cjs/types/overrides.min.cjs +0 -1
- package/dist/cjs/types/overrides.min.cjs.map +0 -1
- package/dist/cjs/utilities/authProviders.cjs +0 -53
- package/dist/cjs/utilities/authProviders.cjs.map +0 -1
- package/dist/cjs/utilities/authProviders.min.cjs +0 -2
- package/dist/cjs/utilities/authProviders.min.cjs.map +0 -1
- package/dist/cjs/utilities/hash.cjs +0 -17
- package/dist/cjs/utilities/hash.cjs.map +0 -1
- package/dist/cjs/utilities/hash.min.cjs +0 -2
- package/dist/cjs/utilities/hash.min.cjs.map +0 -1
- package/dist/cjs/utilities/json.cjs +0 -16
- package/dist/cjs/utilities/json.cjs.map +0 -1
- package/dist/cjs/utilities/json.min.cjs +0 -2
- package/dist/cjs/utilities/json.min.cjs.map +0 -1
- package/dist/cjs/utilities/media.cjs +0 -13
- package/dist/cjs/utilities/media.cjs.map +0 -1
- package/dist/cjs/utilities/media.min.cjs +0 -2
- package/dist/cjs/utilities/media.min.cjs.map +0 -1
- package/dist/cjs/utilities/random.cjs +0 -12
- package/dist/cjs/utilities/random.cjs.map +0 -1
- package/dist/cjs/utilities/random.min.cjs +0 -2
- package/dist/cjs/utilities/random.min.cjs.map +0 -1
- package/dist/cjs/utilities/retry.cjs +0 -14
- package/dist/cjs/utilities/retry.cjs.map +0 -1
- package/dist/cjs/utilities/retry.min.cjs +0 -2
- package/dist/cjs/utilities/retry.min.cjs.map +0 -1
- package/dist/cjs/validations/valleyed.cjs +0 -43
- package/dist/cjs/validations/valleyed.cjs.map +0 -1
- package/dist/cjs/validations/valleyed.min.cjs +0 -2
- package/dist/cjs/validations/valleyed.min.cjs.map +0 -1
- package/dist/esm/audit/events.min.mjs +0 -2
- package/dist/esm/audit/events.min.mjs.map +0 -1
- package/dist/esm/audit/events.mjs +0 -101
- package/dist/esm/audit/events.mjs.map +0 -1
- package/dist/esm/audit/index.min.mjs +0 -2
- package/dist/esm/audit/index.min.mjs.map +0 -1
- package/dist/esm/audit/index.mjs +0 -2
- package/dist/esm/audit/index.mjs.map +0 -1
- package/dist/esm/cache/base.min.mjs +0 -2
- package/dist/esm/cache/base.min.mjs.map +0 -1
- package/dist/esm/cache/base.mjs +0 -6
- package/dist/esm/cache/base.mjs.map +0 -1
- package/dist/esm/cache/pipes.min.mjs +0 -2
- package/dist/esm/cache/pipes.min.mjs.map +0 -1
- package/dist/esm/cache/pipes.mjs +0 -16
- package/dist/esm/cache/pipes.mjs.map +0 -1
- package/dist/esm/cache/types/in-memory.min.mjs +0 -2
- package/dist/esm/cache/types/in-memory.min.mjs.map +0 -1
- package/dist/esm/cache/types/in-memory.mjs +0 -47
- package/dist/esm/cache/types/in-memory.mjs.map +0 -1
- package/dist/esm/cache/types/redis.min.mjs +0 -2
- package/dist/esm/cache/types/redis.min.mjs.map +0 -1
- package/dist/esm/cache/types/redis.mjs +0 -55
- package/dist/esm/cache/types/redis.mjs.map +0 -1
- package/dist/esm/dbs/base/changes.min.mjs +0 -2
- package/dist/esm/dbs/base/changes.min.mjs.map +0 -1
- package/dist/esm/dbs/base/changes.mjs +0 -36
- package/dist/esm/dbs/base/changes.mjs.map +0 -1
- package/dist/esm/dbs/base/core.min.mjs +0 -1
- package/dist/esm/dbs/base/core.mjs +0 -1
- package/dist/esm/dbs/base/db.min.mjs +0 -2
- package/dist/esm/dbs/base/db.min.mjs.map +0 -1
- package/dist/esm/dbs/base/db.mjs +0 -14
- package/dist/esm/dbs/base/db.mjs.map +0 -1
- package/dist/esm/dbs/base/types.min.mjs +0 -2
- package/dist/esm/dbs/base/types.min.mjs.map +0 -1
- package/dist/esm/dbs/base/types.mjs +0 -2
- package/dist/esm/dbs/base/types.mjs.map +0 -1
- package/dist/esm/dbs/mongo/changes.min.mjs +0 -2
- package/dist/esm/dbs/mongo/changes.min.mjs.map +0 -1
- package/dist/esm/dbs/mongo/changes.mjs +0 -81
- package/dist/esm/dbs/mongo/changes.mjs.map +0 -1
- package/dist/esm/dbs/mongo/db.min.mjs +0 -2
- package/dist/esm/dbs/mongo/db.min.mjs.map +0 -1
- package/dist/esm/dbs/mongo/db.mjs +0 -225
- package/dist/esm/dbs/mongo/db.mjs.map +0 -1
- package/dist/esm/dbs/mongo/query.min.mjs +0 -2
- package/dist/esm/dbs/mongo/query.min.mjs.map +0 -1
- package/dist/esm/dbs/mongo/query.mjs +0 -66
- package/dist/esm/dbs/mongo/query.mjs.map +0 -1
- package/dist/esm/dbs/pipes.min.mjs +0 -2
- package/dist/esm/dbs/pipes.min.mjs.map +0 -1
- package/dist/esm/dbs/pipes.mjs +0 -104
- package/dist/esm/dbs/pipes.mjs.map +0 -1
- package/dist/esm/errors/equippedError.min.mjs +0 -2
- package/dist/esm/errors/equippedError.min.mjs.map +0 -1
- package/dist/esm/errors/equippedError.mjs +0 -12
- package/dist/esm/errors/equippedError.mjs.map +0 -1
- package/dist/esm/errors/requestError.min.mjs +0 -2
- package/dist/esm/errors/requestError.min.mjs.map +0 -1
- package/dist/esm/errors/requestError.mjs +0 -12
- package/dist/esm/errors/requestError.mjs.map +0 -1
- package/dist/esm/errors/types/badRequestError.min.mjs +0 -2
- package/dist/esm/errors/types/badRequestError.min.mjs.map +0 -1
- package/dist/esm/errors/types/badRequestError.mjs +0 -12
- package/dist/esm/errors/types/badRequestError.mjs.map +0 -1
- package/dist/esm/errors/types/notAuthenticatedError.min.mjs +0 -2
- package/dist/esm/errors/types/notAuthenticatedError.min.mjs.map +0 -1
- package/dist/esm/errors/types/notAuthenticatedError.mjs +0 -12
- package/dist/esm/errors/types/notAuthenticatedError.mjs.map +0 -1
- package/dist/esm/errors/types/notAuthorizedError.min.mjs +0 -2
- package/dist/esm/errors/types/notAuthorizedError.min.mjs.map +0 -1
- package/dist/esm/errors/types/notAuthorizedError.mjs +0 -12
- package/dist/esm/errors/types/notAuthorizedError.mjs.map +0 -1
- package/dist/esm/errors/types/notFoundError.min.mjs +0 -2
- package/dist/esm/errors/types/notFoundError.min.mjs.map +0 -1
- package/dist/esm/errors/types/notFoundError.mjs +0 -12
- package/dist/esm/errors/types/notFoundError.mjs.map +0 -1
- package/dist/esm/errors/types/tokenExpired.min.mjs +0 -2
- package/dist/esm/errors/types/tokenExpired.min.mjs.map +0 -1
- package/dist/esm/errors/types/tokenExpired.mjs +0 -12
- package/dist/esm/errors/types/tokenExpired.mjs.map +0 -1
- package/dist/esm/errors/types/validationError.min.mjs +0 -2
- package/dist/esm/errors/types/validationError.min.mjs.map +0 -1
- package/dist/esm/errors/types/validationError.mjs +0 -16
- package/dist/esm/errors/types/validationError.mjs.map +0 -1
- package/dist/esm/events/base.min.mjs +0 -2
- package/dist/esm/events/base.min.mjs.map +0 -1
- package/dist/esm/events/base.mjs +0 -6
- package/dist/esm/events/base.mjs.map +0 -1
- package/dist/esm/events/pipes.min.mjs +0 -2
- package/dist/esm/events/pipes.min.mjs.map +0 -1
- package/dist/esm/events/pipes.mjs +0 -28
- package/dist/esm/events/pipes.mjs.map +0 -1
- package/dist/esm/events/types/kafka.min.mjs +0 -2
- package/dist/esm/events/types/kafka.min.mjs.map +0 -1
- package/dist/esm/events/types/kafka.mjs.map +0 -1
- package/dist/esm/events/types/rabbitmq.min.mjs +0 -2
- package/dist/esm/events/types/rabbitmq.min.mjs.map +0 -1
- package/dist/esm/events/types/rabbitmq.mjs +0 -54
- package/dist/esm/events/types/rabbitmq.mjs.map +0 -1
- package/dist/esm/instance/hooks.min.mjs +0 -2
- package/dist/esm/instance/hooks.min.mjs.map +0 -1
- package/dist/esm/instance/hooks.mjs +0 -26
- package/dist/esm/instance/hooks.mjs.map +0 -1
- package/dist/esm/instance/settings.min.mjs +0 -2
- package/dist/esm/instance/settings.min.mjs.map +0 -1
- package/dist/esm/instance/settings.mjs +0 -57
- package/dist/esm/instance/settings.mjs.map +0 -1
- package/dist/esm/jobs/pipes.min.mjs +0 -2
- package/dist/esm/jobs/pipes.min.mjs.map +0 -1
- package/dist/esm/jobs/pipes.mjs +0 -13
- package/dist/esm/jobs/pipes.mjs.map +0 -1
- package/dist/esm/jobs/types/redis.min.mjs +0 -2
- package/dist/esm/jobs/types/redis.min.mjs.map +0 -1
- package/dist/esm/jobs/types/redis.mjs.map +0 -1
- package/dist/esm/server/impls/base.min.mjs +0 -2
- package/dist/esm/server/impls/base.min.mjs.map +0 -1
- package/dist/esm/server/impls/base.mjs +0 -211
- package/dist/esm/server/impls/base.mjs.map +0 -1
- package/dist/esm/server/impls/express.min.mjs +0 -2
- package/dist/esm/server/impls/express.min.mjs.map +0 -1
- package/dist/esm/server/impls/express.mjs +0 -111
- package/dist/esm/server/impls/express.mjs.map +0 -1
- package/dist/esm/server/impls/fastify.min.mjs +0 -2
- package/dist/esm/server/impls/fastify.min.mjs.map +0 -1
- package/dist/esm/server/impls/fastify.mjs +0 -121
- package/dist/esm/server/impls/fastify.mjs.map +0 -1
- package/dist/esm/server/middlewares/index.min.mjs +0 -2
- package/dist/esm/server/middlewares/index.min.mjs.map +0 -1
- package/dist/esm/server/middlewares/index.mjs +0 -2
- package/dist/esm/server/middlewares/index.mjs.map +0 -1
- package/dist/esm/server/middlewares/requireAuthUser.min.mjs +0 -2
- package/dist/esm/server/middlewares/requireAuthUser.min.mjs.map +0 -1
- package/dist/esm/server/middlewares/requireAuthUser.mjs +0 -22
- package/dist/esm/server/middlewares/requireAuthUser.mjs.map +0 -1
- package/dist/esm/server/openapi.min.mjs +0 -30
- package/dist/esm/server/openapi.min.mjs.map +0 -1
- package/dist/esm/server/openapi.mjs +0 -191
- package/dist/esm/server/openapi.mjs.map +0 -1
- package/dist/esm/server/pipes.min.mjs +0 -2
- package/dist/esm/server/pipes.min.mjs.map +0 -1
- package/dist/esm/server/pipes.mjs +0 -67
- package/dist/esm/server/pipes.mjs.map +0 -1
- package/dist/esm/server/requests-auth-methods/apiKeys.min.mjs +0 -2
- package/dist/esm/server/requests-auth-methods/apiKeys.min.mjs.map +0 -1
- package/dist/esm/server/requests-auth-methods/apiKeys.mjs +0 -21
- package/dist/esm/server/requests-auth-methods/apiKeys.mjs.map +0 -1
- package/dist/esm/server/requests-auth-methods/base.min.mjs +0 -2
- package/dist/esm/server/requests-auth-methods/base.min.mjs.map +0 -1
- package/dist/esm/server/requests-auth-methods/base.mjs +0 -20
- package/dist/esm/server/requests-auth-methods/base.mjs.map +0 -1
- package/dist/esm/server/requests-auth-methods/index.min.mjs +0 -2
- package/dist/esm/server/requests-auth-methods/index.min.mjs.map +0 -1
- package/dist/esm/server/requests-auth-methods/index.mjs +0 -4
- package/dist/esm/server/requests-auth-methods/index.mjs.map +0 -1
- package/dist/esm/server/requests-auth-methods/jwt.min.mjs +0 -2
- package/dist/esm/server/requests-auth-methods/jwt.min.mjs.map +0 -1
- package/dist/esm/server/requests-auth-methods/jwt.mjs +0 -78
- package/dist/esm/server/requests-auth-methods/jwt.mjs.map +0 -1
- package/dist/esm/server/requests.min.mjs +0 -2
- package/dist/esm/server/requests.min.mjs.map +0 -1
- package/dist/esm/server/requests.mjs +0 -80
- package/dist/esm/server/requests.mjs.map +0 -1
- package/dist/esm/server/routes.min.mjs +0 -2
- package/dist/esm/server/routes.min.mjs.map +0 -1
- package/dist/esm/server/routes.mjs +0 -74
- package/dist/esm/server/routes.mjs.map +0 -1
- package/dist/esm/server/sockets.min.mjs +0 -2
- package/dist/esm/server/sockets.min.mjs.map +0 -1
- package/dist/esm/server/sockets.mjs +0 -126
- package/dist/esm/server/sockets.mjs.map +0 -1
- package/dist/esm/server/types.min.mjs +0 -2
- package/dist/esm/server/types.min.mjs.map +0 -1
- package/dist/esm/server/types.mjs +0 -34
- package/dist/esm/server/types.mjs.map +0 -1
- package/dist/esm/types/overrides.min.mjs +0 -1
- package/dist/esm/types/overrides.mjs +0 -1
- package/dist/esm/utilities/authProviders.min.mjs +0 -2
- package/dist/esm/utilities/authProviders.min.mjs.map +0 -1
- package/dist/esm/utilities/authProviders.mjs +0 -53
- package/dist/esm/utilities/authProviders.mjs.map +0 -1
- package/dist/esm/utilities/hash.min.mjs +0 -2
- package/dist/esm/utilities/hash.min.mjs.map +0 -1
- package/dist/esm/utilities/hash.mjs +0 -17
- package/dist/esm/utilities/hash.mjs.map +0 -1
- package/dist/esm/utilities/json.min.mjs +0 -2
- package/dist/esm/utilities/json.min.mjs.map +0 -1
- package/dist/esm/utilities/json.mjs +0 -16
- package/dist/esm/utilities/json.mjs.map +0 -1
- package/dist/esm/utilities/media.min.mjs +0 -2
- package/dist/esm/utilities/media.min.mjs.map +0 -1
- package/dist/esm/utilities/media.mjs +0 -13
- package/dist/esm/utilities/media.mjs.map +0 -1
- package/dist/esm/utilities/random.min.mjs +0 -2
- package/dist/esm/utilities/random.min.mjs.map +0 -1
- package/dist/esm/utilities/random.mjs +0 -12
- package/dist/esm/utilities/random.mjs.map +0 -1
- package/dist/esm/utilities/retry.min.mjs +0 -2
- package/dist/esm/utilities/retry.min.mjs.map +0 -1
- package/dist/esm/utilities/retry.mjs +0 -14
- package/dist/esm/utilities/retry.mjs.map +0 -1
- package/dist/esm/validations/valleyed.min.mjs +0 -2
- package/dist/esm/validations/valleyed.min.mjs.map +0 -1
- package/dist/esm/validations/valleyed.mjs +0 -43
- package/dist/esm/validations/valleyed.mjs.map +0 -1
- package/dist/types/audit/events.js +0 -100
- package/dist/types/audit/index.d.ts +0 -44
- package/dist/types/audit/index.js +0 -1
- package/dist/types/cache/base.js +0 -5
- package/dist/types/cache/pipes.js +0 -15
- package/dist/types/cache/types/in-memory.js +0 -46
- package/dist/types/cache/types/redis.js +0 -54
- package/dist/types/db-C8YY1yTO.d.ts +0 -14
- package/dist/types/db-Dj1SGto0.d.ts +0 -24
- package/dist/types/dbs/base/changes.js +0 -35
- package/dist/types/dbs/base/core.js +0 -0
- package/dist/types/dbs/base/db.js +0 -13
- package/dist/types/dbs/base/types.js +0 -1
- package/dist/types/dbs/mongo/changes.js +0 -80
- package/dist/types/dbs/mongo/db.js +0 -224
- package/dist/types/dbs/mongo/query.js +0 -65
- package/dist/types/dbs/pipes.js +0 -103
- package/dist/types/errors/equippedError.js +0 -11
- package/dist/types/errors/requestError.js +0 -11
- package/dist/types/errors/types/badRequestError.js +0 -11
- package/dist/types/errors/types/notAuthenticatedError.js +0 -11
- package/dist/types/errors/types/notAuthorizedError.js +0 -11
- package/dist/types/errors/types/notFoundError.js +0 -11
- package/dist/types/errors/types/tokenExpired.js +0 -11
- package/dist/types/errors/types/validationError.js +0 -15
- package/dist/types/events/base.js +0 -5
- package/dist/types/events/pipes.js +0 -27
- package/dist/types/events/types/rabbitmq.js +0 -53
- package/dist/types/instance/hooks.js +0 -25
- package/dist/types/instance/settings.js +0 -56
- package/dist/types/jobs/pipes.js +0 -12
- package/dist/types/kafka-Cf-TUJgv.d.ts +0 -54
- package/dist/types/server/impls/base.js +0 -210
- package/dist/types/server/impls/express.js +0 -110
- package/dist/types/server/impls/fastify.js +0 -120
- package/dist/types/server/middlewares/index.js +0 -1
- package/dist/types/server/middlewares/requireAuthUser.js +0 -21
- package/dist/types/server/openapi.js +0 -190
- package/dist/types/server/pipes.js +0 -66
- package/dist/types/server/requests-auth-methods/apiKeys.js +0 -20
- package/dist/types/server/requests-auth-methods/base.js +0 -19
- package/dist/types/server/requests-auth-methods/index.js +0 -3
- package/dist/types/server/requests-auth-methods/jwt.js +0 -77
- package/dist/types/server/requests.js +0 -79
- package/dist/types/server/routes.js +0 -73
- package/dist/types/server/sockets.js +0 -125
- package/dist/types/server/types.js +0 -33
- package/dist/types/types/overrides.js +0 -0
- package/dist/types/utilities/authProviders.js +0 -52
- package/dist/types/utilities/hash.js +0 -16
- package/dist/types/utilities/json.js +0 -15
- package/dist/types/utilities/media.js +0 -12
- package/dist/types/utilities/random.js +0 -11
- package/dist/types/utilities/retry.js +0 -13
- package/dist/types/validations/valleyed.js +0 -42
- /package/dist/esm/{dbs/base/core.min.mjs.map → cache/adapters/redis/index.min.mjs.map} +0 -0
- /package/dist/esm/{dbs/base/core.mjs.map → cache/adapters/redis/index.mjs.map} +0 -0
- /package/dist/esm/{types/overrides.min.mjs.map → chunk-AQY7QVYI.min.mjs.map} +0 -0
- /package/dist/esm/{types/overrides.mjs.map → chunk-J5LGTIGS.mjs.map} +0 -0
|
@@ -0,0 +1,3299 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/orm/adapters/mongodb/index.ts
|
|
31
|
+
var mongodb_exports = {};
|
|
32
|
+
__export(mongodb_exports, {
|
|
33
|
+
MongoDbAdapter: () => MongoDbAdapter
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(mongodb_exports);
|
|
36
|
+
var import_node_async_hooks = require("async_hooks");
|
|
37
|
+
var import_mongodb = require("mongodb");
|
|
38
|
+
var import_valleyed5 = require("valleyed");
|
|
39
|
+
|
|
40
|
+
// src/errors/equippedError.ts
|
|
41
|
+
var EquippedError = class extends Error {
|
|
42
|
+
constructor(message, context, cause) {
|
|
43
|
+
super(message, { cause });
|
|
44
|
+
this.message = message;
|
|
45
|
+
this.context = context;
|
|
46
|
+
this.cause = cause;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/instance/index.ts
|
|
51
|
+
var import_pino = __toESM(require("pino"), 1);
|
|
52
|
+
var import_ulid = require("ulid");
|
|
53
|
+
var import_valleyed2 = require("valleyed");
|
|
54
|
+
|
|
55
|
+
// src/instance/hooks.ts
|
|
56
|
+
function registerHook(hooks, record) {
|
|
57
|
+
for (const dep of record.after) {
|
|
58
|
+
if (!hooks.some((h) => h.class === dep)) {
|
|
59
|
+
const depName = dep.name || "unknown";
|
|
60
|
+
const ownerName = record.class?.name || "anonymous";
|
|
61
|
+
throw new Error(`Missing dependency: ${ownerName} declares after: [${depName}], but ${depName} is not registered`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (record.class) {
|
|
65
|
+
const tentative = [...hooks, record];
|
|
66
|
+
detectCycle(tentative);
|
|
67
|
+
}
|
|
68
|
+
hooks.push(record);
|
|
69
|
+
}
|
|
70
|
+
function detectCycle(hooks) {
|
|
71
|
+
const classToAfter = /* @__PURE__ */ new Map();
|
|
72
|
+
for (const h of hooks) {
|
|
73
|
+
if (!h.class) continue;
|
|
74
|
+
const existing = classToAfter.get(h.class);
|
|
75
|
+
if (existing) {
|
|
76
|
+
for (const dep of h.after) {
|
|
77
|
+
if (!existing.includes(dep)) existing.push(dep);
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
classToAfter.set(h.class, [...h.after]);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const visited = /* @__PURE__ */ new Set();
|
|
84
|
+
const stack = /* @__PURE__ */ new Set();
|
|
85
|
+
function visit(cls, path) {
|
|
86
|
+
if (stack.has(cls)) {
|
|
87
|
+
const cycleStart = path.indexOf(cls);
|
|
88
|
+
const cycle = [...path.slice(cycleStart), cls];
|
|
89
|
+
const names = cycle.map((c) => c.name || "unknown");
|
|
90
|
+
throw new Error(`Cycle detected: ${names.join(" \u2192 ")}`);
|
|
91
|
+
}
|
|
92
|
+
if (visited.has(cls)) return;
|
|
93
|
+
stack.add(cls);
|
|
94
|
+
path.push(cls);
|
|
95
|
+
for (const dep of classToAfter.get(cls) ?? []) {
|
|
96
|
+
visit(dep, path);
|
|
97
|
+
}
|
|
98
|
+
path.pop();
|
|
99
|
+
stack.delete(cls);
|
|
100
|
+
visited.add(cls);
|
|
101
|
+
}
|
|
102
|
+
for (const cls of classToAfter.keys()) {
|
|
103
|
+
visit(cls, []);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function resolveHookDAG(hooks, invert = false) {
|
|
107
|
+
if (hooks.length === 0) return [];
|
|
108
|
+
const classToHooks = /* @__PURE__ */ new Map();
|
|
109
|
+
const anonymous = [];
|
|
110
|
+
for (const h of hooks) {
|
|
111
|
+
if (h.class) {
|
|
112
|
+
const list = classToHooks.get(h.class);
|
|
113
|
+
if (list) list.push(h);
|
|
114
|
+
else classToHooks.set(h.class, [h]);
|
|
115
|
+
} else {
|
|
116
|
+
anonymous.push(h);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const classes = [...classToHooks.keys()];
|
|
120
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
121
|
+
const graph = /* @__PURE__ */ new Map();
|
|
122
|
+
for (const cls of classes) {
|
|
123
|
+
inDegree.set(cls, 0);
|
|
124
|
+
graph.set(cls, []);
|
|
125
|
+
}
|
|
126
|
+
for (const h of hooks) {
|
|
127
|
+
if (!h.class) continue;
|
|
128
|
+
for (const dep of h.after) {
|
|
129
|
+
const [from, to] = invert ? [h.class, dep] : [dep, h.class];
|
|
130
|
+
const edges = graph.get(from);
|
|
131
|
+
if (!edges.includes(to)) {
|
|
132
|
+
edges.push(to);
|
|
133
|
+
inDegree.set(to, inDegree.get(to) + 1);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const layers = [];
|
|
138
|
+
let queue = classes.filter((c) => inDegree.get(c) === 0);
|
|
139
|
+
while (queue.length > 0) {
|
|
140
|
+
const layer = [];
|
|
141
|
+
const next = [];
|
|
142
|
+
for (const cls of queue) {
|
|
143
|
+
layer.push(...classToHooks.get(cls) ?? []);
|
|
144
|
+
}
|
|
145
|
+
layers.push(layer);
|
|
146
|
+
for (const cls of queue) {
|
|
147
|
+
for (const neighbor of graph.get(cls) ?? []) {
|
|
148
|
+
const d = inDegree.get(neighbor) - 1;
|
|
149
|
+
inDegree.set(neighbor, d);
|
|
150
|
+
if (d === 0) next.push(neighbor);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
queue = next;
|
|
154
|
+
}
|
|
155
|
+
if (anonymous.length > 0) {
|
|
156
|
+
const anonWithDeps = anonymous.filter((h) => h.after.length > 0);
|
|
157
|
+
const anonWithoutDeps = anonymous.filter((h) => h.after.length === 0);
|
|
158
|
+
if (anonWithDeps.length > 0) {
|
|
159
|
+
let maxLayer = -1;
|
|
160
|
+
for (const h of anonWithDeps) {
|
|
161
|
+
for (const dep of h.after) {
|
|
162
|
+
const depLayer = layers.findIndex((l) => l.some((r) => r.class === dep));
|
|
163
|
+
if (depLayer > maxLayer) maxLayer = depLayer;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const insertAt = maxLayer + 1;
|
|
167
|
+
if (insertAt < layers.length) {
|
|
168
|
+
layers[insertAt].push(...anonWithDeps);
|
|
169
|
+
} else {
|
|
170
|
+
layers.push(anonWithDeps);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (anonWithoutDeps.length > 0) {
|
|
174
|
+
if (layers.length > 0) {
|
|
175
|
+
layers[layers.length - 1].push(...anonWithoutDeps);
|
|
176
|
+
} else {
|
|
177
|
+
layers.push(anonWithoutDeps);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return layers;
|
|
182
|
+
}
|
|
183
|
+
async function runHooks(hooks, onError = (error) => {
|
|
184
|
+
throw error;
|
|
185
|
+
}, invert = false) {
|
|
186
|
+
const layers = resolveHookDAG(hooks, invert);
|
|
187
|
+
for (const layer of layers)
|
|
188
|
+
await Promise.all(
|
|
189
|
+
layer.map(async (h) => {
|
|
190
|
+
try {
|
|
191
|
+
if (typeof h.cb === "function") return await h.cb();
|
|
192
|
+
return await h.cb;
|
|
193
|
+
} catch (error) {
|
|
194
|
+
return onError(error instanceof Error ? error : new Error(`${error}`));
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
if (void 0) {
|
|
200
|
+
const { describe, test, expect } = void 0;
|
|
201
|
+
class A {
|
|
202
|
+
}
|
|
203
|
+
class B {
|
|
204
|
+
}
|
|
205
|
+
class C {
|
|
206
|
+
}
|
|
207
|
+
class D {
|
|
208
|
+
}
|
|
209
|
+
const noop = () => {
|
|
210
|
+
};
|
|
211
|
+
describe("registerHook", () => {
|
|
212
|
+
test("registers a hook with no dependencies", () => {
|
|
213
|
+
const hooks = [];
|
|
214
|
+
registerHook(hooks, { cb: noop, class: A, after: [] });
|
|
215
|
+
expect(hooks).toHaveLength(1);
|
|
216
|
+
});
|
|
217
|
+
test("throws on missing dependency", () => {
|
|
218
|
+
const hooks = [];
|
|
219
|
+
expect(() => registerHook(hooks, { cb: noop, class: B, after: [A] })).toThrow(/Missing dependency.*B.*A/);
|
|
220
|
+
});
|
|
221
|
+
test("throws on cycle: A\u2192B\u2192A", () => {
|
|
222
|
+
const hooks = [];
|
|
223
|
+
registerHook(hooks, { cb: noop, class: A, after: [] });
|
|
224
|
+
registerHook(hooks, { cb: noop, class: B, after: [A] });
|
|
225
|
+
expect(() => registerHook(hooks, { cb: noop, class: A, after: [B] })).toThrow(/Cycle detected/);
|
|
226
|
+
});
|
|
227
|
+
test("throws on transitive cycle: A\u2192B\u2192C\u2192A", () => {
|
|
228
|
+
const hooks = [];
|
|
229
|
+
registerHook(hooks, { cb: noop, class: A, after: [] });
|
|
230
|
+
registerHook(hooks, { cb: noop, class: B, after: [A] });
|
|
231
|
+
registerHook(hooks, { cb: noop, class: C, after: [B] });
|
|
232
|
+
expect(() => registerHook(hooks, { cb: noop, class: A, after: [C] })).toThrow(/Cycle detected/);
|
|
233
|
+
});
|
|
234
|
+
test("allows anonymous hooks with dependencies", () => {
|
|
235
|
+
const hooks = [];
|
|
236
|
+
registerHook(hooks, { cb: noop, class: A, after: [] });
|
|
237
|
+
registerHook(hooks, { cb: noop, after: [A] });
|
|
238
|
+
expect(hooks).toHaveLength(2);
|
|
239
|
+
});
|
|
240
|
+
test("anonymous hook throws on missing dependency", () => {
|
|
241
|
+
const hooks = [];
|
|
242
|
+
expect(() => registerHook(hooks, { cb: noop, after: [A] })).toThrow(/Missing dependency.*anonymous.*A/);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
describe("resolveHookDAG", () => {
|
|
246
|
+
test("linear chain: A \u2192 B \u2192 C", () => {
|
|
247
|
+
const hooks = [
|
|
248
|
+
{ cb: noop, class: A, after: [] },
|
|
249
|
+
{ cb: noop, class: B, after: [A] },
|
|
250
|
+
{ cb: noop, class: C, after: [B] }
|
|
251
|
+
];
|
|
252
|
+
const layers = resolveHookDAG(hooks);
|
|
253
|
+
expect(layers).toHaveLength(3);
|
|
254
|
+
expect(layers[0].every((h) => h.class === A)).toBe(true);
|
|
255
|
+
expect(layers[1].every((h) => h.class === B)).toBe(true);
|
|
256
|
+
expect(layers[2].every((h) => h.class === C)).toBe(true);
|
|
257
|
+
});
|
|
258
|
+
test("fan-out: A \u2192 B, A \u2192 C (B and C are parallel)", () => {
|
|
259
|
+
const hooks = [
|
|
260
|
+
{ cb: noop, class: A, after: [] },
|
|
261
|
+
{ cb: noop, class: B, after: [A] },
|
|
262
|
+
{ cb: noop, class: C, after: [A] }
|
|
263
|
+
];
|
|
264
|
+
const layers = resolveHookDAG(hooks);
|
|
265
|
+
expect(layers).toHaveLength(2);
|
|
266
|
+
expect(layers[0].every((h) => h.class === A)).toBe(true);
|
|
267
|
+
const secondClasses = layers[1].map((h) => h.class);
|
|
268
|
+
expect(secondClasses).toContain(B);
|
|
269
|
+
expect(secondClasses).toContain(C);
|
|
270
|
+
});
|
|
271
|
+
test("fan-in: B \u2192 D, C \u2192 D", () => {
|
|
272
|
+
const hooks = [
|
|
273
|
+
{ cb: noop, class: B, after: [] },
|
|
274
|
+
{ cb: noop, class: C, after: [] },
|
|
275
|
+
{ cb: noop, class: D, after: [B, C] }
|
|
276
|
+
];
|
|
277
|
+
const layers = resolveHookDAG(hooks);
|
|
278
|
+
expect(layers).toHaveLength(2);
|
|
279
|
+
const firstClasses = layers[0].map((h) => h.class);
|
|
280
|
+
expect(firstClasses).toContain(B);
|
|
281
|
+
expect(firstClasses).toContain(C);
|
|
282
|
+
expect(layers[1].every((h) => h.class === D)).toBe(true);
|
|
283
|
+
});
|
|
284
|
+
test("diamond: A \u2192 B, A \u2192 C, B \u2192 D, C \u2192 D", () => {
|
|
285
|
+
const hooks = [
|
|
286
|
+
{ cb: noop, class: A, after: [] },
|
|
287
|
+
{ cb: noop, class: B, after: [A] },
|
|
288
|
+
{ cb: noop, class: C, after: [A] },
|
|
289
|
+
{ cb: noop, class: D, after: [B, C] }
|
|
290
|
+
];
|
|
291
|
+
const layers = resolveHookDAG(hooks);
|
|
292
|
+
expect(layers).toHaveLength(3);
|
|
293
|
+
expect(layers[0][0].class).toBe(A);
|
|
294
|
+
const midClasses = layers[1].map((h) => h.class);
|
|
295
|
+
expect(midClasses).toContain(B);
|
|
296
|
+
expect(midClasses).toContain(C);
|
|
297
|
+
expect(layers[2][0].class).toBe(D);
|
|
298
|
+
});
|
|
299
|
+
test("close-event inversion reverses the graph", () => {
|
|
300
|
+
const hooks = [
|
|
301
|
+
{ cb: noop, class: A, after: [] },
|
|
302
|
+
{ cb: noop, class: B, after: [A] },
|
|
303
|
+
{ cb: noop, class: C, after: [B] }
|
|
304
|
+
];
|
|
305
|
+
const layers = resolveHookDAG(hooks, true);
|
|
306
|
+
expect(layers).toHaveLength(3);
|
|
307
|
+
expect(layers[0].every((h) => h.class === C)).toBe(true);
|
|
308
|
+
expect(layers[1].every((h) => h.class === B)).toBe(true);
|
|
309
|
+
expect(layers[2].every((h) => h.class === A)).toBe(true);
|
|
310
|
+
});
|
|
311
|
+
test("close inversion: fan-out becomes fan-in", () => {
|
|
312
|
+
const hooks = [
|
|
313
|
+
{ cb: noop, class: A, after: [] },
|
|
314
|
+
{ cb: noop, class: B, after: [A] },
|
|
315
|
+
{ cb: noop, class: C, after: [A] }
|
|
316
|
+
];
|
|
317
|
+
const layers = resolveHookDAG(hooks, true);
|
|
318
|
+
expect(layers).toHaveLength(2);
|
|
319
|
+
const firstClasses = layers[0].map((h) => h.class);
|
|
320
|
+
expect(firstClasses).toContain(B);
|
|
321
|
+
expect(firstClasses).toContain(C);
|
|
322
|
+
expect(layers[1].every((h) => h.class === A)).toBe(true);
|
|
323
|
+
});
|
|
324
|
+
test("multiple hooks per class are all in the same layer", () => {
|
|
325
|
+
const cb1 = () => {
|
|
326
|
+
};
|
|
327
|
+
const cb2 = () => {
|
|
328
|
+
};
|
|
329
|
+
const hooks = [
|
|
330
|
+
{ cb: cb1, class: A, after: [] },
|
|
331
|
+
{ cb: cb2, class: A, after: [] },
|
|
332
|
+
{ cb: noop, class: B, after: [A] }
|
|
333
|
+
];
|
|
334
|
+
const layers = resolveHookDAG(hooks);
|
|
335
|
+
expect(layers).toHaveLength(2);
|
|
336
|
+
expect(layers[0]).toHaveLength(2);
|
|
337
|
+
expect(layers[0].every((h) => h.class === A)).toBe(true);
|
|
338
|
+
});
|
|
339
|
+
test("anonymous hooks without deps run at deepest level", () => {
|
|
340
|
+
const hooks = [
|
|
341
|
+
{ cb: noop, class: A, after: [] },
|
|
342
|
+
{ cb: noop, class: B, after: [A] },
|
|
343
|
+
{ cb: noop, after: [] }
|
|
344
|
+
];
|
|
345
|
+
const layers = resolveHookDAG(hooks);
|
|
346
|
+
expect(layers).toHaveLength(2);
|
|
347
|
+
const lastLayer = layers[layers.length - 1];
|
|
348
|
+
expect(lastLayer.some((h) => h.class === void 0)).toBe(true);
|
|
349
|
+
});
|
|
350
|
+
test("anonymous hooks with deps run after their dependencies", () => {
|
|
351
|
+
const hooks = [
|
|
352
|
+
{ cb: noop, class: A, after: [] },
|
|
353
|
+
{ cb: noop, class: B, after: [A] },
|
|
354
|
+
{ cb: noop, class: C, after: [B] },
|
|
355
|
+
{ cb: noop, after: [A] }
|
|
356
|
+
];
|
|
357
|
+
const layers = resolveHookDAG(hooks);
|
|
358
|
+
const anonLayer = layers.findIndex((l) => l.some((h) => h.class === void 0));
|
|
359
|
+
const aLayer = layers.findIndex((l) => l.some((h) => h.class === A));
|
|
360
|
+
expect(anonLayer).toBeGreaterThan(aLayer);
|
|
361
|
+
});
|
|
362
|
+
test("empty hooks returns empty layers", () => {
|
|
363
|
+
expect(resolveHookDAG([])).toEqual([]);
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
describe("runHooks", () => {
|
|
367
|
+
test("same-depth siblings observably overlap", async () => {
|
|
368
|
+
const log = [];
|
|
369
|
+
const hooks = [
|
|
370
|
+
{
|
|
371
|
+
cb: async () => {
|
|
372
|
+
log.push("B-start");
|
|
373
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
374
|
+
log.push("B-end");
|
|
375
|
+
},
|
|
376
|
+
class: B,
|
|
377
|
+
after: []
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
cb: async () => {
|
|
381
|
+
log.push("C-start");
|
|
382
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
383
|
+
log.push("C-end");
|
|
384
|
+
},
|
|
385
|
+
class: C,
|
|
386
|
+
after: []
|
|
387
|
+
}
|
|
388
|
+
];
|
|
389
|
+
await runHooks(hooks);
|
|
390
|
+
expect(log[0]).toBe("B-start");
|
|
391
|
+
expect(log[1]).toBe("C-start");
|
|
392
|
+
});
|
|
393
|
+
test("different depths run sequentially", async () => {
|
|
394
|
+
const log = [];
|
|
395
|
+
const hooks = [
|
|
396
|
+
{
|
|
397
|
+
cb: async () => {
|
|
398
|
+
log.push("A");
|
|
399
|
+
},
|
|
400
|
+
class: A,
|
|
401
|
+
after: []
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
cb: async () => {
|
|
405
|
+
log.push("B");
|
|
406
|
+
},
|
|
407
|
+
class: B,
|
|
408
|
+
after: [A]
|
|
409
|
+
}
|
|
410
|
+
];
|
|
411
|
+
await runHooks(hooks);
|
|
412
|
+
expect(log).toEqual(["A", "B"]);
|
|
413
|
+
});
|
|
414
|
+
test("close inversion runs hooks in reverse dependency order", async () => {
|
|
415
|
+
const log = [];
|
|
416
|
+
const hooks = [
|
|
417
|
+
{ cb: async () => log.push("A"), class: A, after: [] },
|
|
418
|
+
{ cb: async () => log.push("B"), class: B, after: [A] },
|
|
419
|
+
{ cb: async () => log.push("C"), class: C, after: [B] }
|
|
420
|
+
];
|
|
421
|
+
await runHooks(hooks, void 0, true);
|
|
422
|
+
expect(log).toEqual(["C", "B", "A"]);
|
|
423
|
+
});
|
|
424
|
+
test("errors are routed to onError handler", async () => {
|
|
425
|
+
const errors = [];
|
|
426
|
+
const hooks = [
|
|
427
|
+
{
|
|
428
|
+
cb: () => {
|
|
429
|
+
throw new Error("boom");
|
|
430
|
+
},
|
|
431
|
+
class: A,
|
|
432
|
+
after: []
|
|
433
|
+
}
|
|
434
|
+
];
|
|
435
|
+
await runHooks(hooks, (e) => {
|
|
436
|
+
errors.push(e);
|
|
437
|
+
});
|
|
438
|
+
expect(errors).toHaveLength(1);
|
|
439
|
+
expect(errors[0].message).toBe("boom");
|
|
440
|
+
});
|
|
441
|
+
test("raw promise callbacks are awaited", async () => {
|
|
442
|
+
const log = [];
|
|
443
|
+
const hooks = [
|
|
444
|
+
{
|
|
445
|
+
cb: Promise.resolve().then(() => log.push("resolved")),
|
|
446
|
+
class: A,
|
|
447
|
+
after: []
|
|
448
|
+
}
|
|
449
|
+
];
|
|
450
|
+
await runHooks(hooks);
|
|
451
|
+
expect(log).toContain("resolved");
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// src/instance/settings.ts
|
|
457
|
+
var import_valleyed = require("valleyed");
|
|
458
|
+
var instanceSettingsPipe = () => import_valleyed.v.object({
|
|
459
|
+
app: import_valleyed.v.object({
|
|
460
|
+
name: import_valleyed.v.string()
|
|
461
|
+
}),
|
|
462
|
+
log: import_valleyed.v.defaults(
|
|
463
|
+
import_valleyed.v.object({
|
|
464
|
+
level: import_valleyed.v.defaults(import_valleyed.v.in(["fatal", "error", "warn", "info", "debug", "trace", "silent"]), "info")
|
|
465
|
+
}),
|
|
466
|
+
{}
|
|
467
|
+
),
|
|
468
|
+
utils: import_valleyed.v.defaults(
|
|
469
|
+
import_valleyed.v.object({
|
|
470
|
+
hashSaltRounds: import_valleyed.v.defaults(import_valleyed.v.number(), 10),
|
|
471
|
+
paginationDefaultLimit: import_valleyed.v.defaults(import_valleyed.v.number(), 100),
|
|
472
|
+
maxFileUploadSizeInMb: import_valleyed.v.defaults(import_valleyed.v.number(), 500)
|
|
473
|
+
}),
|
|
474
|
+
{}
|
|
475
|
+
)
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
// src/instance/index.ts
|
|
479
|
+
var Instance = class _Instance {
|
|
480
|
+
static #id;
|
|
481
|
+
static #instance;
|
|
482
|
+
static #hooks = {};
|
|
483
|
+
settings;
|
|
484
|
+
log;
|
|
485
|
+
constructor(settings) {
|
|
486
|
+
_Instance.#instance = this;
|
|
487
|
+
this.settings = Object.freeze(settings);
|
|
488
|
+
this.log = (0, import_pino.default)({
|
|
489
|
+
level: this.settings.log.level,
|
|
490
|
+
serializers: {
|
|
491
|
+
err: import_pino.default.stdSerializers.err,
|
|
492
|
+
error: import_pino.default.stdSerializers.err,
|
|
493
|
+
req: import_pino.default.stdSerializers.req,
|
|
494
|
+
res: import_pino.default.stdSerializers.res
|
|
495
|
+
},
|
|
496
|
+
mixin: () => ({
|
|
497
|
+
instanceId: _Instance.#id
|
|
498
|
+
})
|
|
499
|
+
});
|
|
500
|
+
_Instance.#registerOnExitHandler();
|
|
501
|
+
}
|
|
502
|
+
alias(id) {
|
|
503
|
+
if (_Instance.#id !== void 0) return _Instance.crash(new EquippedError("Instance already has an alias", {}));
|
|
504
|
+
_Instance.#id = id;
|
|
505
|
+
}
|
|
506
|
+
get id() {
|
|
507
|
+
if (_Instance.#id === void 0) return _Instance.crash(new EquippedError("Instance doesnt have an alias yet", {}));
|
|
508
|
+
return _Instance.#id;
|
|
509
|
+
}
|
|
510
|
+
getScopedName(name, key = ".") {
|
|
511
|
+
return [this.settings.app.name, name].join(key);
|
|
512
|
+
}
|
|
513
|
+
async start() {
|
|
514
|
+
try {
|
|
515
|
+
await runHooks(_Instance.#hooks["setup"] ?? []);
|
|
516
|
+
await runHooks(_Instance.#hooks["start"] ?? []);
|
|
517
|
+
} catch (error) {
|
|
518
|
+
_Instance.crash(new EquippedError(`Error starting instance`, {}, error));
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
static envs(envsPipe) {
|
|
522
|
+
const envValidity = import_valleyed2.v.validate(envsPipe, process.env);
|
|
523
|
+
if (!envValidity.valid) {
|
|
524
|
+
_Instance.crash(
|
|
525
|
+
new EquippedError(`Environment variables are not valid
|
|
526
|
+
${envValidity.error.toString()}`, {
|
|
527
|
+
messages: envValidity.error.messages
|
|
528
|
+
})
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
return envValidity.value;
|
|
532
|
+
}
|
|
533
|
+
static create(settings) {
|
|
534
|
+
if (_Instance.#instance) return _Instance.crash(new EquippedError("Instance has been initialized already", {}));
|
|
535
|
+
const settingsValidity = import_valleyed2.v.validate(instanceSettingsPipe(), settings);
|
|
536
|
+
if (!settingsValidity.valid) {
|
|
537
|
+
_Instance.crash(
|
|
538
|
+
new EquippedError(`Settings are not valid
|
|
539
|
+
${settingsValidity.error.toString()}`, {
|
|
540
|
+
messages: settingsValidity.error.messages
|
|
541
|
+
})
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
return new _Instance(settingsValidity.value);
|
|
545
|
+
}
|
|
546
|
+
static get() {
|
|
547
|
+
if (!_Instance.#instance)
|
|
548
|
+
return _Instance.crash(
|
|
549
|
+
new EquippedError("Has not been initialized. Make sure an instance has been created before you get an instance", {})
|
|
550
|
+
);
|
|
551
|
+
return _Instance.#instance;
|
|
552
|
+
}
|
|
553
|
+
static maybeGet() {
|
|
554
|
+
return _Instance.#instance;
|
|
555
|
+
}
|
|
556
|
+
static on(event, cb, options) {
|
|
557
|
+
_Instance.#hooks[event] ??= [];
|
|
558
|
+
const record = { cb, class: options?.class, after: options?.after ?? [] };
|
|
559
|
+
registerHook(_Instance.#hooks[event], record);
|
|
560
|
+
}
|
|
561
|
+
static #registerOnExitHandler() {
|
|
562
|
+
const signals = {
|
|
563
|
+
SIGHUP: 1,
|
|
564
|
+
SIGINT: 2,
|
|
565
|
+
SIGTERM: 15
|
|
566
|
+
};
|
|
567
|
+
Object.entries(signals).forEach(([signal, code]) => {
|
|
568
|
+
process.on(signal, async () => {
|
|
569
|
+
await runHooks(_Instance.#hooks["close"] ?? [], () => {
|
|
570
|
+
}, true);
|
|
571
|
+
process.exit(128 + code);
|
|
572
|
+
});
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
static resolveBeforeCrash(cb) {
|
|
576
|
+
const value = cb();
|
|
577
|
+
_Instance.on("close", async () => await value);
|
|
578
|
+
return value;
|
|
579
|
+
}
|
|
580
|
+
static crash(error) {
|
|
581
|
+
console.error(error);
|
|
582
|
+
process.exit(1);
|
|
583
|
+
}
|
|
584
|
+
static createId(opts) {
|
|
585
|
+
return `${opts?.prefix ?? ""}${(0, import_ulid.ulid)(opts?.time?.getTime())}`;
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
// src/utilities/configurable.ts
|
|
590
|
+
var import_valleyed3 = require("valleyed");
|
|
591
|
+
function configurable(pipeFn, base) {
|
|
592
|
+
const pipe = pipeFn();
|
|
593
|
+
import_valleyed3.v.compile(pipe);
|
|
594
|
+
class Configurable extends base {
|
|
595
|
+
constructor(config, ...baseArgs) {
|
|
596
|
+
super(...baseArgs);
|
|
597
|
+
this.config = config;
|
|
598
|
+
}
|
|
599
|
+
static create(input, ...args) {
|
|
600
|
+
const r = import_valleyed3.v.validate(pipe, input);
|
|
601
|
+
if (!r.valid) throw r.error;
|
|
602
|
+
return new this(r.value, ...args);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return Configurable;
|
|
606
|
+
}
|
|
607
|
+
if (void 0) {
|
|
608
|
+
const { describe, test, expect, expectTypeOf } = void 0;
|
|
609
|
+
const { v: v6 } = null;
|
|
610
|
+
const testPipe = () => v6.object({
|
|
611
|
+
host: v6.string(),
|
|
612
|
+
port: v6.number()
|
|
613
|
+
});
|
|
614
|
+
class TestBase {
|
|
615
|
+
baseValue;
|
|
616
|
+
constructor() {
|
|
617
|
+
this.baseValue = "base";
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
class TestBaseWithArgs {
|
|
621
|
+
label;
|
|
622
|
+
constructor(label) {
|
|
623
|
+
this.label = label;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
describe("configurable", () => {
|
|
627
|
+
test("validation runs in static create before constructor body executes", () => {
|
|
628
|
+
let constructorRan = false;
|
|
629
|
+
const Wrapped = configurable(testPipe, TestBase);
|
|
630
|
+
class MyClass extends Wrapped {
|
|
631
|
+
constructor(config) {
|
|
632
|
+
super(config);
|
|
633
|
+
constructorRan = true;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
expect(() => MyClass.create({ host: 123, port: "bad" })).toThrow();
|
|
637
|
+
expect(constructorRan).toBe(false);
|
|
638
|
+
MyClass.create({ host: "localhost", port: 3e3 });
|
|
639
|
+
expect(constructorRan).toBe(true);
|
|
640
|
+
});
|
|
641
|
+
test("constructor receives validated value", () => {
|
|
642
|
+
const Wrapped = configurable(testPipe, TestBase);
|
|
643
|
+
let receivedConfig;
|
|
644
|
+
class MyClass extends Wrapped {
|
|
645
|
+
constructor(config) {
|
|
646
|
+
super(config);
|
|
647
|
+
receivedConfig = this.config;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
MyClass.create({ host: "localhost", port: 3e3 });
|
|
651
|
+
expect(receivedConfig).toEqual({ host: "localhost", port: 3e3 });
|
|
652
|
+
});
|
|
653
|
+
test("external new is a compile error", () => {
|
|
654
|
+
const Wrapped = configurable(testPipe, TestBase);
|
|
655
|
+
class MyClass extends Wrapped {
|
|
656
|
+
constructor(config) {
|
|
657
|
+
super(config);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
void (() => new MyClass({ host: "localhost", port: 3e3 }));
|
|
661
|
+
});
|
|
662
|
+
test("static Config resolves to PipeOutput<P> at the type level", () => {
|
|
663
|
+
const Wrapped = configurable(testPipe, TestBase);
|
|
664
|
+
class MyClass extends Wrapped {
|
|
665
|
+
constructor(config) {
|
|
666
|
+
super(config);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
expectTypeOf().toEqualTypeOf();
|
|
670
|
+
expect(MyClass.create).toBeTypeOf("function");
|
|
671
|
+
});
|
|
672
|
+
test("ConstructorParameters<This>-based extras inference works for non-zero-arg leaf signatures", () => {
|
|
673
|
+
const Wrapped = configurable(testPipe, TestBase);
|
|
674
|
+
class MyClass extends Wrapped {
|
|
675
|
+
extra;
|
|
676
|
+
constructor(config, extra) {
|
|
677
|
+
super(config);
|
|
678
|
+
this.extra = extra;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
const instance = MyClass.create({ host: "localhost", port: 3e3 }, 42);
|
|
682
|
+
expect(instance.extra).toBe(42);
|
|
683
|
+
expectTypeOf(instance).toHaveProperty("extra");
|
|
684
|
+
expectTypeOf(instance.extra).toEqualTypeOf();
|
|
685
|
+
void (() => MyClass.create({ host: "localhost", port: 3e3 }, "not-a-number"));
|
|
686
|
+
});
|
|
687
|
+
test("base-args forwarding works for non-zero-arg bases", () => {
|
|
688
|
+
const Wrapped = configurable(testPipe, TestBaseWithArgs);
|
|
689
|
+
class MyClass extends Wrapped {
|
|
690
|
+
constructor(config, label) {
|
|
691
|
+
super(config, label);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
const instance = MyClass.create({ host: "localhost", port: 3e3 }, "test-label");
|
|
695
|
+
expect(instance.label).toBe("test-label");
|
|
696
|
+
});
|
|
697
|
+
test("config is accessible as protected readonly on instances", () => {
|
|
698
|
+
const Wrapped = configurable(testPipe, TestBase);
|
|
699
|
+
class MyClass extends Wrapped {
|
|
700
|
+
constructor(config) {
|
|
701
|
+
super(config);
|
|
702
|
+
}
|
|
703
|
+
getHost() {
|
|
704
|
+
return this.config.host;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
const instance = MyClass.create({ host: "localhost", port: 3e3 });
|
|
708
|
+
expect(instance.getHost()).toBe("localhost");
|
|
709
|
+
});
|
|
710
|
+
test("accepts an abstract base class", () => {
|
|
711
|
+
class AbstractBase {
|
|
712
|
+
}
|
|
713
|
+
const Wrapped = configurable(testPipe, AbstractBase);
|
|
714
|
+
class Concrete extends Wrapped {
|
|
715
|
+
constructor(config) {
|
|
716
|
+
super(config);
|
|
717
|
+
}
|
|
718
|
+
greet() {
|
|
719
|
+
return `hello from ${this.config.host}`;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
const instance = Concrete.create({ host: "localhost", port: 3e3 });
|
|
723
|
+
expect(instance.greet()).toBe("hello from localhost");
|
|
724
|
+
expectTypeOf(instance).toHaveProperty("config");
|
|
725
|
+
});
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// src/orm/fields.ts
|
|
730
|
+
var import_valleyed4 = require("valleyed");
|
|
731
|
+
var Field = class {
|
|
732
|
+
name;
|
|
733
|
+
path;
|
|
734
|
+
constructor(name, path) {
|
|
735
|
+
this.name = name;
|
|
736
|
+
this.path = path ?? [name];
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
function toFieldName(field) {
|
|
740
|
+
if (field instanceof Field) return field.path.join(".");
|
|
741
|
+
return field;
|
|
742
|
+
}
|
|
743
|
+
if (void 0) {
|
|
744
|
+
const { describe, test, expect } = void 0;
|
|
745
|
+
describe("Fields", () => {
|
|
746
|
+
describe("Field", () => {
|
|
747
|
+
test("stores name and defaults path to [name]", () => {
|
|
748
|
+
const f = new Field("email");
|
|
749
|
+
expect(f.name).toBe("email");
|
|
750
|
+
expect(f.path).toEqual(["email"]);
|
|
751
|
+
});
|
|
752
|
+
test("stores provided custom path", () => {
|
|
753
|
+
const f = new Field("profile", ["profile", "displayName"]);
|
|
754
|
+
expect(f.name).toBe("profile");
|
|
755
|
+
expect(f.path).toEqual(["profile", "displayName"]);
|
|
756
|
+
});
|
|
757
|
+
});
|
|
758
|
+
describe("toFieldName()", () => {
|
|
759
|
+
test("returns string input unchanged", () => {
|
|
760
|
+
expect(toFieldName("createdAt")).toBe("createdAt");
|
|
761
|
+
});
|
|
762
|
+
test("converts Field path into dot notation", () => {
|
|
763
|
+
const f = new Field("profile", ["profile", "displayName"]);
|
|
764
|
+
expect(toFieldName(f)).toBe("profile.displayName");
|
|
765
|
+
});
|
|
766
|
+
});
|
|
767
|
+
describe("SchemaField", () => {
|
|
768
|
+
test("extends Field and stores pipe", () => {
|
|
769
|
+
const mockPipe = v4.string();
|
|
770
|
+
const f = new SchemaField("email", mockPipe);
|
|
771
|
+
expect(f).toBeInstanceOf(Field);
|
|
772
|
+
expect(f.name).toBe("email");
|
|
773
|
+
expect(f.path).toEqual(["email"]);
|
|
774
|
+
expect(f.pipe).toBe(mockPipe);
|
|
775
|
+
});
|
|
776
|
+
test("stores lifecycle hooks when provided", () => {
|
|
777
|
+
const mockPipe = v4.number();
|
|
778
|
+
const f = new SchemaField("updatedAt", mockPipe, {
|
|
779
|
+
onCreate: () => 1e3,
|
|
780
|
+
onUpdate: () => 2e3
|
|
781
|
+
});
|
|
782
|
+
expect(f.onCreate?.()).toBe(1e3);
|
|
783
|
+
expect(f.onUpdate?.()).toBe(2e3);
|
|
784
|
+
});
|
|
785
|
+
test("keeps lifecycle hooks undefined when omitted", () => {
|
|
786
|
+
const mockPipe = v4.string();
|
|
787
|
+
const f = new SchemaField("name", mockPipe);
|
|
788
|
+
expect(f.onCreate).toBeUndefined();
|
|
789
|
+
expect(f.onUpdate).toBeUndefined();
|
|
790
|
+
});
|
|
791
|
+
});
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// src/orm/filter.ts
|
|
796
|
+
var Filter = class {
|
|
797
|
+
constructor(field, op, value) {
|
|
798
|
+
this.op = op;
|
|
799
|
+
this.value = value;
|
|
800
|
+
this.field = toFieldName(field);
|
|
801
|
+
}
|
|
802
|
+
field;
|
|
803
|
+
};
|
|
804
|
+
var FilterGroup = class _FilterGroup {
|
|
805
|
+
constructor(op = "and", children) {
|
|
806
|
+
this.op = op;
|
|
807
|
+
this.children = children ?? [];
|
|
808
|
+
}
|
|
809
|
+
children;
|
|
810
|
+
#withChild(child) {
|
|
811
|
+
return new _FilterGroup(this.op, [...this.children, child]);
|
|
812
|
+
}
|
|
813
|
+
eq(field, value) {
|
|
814
|
+
return this.#withChild(new Filter(field, "eq", value));
|
|
815
|
+
}
|
|
816
|
+
ne(field, value) {
|
|
817
|
+
return this.#withChild(new Filter(field, "ne", value));
|
|
818
|
+
}
|
|
819
|
+
gt(field, value) {
|
|
820
|
+
return this.#withChild(new Filter(field, "gt", value));
|
|
821
|
+
}
|
|
822
|
+
gte(field, value) {
|
|
823
|
+
return this.#withChild(new Filter(field, "gte", value));
|
|
824
|
+
}
|
|
825
|
+
lt(field, value) {
|
|
826
|
+
return this.#withChild(new Filter(field, "lt", value));
|
|
827
|
+
}
|
|
828
|
+
lte(field, value) {
|
|
829
|
+
return this.#withChild(new Filter(field, "lte", value));
|
|
830
|
+
}
|
|
831
|
+
in(field, value) {
|
|
832
|
+
return this.#withChild(new Filter(field, "in", value));
|
|
833
|
+
}
|
|
834
|
+
notIn(field, value) {
|
|
835
|
+
return this.#withChild(new Filter(field, "notIn", value));
|
|
836
|
+
}
|
|
837
|
+
like(field, value) {
|
|
838
|
+
return this.#withChild(new Filter(field, "like", value));
|
|
839
|
+
}
|
|
840
|
+
exists(field) {
|
|
841
|
+
return this.#withChild(new Filter(field, "exists", true));
|
|
842
|
+
}
|
|
843
|
+
notExists(field) {
|
|
844
|
+
return this.#withChild(new Filter(field, "notExists", true));
|
|
845
|
+
}
|
|
846
|
+
contains(field, value) {
|
|
847
|
+
return this.#withChild(new Filter(field, "contains", value));
|
|
848
|
+
}
|
|
849
|
+
notContains(field, value) {
|
|
850
|
+
return this.#withChild(new Filter(field, "notContains", value));
|
|
851
|
+
}
|
|
852
|
+
and(facFns) {
|
|
853
|
+
if (facFns.length === 0) throw new EquippedError("and() requires at least one filter factory", { op: "and" });
|
|
854
|
+
const group = new _FilterGroup("and", facFns.map((fn) => fn(_FilterGroup.create())));
|
|
855
|
+
return this.#withChild(group);
|
|
856
|
+
}
|
|
857
|
+
or(facFns) {
|
|
858
|
+
if (facFns.length === 0) throw new EquippedError("or() requires at least one filter factory", { op: "or" });
|
|
859
|
+
const group = new _FilterGroup("or", facFns.map((fn) => fn(_FilterGroup.create())));
|
|
860
|
+
return this.#withChild(group);
|
|
861
|
+
}
|
|
862
|
+
clone() {
|
|
863
|
+
return new _FilterGroup(
|
|
864
|
+
this.op,
|
|
865
|
+
this.children.map((c) => {
|
|
866
|
+
if (c instanceof Filter) return new Filter(c.field, c.op, structuredClone(c.value));
|
|
867
|
+
return c.clone();
|
|
868
|
+
})
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
static create() {
|
|
872
|
+
return new _FilterGroup();
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
if (void 0) {
|
|
876
|
+
const { describe, test, expect } = void 0;
|
|
877
|
+
const { v: v6 } = null;
|
|
878
|
+
const { Schema } = null;
|
|
879
|
+
const UserSchema = Schema.from("users").pk("id", v6.string(), () => "u1").field("email", v6.string()).field("age", v6.number()).field("name", v6.string()).field("tags", v6.array(v6.string())).build();
|
|
880
|
+
describe("FilterGroup", () => {
|
|
881
|
+
describe("filter-op methods", () => {
|
|
882
|
+
test("eq adds a filter clause with op eq", () => {
|
|
883
|
+
const g = FilterGroup.create().eq(UserSchema.fields.age, 25);
|
|
884
|
+
expect(g.children).toHaveLength(1);
|
|
885
|
+
const f = g.children[0];
|
|
886
|
+
expect(f.field).toBe("age");
|
|
887
|
+
expect(f.op).toBe("eq");
|
|
888
|
+
expect(f.value).toBe(25);
|
|
889
|
+
});
|
|
890
|
+
test("ne adds a filter clause with op ne", () => {
|
|
891
|
+
const g = FilterGroup.create().ne("name", "Bob");
|
|
892
|
+
const f = g.children[0];
|
|
893
|
+
expect(f.op).toBe("ne");
|
|
894
|
+
expect(f.value).toBe("Bob");
|
|
895
|
+
});
|
|
896
|
+
test("gt/gte/lt/lte produce correct ops", () => {
|
|
897
|
+
const g = FilterGroup.create().gt(UserSchema.fields.age, 10).gte(UserSchema.fields.age, 20).lt(UserSchema.fields.age, 30).lte(UserSchema.fields.age, 40);
|
|
898
|
+
expect(g.children.map((c) => c.op)).toEqual(["gt", "gte", "lt", "lte"]);
|
|
899
|
+
});
|
|
900
|
+
test("in adds a filter clause with array value", () => {
|
|
901
|
+
const g = FilterGroup.create().in(UserSchema.fields.age, [1, 2, 3]);
|
|
902
|
+
const f = g.children[0];
|
|
903
|
+
expect(f.op).toBe("in");
|
|
904
|
+
expect(f.value).toEqual([1, 2, 3]);
|
|
905
|
+
});
|
|
906
|
+
test("notIn adds a filter clause with op notIn", () => {
|
|
907
|
+
const g = FilterGroup.create().notIn(UserSchema.fields.age, [4, 5]);
|
|
908
|
+
const f = g.children[0];
|
|
909
|
+
expect(f.op).toBe("notIn");
|
|
910
|
+
expect(f.value).toEqual([4, 5]);
|
|
911
|
+
});
|
|
912
|
+
test("like adds a filter clause with op like", () => {
|
|
913
|
+
const g = FilterGroup.create().like(UserSchema.fields.email, "alice");
|
|
914
|
+
const f = g.children[0];
|
|
915
|
+
expect(f.op).toBe("like");
|
|
916
|
+
expect(f.value).toBe("alice");
|
|
917
|
+
});
|
|
918
|
+
test("exists adds a filter clause with op exists", () => {
|
|
919
|
+
const g = FilterGroup.create().exists(UserSchema.fields.name);
|
|
920
|
+
const f = g.children[0];
|
|
921
|
+
expect(f.op).toBe("exists");
|
|
922
|
+
});
|
|
923
|
+
test("notExists is its own op, not a boolean form of exists", () => {
|
|
924
|
+
const g = FilterGroup.create().notExists(UserSchema.fields.name);
|
|
925
|
+
const f = g.children[0];
|
|
926
|
+
expect(f.op).toBe("notExists");
|
|
927
|
+
expect(f.op).not.toBe("exists");
|
|
928
|
+
});
|
|
929
|
+
test("contains/notContains produce correct ops", () => {
|
|
930
|
+
const g = FilterGroup.create().contains("tags", ["a"]).notContains("tags", ["b"]);
|
|
931
|
+
expect(g.children[0].op).toBe("contains");
|
|
932
|
+
expect(g.children[1].op).toBe("notContains");
|
|
933
|
+
});
|
|
934
|
+
test("all 13 filter ops produce matching Filter.op values (name-parity)", () => {
|
|
935
|
+
const g = FilterGroup.create().eq("f", 1).ne("f", 1).gt("f", 1).gte("f", 1).lt("f", 1).lte("f", 1).in("f", [1]).notIn("f", [1]).like("f", "x").exists("f").notExists("f").contains("f", [1]).notContains("f", [1]);
|
|
936
|
+
const ops = g.children.map((c) => c.op);
|
|
937
|
+
expect(ops).toEqual([
|
|
938
|
+
"eq",
|
|
939
|
+
"ne",
|
|
940
|
+
"gt",
|
|
941
|
+
"gte",
|
|
942
|
+
"lt",
|
|
943
|
+
"lte",
|
|
944
|
+
"in",
|
|
945
|
+
"notIn",
|
|
946
|
+
"like",
|
|
947
|
+
"exists",
|
|
948
|
+
"notExists",
|
|
949
|
+
"contains",
|
|
950
|
+
"notContains"
|
|
951
|
+
]);
|
|
952
|
+
});
|
|
953
|
+
});
|
|
954
|
+
describe("raw-string field overload", () => {
|
|
955
|
+
test("accepts raw string field name", () => {
|
|
956
|
+
const g = FilterGroup.create().eq("age", 18);
|
|
957
|
+
const f = g.children[0];
|
|
958
|
+
expect(f.field).toBe("age");
|
|
959
|
+
});
|
|
960
|
+
test("accepts typed Field ref and extracts field name", () => {
|
|
961
|
+
const g = FilterGroup.create().eq(UserSchema.fields.age, 18);
|
|
962
|
+
const f = g.children[0];
|
|
963
|
+
expect(f.field).toBe("age");
|
|
964
|
+
});
|
|
965
|
+
});
|
|
966
|
+
describe("structural combinators", () => {
|
|
967
|
+
test("and() creates a nested and-group", () => {
|
|
968
|
+
const g = FilterGroup.create().and([
|
|
969
|
+
(q) => q.eq("age", 10),
|
|
970
|
+
(q) => q.eq("name", "Alice")
|
|
971
|
+
]);
|
|
972
|
+
expect(g.children).toHaveLength(1);
|
|
973
|
+
const nested = g.children[0];
|
|
974
|
+
expect(nested.op).toBe("and");
|
|
975
|
+
expect(nested.children).toHaveLength(2);
|
|
976
|
+
});
|
|
977
|
+
test("or() creates a nested or-group", () => {
|
|
978
|
+
const g = FilterGroup.create().or([
|
|
979
|
+
(q) => q.eq("name", "Alice"),
|
|
980
|
+
(q) => q.eq("name", "Bob")
|
|
981
|
+
]);
|
|
982
|
+
const nested = g.children[0];
|
|
983
|
+
expect(nested.op).toBe("or");
|
|
984
|
+
});
|
|
985
|
+
});
|
|
986
|
+
describe("empty-combinator rejection", () => {
|
|
987
|
+
test("and([]) throws at builder time", () => {
|
|
988
|
+
expect(() => FilterGroup.create().and([])).toThrow();
|
|
989
|
+
});
|
|
990
|
+
test("or([]) throws at builder time", () => {
|
|
991
|
+
expect(() => FilterGroup.create().or([])).toThrow();
|
|
992
|
+
});
|
|
993
|
+
test("thrown error has stack pointing at offending call", () => {
|
|
994
|
+
try {
|
|
995
|
+
FilterGroup.create().and([]);
|
|
996
|
+
expect.unreachable();
|
|
997
|
+
} catch (e) {
|
|
998
|
+
expect(e.stack).toContain("filter.ts");
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
});
|
|
1002
|
+
describe("clone()", () => {
|
|
1003
|
+
test("deep-clones the tree", () => {
|
|
1004
|
+
const original = FilterGroup.create().eq("name", "Alice").and([(q) => q.gt("age", 20)]);
|
|
1005
|
+
const cloned = original.clone();
|
|
1006
|
+
expect(cloned.children).toHaveLength(2);
|
|
1007
|
+
expect(cloned).not.toBe(original);
|
|
1008
|
+
expect(cloned.children[0]).not.toBe(original.children[0]);
|
|
1009
|
+
});
|
|
1010
|
+
test("structuredClone of values \u2014 mutations on clone do not leak", () => {
|
|
1011
|
+
const arr = [1, 2, 3];
|
|
1012
|
+
const original = FilterGroup.create().in("ids", arr);
|
|
1013
|
+
const cloned = original.clone();
|
|
1014
|
+
const clonedValue = cloned.children[0].value;
|
|
1015
|
+
clonedValue.push(4);
|
|
1016
|
+
expect(original.children[0].value).toEqual([1, 2, 3]);
|
|
1017
|
+
expect(clonedValue).toEqual([1, 2, 3, 4]);
|
|
1018
|
+
});
|
|
1019
|
+
});
|
|
1020
|
+
});
|
|
1021
|
+
describe("clone-on-step: fan-out independence", () => {
|
|
1022
|
+
test(".eq() returns a new FilterGroup, not the same instance", () => {
|
|
1023
|
+
const base = FilterGroup.create();
|
|
1024
|
+
const a = base.eq("name", "Alice");
|
|
1025
|
+
expect(a).not.toBe(base);
|
|
1026
|
+
});
|
|
1027
|
+
test("fan-out from shared base does not pollute either branch", () => {
|
|
1028
|
+
const base = FilterGroup.create().eq("name", "Alice");
|
|
1029
|
+
const branchA = base.gt("age", 20);
|
|
1030
|
+
const branchB = base.lt("age", 40);
|
|
1031
|
+
expect(branchA.children).toHaveLength(2);
|
|
1032
|
+
expect(branchB.children).toHaveLength(2);
|
|
1033
|
+
expect(base.children).toHaveLength(1);
|
|
1034
|
+
expect(branchA.children[1].op).toBe("gt");
|
|
1035
|
+
expect(branchB.children[1].op).toBe("lt");
|
|
1036
|
+
});
|
|
1037
|
+
test(".and() returns a new FilterGroup", () => {
|
|
1038
|
+
const base = FilterGroup.create().eq("name", "Alice");
|
|
1039
|
+
const withAnd = base.and([(q) => q.gt("age", 20)]);
|
|
1040
|
+
expect(withAnd).not.toBe(base);
|
|
1041
|
+
expect(base.children).toHaveLength(1);
|
|
1042
|
+
expect(withAnd.children).toHaveLength(2);
|
|
1043
|
+
});
|
|
1044
|
+
test(".or() returns a new FilterGroup", () => {
|
|
1045
|
+
const base = FilterGroup.create().eq("name", "Alice");
|
|
1046
|
+
const withOr = base.or([(q) => q.gt("age", 20)]);
|
|
1047
|
+
expect(withOr).not.toBe(base);
|
|
1048
|
+
expect(base.children).toHaveLength(1);
|
|
1049
|
+
expect(withOr.children).toHaveLength(2);
|
|
1050
|
+
});
|
|
1051
|
+
});
|
|
1052
|
+
describe("assertNormalisedFilter", () => {
|
|
1053
|
+
test("passes for valid filter referencing known fields", () => {
|
|
1054
|
+
const g = FilterGroup.create().eq(UserSchema.fields.age, 25);
|
|
1055
|
+
expect(() => assertNormalisedFilter(UserSchema, g)).not.toThrow();
|
|
1056
|
+
});
|
|
1057
|
+
test("passes for raw string field that exists on schema", () => {
|
|
1058
|
+
const g = FilterGroup.create().eq("email", "test@test.com");
|
|
1059
|
+
expect(() => assertNormalisedFilter(UserSchema, g)).not.toThrow();
|
|
1060
|
+
});
|
|
1061
|
+
test("rejects unknown field name with OrmValidationError", () => {
|
|
1062
|
+
const g = FilterGroup.create().eq("unknownField", 42);
|
|
1063
|
+
expect(() => assertNormalisedFilter(UserSchema, g)).toThrow(OrmValidationError);
|
|
1064
|
+
});
|
|
1065
|
+
test("rejected error has kind validation", () => {
|
|
1066
|
+
const g = FilterGroup.create().eq("badField", 42);
|
|
1067
|
+
try {
|
|
1068
|
+
assertNormalisedFilter(UserSchema, g);
|
|
1069
|
+
expect.unreachable();
|
|
1070
|
+
} catch (e) {
|
|
1071
|
+
expect(e).toBeInstanceOf(OrmValidationError);
|
|
1072
|
+
expect(e.kind).toBe("validation");
|
|
1073
|
+
expect(e.failures).toHaveLength(1);
|
|
1074
|
+
expect(e.failures[0].field).toBe("badField");
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
test("preserves logical field names verbatim for Field refs", () => {
|
|
1078
|
+
const g = FilterGroup.create().eq(UserSchema.fields.email, "a@b.com");
|
|
1079
|
+
const f = g.children[0];
|
|
1080
|
+
expect(f.field).toBe("email");
|
|
1081
|
+
assertNormalisedFilter(UserSchema, g);
|
|
1082
|
+
});
|
|
1083
|
+
test("preserves logical field names verbatim for raw-string overloads", () => {
|
|
1084
|
+
const g = FilterGroup.create().eq("age", 18);
|
|
1085
|
+
const f = g.children[0];
|
|
1086
|
+
expect(f.field).toBe("age");
|
|
1087
|
+
assertNormalisedFilter(UserSchema, g);
|
|
1088
|
+
});
|
|
1089
|
+
test("walks nested groups to detect unknown fields", () => {
|
|
1090
|
+
const g = FilterGroup.create().and([
|
|
1091
|
+
(q) => q.eq("name", "Alice"),
|
|
1092
|
+
(q) => q.or([(inner) => inner.eq("nonexistent", "val")])
|
|
1093
|
+
]);
|
|
1094
|
+
expect(() => assertNormalisedFilter(UserSchema, g)).toThrow(OrmValidationError);
|
|
1095
|
+
});
|
|
1096
|
+
});
|
|
1097
|
+
describe("assertNormalisedAggregate", () => {
|
|
1098
|
+
const adapter = { aggregateOps: ["count"] };
|
|
1099
|
+
test("passes for valid count aggregate", () => {
|
|
1100
|
+
const spec = { aggregates: [{ fn: "count", alias: "total" }], groupBy: [] };
|
|
1101
|
+
expect(() => assertNormalisedAggregate(UserSchema, adapter, spec)).not.toThrow();
|
|
1102
|
+
});
|
|
1103
|
+
test("rejects empty aggregator list", () => {
|
|
1104
|
+
const spec = { aggregates: [], groupBy: [] };
|
|
1105
|
+
expect(() => assertNormalisedAggregate(UserSchema, adapter, spec)).toThrow(OrmValidationError);
|
|
1106
|
+
try {
|
|
1107
|
+
assertNormalisedAggregate(UserSchema, adapter, spec);
|
|
1108
|
+
expect.unreachable();
|
|
1109
|
+
} catch (e) {
|
|
1110
|
+
expect(e.kind).toBe("aggregate");
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
test("rejects undeclared aggregate op", () => {
|
|
1114
|
+
const spec = { aggregates: [{ fn: "sum", alias: "total", field: "age" }], groupBy: [] };
|
|
1115
|
+
expect(() => assertNormalisedAggregate(UserSchema, adapter, spec)).toThrow(OrmValidationError);
|
|
1116
|
+
try {
|
|
1117
|
+
assertNormalisedAggregate(UserSchema, adapter, spec);
|
|
1118
|
+
expect.unreachable();
|
|
1119
|
+
} catch (e) {
|
|
1120
|
+
const err = e;
|
|
1121
|
+
expect(err.kind).toBe("aggregate");
|
|
1122
|
+
expect(err.failures[0].alias).toBe("total");
|
|
1123
|
+
expect(err.failures[0].cause).toContain("Undeclared");
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
test("rejects duplicate aliases", () => {
|
|
1127
|
+
const spec = {
|
|
1128
|
+
aggregates: [
|
|
1129
|
+
{ fn: "count", alias: "total" },
|
|
1130
|
+
{ fn: "count", alias: "total" }
|
|
1131
|
+
],
|
|
1132
|
+
groupBy: []
|
|
1133
|
+
};
|
|
1134
|
+
expect(() => assertNormalisedAggregate(UserSchema, adapter, spec)).toThrow(OrmValidationError);
|
|
1135
|
+
try {
|
|
1136
|
+
assertNormalisedAggregate(UserSchema, adapter, spec);
|
|
1137
|
+
expect.unreachable();
|
|
1138
|
+
} catch (e) {
|
|
1139
|
+
const err = e;
|
|
1140
|
+
expect(err.failures.some((f) => f.alias === "total")).toBe(true);
|
|
1141
|
+
}
|
|
1142
|
+
});
|
|
1143
|
+
test("validates where filter against schema", () => {
|
|
1144
|
+
const spec = {
|
|
1145
|
+
aggregates: [{ fn: "count", alias: "total" }],
|
|
1146
|
+
groupBy: [],
|
|
1147
|
+
where: FilterGroup.create().eq("unknownField", 42)
|
|
1148
|
+
};
|
|
1149
|
+
expect(() => assertNormalisedAggregate(UserSchema, adapter, spec)).toThrow(OrmValidationError);
|
|
1150
|
+
});
|
|
1151
|
+
test("collects multiple failures", () => {
|
|
1152
|
+
const spec = {
|
|
1153
|
+
aggregates: [
|
|
1154
|
+
{ fn: "sum", alias: "x", field: "age" },
|
|
1155
|
+
{ fn: "avg", alias: "x", field: "age" }
|
|
1156
|
+
],
|
|
1157
|
+
groupBy: []
|
|
1158
|
+
};
|
|
1159
|
+
try {
|
|
1160
|
+
assertNormalisedAggregate(UserSchema, adapter, spec);
|
|
1161
|
+
expect.unreachable();
|
|
1162
|
+
} catch (e) {
|
|
1163
|
+
const err = e;
|
|
1164
|
+
expect(err.failures.length).toBeGreaterThanOrEqual(2);
|
|
1165
|
+
}
|
|
1166
|
+
});
|
|
1167
|
+
test("rejects unknown groupBy field", () => {
|
|
1168
|
+
const allOps = { aggregateOps: ["count", "sum"] };
|
|
1169
|
+
const spec = {
|
|
1170
|
+
aggregates: [{ fn: "count", alias: "total" }],
|
|
1171
|
+
groupBy: ["nonexistent"]
|
|
1172
|
+
};
|
|
1173
|
+
try {
|
|
1174
|
+
assertNormalisedAggregate(UserSchema, allOps, spec);
|
|
1175
|
+
expect.unreachable();
|
|
1176
|
+
} catch (e) {
|
|
1177
|
+
const err = e;
|
|
1178
|
+
expect(err.kind).toBe("aggregate");
|
|
1179
|
+
expect(err.failures[0].cause).toContain("groupBy");
|
|
1180
|
+
expect(err.failures[0].field).toBe("nonexistent");
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1183
|
+
test("rejects alias colliding with groupBy field name", () => {
|
|
1184
|
+
const allOps = { aggregateOps: ["count"] };
|
|
1185
|
+
const spec = {
|
|
1186
|
+
aggregates: [{ fn: "count", alias: "name" }],
|
|
1187
|
+
groupBy: ["name"]
|
|
1188
|
+
};
|
|
1189
|
+
try {
|
|
1190
|
+
assertNormalisedAggregate(UserSchema, allOps, spec);
|
|
1191
|
+
expect.unreachable();
|
|
1192
|
+
} catch (e) {
|
|
1193
|
+
const err = e;
|
|
1194
|
+
expect(err.kind).toBe("aggregate");
|
|
1195
|
+
expect(err.failures.some((f) => String(f.cause).includes("collides"))).toBe(true);
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1198
|
+
test("rejects unknown having field", () => {
|
|
1199
|
+
const allOps = { aggregateOps: ["count"] };
|
|
1200
|
+
const spec = {
|
|
1201
|
+
aggregates: [{ fn: "count", alias: "total" }],
|
|
1202
|
+
groupBy: ["name"],
|
|
1203
|
+
having: FilterGroup.create().gt("nonexistent", 0)
|
|
1204
|
+
};
|
|
1205
|
+
try {
|
|
1206
|
+
assertNormalisedAggregate(UserSchema, allOps, spec);
|
|
1207
|
+
expect.unreachable();
|
|
1208
|
+
} catch (e) {
|
|
1209
|
+
const err = e;
|
|
1210
|
+
expect(err.kind).toBe("aggregate");
|
|
1211
|
+
expect(err.failures[0].cause).toContain("having");
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
test("having accepts alias and groupBy field names", () => {
|
|
1215
|
+
const allOps = { aggregateOps: ["count"] };
|
|
1216
|
+
const spec = {
|
|
1217
|
+
aggregates: [{ fn: "count", alias: "total" }],
|
|
1218
|
+
groupBy: ["name"],
|
|
1219
|
+
having: FilterGroup.create().gt("total", 0).eq("name", "Alice")
|
|
1220
|
+
};
|
|
1221
|
+
expect(() => assertNormalisedAggregate(UserSchema, allOps, spec)).not.toThrow();
|
|
1222
|
+
});
|
|
1223
|
+
test("passes valid groupBy with known schema fields", () => {
|
|
1224
|
+
const allOps = { aggregateOps: ["count"] };
|
|
1225
|
+
const spec = {
|
|
1226
|
+
aggregates: [{ fn: "count", alias: "total" }],
|
|
1227
|
+
groupBy: ["name", "age"]
|
|
1228
|
+
};
|
|
1229
|
+
expect(() => assertNormalisedAggregate(UserSchema, allOps, spec)).not.toThrow();
|
|
1230
|
+
});
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// src/orm/updates.ts
|
|
1235
|
+
var SetOp = class {
|
|
1236
|
+
constructor(values) {
|
|
1237
|
+
this.values = values;
|
|
1238
|
+
}
|
|
1239
|
+
kind = "set";
|
|
1240
|
+
};
|
|
1241
|
+
var IncOp = class {
|
|
1242
|
+
constructor(field, value) {
|
|
1243
|
+
this.field = field;
|
|
1244
|
+
this.value = value;
|
|
1245
|
+
}
|
|
1246
|
+
kind = "inc";
|
|
1247
|
+
};
|
|
1248
|
+
var MulOp = class {
|
|
1249
|
+
constructor(field, value) {
|
|
1250
|
+
this.field = field;
|
|
1251
|
+
this.value = value;
|
|
1252
|
+
}
|
|
1253
|
+
kind = "mul";
|
|
1254
|
+
};
|
|
1255
|
+
var MinOp = class {
|
|
1256
|
+
constructor(field, value) {
|
|
1257
|
+
this.field = field;
|
|
1258
|
+
this.value = value;
|
|
1259
|
+
}
|
|
1260
|
+
kind = "min";
|
|
1261
|
+
};
|
|
1262
|
+
var MaxOp = class {
|
|
1263
|
+
constructor(field, value) {
|
|
1264
|
+
this.field = field;
|
|
1265
|
+
this.value = value;
|
|
1266
|
+
}
|
|
1267
|
+
kind = "max";
|
|
1268
|
+
};
|
|
1269
|
+
var UnsetOp = class {
|
|
1270
|
+
constructor(field) {
|
|
1271
|
+
this.field = field;
|
|
1272
|
+
}
|
|
1273
|
+
kind = "unset";
|
|
1274
|
+
};
|
|
1275
|
+
var PushOp = class {
|
|
1276
|
+
constructor(field, value) {
|
|
1277
|
+
this.field = field;
|
|
1278
|
+
this.value = value;
|
|
1279
|
+
}
|
|
1280
|
+
kind = "push";
|
|
1281
|
+
};
|
|
1282
|
+
var PullOp = class {
|
|
1283
|
+
constructor(field, value) {
|
|
1284
|
+
this.field = field;
|
|
1285
|
+
this.value = value;
|
|
1286
|
+
}
|
|
1287
|
+
kind = "pull";
|
|
1288
|
+
};
|
|
1289
|
+
var PatchOp = class {
|
|
1290
|
+
constructor(field, value) {
|
|
1291
|
+
this.field = field;
|
|
1292
|
+
this.value = value;
|
|
1293
|
+
}
|
|
1294
|
+
kind = "patch";
|
|
1295
|
+
};
|
|
1296
|
+
function flattenOps(ops) {
|
|
1297
|
+
const data = {};
|
|
1298
|
+
for (const op of ops) {
|
|
1299
|
+
if (op instanceof SetOp) Object.assign(data, op.values);
|
|
1300
|
+
else data[op.field] = op;
|
|
1301
|
+
}
|
|
1302
|
+
return data;
|
|
1303
|
+
}
|
|
1304
|
+
if (void 0) {
|
|
1305
|
+
const { describe, test, expect, expectTypeOf } = void 0;
|
|
1306
|
+
const { v: v6 } = null;
|
|
1307
|
+
const { Schema } = null;
|
|
1308
|
+
const TestSchema = Schema.from("test").pk("id", v6.string(), () => "x").field("name", v6.string()).field("age", v6.number()).field("score", v6.optional(v6.number())).field("tags", v6.array(v6.string())).field("meta", v6.object({ a: v6.number() })).field("createdAt", v6.number(), { onCreate: () => 1e3 }).field("updatedAt", v6.number(), { onCreate: () => 1e3, onUpdate: () => 2e3 }).build();
|
|
1309
|
+
describe("op classes", () => {
|
|
1310
|
+
test('SetOp has kind "set" and stores values', () => {
|
|
1311
|
+
const op = new SetOp({ name: "Alice" });
|
|
1312
|
+
expect(op.kind).toBe("set");
|
|
1313
|
+
expect(op.values).toEqual({ name: "Alice" });
|
|
1314
|
+
});
|
|
1315
|
+
test('IncOp has kind "inc" and stores field + value', () => {
|
|
1316
|
+
const op = new IncOp("age", 5);
|
|
1317
|
+
expect(op.kind).toBe("inc");
|
|
1318
|
+
expect(op.field).toBe("age");
|
|
1319
|
+
expect(op.value).toBe(5);
|
|
1320
|
+
});
|
|
1321
|
+
test('MulOp has kind "mul" and stores field + value', () => {
|
|
1322
|
+
const op = new MulOp("score", 3);
|
|
1323
|
+
expect(op.kind).toBe("mul");
|
|
1324
|
+
expect(op.field).toBe("score");
|
|
1325
|
+
expect(op.value).toBe(3);
|
|
1326
|
+
});
|
|
1327
|
+
test('MinOp has kind "min"', () => {
|
|
1328
|
+
expect(new MinOp("age", 10).kind).toBe("min");
|
|
1329
|
+
});
|
|
1330
|
+
test('MaxOp has kind "max"', () => {
|
|
1331
|
+
expect(new MaxOp("age", 99).kind).toBe("max");
|
|
1332
|
+
});
|
|
1333
|
+
test('UnsetOp has kind "unset"', () => {
|
|
1334
|
+
expect(new UnsetOp("score").kind).toBe("unset");
|
|
1335
|
+
});
|
|
1336
|
+
test('PushOp has kind "push"', () => {
|
|
1337
|
+
expect(new PushOp("tags", "x").kind).toBe("push");
|
|
1338
|
+
});
|
|
1339
|
+
test('PullOp has kind "pull"', () => {
|
|
1340
|
+
expect(new PullOp("tags", "x").kind).toBe("pull");
|
|
1341
|
+
});
|
|
1342
|
+
test('PatchOp has kind "patch"', () => {
|
|
1343
|
+
expect(new PatchOp("meta", { a: 9 }).kind).toBe("patch");
|
|
1344
|
+
});
|
|
1345
|
+
});
|
|
1346
|
+
describe("isUpdateOp", () => {
|
|
1347
|
+
test("returns true for every op class", () => {
|
|
1348
|
+
expect(isUpdateOp(new SetOp({}))).toBe(true);
|
|
1349
|
+
expect(isUpdateOp(new IncOp("f", 1))).toBe(true);
|
|
1350
|
+
expect(isUpdateOp(new MulOp("f", 2))).toBe(true);
|
|
1351
|
+
expect(isUpdateOp(new MinOp("f", 0))).toBe(true);
|
|
1352
|
+
expect(isUpdateOp(new MaxOp("f", 10))).toBe(true);
|
|
1353
|
+
expect(isUpdateOp(new UnsetOp("f"))).toBe(true);
|
|
1354
|
+
expect(isUpdateOp(new PushOp("f", "v"))).toBe(true);
|
|
1355
|
+
expect(isUpdateOp(new PullOp("f", "v"))).toBe(true);
|
|
1356
|
+
expect(isUpdateOp(new PatchOp("f", {}))).toBe(true);
|
|
1357
|
+
});
|
|
1358
|
+
test("returns false for non-op values", () => {
|
|
1359
|
+
expect(isUpdateOp("string")).toBe(false);
|
|
1360
|
+
expect(isUpdateOp(42)).toBe(false);
|
|
1361
|
+
expect(isUpdateOp(null)).toBe(false);
|
|
1362
|
+
expect(isUpdateOp({})).toBe(false);
|
|
1363
|
+
});
|
|
1364
|
+
});
|
|
1365
|
+
describe("op helper functions", () => {
|
|
1366
|
+
test("set() creates SetOp with values", () => {
|
|
1367
|
+
const op = set({ name: "Alice", age: 30 });
|
|
1368
|
+
expect(op).toBeInstanceOf(SetOp);
|
|
1369
|
+
expect(op.kind).toBe("set");
|
|
1370
|
+
expect(op.values).toEqual({ name: "Alice", age: 30 });
|
|
1371
|
+
});
|
|
1372
|
+
test("inc() creates IncOp from field ref", () => {
|
|
1373
|
+
const op = inc(TestSchema.fields.age, 5);
|
|
1374
|
+
expect(op).toBeInstanceOf(IncOp);
|
|
1375
|
+
expect(op.field).toBe("age");
|
|
1376
|
+
expect(op.value).toBe(5);
|
|
1377
|
+
});
|
|
1378
|
+
test("mul() creates MulOp from field ref", () => {
|
|
1379
|
+
const op = mul(TestSchema.fields.age, 3);
|
|
1380
|
+
expect(op).toBeInstanceOf(MulOp);
|
|
1381
|
+
expect(op.field).toBe("age");
|
|
1382
|
+
expect(op.value).toBe(3);
|
|
1383
|
+
});
|
|
1384
|
+
test("min() creates MinOp from field ref", () => {
|
|
1385
|
+
const op = min(TestSchema.fields.age, 10);
|
|
1386
|
+
expect(op).toBeInstanceOf(MinOp);
|
|
1387
|
+
expect(op.field).toBe("age");
|
|
1388
|
+
});
|
|
1389
|
+
test("max() creates MaxOp from field ref", () => {
|
|
1390
|
+
const op = max(TestSchema.fields.age, 99);
|
|
1391
|
+
expect(op).toBeInstanceOf(MaxOp);
|
|
1392
|
+
expect(op.field).toBe("age");
|
|
1393
|
+
});
|
|
1394
|
+
test("unset() creates UnsetOp from field ref", () => {
|
|
1395
|
+
const op = unset(TestSchema.fields.score);
|
|
1396
|
+
expect(op).toBeInstanceOf(UnsetOp);
|
|
1397
|
+
expect(op.field).toBe("score");
|
|
1398
|
+
});
|
|
1399
|
+
test("push() creates PushOp from field ref", () => {
|
|
1400
|
+
const op = push(TestSchema.fields.tags, "new-tag");
|
|
1401
|
+
expect(op).toBeInstanceOf(PushOp);
|
|
1402
|
+
expect(op.field).toBe("tags");
|
|
1403
|
+
expect(op.value).toBe("new-tag");
|
|
1404
|
+
});
|
|
1405
|
+
test("pull() creates PullOp from field ref", () => {
|
|
1406
|
+
const op = pull(TestSchema.fields.tags, "old-tag");
|
|
1407
|
+
expect(op).toBeInstanceOf(PullOp);
|
|
1408
|
+
expect(op.field).toBe("tags");
|
|
1409
|
+
});
|
|
1410
|
+
test("patch() creates PatchOp from field ref", () => {
|
|
1411
|
+
const op = patch(TestSchema.fields.meta, { a: 9 });
|
|
1412
|
+
expect(op).toBeInstanceOf(PatchOp);
|
|
1413
|
+
expect(op.field).toBe("meta");
|
|
1414
|
+
expect(op.value).toEqual({ a: 9 });
|
|
1415
|
+
});
|
|
1416
|
+
});
|
|
1417
|
+
describe("opTouchedFields", () => {
|
|
1418
|
+
test("SetOp returns all keys from values", () => {
|
|
1419
|
+
expect(opTouchedFields(new SetOp({ name: "A", age: 1 }))).toEqual(["name", "age"]);
|
|
1420
|
+
});
|
|
1421
|
+
test("IncOp returns the field", () => {
|
|
1422
|
+
expect(opTouchedFields(new IncOp("age", 1))).toEqual(["age"]);
|
|
1423
|
+
});
|
|
1424
|
+
test("UnsetOp returns the field", () => {
|
|
1425
|
+
expect(opTouchedFields(new UnsetOp("score"))).toEqual(["score"]);
|
|
1426
|
+
});
|
|
1427
|
+
});
|
|
1428
|
+
describe("type-level: field-category helpers", () => {
|
|
1429
|
+
test("NumericFieldOf only matches numeric fields (excludes optional numeric)", () => {
|
|
1430
|
+
expectTypeOf().toEqualTypeOf();
|
|
1431
|
+
});
|
|
1432
|
+
test("ComparableFieldOf matches numeric and string fields (excludes optional)", () => {
|
|
1433
|
+
expectTypeOf().toEqualTypeOf();
|
|
1434
|
+
});
|
|
1435
|
+
test("OptionalFieldOf only matches optional fields", () => {
|
|
1436
|
+
expectTypeOf().toEqualTypeOf();
|
|
1437
|
+
});
|
|
1438
|
+
test("ArrayFieldOf only matches array fields", () => {
|
|
1439
|
+
expectTypeOf().toEqualTypeOf();
|
|
1440
|
+
});
|
|
1441
|
+
test("ObjectFieldOf only matches object fields (not arrays)", () => {
|
|
1442
|
+
expectTypeOf().toEqualTypeOf();
|
|
1443
|
+
});
|
|
1444
|
+
});
|
|
1445
|
+
describe("type-level: inc on a string field is a TS error", () => {
|
|
1446
|
+
test("inc rejects non-numeric fields", () => {
|
|
1447
|
+
inc(TestSchema.fields.name, 1);
|
|
1448
|
+
});
|
|
1449
|
+
});
|
|
1450
|
+
describe("type-level: push on a non-array field is a TS error", () => {
|
|
1451
|
+
test("push rejects non-array fields", () => {
|
|
1452
|
+
push(TestSchema.fields.name, "val");
|
|
1453
|
+
});
|
|
1454
|
+
});
|
|
1455
|
+
describe("type-level: per-op gating via UpdateOp<S, A>", () => {
|
|
1456
|
+
test("undeclared ops resolve to never", async () => {
|
|
1457
|
+
const { OrmAdapter: OrmAdapter2 } = await null;
|
|
1458
|
+
class LimitedAdapter extends OrmAdapter2 {
|
|
1459
|
+
schemaConfigPipe = v6.object({});
|
|
1460
|
+
supportedFieldTypes = ["string", "number"];
|
|
1461
|
+
updateOps = ["set", "inc"];
|
|
1462
|
+
}
|
|
1463
|
+
expectTypeOf().toEqualTypeOf();
|
|
1464
|
+
});
|
|
1465
|
+
test("adapter with no updateOps resolves all variants to never", async () => {
|
|
1466
|
+
const { OrmAdapter: OrmAdapter2 } = await null;
|
|
1467
|
+
class NoOpsAdapter extends OrmAdapter2 {
|
|
1468
|
+
schemaConfigPipe = v6.object({});
|
|
1469
|
+
supportedFieldTypes = ["string"];
|
|
1470
|
+
updateOps = [];
|
|
1471
|
+
}
|
|
1472
|
+
expectTypeOf().toBeNever();
|
|
1473
|
+
});
|
|
1474
|
+
test("adapter with all updateOps includes all variants", async () => {
|
|
1475
|
+
const { OrmAdapter: OrmAdapter2 } = await null;
|
|
1476
|
+
class FullAdapter extends OrmAdapter2 {
|
|
1477
|
+
schemaConfigPipe = v6.object({});
|
|
1478
|
+
supportedFieldTypes = ["string", "number"];
|
|
1479
|
+
updateOps = ["set", "inc", "mul", "min", "max", "unset", "push", "pull", "patch"];
|
|
1480
|
+
}
|
|
1481
|
+
expectTypeOf().toEqualTypeOf();
|
|
1482
|
+
});
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
// src/orm/adapters/mongodb/query.ts
|
|
1487
|
+
function compileMongoFilter(group, primaryKey) {
|
|
1488
|
+
const clauses = [];
|
|
1489
|
+
for (const child of group.children) {
|
|
1490
|
+
const compiled = compileChild(child, primaryKey);
|
|
1491
|
+
if (compiled) clauses.push(compiled);
|
|
1492
|
+
}
|
|
1493
|
+
if (clauses.length === 0) return {};
|
|
1494
|
+
if (clauses.length === 1) return clauses[0];
|
|
1495
|
+
return { $and: clauses };
|
|
1496
|
+
}
|
|
1497
|
+
function compileMongoQuery(group, options, primaryKey) {
|
|
1498
|
+
const mongoFilter = compileMongoFilter(group, primaryKey);
|
|
1499
|
+
const orderBys = options?.orderBy ?? [];
|
|
1500
|
+
const sort = orderBys.length > 0 ? Object.fromEntries(orderBys.map((o) => [mapField(o.field, primaryKey), o.direction === "desc" ? -1 : 1])) : void 0;
|
|
1501
|
+
const selects = options?.select ?? [];
|
|
1502
|
+
const projection = selects.length > 0 ? Object.fromEntries(selects.map((f) => [mapField(f, primaryKey), 1])) : void 0;
|
|
1503
|
+
return {
|
|
1504
|
+
filter: mongoFilter,
|
|
1505
|
+
sort,
|
|
1506
|
+
limit: options?.limit,
|
|
1507
|
+
skip: options?.offset,
|
|
1508
|
+
projection
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
function mapField(field, primaryKey) {
|
|
1512
|
+
if (field === "id" && primaryKey === "_id") return "_id";
|
|
1513
|
+
return field;
|
|
1514
|
+
}
|
|
1515
|
+
function compileFilter(f, primaryKey) {
|
|
1516
|
+
const field = mapField(f.field, primaryKey);
|
|
1517
|
+
switch (f.op) {
|
|
1518
|
+
case "eq":
|
|
1519
|
+
return { [field]: { $eq: f.value } };
|
|
1520
|
+
case "ne":
|
|
1521
|
+
return { [field]: { $ne: f.value } };
|
|
1522
|
+
case "gt":
|
|
1523
|
+
return { [field]: { $gt: f.value } };
|
|
1524
|
+
case "gte":
|
|
1525
|
+
return { [field]: { $gte: f.value } };
|
|
1526
|
+
case "lt":
|
|
1527
|
+
return { [field]: { $lt: f.value } };
|
|
1528
|
+
case "lte":
|
|
1529
|
+
return { [field]: { $lte: f.value } };
|
|
1530
|
+
case "in":
|
|
1531
|
+
return { [field]: { $in: f.value } };
|
|
1532
|
+
case "notIn":
|
|
1533
|
+
return { [field]: { $nin: f.value } };
|
|
1534
|
+
case "like":
|
|
1535
|
+
return { [field]: { $regex: new RegExp(String(f.value), "i") } };
|
|
1536
|
+
case "exists":
|
|
1537
|
+
return { [field]: { $exists: true, $ne: null } };
|
|
1538
|
+
case "notExists":
|
|
1539
|
+
return { [field]: { $eq: null } };
|
|
1540
|
+
case "contains":
|
|
1541
|
+
return { [field]: { $all: Array.isArray(f.value) ? f.value : [f.value] } };
|
|
1542
|
+
case "notContains":
|
|
1543
|
+
return { [field]: { $not: { $all: Array.isArray(f.value) ? f.value : [f.value] } } };
|
|
1544
|
+
default:
|
|
1545
|
+
return { [field]: { $eq: f.value } };
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
function compileChild(child, primaryKey) {
|
|
1549
|
+
if (child instanceof Filter) return compileFilter(child, primaryKey);
|
|
1550
|
+
if (child instanceof FilterGroup) return compileGroup(child, primaryKey);
|
|
1551
|
+
return null;
|
|
1552
|
+
}
|
|
1553
|
+
function compileGroup(group, primaryKey) {
|
|
1554
|
+
const clauses = group.children.map((c) => compileChild(c, primaryKey)).filter((c) => c !== null);
|
|
1555
|
+
if (clauses.length === 0) return null;
|
|
1556
|
+
if (clauses.length === 1) return clauses[0];
|
|
1557
|
+
return { [group.op === "or" ? "$or" : "$and"]: clauses };
|
|
1558
|
+
}
|
|
1559
|
+
function compileMongoUpdate(data) {
|
|
1560
|
+
const $set = {};
|
|
1561
|
+
const $inc = {};
|
|
1562
|
+
const $mul = {};
|
|
1563
|
+
const $min = {};
|
|
1564
|
+
const $max = {};
|
|
1565
|
+
const $unset = {};
|
|
1566
|
+
const $push = {};
|
|
1567
|
+
const $pull = {};
|
|
1568
|
+
for (const [key, value] of Object.entries(data)) {
|
|
1569
|
+
if (value instanceof IncOp) $inc[key] = value.value;
|
|
1570
|
+
else if (value instanceof MulOp) $mul[key] = value.value;
|
|
1571
|
+
else if (value instanceof MinOp) $min[key] = value.value;
|
|
1572
|
+
else if (value instanceof MaxOp) $max[key] = value.value;
|
|
1573
|
+
else if (value instanceof UnsetOp) $unset[key] = "";
|
|
1574
|
+
else if (value instanceof PushOp) $push[key] = value.value;
|
|
1575
|
+
else if (value instanceof PullOp) $pull[key] = value.value;
|
|
1576
|
+
else if (value instanceof PatchOp) {
|
|
1577
|
+
const patchVal = value.value;
|
|
1578
|
+
for (const [subKey, subVal] of Object.entries(patchVal)) {
|
|
1579
|
+
$set[`${key}.${subKey}`] = subVal;
|
|
1580
|
+
}
|
|
1581
|
+
} else $set[key] = value;
|
|
1582
|
+
}
|
|
1583
|
+
const result = {};
|
|
1584
|
+
if (Object.keys($set).length) result.$set = $set;
|
|
1585
|
+
if (Object.keys($inc).length) result.$inc = $inc;
|
|
1586
|
+
if (Object.keys($mul).length) result.$mul = $mul;
|
|
1587
|
+
if (Object.keys($min).length) result.$min = $min;
|
|
1588
|
+
if (Object.keys($max).length) result.$max = $max;
|
|
1589
|
+
if (Object.keys($unset).length) result.$unset = $unset;
|
|
1590
|
+
if (Object.keys($push).length) result.$push = $push;
|
|
1591
|
+
if (Object.keys($pull).length) result.$pull = $pull;
|
|
1592
|
+
return result;
|
|
1593
|
+
}
|
|
1594
|
+
function compileMongoOps(ops) {
|
|
1595
|
+
return compileMongoUpdate(flattenOps(ops));
|
|
1596
|
+
}
|
|
1597
|
+
function compileMongoAggregate(spec, primaryKey) {
|
|
1598
|
+
const pipeline = [];
|
|
1599
|
+
if (spec.where) {
|
|
1600
|
+
const match = compileMongoFilter(spec.where, primaryKey);
|
|
1601
|
+
if (Object.keys(match).length > 0) pipeline.push({ $match: match });
|
|
1602
|
+
}
|
|
1603
|
+
const groupId = spec.groupBy.length > 0 ? Object.fromEntries(spec.groupBy.map((f) => [f, `$${mapField(f, primaryKey)}`])) : null;
|
|
1604
|
+
const accumulators = {};
|
|
1605
|
+
for (const agg of spec.aggregates) {
|
|
1606
|
+
const fieldRef = agg.field ? `$${mapField(agg.field, primaryKey)}` : void 0;
|
|
1607
|
+
switch (agg.fn) {
|
|
1608
|
+
case "count":
|
|
1609
|
+
accumulators[agg.alias] = { $sum: 1 };
|
|
1610
|
+
break;
|
|
1611
|
+
case "countDistinct":
|
|
1612
|
+
accumulators[agg.alias] = { $addToSet: fieldRef };
|
|
1613
|
+
break;
|
|
1614
|
+
case "sum":
|
|
1615
|
+
case "avg":
|
|
1616
|
+
case "min":
|
|
1617
|
+
case "max":
|
|
1618
|
+
accumulators[agg.alias] = { [`$${agg.fn}`]: fieldRef };
|
|
1619
|
+
break;
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
pipeline.push({ $group: { _id: groupId, ...accumulators } });
|
|
1623
|
+
const project = { _id: 0 };
|
|
1624
|
+
for (const f of spec.groupBy) {
|
|
1625
|
+
project[f] = `$_id.${f}`;
|
|
1626
|
+
}
|
|
1627
|
+
for (const agg of spec.aggregates) {
|
|
1628
|
+
if (agg.fn === "countDistinct") {
|
|
1629
|
+
project[agg.alias] = { $size: `$${agg.alias}` };
|
|
1630
|
+
} else {
|
|
1631
|
+
project[agg.alias] = 1;
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
pipeline.push({ $project: project });
|
|
1635
|
+
if (spec.having) {
|
|
1636
|
+
const havingMatch = compileMongoFilter(spec.having, "");
|
|
1637
|
+
if (Object.keys(havingMatch).length > 0) pipeline.push({ $match: havingMatch });
|
|
1638
|
+
}
|
|
1639
|
+
return pipeline;
|
|
1640
|
+
}
|
|
1641
|
+
if (void 0) {
|
|
1642
|
+
const { describe, test, expect } = void 0;
|
|
1643
|
+
const { FilterGroup: FilterGroup2 } = null;
|
|
1644
|
+
const { OrderBy } = null;
|
|
1645
|
+
describe("compileMongoFilter", () => {
|
|
1646
|
+
test("empty filter group compiles to empty object", () => {
|
|
1647
|
+
const group = FilterGroup2.create();
|
|
1648
|
+
expect(compileMongoFilter(group, "id")).toEqual({});
|
|
1649
|
+
});
|
|
1650
|
+
test("single eq filter compiles to $eq", () => {
|
|
1651
|
+
const group = FilterGroup2.create().eq("name", "Alice");
|
|
1652
|
+
expect(compileMongoFilter(group, "id")).toEqual({ name: { $eq: "Alice" } });
|
|
1653
|
+
});
|
|
1654
|
+
test("ne, gt, gte, lt, lte compile to corresponding Mongo ops", () => {
|
|
1655
|
+
expect(compileMongoFilter(FilterGroup2.create().ne("age", 5), "id")).toEqual({ age: { $ne: 5 } });
|
|
1656
|
+
expect(compileMongoFilter(FilterGroup2.create().gt("age", 10), "id")).toEqual({ age: { $gt: 10 } });
|
|
1657
|
+
expect(compileMongoFilter(FilterGroup2.create().gte("age", 20), "id")).toEqual({ age: { $gte: 20 } });
|
|
1658
|
+
expect(compileMongoFilter(FilterGroup2.create().lt("age", 30), "id")).toEqual({ age: { $lt: 30 } });
|
|
1659
|
+
expect(compileMongoFilter(FilterGroup2.create().lte("age", 40), "id")).toEqual({ age: { $lte: 40 } });
|
|
1660
|
+
});
|
|
1661
|
+
test("in compiles to $in", () => {
|
|
1662
|
+
const group = FilterGroup2.create().in("status", ["a", "b"]);
|
|
1663
|
+
expect(compileMongoFilter(group, "id")).toEqual({ status: { $in: ["a", "b"] } });
|
|
1664
|
+
});
|
|
1665
|
+
test("notIn compiles to $nin (canonical name notIn, Mongo op $nin)", () => {
|
|
1666
|
+
const group = FilterGroup2.create().notIn("status", ["x", "y"]);
|
|
1667
|
+
expect(compileMongoFilter(group, "id")).toEqual({ status: { $nin: ["x", "y"] } });
|
|
1668
|
+
});
|
|
1669
|
+
test("like compiles to $regex with case-insensitive flag", () => {
|
|
1670
|
+
const group = FilterGroup2.create().like("name", "ali");
|
|
1671
|
+
const result = compileMongoFilter(group, "id");
|
|
1672
|
+
expect(result.name).toEqual({ $regex: expect.any(RegExp) });
|
|
1673
|
+
const regex = result.name.$regex;
|
|
1674
|
+
expect(regex.flags).toBe("i");
|
|
1675
|
+
expect(regex.test("Alice")).toBe(true);
|
|
1676
|
+
});
|
|
1677
|
+
test("exists compiles to $exists: true, $ne: null", () => {
|
|
1678
|
+
const group = FilterGroup2.create().exists("val");
|
|
1679
|
+
expect(compileMongoFilter(group, "id")).toEqual({ val: { $exists: true, $ne: null } });
|
|
1680
|
+
});
|
|
1681
|
+
test("notExists is its own op \u2014 compiles to $eq: null", () => {
|
|
1682
|
+
const group = FilterGroup2.create().notExists("val");
|
|
1683
|
+
expect(compileMongoFilter(group, "id")).toEqual({ val: { $eq: null } });
|
|
1684
|
+
});
|
|
1685
|
+
test("contains compiles to $all", () => {
|
|
1686
|
+
const group = FilterGroup2.create().contains("tags", ["a", "b"]);
|
|
1687
|
+
expect(compileMongoFilter(group, "id")).toEqual({ tags: { $all: ["a", "b"] } });
|
|
1688
|
+
});
|
|
1689
|
+
test("notContains compiles to $not: { $all }", () => {
|
|
1690
|
+
const group = FilterGroup2.create().notContains("tags", ["x"]);
|
|
1691
|
+
expect(compileMongoFilter(group, "id")).toEqual({ tags: { $not: { $all: ["x"] } } });
|
|
1692
|
+
});
|
|
1693
|
+
test("multiple clauses produce $and", () => {
|
|
1694
|
+
const group = FilterGroup2.create().eq("a", 1).gt("b", 2);
|
|
1695
|
+
const result = compileMongoFilter(group, "id");
|
|
1696
|
+
expect(result).toEqual({ $and: [{ a: { $eq: 1 } }, { b: { $gt: 2 } }] });
|
|
1697
|
+
});
|
|
1698
|
+
test("nested and/or groups compile correctly", () => {
|
|
1699
|
+
const group = FilterGroup2.create().and([
|
|
1700
|
+
(q) => q.eq("a", 1),
|
|
1701
|
+
(q) => q.or([(g) => g.eq("b", 2), (g) => g.eq("c", 3)])
|
|
1702
|
+
]);
|
|
1703
|
+
const result = compileMongoFilter(group, "id");
|
|
1704
|
+
expect(result).toEqual({
|
|
1705
|
+
$and: [{ a: { $eq: 1 } }, { $or: [{ b: { $eq: 2 } }, { c: { $eq: 3 } }] }]
|
|
1706
|
+
});
|
|
1707
|
+
});
|
|
1708
|
+
test("maps id field to _id when primaryKey is _id", () => {
|
|
1709
|
+
const group = FilterGroup2.create().eq("id", "abc");
|
|
1710
|
+
expect(compileMongoFilter(group, "_id")).toEqual({ _id: { $eq: "abc" } });
|
|
1711
|
+
});
|
|
1712
|
+
});
|
|
1713
|
+
describe("compileMongoQuery", () => {
|
|
1714
|
+
test("options: sort, limit, skip, projection", () => {
|
|
1715
|
+
const group = FilterGroup2.create().eq("active", true);
|
|
1716
|
+
const options = {
|
|
1717
|
+
orderBy: [new OrderBy("name", "asc"), new OrderBy("age", "desc")],
|
|
1718
|
+
limit: 10,
|
|
1719
|
+
offset: 5,
|
|
1720
|
+
select: ["name", "age"]
|
|
1721
|
+
};
|
|
1722
|
+
const result = compileMongoQuery(group, options, "id");
|
|
1723
|
+
expect(result.filter).toEqual({ active: { $eq: true } });
|
|
1724
|
+
expect(result.sort).toEqual({ name: 1, age: -1 });
|
|
1725
|
+
expect(result.limit).toBe(10);
|
|
1726
|
+
expect(result.skip).toBe(5);
|
|
1727
|
+
expect(result.projection).toEqual({ name: 1, age: 1 });
|
|
1728
|
+
});
|
|
1729
|
+
test("undefined options returns undefined for sort/limit/skip/projection", () => {
|
|
1730
|
+
const group = FilterGroup2.create().eq("x", 1);
|
|
1731
|
+
const result = compileMongoQuery(group, void 0, "id");
|
|
1732
|
+
expect(result.sort).toBeUndefined();
|
|
1733
|
+
expect(result.limit).toBeUndefined();
|
|
1734
|
+
expect(result.skip).toBeUndefined();
|
|
1735
|
+
expect(result.projection).toBeUndefined();
|
|
1736
|
+
});
|
|
1737
|
+
});
|
|
1738
|
+
describe("compileMongoUpdate", () => {
|
|
1739
|
+
test("plain values go into $set", () => {
|
|
1740
|
+
const result = compileMongoUpdate({ name: "Alice", age: 30 });
|
|
1741
|
+
expect(result).toEqual({ $set: { name: "Alice", age: 30 } });
|
|
1742
|
+
});
|
|
1743
|
+
test("IncOp goes into $inc", () => {
|
|
1744
|
+
const result = compileMongoUpdate({ count: new IncOp("count", 5) });
|
|
1745
|
+
expect(result).toEqual({ $inc: { count: 5 } });
|
|
1746
|
+
});
|
|
1747
|
+
test("MulOp goes into $mul", () => {
|
|
1748
|
+
const result = compileMongoUpdate({ score: new MulOp("score", 2) });
|
|
1749
|
+
expect(result).toEqual({ $mul: { score: 2 } });
|
|
1750
|
+
});
|
|
1751
|
+
test("MinOp/MaxOp go into $min/$max", () => {
|
|
1752
|
+
const result = compileMongoUpdate({
|
|
1753
|
+
lo: new MinOp("lo", 1),
|
|
1754
|
+
hi: new MaxOp("hi", 99)
|
|
1755
|
+
});
|
|
1756
|
+
expect(result).toEqual({ $min: { lo: 1 }, $max: { hi: 99 } });
|
|
1757
|
+
});
|
|
1758
|
+
test("UnsetOp goes into $unset", () => {
|
|
1759
|
+
const result = compileMongoUpdate({ old: new UnsetOp("old") });
|
|
1760
|
+
expect(result).toEqual({ $unset: { old: "" } });
|
|
1761
|
+
});
|
|
1762
|
+
test("PushOp/PullOp go into $push/$pull", () => {
|
|
1763
|
+
const result = compileMongoUpdate({
|
|
1764
|
+
tags: new PushOp("tags", "new"),
|
|
1765
|
+
removed: new PullOp("removed", "old")
|
|
1766
|
+
});
|
|
1767
|
+
expect(result).toEqual({ $push: { tags: "new" }, $pull: { removed: "old" } });
|
|
1768
|
+
});
|
|
1769
|
+
test("PatchOp produces dot-notation $set entries", () => {
|
|
1770
|
+
const result = compileMongoUpdate({ meta: new PatchOp("meta", { a: 1, b: 2 }) });
|
|
1771
|
+
expect(result).toEqual({ $set: { "meta.a": 1, "meta.b": 2 } });
|
|
1772
|
+
});
|
|
1773
|
+
test("empty data produces empty result", () => {
|
|
1774
|
+
const result = compileMongoUpdate({});
|
|
1775
|
+
expect(result).toEqual({});
|
|
1776
|
+
});
|
|
1777
|
+
});
|
|
1778
|
+
describe("compileMongoOps", () => {
|
|
1779
|
+
test("SetOp values go into $set", () => {
|
|
1780
|
+
const result = compileMongoOps([new SetOp2({ name: "Bob", age: 25 })]);
|
|
1781
|
+
expect(result).toEqual({ $set: { name: "Bob", age: 25 } });
|
|
1782
|
+
});
|
|
1783
|
+
test("mixed ops compile to separate Mongo update operators", () => {
|
|
1784
|
+
const result = compileMongoOps([
|
|
1785
|
+
new SetOp2({ name: "Alice" }),
|
|
1786
|
+
new IncOp("count", 1),
|
|
1787
|
+
new PushOp("tags", "x")
|
|
1788
|
+
]);
|
|
1789
|
+
expect(result).toEqual({
|
|
1790
|
+
$set: { name: "Alice" },
|
|
1791
|
+
$inc: { count: 1 },
|
|
1792
|
+
$push: { tags: "x" }
|
|
1793
|
+
});
|
|
1794
|
+
});
|
|
1795
|
+
test("empty ops produces empty result", () => {
|
|
1796
|
+
expect(compileMongoOps([])).toEqual({});
|
|
1797
|
+
});
|
|
1798
|
+
});
|
|
1799
|
+
describe("compileMongoAggregate", () => {
|
|
1800
|
+
test("bare count produces $group with $sum:1 and $project", () => {
|
|
1801
|
+
const spec = {
|
|
1802
|
+
aggregates: [{ fn: "count", alias: "total" }],
|
|
1803
|
+
groupBy: []
|
|
1804
|
+
};
|
|
1805
|
+
expect(compileMongoAggregate(spec, "_id")).toEqual([
|
|
1806
|
+
{ $group: { _id: null, total: { $sum: 1 } } },
|
|
1807
|
+
{ $project: { _id: 0, total: 1 } }
|
|
1808
|
+
]);
|
|
1809
|
+
});
|
|
1810
|
+
test("multi-aggregator produces all accumulators in $group", () => {
|
|
1811
|
+
const spec = {
|
|
1812
|
+
aggregates: [
|
|
1813
|
+
{ fn: "count", alias: "total" },
|
|
1814
|
+
{ fn: "sum", field: "amount", alias: "revenue" },
|
|
1815
|
+
{ fn: "avg", field: "price", alias: "avgPrice" }
|
|
1816
|
+
],
|
|
1817
|
+
groupBy: []
|
|
1818
|
+
};
|
|
1819
|
+
expect(compileMongoAggregate(spec, "_id")).toEqual([
|
|
1820
|
+
{
|
|
1821
|
+
$group: {
|
|
1822
|
+
_id: null,
|
|
1823
|
+
total: { $sum: 1 },
|
|
1824
|
+
revenue: { $sum: "$amount" },
|
|
1825
|
+
avgPrice: { $avg: "$price" }
|
|
1826
|
+
}
|
|
1827
|
+
},
|
|
1828
|
+
{ $project: { _id: 0, total: 1, revenue: 1, avgPrice: 1 } }
|
|
1829
|
+
]);
|
|
1830
|
+
});
|
|
1831
|
+
test("single-column groupBy produces composite _id and $project lift", () => {
|
|
1832
|
+
const spec = {
|
|
1833
|
+
aggregates: [{ fn: "count", alias: "total" }],
|
|
1834
|
+
groupBy: ["region"]
|
|
1835
|
+
};
|
|
1836
|
+
expect(compileMongoAggregate(spec, "_id")).toEqual([
|
|
1837
|
+
{ $group: { _id: { region: "$region" }, total: { $sum: 1 } } },
|
|
1838
|
+
{ $project: { _id: 0, region: "$_id.region", total: 1 } }
|
|
1839
|
+
]);
|
|
1840
|
+
});
|
|
1841
|
+
test("multi-column groupBy via composite _id", () => {
|
|
1842
|
+
const spec = {
|
|
1843
|
+
aggregates: [{ fn: "sum", field: "amount", alias: "revenue" }],
|
|
1844
|
+
groupBy: ["region", "year"]
|
|
1845
|
+
};
|
|
1846
|
+
expect(compileMongoAggregate(spec, "_id")).toEqual([
|
|
1847
|
+
{
|
|
1848
|
+
$group: {
|
|
1849
|
+
_id: { region: "$region", year: "$year" },
|
|
1850
|
+
revenue: { $sum: "$amount" }
|
|
1851
|
+
}
|
|
1852
|
+
},
|
|
1853
|
+
{
|
|
1854
|
+
$project: {
|
|
1855
|
+
_id: 0,
|
|
1856
|
+
region: "$_id.region",
|
|
1857
|
+
year: "$_id.year",
|
|
1858
|
+
revenue: 1
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
]);
|
|
1862
|
+
});
|
|
1863
|
+
test("where-only emits $match before $group", () => {
|
|
1864
|
+
const spec = {
|
|
1865
|
+
where: FilterGroup2.create().eq("active", true),
|
|
1866
|
+
aggregates: [{ fn: "count", alias: "total" }],
|
|
1867
|
+
groupBy: []
|
|
1868
|
+
};
|
|
1869
|
+
expect(compileMongoAggregate(spec, "_id")).toEqual([
|
|
1870
|
+
{ $match: { active: { $eq: true } } },
|
|
1871
|
+
{ $group: { _id: null, total: { $sum: 1 } } },
|
|
1872
|
+
{ $project: { _id: 0, total: 1 } }
|
|
1873
|
+
]);
|
|
1874
|
+
});
|
|
1875
|
+
test("having-only emits $match after $project", () => {
|
|
1876
|
+
const spec = {
|
|
1877
|
+
aggregates: [{ fn: "count", alias: "total" }],
|
|
1878
|
+
groupBy: ["region"],
|
|
1879
|
+
having: FilterGroup2.create().gt("total", 5)
|
|
1880
|
+
};
|
|
1881
|
+
expect(compileMongoAggregate(spec, "_id")).toEqual([
|
|
1882
|
+
{ $group: { _id: { region: "$region" }, total: { $sum: 1 } } },
|
|
1883
|
+
{ $project: { _id: 0, region: "$_id.region", total: 1 } },
|
|
1884
|
+
{ $match: { total: { $gt: 5 } } }
|
|
1885
|
+
]);
|
|
1886
|
+
});
|
|
1887
|
+
test("where + having produces both $match stages", () => {
|
|
1888
|
+
const spec = {
|
|
1889
|
+
where: FilterGroup2.create().eq("active", true),
|
|
1890
|
+
aggregates: [{ fn: "sum", field: "amount", alias: "revenue" }],
|
|
1891
|
+
groupBy: ["region"],
|
|
1892
|
+
having: FilterGroup2.create().gte("revenue", 1e3)
|
|
1893
|
+
};
|
|
1894
|
+
expect(compileMongoAggregate(spec, "_id")).toEqual([
|
|
1895
|
+
{ $match: { active: { $eq: true } } },
|
|
1896
|
+
{
|
|
1897
|
+
$group: {
|
|
1898
|
+
_id: { region: "$region" },
|
|
1899
|
+
revenue: { $sum: "$amount" }
|
|
1900
|
+
}
|
|
1901
|
+
},
|
|
1902
|
+
{ $project: { _id: 0, region: "$_id.region", revenue: 1 } },
|
|
1903
|
+
{ $match: { revenue: { $gte: 1e3 } } }
|
|
1904
|
+
]);
|
|
1905
|
+
});
|
|
1906
|
+
test("countDistinct uses $addToSet in $group and $size in $project", () => {
|
|
1907
|
+
const spec = {
|
|
1908
|
+
aggregates: [{ fn: "countDistinct", field: "userId", alias: "uniqueUsers" }],
|
|
1909
|
+
groupBy: []
|
|
1910
|
+
};
|
|
1911
|
+
expect(compileMongoAggregate(spec, "_id")).toEqual([
|
|
1912
|
+
{ $group: { _id: null, uniqueUsers: { $addToSet: "$userId" } } },
|
|
1913
|
+
{ $project: { _id: 0, uniqueUsers: { $size: "$uniqueUsers" } } }
|
|
1914
|
+
]);
|
|
1915
|
+
});
|
|
1916
|
+
test("min/max produce $min/$max accumulators", () => {
|
|
1917
|
+
const spec = {
|
|
1918
|
+
aggregates: [
|
|
1919
|
+
{ fn: "min", field: "price", alias: "lowest" },
|
|
1920
|
+
{ fn: "max", field: "price", alias: "highest" }
|
|
1921
|
+
],
|
|
1922
|
+
groupBy: []
|
|
1923
|
+
};
|
|
1924
|
+
expect(compileMongoAggregate(spec, "_id")).toEqual([
|
|
1925
|
+
{
|
|
1926
|
+
$group: {
|
|
1927
|
+
_id: null,
|
|
1928
|
+
lowest: { $min: "$price" },
|
|
1929
|
+
highest: { $max: "$price" }
|
|
1930
|
+
}
|
|
1931
|
+
},
|
|
1932
|
+
{ $project: { _id: 0, lowest: 1, highest: 1 } }
|
|
1933
|
+
]);
|
|
1934
|
+
});
|
|
1935
|
+
test("field-name mapping: id field maps to _id when primaryKey is _id", () => {
|
|
1936
|
+
const spec = {
|
|
1937
|
+
where: FilterGroup2.create().eq("id", "abc"),
|
|
1938
|
+
aggregates: [{ fn: "count", alias: "total" }],
|
|
1939
|
+
groupBy: ["id"]
|
|
1940
|
+
};
|
|
1941
|
+
expect(compileMongoAggregate(spec, "_id")).toEqual([
|
|
1942
|
+
{ $match: { _id: { $eq: "abc" } } },
|
|
1943
|
+
{ $group: { _id: { id: "$_id" }, total: { $sum: 1 } } },
|
|
1944
|
+
{ $project: { _id: 0, id: "$_id.id", total: 1 } }
|
|
1945
|
+
]);
|
|
1946
|
+
});
|
|
1947
|
+
test("countDistinct with groupBy combines $addToSet, groupBy lift, and $size", () => {
|
|
1948
|
+
const spec = {
|
|
1949
|
+
aggregates: [{ fn: "countDistinct", field: "category", alias: "uniqueCats" }],
|
|
1950
|
+
groupBy: ["region"]
|
|
1951
|
+
};
|
|
1952
|
+
expect(compileMongoAggregate(spec, "_id")).toEqual([
|
|
1953
|
+
{
|
|
1954
|
+
$group: {
|
|
1955
|
+
_id: { region: "$region" },
|
|
1956
|
+
uniqueCats: { $addToSet: "$category" }
|
|
1957
|
+
}
|
|
1958
|
+
},
|
|
1959
|
+
{
|
|
1960
|
+
$project: {
|
|
1961
|
+
_id: 0,
|
|
1962
|
+
region: "$_id.region",
|
|
1963
|
+
uniqueCats: { $size: "$uniqueCats" }
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
]);
|
|
1967
|
+
});
|
|
1968
|
+
test("all six aggregators in a single pipeline", () => {
|
|
1969
|
+
const spec = {
|
|
1970
|
+
aggregates: [
|
|
1971
|
+
{ fn: "count", alias: "total" },
|
|
1972
|
+
{ fn: "countDistinct", field: "status", alias: "uniqueStatuses" },
|
|
1973
|
+
{ fn: "sum", field: "amount", alias: "totalAmount" },
|
|
1974
|
+
{ fn: "avg", field: "amount", alias: "avgAmount" },
|
|
1975
|
+
{ fn: "min", field: "amount", alias: "minAmount" },
|
|
1976
|
+
{ fn: "max", field: "amount", alias: "maxAmount" }
|
|
1977
|
+
],
|
|
1978
|
+
groupBy: ["region"]
|
|
1979
|
+
};
|
|
1980
|
+
const pipeline = compileMongoAggregate(spec, "_id");
|
|
1981
|
+
expect(pipeline).toEqual([
|
|
1982
|
+
{
|
|
1983
|
+
$group: {
|
|
1984
|
+
_id: { region: "$region" },
|
|
1985
|
+
total: { $sum: 1 },
|
|
1986
|
+
uniqueStatuses: { $addToSet: "$status" },
|
|
1987
|
+
totalAmount: { $sum: "$amount" },
|
|
1988
|
+
avgAmount: { $avg: "$amount" },
|
|
1989
|
+
minAmount: { $min: "$amount" },
|
|
1990
|
+
maxAmount: { $max: "$amount" }
|
|
1991
|
+
}
|
|
1992
|
+
},
|
|
1993
|
+
{
|
|
1994
|
+
$project: {
|
|
1995
|
+
_id: 0,
|
|
1996
|
+
region: "$_id.region",
|
|
1997
|
+
total: 1,
|
|
1998
|
+
uniqueStatuses: { $size: "$uniqueStatuses" },
|
|
1999
|
+
totalAmount: 1,
|
|
2000
|
+
avgAmount: 1,
|
|
2001
|
+
minAmount: 1,
|
|
2002
|
+
maxAmount: 1
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
]);
|
|
2006
|
+
});
|
|
2007
|
+
test("empty where filter is omitted from pipeline", () => {
|
|
2008
|
+
const spec = {
|
|
2009
|
+
where: FilterGroup2.create(),
|
|
2010
|
+
aggregates: [{ fn: "count", alias: "total" }],
|
|
2011
|
+
groupBy: []
|
|
2012
|
+
};
|
|
2013
|
+
expect(compileMongoAggregate(spec, "_id")).toEqual([
|
|
2014
|
+
{ $group: { _id: null, total: { $sum: 1 } } },
|
|
2015
|
+
{ $project: { _id: 0, total: 1 } }
|
|
2016
|
+
]);
|
|
2017
|
+
});
|
|
2018
|
+
});
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
// src/orm/orm-adapter.ts
|
|
2022
|
+
var OrmAdapter = class {
|
|
2023
|
+
queryableOps = [];
|
|
2024
|
+
updateOps = [];
|
|
2025
|
+
aggregateOps = [];
|
|
2026
|
+
supportedFieldTypes = [];
|
|
2027
|
+
onFatalError(err) {
|
|
2028
|
+
const wrapped = err instanceof EquippedError ? err : new EquippedError("OrmAdapter fatal error", {}, err);
|
|
2029
|
+
Instance.crash(wrapped);
|
|
2030
|
+
}
|
|
2031
|
+
constructor() {
|
|
2032
|
+
const self = this;
|
|
2033
|
+
if (typeof self.connect === "function") {
|
|
2034
|
+
Instance.on("start", () => self.connect(), { class: this.constructor });
|
|
2035
|
+
}
|
|
2036
|
+
if (typeof self.disconnect === "function") {
|
|
2037
|
+
Instance.on("close", () => self.disconnect(), { class: this.constructor });
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
use(schema, config) {
|
|
2041
|
+
const self = this;
|
|
2042
|
+
const emptyIterator = async function* () {
|
|
2043
|
+
};
|
|
2044
|
+
const use = {
|
|
2045
|
+
findMany: (filter, opts) => self.findMany?.(schema, config, filter, opts) ?? Promise.resolve([]),
|
|
2046
|
+
iterateMany: (filter, opts) => self.iterateMany?.(schema, config, filter, opts) ?? emptyIterator(),
|
|
2047
|
+
findOne: async (filter) => {
|
|
2048
|
+
const rows = await use.findMany(filter, { limit: 1 });
|
|
2049
|
+
return rows[0] ?? null;
|
|
2050
|
+
},
|
|
2051
|
+
count: (filter) => self.count?.(schema, config, filter) ?? Promise.reject(new Error("count not implemented")),
|
|
2052
|
+
createOne: async (d) => {
|
|
2053
|
+
const rows = await use.createMany([d]);
|
|
2054
|
+
return rows[0];
|
|
2055
|
+
},
|
|
2056
|
+
createMany: (d) => self.createMany?.(schema, config, d) ?? Promise.resolve([]),
|
|
2057
|
+
updateMany: (filter, d) => self.updateMany?.(schema, config, filter, d) ?? Promise.resolve([]),
|
|
2058
|
+
updateOne: async (filter, d) => {
|
|
2059
|
+
const match = await use.findOne(filter);
|
|
2060
|
+
if (!match) return null;
|
|
2061
|
+
const pk = schema.pkField.name;
|
|
2062
|
+
const pkFilter = FilterGroup.create().eq(pk, match[pk]);
|
|
2063
|
+
const rows = await use.updateMany(pkFilter, d);
|
|
2064
|
+
return rows[0] ?? null;
|
|
2065
|
+
},
|
|
2066
|
+
upsertOne: (filter, create, ops) => self.upsertOne?.(schema, config, filter, create, ops) ?? Promise.reject(new Error("upsertOne not implemented")),
|
|
2067
|
+
deleteOne: async (filter) => {
|
|
2068
|
+
const row = await use.findOne(filter);
|
|
2069
|
+
if (!row) return null;
|
|
2070
|
+
const pk = schema.pkField.name;
|
|
2071
|
+
if (self.deleteByPk) {
|
|
2072
|
+
await self.deleteByPk(schema, config, row[pk]);
|
|
2073
|
+
} else if (self.deleteMany) {
|
|
2074
|
+
const pkFilter = FilterGroup.create().eq(pk, row[pk]);
|
|
2075
|
+
await self.deleteMany(schema, config, pkFilter);
|
|
2076
|
+
}
|
|
2077
|
+
return row;
|
|
2078
|
+
},
|
|
2079
|
+
deleteMany: (filter) => self.deleteMany?.(schema, config, filter) ?? Promise.resolve([]),
|
|
2080
|
+
raw: (...args) => self.raw?.(schema, config, ...args) ?? Promise.reject(new Error("raw not implemented")),
|
|
2081
|
+
aggregate: (spec) => self.aggregate?.(schema, config, spec) ?? Promise.reject(new Error("aggregate not implemented")),
|
|
2082
|
+
aggregateOps: self.aggregateOps ?? []
|
|
2083
|
+
};
|
|
2084
|
+
return use;
|
|
2085
|
+
}
|
|
2086
|
+
};
|
|
2087
|
+
if (void 0) {
|
|
2088
|
+
const { describe, test, expect, vi } = void 0;
|
|
2089
|
+
const { v: v6 } = null;
|
|
2090
|
+
describe("OrmAdapter", () => {
|
|
2091
|
+
test("subclass with connect/disconnect auto-registers Instance hooks", async () => {
|
|
2092
|
+
const { Instance: Inst } = await null;
|
|
2093
|
+
const onSpy = vi.spyOn(Inst, "on").mockImplementation(() => {
|
|
2094
|
+
});
|
|
2095
|
+
class TestAdapter extends OrmAdapter {
|
|
2096
|
+
schemaConfigPipe = v6.object({ table: v6.string() });
|
|
2097
|
+
supportedFieldTypes = ["string"];
|
|
2098
|
+
async connect() {
|
|
2099
|
+
}
|
|
2100
|
+
async disconnect() {
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
new TestAdapter();
|
|
2104
|
+
expect(onSpy).toHaveBeenCalledWith("start", expect.any(Function), expect.objectContaining({ class: TestAdapter }));
|
|
2105
|
+
expect(onSpy).toHaveBeenCalledWith("close", expect.any(Function), expect.objectContaining({ class: TestAdapter }));
|
|
2106
|
+
onSpy.mockRestore();
|
|
2107
|
+
});
|
|
2108
|
+
test("subclass without connect/disconnect does not register hooks", async () => {
|
|
2109
|
+
const { Instance: Inst } = await null;
|
|
2110
|
+
const onSpy = vi.spyOn(Inst, "on").mockImplementation(() => {
|
|
2111
|
+
});
|
|
2112
|
+
class MinimalAdapter extends OrmAdapter {
|
|
2113
|
+
schemaConfigPipe = v6.object({ table: v6.string() });
|
|
2114
|
+
}
|
|
2115
|
+
new MinimalAdapter();
|
|
2116
|
+
expect(onSpy).not.toHaveBeenCalled();
|
|
2117
|
+
onSpy.mockRestore();
|
|
2118
|
+
});
|
|
2119
|
+
test("capability declarations default to empty arrays", async () => {
|
|
2120
|
+
const { Instance: Inst } = await null;
|
|
2121
|
+
vi.spyOn(Inst, "on").mockImplementation(() => {
|
|
2122
|
+
});
|
|
2123
|
+
class DefaultAdapter extends OrmAdapter {
|
|
2124
|
+
schemaConfigPipe = v6.object({ table: v6.string() });
|
|
2125
|
+
}
|
|
2126
|
+
const adapter = new DefaultAdapter();
|
|
2127
|
+
expect(adapter.queryableOps).toEqual([]);
|
|
2128
|
+
expect(adapter.updateOps).toEqual([]);
|
|
2129
|
+
expect(adapter.aggregateOps).toEqual([]);
|
|
2130
|
+
expect(adapter.supportedFieldTypes).toEqual([]);
|
|
2131
|
+
vi.restoreAllMocks();
|
|
2132
|
+
});
|
|
2133
|
+
test("onFatalError wraps non-EquippedError and calls Instance.crash", async () => {
|
|
2134
|
+
const { Instance: Inst } = await null;
|
|
2135
|
+
vi.spyOn(Inst, "on").mockImplementation(() => {
|
|
2136
|
+
});
|
|
2137
|
+
const crashSpy = vi.spyOn(Inst, "crash").mockImplementation((() => {
|
|
2138
|
+
throw new Error("crash");
|
|
2139
|
+
}));
|
|
2140
|
+
class FatalAdapter extends OrmAdapter {
|
|
2141
|
+
schemaConfigPipe = v6.object({ table: v6.string() });
|
|
2142
|
+
triggerFatal() {
|
|
2143
|
+
this.onFatalError(new Error("something broke"));
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
const adapter = new FatalAdapter();
|
|
2147
|
+
expect(() => adapter.triggerFatal()).toThrow("crash");
|
|
2148
|
+
expect(crashSpy).toHaveBeenCalled();
|
|
2149
|
+
vi.restoreAllMocks();
|
|
2150
|
+
});
|
|
2151
|
+
test("onFatalError passes through EquippedError directly", async () => {
|
|
2152
|
+
const { Instance: Inst } = await null;
|
|
2153
|
+
vi.spyOn(Inst, "on").mockImplementation(() => {
|
|
2154
|
+
});
|
|
2155
|
+
let crashedWith;
|
|
2156
|
+
vi.spyOn(Inst, "crash").mockImplementation(((err) => {
|
|
2157
|
+
crashedWith = err;
|
|
2158
|
+
throw err;
|
|
2159
|
+
}));
|
|
2160
|
+
class FatalAdapter extends OrmAdapter {
|
|
2161
|
+
schemaConfigPipe = v6.object({ table: v6.string() });
|
|
2162
|
+
triggerFatal() {
|
|
2163
|
+
this.onFatalError(new EquippedError("equipped error", { detail: "test" }));
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
const adapter = new FatalAdapter();
|
|
2167
|
+
expect(() => adapter.triggerFatal()).toThrow();
|
|
2168
|
+
expect(crashedWith).toBeInstanceOf(EquippedError);
|
|
2169
|
+
expect(crashedWith.message).toBe("equipped error");
|
|
2170
|
+
vi.restoreAllMocks();
|
|
2171
|
+
});
|
|
2172
|
+
test("use() creates OrmUse bridge that delegates to flat methods", async () => {
|
|
2173
|
+
const { Instance: Inst } = await null;
|
|
2174
|
+
const { Schema } = await null;
|
|
2175
|
+
vi.spyOn(Inst, "on").mockImplementation(() => {
|
|
2176
|
+
});
|
|
2177
|
+
class BridgeAdapter extends OrmAdapter {
|
|
2178
|
+
schemaConfigPipe = v6.object({ table: v6.string() });
|
|
2179
|
+
supportedFieldTypes = ["string"];
|
|
2180
|
+
queryableOps = ["eq"];
|
|
2181
|
+
async findMany(_s, _c, _f, _o) {
|
|
2182
|
+
return [{ id: "found" }];
|
|
2183
|
+
}
|
|
2184
|
+
async count(_s, _c, _f) {
|
|
2185
|
+
return 1;
|
|
2186
|
+
}
|
|
2187
|
+
async *iterateMany(_s, _c, _f, _o) {
|
|
2188
|
+
yield { id: "iterated" };
|
|
2189
|
+
}
|
|
2190
|
+
async createMany(_s, _c, data) {
|
|
2191
|
+
return data;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
const TestSchema = Schema.from("bridge_test").pk("id", v6.string(), () => "x").build();
|
|
2195
|
+
const adapter = new BridgeAdapter();
|
|
2196
|
+
const ormUse = adapter.use(TestSchema, { table: "bridge_test" });
|
|
2197
|
+
const rows = await ormUse.findMany(FilterGroup.create());
|
|
2198
|
+
expect(rows).toEqual([{ id: "found" }]);
|
|
2199
|
+
await expect(ormUse.count(FilterGroup.create())).resolves.toBe(1);
|
|
2200
|
+
const iterated = [];
|
|
2201
|
+
for await (const row of ormUse.iterateMany(FilterGroup.create())) iterated.push(row);
|
|
2202
|
+
expect(iterated).toEqual([{ id: "iterated" }]);
|
|
2203
|
+
const created = await ormUse.createOne({ id: "new" });
|
|
2204
|
+
expect(created).toEqual({ id: "new" });
|
|
2205
|
+
vi.restoreAllMocks();
|
|
2206
|
+
});
|
|
2207
|
+
test("use() bridge delegates updateMany to flat method", async () => {
|
|
2208
|
+
const { Instance: Inst } = await null;
|
|
2209
|
+
const { Schema } = await null;
|
|
2210
|
+
vi.spyOn(Inst, "on").mockImplementation(() => {
|
|
2211
|
+
});
|
|
2212
|
+
class UpdateAdapter extends OrmAdapter {
|
|
2213
|
+
schemaConfigPipe = v6.object({ table: v6.string() });
|
|
2214
|
+
supportedFieldTypes = ["string", "number"];
|
|
2215
|
+
updateOps = ["set"];
|
|
2216
|
+
async findMany(_s, _c, _f) {
|
|
2217
|
+
return [{ id: "u1", name: "old" }];
|
|
2218
|
+
}
|
|
2219
|
+
async updateMany(_s, _c, _f, data) {
|
|
2220
|
+
return [{ id: "u1", ...data }];
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
const TestSchema = Schema.from("upd_test").pk("id", v6.string(), () => "x").field("name", v6.string()).build();
|
|
2224
|
+
const adapter = new UpdateAdapter();
|
|
2225
|
+
const ormUse = adapter.use(TestSchema, { table: "upd_test" });
|
|
2226
|
+
const updated = await ormUse.updateOne(FilterGroup.create().eq("id", "u1"), { name: "new" });
|
|
2227
|
+
expect(updated).not.toBeNull();
|
|
2228
|
+
expect(updated.name).toBe("new");
|
|
2229
|
+
vi.restoreAllMocks();
|
|
2230
|
+
});
|
|
2231
|
+
test("use().iterateMany does not fall back to findMany when adapter omits iterateMany", async () => {
|
|
2232
|
+
const { Instance: Inst } = await null;
|
|
2233
|
+
const { Schema } = await null;
|
|
2234
|
+
vi.spyOn(Inst, "on").mockImplementation(() => {
|
|
2235
|
+
});
|
|
2236
|
+
let findManyCalls = 0;
|
|
2237
|
+
class FindOnlyAdapter extends OrmAdapter {
|
|
2238
|
+
schemaConfigPipe = v6.object({ table: v6.string() });
|
|
2239
|
+
supportedFieldTypes = ["string"];
|
|
2240
|
+
async findMany() {
|
|
2241
|
+
findManyCalls += 1;
|
|
2242
|
+
return [{ id: "found" }];
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
const TestSchema = Schema.from("iter_no_fallback").pk("id", v6.string(), () => "x").build();
|
|
2246
|
+
const adapter = new FindOnlyAdapter();
|
|
2247
|
+
const rows = [];
|
|
2248
|
+
for await (const row of adapter.use(TestSchema, { table: "iter_no_fallback" }).iterateMany(FilterGroup.create())) rows.push(row);
|
|
2249
|
+
expect(rows).toEqual([]);
|
|
2250
|
+
expect(findManyCalls).toBe(0);
|
|
2251
|
+
vi.restoreAllMocks();
|
|
2252
|
+
});
|
|
2253
|
+
test("use() bridge delegates deleteByPk via deleteOne", async () => {
|
|
2254
|
+
const { Instance: Inst } = await null;
|
|
2255
|
+
const { Schema } = await null;
|
|
2256
|
+
vi.spyOn(Inst, "on").mockImplementation(() => {
|
|
2257
|
+
});
|
|
2258
|
+
let deletedPk = null;
|
|
2259
|
+
class DeleteAdapter extends OrmAdapter {
|
|
2260
|
+
schemaConfigPipe = v6.object({ table: v6.string() });
|
|
2261
|
+
supportedFieldTypes = ["string"];
|
|
2262
|
+
async findMany(_s, _c, _f) {
|
|
2263
|
+
return [{ id: "d1" }];
|
|
2264
|
+
}
|
|
2265
|
+
async deleteByPk(_s, _c, pk) {
|
|
2266
|
+
deletedPk = pk;
|
|
2267
|
+
return { id: String(pk) };
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
const TestSchema = Schema.from("del_test").pk("id", v6.string(), () => "x").build();
|
|
2271
|
+
const adapter = new DeleteAdapter();
|
|
2272
|
+
const ormUse = adapter.use(TestSchema, { table: "del_test" });
|
|
2273
|
+
const deleted = await ormUse.deleteOne(FilterGroup.create().eq("id", "d1"));
|
|
2274
|
+
expect(deleted).toEqual({ id: "d1" });
|
|
2275
|
+
expect(deletedPk).toBe("d1");
|
|
2276
|
+
vi.restoreAllMocks();
|
|
2277
|
+
});
|
|
2278
|
+
test("use() bridge delegates session to flat method", async () => {
|
|
2279
|
+
const { Instance: Inst } = await null;
|
|
2280
|
+
vi.spyOn(Inst, "on").mockImplementation(() => {
|
|
2281
|
+
});
|
|
2282
|
+
let sessionRan = false;
|
|
2283
|
+
class SessionAdapter extends OrmAdapter {
|
|
2284
|
+
schemaConfigPipe = v6.object({ table: v6.string() });
|
|
2285
|
+
async session(fn) {
|
|
2286
|
+
sessionRan = true;
|
|
2287
|
+
return fn();
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
const adapter = new SessionAdapter();
|
|
2291
|
+
const result = await adapter.session(async () => 42);
|
|
2292
|
+
expect(result).toBe(42);
|
|
2293
|
+
expect(sessionRan).toBe(true);
|
|
2294
|
+
vi.restoreAllMocks();
|
|
2295
|
+
});
|
|
2296
|
+
test("type-level: subclass method override enforces canonical signature", async () => {
|
|
2297
|
+
const { Instance: Inst } = await null;
|
|
2298
|
+
vi.spyOn(Inst, "on").mockImplementation(() => {
|
|
2299
|
+
});
|
|
2300
|
+
class CorrectAdapter extends OrmAdapter {
|
|
2301
|
+
schemaConfigPipe = v6.object({ table: v6.string() });
|
|
2302
|
+
async findByPk(_s, _c, _pk) {
|
|
2303
|
+
return null;
|
|
2304
|
+
}
|
|
2305
|
+
async createMany(_s, _c, _d) {
|
|
2306
|
+
return [];
|
|
2307
|
+
}
|
|
2308
|
+
async raw(_s, _c, ..._args) {
|
|
2309
|
+
return null;
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
const adapter = new CorrectAdapter();
|
|
2313
|
+
expect(adapter.findByPk).toBeTypeOf("function");
|
|
2314
|
+
expect(adapter.createMany).toBeTypeOf("function");
|
|
2315
|
+
expect(adapter.raw).toBeTypeOf("function");
|
|
2316
|
+
vi.restoreAllMocks();
|
|
2317
|
+
});
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
// src/orm/adapters/mongodb/index.ts
|
|
2322
|
+
var mongoSchemaConfigPipe = () => import_valleyed5.v.object({ db: import_valleyed5.v.string(), col: import_valleyed5.v.string() });
|
|
2323
|
+
var mongoConnectionPipe = () => import_valleyed5.v.object({
|
|
2324
|
+
uri: import_valleyed5.v.string()
|
|
2325
|
+
});
|
|
2326
|
+
var MIGRATION_TRACKER_COLLECTION = "equipped_migrations";
|
|
2327
|
+
var MongoDbAdapter = class extends configurable(mongoConnectionPipe, OrmAdapter) {
|
|
2328
|
+
schemaConfigPipe = mongoSchemaConfigPipe();
|
|
2329
|
+
queryableOps = ["eq", "ne", "gt", "gte", "lt", "lte", "in", "notIn", "like", "exists", "notExists", "contains", "notContains"];
|
|
2330
|
+
updateOps = ["set", "inc", "mul", "min", "max", "unset", "push", "pull", "patch"];
|
|
2331
|
+
aggregateOps = ["count", "countDistinct", "sum", "avg", "min", "max"];
|
|
2332
|
+
supportedFieldTypes = ["string", "number", "boolean", "null", "object", "array", "date"];
|
|
2333
|
+
client;
|
|
2334
|
+
#sessionStore = new import_node_async_hooks.AsyncLocalStorage();
|
|
2335
|
+
constructor(config, options) {
|
|
2336
|
+
super(config);
|
|
2337
|
+
this.client = new import_mongodb.MongoClient(config.uri, {
|
|
2338
|
+
...options,
|
|
2339
|
+
ignoreUndefined: true
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
#getCollection(schemaCfg) {
|
|
2343
|
+
return this.client.db(schemaCfg.db).collection(schemaCfg.col);
|
|
2344
|
+
}
|
|
2345
|
+
async connect() {
|
|
2346
|
+
try {
|
|
2347
|
+
await this.client.connect();
|
|
2348
|
+
} catch (error) {
|
|
2349
|
+
throw new EquippedError("Failed to connect MongoDB client", { adapter: "mongodb" }, error);
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
async disconnect() {
|
|
2353
|
+
try {
|
|
2354
|
+
await this.client.close();
|
|
2355
|
+
} catch (error) {
|
|
2356
|
+
throw new EquippedError("Failed to disconnect MongoDB client", { adapter: "mongodb" }, error);
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
async findByPk(schema, config, pk) {
|
|
2360
|
+
const schemaCfg = config;
|
|
2361
|
+
try {
|
|
2362
|
+
const pkName = schema.pkField.name;
|
|
2363
|
+
const collection = this.#getCollection(schemaCfg);
|
|
2364
|
+
const doc = await collection.findOne(
|
|
2365
|
+
{ [pkName]: pk },
|
|
2366
|
+
{ session: this.#sessionStore.getStore() }
|
|
2367
|
+
);
|
|
2368
|
+
return doc;
|
|
2369
|
+
} catch (error) {
|
|
2370
|
+
throw new EquippedError(
|
|
2371
|
+
"MongoDB findByPk failed",
|
|
2372
|
+
{ adapter: "mongodb", operation: "findByPk", collection: schemaCfg.col },
|
|
2373
|
+
error
|
|
2374
|
+
);
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
async createMany(_schema, config, data) {
|
|
2378
|
+
const schemaCfg = config;
|
|
2379
|
+
try {
|
|
2380
|
+
const collection = this.#getCollection(schemaCfg);
|
|
2381
|
+
const docs = data.map((d) => d);
|
|
2382
|
+
await collection.insertMany(docs, { session: this.#sessionStore.getStore() });
|
|
2383
|
+
return data;
|
|
2384
|
+
} catch (error) {
|
|
2385
|
+
throw new EquippedError(
|
|
2386
|
+
"MongoDB createMany failed",
|
|
2387
|
+
{ adapter: "mongodb", operation: "createMany", collection: schemaCfg.col },
|
|
2388
|
+
error
|
|
2389
|
+
);
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
async updateByPk(schema, config, pk, ops) {
|
|
2393
|
+
const schemaCfg = config;
|
|
2394
|
+
try {
|
|
2395
|
+
const pkName = schema.pkField.name;
|
|
2396
|
+
const collection = this.#getCollection(schemaCfg);
|
|
2397
|
+
const update = compileMongoOps(ops);
|
|
2398
|
+
if (Object.keys(update).length === 0) {
|
|
2399
|
+
return await collection.findOne({ [pkName]: pk }, { session: this.#sessionStore.getStore() });
|
|
2400
|
+
}
|
|
2401
|
+
return await collection.findOneAndUpdate(
|
|
2402
|
+
{ [pkName]: pk },
|
|
2403
|
+
update,
|
|
2404
|
+
{ returnDocument: "after", session: this.#sessionStore.getStore() }
|
|
2405
|
+
);
|
|
2406
|
+
} catch (error) {
|
|
2407
|
+
throw new EquippedError(
|
|
2408
|
+
"MongoDB updateByPk failed",
|
|
2409
|
+
{ adapter: "mongodb", operation: "updateByPk", collection: schemaCfg.col },
|
|
2410
|
+
error
|
|
2411
|
+
);
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
async deleteByPk(schema, config, pk) {
|
|
2415
|
+
const schemaCfg = config;
|
|
2416
|
+
try {
|
|
2417
|
+
const pkName = schema.pkField.name;
|
|
2418
|
+
const collection = this.#getCollection(schemaCfg);
|
|
2419
|
+
return await collection.findOneAndDelete(
|
|
2420
|
+
{ [pkName]: pk },
|
|
2421
|
+
{ session: this.#sessionStore.getStore() }
|
|
2422
|
+
);
|
|
2423
|
+
} catch (error) {
|
|
2424
|
+
throw new EquippedError(
|
|
2425
|
+
"MongoDB deleteByPk failed",
|
|
2426
|
+
{ adapter: "mongodb", operation: "deleteByPk", collection: schemaCfg.col },
|
|
2427
|
+
error
|
|
2428
|
+
);
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
async raw(_schema, config, pipeline) {
|
|
2432
|
+
const schemaCfg = config;
|
|
2433
|
+
try {
|
|
2434
|
+
const collection = this.#getCollection(schemaCfg);
|
|
2435
|
+
const result = await collection.aggregate(pipeline, { session: this.#sessionStore.getStore() }).toArray();
|
|
2436
|
+
return result;
|
|
2437
|
+
} catch (error) {
|
|
2438
|
+
if (error instanceof EquippedError) throw error;
|
|
2439
|
+
throw new EquippedError(
|
|
2440
|
+
"MongoDB raw failed",
|
|
2441
|
+
{ adapter: "mongodb", operation: "raw", collection: schemaCfg.col },
|
|
2442
|
+
error
|
|
2443
|
+
);
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
async aggregate(schema, config, spec) {
|
|
2447
|
+
const schemaCfg = config;
|
|
2448
|
+
try {
|
|
2449
|
+
const pk = schema.pkField.name;
|
|
2450
|
+
const collection = this.#getCollection(schemaCfg);
|
|
2451
|
+
const pipeline = compileMongoAggregate(spec, pk);
|
|
2452
|
+
const result = await collection.aggregate(pipeline, { session: this.#sessionStore.getStore() }).toArray();
|
|
2453
|
+
return result;
|
|
2454
|
+
} catch (error) {
|
|
2455
|
+
if (error instanceof EquippedError) throw error;
|
|
2456
|
+
throw new EquippedError(
|
|
2457
|
+
"MongoDB aggregate failed",
|
|
2458
|
+
{ adapter: "mongodb", operation: "aggregate", collection: schemaCfg.col },
|
|
2459
|
+
error
|
|
2460
|
+
);
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
async findMany(schema, config, filter, options) {
|
|
2464
|
+
const schemaCfg = config;
|
|
2465
|
+
try {
|
|
2466
|
+
const pk = schema.pkField.name;
|
|
2467
|
+
const { filter: mongoFilter, sort, limit, skip, projection } = compileMongoQuery(filter, options, pk);
|
|
2468
|
+
const collection = this.#getCollection(schemaCfg);
|
|
2469
|
+
let cursor = collection.find(mongoFilter, {
|
|
2470
|
+
session: this.#sessionStore.getStore(),
|
|
2471
|
+
projection
|
|
2472
|
+
});
|
|
2473
|
+
if (sort) cursor = cursor.sort(sort);
|
|
2474
|
+
if (limit) cursor = cursor.limit(limit);
|
|
2475
|
+
if (skip) cursor = cursor.skip(skip);
|
|
2476
|
+
return await cursor.toArray();
|
|
2477
|
+
} catch (error) {
|
|
2478
|
+
throw new EquippedError(
|
|
2479
|
+
"MongoDB findMany failed",
|
|
2480
|
+
{ adapter: "mongodb", operation: "findMany", collection: schemaCfg.col },
|
|
2481
|
+
error
|
|
2482
|
+
);
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
async count(schema, config, filter) {
|
|
2486
|
+
const schemaCfg = config;
|
|
2487
|
+
try {
|
|
2488
|
+
const pk = schema.pkField.name;
|
|
2489
|
+
const collection = this.#getCollection(schemaCfg);
|
|
2490
|
+
return await collection.countDocuments(compileMongoFilter(filter, pk), { session: this.#sessionStore.getStore() });
|
|
2491
|
+
} catch (error) {
|
|
2492
|
+
throw new EquippedError(
|
|
2493
|
+
"MongoDB count failed",
|
|
2494
|
+
{ adapter: "mongodb", operation: "count", collection: schemaCfg.col },
|
|
2495
|
+
error
|
|
2496
|
+
);
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
async *iterateMany(schema, config, filter, options) {
|
|
2500
|
+
const schemaCfg = config;
|
|
2501
|
+
let cursor;
|
|
2502
|
+
try {
|
|
2503
|
+
const pk = schema.pkField.name;
|
|
2504
|
+
const { filter: mongoFilter, sort, limit, skip, projection } = compileMongoQuery(filter, options, pk);
|
|
2505
|
+
const collection = this.#getCollection(schemaCfg);
|
|
2506
|
+
cursor = collection.find(mongoFilter, {
|
|
2507
|
+
session: this.#sessionStore.getStore(),
|
|
2508
|
+
projection
|
|
2509
|
+
});
|
|
2510
|
+
if (sort) cursor = cursor.sort(sort);
|
|
2511
|
+
if (limit) cursor = cursor.limit(limit);
|
|
2512
|
+
if (skip) cursor = cursor.skip(skip);
|
|
2513
|
+
if (options?.batchSize !== void 0) cursor = cursor.batchSize(options.batchSize);
|
|
2514
|
+
for await (const row of cursor) {
|
|
2515
|
+
yield row;
|
|
2516
|
+
}
|
|
2517
|
+
} catch (error) {
|
|
2518
|
+
throw new EquippedError(
|
|
2519
|
+
"MongoDB iterateMany failed",
|
|
2520
|
+
{ adapter: "mongodb", operation: "iterateMany", collection: schemaCfg.col },
|
|
2521
|
+
error
|
|
2522
|
+
);
|
|
2523
|
+
} finally {
|
|
2524
|
+
await cursor?.close();
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
async updateMany(schema, config, filter, data) {
|
|
2528
|
+
const schemaCfg = config;
|
|
2529
|
+
try {
|
|
2530
|
+
const pk = schema.pkField.name;
|
|
2531
|
+
const collection = this.#getCollection(schemaCfg);
|
|
2532
|
+
const session = this.#sessionStore.getStore();
|
|
2533
|
+
const mongoFilter = compileMongoFilter(filter, pk);
|
|
2534
|
+
const matchingDocs = await collection.find(mongoFilter, { session, projection: { [pk]: 1 } }).toArray();
|
|
2535
|
+
const ids = matchingDocs.map((d) => d[pk]);
|
|
2536
|
+
const idFilter = { [pk]: { $in: ids } };
|
|
2537
|
+
const update = compileMongoUpdate(data);
|
|
2538
|
+
if (Object.keys(update).length > 0) {
|
|
2539
|
+
await collection.updateMany(idFilter, update, { session });
|
|
2540
|
+
}
|
|
2541
|
+
return await collection.find({ [pk]: { $in: ids } }, { session }).toArray();
|
|
2542
|
+
} catch (error) {
|
|
2543
|
+
throw new EquippedError(
|
|
2544
|
+
"MongoDB updateMany failed",
|
|
2545
|
+
{ adapter: "mongodb", operation: "updateMany", collection: schemaCfg.col },
|
|
2546
|
+
error
|
|
2547
|
+
);
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
async deleteMany(schema, config, filter) {
|
|
2551
|
+
const schemaCfg = config;
|
|
2552
|
+
try {
|
|
2553
|
+
const pk = schema.pkField.name;
|
|
2554
|
+
const collection = this.#getCollection(schemaCfg);
|
|
2555
|
+
const session = this.#sessionStore.getStore();
|
|
2556
|
+
const mongoFilter = compileMongoFilter(filter, pk);
|
|
2557
|
+
const docs = await collection.find(mongoFilter, { session }).toArray();
|
|
2558
|
+
if (docs.length > 0) {
|
|
2559
|
+
await collection.deleteMany(mongoFilter, { session });
|
|
2560
|
+
}
|
|
2561
|
+
return docs;
|
|
2562
|
+
} catch (error) {
|
|
2563
|
+
throw new EquippedError(
|
|
2564
|
+
"MongoDB deleteMany failed",
|
|
2565
|
+
{ adapter: "mongodb", operation: "deleteMany", collection: schemaCfg.col },
|
|
2566
|
+
error
|
|
2567
|
+
);
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
async upsertOne(schema, config, filter, create, ops) {
|
|
2571
|
+
const schemaCfg = config;
|
|
2572
|
+
try {
|
|
2573
|
+
const pk = schema.pkField.name;
|
|
2574
|
+
const collection = this.#getCollection(schemaCfg);
|
|
2575
|
+
const mongoFilter = compileMongoFilter(filter, pk);
|
|
2576
|
+
const updateDoc = compileMongoOps(ops);
|
|
2577
|
+
const doc = await collection.findOneAndUpdate(
|
|
2578
|
+
mongoFilter,
|
|
2579
|
+
{
|
|
2580
|
+
...updateDoc,
|
|
2581
|
+
$setOnInsert: create
|
|
2582
|
+
},
|
|
2583
|
+
{
|
|
2584
|
+
returnDocument: "after",
|
|
2585
|
+
session: this.#sessionStore.getStore(),
|
|
2586
|
+
upsert: true
|
|
2587
|
+
}
|
|
2588
|
+
);
|
|
2589
|
+
return doc;
|
|
2590
|
+
} catch (error) {
|
|
2591
|
+
throw new EquippedError(
|
|
2592
|
+
"MongoDB upsertOne failed",
|
|
2593
|
+
{ adapter: "mongodb", operation: "upsertOne", collection: schemaCfg.col },
|
|
2594
|
+
error
|
|
2595
|
+
);
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
async session(fn) {
|
|
2599
|
+
if (this.#sessionStore.getStore()) return fn();
|
|
2600
|
+
try {
|
|
2601
|
+
const session = await this.client.startSession();
|
|
2602
|
+
try {
|
|
2603
|
+
return await session.withTransaction(async () => this.#sessionStore.run(session, fn));
|
|
2604
|
+
} finally {
|
|
2605
|
+
await session.endSession();
|
|
2606
|
+
}
|
|
2607
|
+
} catch (error) {
|
|
2608
|
+
if (error instanceof EquippedError) throw error;
|
|
2609
|
+
throw new EquippedError("MongoDB session failed", { adapter: "mongodb", operation: "session" }, error);
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
async loadMigrations() {
|
|
2613
|
+
const db = this.client.db();
|
|
2614
|
+
const docs = await db.collection(MIGRATION_TRACKER_COLLECTION).find({}).toArray();
|
|
2615
|
+
return docs.map((d) => ({ id: String(d._id), appliedAt: d.appliedAt }));
|
|
2616
|
+
}
|
|
2617
|
+
async recordMigration(id, appliedAt) {
|
|
2618
|
+
const db = this.client.db();
|
|
2619
|
+
await db.collection(MIGRATION_TRACKER_COLLECTION).insertOne({ _id: id, appliedAt });
|
|
2620
|
+
}
|
|
2621
|
+
async applyAddIndex(change) {
|
|
2622
|
+
const db = this.client.db();
|
|
2623
|
+
const fields = {};
|
|
2624
|
+
for (const f of change.on) {
|
|
2625
|
+
fields[f] = 1;
|
|
2626
|
+
}
|
|
2627
|
+
const name = change.name ?? `${change.table}_${change.on.join("_")}_idx`;
|
|
2628
|
+
await db.collection(change.table).createIndex(fields, { unique: change.unique ?? false, name });
|
|
2629
|
+
}
|
|
2630
|
+
async applyDropIndex(change) {
|
|
2631
|
+
const db = this.client.db();
|
|
2632
|
+
const collections = await db.listCollections().toArray();
|
|
2633
|
+
for (const col of collections) {
|
|
2634
|
+
const indexes = await db.collection(col.name).listIndexes().toArray();
|
|
2635
|
+
if (indexes.some((idx) => idx.name === change.name)) {
|
|
2636
|
+
await db.collection(col.name).dropIndex(change.name);
|
|
2637
|
+
return;
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
async introspect() {
|
|
2642
|
+
const db = this.client.db();
|
|
2643
|
+
const collections = await db.listCollections().toArray();
|
|
2644
|
+
const schemas = [];
|
|
2645
|
+
for (const col of collections) {
|
|
2646
|
+
if (col.name === MIGRATION_TRACKER_COLLECTION) continue;
|
|
2647
|
+
const rawIndexes = await db.collection(col.name).listIndexes().toArray();
|
|
2648
|
+
const indexes = rawIndexes.filter((idx) => idx.name !== "_id_").map((idx) => ({
|
|
2649
|
+
name: idx.name,
|
|
2650
|
+
on: Object.keys(idx.key),
|
|
2651
|
+
unique: !!idx.unique
|
|
2652
|
+
}));
|
|
2653
|
+
schemas.push({
|
|
2654
|
+
name: col.name,
|
|
2655
|
+
pk: void 0,
|
|
2656
|
+
fields: [],
|
|
2657
|
+
indexes,
|
|
2658
|
+
foreignKeys: []
|
|
2659
|
+
});
|
|
2660
|
+
}
|
|
2661
|
+
return schemas;
|
|
2662
|
+
}
|
|
2663
|
+
};
|
|
2664
|
+
if (void 0) {
|
|
2665
|
+
const { describe, test, expect, expectTypeOf } = void 0;
|
|
2666
|
+
describe("MongoDbAdapter: class-via-configurable shape", () => {
|
|
2667
|
+
test("MongoDbAdapter.create validates connection config via pipe", () => {
|
|
2668
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017" });
|
|
2669
|
+
expect(adapter).toBeInstanceOf(MongoDbAdapter);
|
|
2670
|
+
expect(adapter.client).toBeInstanceOf(import_mongodb.MongoClient);
|
|
2671
|
+
});
|
|
2672
|
+
test("MongoDbAdapter.create rejects invalid config", () => {
|
|
2673
|
+
expect(() => MongoDbAdapter.create({})).toThrow();
|
|
2674
|
+
expect(() => MongoDbAdapter.create({ uri: 123 })).toThrow();
|
|
2675
|
+
});
|
|
2676
|
+
test("readonly schemaConfigPipe declared with { db, col } shape", () => {
|
|
2677
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017" });
|
|
2678
|
+
expect(adapter.schemaConfigPipe).toBeDefined();
|
|
2679
|
+
});
|
|
2680
|
+
test("capability declarations use as const", () => {
|
|
2681
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017" });
|
|
2682
|
+
expect(adapter.supportedFieldTypes).toEqual([
|
|
2683
|
+
"string",
|
|
2684
|
+
"number",
|
|
2685
|
+
"boolean",
|
|
2686
|
+
"null",
|
|
2687
|
+
"object",
|
|
2688
|
+
"array",
|
|
2689
|
+
"date"
|
|
2690
|
+
]);
|
|
2691
|
+
expect(adapter.queryableOps).toEqual([
|
|
2692
|
+
"eq",
|
|
2693
|
+
"ne",
|
|
2694
|
+
"gt",
|
|
2695
|
+
"gte",
|
|
2696
|
+
"lt",
|
|
2697
|
+
"lte",
|
|
2698
|
+
"in",
|
|
2699
|
+
"notIn",
|
|
2700
|
+
"like",
|
|
2701
|
+
"exists",
|
|
2702
|
+
"notExists",
|
|
2703
|
+
"contains",
|
|
2704
|
+
"notContains"
|
|
2705
|
+
]);
|
|
2706
|
+
expect(adapter.updateOps).toEqual([
|
|
2707
|
+
"set",
|
|
2708
|
+
"inc",
|
|
2709
|
+
"mul",
|
|
2710
|
+
"min",
|
|
2711
|
+
"max",
|
|
2712
|
+
"unset",
|
|
2713
|
+
"push",
|
|
2714
|
+
"pull",
|
|
2715
|
+
"patch"
|
|
2716
|
+
]);
|
|
2717
|
+
expect(adapter.aggregateOps).toEqual([
|
|
2718
|
+
"count",
|
|
2719
|
+
"countDistinct",
|
|
2720
|
+
"sum",
|
|
2721
|
+
"avg",
|
|
2722
|
+
"min",
|
|
2723
|
+
"max"
|
|
2724
|
+
]);
|
|
2725
|
+
});
|
|
2726
|
+
test("underlying MongoClient exposed as readonly instance field", () => {
|
|
2727
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017" });
|
|
2728
|
+
expect(adapter.client).toBeInstanceOf(import_mongodb.MongoClient);
|
|
2729
|
+
});
|
|
2730
|
+
test("adapter.use returns OrmUse-shaped object", async () => {
|
|
2731
|
+
const { Schema } = await null;
|
|
2732
|
+
const { v: v6 } = await null;
|
|
2733
|
+
const schema = Schema.from("test").pk("id", v6.string(), () => "x").build();
|
|
2734
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017" });
|
|
2735
|
+
const use = adapter.use(schema, { db: "testdb", col: "testcol" });
|
|
2736
|
+
expect(use.findMany).toBeTypeOf("function");
|
|
2737
|
+
expect(use.iterateMany).toBeTypeOf("function");
|
|
2738
|
+
expect(use.findOne).toBeTypeOf("function");
|
|
2739
|
+
expect(use.count).toBeTypeOf("function");
|
|
2740
|
+
expect(use.createOne).toBeTypeOf("function");
|
|
2741
|
+
expect(use.createMany).toBeTypeOf("function");
|
|
2742
|
+
expect(use.updateMany).toBeTypeOf("function");
|
|
2743
|
+
expect(use.updateOne).toBeTypeOf("function");
|
|
2744
|
+
expect(use.upsertOne).toBeTypeOf("function");
|
|
2745
|
+
expect(use.deleteOne).toBeTypeOf("function");
|
|
2746
|
+
expect(use.deleteMany).toBeTypeOf("function");
|
|
2747
|
+
expect(use.raw).toBeTypeOf("function");
|
|
2748
|
+
});
|
|
2749
|
+
test("type-level: adapter declares all 9 canonical update ops", () => {
|
|
2750
|
+
const _adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017" });
|
|
2751
|
+
expectTypeOf().toEqualTypeOf();
|
|
2752
|
+
});
|
|
2753
|
+
test("type-level: adapter declares all 7 field types", () => {
|
|
2754
|
+
const _adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017" });
|
|
2755
|
+
expectTypeOf().toEqualTypeOf();
|
|
2756
|
+
});
|
|
2757
|
+
test("type-level: adapter declares all 13 queryable ops", () => {
|
|
2758
|
+
const _adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017" });
|
|
2759
|
+
expectTypeOf().toEqualTypeOf();
|
|
2760
|
+
});
|
|
2761
|
+
test("type-level: Repo.from with MongoDbAdapter enables all builder methods", async () => {
|
|
2762
|
+
const { Repo } = await null;
|
|
2763
|
+
const { Schema } = await null;
|
|
2764
|
+
const { v: v6 } = await null;
|
|
2765
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017" });
|
|
2766
|
+
const repo = Repo.from(adapter).resolve(() => ({ db: "test", col: "test" })).build();
|
|
2767
|
+
const _TestSchema = Schema.from("test").pk("id", v6.string(), () => "x").build();
|
|
2768
|
+
const _one = repo.on(_TestSchema).one();
|
|
2769
|
+
const _all = repo.on(_TestSchema).all();
|
|
2770
|
+
const _ref = repo.on(_TestSchema);
|
|
2771
|
+
expectTypeOf(_one.create).toBeFunction();
|
|
2772
|
+
expectTypeOf(_one.find).toBeFunction();
|
|
2773
|
+
expectTypeOf(_one.update).toBeFunction();
|
|
2774
|
+
expectTypeOf(_one.delete).toBeFunction();
|
|
2775
|
+
expectTypeOf(_one.upsert).toBeFunction();
|
|
2776
|
+
expectTypeOf(_all.create).toBeFunction();
|
|
2777
|
+
expectTypeOf(_all.find).toBeFunction();
|
|
2778
|
+
expectTypeOf(_all.count).toBeFunction();
|
|
2779
|
+
expectTypeOf(_all.update).toBeFunction();
|
|
2780
|
+
expectTypeOf(_all.delete).toBeFunction();
|
|
2781
|
+
expectTypeOf(_ref.raw).toBeFunction();
|
|
2782
|
+
expectTypeOf(repo.session).toBeFunction();
|
|
2783
|
+
});
|
|
2784
|
+
test("type-level: raw arg-tuple infers (pipeline: Record<string, unknown>[]) from MongoDbAdapter", async () => {
|
|
2785
|
+
const { Repo } = await null;
|
|
2786
|
+
const { Schema } = await null;
|
|
2787
|
+
const { v: v6 } = await null;
|
|
2788
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017" });
|
|
2789
|
+
const repo = Repo.from(adapter).resolve(() => ({ db: "test", col: "test" })).build();
|
|
2790
|
+
const _TestSchema = Schema.from("test").pk("id", v6.string(), () => "x").build();
|
|
2791
|
+
const _ref = repo.on(_TestSchema);
|
|
2792
|
+
expectTypeOf(_ref.raw).parameters.toEqualTypeOf();
|
|
2793
|
+
});
|
|
2794
|
+
test("type-level: per-call <T> override narrows MongoDbAdapter raw return type", async () => {
|
|
2795
|
+
const { Repo } = await null;
|
|
2796
|
+
const { Schema } = await null;
|
|
2797
|
+
const { v: v6 } = await null;
|
|
2798
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017" });
|
|
2799
|
+
const repo = Repo.from(adapter).resolve(() => ({ db: "test", col: "test" })).build();
|
|
2800
|
+
const _TestSchema = Schema.from("test").pk("id", v6.string(), () => "x").build();
|
|
2801
|
+
const _ref = repo.on(_TestSchema);
|
|
2802
|
+
expectTypeOf(_ref.raw).returns.toEqualTypeOf();
|
|
2803
|
+
});
|
|
2804
|
+
test("count forwards compiled filter to collection.countDocuments", async () => {
|
|
2805
|
+
const { Schema } = await null;
|
|
2806
|
+
const { v: v6 } = await null;
|
|
2807
|
+
const { FilterGroup: FilterGroup2 } = await null;
|
|
2808
|
+
const schema = Schema.from("mongo_count").pk("_id", v6.string(), () => "x").field("name", v6.string()).build();
|
|
2809
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017" });
|
|
2810
|
+
let capturedFilter;
|
|
2811
|
+
adapter.client.db = () => ({
|
|
2812
|
+
collection: () => ({
|
|
2813
|
+
countDocuments: async (filter, _opts) => {
|
|
2814
|
+
capturedFilter = filter;
|
|
2815
|
+
return 7;
|
|
2816
|
+
}
|
|
2817
|
+
})
|
|
2818
|
+
});
|
|
2819
|
+
const result = await adapter.count(schema, { db: "testdb", col: "things" }, FilterGroup2.create().eq("name", "Alice"));
|
|
2820
|
+
expect(capturedFilter).toEqual({ name: { $eq: "Alice" } });
|
|
2821
|
+
expect(result).toBe(7);
|
|
2822
|
+
});
|
|
2823
|
+
test("raw forwards pipeline to collection.aggregate at runtime", async () => {
|
|
2824
|
+
const { Schema } = await null;
|
|
2825
|
+
const { v: v6 } = await null;
|
|
2826
|
+
const schema = Schema.from("mongo_raw").pk("id", v6.string(), () => "x").build();
|
|
2827
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017" });
|
|
2828
|
+
let capturedPipeline;
|
|
2829
|
+
const mockResults = [{ total: 42 }];
|
|
2830
|
+
adapter.client.db = () => ({
|
|
2831
|
+
collection: () => ({
|
|
2832
|
+
aggregate: (pipeline, _opts) => {
|
|
2833
|
+
capturedPipeline = pipeline;
|
|
2834
|
+
return { toArray: async () => mockResults };
|
|
2835
|
+
}
|
|
2836
|
+
})
|
|
2837
|
+
});
|
|
2838
|
+
const result = await adapter.use(schema, { db: "testdb", col: "things" }).raw([{ $count: "total" }]);
|
|
2839
|
+
expect(capturedPipeline).toEqual([{ $count: "total" }]);
|
|
2840
|
+
expect(result).toEqual(mockResults);
|
|
2841
|
+
});
|
|
2842
|
+
test("iterateMany forwards batchSize and closes cursor on early return", async () => {
|
|
2843
|
+
const { Schema } = await null;
|
|
2844
|
+
const { v: v6 } = await null;
|
|
2845
|
+
const { FilterGroup: FilterGroup2 } = await null;
|
|
2846
|
+
const { OrderBy } = await null;
|
|
2847
|
+
const schema = Schema.from("mongo_iter").pk("_id", v6.string(), () => "x").field("age", v6.number()).build();
|
|
2848
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017" });
|
|
2849
|
+
let capturedFindFilter;
|
|
2850
|
+
let capturedFindOptions;
|
|
2851
|
+
let capturedSort;
|
|
2852
|
+
let capturedLimit;
|
|
2853
|
+
let capturedSkip;
|
|
2854
|
+
let capturedBatchSize;
|
|
2855
|
+
let closeCalls = 0;
|
|
2856
|
+
const cursor = {
|
|
2857
|
+
sort(sort) {
|
|
2858
|
+
capturedSort = sort;
|
|
2859
|
+
return this;
|
|
2860
|
+
},
|
|
2861
|
+
limit(limit) {
|
|
2862
|
+
capturedLimit = limit;
|
|
2863
|
+
return this;
|
|
2864
|
+
},
|
|
2865
|
+
skip(skip) {
|
|
2866
|
+
capturedSkip = skip;
|
|
2867
|
+
return this;
|
|
2868
|
+
},
|
|
2869
|
+
batchSize(batchSize) {
|
|
2870
|
+
capturedBatchSize = batchSize;
|
|
2871
|
+
return this;
|
|
2872
|
+
},
|
|
2873
|
+
async close() {
|
|
2874
|
+
closeCalls += 1;
|
|
2875
|
+
},
|
|
2876
|
+
async *[Symbol.asyncIterator]() {
|
|
2877
|
+
yield { _id: "u4", age: 50 };
|
|
2878
|
+
yield { _id: "u3", age: 40 };
|
|
2879
|
+
}
|
|
2880
|
+
};
|
|
2881
|
+
adapter.client.db = () => ({
|
|
2882
|
+
collection: () => ({
|
|
2883
|
+
find: (filter, options) => {
|
|
2884
|
+
capturedFindFilter = filter;
|
|
2885
|
+
capturedFindOptions = options;
|
|
2886
|
+
return cursor;
|
|
2887
|
+
}
|
|
2888
|
+
})
|
|
2889
|
+
});
|
|
2890
|
+
const rows = [];
|
|
2891
|
+
for await (const row of adapter.use(schema, { db: "testdb", col: "people" }).iterateMany(FilterGroup2.create().gt("age", 19), {
|
|
2892
|
+
orderBy: [new OrderBy("age", "desc")],
|
|
2893
|
+
offset: 1,
|
|
2894
|
+
limit: 2,
|
|
2895
|
+
batchSize: 25
|
|
2896
|
+
})) {
|
|
2897
|
+
rows.push(row);
|
|
2898
|
+
break;
|
|
2899
|
+
}
|
|
2900
|
+
expect(capturedFindFilter).toEqual({ age: { $gt: 19 } });
|
|
2901
|
+
expect(capturedFindOptions).toEqual({ session: void 0, projection: void 0 });
|
|
2902
|
+
expect(capturedSort).toEqual({ age: -1 });
|
|
2903
|
+
expect(capturedLimit).toBe(2);
|
|
2904
|
+
expect(capturedSkip).toBe(1);
|
|
2905
|
+
expect(capturedBatchSize).toBe(25);
|
|
2906
|
+
expect(rows).toEqual([{ _id: "u4", age: 50 }]);
|
|
2907
|
+
expect(closeCalls).toBe(1);
|
|
2908
|
+
});
|
|
2909
|
+
test("iterateMany closes cursor on normal completion", async () => {
|
|
2910
|
+
const { Schema } = await null;
|
|
2911
|
+
const { v: v6 } = await null;
|
|
2912
|
+
const { FilterGroup: FilterGroup2 } = await null;
|
|
2913
|
+
const schema = Schema.from("mongo_iter_complete").pk("_id", v6.string(), () => "x").field("age", v6.number()).build();
|
|
2914
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017" });
|
|
2915
|
+
let closeCalls = 0;
|
|
2916
|
+
const cursor = {
|
|
2917
|
+
async close() {
|
|
2918
|
+
closeCalls += 1;
|
|
2919
|
+
},
|
|
2920
|
+
async *[Symbol.asyncIterator]() {
|
|
2921
|
+
yield { _id: "u1", age: 30 };
|
|
2922
|
+
yield { _id: "u2", age: 40 };
|
|
2923
|
+
}
|
|
2924
|
+
};
|
|
2925
|
+
adapter.client.db = () => ({
|
|
2926
|
+
collection: () => ({
|
|
2927
|
+
find: () => cursor
|
|
2928
|
+
})
|
|
2929
|
+
});
|
|
2930
|
+
const rows = [];
|
|
2931
|
+
for await (const row of adapter.use(schema, { db: "testdb", col: "people" }).iterateMany(FilterGroup2.create())) {
|
|
2932
|
+
rows.push(row);
|
|
2933
|
+
}
|
|
2934
|
+
expect(rows).toEqual([{ _id: "u1", age: 30 }, { _id: "u2", age: 40 }]);
|
|
2935
|
+
expect(closeCalls).toBe(1);
|
|
2936
|
+
});
|
|
2937
|
+
test("nested session returns callback without starting new transaction", () => {
|
|
2938
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017" });
|
|
2939
|
+
expect(adapter.session).toBeTypeOf("function");
|
|
2940
|
+
});
|
|
2941
|
+
test("type-level: adapter declares all 6 canonical aggregate ops", () => {
|
|
2942
|
+
const _adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017" });
|
|
2943
|
+
expectTypeOf().toEqualTypeOf();
|
|
2944
|
+
});
|
|
2945
|
+
test("type-level: Repo.from with MongoDbAdapter enables aggregate method", async () => {
|
|
2946
|
+
const { Repo } = await null;
|
|
2947
|
+
const { Schema } = await null;
|
|
2948
|
+
const { v: v6 } = await null;
|
|
2949
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017" });
|
|
2950
|
+
const repo = Repo.from(adapter).resolve(() => ({ db: "test", col: "test" })).build();
|
|
2951
|
+
const _TestSchema = Schema.from("test").pk("id", v6.string(), () => "x").build();
|
|
2952
|
+
const _ref = repo.on(_TestSchema);
|
|
2953
|
+
expectTypeOf(_ref.aggregate).toBeFunction();
|
|
2954
|
+
});
|
|
2955
|
+
test("aggregate forwards compiled pipeline to collection.aggregate", async () => {
|
|
2956
|
+
const { Schema } = await null;
|
|
2957
|
+
const { v: v6 } = await null;
|
|
2958
|
+
const schema = Schema.from("mongo_agg").pk("_id", v6.string(), () => "x").field("amount", v6.number()).build();
|
|
2959
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017" });
|
|
2960
|
+
let capturedPipeline;
|
|
2961
|
+
const mockResults = [{ total: 3, revenue: 150 }];
|
|
2962
|
+
adapter.client.db = () => ({
|
|
2963
|
+
collection: () => ({
|
|
2964
|
+
aggregate: (pipeline, _opts) => {
|
|
2965
|
+
capturedPipeline = pipeline;
|
|
2966
|
+
return { toArray: async () => mockResults };
|
|
2967
|
+
}
|
|
2968
|
+
})
|
|
2969
|
+
});
|
|
2970
|
+
const result = await adapter.aggregate(schema, { db: "testdb", col: "orders" }, {
|
|
2971
|
+
aggregates: [
|
|
2972
|
+
{ fn: "count", alias: "total" },
|
|
2973
|
+
{ fn: "sum", field: "amount", alias: "revenue" }
|
|
2974
|
+
],
|
|
2975
|
+
groupBy: []
|
|
2976
|
+
});
|
|
2977
|
+
expect(capturedPipeline).toEqual([
|
|
2978
|
+
{ $group: { _id: null, total: { $sum: 1 }, revenue: { $sum: "$amount" } } },
|
|
2979
|
+
{ $project: { _id: 0, total: 1, revenue: 1 } }
|
|
2980
|
+
]);
|
|
2981
|
+
expect(result).toEqual(mockResults);
|
|
2982
|
+
});
|
|
2983
|
+
test("aggregate with where and groupBy produces correct pipeline", async () => {
|
|
2984
|
+
const { Schema } = await null;
|
|
2985
|
+
const { v: v6 } = await null;
|
|
2986
|
+
const { FilterGroup: FilterGroup2 } = await null;
|
|
2987
|
+
const schema = Schema.from("mongo_agg2").pk("_id", v6.string(), () => "x").field("region", v6.string()).field("amount", v6.number()).build();
|
|
2988
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017" });
|
|
2989
|
+
let capturedPipeline;
|
|
2990
|
+
adapter.client.db = () => ({
|
|
2991
|
+
collection: () => ({
|
|
2992
|
+
aggregate: (pipeline, _opts) => {
|
|
2993
|
+
capturedPipeline = pipeline;
|
|
2994
|
+
return { toArray: async () => [{ region: "US", total: 2 }] };
|
|
2995
|
+
}
|
|
2996
|
+
})
|
|
2997
|
+
});
|
|
2998
|
+
await adapter.aggregate(schema, { db: "testdb", col: "orders" }, {
|
|
2999
|
+
where: FilterGroup2.create().gt("amount", 10),
|
|
3000
|
+
aggregates: [{ fn: "count", alias: "total" }],
|
|
3001
|
+
groupBy: ["region"]
|
|
3002
|
+
});
|
|
3003
|
+
expect(capturedPipeline).toEqual([
|
|
3004
|
+
{ $match: { amount: { $gt: 10 } } },
|
|
3005
|
+
{ $group: { _id: { region: "$region" }, total: { $sum: 1 } } },
|
|
3006
|
+
{ $project: { _id: 0, region: "$_id.region", total: 1 } }
|
|
3007
|
+
]);
|
|
3008
|
+
});
|
|
3009
|
+
test("auto-wires Instance hooks for connect/disconnect", async () => {
|
|
3010
|
+
const { Instance: Inst } = await null;
|
|
3011
|
+
const { vi } = await null;
|
|
3012
|
+
const onSpy = vi.spyOn(Inst, "on").mockImplementation(() => {
|
|
3013
|
+
});
|
|
3014
|
+
MongoDbAdapter.create({ uri: "mongodb://localhost:27017" });
|
|
3015
|
+
expect(onSpy).toHaveBeenCalledWith("start", expect.any(Function), expect.objectContaining({ class: MongoDbAdapter }));
|
|
3016
|
+
expect(onSpy).toHaveBeenCalledWith("close", expect.any(Function), expect.objectContaining({ class: MongoDbAdapter }));
|
|
3017
|
+
onSpy.mockRestore();
|
|
3018
|
+
});
|
|
3019
|
+
});
|
|
3020
|
+
describe("MongoDbAdapter: migrations", () => {
|
|
3021
|
+
function mockMongoDb(adapter) {
|
|
3022
|
+
const state = {
|
|
3023
|
+
migrations: /* @__PURE__ */ new Map(),
|
|
3024
|
+
collections: /* @__PURE__ */ new Map()
|
|
3025
|
+
};
|
|
3026
|
+
const getCol = (name) => {
|
|
3027
|
+
if (!state.collections.has(name)) {
|
|
3028
|
+
state.collections.set(name, { indexes: [] });
|
|
3029
|
+
}
|
|
3030
|
+
return state.collections.get(name);
|
|
3031
|
+
};
|
|
3032
|
+
adapter.client.db = () => ({
|
|
3033
|
+
collection: (name) => {
|
|
3034
|
+
if (name === MIGRATION_TRACKER_COLLECTION) {
|
|
3035
|
+
return {
|
|
3036
|
+
find: () => ({ toArray: async () => [...state.migrations.values()] }),
|
|
3037
|
+
insertOne: async (doc) => {
|
|
3038
|
+
state.migrations.set(doc._id, doc);
|
|
3039
|
+
}
|
|
3040
|
+
};
|
|
3041
|
+
}
|
|
3042
|
+
const col = getCol(name);
|
|
3043
|
+
return {
|
|
3044
|
+
createIndex: async (fields, opts) => {
|
|
3045
|
+
col.indexes.push({ name: opts.name, key: fields, unique: opts.unique });
|
|
3046
|
+
},
|
|
3047
|
+
dropIndex: async (indexName) => {
|
|
3048
|
+
col.indexes = col.indexes.filter((i) => i.name !== indexName);
|
|
3049
|
+
},
|
|
3050
|
+
listIndexes: () => ({
|
|
3051
|
+
toArray: async () => [
|
|
3052
|
+
{ name: "_id_", key: { _id: 1 } },
|
|
3053
|
+
...col.indexes
|
|
3054
|
+
]
|
|
3055
|
+
})
|
|
3056
|
+
};
|
|
3057
|
+
},
|
|
3058
|
+
listCollections: () => ({
|
|
3059
|
+
toArray: async () => [...state.collections.keys()].map((name) => ({ name }))
|
|
3060
|
+
})
|
|
3061
|
+
});
|
|
3062
|
+
return state;
|
|
3063
|
+
}
|
|
3064
|
+
test("loadMigrations reads from equipped_migrations collection", async () => {
|
|
3065
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017/testdb" });
|
|
3066
|
+
const state = mockMongoDb(adapter);
|
|
3067
|
+
state.migrations.set("0001", { _id: "0001", appliedAt: 1e3 });
|
|
3068
|
+
state.migrations.set("0002", { _id: "0002", appliedAt: 2e3 });
|
|
3069
|
+
const result = await adapter.loadMigrations();
|
|
3070
|
+
expect(result).toEqual([
|
|
3071
|
+
{ id: "0001", appliedAt: 1e3 },
|
|
3072
|
+
{ id: "0002", appliedAt: 2e3 }
|
|
3073
|
+
]);
|
|
3074
|
+
});
|
|
3075
|
+
test("loadMigrations returns empty array when no migrations exist", async () => {
|
|
3076
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017/testdb" });
|
|
3077
|
+
mockMongoDb(adapter);
|
|
3078
|
+
const result = await adapter.loadMigrations();
|
|
3079
|
+
expect(result).toEqual([]);
|
|
3080
|
+
});
|
|
3081
|
+
test("recordMigration inserts into equipped_migrations with _id", async () => {
|
|
3082
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017/testdb" });
|
|
3083
|
+
const state = mockMongoDb(adapter);
|
|
3084
|
+
await adapter.recordMigration("0001", 1234);
|
|
3085
|
+
expect(state.migrations.has("0001")).toBe(true);
|
|
3086
|
+
expect(state.migrations.get("0001")).toEqual({ _id: "0001", appliedAt: 1234 });
|
|
3087
|
+
});
|
|
3088
|
+
test("applyAddIndex creates index with specified name and fields", async () => {
|
|
3089
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017/testdb" });
|
|
3090
|
+
const state = mockMongoDb(adapter);
|
|
3091
|
+
await adapter.applyAddIndex({ kind: "addIndex", table: "users", on: ["email"], unique: true, name: "users_email_unique" });
|
|
3092
|
+
const col = state.collections.get("users");
|
|
3093
|
+
expect(col.indexes).toHaveLength(1);
|
|
3094
|
+
expect(col.indexes[0]).toEqual({ name: "users_email_unique", key: { email: 1 }, unique: true });
|
|
3095
|
+
});
|
|
3096
|
+
test("applyAddIndex auto-derives name when absent", async () => {
|
|
3097
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017/testdb" });
|
|
3098
|
+
const state = mockMongoDb(adapter);
|
|
3099
|
+
await adapter.applyAddIndex({ kind: "addIndex", table: "users", on: ["email"] });
|
|
3100
|
+
const col = state.collections.get("users");
|
|
3101
|
+
expect(col.indexes[0].name).toBe("users_email_idx");
|
|
3102
|
+
});
|
|
3103
|
+
test("applyAddIndex auto-derived compound index name", async () => {
|
|
3104
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017/testdb" });
|
|
3105
|
+
const state = mockMongoDb(adapter);
|
|
3106
|
+
await adapter.applyAddIndex({ kind: "addIndex", table: "orders", on: ["userId", "createdAt"] });
|
|
3107
|
+
const col = state.collections.get("orders");
|
|
3108
|
+
expect(col.indexes[0].name).toBe("orders_userId_createdAt_idx");
|
|
3109
|
+
expect(col.indexes[0].key).toEqual({ userId: 1, createdAt: 1 });
|
|
3110
|
+
});
|
|
3111
|
+
test("applyDropIndex finds and drops index by scanning collections", async () => {
|
|
3112
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017/testdb" });
|
|
3113
|
+
const state = mockMongoDb(adapter);
|
|
3114
|
+
state.collections.set("users", { indexes: [{ name: "users_email_idx", key: { email: 1 }, unique: true }] });
|
|
3115
|
+
await adapter.applyDropIndex({ kind: "dropIndex", name: "users_email_idx" });
|
|
3116
|
+
expect(state.collections.get("users").indexes).toHaveLength(0);
|
|
3117
|
+
});
|
|
3118
|
+
test("applyDropIndex is a no-op when index not found", async () => {
|
|
3119
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017/testdb" });
|
|
3120
|
+
mockMongoDb(adapter);
|
|
3121
|
+
await adapter.applyDropIndex({ kind: "dropIndex", name: "nonexistent_idx" });
|
|
3122
|
+
});
|
|
3123
|
+
test("introspect returns DiscoveredSchema with empty fields, pk, and foreignKeys", async () => {
|
|
3124
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017/testdb" });
|
|
3125
|
+
const state = mockMongoDb(adapter);
|
|
3126
|
+
state.collections.set("users", { indexes: [] });
|
|
3127
|
+
state.collections.set("posts", { indexes: [] });
|
|
3128
|
+
const schemas = await adapter.introspect();
|
|
3129
|
+
expect(schemas).toHaveLength(2);
|
|
3130
|
+
for (const s of schemas) {
|
|
3131
|
+
expect(s.pk).toBeUndefined();
|
|
3132
|
+
expect(s.fields).toEqual([]);
|
|
3133
|
+
expect(s.foreignKeys).toEqual([]);
|
|
3134
|
+
}
|
|
3135
|
+
});
|
|
3136
|
+
test("introspect skips _id_ index", async () => {
|
|
3137
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017/testdb" });
|
|
3138
|
+
const state = mockMongoDb(adapter);
|
|
3139
|
+
state.collections.set("users", { indexes: [{ name: "users_email_idx", key: { email: 1 }, unique: false }] });
|
|
3140
|
+
const schemas = await adapter.introspect();
|
|
3141
|
+
expect(schemas).toHaveLength(1);
|
|
3142
|
+
expect(schemas[0].indexes).toHaveLength(1);
|
|
3143
|
+
expect(schemas[0].indexes[0].name).toBe("users_email_idx");
|
|
3144
|
+
});
|
|
3145
|
+
test("introspect skips tracker collection", async () => {
|
|
3146
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017/testdb" });
|
|
3147
|
+
const state = mockMongoDb(adapter);
|
|
3148
|
+
state.collections.set("users", { indexes: [] });
|
|
3149
|
+
state.collections.set(MIGRATION_TRACKER_COLLECTION, { indexes: [] });
|
|
3150
|
+
const schemas = await adapter.introspect();
|
|
3151
|
+
expect(schemas).toHaveLength(1);
|
|
3152
|
+
expect(schemas[0].name).toBe("users");
|
|
3153
|
+
});
|
|
3154
|
+
test("introspect maps index keys to on array and unique flag", async () => {
|
|
3155
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017/testdb" });
|
|
3156
|
+
const state = mockMongoDb(adapter);
|
|
3157
|
+
state.collections.set("orders", {
|
|
3158
|
+
indexes: [
|
|
3159
|
+
{ name: "orders_userId_createdAt_idx", key: { userId: 1, createdAt: 1 }, unique: false },
|
|
3160
|
+
{ name: "orders_email_unique", key: { email: 1 }, unique: true }
|
|
3161
|
+
]
|
|
3162
|
+
});
|
|
3163
|
+
const schemas = await adapter.introspect();
|
|
3164
|
+
expect(schemas[0].indexes).toEqual([
|
|
3165
|
+
{ name: "orders_userId_createdAt_idx", on: ["userId", "createdAt"], unique: false },
|
|
3166
|
+
{ name: "orders_email_unique", on: ["email"], unique: true }
|
|
3167
|
+
]);
|
|
3168
|
+
});
|
|
3169
|
+
test("adapter does not implement acquireMigrationLock", () => {
|
|
3170
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017/testdb" });
|
|
3171
|
+
expect(adapter.acquireMigrationLock).toBeUndefined();
|
|
3172
|
+
});
|
|
3173
|
+
test("adapter does not implement DDL apply methods", () => {
|
|
3174
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017/testdb" });
|
|
3175
|
+
expect(adapter.applyCreateTable).toBeUndefined();
|
|
3176
|
+
expect(adapter.applyDropTable).toBeUndefined();
|
|
3177
|
+
expect(adapter.applyAddField).toBeUndefined();
|
|
3178
|
+
expect(adapter.applyDropField).toBeUndefined();
|
|
3179
|
+
expect(adapter.applyModifyField).toBeUndefined();
|
|
3180
|
+
expect(adapter.applyRenameTable).toBeUndefined();
|
|
3181
|
+
expect(adapter.applyRenameField).toBeUndefined();
|
|
3182
|
+
expect(adapter.applyAddForeignKey).toBeUndefined();
|
|
3183
|
+
expect(adapter.applyDropForeignKey).toBeUndefined();
|
|
3184
|
+
});
|
|
3185
|
+
test("type-level: ChangeFor<MongoDbAdapter> excludes DDL variants", () => {
|
|
3186
|
+
expectTypeOf().not.toBeNever();
|
|
3187
|
+
expectTypeOf().not.toBeNever();
|
|
3188
|
+
expectTypeOf().not.toBeNever();
|
|
3189
|
+
expectTypeOf().toBeNever();
|
|
3190
|
+
expectTypeOf().toBeNever();
|
|
3191
|
+
expectTypeOf().toBeNever();
|
|
3192
|
+
expectTypeOf().toBeNever();
|
|
3193
|
+
expectTypeOf().toBeNever();
|
|
3194
|
+
expectTypeOf().toBeNever();
|
|
3195
|
+
expectTypeOf().toBeNever();
|
|
3196
|
+
expectTypeOf().toBeNever();
|
|
3197
|
+
expectTypeOf().toBeNever();
|
|
3198
|
+
});
|
|
3199
|
+
test("type-level: Migrator.from(repo, adapter).build() works without withoutLock", async () => {
|
|
3200
|
+
const { Migrator } = await null;
|
|
3201
|
+
const { Repo } = await null;
|
|
3202
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017/testdb" });
|
|
3203
|
+
const repo = Repo.from(adapter).resolve(() => ({ db: "test", col: "test" })).build();
|
|
3204
|
+
const step = Migrator.from(repo, adapter).migrations([]);
|
|
3205
|
+
expectTypeOf(step).toHaveProperty("build");
|
|
3206
|
+
expectTypeOf(step).toHaveProperty("withoutLock");
|
|
3207
|
+
});
|
|
3208
|
+
test("type-level: Migrator.from(repo, adapter).withoutLock().build() also works", async () => {
|
|
3209
|
+
const { Migrator } = await null;
|
|
3210
|
+
const { Repo } = await null;
|
|
3211
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017/testdb" });
|
|
3212
|
+
const repo = Repo.from(adapter).resolve(() => ({ db: "test", col: "test" })).build();
|
|
3213
|
+
const migrator = Migrator.from(repo, adapter).migrations([]).withoutLock().build();
|
|
3214
|
+
expect(migrator).toBeDefined();
|
|
3215
|
+
});
|
|
3216
|
+
test("end-to-end: Migrator runs addIndex + dropIndex + execute changes", async () => {
|
|
3217
|
+
const { Migrator } = await null;
|
|
3218
|
+
const { Repo } = await null;
|
|
3219
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017/testdb" });
|
|
3220
|
+
const state = mockMongoDb(adapter);
|
|
3221
|
+
const repo = Repo.from(adapter).resolve(() => ({ db: "test", col: "test" })).build();
|
|
3222
|
+
let executeRan = false;
|
|
3223
|
+
const migrations = [
|
|
3224
|
+
{
|
|
3225
|
+
id: "0001-add-idx",
|
|
3226
|
+
tx: false,
|
|
3227
|
+
changes: [
|
|
3228
|
+
{ kind: "addIndex", table: "users", on: ["email"], unique: true }
|
|
3229
|
+
]
|
|
3230
|
+
},
|
|
3231
|
+
{
|
|
3232
|
+
id: "0002-backfill",
|
|
3233
|
+
tx: false,
|
|
3234
|
+
changes: [
|
|
3235
|
+
{ kind: "execute", up: async () => {
|
|
3236
|
+
executeRan = true;
|
|
3237
|
+
} }
|
|
3238
|
+
]
|
|
3239
|
+
},
|
|
3240
|
+
{
|
|
3241
|
+
id: "0003-drop-idx",
|
|
3242
|
+
tx: false,
|
|
3243
|
+
changes: [
|
|
3244
|
+
{ kind: "dropIndex", name: "users_email_idx" }
|
|
3245
|
+
]
|
|
3246
|
+
}
|
|
3247
|
+
];
|
|
3248
|
+
const migrator = Migrator.from(repo, adapter).migrations(migrations).build();
|
|
3249
|
+
const result = await migrator.up();
|
|
3250
|
+
expect(result.ran).toEqual(["0001-add-idx", "0002-backfill", "0003-drop-idx"]);
|
|
3251
|
+
expect(executeRan).toBe(true);
|
|
3252
|
+
expect(state.migrations.size).toBe(3);
|
|
3253
|
+
expect(state.collections.get("users").indexes).toHaveLength(0);
|
|
3254
|
+
});
|
|
3255
|
+
test("introspect round-trip: addIndex then introspect sees the index", async () => {
|
|
3256
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017/testdb" });
|
|
3257
|
+
const state = mockMongoDb(adapter);
|
|
3258
|
+
state.collections.set("users", {
|
|
3259
|
+
indexes: [{ name: "users_email_idx", key: { email: 1 }, unique: true }]
|
|
3260
|
+
});
|
|
3261
|
+
const schemas = await adapter.introspect();
|
|
3262
|
+
expect(schemas).toHaveLength(1);
|
|
3263
|
+
expect(schemas[0].name).toBe("users");
|
|
3264
|
+
expect(schemas[0].pk).toBeUndefined();
|
|
3265
|
+
expect(schemas[0].fields).toEqual([]);
|
|
3266
|
+
expect(schemas[0].foreignKeys).toEqual([]);
|
|
3267
|
+
expect(schemas[0].indexes).toEqual([
|
|
3268
|
+
{ name: "users_email_idx", on: ["email"], unique: true }
|
|
3269
|
+
]);
|
|
3270
|
+
});
|
|
3271
|
+
test("data backfill via execute change", async () => {
|
|
3272
|
+
const { Migrator } = await null;
|
|
3273
|
+
const { Repo } = await null;
|
|
3274
|
+
const adapter = MongoDbAdapter.create({ uri: "mongodb://localhost:27017/testdb" });
|
|
3275
|
+
mockMongoDb(adapter);
|
|
3276
|
+
const repo = Repo.from(adapter).resolve(() => ({ db: "test", col: "test" })).build();
|
|
3277
|
+
const backfilledData = [];
|
|
3278
|
+
const m = {
|
|
3279
|
+
id: "0001-backfill",
|
|
3280
|
+
tx: false,
|
|
3281
|
+
changes: [{
|
|
3282
|
+
kind: "execute",
|
|
3283
|
+
up: async () => {
|
|
3284
|
+
backfilledData.push("user-1-normalized");
|
|
3285
|
+
backfilledData.push("user-2-normalized");
|
|
3286
|
+
}
|
|
3287
|
+
}]
|
|
3288
|
+
};
|
|
3289
|
+
const migrator = Migrator.from(repo, adapter).migrations([m]).build();
|
|
3290
|
+
await migrator.up();
|
|
3291
|
+
expect(backfilledData).toEqual(["user-1-normalized", "user-2-normalized"]);
|
|
3292
|
+
});
|
|
3293
|
+
});
|
|
3294
|
+
}
|
|
3295
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3296
|
+
0 && (module.exports = {
|
|
3297
|
+
MongoDbAdapter
|
|
3298
|
+
});
|
|
3299
|
+
//# sourceMappingURL=index.cjs.map
|