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