equipped 5.3.1 → 5.3.2

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.
Files changed (191) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/cjs/cache/adapters/in-memory/index.cjs +155 -155
  3. package/dist/cjs/cache/adapters/in-memory/index.cjs.map +1 -1
  4. package/dist/cjs/cache/adapters/in-memory/index.min.cjs +3 -3
  5. package/dist/cjs/cache/adapters/in-memory/index.min.cjs.map +1 -1
  6. package/dist/cjs/dbs/adapters/mongodb/index.cjs.map +1 -1
  7. package/dist/cjs/dbs/adapters/mongodb/index.min.cjs +2 -2
  8. package/dist/cjs/dbs/adapters/mongodb/index.min.cjs.map +1 -1
  9. package/dist/cjs/dbs/index.cjs +181 -179
  10. package/dist/cjs/dbs/index.cjs.map +1 -1
  11. package/dist/cjs/dbs/index.min.cjs +3 -3
  12. package/dist/cjs/dbs/index.min.cjs.map +1 -1
  13. package/dist/cjs/events/adapters/kafka/index.cjs.map +1 -1
  14. package/dist/cjs/events/adapters/kafka/index.min.cjs +2 -2
  15. package/dist/cjs/events/adapters/kafka/index.min.cjs.map +1 -1
  16. package/dist/cjs/events/adapters/rabbitmq/index.cjs +179 -179
  17. package/dist/cjs/events/adapters/rabbitmq/index.cjs.map +1 -1
  18. package/dist/cjs/events/adapters/rabbitmq/index.min.cjs +3 -3
  19. package/dist/cjs/events/adapters/rabbitmq/index.min.cjs.map +1 -1
  20. package/dist/cjs/orm/adapters/mongodb/index.cjs +144 -144
  21. package/dist/cjs/orm/adapters/mongodb/index.cjs.map +1 -1
  22. package/dist/cjs/orm/adapters/mongodb/index.min.cjs +1 -1
  23. package/dist/cjs/orm/adapters/mongodb/index.min.cjs.map +1 -1
  24. package/dist/cjs/orm/adapters/postgresql/index.cjs +144 -215
  25. package/dist/cjs/orm/adapters/postgresql/index.cjs.map +1 -1
  26. package/dist/cjs/orm/adapters/postgresql/index.min.cjs +11 -11
  27. package/dist/cjs/orm/adapters/postgresql/index.min.cjs.map +1 -1
  28. package/dist/cjs/server/adapters/express/index.cjs +291 -225
  29. package/dist/cjs/server/adapters/express/index.cjs.map +1 -1
  30. package/dist/cjs/server/adapters/express/index.min.cjs +7 -7
  31. package/dist/cjs/server/adapters/express/index.min.cjs.map +1 -1
  32. package/dist/cjs/server/adapters/fastify/index.cjs +276 -210
  33. package/dist/cjs/server/adapters/fastify/index.cjs.map +1 -1
  34. package/dist/cjs/server/adapters/fastify/index.min.cjs +7 -7
  35. package/dist/cjs/server/adapters/fastify/index.min.cjs.map +1 -1
  36. package/dist/cjs/server/index.cjs +81 -15
  37. package/dist/cjs/server/index.cjs.map +1 -1
  38. package/dist/cjs/server/index.min.cjs +7 -7
  39. package/dist/cjs/server/index.min.cjs.map +1 -1
  40. package/dist/cjs/utilities/index.cjs +0 -5
  41. package/dist/cjs/utilities/index.cjs.map +1 -1
  42. package/dist/cjs/utilities/index.min.cjs +2 -2
  43. package/dist/cjs/utilities/index.min.cjs.map +1 -1
  44. package/dist/esm/cache/adapters/in-memory/index.min.mjs +1 -1
  45. package/dist/esm/cache/adapters/in-memory/index.min.mjs.map +1 -1
  46. package/dist/esm/cache/adapters/in-memory/index.mjs +6 -3
  47. package/dist/esm/cache/adapters/in-memory/index.mjs.map +1 -1
  48. package/dist/esm/cache/adapters/redis/index.min.mjs +1 -1
  49. package/dist/esm/cache/adapters/redis/index.mjs +5 -3
  50. package/dist/esm/cache/index.min.mjs +1 -1
  51. package/dist/esm/cache/index.mjs +2 -2
  52. package/dist/esm/{chunk-5PZACGPI.mjs → chunk-26BOI6IL.mjs} +7 -5
  53. package/dist/esm/{chunk-5PZACGPI.mjs.map → chunk-26BOI6IL.mjs.map} +1 -1
  54. package/dist/esm/{chunk-7SQ5JQYR.min.mjs → chunk-AODOD22O.min.mjs} +2 -2
  55. package/dist/esm/chunk-APSDCRY5.min.mjs +32 -0
  56. package/dist/esm/chunk-APSDCRY5.min.mjs.map +1 -0
  57. package/dist/esm/{chunk-A4P7P6BU.mjs → chunk-CEYT5YBA.mjs} +157 -347
  58. package/dist/esm/chunk-CEYT5YBA.mjs.map +1 -0
  59. package/dist/esm/chunk-CU2HU5UE.min.mjs +2 -0
  60. package/dist/esm/chunk-CU2HU5UE.min.mjs.map +1 -0
  61. package/dist/esm/chunk-DAK7OKSH.min.mjs +2 -0
  62. package/dist/esm/{chunk-MQXTTWPS.min.mjs.map → chunk-DAK7OKSH.min.mjs.map} +1 -1
  63. package/dist/esm/{chunk-IRYCN6PI.min.mjs → chunk-DAUQA32Q.min.mjs} +2 -2
  64. package/dist/esm/{chunk-BWUFPMVG.mjs → chunk-FQYRVHQT.mjs} +2 -2
  65. package/dist/esm/chunk-NEF3UYFA.mjs +144 -0
  66. package/dist/esm/chunk-NEF3UYFA.mjs.map +1 -0
  67. package/dist/esm/chunk-NNFUYHQ7.mjs +135 -0
  68. package/dist/esm/chunk-NNFUYHQ7.mjs.map +1 -0
  69. package/dist/esm/{chunk-7CP7ZE2O.mjs → chunk-PCEHFZ6G.mjs} +9 -5
  70. package/dist/esm/{chunk-7CP7ZE2O.mjs.map → chunk-PCEHFZ6G.mjs.map} +1 -1
  71. package/dist/esm/{chunk-P4PRHHIY.min.mjs → chunk-QCCDJLBH.min.mjs} +2 -2
  72. package/dist/esm/{chunk-3TP7NINX.mjs → chunk-RIXTQQUU.mjs} +2 -2
  73. package/dist/esm/{chunk-LKS5ZE45.mjs → chunk-RPPYCWXW.mjs} +2 -2
  74. package/dist/esm/{chunk-HXFNN6RI.mjs → chunk-T2O72JBW.mjs} +3 -3
  75. package/dist/esm/{chunk-TSVHZZE4.mjs → chunk-VQO4MSEC.mjs} +5 -5
  76. package/dist/esm/{chunk-676VIABN.min.mjs → chunk-X7VNAGLR.min.mjs} +2 -2
  77. package/dist/esm/chunk-XTXAEBXY.min.mjs +2 -0
  78. package/dist/esm/{chunk-7NF7PV6S.min.mjs.map → chunk-XTXAEBXY.min.mjs.map} +1 -1
  79. package/dist/esm/{chunk-2GGMLKRP.min.mjs → chunk-YDQW6ETS.min.mjs} +2 -2
  80. package/dist/esm/chunk-ZDBDR3IB.min.mjs +2 -0
  81. package/dist/esm/chunk-ZDBDR3IB.min.mjs.map +1 -0
  82. package/dist/esm/dbs/adapters/mongodb/index.min.mjs +1 -1
  83. package/dist/esm/dbs/adapters/mongodb/index.min.mjs.map +1 -1
  84. package/dist/esm/dbs/adapters/mongodb/index.mjs +8 -5
  85. package/dist/esm/dbs/adapters/mongodb/index.mjs.map +1 -1
  86. package/dist/esm/dbs/index.min.mjs +1 -1
  87. package/dist/esm/dbs/index.mjs +5 -3
  88. package/dist/esm/errors/index.min.mjs +1 -1
  89. package/dist/esm/errors/index.mjs +1 -1
  90. package/dist/esm/events/adapters/kafka/index.min.mjs +1 -1
  91. package/dist/esm/events/adapters/kafka/index.mjs +4 -2
  92. package/dist/esm/events/adapters/rabbitmq/index.min.mjs +1 -1
  93. package/dist/esm/events/adapters/rabbitmq/index.min.mjs.map +1 -1
  94. package/dist/esm/events/adapters/rabbitmq/index.mjs +7 -3
  95. package/dist/esm/events/adapters/rabbitmq/index.mjs.map +1 -1
  96. package/dist/esm/index.min.mjs +1 -1
  97. package/dist/esm/index.mjs +1 -1
  98. package/dist/esm/instance/index.min.mjs +1 -1
  99. package/dist/esm/instance/index.mjs +1 -1
  100. package/dist/esm/{introspection-YJF3QAUK.mjs → introspection-FGDVYTPP.mjs} +3 -3
  101. package/dist/esm/introspection-UDWDS6DV.min.mjs +2 -0
  102. package/dist/esm/jobs/adapters/redis/index.min.mjs +1 -1
  103. package/dist/esm/jobs/adapters/redis/index.min.mjs.map +1 -1
  104. package/dist/esm/jobs/adapters/redis/index.mjs +10 -6
  105. package/dist/esm/jobs/adapters/redis/index.mjs.map +1 -1
  106. package/dist/esm/orm/adapters/in-memory/index.min.mjs +1 -1
  107. package/dist/esm/orm/adapters/in-memory/index.mjs +4 -3
  108. package/dist/esm/orm/adapters/json/index.min.mjs +1 -1
  109. package/dist/esm/orm/adapters/json/index.min.mjs.map +1 -1
  110. package/dist/esm/orm/adapters/json/index.mjs +6 -5
  111. package/dist/esm/orm/adapters/json/index.mjs.map +1 -1
  112. package/dist/esm/orm/adapters/mongodb/index.min.mjs +1 -1
  113. package/dist/esm/orm/adapters/mongodb/index.min.mjs.map +1 -1
  114. package/dist/esm/orm/adapters/mongodb/index.mjs +6 -4
  115. package/dist/esm/orm/adapters/mongodb/index.mjs.map +1 -1
  116. package/dist/esm/orm/adapters/postgresql/index.min.mjs +2 -2
  117. package/dist/esm/orm/adapters/postgresql/index.min.mjs.map +1 -1
  118. package/dist/esm/orm/adapters/postgresql/index.mjs +8 -5
  119. package/dist/esm/orm/adapters/postgresql/index.mjs.map +1 -1
  120. package/dist/esm/orm/index.min.mjs +1 -1
  121. package/dist/esm/orm/index.mjs +3 -3
  122. package/dist/esm/server/adapters/express/index.min.mjs +1 -1
  123. package/dist/esm/server/adapters/express/index.min.mjs.map +1 -1
  124. package/dist/esm/server/adapters/express/index.mjs +7 -3
  125. package/dist/esm/server/adapters/express/index.mjs.map +1 -1
  126. package/dist/esm/server/adapters/fastify/index.min.mjs +1 -1
  127. package/dist/esm/server/adapters/fastify/index.min.mjs.map +1 -1
  128. package/dist/esm/server/adapters/fastify/index.mjs +7 -3
  129. package/dist/esm/server/adapters/fastify/index.mjs.map +1 -1
  130. package/dist/esm/server/index.min.mjs +1 -1
  131. package/dist/esm/server/index.mjs +1 -1
  132. package/dist/esm/utilities/index.min.mjs +1 -1
  133. package/dist/esm/utilities/index.mjs +5 -4
  134. package/dist/esm/validations/index.min.mjs +1 -1
  135. package/dist/esm/validations/index.mjs +1 -1
  136. package/dist/types/{base-BDTBLZ9a.d.ts → base-qDndMBf8.d.ts} +2 -2
  137. package/dist/types/cache/adapters/in-memory/index.js +7 -4
  138. package/dist/types/cache/adapters/redis/index.js +5 -3
  139. package/dist/types/cache/index.js +2 -2
  140. package/dist/types/{chunk-4DH3POYM.js → chunk-35CDCTLR.js} +4 -4
  141. package/dist/types/{chunk-J3TGB5YQ.js → chunk-4YQA5GPT.js} +8 -4
  142. package/dist/types/{chunk-YLJNBL6J.js → chunk-AQIV47BY.js} +6 -4
  143. package/dist/types/{chunk-TT5EORDK.js → chunk-BKDWUJRG.js} +156 -346
  144. package/dist/types/{chunk-5Z26ZURE.js → chunk-DA2VYP7D.js} +1 -1
  145. package/dist/types/{chunk-LPB5LS7A.js → chunk-GDMGPX4C.js} +1 -1
  146. package/dist/types/chunk-H5V6ADFD.js +134 -0
  147. package/dist/types/{chunk-IZDJDGOV.js → chunk-OOKJEMQI.js} +2 -2
  148. package/dist/types/chunk-PPD4WDG6.js +143 -0
  149. package/dist/types/{chunk-55CTVK7R.js → chunk-T7K7PO6G.js} +1 -1
  150. package/dist/types/dbs/adapters/mongodb/index.js +8 -5
  151. package/dist/types/dbs/index.js +5 -3
  152. package/dist/types/errors/index.js +1 -1
  153. package/dist/types/events/adapters/kafka/index.js +4 -2
  154. package/dist/types/events/adapters/rabbitmq/index.js +7 -3
  155. package/dist/types/index.js +1 -1
  156. package/dist/types/instance/index.js +1 -1
  157. package/dist/types/{introspection-EHGM4RMO.js → introspection-FSNS33CQ.js} +2 -2
  158. package/dist/types/jobs/adapters/redis/index.js +10 -6
  159. package/dist/types/orm/adapters/in-memory/index.js +4 -3
  160. package/dist/types/orm/adapters/json/index.js +6 -5
  161. package/dist/types/orm/adapters/mongodb/index.js +6 -4
  162. package/dist/types/orm/adapters/postgresql/index.js +8 -5
  163. package/dist/types/orm/index.js +3 -3
  164. package/dist/types/server/adapters/express/index.d.ts +1 -1
  165. package/dist/types/server/adapters/express/index.js +7 -3
  166. package/dist/types/server/adapters/fastify/index.d.ts +1 -1
  167. package/dist/types/server/adapters/fastify/index.js +7 -3
  168. package/dist/types/server/index.d.ts +2 -2
  169. package/dist/types/server/index.js +1 -1
  170. package/dist/types/utilities/index.d.ts +1 -2
  171. package/dist/types/utilities/index.js +5 -4
  172. package/dist/types/validations/index.js +1 -1
  173. package/package.json +1 -1
  174. package/dist/esm/chunk-7NF7PV6S.min.mjs +0 -2
  175. package/dist/esm/chunk-A4P7P6BU.mjs.map +0 -1
  176. package/dist/esm/chunk-IN5YKIJ7.min.mjs +0 -32
  177. package/dist/esm/chunk-IN5YKIJ7.min.mjs.map +0 -1
  178. package/dist/esm/chunk-MQXTTWPS.min.mjs +0 -2
  179. package/dist/esm/introspection-THKMXV5V.min.mjs +0 -2
  180. /package/dist/esm/{chunk-7SQ5JQYR.min.mjs.map → chunk-AODOD22O.min.mjs.map} +0 -0
  181. /package/dist/esm/{chunk-IRYCN6PI.min.mjs.map → chunk-DAUQA32Q.min.mjs.map} +0 -0
  182. /package/dist/esm/{chunk-BWUFPMVG.mjs.map → chunk-FQYRVHQT.mjs.map} +0 -0
  183. /package/dist/esm/{chunk-P4PRHHIY.min.mjs.map → chunk-QCCDJLBH.min.mjs.map} +0 -0
  184. /package/dist/esm/{chunk-3TP7NINX.mjs.map → chunk-RIXTQQUU.mjs.map} +0 -0
  185. /package/dist/esm/{chunk-LKS5ZE45.mjs.map → chunk-RPPYCWXW.mjs.map} +0 -0
  186. /package/dist/esm/{chunk-HXFNN6RI.mjs.map → chunk-T2O72JBW.mjs.map} +0 -0
  187. /package/dist/esm/{chunk-TSVHZZE4.mjs.map → chunk-VQO4MSEC.mjs.map} +0 -0
  188. /package/dist/esm/{chunk-676VIABN.min.mjs.map → chunk-X7VNAGLR.min.mjs.map} +0 -0
  189. /package/dist/esm/{chunk-2GGMLKRP.min.mjs.map → chunk-YDQW6ETS.min.mjs.map} +0 -0
  190. /package/dist/esm/{introspection-THKMXV5V.min.mjs.map → introspection-FGDVYTPP.mjs.map} +0 -0
  191. /package/dist/esm/{introspection-YJF3QAUK.mjs.map → introspection-UDWDS6DV.min.mjs.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/jobs/adapters/redis/index.ts"],"sourcesContent":["import { Queue, Worker } from 'bullmq'\nimport { v } from 'valleyed'\n\nimport { RedisCache, redisConfigPipe } from '../../../cache/adapters/redis'\nimport { Instance } from '../../../instance'\nimport { Random, configurable } from '../../../utilities'\nimport { Job, JobNames, type Cron, type DelayedJobEvent, type RepeatableJobEvent } from '../base'\n\nexport const redisJobsConfigPipe = () =>\n\tv.meta(\n\t\tv.object({\n\t\t\tredisConfig: redisConfigPipe(),\n\t\t\tqueueName: v.string(),\n\t\t}),\n\t\t{ title: 'Redis Jobs Config', $refId: 'RedisJobsConfig' },\n\t)\n\nexport class RedisJob extends configurable(redisJobsConfigPipe, Job) {\n\t#queue: Queue\n\n\tprotected constructor(config: typeof RedisJob.Config) {\n\t\tsuper(config)\n\n\t\tconst redisCache = (RedisCache.create as any)(config.redisConfig, {\n\t\t\tmaxRetriesPerRequest: null,\n\t\t\tenableReadyCheck: false,\n\t\t})\n\t\tconst queueName = Instance.get().getScopedName(config.queueName)\n\t\tthis.#queue = new Queue(queueName, { connection: redisCache.connectionOptions, skipVersionCheck: true })\n\n\t\tconst worker = new Worker(\n\t\t\tqueueName,\n\t\t\tasync (job) => {\n\t\t\t\tswitch (job.name) {\n\t\t\t\t\tcase JobNames.DelayedJob:\n\t\t\t\t\t\treturn (this.callbacks.onDelayed as any)?.(job.data)\n\t\t\t\t\tcase JobNames.CronJob:\n\t\t\t\t\t\treturn (this.callbacks.onCron as any)?.(job.data.type)\n\t\t\t\t\tcase JobNames.RepeatableJob:\n\t\t\t\t\t\treturn (this.callbacks.onRepeatable as any)?.(job.data)\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ connection: redisCache.connectionOptions, autorun: false, skipVersionCheck: true },\n\t\t)\n\n\t\tInstance.on('start', async () => {\n\t\t\tawait this.#cleanup()\n\t\t\tawait Promise.all(this.crons.map(({ cron, name }) => this.#addCron(name, cron)))\n\t\t\tworker.run()\n\t\t}, { class: RedisJob })\n\t}\n\n\t#getNewId() {\n\t\treturn [Date.now(), Random.string()].join('_')\n\t}\n\n\tasync #addCron(type: Cron | string, cron: string) {\n\t\tconst job = await this.#queue.add(\n\t\t\tJobNames.CronJob,\n\t\t\t{ type },\n\t\t\t{\n\t\t\t\tjobId: this.#getNewId(),\n\t\t\t\trepeat: { pattern: cron },\n\t\t\t\tremoveOnComplete: true,\n\t\t\t\tbackoff: 1000,\n\t\t\t\tattempts: 3,\n\t\t\t},\n\t\t)\n\t\treturn job.id!.toString()\n\t}\n\n\tasync #cleanup() {\n\t\tawait this.retryAllFailedJobs()\n\t\tconst repeatableJobs = await this.#queue.getJobSchedulers()\n\t\tawait Promise.all(repeatableJobs.map((job) => this.#queue.removeJobScheduler(job.key)))\n\t}\n\n\tasync addDelayed(data: DelayedJobEvent, delayInMs: number): Promise<string> {\n\t\tconst job = await this.#queue.add(JobNames.DelayedJob, data, {\n\t\t\tjobId: this.#getNewId(),\n\t\t\tdelay: delayInMs,\n\t\t\tremoveOnComplete: true,\n\t\t\tbackoff: 1000,\n\t\t\tattempts: 3,\n\t\t})\n\t\treturn job.id!.toString()\n\t}\n\tasync addRepeatable(data: RepeatableJobEvent, cron: string, tz?: string): Promise<string> {\n\t\tconst job = await this.#queue.add(JobNames.RepeatableJob, data, {\n\t\t\tjobId: this.#getNewId(),\n\t\t\trepeat: { pattern: cron, ...(tz ? { tz } : {}) },\n\t\t\tremoveOnComplete: true,\n\t\t\tbackoff: 1000,\n\t\t\tattempts: 3,\n\t\t})\n\t\treturn job.opts?.repeat?.key ?? ''\n\t}\n\tasync removeDelayed(jobId: string) {\n\t\tconst job = await this.#queue.getJob(jobId)\n\t\tif (job) await job.remove()\n\t}\n\tasync retryAllFailedJobs() {\n\t\tconst failedJobs = await this.#queue.getFailed()\n\t\tawait Promise.all(failedJobs.map((job) => job.retry()))\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { test, expectTypeOf } = import.meta.vitest\n\ttype PipeOutput<P> = import('valleyed').PipeOutput<P>\n\n\ttest('external new is a compile error', () => {\n\t\t// @ts-expect-error — protected constructor prevents external instantiation\n\t\tvoid (() => new RedisJob({ redisConfig: { host: '' }, queueName: '' }))\n\t})\n\n\ttest('Config type matches pipe output', () => {\n\t\texpectTypeOf<typeof RedisJob.Config>().toEqualTypeOf<PipeOutput<ReturnType<typeof redisJobsConfigPipe>>>()\n\t})\n}\n"],"mappings":"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","names":["Queue","Worker","v","redisJobsConfigPipe","v","redisConfigPipe","RedisJob","_RedisJob","configurable","Job","#queue","config","redisCache","RedisCache","queueName","Instance","Queue","worker","Worker","job","#cleanup","cron","name","#addCron","#getNewId","random_exports","type","repeatableJobs","data","delayInMs","tz","jobId","failedJobs"]}
1
+ {"version":3,"sources":["../../../../../src/jobs/adapters/redis/index.ts"],"sourcesContent":["import { Queue, Worker } from 'bullmq'\nimport { v } from 'valleyed'\n\nimport { RedisCache, redisConfigPipe } from '../../../cache/adapters/redis'\nimport { Instance } from '../../../instance'\nimport { Random, configurable } from '../../../utilities'\nimport { Job, JobNames, type Cron, type DelayedJobEvent, type RepeatableJobEvent } from '../base'\n\nexport const redisJobsConfigPipe = () =>\n\tv.meta(\n\t\tv.object({\n\t\t\tredisConfig: redisConfigPipe(),\n\t\t\tqueueName: v.string(),\n\t\t}),\n\t\t{ title: 'Redis Jobs Config', $refId: 'RedisJobsConfig' },\n\t)\n\nexport class RedisJob extends configurable(redisJobsConfigPipe, Job) {\n\t#queue: Queue\n\n\tprotected constructor(config: typeof RedisJob.Config) {\n\t\tsuper(config)\n\n\t\tconst redisCache = (RedisCache.create as any)(config.redisConfig, {\n\t\t\tmaxRetriesPerRequest: null,\n\t\t\tenableReadyCheck: false,\n\t\t})\n\t\tconst queueName = Instance.get().getScopedName(config.queueName)\n\t\tthis.#queue = new Queue(queueName, { connection: redisCache.connectionOptions, skipVersionCheck: true })\n\n\t\tconst worker = new Worker(\n\t\t\tqueueName,\n\t\t\tasync (job) => {\n\t\t\t\tswitch (job.name) {\n\t\t\t\t\tcase JobNames.DelayedJob:\n\t\t\t\t\t\treturn (this.callbacks.onDelayed as any)?.(job.data)\n\t\t\t\t\tcase JobNames.CronJob:\n\t\t\t\t\t\treturn (this.callbacks.onCron as any)?.(job.data.type)\n\t\t\t\t\tcase JobNames.RepeatableJob:\n\t\t\t\t\t\treturn (this.callbacks.onRepeatable as any)?.(job.data)\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ connection: redisCache.connectionOptions, autorun: false, skipVersionCheck: true },\n\t\t)\n\n\t\tInstance.on('start', async () => {\n\t\t\tawait this.#cleanup()\n\t\t\tawait Promise.all(this.crons.map(({ cron, name }) => this.#addCron(name, cron)))\n\t\t\tworker.run()\n\t\t}, { class: RedisJob })\n\t}\n\n\t#getNewId() {\n\t\treturn [Date.now(), Random.string()].join('_')\n\t}\n\n\tasync #addCron(type: Cron | string, cron: string) {\n\t\tconst job = await this.#queue.add(\n\t\t\tJobNames.CronJob,\n\t\t\t{ type },\n\t\t\t{\n\t\t\t\tjobId: this.#getNewId(),\n\t\t\t\trepeat: { pattern: cron },\n\t\t\t\tremoveOnComplete: true,\n\t\t\t\tbackoff: 1000,\n\t\t\t\tattempts: 3,\n\t\t\t},\n\t\t)\n\t\treturn job.id!.toString()\n\t}\n\n\tasync #cleanup() {\n\t\tawait this.retryAllFailedJobs()\n\t\tconst repeatableJobs = await this.#queue.getJobSchedulers()\n\t\tawait Promise.all(repeatableJobs.map((job) => this.#queue.removeJobScheduler(job.key)))\n\t}\n\n\tasync addDelayed(data: DelayedJobEvent, delayInMs: number): Promise<string> {\n\t\tconst job = await this.#queue.add(JobNames.DelayedJob, data, {\n\t\t\tjobId: this.#getNewId(),\n\t\t\tdelay: delayInMs,\n\t\t\tremoveOnComplete: true,\n\t\t\tbackoff: 1000,\n\t\t\tattempts: 3,\n\t\t})\n\t\treturn job.id!.toString()\n\t}\n\tasync addRepeatable(data: RepeatableJobEvent, cron: string, tz?: string): Promise<string> {\n\t\tconst job = await this.#queue.add(JobNames.RepeatableJob, data, {\n\t\t\tjobId: this.#getNewId(),\n\t\t\trepeat: { pattern: cron, ...(tz ? { tz } : {}) },\n\t\t\tremoveOnComplete: true,\n\t\t\tbackoff: 1000,\n\t\t\tattempts: 3,\n\t\t})\n\t\treturn job.opts?.repeat?.key ?? ''\n\t}\n\tasync removeDelayed(jobId: string) {\n\t\tconst job = await this.#queue.getJob(jobId)\n\t\tif (job) await job.remove()\n\t}\n\tasync retryAllFailedJobs() {\n\t\tconst failedJobs = await this.#queue.getFailed()\n\t\tawait Promise.all(failedJobs.map((job) => job.retry()))\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { test, expectTypeOf } = import.meta.vitest\n\ttype PipeOutput<P> = import('valleyed').PipeOutput<P>\n\n\ttest('external new is a compile error', () => {\n\t\t// @ts-expect-error — protected constructor prevents external instantiation\n\t\tvoid (() => new RedisJob({ redisConfig: { host: '' }, queueName: '' }))\n\t})\n\n\ttest('Config type matches pipe output', () => {\n\t\texpectTypeOf<typeof RedisJob.Config>().toEqualTypeOf<PipeOutput<ReturnType<typeof redisJobsConfigPipe>>>()\n\t})\n}\n"],"mappings":"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","names":["Queue","Worker","v","redisJobsConfigPipe","v","redisConfigPipe","RedisJob","_RedisJob","configurable","Job","#queue","config","redisCache","RedisCache","queueName","Instance","Queue","worker","Worker","job","#cleanup","cron","name","#addCron","#getNewId","random_exports","type","repeatableJobs","data","delayInMs","tz","jobId","failedJobs"]}
@@ -1,16 +1,20 @@
1
1
  import {
2
2
  RedisCache,
3
3
  redisConfigPipe
4
- } from "../../../chunk-5PZACGPI.mjs";
5
- import "../../../chunk-LKS5ZE45.mjs";
4
+ } from "../../../chunk-26BOI6IL.mjs";
5
+ import "../../../chunk-RPPYCWXW.mjs";
6
+ import {
7
+ random_exports
8
+ } from "../../../chunk-NNFUYHQ7.mjs";
9
+ import {
10
+ configurable
11
+ } from "../../../chunk-NEF3UYFA.mjs";
6
12
  import {
7
13
  Job
8
14
  } from "../../../chunk-DKPZJIIR.mjs";
9
15
  import {
10
- Instance,
11
- configurable,
12
- random_exports
13
- } from "../../../chunk-A4P7P6BU.mjs";
16
+ Instance
17
+ } from "../../../chunk-CEYT5YBA.mjs";
14
18
  import "../../../chunk-VCCS7CAI.mjs";
15
19
  import "../../../chunk-J5LGTIGS.mjs";
16
20
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/jobs/adapters/redis/index.ts"],"sourcesContent":["import { Queue, Worker } from 'bullmq'\nimport { v } from 'valleyed'\n\nimport { RedisCache, redisConfigPipe } from '../../../cache/adapters/redis'\nimport { Instance } from '../../../instance'\nimport { Random, configurable } from '../../../utilities'\nimport { Job, JobNames, type Cron, type DelayedJobEvent, type RepeatableJobEvent } from '../base'\n\nexport const redisJobsConfigPipe = () =>\n\tv.meta(\n\t\tv.object({\n\t\t\tredisConfig: redisConfigPipe(),\n\t\t\tqueueName: v.string(),\n\t\t}),\n\t\t{ title: 'Redis Jobs Config', $refId: 'RedisJobsConfig' },\n\t)\n\nexport class RedisJob extends configurable(redisJobsConfigPipe, Job) {\n\t#queue: Queue\n\n\tprotected constructor(config: typeof RedisJob.Config) {\n\t\tsuper(config)\n\n\t\tconst redisCache = (RedisCache.create as any)(config.redisConfig, {\n\t\t\tmaxRetriesPerRequest: null,\n\t\t\tenableReadyCheck: false,\n\t\t})\n\t\tconst queueName = Instance.get().getScopedName(config.queueName)\n\t\tthis.#queue = new Queue(queueName, { connection: redisCache.connectionOptions, skipVersionCheck: true })\n\n\t\tconst worker = new Worker(\n\t\t\tqueueName,\n\t\t\tasync (job) => {\n\t\t\t\tswitch (job.name) {\n\t\t\t\t\tcase JobNames.DelayedJob:\n\t\t\t\t\t\treturn (this.callbacks.onDelayed as any)?.(job.data)\n\t\t\t\t\tcase JobNames.CronJob:\n\t\t\t\t\t\treturn (this.callbacks.onCron as any)?.(job.data.type)\n\t\t\t\t\tcase JobNames.RepeatableJob:\n\t\t\t\t\t\treturn (this.callbacks.onRepeatable as any)?.(job.data)\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ connection: redisCache.connectionOptions, autorun: false, skipVersionCheck: true },\n\t\t)\n\n\t\tInstance.on('start', async () => {\n\t\t\tawait this.#cleanup()\n\t\t\tawait Promise.all(this.crons.map(({ cron, name }) => this.#addCron(name, cron)))\n\t\t\tworker.run()\n\t\t}, { class: RedisJob })\n\t}\n\n\t#getNewId() {\n\t\treturn [Date.now(), Random.string()].join('_')\n\t}\n\n\tasync #addCron(type: Cron | string, cron: string) {\n\t\tconst job = await this.#queue.add(\n\t\t\tJobNames.CronJob,\n\t\t\t{ type },\n\t\t\t{\n\t\t\t\tjobId: this.#getNewId(),\n\t\t\t\trepeat: { pattern: cron },\n\t\t\t\tremoveOnComplete: true,\n\t\t\t\tbackoff: 1000,\n\t\t\t\tattempts: 3,\n\t\t\t},\n\t\t)\n\t\treturn job.id!.toString()\n\t}\n\n\tasync #cleanup() {\n\t\tawait this.retryAllFailedJobs()\n\t\tconst repeatableJobs = await this.#queue.getJobSchedulers()\n\t\tawait Promise.all(repeatableJobs.map((job) => this.#queue.removeJobScheduler(job.key)))\n\t}\n\n\tasync addDelayed(data: DelayedJobEvent, delayInMs: number): Promise<string> {\n\t\tconst job = await this.#queue.add(JobNames.DelayedJob, data, {\n\t\t\tjobId: this.#getNewId(),\n\t\t\tdelay: delayInMs,\n\t\t\tremoveOnComplete: true,\n\t\t\tbackoff: 1000,\n\t\t\tattempts: 3,\n\t\t})\n\t\treturn job.id!.toString()\n\t}\n\tasync addRepeatable(data: RepeatableJobEvent, cron: string, tz?: string): Promise<string> {\n\t\tconst job = await this.#queue.add(JobNames.RepeatableJob, data, {\n\t\t\tjobId: this.#getNewId(),\n\t\t\trepeat: { pattern: cron, ...(tz ? { tz } : {}) },\n\t\t\tremoveOnComplete: true,\n\t\t\tbackoff: 1000,\n\t\t\tattempts: 3,\n\t\t})\n\t\treturn job.opts?.repeat?.key ?? ''\n\t}\n\tasync removeDelayed(jobId: string) {\n\t\tconst job = await this.#queue.getJob(jobId)\n\t\tif (job) await job.remove()\n\t}\n\tasync retryAllFailedJobs() {\n\t\tconst failedJobs = await this.#queue.getFailed()\n\t\tawait Promise.all(failedJobs.map((job) => job.retry()))\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { test, expectTypeOf } = import.meta.vitest\n\ttype PipeOutput<P> = import('valleyed').PipeOutput<P>\n\n\ttest('external new is a compile error', () => {\n\t\t// @ts-expect-error — protected constructor prevents external instantiation\n\t\tvoid (() => new RedisJob({ redisConfig: { host: '' }, queueName: '' }))\n\t})\n\n\ttest('Config type matches pipe output', () => {\n\t\texpectTypeOf<typeof RedisJob.Config>().toEqualTypeOf<PipeOutput<ReturnType<typeof redisJobsConfigPipe>>>()\n\t})\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../../src/jobs/adapters/redis/index.ts"],"sourcesContent":["import { Queue, Worker } from 'bullmq'\nimport { v } from 'valleyed'\n\nimport { RedisCache, redisConfigPipe } from '../../../cache/adapters/redis'\nimport { Instance } from '../../../instance'\nimport { Random, configurable } from '../../../utilities'\nimport { Job, JobNames, type Cron, type DelayedJobEvent, type RepeatableJobEvent } from '../base'\n\nexport const redisJobsConfigPipe = () =>\n\tv.meta(\n\t\tv.object({\n\t\t\tredisConfig: redisConfigPipe(),\n\t\t\tqueueName: v.string(),\n\t\t}),\n\t\t{ title: 'Redis Jobs Config', $refId: 'RedisJobsConfig' },\n\t)\n\nexport class RedisJob extends configurable(redisJobsConfigPipe, Job) {\n\t#queue: Queue\n\n\tprotected constructor(config: typeof RedisJob.Config) {\n\t\tsuper(config)\n\n\t\tconst redisCache = (RedisCache.create as any)(config.redisConfig, {\n\t\t\tmaxRetriesPerRequest: null,\n\t\t\tenableReadyCheck: false,\n\t\t})\n\t\tconst queueName = Instance.get().getScopedName(config.queueName)\n\t\tthis.#queue = new Queue(queueName, { connection: redisCache.connectionOptions, skipVersionCheck: true })\n\n\t\tconst worker = new Worker(\n\t\t\tqueueName,\n\t\t\tasync (job) => {\n\t\t\t\tswitch (job.name) {\n\t\t\t\t\tcase JobNames.DelayedJob:\n\t\t\t\t\t\treturn (this.callbacks.onDelayed as any)?.(job.data)\n\t\t\t\t\tcase JobNames.CronJob:\n\t\t\t\t\t\treturn (this.callbacks.onCron as any)?.(job.data.type)\n\t\t\t\t\tcase JobNames.RepeatableJob:\n\t\t\t\t\t\treturn (this.callbacks.onRepeatable as any)?.(job.data)\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ connection: redisCache.connectionOptions, autorun: false, skipVersionCheck: true },\n\t\t)\n\n\t\tInstance.on('start', async () => {\n\t\t\tawait this.#cleanup()\n\t\t\tawait Promise.all(this.crons.map(({ cron, name }) => this.#addCron(name, cron)))\n\t\t\tworker.run()\n\t\t}, { class: RedisJob })\n\t}\n\n\t#getNewId() {\n\t\treturn [Date.now(), Random.string()].join('_')\n\t}\n\n\tasync #addCron(type: Cron | string, cron: string) {\n\t\tconst job = await this.#queue.add(\n\t\t\tJobNames.CronJob,\n\t\t\t{ type },\n\t\t\t{\n\t\t\t\tjobId: this.#getNewId(),\n\t\t\t\trepeat: { pattern: cron },\n\t\t\t\tremoveOnComplete: true,\n\t\t\t\tbackoff: 1000,\n\t\t\t\tattempts: 3,\n\t\t\t},\n\t\t)\n\t\treturn job.id!.toString()\n\t}\n\n\tasync #cleanup() {\n\t\tawait this.retryAllFailedJobs()\n\t\tconst repeatableJobs = await this.#queue.getJobSchedulers()\n\t\tawait Promise.all(repeatableJobs.map((job) => this.#queue.removeJobScheduler(job.key)))\n\t}\n\n\tasync addDelayed(data: DelayedJobEvent, delayInMs: number): Promise<string> {\n\t\tconst job = await this.#queue.add(JobNames.DelayedJob, data, {\n\t\t\tjobId: this.#getNewId(),\n\t\t\tdelay: delayInMs,\n\t\t\tremoveOnComplete: true,\n\t\t\tbackoff: 1000,\n\t\t\tattempts: 3,\n\t\t})\n\t\treturn job.id!.toString()\n\t}\n\tasync addRepeatable(data: RepeatableJobEvent, cron: string, tz?: string): Promise<string> {\n\t\tconst job = await this.#queue.add(JobNames.RepeatableJob, data, {\n\t\t\tjobId: this.#getNewId(),\n\t\t\trepeat: { pattern: cron, ...(tz ? { tz } : {}) },\n\t\t\tremoveOnComplete: true,\n\t\t\tbackoff: 1000,\n\t\t\tattempts: 3,\n\t\t})\n\t\treturn job.opts?.repeat?.key ?? ''\n\t}\n\tasync removeDelayed(jobId: string) {\n\t\tconst job = await this.#queue.getJob(jobId)\n\t\tif (job) await job.remove()\n\t}\n\tasync retryAllFailedJobs() {\n\t\tconst failedJobs = await this.#queue.getFailed()\n\t\tawait Promise.all(failedJobs.map((job) => job.retry()))\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { test, expectTypeOf } = import.meta.vitest\n\ttype PipeOutput<P> = import('valleyed').PipeOutput<P>\n\n\ttest('external new is a compile error', () => {\n\t\t// @ts-expect-error — protected constructor prevents external instantiation\n\t\tvoid (() => new RedisJob({ redisConfig: { host: '' }, queueName: '' }))\n\t})\n\n\ttest('Config type matches pipe output', () => {\n\t\texpectTypeOf<typeof RedisJob.Config>().toEqualTypeOf<PipeOutput<ReturnType<typeof redisJobsConfigPipe>>>()\n\t})\n}\n"],"mappings":"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","names":[]}
@@ -1,2 +1,2 @@
1
- import{a}from "../../../chunk-P4PRHHIY.min.mjs";import"../../../chunk-7SQ5JQYR.min.mjs";import"../../../chunk-IN5YKIJ7.min.mjs";import"../../../chunk-DSUJ5FUL.min.mjs";import"../../../chunk-AQY7QVYI.min.mjs";export{a as InMemoryAdapter};
1
+ import{a}from "../../../chunk-QCCDJLBH.min.mjs";import"../../../chunk-CU2HU5UE.min.mjs";import"../../../chunk-AODOD22O.min.mjs";import"../../../chunk-APSDCRY5.min.mjs";import"../../../chunk-DSUJ5FUL.min.mjs";import"../../../chunk-AQY7QVYI.min.mjs";export{a as InMemoryAdapter};
2
2
  //# sourceMappingURL=index.min.mjs.map
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  InMemoryAdapter
3
- } from "../../../chunk-TSVHZZE4.mjs";
4
- import "../../../chunk-3TP7NINX.mjs";
5
- import "../../../chunk-A4P7P6BU.mjs";
3
+ } from "../../../chunk-VQO4MSEC.mjs";
4
+ import "../../../chunk-NEF3UYFA.mjs";
5
+ import "../../../chunk-RIXTQQUU.mjs";
6
+ import "../../../chunk-CEYT5YBA.mjs";
6
7
  import "../../../chunk-VCCS7CAI.mjs";
7
8
  import "../../../chunk-J5LGTIGS.mjs";
8
9
  export {
@@ -1,2 +1,2 @@
1
- import{a as l}from "../../../chunk-P4PRHHIY.min.mjs";import{j as p}from "../../../chunk-7SQ5JQYR.min.mjs";import{m as d}from "../../../chunk-IN5YKIJ7.min.mjs";import"../../../chunk-DSUJ5FUL.min.mjs";import"../../../chunk-AQY7QVYI.min.mjs";import{open as w,readFile as h,rename as y,unlink as f,writeFile as b}from"fs/promises";import{v as o}from"valleyed";var k=()=>o.object({filePath:o.string()}),u=class extends d(k,p){schemaConfigPipe=o.object({table:o.string()});#e=l.create({});supportedFieldTypes=this.#e.supportedFieldTypes;queryableOps=this.#e.queryableOps;updateOps=this.#e.updateOps;aggregateOps=this.#e.aggregateOps;#t=Promise.resolve();constructor(e){super(e)}get stores(){return this.#e.stores}#n(){let e={},a=[...this.#e.migrations.values()];a.length>0&&(e.__migrations=a);let t={};for(let[i,s]of this.#e.tables.entries())t[i]={pk:s.pk,fields:Object.fromEntries(s.fields.entries())};Object.keys(t).length>0&&(e.__tables=t);let n={};for(let[i,s]of this.#e.indexes.entries())n[i]={table:s.table,on:[...s.on],unique:s.unique};Object.keys(n).length>0&&(e.__indexes=n);let r={};for(let[i,s]of this.#e.foreignKeys.entries())r[i]={table:s.table,on:s.on,references:s.references,...s.onDelete?{onDelete:s.onDelete}:{},...s.onUpdate?{onUpdate:s.onUpdate}:{}};Object.keys(r).length>0&&(e.__foreignKeys=r);for(let[i,s]of this.stores.entries()){let c={};for(let[m,g]of s.entries())c[m]=g;e[i]=c}return JSON.stringify(e)}async#s(){let e=this.#n(),a=this.config.filePath+".tmp."+process.pid+"."+Date.now();await b(a,e,"utf-8"),await y(a,this.config.filePath)}#a(){let e=this.#t.then(()=>this.#s());return this.#t=e.catch(()=>{}),e}async connect(){let e;try{e=await h(this.config.filePath,"utf-8")}catch{return}let a=JSON.parse(e);if(this.#e.migrations.clear(),Array.isArray(a.__migrations))for(let t of a.__migrations)this.#e.migrations.set(t.id,{id:t.id,appliedAt:t.appliedAt});if(this.#e.tables.clear(),a.__tables&&typeof a.__tables=="object")for(let[t,n]of Object.entries(a.__tables)){let r=new Map;if(n.fields)for(let[i,s]of Object.entries(n.fields))r.set(i,s);this.#e.tables.set(t,{pk:n.pk,fields:r})}if(this.#e.indexes.clear(),a.__indexes&&typeof a.__indexes=="object")for(let[t,n]of Object.entries(a.__indexes))this.#e.indexes.set(t,{table:n.table,on:n.on,unique:n.unique});if(this.#e.foreignKeys.clear(),a.__foreignKeys&&typeof a.__foreignKeys=="object")for(let[t,n]of Object.entries(a.__foreignKeys))this.#e.foreignKeys.set(t,n);this.stores.clear();for(let[t,n]of Object.entries(a)){if(t.startsWith("__"))continue;let r=new Map;for(let[i,s]of Object.entries(n))r.set(i,s);this.stores.set(t,r)}}async disconnect(){await this.#a()}async findByPk(e,a,t){return this.#e.findByPk(e,a,t)}async createMany(e,a,t){let n=await this.#e.createMany(e,a,t);return await this.#a(),n}async updateByPk(e,a,t,n){let r=await this.#e.updateByPk(e,a,t,n);return r&&await this.#a(),r}async deleteByPk(e,a,t){let n=await this.#e.deleteByPk(e,a,t);return n&&await this.#a(),n}async findMany(e,a,t,n){return this.#e.findMany(e,a,t,n)}async count(e,a,t){return this.#e.count(e,a,t)}iterateMany(e,a,t,n){return this.#e.iterateMany(e,a,t,n)}async updateMany(e,a,t,n){let r=await this.#e.updateMany(e,a,t,n);return r.length&&await this.#a(),r}async deleteMany(e,a,t){let n=await this.#e.deleteMany(e,a,t);return n.length&&await this.#a(),n}async upsertOne(e,a,t,n,r){let i=await this.#e.upsertOne(e,a,t,n,r);return await this.#a(),i}async aggregate(e,a,t){return this.#e.aggregate(e,a,t)}async session(e){return this.#e.session(async()=>{let a=await e();return await this.#a(),a})}async loadMigrations(){return await this.connect(),this.#e.loadMigrations()}async recordMigration(e,a){await this.#e.recordMigration(e,a),await this.#a()}async acquireMigrationLock(e){let a=this.config.filePath+".lock",t=Date.now()+3e4;for(;;)try{await(await w(a,"wx")).close();break}catch(n){if(n.code!=="EEXIST")throw n;if(Date.now()>t)throw new Error("Timed out waiting for migration lock");await new Promise(r=>setTimeout(r,50))}try{return await e()}finally{await f(a).catch(()=>{})}}async applyCreateTable(e){await this.#e.applyCreateTable(e),await this.#a()}async applyDropTable(e){await this.#e.applyDropTable(e),await this.#a()}async applyAddField(e){await this.#e.applyAddField(e),await this.#a()}async applyDropField(e){await this.#e.applyDropField(e),await this.#a()}async applyModifyField(e){await this.#e.applyModifyField(e),await this.#a()}async applyRenameTable(e){await this.#e.applyRenameTable(e),await this.#a()}async applyRenameField(e){await this.#e.applyRenameField(e),await this.#a()}async applyAddIndex(e){await this.#e.applyAddIndex(e),await this.#a()}async applyDropIndex(e){await this.#e.applyDropIndex(e),await this.#a()}async applyAddForeignKey(e){await this.#e.applyAddForeignKey(e),await this.#a()}async applyDropForeignKey(e){await this.#e.applyDropForeignKey(e),await this.#a()}async introspect(){return this.#e.introspect()}};export{u as JsonAdapter};
1
+ import{a as l}from "../../../chunk-QCCDJLBH.min.mjs";import{a as d}from "../../../chunk-CU2HU5UE.min.mjs";import{j as p}from "../../../chunk-AODOD22O.min.mjs";import"../../../chunk-APSDCRY5.min.mjs";import"../../../chunk-DSUJ5FUL.min.mjs";import"../../../chunk-AQY7QVYI.min.mjs";import{open as w,readFile as h,rename as y,unlink as f,writeFile as b}from"fs/promises";import{v as o}from"valleyed";var k=()=>o.object({filePath:o.string()}),u=class extends d(k,p){schemaConfigPipe=o.object({table:o.string()});#e=l.create({});supportedFieldTypes=this.#e.supportedFieldTypes;queryableOps=this.#e.queryableOps;updateOps=this.#e.updateOps;aggregateOps=this.#e.aggregateOps;#t=Promise.resolve();constructor(e){super(e)}get stores(){return this.#e.stores}#n(){let e={},a=[...this.#e.migrations.values()];a.length>0&&(e.__migrations=a);let t={};for(let[i,s]of this.#e.tables.entries())t[i]={pk:s.pk,fields:Object.fromEntries(s.fields.entries())};Object.keys(t).length>0&&(e.__tables=t);let n={};for(let[i,s]of this.#e.indexes.entries())n[i]={table:s.table,on:[...s.on],unique:s.unique};Object.keys(n).length>0&&(e.__indexes=n);let r={};for(let[i,s]of this.#e.foreignKeys.entries())r[i]={table:s.table,on:s.on,references:s.references,...s.onDelete?{onDelete:s.onDelete}:{},...s.onUpdate?{onUpdate:s.onUpdate}:{}};Object.keys(r).length>0&&(e.__foreignKeys=r);for(let[i,s]of this.stores.entries()){let c={};for(let[m,g]of s.entries())c[m]=g;e[i]=c}return JSON.stringify(e)}async#s(){let e=this.#n(),a=this.config.filePath+".tmp."+process.pid+"."+Date.now();await b(a,e,"utf-8"),await y(a,this.config.filePath)}#a(){let e=this.#t.then(()=>this.#s());return this.#t=e.catch(()=>{}),e}async connect(){let e;try{e=await h(this.config.filePath,"utf-8")}catch{return}let a=JSON.parse(e);if(this.#e.migrations.clear(),Array.isArray(a.__migrations))for(let t of a.__migrations)this.#e.migrations.set(t.id,{id:t.id,appliedAt:t.appliedAt});if(this.#e.tables.clear(),a.__tables&&typeof a.__tables=="object")for(let[t,n]of Object.entries(a.__tables)){let r=new Map;if(n.fields)for(let[i,s]of Object.entries(n.fields))r.set(i,s);this.#e.tables.set(t,{pk:n.pk,fields:r})}if(this.#e.indexes.clear(),a.__indexes&&typeof a.__indexes=="object")for(let[t,n]of Object.entries(a.__indexes))this.#e.indexes.set(t,{table:n.table,on:n.on,unique:n.unique});if(this.#e.foreignKeys.clear(),a.__foreignKeys&&typeof a.__foreignKeys=="object")for(let[t,n]of Object.entries(a.__foreignKeys))this.#e.foreignKeys.set(t,n);this.stores.clear();for(let[t,n]of Object.entries(a)){if(t.startsWith("__"))continue;let r=new Map;for(let[i,s]of Object.entries(n))r.set(i,s);this.stores.set(t,r)}}async disconnect(){await this.#a()}async findByPk(e,a,t){return this.#e.findByPk(e,a,t)}async createMany(e,a,t){let n=await this.#e.createMany(e,a,t);return await this.#a(),n}async updateByPk(e,a,t,n){let r=await this.#e.updateByPk(e,a,t,n);return r&&await this.#a(),r}async deleteByPk(e,a,t){let n=await this.#e.deleteByPk(e,a,t);return n&&await this.#a(),n}async findMany(e,a,t,n){return this.#e.findMany(e,a,t,n)}async count(e,a,t){return this.#e.count(e,a,t)}iterateMany(e,a,t,n){return this.#e.iterateMany(e,a,t,n)}async updateMany(e,a,t,n){let r=await this.#e.updateMany(e,a,t,n);return r.length&&await this.#a(),r}async deleteMany(e,a,t){let n=await this.#e.deleteMany(e,a,t);return n.length&&await this.#a(),n}async upsertOne(e,a,t,n,r){let i=await this.#e.upsertOne(e,a,t,n,r);return await this.#a(),i}async aggregate(e,a,t){return this.#e.aggregate(e,a,t)}async session(e){return this.#e.session(async()=>{let a=await e();return await this.#a(),a})}async loadMigrations(){return await this.connect(),this.#e.loadMigrations()}async recordMigration(e,a){await this.#e.recordMigration(e,a),await this.#a()}async acquireMigrationLock(e){let a=this.config.filePath+".lock",t=Date.now()+3e4;for(;;)try{await(await w(a,"wx")).close();break}catch(n){if(n.code!=="EEXIST")throw n;if(Date.now()>t)throw new Error("Timed out waiting for migration lock");await new Promise(r=>setTimeout(r,50))}try{return await e()}finally{await f(a).catch(()=>{})}}async applyCreateTable(e){await this.#e.applyCreateTable(e),await this.#a()}async applyDropTable(e){await this.#e.applyDropTable(e),await this.#a()}async applyAddField(e){await this.#e.applyAddField(e),await this.#a()}async applyDropField(e){await this.#e.applyDropField(e),await this.#a()}async applyModifyField(e){await this.#e.applyModifyField(e),await this.#a()}async applyRenameTable(e){await this.#e.applyRenameTable(e),await this.#a()}async applyRenameField(e){await this.#e.applyRenameField(e),await this.#a()}async applyAddIndex(e){await this.#e.applyAddIndex(e),await this.#a()}async applyDropIndex(e){await this.#e.applyDropIndex(e),await this.#a()}async applyAddForeignKey(e){await this.#e.applyAddForeignKey(e),await this.#a()}async applyDropForeignKey(e){await this.#e.applyDropForeignKey(e),await this.#a()}async introspect(){return this.#e.introspect()}};export{u as JsonAdapter};
2
2
  //# sourceMappingURL=index.min.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/orm/adapters/json/index.ts"],"sourcesContent":["import { open, readFile, rename, unlink, writeFile } from 'node:fs/promises'\n\nimport { v } from 'valleyed'\n\nimport { configurable } from '../../../utilities/configurable'\nimport type { FilterGroup } from '../../filter'\nimport type { DiscoveredSchema } from '../../migrations/introspection-types'\nimport type { AddFieldChange, AddForeignKeyChange, AddIndexChange, AnyFieldSpec, CreateTableChange, DropFieldChange, DropForeignKeyChange, DropIndexChange, DropTableChange, ModifyFieldChange, RenameFieldChange, RenameTableChange } from '../../migrations/types'\nimport { OrmAdapter, type AggregateSpec } from '../../orm-adapter'\nimport type { IterationQueryOptions, QueryOptions } from '../../query-options'\nimport type { AnySchema } from '../../schema'\nimport type { AnyUpdateOp } from '../../updates'\nimport { InMemoryAdapter, type InMemoryRepoConfig } from '../in-memory'\n\nexport type JsonAdapterConfig = InMemoryRepoConfig\n\nconst jsonConnectionPipe = () =>\n\tv.object({\n\t\tfilePath: v.string(),\n\t})\n\nexport class JsonAdapter extends configurable(jsonConnectionPipe, OrmAdapter) {\n\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\n\treadonly #inMemory = InMemoryAdapter.create({})\n\n\treadonly supportedFieldTypes = this.#inMemory.supportedFieldTypes\n\treadonly queryableOps = this.#inMemory.queryableOps\n\treadonly updateOps = this.#inMemory.updateOps\n\treadonly aggregateOps = this.#inMemory.aggregateOps\n\n\t#writeQueue: Promise<void> = Promise.resolve()\n\n\tprotected constructor(config: typeof JsonAdapter.Config) {\n\t\tsuper(config)\n\t}\n\n\tget stores() {\n\t\treturn this.#inMemory.stores\n\t}\n\n\t#serialize(): string {\n\t\tconst obj: Record<string, unknown> = {}\n\n\t\tconst migrations = [...this.#inMemory.migrations.values()]\n\t\tif (migrations.length > 0) obj['__migrations'] = migrations\n\n\t\tconst tables: Record<string, unknown> = {}\n\t\tfor (const [name, meta] of this.#inMemory.tables.entries()) {\n\t\t\ttables[name] = { pk: meta.pk, fields: Object.fromEntries(meta.fields.entries()) }\n\t\t}\n\t\tif (Object.keys(tables).length > 0) obj['__tables'] = tables\n\n\t\tconst indexes: Record<string, unknown> = {}\n\t\tfor (const [name, meta] of this.#inMemory.indexes.entries()) {\n\t\t\tindexes[name] = { table: meta.table, on: [...meta.on], unique: meta.unique }\n\t\t}\n\t\tif (Object.keys(indexes).length > 0) obj['__indexes'] = indexes\n\n\t\tconst foreignKeys: Record<string, unknown> = {}\n\t\tfor (const [name, meta] of this.#inMemory.foreignKeys.entries()) {\n\t\t\tforeignKeys[name] = {\n\t\t\t\ttable: meta.table,\n\t\t\t\ton: meta.on,\n\t\t\t\treferences: meta.references,\n\t\t\t\t...(meta.onDelete ? { onDelete: meta.onDelete } : {}),\n\t\t\t\t...(meta.onUpdate ? { onUpdate: meta.onUpdate } : {}),\n\t\t\t}\n\t\t}\n\t\tif (Object.keys(foreignKeys).length > 0) obj['__foreignKeys'] = foreignKeys\n\n\t\tfor (const [name, store] of this.stores.entries()) {\n\t\t\tconst records: Record<string, Record<string, unknown>> = {}\n\t\t\tfor (const [pk, doc] of store.entries()) {\n\t\t\t\trecords[pk] = doc\n\t\t\t}\n\t\t\tobj[name] = records\n\t\t}\n\n\t\treturn JSON.stringify(obj)\n\t}\n\n\tasync #atomicWrite(): Promise<void> {\n\t\tconst data = this.#serialize()\n\t\tconst tmpPath = this.config.filePath + '.tmp.' + process.pid + '.' + Date.now()\n\t\tawait writeFile(tmpPath, data, 'utf-8')\n\t\tawait rename(tmpPath, this.config.filePath)\n\t}\n\n\t#persistToDisk(): Promise<void> {\n\t\tconst next = this.#writeQueue.then(() => this.#atomicWrite())\n\t\tthis.#writeQueue = next.catch(() => {})\n\t\treturn next\n\t}\n\n\tasync connect(): Promise<void> {\n\t\tlet raw: string\n\t\ttry {\n\t\t\traw = await readFile(this.config.filePath, 'utf-8')\n\t\t} catch {\n\t\t\treturn\n\t\t}\n\t\tconst data = JSON.parse(raw) as Record<string, unknown>\n\n\t\tthis.#inMemory.migrations.clear()\n\t\tif (Array.isArray(data['__migrations'])) {\n\t\t\tfor (const m of data['__migrations'] as Array<{ id: string; appliedAt: number }>) {\n\t\t\t\tthis.#inMemory.migrations.set(m.id, { id: m.id, appliedAt: m.appliedAt })\n\t\t\t}\n\t\t}\n\n\t\tthis.#inMemory.tables.clear()\n\t\tif (data['__tables'] && typeof data['__tables'] === 'object') {\n\t\t\tfor (const [name, meta] of Object.entries(data['__tables'] as Record<string, any>)) {\n\t\t\t\tconst fields = new Map<string, AnyFieldSpec>()\n\t\t\t\tif (meta.fields) {\n\t\t\t\t\tfor (const [fieldName, fspec] of Object.entries(meta.fields as Record<string, AnyFieldSpec>)) {\n\t\t\t\t\t\tfields.set(fieldName, fspec)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.#inMemory.tables.set(name, { pk: meta.pk, fields })\n\t\t\t}\n\t\t}\n\n\t\tthis.#inMemory.indexes.clear()\n\t\tif (data['__indexes'] && typeof data['__indexes'] === 'object') {\n\t\t\tfor (const [name, meta] of Object.entries(data['__indexes'] as Record<string, any>)) {\n\t\t\t\tthis.#inMemory.indexes.set(name, { table: meta.table, on: meta.on, unique: meta.unique })\n\t\t\t}\n\t\t}\n\n\t\tthis.#inMemory.foreignKeys.clear()\n\t\tif (data['__foreignKeys'] && typeof data['__foreignKeys'] === 'object') {\n\t\t\tfor (const [name, meta] of Object.entries(data['__foreignKeys'] as Record<string, any>)) {\n\t\t\t\tthis.#inMemory.foreignKeys.set(name, meta)\n\t\t\t}\n\t\t}\n\n\t\tthis.stores.clear()\n\t\tfor (const [name, records] of Object.entries(data)) {\n\t\t\tif (name.startsWith('__')) continue\n\t\t\tconst store = new Map<string, Record<string, unknown>>()\n\t\t\tfor (const [pk, doc] of Object.entries(records as Record<string, Record<string, unknown>>)) {\n\t\t\t\tstore.set(pk, doc)\n\t\t\t}\n\t\t\tthis.stores.set(name, store)\n\t\t}\n\t}\n\n\tasync disconnect(): Promise<void> {\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync findByPk(schema: AnySchema, config: unknown, pk: unknown) {\n\t\treturn this.#inMemory.findByPk(schema, config, pk)\n\t}\n\n\tasync createMany(schema: AnySchema, config: unknown, data: Record<string, unknown>[]) {\n\t\tconst result = await this.#inMemory.createMany(schema, config, data)\n\t\tawait this.#persistToDisk()\n\t\treturn result\n\t}\n\n\tasync updateByPk(schema: AnySchema, config: unknown, pk: unknown, ops: AnyUpdateOp[]) {\n\t\tconst result = await this.#inMemory.updateByPk(schema, config, pk, ops)\n\t\tif (result) await this.#persistToDisk()\n\t\treturn result\n\t}\n\n\tasync deleteByPk(schema: AnySchema, config: unknown, pk: unknown) {\n\t\tconst result = await this.#inMemory.deleteByPk(schema, config, pk)\n\t\tif (result) await this.#persistToDisk()\n\t\treturn result\n\t}\n\n\tasync findMany(schema: AnySchema, config: unknown, group: FilterGroup, options?: QueryOptions) {\n\t\treturn this.#inMemory.findMany(schema, config, group, options)\n\t}\n\n\tasync count(schema: AnySchema, config: unknown, group: FilterGroup) {\n\t\treturn this.#inMemory.count(schema, config, group)\n\t}\n\n\titerateMany(schema: AnySchema, config: unknown, group: FilterGroup, options?: IterationQueryOptions) {\n\t\treturn this.#inMemory.iterateMany(schema, config, group, options)\n\t}\n\n\tasync updateMany(schema: AnySchema, config: unknown, group: FilterGroup, data: Record<string, unknown>) {\n\t\tconst result = await this.#inMemory.updateMany(schema, config, group, data)\n\t\tif (result.length) await this.#persistToDisk()\n\t\treturn result\n\t}\n\n\tasync deleteMany(schema: AnySchema, config: unknown, filter: FilterGroup) {\n\t\tconst result = await this.#inMemory.deleteMany(schema, config, filter)\n\t\tif (result.length) await this.#persistToDisk()\n\t\treturn result\n\t}\n\n\tasync upsertOne(schema: AnySchema, config: unknown, filter: FilterGroup, create: Record<string, unknown>, ops: AnyUpdateOp[]) {\n\t\tconst result = await this.#inMemory.upsertOne(schema, config, filter, create, ops)\n\t\tawait this.#persistToDisk()\n\t\treturn result\n\t}\n\n\tasync aggregate(schema: AnySchema, config: unknown, spec: AggregateSpec) {\n\t\treturn this.#inMemory.aggregate(schema, config, spec)\n\t}\n\n\tasync session<T>(fn: () => Promise<T>): Promise<T> {\n\t\treturn this.#inMemory.session(async () => {\n\t\t\tconst result = await fn()\n\t\t\tawait this.#persistToDisk()\n\t\t\treturn result\n\t\t})\n\t}\n\n\tasync loadMigrations(): Promise<{ id: string; appliedAt: number }[]> {\n\t\tawait this.connect()\n\t\treturn this.#inMemory.loadMigrations()\n\t}\n\n\tasync recordMigration(id: string, appliedAt: number): Promise<void> {\n\t\tawait this.#inMemory.recordMigration(id, appliedAt)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync acquireMigrationLock<T>(fn: () => Promise<T>): Promise<T> {\n\t\tconst lockPath = this.config.filePath + '.lock'\n\t\tconst deadline = Date.now() + 30_000\n\t\twhile (true) {\n\t\t\ttry {\n\t\t\t\tconst handle = await open(lockPath, 'wx')\n\t\t\t\tawait handle.close()\n\t\t\t\tbreak\n\t\t\t} catch (err: any) {\n\t\t\t\tif (err.code !== 'EEXIST') throw err\n\t\t\t\tif (Date.now() > deadline) throw new Error('Timed out waiting for migration lock')\n\t\t\t\tawait new Promise((r) => setTimeout(r, 50))\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\treturn await fn()\n\t\t} finally {\n\t\t\tawait unlink(lockPath).catch(() => {})\n\t\t}\n\t}\n\n\tasync applyCreateTable(change: CreateTableChange<any>): Promise<void> {\n\t\tawait this.#inMemory.applyCreateTable(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyDropTable(change: DropTableChange): Promise<void> {\n\t\tawait this.#inMemory.applyDropTable(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyAddField(change: AddFieldChange<any>): Promise<void> {\n\t\tawait this.#inMemory.applyAddField(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyDropField(change: DropFieldChange): Promise<void> {\n\t\tawait this.#inMemory.applyDropField(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyModifyField(change: ModifyFieldChange<any>): Promise<void> {\n\t\tawait this.#inMemory.applyModifyField(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyRenameTable(change: RenameTableChange): Promise<void> {\n\t\tawait this.#inMemory.applyRenameTable(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyRenameField(change: RenameFieldChange): Promise<void> {\n\t\tawait this.#inMemory.applyRenameField(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyAddIndex(change: AddIndexChange): Promise<void> {\n\t\tawait this.#inMemory.applyAddIndex(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyDropIndex(change: DropIndexChange): Promise<void> {\n\t\tawait this.#inMemory.applyDropIndex(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyAddForeignKey(change: AddForeignKeyChange): Promise<void> {\n\t\tawait this.#inMemory.applyAddForeignKey(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyDropForeignKey(change: DropForeignKeyChange): Promise<void> {\n\t\tawait this.#inMemory.applyDropForeignKey(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync introspect(): Promise<DiscoveredSchema[]> {\n\t\treturn this.#inMemory.introspect()\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect, beforeEach, afterEach } = import.meta.vitest\n\tconst { FilterGroup } = await import('../../filter')\n\tconst { OrderBy } = await import('../../query-options')\n\tconst { Schema } = await import('../../schema')\n\tconst { IncOp, PatchOp, PullOp, PushOp } = await import('../../updates')\n\tconst { v } = await import('valleyed')\n\tconst { mkdtemp, rm } = await import('node:fs/promises')\n\tconst { tmpdir } = await import('node:os')\n\tconst { join } = await import('node:path')\n\tconst { Migrator } = await import('../../migrations/migrator')\n\tconst { Repo } = await import('../../repo/repo')\n\tconst { OrmMigrationError } = await import('../../errors/migration')\n\ttype Migration<A extends import('../base').OrmAdapterLike<any>> = import('../../migrations/types').Migration<A>\n\n\tlet tmpDir: string\n\tlet filePath: string\n\n\tbeforeEach(async () => {\n\t\ttmpDir = await mkdtemp(join(tmpdir(), 'json-adapter-'))\n\t\tfilePath = join(tmpDir, 'test.json')\n\t})\n\n\tafterEach(async () => {\n\t\tawait rm(tmpDir, { recursive: true, force: true })\n\t})\n\n\tdescribe('json adapter — class construction', () => {\n\t\ttest('JsonAdapter.create({ filePath }) produces a working adapter', () => {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\texpect(adapter).toBeInstanceOf(JsonAdapter)\n\t\t\texpect(adapter.schemaConfigPipe).toBeDefined()\n\t\t\texpect(adapter.supportedFieldTypes).toEqual(['string', 'number', 'boolean', 'null', 'object', 'array', 'date'])\n\t\t})\n\n\t\ttest('auto-wired Instance hooks register connect/disconnect keyed by class', async () => {\n\t\t\tconst { Instance: Inst } = await import('../../../instance')\n\t\t\tconst onSpy = (await import('vitest')).vi.spyOn(Inst, 'on').mockImplementation(() => {})\n\n\t\t\tJsonAdapter.create({ filePath })\n\n\t\t\texpect(onSpy).toHaveBeenCalledWith('start', expect.any(Function), expect.objectContaining({ class: JsonAdapter }))\n\t\t\texpect(onSpy).toHaveBeenCalledWith('close', expect.any(Function), expect.objectContaining({ class: JsonAdapter }))\n\n\t\t\tonSpy.mockRestore()\n\t\t})\n\t})\n\n\tdescribe('json adapter — surface parity with in-memory', () => {\n\t\ttest('CRUD: create, find, update, delete round-trip', async () => {\n\t\t\tconst schema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'u')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.field('age', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\n\t\t\tconst use = adapter.use(schema, { table: 'users' })\n\n\t\t\tconst created = await use.createMany([\n\t\t\t\t{ id: 'u1', name: 'Alice', age: 30 },\n\t\t\t\t{ id: 'u2', name: 'Bob', age: 20 },\n\t\t\t])\n\t\t\texpect(created).toHaveLength(2)\n\n\t\t\tconst found = await use.findOne(FilterGroup.create().eq('id', 'u1'))\n\t\t\texpect(found).toEqual({ id: 'u1', name: 'Alice', age: 30 })\n\n\t\t\tconst many = await use.findMany(FilterGroup.create().gt('age', 19), {\n\t\t\t\torderBy: [new OrderBy('age', 'asc')],\n\t\t\t})\n\t\t\texpect(many).toEqual([\n\t\t\t\t{ id: 'u2', name: 'Bob', age: 20 },\n\t\t\t\t{ id: 'u1', name: 'Alice', age: 30 },\n\t\t\t])\n\n\t\t\tawait use.updateOne(FilterGroup.create().eq('id', 'u1'), { age: 31 })\n\t\t\tconst updated = await use.findOne(FilterGroup.create().eq('id', 'u1'))\n\t\t\texpect(updated?.age).toBe(31)\n\n\t\t\tconst deleted = await use.deleteOne(FilterGroup.create().eq('id', 'u2'))\n\t\t\texpect(deleted).toEqual({ id: 'u2', name: 'Bob', age: 20 })\n\n\t\t\tconst remaining = await use.findMany(FilterGroup.create())\n\t\t\texpect(remaining).toHaveLength(1)\n\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('supports nested filters, ordering, select, offset and limit', async () => {\n\t\t\tconst schema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'u')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.field('age', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'users' })\n\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'u1', name: 'Alice', age: 30 },\n\t\t\t\t{ id: 'u2', name: 'Bob', age: 20 },\n\t\t\t\t{ id: 'u3', name: 'Carol', age: 40 },\n\t\t\t])\n\n\t\t\tconst builtGroup = FilterGroup.create().and([\n\t\t\t\t(q) => q.gt('age', 19),\n\t\t\t\t(q) => q.or([(g) => g.eq('name', 'Alice'), (g) => g.eq('name', 'Carol')]),\n\t\t\t])\n\t\t\tconst options = { orderBy: [new OrderBy('age', 'desc')], offset: 1, limit: 1, select: ['id', 'name'] as const }\n\t\t\tconst rows = await use.findMany(builtGroup, options)\n\t\t\texpect(rows).toEqual([{ id: 'u1', name: 'Alice' }])\n\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('count delegates to the in-memory store and persists separately from query options', async () => {\n\t\t\tconst schema = Schema.from('count_users')\n\t\t\t\t.pk('id', v.string(), () => 'u')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'count_users' })\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'u1', name: 'Alice' },\n\t\t\t\t{ id: 'u2', name: 'Bob' },\n\t\t\t\t{ id: 'u3', name: 'Alice' },\n\t\t\t])\n\n\t\t\tawait expect(use.count(FilterGroup.create().eq('name', 'Alice'))).resolves.toBe(2)\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('iterateMany with batchSize matches findMany through in-memory delegation', async () => {\n\t\t\tconst schema = Schema.from('json_iter_users')\n\t\t\t\t.pk('id', v.string(), () => 'u')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.field('age', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'json_iter_users' })\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'u1', name: 'Alice', age: 30 },\n\t\t\t\t{ id: 'u2', name: 'Bob', age: 20 },\n\t\t\t\t{ id: 'u3', name: 'Carol', age: 40 },\n\t\t\t\t{ id: 'u4', name: 'Dan', age: 50 },\n\t\t\t])\n\n\t\t\tconst filter = FilterGroup.create().gt('age', 19)\n\t\t\tconst options = {\n\t\t\t\torderBy: [new OrderBy('age', 'desc')],\n\t\t\t\toffset: 1,\n\t\t\t\tlimit: 2,\n\t\t\t\tselect: ['id', 'name'],\n\t\t\t}\n\t\t\tconst expected = await use.findMany(filter, options)\n\t\t\tconst rows: Record<string, unknown>[] = []\n\t\t\tfor await (const row of use.iterateMany(filter, { ...options, batchSize: 2 })) {\n\t\t\t\trows.push(row)\n\t\t\t}\n\t\t\texpect(rows).toEqual(expected)\n\t\t\texpect(rows).toEqual([\n\t\t\t\t{ id: 'u3', name: 'Carol' },\n\t\t\t\t{ id: 'u1', name: 'Alice' },\n\t\t\t])\n\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('supports update operators and rollback on failed session', async () => {\n\t\t\tconst schema = Schema.from('docs')\n\t\t\t\t.pk('id', v.string(), () => 'd1')\n\t\t\t\t.field('count', v.number())\n\t\t\t\t.field('tags', v.array(v.string()))\n\t\t\t\t.field('meta', v.object({ a: v.number() }))\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'docs' })\n\n\t\t\tawait use.createOne({ id: 'd1', count: 1, tags: ['x'], meta: { a: 1 } })\n\n\t\t\tawait adapter.session(async () => {\n\t\t\t\tawait use.updateOne(FilterGroup.create().eq('id', 'd1'), {\n\t\t\t\t\tcount: new IncOp('count', 2),\n\t\t\t\t\ttags: new PushOp('tags', 'y'),\n\t\t\t\t\tmeta: new PatchOp('meta', { a: 9 }),\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tawait expect(\n\t\t\t\tadapter.session(async () => {\n\t\t\t\t\tawait use.updateOne(FilterGroup.create().eq('id', 'd1'), { tags: new PullOp('tags', 'x') })\n\t\t\t\t\tthrow new Error('boom')\n\t\t\t\t}),\n\t\t\t).rejects.toThrow('boom')\n\n\t\t\tconst row = await use.findOne(FilterGroup.create().eq('id', 'd1'))\n\t\t\texpect(row).toEqual({ id: 'd1', count: 3, tags: ['x', 'y'], meta: { a: 9 } })\n\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('upsertOne inserts when missing and updates when existing', async () => {\n\t\t\tconst schema = Schema.from('items')\n\t\t\t\t.pk('id', v.string(), () => 'i')\n\t\t\t\t.field('val', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'items' })\n\n\t\t\tconst inserted = await use.upsertOne(FilterGroup.create().eq('id', 'i1'), { id: 'i1', val: 10 }, [])\n\t\t\texpect(inserted).toEqual({ id: 'i1', val: 10 })\n\n\t\t\tconst updated = await use.upsertOne(FilterGroup.create().eq('id', 'i1'), { id: 'i1', val: 10 }, [new IncOp('val', 5)])\n\t\t\texpect(updated).toEqual({ id: 'i1', val: 15 })\n\n\t\t\tawait adapter.disconnect()\n\t\t})\n\t})\n\n\tdescribe('json adapter — persistence', () => {\n\t\ttest('state persists across adapter restarts', async () => {\n\t\t\tconst schema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'u')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\n\t\t\tconst a1 = JsonAdapter.create({ filePath })\n\t\t\tawait a1.connect()\n\t\t\tconst use1 = a1.use(schema, { table: 'users' })\n\t\t\tawait use1.createMany([\n\t\t\t\t{ id: 'u1', name: 'Alice' },\n\t\t\t\t{ id: 'u2', name: 'Bob' },\n\t\t\t])\n\t\t\tawait a1.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst use2 = a2.use(schema, { table: 'users' })\n\t\t\tconst rows = await use2.findMany(FilterGroup.create())\n\t\t\texpect(rows).toHaveLength(2)\n\t\t\texpect(rows.map((r) => r.name).sort()).toEqual(['Alice', 'Bob'])\n\t\t\tawait a2.disconnect()\n\t\t})\n\n\t\ttest('failed session does not persist rolled-back state to disk', async () => {\n\t\t\tconst schema = Schema.from('docs')\n\t\t\t\t.pk('id', v.string(), () => 'd')\n\t\t\t\t.field('val', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst a1 = JsonAdapter.create({ filePath })\n\t\t\tawait a1.connect()\n\t\t\tconst use1 = a1.use(schema, { table: 'docs' })\n\t\t\tawait use1.createOne({ id: 'd1', val: 1 })\n\t\t\tawait a1.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst use2 = a2.use(schema, { table: 'docs' })\n\t\t\tawait expect(\n\t\t\t\ta2.session(async () => {\n\t\t\t\t\tawait use2.updateOne(FilterGroup.create().eq('id', 'd1'), { val: 999 })\n\t\t\t\t\tthrow new Error('rollback')\n\t\t\t\t}),\n\t\t\t).rejects.toThrow('rollback')\n\t\t\tawait a2.disconnect()\n\n\t\t\tconst a3 = JsonAdapter.create({ filePath })\n\t\t\tawait a3.connect()\n\t\t\tconst use3 = a3.use(schema, { table: 'docs' })\n\t\t\tconst row = await use3.findOne(FilterGroup.create().eq('id', 'd1'))\n\t\t\texpect(row?.val).toBe(1)\n\t\t\tawait a3.disconnect()\n\t\t})\n\t})\n\n\tdescribe('json adapter — atomic writes', () => {\n\t\ttest('file is valid JSON after every write', async () => {\n\t\t\tconst { readFile: rf } = await import('node:fs/promises')\n\t\t\tconst schema = Schema.from('items')\n\t\t\t\t.pk('id', v.string(), () => 'i')\n\t\t\t\t.field('val', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'items' })\n\n\t\t\tfor (let i = 0; i < 10; i++) {\n\t\t\t\tawait use.createOne({ id: `i${i}`, val: i })\n\t\t\t\tconst raw = await rf(filePath, 'utf-8')\n\t\t\t\texpect(() => JSON.parse(raw)).not.toThrow()\n\t\t\t}\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('no .tmp files remain after writes complete', async () => {\n\t\t\tconst { readdir } = await import('node:fs/promises')\n\t\t\tconst schema = Schema.from('items')\n\t\t\t\t.pk('id', v.string(), () => 'i')\n\t\t\t\t.field('val', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'items' })\n\n\t\t\tfor (let i = 0; i < 5; i++) {\n\t\t\t\tawait use.createOne({ id: `i${i}`, val: i })\n\t\t\t}\n\t\t\tawait adapter.disconnect()\n\n\t\t\tconst files = await readdir(tmpDir)\n\t\t\tconst tmpFiles = files.filter((f) => f.includes('.tmp.'))\n\t\t\texpect(tmpFiles).toHaveLength(0)\n\t\t})\n\t})\n\n\tdescribe('json adapter — aggregate delegation', () => {\n\t\tconst orderSchema = Schema.from('orders')\n\t\t\t.pk('id', v.string(), () => 'o')\n\t\t\t.field('amount', v.number())\n\t\t\t.field('region', v.string())\n\t\t\t.build()\n\n\t\tconst orderSeed = [\n\t\t\t{ id: 'o1', amount: 100, region: 'us' },\n\t\t\t{ id: 'o2', amount: 200, region: 'eu' },\n\t\t\t{ id: 'o3', amount: 150, region: 'us' },\n\t\t]\n\n\t\tasync function makeSeededAdapter() {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(orderSchema, { table: 'orders' })\n\t\t\tawait use.createMany(orderSeed)\n\t\t\treturn adapter\n\t\t}\n\n\t\ttest('aggregateOps matches in-memory adapter', () => {\n\t\t\tconst json = JsonAdapter.create({ filePath })\n\t\t\tconst inMem = InMemoryAdapter.create({})\n\t\t\texpect(json.aggregateOps).toEqual(inMem.aggregateOps)\n\t\t\texpect(json.aggregateOps).toEqual(['count', 'countDistinct', 'sum', 'avg', 'min', 'max'])\n\t\t})\n\n\t\ttest('count returns identical result to in-memory', async () => {\n\t\t\tconst adapter = await makeSeededAdapter()\n\t\t\tconst result = await adapter.aggregate(orderSchema, { table: 'orders' }, {\n\t\t\t\taggregates: [{ fn: 'count', alias: 'total' }],\n\t\t\t\tgroupBy: [],\n\t\t\t})\n\t\t\texpect(result).toEqual([{ total: 3 }])\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('countDistinct returns identical result to in-memory', async () => {\n\t\t\tconst adapter = await makeSeededAdapter()\n\t\t\tconst result = await adapter.aggregate(orderSchema, { table: 'orders' }, {\n\t\t\t\taggregates: [{ fn: 'countDistinct', field: 'region', alias: 'uniqueRegions' }],\n\t\t\t\tgroupBy: [],\n\t\t\t})\n\t\t\texpect(result).toEqual([{ uniqueRegions: 2 }])\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('sum, avg, min, max return identical results to in-memory', async () => {\n\t\t\tconst adapter = await makeSeededAdapter()\n\t\t\tconst result = await adapter.aggregate(orderSchema, { table: 'orders' }, {\n\t\t\t\taggregates: [\n\t\t\t\t\t{ fn: 'sum', field: 'amount', alias: 'totalAmount' },\n\t\t\t\t\t{ fn: 'avg', field: 'amount', alias: 'avgAmount' },\n\t\t\t\t\t{ fn: 'min', field: 'amount', alias: 'minAmount' },\n\t\t\t\t\t{ fn: 'max', field: 'amount', alias: 'maxAmount' },\n\t\t\t\t],\n\t\t\t\tgroupBy: [],\n\t\t\t})\n\t\t\texpect(result).toEqual([{ totalAmount: 450, avgAmount: 150, minAmount: 100, maxAmount: 200 }])\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('groupBy returns identical results to in-memory', async () => {\n\t\t\tconst adapter = await makeSeededAdapter()\n\t\t\tconst result = await adapter.aggregate(orderSchema, { table: 'orders' }, {\n\t\t\t\taggregates: [\n\t\t\t\t\t{ fn: 'count', alias: 'cnt' },\n\t\t\t\t\t{ fn: 'sum', field: 'amount', alias: 'total' },\n\t\t\t\t],\n\t\t\t\tgroupBy: ['region'],\n\t\t\t})\n\t\t\tconst sorted = result.sort((a, b) => String(a.region).localeCompare(String(b.region)))\n\t\t\texpect(sorted).toEqual([\n\t\t\t\t{ region: 'eu', cnt: 1, total: 200 },\n\t\t\t\t{ region: 'us', cnt: 2, total: 250 },\n\t\t\t])\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('where pre-filter returns identical results to in-memory', async () => {\n\t\t\tconst adapter = await makeSeededAdapter()\n\t\t\tconst result = await adapter.aggregate(orderSchema, { table: 'orders' }, {\n\t\t\t\twhere: FilterGroup.create().gt('amount', 100),\n\t\t\t\taggregates: [{ fn: 'count', alias: 'cnt' }],\n\t\t\t\tgroupBy: [],\n\t\t\t})\n\t\t\texpect(result).toEqual([{ cnt: 2 }])\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('having post-filter returns identical results to in-memory', async () => {\n\t\t\tconst adapter = await makeSeededAdapter()\n\t\t\tconst result = await adapter.aggregate(orderSchema, { table: 'orders' }, {\n\t\t\t\taggregates: [{ fn: 'count', alias: 'cnt' }],\n\t\t\t\tgroupBy: ['region'],\n\t\t\t\thaving: FilterGroup.create().gt('cnt', 1),\n\t\t\t})\n\t\t\texpect(result).toEqual([{ region: 'us', cnt: 2 }])\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('aggregate delegates to same in-memory instance used by CRUD', async () => {\n\t\t\tconst schema = Schema.from('items')\n\t\t\t\t.pk('id', v.string(), () => 'i')\n\t\t\t\t.field('val', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'items' })\n\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'i1', val: 10 },\n\t\t\t\t{ id: 'i2', val: 20 },\n\t\t\t])\n\n\t\t\tconst spec: AggregateSpec = {\n\t\t\t\taggregates: [{ fn: 'sum', field: 'val', alias: 'total' }],\n\t\t\t\tgroupBy: [],\n\t\t\t}\n\t\t\tconst result = await adapter.aggregate(schema, { table: 'items' }, spec)\n\t\t\texpect(result).toEqual([{ total: 30 }])\n\n\t\t\tawait use.createOne({ id: 'i3', val: 30 })\n\t\t\tconst result2 = await adapter.aggregate(schema, { table: 'items' }, spec)\n\t\t\texpect(result2).toEqual([{ total: 60 }])\n\n\t\t\tawait adapter.disconnect()\n\t\t})\n\t})\n\n\tdescribe('json adapter — concurrent writes serialize', () => {\n\t\ttest('parallel writes from same process all persist', async () => {\n\t\t\tconst schema = Schema.from('items')\n\t\t\t\t.pk('id', v.string(), () => 'i')\n\t\t\t\t.field('val', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'items' })\n\n\t\t\tawait Promise.all(Array.from({ length: 20 }, (_, i) => use.createOne({ id: `i${i}`, val: i })))\n\n\t\t\tconst rows = await use.findMany(FilterGroup.create())\n\t\t\texpect(rows).toHaveLength(20)\n\t\t\tawait adapter.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst use2 = a2.use(schema, { table: 'items' })\n\t\t\tconst reloaded = await use2.findMany(FilterGroup.create())\n\t\t\texpect(reloaded).toHaveLength(20)\n\t\t\tawait a2.disconnect()\n\t\t})\n\t})\n\n\tdescribe('json adapter — migration storage', () => {\n\t\ttest('recordMigration + loadMigrations round-trip', async () => {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\n\t\t\tawait adapter.recordMigration('m-001', 1000)\n\t\t\tawait adapter.recordMigration('m-002', 2000)\n\n\t\t\tconst loaded = await adapter.loadMigrations()\n\t\t\texpect(loaded).toHaveLength(2)\n\t\t\texpect(loaded).toEqual([\n\t\t\t\t{ id: 'm-001', appliedAt: 1000 },\n\t\t\t\t{ id: 'm-002', appliedAt: 2000 },\n\t\t\t])\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('migration tracker persists across adapter restarts', async () => {\n\t\t\tconst a1 = JsonAdapter.create({ filePath })\n\t\t\tawait a1.connect()\n\t\t\tawait a1.recordMigration('m-001', 1000)\n\t\t\tawait a1.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst loaded = await a2.loadMigrations()\n\t\t\texpect(loaded).toEqual([{ id: 'm-001', appliedAt: 1000 }])\n\t\t\tawait a2.disconnect()\n\t\t})\n\n\t\ttest('__migrations key in JSON file stores tracker data', async () => {\n\t\t\tconst { readFile: rf } = await import('node:fs/promises')\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tawait adapter.recordMigration('m-001', 1000)\n\t\t\tawait adapter.disconnect()\n\n\t\t\tconst raw = JSON.parse(await rf(filePath, 'utf-8'))\n\t\t\texpect(raw['__migrations']).toEqual([{ id: 'm-001', appliedAt: 1000 }])\n\t\t})\n\t})\n\n\tdescribe('json adapter — apply* methods', () => {\n\t\ttest('applyCreateTable persists table metadata', async () => {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tawait adapter.applyCreateTable({\n\t\t\t\tkind: 'createTable',\n\t\t\t\tname: 'users',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [{ name: 'email', type: 'string', unique: true }],\n\t\t\t})\n\t\t\tawait adapter.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst schemas = await a2.introspect()\n\t\t\texpect(schemas).toHaveLength(1)\n\t\t\texpect(schemas[0].name).toBe('users')\n\t\t\texpect(schemas[0].pk).toEqual({ name: 'id', type: 'string' })\n\t\t\texpect(schemas[0].fields).toEqual([{ name: 'email', type: 'string', nullable: false, unique: true }])\n\t\t\tawait a2.disconnect()\n\t\t})\n\n\t\ttest('applyAddIndex and applyDropIndex persist across restarts', async () => {\n\t\t\tconst a1 = JsonAdapter.create({ filePath })\n\t\t\tawait a1.connect()\n\t\t\tawait a1.applyCreateTable({ kind: 'createTable', name: 'users', pk: { name: 'id', type: 'string' }, fields: [] })\n\t\t\tawait a1.applyAddIndex({ kind: 'addIndex', table: 'users', on: ['email'], unique: true })\n\t\t\tawait a1.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst schemas = await a2.introspect()\n\t\t\texpect(schemas[0].indexes).toEqual([{ name: 'users_email_idx', on: ['email'], unique: true }])\n\t\t\tawait a2.applyDropIndex({ kind: 'dropIndex', name: 'users_email_idx' })\n\t\t\tawait a2.disconnect()\n\n\t\t\tconst a3 = JsonAdapter.create({ filePath })\n\t\t\tawait a3.connect()\n\t\t\tconst schemas2 = await a3.introspect()\n\t\t\texpect(schemas2[0].indexes).toEqual([])\n\t\t\tawait a3.disconnect()\n\t\t})\n\n\t\ttest('applyAddForeignKey and applyDropForeignKey persist across restarts', async () => {\n\t\t\tconst a1 = JsonAdapter.create({ filePath })\n\t\t\tawait a1.connect()\n\t\t\tawait a1.applyCreateTable({ kind: 'createTable', name: 'users', pk: { name: 'id', type: 'string' }, fields: [] })\n\t\t\tawait a1.applyCreateTable({ kind: 'createTable', name: 'posts', pk: { name: 'id', type: 'string' }, fields: [] })\n\t\t\tawait a1.applyAddForeignKey({ kind: 'addForeignKey', table: 'posts', on: 'authorId', references: { table: 'users', column: 'id' }, onDelete: 'cascade' })\n\t\t\tawait a1.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst schemas = await a2.introspect()\n\t\t\tconst posts = schemas.find((s) => s.name === 'posts')!\n\t\t\texpect(posts.foreignKeys).toEqual([{\n\t\t\t\tname: 'posts_authorId_fk',\n\t\t\t\ton: 'authorId',\n\t\t\t\treferences: { table: 'users', column: 'id' },\n\t\t\t\tonDelete: 'cascade',\n\t\t\t}])\n\t\t\tawait a2.applyDropForeignKey({ kind: 'dropForeignKey', table: 'posts', name: 'posts_authorId_fk' })\n\t\t\tawait a2.disconnect()\n\n\t\t\tconst a3 = JsonAdapter.create({ filePath })\n\t\t\tawait a3.connect()\n\t\t\tconst schemas2 = await a3.introspect()\n\t\t\tconst posts2 = schemas2.find((s) => s.name === 'posts')!\n\t\t\texpect(posts2.foreignKeys).toEqual([])\n\t\t\tawait a3.disconnect()\n\t\t})\n\t})\n\n\tdescribe('json adapter — acquireMigrationLock', () => {\n\t\ttest('file lock serialises concurrent calls', async () => {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\n\t\t\tconst order: string[] = []\n\t\t\tlet resolveGate!: () => void\n\t\t\tconst gate = new Promise<void>((r) => { resolveGate = r })\n\n\t\t\tconst p1 = adapter.acquireMigrationLock(async () => {\n\t\t\t\torder.push('p1-start')\n\t\t\t\tawait gate\n\t\t\t\torder.push('p1-end')\n\t\t\t})\n\t\t\tawait new Promise((r) => setTimeout(r, 100))\n\t\t\tconst p2 = adapter.acquireMigrationLock(async () => {\n\t\t\t\torder.push('p2')\n\t\t\t})\n\n\t\t\tawait new Promise((r) => setTimeout(r, 100))\n\t\t\texpect(order).toEqual(['p1-start'])\n\n\t\t\tresolveGate()\n\t\t\tawait Promise.all([p1, p2])\n\t\t\texpect(order).toEqual(['p1-start', 'p1-end', 'p2'])\n\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('lock file is cleaned up after successful execution', async () => {\n\t\t\tconst { access } = await import('node:fs/promises')\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\n\t\t\tawait adapter.acquireMigrationLock(async () => { /* no-op */ })\n\n\t\t\tawait expect(access(filePath + '.lock')).rejects.toThrow()\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('lock file is cleaned up after failed execution', async () => {\n\t\t\tconst { access } = await import('node:fs/promises')\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\n\t\t\tawait expect(\n\t\t\t\tadapter.acquireMigrationLock(async () => { throw new Error('boom') }),\n\t\t\t).rejects.toThrow('boom')\n\n\t\t\tawait expect(access(filePath + '.lock')).rejects.toThrow()\n\t\t\tawait adapter.disconnect()\n\t\t})\n\t})\n\n\tdescribe('json adapter — end-to-end migrations via Migrator', () => {\n\t\tconst UserSchema = Schema.from('users')\n\t\t\t.pk('id', v.string(), () => `u-${Math.random().toString(36).slice(2)}`)\n\t\t\t.field('email', v.string())\n\t\t\t.build()\n\n\t\tfunction makeEnv() {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tconst repo = new Repo({ adapter, resolve: (s) => ({ table: s.name }) })\n\t\t\treturn { adapter, repo }\n\t\t}\n\n\t\ttest('end-to-end migration run covering every declarative variant + execute', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tawait adapter.connect()\n\n\t\t\tconst migrations: Migration<typeof adapter>[] = [\n\t\t\t\t{\n\t\t\t\t\tid: '0001-create',\n\t\t\t\t\tchanges: [\n\t\t\t\t\t\t{ kind: 'createTable', name: 'users', pk: { name: 'id', type: 'string' }, fields: [{ name: 'email', type: 'string' }] },\n\t\t\t\t\t\t{ kind: 'createTable', name: 'posts', pk: { name: 'id', type: 'string' }, fields: [{ name: 'title', type: 'string' }, { name: 'authorId', type: 'string' }] },\n\t\t\t\t\t\t{ kind: 'addIndex', table: 'users', on: ['email'], unique: true },\n\t\t\t\t\t\t{ kind: 'addForeignKey', table: 'posts', on: 'authorId', references: { table: 'users', column: 'id' } },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tid: '0002-evolve',\n\t\t\t\t\tchanges: [\n\t\t\t\t\t\t{ kind: 'addField', table: 'users', field: { name: 'age', type: 'number', nullable: true } },\n\t\t\t\t\t\t{ kind: 'modifyField', table: 'users', name: 'email', to: { name: 'email', type: 'string', unique: true, nullable: true } },\n\t\t\t\t\t\t{ kind: 'renameField', table: 'posts', from: 'title', to: 'headline' },\n\t\t\t\t\t\t{ kind: 'renameTable', from: 'posts', to: 'articles' },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tid: '0003-cleanup',\n\t\t\t\t\tchanges: [\n\t\t\t\t\t\t{ kind: 'dropField', table: 'users', name: 'age' },\n\t\t\t\t\t\t{ kind: 'dropForeignKey', table: 'posts', name: 'posts_authorId_fk' },\n\t\t\t\t\t\t{ kind: 'dropIndex', name: 'users_email_idx' },\n\t\t\t\t\t\t{ kind: 'dropTable', name: 'articles' },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tid: '0004-seed',\n\t\t\t\t\tchanges: [{\n\t\t\t\t\t\tkind: 'execute',\n\t\t\t\t\t\tup: async (r) => {\n\t\t\t\t\t\t\tawait r.on(UserSchema).one().create({ id: 'seed-1', email: 'test@example.com' })\n\t\t\t\t\t\t},\n\t\t\t\t\t}],\n\t\t\t\t},\n\t\t\t]\n\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations(migrations).build()\n\t\t\tconst result = await migrator.up()\n\t\t\texpect(result.ran).toEqual(['0001-create', '0002-evolve', '0003-cleanup', '0004-seed'])\n\n\t\t\tconst recorded = await adapter.loadMigrations()\n\t\t\texpect(recorded).toHaveLength(4)\n\n\t\t\tconst row = await repo.on(UserSchema).one().id('seed-1').find()\n\t\t\texpect(row).not.toBeNull()\n\t\t\texpect(row!.email).toBe('test@example.com')\n\n\t\t\t// Verify via re-reading the file\n\t\t\tawait adapter.disconnect()\n\t\t\tconst { adapter: a2, repo: r2 } = makeEnv()\n\t\t\tawait a2.connect()\n\t\t\tconst recorded2 = await a2.loadMigrations()\n\t\t\texpect(recorded2).toHaveLength(4)\n\n\t\t\tconst row2 = await r2.on(UserSchema).one().id('seed-1').find()\n\t\t\texpect(row2!.email).toBe('test@example.com')\n\t\t\tawait a2.disconnect()\n\t\t})\n\n\t\ttest('file lock serialises concurrent migrator.up() runs', async () => {\n\t\t\tconst order: string[] = []\n\t\t\tlet resolveGate!: () => void\n\t\t\tconst gate = new Promise<void>((r) => { resolveGate = r })\n\n\t\t\tconst migrations: Migration<JsonAdapter>[] = [\n\t\t\t\t{\n\t\t\t\t\tid: '0001',\n\t\t\t\t\tchanges: [{\n\t\t\t\t\t\tkind: 'execute',\n\t\t\t\t\t\tup: async () => {\n\t\t\t\t\t\t\torder.push('0001-start')\n\t\t\t\t\t\t\tawait gate\n\t\t\t\t\t\t\torder.push('0001-end')\n\t\t\t\t\t\t},\n\t\t\t\t\t}],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tid: '0002',\n\t\t\t\t\tchanges: [{ kind: 'execute', up: async () => { order.push('0002') } }],\n\t\t\t\t},\n\t\t\t]\n\n\t\t\tconst adapter1 = JsonAdapter.create({ filePath })\n\t\t\tawait adapter1.connect()\n\t\t\tconst repo1 = new Repo({ adapter: adapter1, resolve: (s) => ({ table: s.name }) })\n\t\t\tconst migrator1 = Migrator.from(repo1, adapter1).migrations(migrations).build()\n\n\t\t\tconst adapter2 = JsonAdapter.create({ filePath })\n\t\t\tawait adapter2.connect()\n\t\t\tconst repo2 = new Repo({ adapter: adapter2, resolve: (s) => ({ table: s.name }) })\n\t\t\tconst migrator2 = Migrator.from(repo2, adapter2).migrations(migrations).build()\n\n\t\t\tconst p1 = migrator1.up()\n\t\t\tconst p2 = migrator2.up()\n\n\t\t\tawait new Promise((r) => setTimeout(r, 200))\n\t\t\texpect(order).toEqual(['0001-start'])\n\n\t\t\tresolveGate()\n\t\t\tawait Promise.all([p1, p2])\n\n\t\t\texpect(order).toEqual(['0001-start', '0001-end', '0002'])\n\n\t\t\tawait adapter1.disconnect()\n\t\t\tawait adapter2.disconnect()\n\t\t})\n\n\t\ttest('tx:true rollback leaves file unchanged on failure', async () => {\n\t\t\tconst { adapter } = makeEnv()\n\t\t\tawait adapter.connect()\n\n\t\t\tawait adapter.applyCreateTable({ kind: 'createTable', name: 'users', pk: { name: 'id', type: 'string' }, fields: [] })\n\t\t\tawait adapter.disconnect()\n\n\t\t\tconst { adapter: a2, repo: r2 } = makeEnv()\n\t\t\tawait a2.connect()\n\n\t\t\tconst m: Migration<typeof a2> = {\n\t\t\t\tid: '0001-will-fail',\n\t\t\t\tchanges: [\n\t\t\t\t\t{ kind: 'addIndex', table: 'users', on: ['email'] },\n\t\t\t\t\t{\n\t\t\t\t\t\tkind: 'execute',\n\t\t\t\t\t\tup: async (r) => {\n\t\t\t\t\t\t\tawait r.on(UserSchema).one().create({ id: 'partial', email: 'fail@test.com' })\n\t\t\t\t\t\t\tthrow new Error('mid-migration boom')\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t}\n\t\t\tconst migrator = Migrator.from(r2, a2).migrations([m]).build()\n\t\t\tawait expect(migrator.up()).rejects.toThrow(OrmMigrationError)\n\t\t\tawait a2.disconnect()\n\n\t\t\tconst { adapter: a3, repo: r3 } = makeEnv()\n\t\t\tawait a3.connect()\n\t\t\tconst recorded = await a3.loadMigrations()\n\t\t\texpect(recorded).toHaveLength(0)\n\t\t\tconst schemas = await a3.introspect()\n\t\t\tconst users = schemas.find((s) => s.name === 'users')!\n\t\t\texpect(users.indexes).toEqual([])\n\t\t\tconst row = await r3.on(UserSchema).one().id('partial').find()\n\t\t\texpect(row).toBeNull()\n\t\t\tawait a3.disconnect()\n\t\t})\n\n\t\ttest('introspect() round-trip: apply* then introspect returns matching descriptors', async () => {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\n\t\t\tawait adapter.applyCreateTable({\n\t\t\t\tkind: 'createTable',\n\t\t\t\tname: 'users',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [\n\t\t\t\t\t{ name: 'email', type: 'string', unique: true },\n\t\t\t\t\t{ name: 'age', type: 'number', nullable: true },\n\t\t\t\t],\n\t\t\t})\n\t\t\tawait adapter.applyAddIndex({ kind: 'addIndex', table: 'users', on: ['email'], unique: true })\n\t\t\tawait adapter.applyCreateTable({\n\t\t\t\tkind: 'createTable',\n\t\t\t\tname: 'posts',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [{ name: 'authorId', type: 'string' }],\n\t\t\t})\n\t\t\tawait adapter.applyAddForeignKey({\n\t\t\t\tkind: 'addForeignKey',\n\t\t\t\ttable: 'posts',\n\t\t\t\ton: 'authorId',\n\t\t\t\treferences: { table: 'users', column: 'id' },\n\t\t\t\tonDelete: 'cascade',\n\t\t\t})\n\t\t\tawait adapter.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst schemas = await a2.introspect()\n\n\t\t\tconst users = schemas.find((s) => s.name === 'users')!\n\t\t\texpect(users.pk).toEqual({ name: 'id', type: 'string' })\n\t\t\texpect(users.fields).toEqual([\n\t\t\t\t{ name: 'email', type: 'string', nullable: false, unique: true },\n\t\t\t\t{ name: 'age', type: 'number', nullable: true },\n\t\t\t])\n\t\t\texpect(users.indexes).toEqual([{ name: 'users_email_idx', on: ['email'], unique: true }])\n\n\t\t\tconst posts = schemas.find((s) => s.name === 'posts')!\n\t\t\texpect(posts.pk).toEqual({ name: 'id', type: 'string' })\n\t\t\texpect(posts.fields).toEqual([{ name: 'authorId', type: 'string', nullable: false }])\n\t\t\texpect(posts.foreignKeys).toEqual([{\n\t\t\t\tname: 'posts_authorId_fk',\n\t\t\t\ton: 'authorId',\n\t\t\t\treferences: { table: 'users', column: 'id' },\n\t\t\t\tonDelete: 'cascade',\n\t\t\t}])\n\n\t\t\tawait a2.disconnect()\n\t\t})\n\n\t\ttest('metadata keys do not leak into data stores', async () => {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tawait adapter.recordMigration('m-001', 1000)\n\t\t\tawait adapter.applyCreateTable({ kind: 'createTable', name: 'users', pk: { name: 'id', type: 'string' }, fields: [] })\n\n\t\t\texpect(adapter.stores.has('__migrations')).toBe(false)\n\t\t\texpect(adapter.stores.has('__tables')).toBe(false)\n\t\t\tawait adapter.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\texpect(a2.stores.has('__migrations')).toBe(false)\n\t\t\texpect(a2.stores.has('__tables')).toBe(false)\n\t\t\tawait a2.disconnect()\n\t\t})\n\t})\n}\n"],"mappings":"4OAAA,OAAS,QAAAA,EAAM,YAAAC,EAAU,UAAAC,EAAQ,UAAAC,EAAQ,aAAAC,MAAiB,cAE1D,OAAS,KAAAC,MAAS,WAclB,IAAMC,EAAqB,IAC1BC,EAAE,OAAO,CACR,SAAUA,EAAE,OAAO,CACpB,CAAC,EAEWC,EAAN,cAA0BC,EAAaH,EAAoBI,CAAU,CAAE,CACpE,iBAAmBH,EAAE,OAAO,CAAE,MAAOA,EAAE,OAAO,CAAE,CAAC,EAEjDI,GAAYC,EAAgB,OAAO,CAAC,CAAC,EAErC,oBAAsB,KAAKD,GAAU,oBACrC,aAAe,KAAKA,GAAU,aAC9B,UAAY,KAAKA,GAAU,UAC3B,aAAe,KAAKA,GAAU,aAEvCE,GAA6B,QAAQ,QAAQ,EAEnC,YAAYC,EAAmC,CACxD,MAAMA,CAAM,CACb,CAEA,IAAI,QAAS,CACZ,OAAO,KAAKH,GAAU,MACvB,CAEAI,IAAqB,CACpB,IAAMC,EAA+B,CAAC,EAEhCC,EAAa,CAAC,GAAG,KAAKN,GAAU,WAAW,OAAO,CAAC,EACrDM,EAAW,OAAS,IAAGD,EAAI,aAAkBC,GAEjD,IAAMC,EAAkC,CAAC,EACzC,OAAW,CAACC,EAAMC,CAAI,IAAK,KAAKT,GAAU,OAAO,QAAQ,EACxDO,EAAOC,CAAI,EAAI,CAAE,GAAIC,EAAK,GAAI,OAAQ,OAAO,YAAYA,EAAK,OAAO,QAAQ,CAAC,CAAE,EAE7E,OAAO,KAAKF,CAAM,EAAE,OAAS,IAAGF,EAAI,SAAcE,GAEtD,IAAMG,EAAmC,CAAC,EAC1C,OAAW,CAACF,EAAMC,CAAI,IAAK,KAAKT,GAAU,QAAQ,QAAQ,EACzDU,EAAQF,CAAI,EAAI,CAAE,MAAOC,EAAK,MAAO,GAAI,CAAC,GAAGA,EAAK,EAAE,EAAG,OAAQA,EAAK,MAAO,EAExE,OAAO,KAAKC,CAAO,EAAE,OAAS,IAAGL,EAAI,UAAeK,GAExD,IAAMC,EAAuC,CAAC,EAC9C,OAAW,CAACH,EAAMC,CAAI,IAAK,KAAKT,GAAU,YAAY,QAAQ,EAC7DW,EAAYH,CAAI,EAAI,CACnB,MAAOC,EAAK,MACZ,GAAIA,EAAK,GACT,WAAYA,EAAK,WACjB,GAAIA,EAAK,SAAW,CAAE,SAAUA,EAAK,QAAS,EAAI,CAAC,EACnD,GAAIA,EAAK,SAAW,CAAE,SAAUA,EAAK,QAAS,EAAI,CAAC,CACpD,EAEG,OAAO,KAAKE,CAAW,EAAE,OAAS,IAAGN,EAAI,cAAmBM,GAEhE,OAAW,CAACH,EAAMI,CAAK,IAAK,KAAK,OAAO,QAAQ,EAAG,CAClD,IAAMC,EAAmD,CAAC,EAC1D,OAAW,CAACC,EAAIC,CAAG,IAAKH,EAAM,QAAQ,EACrCC,EAAQC,CAAE,EAAIC,EAEfV,EAAIG,CAAI,EAAIK,CACb,CAEA,OAAO,KAAK,UAAUR,CAAG,CAC1B,CAEA,KAAMW,IAA8B,CACnC,IAAMC,EAAO,KAAKb,GAAW,EACvBc,EAAU,KAAK,OAAO,SAAW,QAAU,QAAQ,IAAM,IAAM,KAAK,IAAI,EAC9E,MAAMC,EAAUD,EAASD,EAAM,OAAO,EACtC,MAAMG,EAAOF,EAAS,KAAK,OAAO,QAAQ,CAC3C,CAEAG,IAAgC,CAC/B,IAAMC,EAAO,KAAKpB,GAAY,KAAK,IAAM,KAAKc,GAAa,CAAC,EAC5D,YAAKd,GAAcoB,EAAK,MAAM,IAAM,CAAC,CAAC,EAC/BA,CACR,CAEA,MAAM,SAAyB,CAC9B,IAAIC,EACJ,GAAI,CACHA,EAAM,MAAMC,EAAS,KAAK,OAAO,SAAU,OAAO,CACnD,MAAQ,CACP,MACD,CACA,IAAMP,EAAO,KAAK,MAAMM,CAAG,EAG3B,GADA,KAAKvB,GAAU,WAAW,MAAM,EAC5B,MAAM,QAAQiB,EAAK,YAAe,EACrC,QAAWQ,KAAKR,EAAK,aACpB,KAAKjB,GAAU,WAAW,IAAIyB,EAAE,GAAI,CAAE,GAAIA,EAAE,GAAI,UAAWA,EAAE,SAAU,CAAC,EAK1E,GADA,KAAKzB,GAAU,OAAO,MAAM,EACxBiB,EAAK,UAAe,OAAOA,EAAK,UAAgB,SACnD,OAAW,CAACT,EAAMC,CAAI,IAAK,OAAO,QAAQQ,EAAK,QAAkC,EAAG,CACnF,IAAMS,EAAS,IAAI,IACnB,GAAIjB,EAAK,OACR,OAAW,CAACkB,EAAWC,CAAK,IAAK,OAAO,QAAQnB,EAAK,MAAsC,EAC1FiB,EAAO,IAAIC,EAAWC,CAAK,EAG7B,KAAK5B,GAAU,OAAO,IAAIQ,EAAM,CAAE,GAAIC,EAAK,GAAI,OAAAiB,CAAO,CAAC,CACxD,CAID,GADA,KAAK1B,GAAU,QAAQ,MAAM,EACzBiB,EAAK,WAAgB,OAAOA,EAAK,WAAiB,SACrD,OAAW,CAACT,EAAMC,CAAI,IAAK,OAAO,QAAQQ,EAAK,SAAmC,EACjF,KAAKjB,GAAU,QAAQ,IAAIQ,EAAM,CAAE,MAAOC,EAAK,MAAO,GAAIA,EAAK,GAAI,OAAQA,EAAK,MAAO,CAAC,EAK1F,GADA,KAAKT,GAAU,YAAY,MAAM,EAC7BiB,EAAK,eAAoB,OAAOA,EAAK,eAAqB,SAC7D,OAAW,CAACT,EAAMC,CAAI,IAAK,OAAO,QAAQQ,EAAK,aAAuC,EACrF,KAAKjB,GAAU,YAAY,IAAIQ,EAAMC,CAAI,EAI3C,KAAK,OAAO,MAAM,EAClB,OAAW,CAACD,EAAMK,CAAO,IAAK,OAAO,QAAQI,CAAI,EAAG,CACnD,GAAIT,EAAK,WAAW,IAAI,EAAG,SAC3B,IAAMI,EAAQ,IAAI,IAClB,OAAW,CAACE,EAAIC,CAAG,IAAK,OAAO,QAAQF,CAAkD,EACxFD,EAAM,IAAIE,EAAIC,CAAG,EAElB,KAAK,OAAO,IAAIP,EAAMI,CAAK,CAC5B,CACD,CAEA,MAAM,YAA4B,CACjC,MAAM,KAAKS,GAAe,CAC3B,CAEA,MAAM,SAASQ,EAAmB1B,EAAiBW,EAAa,CAC/D,OAAO,KAAKd,GAAU,SAAS6B,EAAQ1B,EAAQW,CAAE,CAClD,CAEA,MAAM,WAAWe,EAAmB1B,EAAiBc,EAAiC,CACrF,IAAMa,EAAS,MAAM,KAAK9B,GAAU,WAAW6B,EAAQ1B,EAAQc,CAAI,EACnE,aAAM,KAAKI,GAAe,EACnBS,CACR,CAEA,MAAM,WAAWD,EAAmB1B,EAAiBW,EAAaiB,EAAoB,CACrF,IAAMD,EAAS,MAAM,KAAK9B,GAAU,WAAW6B,EAAQ1B,EAAQW,EAAIiB,CAAG,EACtE,OAAID,GAAQ,MAAM,KAAKT,GAAe,EAC/BS,CACR,CAEA,MAAM,WAAWD,EAAmB1B,EAAiBW,EAAa,CACjE,IAAMgB,EAAS,MAAM,KAAK9B,GAAU,WAAW6B,EAAQ1B,EAAQW,CAAE,EACjE,OAAIgB,GAAQ,MAAM,KAAKT,GAAe,EAC/BS,CACR,CAEA,MAAM,SAASD,EAAmB1B,EAAiB6B,EAAoBC,EAAwB,CAC9F,OAAO,KAAKjC,GAAU,SAAS6B,EAAQ1B,EAAQ6B,EAAOC,CAAO,CAC9D,CAEA,MAAM,MAAMJ,EAAmB1B,EAAiB6B,EAAoB,CACnE,OAAO,KAAKhC,GAAU,MAAM6B,EAAQ1B,EAAQ6B,CAAK,CAClD,CAEA,YAAYH,EAAmB1B,EAAiB6B,EAAoBC,EAAiC,CACpG,OAAO,KAAKjC,GAAU,YAAY6B,EAAQ1B,EAAQ6B,EAAOC,CAAO,CACjE,CAEA,MAAM,WAAWJ,EAAmB1B,EAAiB6B,EAAoBf,EAA+B,CACvG,IAAMa,EAAS,MAAM,KAAK9B,GAAU,WAAW6B,EAAQ1B,EAAQ6B,EAAOf,CAAI,EAC1E,OAAIa,EAAO,QAAQ,MAAM,KAAKT,GAAe,EACtCS,CACR,CAEA,MAAM,WAAWD,EAAmB1B,EAAiB+B,EAAqB,CACzE,IAAMJ,EAAS,MAAM,KAAK9B,GAAU,WAAW6B,EAAQ1B,EAAQ+B,CAAM,EACrE,OAAIJ,EAAO,QAAQ,MAAM,KAAKT,GAAe,EACtCS,CACR,CAEA,MAAM,UAAUD,EAAmB1B,EAAiB+B,EAAqBC,EAAiCJ,EAAoB,CAC7H,IAAMD,EAAS,MAAM,KAAK9B,GAAU,UAAU6B,EAAQ1B,EAAQ+B,EAAQC,EAAQJ,CAAG,EACjF,aAAM,KAAKV,GAAe,EACnBS,CACR,CAEA,MAAM,UAAUD,EAAmB1B,EAAiBiC,EAAqB,CACxE,OAAO,KAAKpC,GAAU,UAAU6B,EAAQ1B,EAAQiC,CAAI,CACrD,CAEA,MAAM,QAAWC,EAAkC,CAClD,OAAO,KAAKrC,GAAU,QAAQ,SAAY,CACzC,IAAM8B,EAAS,MAAMO,EAAG,EACxB,aAAM,KAAKhB,GAAe,EACnBS,CACR,CAAC,CACF,CAEA,MAAM,gBAA+D,CACpE,aAAM,KAAK,QAAQ,EACZ,KAAK9B,GAAU,eAAe,CACtC,CAEA,MAAM,gBAAgBsC,EAAYC,EAAkC,CACnE,MAAM,KAAKvC,GAAU,gBAAgBsC,EAAIC,CAAS,EAClD,MAAM,KAAKlB,GAAe,CAC3B,CAEA,MAAM,qBAAwBgB,EAAkC,CAC/D,IAAMG,EAAW,KAAK,OAAO,SAAW,QAClCC,EAAW,KAAK,IAAI,EAAI,IAC9B,OACC,GAAI,CAEH,MADe,MAAMC,EAAKF,EAAU,IAAI,GAC3B,MAAM,EACnB,KACD,OAASG,EAAU,CAClB,GAAIA,EAAI,OAAS,SAAU,MAAMA,EACjC,GAAI,KAAK,IAAI,EAAIF,EAAU,MAAM,IAAI,MAAM,sCAAsC,EACjF,MAAM,IAAI,QAAS,GAAM,WAAW,EAAG,EAAE,CAAC,CAC3C,CAED,GAAI,CACH,OAAO,MAAMJ,EAAG,CACjB,QAAE,CACD,MAAMO,EAAOJ,CAAQ,EAAE,MAAM,IAAM,CAAC,CAAC,CACtC,CACD,CAEA,MAAM,iBAAiBK,EAA+C,CACrE,MAAM,KAAK7C,GAAU,iBAAiB6C,CAAM,EAC5C,MAAM,KAAKxB,GAAe,CAC3B,CAEA,MAAM,eAAewB,EAAwC,CAC5D,MAAM,KAAK7C,GAAU,eAAe6C,CAAM,EAC1C,MAAM,KAAKxB,GAAe,CAC3B,CAEA,MAAM,cAAcwB,EAA4C,CAC/D,MAAM,KAAK7C,GAAU,cAAc6C,CAAM,EACzC,MAAM,KAAKxB,GAAe,CAC3B,CAEA,MAAM,eAAewB,EAAwC,CAC5D,MAAM,KAAK7C,GAAU,eAAe6C,CAAM,EAC1C,MAAM,KAAKxB,GAAe,CAC3B,CAEA,MAAM,iBAAiBwB,EAA+C,CACrE,MAAM,KAAK7C,GAAU,iBAAiB6C,CAAM,EAC5C,MAAM,KAAKxB,GAAe,CAC3B,CAEA,MAAM,iBAAiBwB,EAA0C,CAChE,MAAM,KAAK7C,GAAU,iBAAiB6C,CAAM,EAC5C,MAAM,KAAKxB,GAAe,CAC3B,CAEA,MAAM,iBAAiBwB,EAA0C,CAChE,MAAM,KAAK7C,GAAU,iBAAiB6C,CAAM,EAC5C,MAAM,KAAKxB,GAAe,CAC3B,CAEA,MAAM,cAAcwB,EAAuC,CAC1D,MAAM,KAAK7C,GAAU,cAAc6C,CAAM,EACzC,MAAM,KAAKxB,GAAe,CAC3B,CAEA,MAAM,eAAewB,EAAwC,CAC5D,MAAM,KAAK7C,GAAU,eAAe6C,CAAM,EAC1C,MAAM,KAAKxB,GAAe,CAC3B,CAEA,MAAM,mBAAmBwB,EAA4C,CACpE,MAAM,KAAK7C,GAAU,mBAAmB6C,CAAM,EAC9C,MAAM,KAAKxB,GAAe,CAC3B,CAEA,MAAM,oBAAoBwB,EAA6C,CACtE,MAAM,KAAK7C,GAAU,oBAAoB6C,CAAM,EAC/C,MAAM,KAAKxB,GAAe,CAC3B,CAEA,MAAM,YAA0C,CAC/C,OAAO,KAAKrB,GAAU,WAAW,CAClC,CACD","names":["open","readFile","rename","unlink","writeFile","v","jsonConnectionPipe","v","JsonAdapter","configurable","OrmAdapter","#inMemory","InMemoryAdapter","#writeQueue","config","#serialize","obj","migrations","tables","name","meta","indexes","foreignKeys","store","records","pk","doc","#atomicWrite","data","tmpPath","writeFile","rename","#persistToDisk","next","raw","readFile","m","fields","fieldName","fspec","schema","result","ops","group","options","filter","create","spec","fn","id","appliedAt","lockPath","deadline","open","err","unlink","change"]}
1
+ {"version":3,"sources":["../../../../../src/orm/adapters/json/index.ts"],"sourcesContent":["import { open, readFile, rename, unlink, writeFile } from 'node:fs/promises'\n\nimport { v } from 'valleyed'\n\nimport { configurable } from '../../../utilities/configurable'\nimport type { FilterGroup } from '../../filter'\nimport type { DiscoveredSchema } from '../../migrations/introspection-types'\nimport type { AddFieldChange, AddForeignKeyChange, AddIndexChange, AnyFieldSpec, CreateTableChange, DropFieldChange, DropForeignKeyChange, DropIndexChange, DropTableChange, ModifyFieldChange, RenameFieldChange, RenameTableChange } from '../../migrations/types'\nimport { OrmAdapter, type AggregateSpec } from '../../orm-adapter'\nimport type { IterationQueryOptions, QueryOptions } from '../../query-options'\nimport type { AnySchema } from '../../schema'\nimport type { AnyUpdateOp } from '../../updates'\nimport { InMemoryAdapter, type InMemoryRepoConfig } from '../in-memory'\n\nexport type JsonAdapterConfig = InMemoryRepoConfig\n\nconst jsonConnectionPipe = () =>\n\tv.object({\n\t\tfilePath: v.string(),\n\t})\n\nexport class JsonAdapter extends configurable(jsonConnectionPipe, OrmAdapter) {\n\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\n\treadonly #inMemory = InMemoryAdapter.create({})\n\n\treadonly supportedFieldTypes = this.#inMemory.supportedFieldTypes\n\treadonly queryableOps = this.#inMemory.queryableOps\n\treadonly updateOps = this.#inMemory.updateOps\n\treadonly aggregateOps = this.#inMemory.aggregateOps\n\n\t#writeQueue: Promise<void> = Promise.resolve()\n\n\tprotected constructor(config: typeof JsonAdapter.Config) {\n\t\tsuper(config)\n\t}\n\n\tget stores() {\n\t\treturn this.#inMemory.stores\n\t}\n\n\t#serialize(): string {\n\t\tconst obj: Record<string, unknown> = {}\n\n\t\tconst migrations = [...this.#inMemory.migrations.values()]\n\t\tif (migrations.length > 0) obj['__migrations'] = migrations\n\n\t\tconst tables: Record<string, unknown> = {}\n\t\tfor (const [name, meta] of this.#inMemory.tables.entries()) {\n\t\t\ttables[name] = { pk: meta.pk, fields: Object.fromEntries(meta.fields.entries()) }\n\t\t}\n\t\tif (Object.keys(tables).length > 0) obj['__tables'] = tables\n\n\t\tconst indexes: Record<string, unknown> = {}\n\t\tfor (const [name, meta] of this.#inMemory.indexes.entries()) {\n\t\t\tindexes[name] = { table: meta.table, on: [...meta.on], unique: meta.unique }\n\t\t}\n\t\tif (Object.keys(indexes).length > 0) obj['__indexes'] = indexes\n\n\t\tconst foreignKeys: Record<string, unknown> = {}\n\t\tfor (const [name, meta] of this.#inMemory.foreignKeys.entries()) {\n\t\t\tforeignKeys[name] = {\n\t\t\t\ttable: meta.table,\n\t\t\t\ton: meta.on,\n\t\t\t\treferences: meta.references,\n\t\t\t\t...(meta.onDelete ? { onDelete: meta.onDelete } : {}),\n\t\t\t\t...(meta.onUpdate ? { onUpdate: meta.onUpdate } : {}),\n\t\t\t}\n\t\t}\n\t\tif (Object.keys(foreignKeys).length > 0) obj['__foreignKeys'] = foreignKeys\n\n\t\tfor (const [name, store] of this.stores.entries()) {\n\t\t\tconst records: Record<string, Record<string, unknown>> = {}\n\t\t\tfor (const [pk, doc] of store.entries()) {\n\t\t\t\trecords[pk] = doc\n\t\t\t}\n\t\t\tobj[name] = records\n\t\t}\n\n\t\treturn JSON.stringify(obj)\n\t}\n\n\tasync #atomicWrite(): Promise<void> {\n\t\tconst data = this.#serialize()\n\t\tconst tmpPath = this.config.filePath + '.tmp.' + process.pid + '.' + Date.now()\n\t\tawait writeFile(tmpPath, data, 'utf-8')\n\t\tawait rename(tmpPath, this.config.filePath)\n\t}\n\n\t#persistToDisk(): Promise<void> {\n\t\tconst next = this.#writeQueue.then(() => this.#atomicWrite())\n\t\tthis.#writeQueue = next.catch(() => {})\n\t\treturn next\n\t}\n\n\tasync connect(): Promise<void> {\n\t\tlet raw: string\n\t\ttry {\n\t\t\traw = await readFile(this.config.filePath, 'utf-8')\n\t\t} catch {\n\t\t\treturn\n\t\t}\n\t\tconst data = JSON.parse(raw) as Record<string, unknown>\n\n\t\tthis.#inMemory.migrations.clear()\n\t\tif (Array.isArray(data['__migrations'])) {\n\t\t\tfor (const m of data['__migrations'] as Array<{ id: string; appliedAt: number }>) {\n\t\t\t\tthis.#inMemory.migrations.set(m.id, { id: m.id, appliedAt: m.appliedAt })\n\t\t\t}\n\t\t}\n\n\t\tthis.#inMemory.tables.clear()\n\t\tif (data['__tables'] && typeof data['__tables'] === 'object') {\n\t\t\tfor (const [name, meta] of Object.entries(data['__tables'] as Record<string, any>)) {\n\t\t\t\tconst fields = new Map<string, AnyFieldSpec>()\n\t\t\t\tif (meta.fields) {\n\t\t\t\t\tfor (const [fieldName, fspec] of Object.entries(meta.fields as Record<string, AnyFieldSpec>)) {\n\t\t\t\t\t\tfields.set(fieldName, fspec)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.#inMemory.tables.set(name, { pk: meta.pk, fields })\n\t\t\t}\n\t\t}\n\n\t\tthis.#inMemory.indexes.clear()\n\t\tif (data['__indexes'] && typeof data['__indexes'] === 'object') {\n\t\t\tfor (const [name, meta] of Object.entries(data['__indexes'] as Record<string, any>)) {\n\t\t\t\tthis.#inMemory.indexes.set(name, { table: meta.table, on: meta.on, unique: meta.unique })\n\t\t\t}\n\t\t}\n\n\t\tthis.#inMemory.foreignKeys.clear()\n\t\tif (data['__foreignKeys'] && typeof data['__foreignKeys'] === 'object') {\n\t\t\tfor (const [name, meta] of Object.entries(data['__foreignKeys'] as Record<string, any>)) {\n\t\t\t\tthis.#inMemory.foreignKeys.set(name, meta)\n\t\t\t}\n\t\t}\n\n\t\tthis.stores.clear()\n\t\tfor (const [name, records] of Object.entries(data)) {\n\t\t\tif (name.startsWith('__')) continue\n\t\t\tconst store = new Map<string, Record<string, unknown>>()\n\t\t\tfor (const [pk, doc] of Object.entries(records as Record<string, Record<string, unknown>>)) {\n\t\t\t\tstore.set(pk, doc)\n\t\t\t}\n\t\t\tthis.stores.set(name, store)\n\t\t}\n\t}\n\n\tasync disconnect(): Promise<void> {\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync findByPk(schema: AnySchema, config: unknown, pk: unknown) {\n\t\treturn this.#inMemory.findByPk(schema, config, pk)\n\t}\n\n\tasync createMany(schema: AnySchema, config: unknown, data: Record<string, unknown>[]) {\n\t\tconst result = await this.#inMemory.createMany(schema, config, data)\n\t\tawait this.#persistToDisk()\n\t\treturn result\n\t}\n\n\tasync updateByPk(schema: AnySchema, config: unknown, pk: unknown, ops: AnyUpdateOp[]) {\n\t\tconst result = await this.#inMemory.updateByPk(schema, config, pk, ops)\n\t\tif (result) await this.#persistToDisk()\n\t\treturn result\n\t}\n\n\tasync deleteByPk(schema: AnySchema, config: unknown, pk: unknown) {\n\t\tconst result = await this.#inMemory.deleteByPk(schema, config, pk)\n\t\tif (result) await this.#persistToDisk()\n\t\treturn result\n\t}\n\n\tasync findMany(schema: AnySchema, config: unknown, group: FilterGroup, options?: QueryOptions) {\n\t\treturn this.#inMemory.findMany(schema, config, group, options)\n\t}\n\n\tasync count(schema: AnySchema, config: unknown, group: FilterGroup) {\n\t\treturn this.#inMemory.count(schema, config, group)\n\t}\n\n\titerateMany(schema: AnySchema, config: unknown, group: FilterGroup, options?: IterationQueryOptions) {\n\t\treturn this.#inMemory.iterateMany(schema, config, group, options)\n\t}\n\n\tasync updateMany(schema: AnySchema, config: unknown, group: FilterGroup, data: Record<string, unknown>) {\n\t\tconst result = await this.#inMemory.updateMany(schema, config, group, data)\n\t\tif (result.length) await this.#persistToDisk()\n\t\treturn result\n\t}\n\n\tasync deleteMany(schema: AnySchema, config: unknown, filter: FilterGroup) {\n\t\tconst result = await this.#inMemory.deleteMany(schema, config, filter)\n\t\tif (result.length) await this.#persistToDisk()\n\t\treturn result\n\t}\n\n\tasync upsertOne(schema: AnySchema, config: unknown, filter: FilterGroup, create: Record<string, unknown>, ops: AnyUpdateOp[]) {\n\t\tconst result = await this.#inMemory.upsertOne(schema, config, filter, create, ops)\n\t\tawait this.#persistToDisk()\n\t\treturn result\n\t}\n\n\tasync aggregate(schema: AnySchema, config: unknown, spec: AggregateSpec) {\n\t\treturn this.#inMemory.aggregate(schema, config, spec)\n\t}\n\n\tasync session<T>(fn: () => Promise<T>): Promise<T> {\n\t\treturn this.#inMemory.session(async () => {\n\t\t\tconst result = await fn()\n\t\t\tawait this.#persistToDisk()\n\t\t\treturn result\n\t\t})\n\t}\n\n\tasync loadMigrations(): Promise<{ id: string; appliedAt: number }[]> {\n\t\tawait this.connect()\n\t\treturn this.#inMemory.loadMigrations()\n\t}\n\n\tasync recordMigration(id: string, appliedAt: number): Promise<void> {\n\t\tawait this.#inMemory.recordMigration(id, appliedAt)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync acquireMigrationLock<T>(fn: () => Promise<T>): Promise<T> {\n\t\tconst lockPath = this.config.filePath + '.lock'\n\t\tconst deadline = Date.now() + 30_000\n\t\twhile (true) {\n\t\t\ttry {\n\t\t\t\tconst handle = await open(lockPath, 'wx')\n\t\t\t\tawait handle.close()\n\t\t\t\tbreak\n\t\t\t} catch (err: any) {\n\t\t\t\tif (err.code !== 'EEXIST') throw err\n\t\t\t\tif (Date.now() > deadline) throw new Error('Timed out waiting for migration lock')\n\t\t\t\tawait new Promise((r) => setTimeout(r, 50))\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\treturn await fn()\n\t\t} finally {\n\t\t\tawait unlink(lockPath).catch(() => {})\n\t\t}\n\t}\n\n\tasync applyCreateTable(change: CreateTableChange<any>): Promise<void> {\n\t\tawait this.#inMemory.applyCreateTable(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyDropTable(change: DropTableChange): Promise<void> {\n\t\tawait this.#inMemory.applyDropTable(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyAddField(change: AddFieldChange<any>): Promise<void> {\n\t\tawait this.#inMemory.applyAddField(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyDropField(change: DropFieldChange): Promise<void> {\n\t\tawait this.#inMemory.applyDropField(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyModifyField(change: ModifyFieldChange<any>): Promise<void> {\n\t\tawait this.#inMemory.applyModifyField(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyRenameTable(change: RenameTableChange): Promise<void> {\n\t\tawait this.#inMemory.applyRenameTable(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyRenameField(change: RenameFieldChange): Promise<void> {\n\t\tawait this.#inMemory.applyRenameField(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyAddIndex(change: AddIndexChange): Promise<void> {\n\t\tawait this.#inMemory.applyAddIndex(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyDropIndex(change: DropIndexChange): Promise<void> {\n\t\tawait this.#inMemory.applyDropIndex(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyAddForeignKey(change: AddForeignKeyChange): Promise<void> {\n\t\tawait this.#inMemory.applyAddForeignKey(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyDropForeignKey(change: DropForeignKeyChange): Promise<void> {\n\t\tawait this.#inMemory.applyDropForeignKey(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync introspect(): Promise<DiscoveredSchema[]> {\n\t\treturn this.#inMemory.introspect()\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect, beforeEach, afterEach } = import.meta.vitest\n\tconst { FilterGroup } = await import('../../filter')\n\tconst { OrderBy } = await import('../../query-options')\n\tconst { Schema } = await import('../../schema')\n\tconst { IncOp, PatchOp, PullOp, PushOp } = await import('../../updates')\n\tconst { v } = await import('valleyed')\n\tconst { mkdtemp, rm } = await import('node:fs/promises')\n\tconst { tmpdir } = await import('node:os')\n\tconst { join } = await import('node:path')\n\tconst { Migrator } = await import('../../migrations/migrator')\n\tconst { Repo } = await import('../../repo/repo')\n\tconst { OrmMigrationError } = await import('../../errors/migration')\n\ttype Migration<A extends import('../base').OrmAdapterLike<any>> = import('../../migrations/types').Migration<A>\n\n\tlet tmpDir: string\n\tlet filePath: string\n\n\tbeforeEach(async () => {\n\t\ttmpDir = await mkdtemp(join(tmpdir(), 'json-adapter-'))\n\t\tfilePath = join(tmpDir, 'test.json')\n\t})\n\n\tafterEach(async () => {\n\t\tawait rm(tmpDir, { recursive: true, force: true })\n\t})\n\n\tdescribe('json adapter — class construction', () => {\n\t\ttest('JsonAdapter.create({ filePath }) produces a working adapter', () => {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\texpect(adapter).toBeInstanceOf(JsonAdapter)\n\t\t\texpect(adapter.schemaConfigPipe).toBeDefined()\n\t\t\texpect(adapter.supportedFieldTypes).toEqual(['string', 'number', 'boolean', 'null', 'object', 'array', 'date'])\n\t\t})\n\n\t\ttest('auto-wired Instance hooks register connect/disconnect keyed by class', async () => {\n\t\t\tconst { Instance: Inst } = await import('../../../instance')\n\t\t\tconst onSpy = (await import('vitest')).vi.spyOn(Inst, 'on').mockImplementation(() => {})\n\n\t\t\tJsonAdapter.create({ filePath })\n\n\t\t\texpect(onSpy).toHaveBeenCalledWith('start', expect.any(Function), expect.objectContaining({ class: JsonAdapter }))\n\t\t\texpect(onSpy).toHaveBeenCalledWith('close', expect.any(Function), expect.objectContaining({ class: JsonAdapter }))\n\n\t\t\tonSpy.mockRestore()\n\t\t})\n\t})\n\n\tdescribe('json adapter — surface parity with in-memory', () => {\n\t\ttest('CRUD: create, find, update, delete round-trip', async () => {\n\t\t\tconst schema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'u')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.field('age', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\n\t\t\tconst use = adapter.use(schema, { table: 'users' })\n\n\t\t\tconst created = await use.createMany([\n\t\t\t\t{ id: 'u1', name: 'Alice', age: 30 },\n\t\t\t\t{ id: 'u2', name: 'Bob', age: 20 },\n\t\t\t])\n\t\t\texpect(created).toHaveLength(2)\n\n\t\t\tconst found = await use.findOne(FilterGroup.create().eq('id', 'u1'))\n\t\t\texpect(found).toEqual({ id: 'u1', name: 'Alice', age: 30 })\n\n\t\t\tconst many = await use.findMany(FilterGroup.create().gt('age', 19), {\n\t\t\t\torderBy: [new OrderBy('age', 'asc')],\n\t\t\t})\n\t\t\texpect(many).toEqual([\n\t\t\t\t{ id: 'u2', name: 'Bob', age: 20 },\n\t\t\t\t{ id: 'u1', name: 'Alice', age: 30 },\n\t\t\t])\n\n\t\t\tawait use.updateOne(FilterGroup.create().eq('id', 'u1'), { age: 31 })\n\t\t\tconst updated = await use.findOne(FilterGroup.create().eq('id', 'u1'))\n\t\t\texpect(updated?.age).toBe(31)\n\n\t\t\tconst deleted = await use.deleteOne(FilterGroup.create().eq('id', 'u2'))\n\t\t\texpect(deleted).toEqual({ id: 'u2', name: 'Bob', age: 20 })\n\n\t\t\tconst remaining = await use.findMany(FilterGroup.create())\n\t\t\texpect(remaining).toHaveLength(1)\n\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('supports nested filters, ordering, select, offset and limit', async () => {\n\t\t\tconst schema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'u')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.field('age', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'users' })\n\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'u1', name: 'Alice', age: 30 },\n\t\t\t\t{ id: 'u2', name: 'Bob', age: 20 },\n\t\t\t\t{ id: 'u3', name: 'Carol', age: 40 },\n\t\t\t])\n\n\t\t\tconst builtGroup = FilterGroup.create().and([\n\t\t\t\t(q) => q.gt('age', 19),\n\t\t\t\t(q) => q.or([(g) => g.eq('name', 'Alice'), (g) => g.eq('name', 'Carol')]),\n\t\t\t])\n\t\t\tconst options = { orderBy: [new OrderBy('age', 'desc')], offset: 1, limit: 1, select: ['id', 'name'] as const }\n\t\t\tconst rows = await use.findMany(builtGroup, options)\n\t\t\texpect(rows).toEqual([{ id: 'u1', name: 'Alice' }])\n\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('count delegates to the in-memory store and persists separately from query options', async () => {\n\t\t\tconst schema = Schema.from('count_users')\n\t\t\t\t.pk('id', v.string(), () => 'u')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'count_users' })\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'u1', name: 'Alice' },\n\t\t\t\t{ id: 'u2', name: 'Bob' },\n\t\t\t\t{ id: 'u3', name: 'Alice' },\n\t\t\t])\n\n\t\t\tawait expect(use.count(FilterGroup.create().eq('name', 'Alice'))).resolves.toBe(2)\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('iterateMany with batchSize matches findMany through in-memory delegation', async () => {\n\t\t\tconst schema = Schema.from('json_iter_users')\n\t\t\t\t.pk('id', v.string(), () => 'u')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.field('age', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'json_iter_users' })\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'u1', name: 'Alice', age: 30 },\n\t\t\t\t{ id: 'u2', name: 'Bob', age: 20 },\n\t\t\t\t{ id: 'u3', name: 'Carol', age: 40 },\n\t\t\t\t{ id: 'u4', name: 'Dan', age: 50 },\n\t\t\t])\n\n\t\t\tconst filter = FilterGroup.create().gt('age', 19)\n\t\t\tconst options = {\n\t\t\t\torderBy: [new OrderBy('age', 'desc')],\n\t\t\t\toffset: 1,\n\t\t\t\tlimit: 2,\n\t\t\t\tselect: ['id', 'name'],\n\t\t\t}\n\t\t\tconst expected = await use.findMany(filter, options)\n\t\t\tconst rows: Record<string, unknown>[] = []\n\t\t\tfor await (const row of use.iterateMany(filter, { ...options, batchSize: 2 })) {\n\t\t\t\trows.push(row)\n\t\t\t}\n\t\t\texpect(rows).toEqual(expected)\n\t\t\texpect(rows).toEqual([\n\t\t\t\t{ id: 'u3', name: 'Carol' },\n\t\t\t\t{ id: 'u1', name: 'Alice' },\n\t\t\t])\n\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('supports update operators and rollback on failed session', async () => {\n\t\t\tconst schema = Schema.from('docs')\n\t\t\t\t.pk('id', v.string(), () => 'd1')\n\t\t\t\t.field('count', v.number())\n\t\t\t\t.field('tags', v.array(v.string()))\n\t\t\t\t.field('meta', v.object({ a: v.number() }))\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'docs' })\n\n\t\t\tawait use.createOne({ id: 'd1', count: 1, tags: ['x'], meta: { a: 1 } })\n\n\t\t\tawait adapter.session(async () => {\n\t\t\t\tawait use.updateOne(FilterGroup.create().eq('id', 'd1'), {\n\t\t\t\t\tcount: new IncOp('count', 2),\n\t\t\t\t\ttags: new PushOp('tags', 'y'),\n\t\t\t\t\tmeta: new PatchOp('meta', { a: 9 }),\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tawait expect(\n\t\t\t\tadapter.session(async () => {\n\t\t\t\t\tawait use.updateOne(FilterGroup.create().eq('id', 'd1'), { tags: new PullOp('tags', 'x') })\n\t\t\t\t\tthrow new Error('boom')\n\t\t\t\t}),\n\t\t\t).rejects.toThrow('boom')\n\n\t\t\tconst row = await use.findOne(FilterGroup.create().eq('id', 'd1'))\n\t\t\texpect(row).toEqual({ id: 'd1', count: 3, tags: ['x', 'y'], meta: { a: 9 } })\n\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('upsertOne inserts when missing and updates when existing', async () => {\n\t\t\tconst schema = Schema.from('items')\n\t\t\t\t.pk('id', v.string(), () => 'i')\n\t\t\t\t.field('val', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'items' })\n\n\t\t\tconst inserted = await use.upsertOne(FilterGroup.create().eq('id', 'i1'), { id: 'i1', val: 10 }, [])\n\t\t\texpect(inserted).toEqual({ id: 'i1', val: 10 })\n\n\t\t\tconst updated = await use.upsertOne(FilterGroup.create().eq('id', 'i1'), { id: 'i1', val: 10 }, [new IncOp('val', 5)])\n\t\t\texpect(updated).toEqual({ id: 'i1', val: 15 })\n\n\t\t\tawait adapter.disconnect()\n\t\t})\n\t})\n\n\tdescribe('json adapter — persistence', () => {\n\t\ttest('state persists across adapter restarts', async () => {\n\t\t\tconst schema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'u')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\n\t\t\tconst a1 = JsonAdapter.create({ filePath })\n\t\t\tawait a1.connect()\n\t\t\tconst use1 = a1.use(schema, { table: 'users' })\n\t\t\tawait use1.createMany([\n\t\t\t\t{ id: 'u1', name: 'Alice' },\n\t\t\t\t{ id: 'u2', name: 'Bob' },\n\t\t\t])\n\t\t\tawait a1.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst use2 = a2.use(schema, { table: 'users' })\n\t\t\tconst rows = await use2.findMany(FilterGroup.create())\n\t\t\texpect(rows).toHaveLength(2)\n\t\t\texpect(rows.map((r) => r.name).sort()).toEqual(['Alice', 'Bob'])\n\t\t\tawait a2.disconnect()\n\t\t})\n\n\t\ttest('failed session does not persist rolled-back state to disk', async () => {\n\t\t\tconst schema = Schema.from('docs')\n\t\t\t\t.pk('id', v.string(), () => 'd')\n\t\t\t\t.field('val', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst a1 = JsonAdapter.create({ filePath })\n\t\t\tawait a1.connect()\n\t\t\tconst use1 = a1.use(schema, { table: 'docs' })\n\t\t\tawait use1.createOne({ id: 'd1', val: 1 })\n\t\t\tawait a1.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst use2 = a2.use(schema, { table: 'docs' })\n\t\t\tawait expect(\n\t\t\t\ta2.session(async () => {\n\t\t\t\t\tawait use2.updateOne(FilterGroup.create().eq('id', 'd1'), { val: 999 })\n\t\t\t\t\tthrow new Error('rollback')\n\t\t\t\t}),\n\t\t\t).rejects.toThrow('rollback')\n\t\t\tawait a2.disconnect()\n\n\t\t\tconst a3 = JsonAdapter.create({ filePath })\n\t\t\tawait a3.connect()\n\t\t\tconst use3 = a3.use(schema, { table: 'docs' })\n\t\t\tconst row = await use3.findOne(FilterGroup.create().eq('id', 'd1'))\n\t\t\texpect(row?.val).toBe(1)\n\t\t\tawait a3.disconnect()\n\t\t})\n\t})\n\n\tdescribe('json adapter — atomic writes', () => {\n\t\ttest('file is valid JSON after every write', async () => {\n\t\t\tconst { readFile: rf } = await import('node:fs/promises')\n\t\t\tconst schema = Schema.from('items')\n\t\t\t\t.pk('id', v.string(), () => 'i')\n\t\t\t\t.field('val', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'items' })\n\n\t\t\tfor (let i = 0; i < 10; i++) {\n\t\t\t\tawait use.createOne({ id: `i${i}`, val: i })\n\t\t\t\tconst raw = await rf(filePath, 'utf-8')\n\t\t\t\texpect(() => JSON.parse(raw)).not.toThrow()\n\t\t\t}\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('no .tmp files remain after writes complete', async () => {\n\t\t\tconst { readdir } = await import('node:fs/promises')\n\t\t\tconst schema = Schema.from('items')\n\t\t\t\t.pk('id', v.string(), () => 'i')\n\t\t\t\t.field('val', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'items' })\n\n\t\t\tfor (let i = 0; i < 5; i++) {\n\t\t\t\tawait use.createOne({ id: `i${i}`, val: i })\n\t\t\t}\n\t\t\tawait adapter.disconnect()\n\n\t\t\tconst files = await readdir(tmpDir)\n\t\t\tconst tmpFiles = files.filter((f) => f.includes('.tmp.'))\n\t\t\texpect(tmpFiles).toHaveLength(0)\n\t\t})\n\t})\n\n\tdescribe('json adapter — aggregate delegation', () => {\n\t\tconst orderSchema = Schema.from('orders')\n\t\t\t.pk('id', v.string(), () => 'o')\n\t\t\t.field('amount', v.number())\n\t\t\t.field('region', v.string())\n\t\t\t.build()\n\n\t\tconst orderSeed = [\n\t\t\t{ id: 'o1', amount: 100, region: 'us' },\n\t\t\t{ id: 'o2', amount: 200, region: 'eu' },\n\t\t\t{ id: 'o3', amount: 150, region: 'us' },\n\t\t]\n\n\t\tasync function makeSeededAdapter() {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(orderSchema, { table: 'orders' })\n\t\t\tawait use.createMany(orderSeed)\n\t\t\treturn adapter\n\t\t}\n\n\t\ttest('aggregateOps matches in-memory adapter', () => {\n\t\t\tconst json = JsonAdapter.create({ filePath })\n\t\t\tconst inMem = InMemoryAdapter.create({})\n\t\t\texpect(json.aggregateOps).toEqual(inMem.aggregateOps)\n\t\t\texpect(json.aggregateOps).toEqual(['count', 'countDistinct', 'sum', 'avg', 'min', 'max'])\n\t\t})\n\n\t\ttest('count returns identical result to in-memory', async () => {\n\t\t\tconst adapter = await makeSeededAdapter()\n\t\t\tconst result = await adapter.aggregate(orderSchema, { table: 'orders' }, {\n\t\t\t\taggregates: [{ fn: 'count', alias: 'total' }],\n\t\t\t\tgroupBy: [],\n\t\t\t})\n\t\t\texpect(result).toEqual([{ total: 3 }])\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('countDistinct returns identical result to in-memory', async () => {\n\t\t\tconst adapter = await makeSeededAdapter()\n\t\t\tconst result = await adapter.aggregate(orderSchema, { table: 'orders' }, {\n\t\t\t\taggregates: [{ fn: 'countDistinct', field: 'region', alias: 'uniqueRegions' }],\n\t\t\t\tgroupBy: [],\n\t\t\t})\n\t\t\texpect(result).toEqual([{ uniqueRegions: 2 }])\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('sum, avg, min, max return identical results to in-memory', async () => {\n\t\t\tconst adapter = await makeSeededAdapter()\n\t\t\tconst result = await adapter.aggregate(orderSchema, { table: 'orders' }, {\n\t\t\t\taggregates: [\n\t\t\t\t\t{ fn: 'sum', field: 'amount', alias: 'totalAmount' },\n\t\t\t\t\t{ fn: 'avg', field: 'amount', alias: 'avgAmount' },\n\t\t\t\t\t{ fn: 'min', field: 'amount', alias: 'minAmount' },\n\t\t\t\t\t{ fn: 'max', field: 'amount', alias: 'maxAmount' },\n\t\t\t\t],\n\t\t\t\tgroupBy: [],\n\t\t\t})\n\t\t\texpect(result).toEqual([{ totalAmount: 450, avgAmount: 150, minAmount: 100, maxAmount: 200 }])\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('groupBy returns identical results to in-memory', async () => {\n\t\t\tconst adapter = await makeSeededAdapter()\n\t\t\tconst result = await adapter.aggregate(orderSchema, { table: 'orders' }, {\n\t\t\t\taggregates: [\n\t\t\t\t\t{ fn: 'count', alias: 'cnt' },\n\t\t\t\t\t{ fn: 'sum', field: 'amount', alias: 'total' },\n\t\t\t\t],\n\t\t\t\tgroupBy: ['region'],\n\t\t\t})\n\t\t\tconst sorted = result.sort((a, b) => String(a.region).localeCompare(String(b.region)))\n\t\t\texpect(sorted).toEqual([\n\t\t\t\t{ region: 'eu', cnt: 1, total: 200 },\n\t\t\t\t{ region: 'us', cnt: 2, total: 250 },\n\t\t\t])\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('where pre-filter returns identical results to in-memory', async () => {\n\t\t\tconst adapter = await makeSeededAdapter()\n\t\t\tconst result = await adapter.aggregate(orderSchema, { table: 'orders' }, {\n\t\t\t\twhere: FilterGroup.create().gt('amount', 100),\n\t\t\t\taggregates: [{ fn: 'count', alias: 'cnt' }],\n\t\t\t\tgroupBy: [],\n\t\t\t})\n\t\t\texpect(result).toEqual([{ cnt: 2 }])\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('having post-filter returns identical results to in-memory', async () => {\n\t\t\tconst adapter = await makeSeededAdapter()\n\t\t\tconst result = await adapter.aggregate(orderSchema, { table: 'orders' }, {\n\t\t\t\taggregates: [{ fn: 'count', alias: 'cnt' }],\n\t\t\t\tgroupBy: ['region'],\n\t\t\t\thaving: FilterGroup.create().gt('cnt', 1),\n\t\t\t})\n\t\t\texpect(result).toEqual([{ region: 'us', cnt: 2 }])\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('aggregate delegates to same in-memory instance used by CRUD', async () => {\n\t\t\tconst schema = Schema.from('items')\n\t\t\t\t.pk('id', v.string(), () => 'i')\n\t\t\t\t.field('val', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'items' })\n\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'i1', val: 10 },\n\t\t\t\t{ id: 'i2', val: 20 },\n\t\t\t])\n\n\t\t\tconst spec: AggregateSpec = {\n\t\t\t\taggregates: [{ fn: 'sum', field: 'val', alias: 'total' }],\n\t\t\t\tgroupBy: [],\n\t\t\t}\n\t\t\tconst result = await adapter.aggregate(schema, { table: 'items' }, spec)\n\t\t\texpect(result).toEqual([{ total: 30 }])\n\n\t\t\tawait use.createOne({ id: 'i3', val: 30 })\n\t\t\tconst result2 = await adapter.aggregate(schema, { table: 'items' }, spec)\n\t\t\texpect(result2).toEqual([{ total: 60 }])\n\n\t\t\tawait adapter.disconnect()\n\t\t})\n\t})\n\n\tdescribe('json adapter — concurrent writes serialize', () => {\n\t\ttest('parallel writes from same process all persist', async () => {\n\t\t\tconst schema = Schema.from('items')\n\t\t\t\t.pk('id', v.string(), () => 'i')\n\t\t\t\t.field('val', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'items' })\n\n\t\t\tawait Promise.all(Array.from({ length: 20 }, (_, i) => use.createOne({ id: `i${i}`, val: i })))\n\n\t\t\tconst rows = await use.findMany(FilterGroup.create())\n\t\t\texpect(rows).toHaveLength(20)\n\t\t\tawait adapter.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst use2 = a2.use(schema, { table: 'items' })\n\t\t\tconst reloaded = await use2.findMany(FilterGroup.create())\n\t\t\texpect(reloaded).toHaveLength(20)\n\t\t\tawait a2.disconnect()\n\t\t})\n\t})\n\n\tdescribe('json adapter — migration storage', () => {\n\t\ttest('recordMigration + loadMigrations round-trip', async () => {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\n\t\t\tawait adapter.recordMigration('m-001', 1000)\n\t\t\tawait adapter.recordMigration('m-002', 2000)\n\n\t\t\tconst loaded = await adapter.loadMigrations()\n\t\t\texpect(loaded).toHaveLength(2)\n\t\t\texpect(loaded).toEqual([\n\t\t\t\t{ id: 'm-001', appliedAt: 1000 },\n\t\t\t\t{ id: 'm-002', appliedAt: 2000 },\n\t\t\t])\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('migration tracker persists across adapter restarts', async () => {\n\t\t\tconst a1 = JsonAdapter.create({ filePath })\n\t\t\tawait a1.connect()\n\t\t\tawait a1.recordMigration('m-001', 1000)\n\t\t\tawait a1.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst loaded = await a2.loadMigrations()\n\t\t\texpect(loaded).toEqual([{ id: 'm-001', appliedAt: 1000 }])\n\t\t\tawait a2.disconnect()\n\t\t})\n\n\t\ttest('__migrations key in JSON file stores tracker data', async () => {\n\t\t\tconst { readFile: rf } = await import('node:fs/promises')\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tawait adapter.recordMigration('m-001', 1000)\n\t\t\tawait adapter.disconnect()\n\n\t\t\tconst raw = JSON.parse(await rf(filePath, 'utf-8'))\n\t\t\texpect(raw['__migrations']).toEqual([{ id: 'm-001', appliedAt: 1000 }])\n\t\t})\n\t})\n\n\tdescribe('json adapter — apply* methods', () => {\n\t\ttest('applyCreateTable persists table metadata', async () => {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tawait adapter.applyCreateTable({\n\t\t\t\tkind: 'createTable',\n\t\t\t\tname: 'users',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [{ name: 'email', type: 'string', unique: true }],\n\t\t\t})\n\t\t\tawait adapter.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst schemas = await a2.introspect()\n\t\t\texpect(schemas).toHaveLength(1)\n\t\t\texpect(schemas[0].name).toBe('users')\n\t\t\texpect(schemas[0].pk).toEqual({ name: 'id', type: 'string' })\n\t\t\texpect(schemas[0].fields).toEqual([{ name: 'email', type: 'string', nullable: false, unique: true }])\n\t\t\tawait a2.disconnect()\n\t\t})\n\n\t\ttest('applyAddIndex and applyDropIndex persist across restarts', async () => {\n\t\t\tconst a1 = JsonAdapter.create({ filePath })\n\t\t\tawait a1.connect()\n\t\t\tawait a1.applyCreateTable({ kind: 'createTable', name: 'users', pk: { name: 'id', type: 'string' }, fields: [] })\n\t\t\tawait a1.applyAddIndex({ kind: 'addIndex', table: 'users', on: ['email'], unique: true })\n\t\t\tawait a1.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst schemas = await a2.introspect()\n\t\t\texpect(schemas[0].indexes).toEqual([{ name: 'users_email_idx', on: ['email'], unique: true }])\n\t\t\tawait a2.applyDropIndex({ kind: 'dropIndex', name: 'users_email_idx' })\n\t\t\tawait a2.disconnect()\n\n\t\t\tconst a3 = JsonAdapter.create({ filePath })\n\t\t\tawait a3.connect()\n\t\t\tconst schemas2 = await a3.introspect()\n\t\t\texpect(schemas2[0].indexes).toEqual([])\n\t\t\tawait a3.disconnect()\n\t\t})\n\n\t\ttest('applyAddForeignKey and applyDropForeignKey persist across restarts', async () => {\n\t\t\tconst a1 = JsonAdapter.create({ filePath })\n\t\t\tawait a1.connect()\n\t\t\tawait a1.applyCreateTable({ kind: 'createTable', name: 'users', pk: { name: 'id', type: 'string' }, fields: [] })\n\t\t\tawait a1.applyCreateTable({ kind: 'createTable', name: 'posts', pk: { name: 'id', type: 'string' }, fields: [] })\n\t\t\tawait a1.applyAddForeignKey({ kind: 'addForeignKey', table: 'posts', on: 'authorId', references: { table: 'users', column: 'id' }, onDelete: 'cascade' })\n\t\t\tawait a1.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst schemas = await a2.introspect()\n\t\t\tconst posts = schemas.find((s) => s.name === 'posts')!\n\t\t\texpect(posts.foreignKeys).toEqual([{\n\t\t\t\tname: 'posts_authorId_fk',\n\t\t\t\ton: 'authorId',\n\t\t\t\treferences: { table: 'users', column: 'id' },\n\t\t\t\tonDelete: 'cascade',\n\t\t\t}])\n\t\t\tawait a2.applyDropForeignKey({ kind: 'dropForeignKey', table: 'posts', name: 'posts_authorId_fk' })\n\t\t\tawait a2.disconnect()\n\n\t\t\tconst a3 = JsonAdapter.create({ filePath })\n\t\t\tawait a3.connect()\n\t\t\tconst schemas2 = await a3.introspect()\n\t\t\tconst posts2 = schemas2.find((s) => s.name === 'posts')!\n\t\t\texpect(posts2.foreignKeys).toEqual([])\n\t\t\tawait a3.disconnect()\n\t\t})\n\t})\n\n\tdescribe('json adapter — acquireMigrationLock', () => {\n\t\ttest('file lock serialises concurrent calls', async () => {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\n\t\t\tconst order: string[] = []\n\t\t\tlet resolveGate!: () => void\n\t\t\tconst gate = new Promise<void>((r) => { resolveGate = r })\n\n\t\t\tconst p1 = adapter.acquireMigrationLock(async () => {\n\t\t\t\torder.push('p1-start')\n\t\t\t\tawait gate\n\t\t\t\torder.push('p1-end')\n\t\t\t})\n\t\t\tawait new Promise((r) => setTimeout(r, 100))\n\t\t\tconst p2 = adapter.acquireMigrationLock(async () => {\n\t\t\t\torder.push('p2')\n\t\t\t})\n\n\t\t\tawait new Promise((r) => setTimeout(r, 100))\n\t\t\texpect(order).toEqual(['p1-start'])\n\n\t\t\tresolveGate()\n\t\t\tawait Promise.all([p1, p2])\n\t\t\texpect(order).toEqual(['p1-start', 'p1-end', 'p2'])\n\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('lock file is cleaned up after successful execution', async () => {\n\t\t\tconst { access } = await import('node:fs/promises')\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\n\t\t\tawait adapter.acquireMigrationLock(async () => { /* no-op */ })\n\n\t\t\tawait expect(access(filePath + '.lock')).rejects.toThrow()\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('lock file is cleaned up after failed execution', async () => {\n\t\t\tconst { access } = await import('node:fs/promises')\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\n\t\t\tawait expect(\n\t\t\t\tadapter.acquireMigrationLock(async () => { throw new Error('boom') }),\n\t\t\t).rejects.toThrow('boom')\n\n\t\t\tawait expect(access(filePath + '.lock')).rejects.toThrow()\n\t\t\tawait adapter.disconnect()\n\t\t})\n\t})\n\n\tdescribe('json adapter — end-to-end migrations via Migrator', () => {\n\t\tconst UserSchema = Schema.from('users')\n\t\t\t.pk('id', v.string(), () => `u-${Math.random().toString(36).slice(2)}`)\n\t\t\t.field('email', v.string())\n\t\t\t.build()\n\n\t\tfunction makeEnv() {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tconst repo = new Repo({ adapter, resolve: (s) => ({ table: s.name }) })\n\t\t\treturn { adapter, repo }\n\t\t}\n\n\t\ttest('end-to-end migration run covering every declarative variant + execute', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tawait adapter.connect()\n\n\t\t\tconst migrations: Migration<typeof adapter>[] = [\n\t\t\t\t{\n\t\t\t\t\tid: '0001-create',\n\t\t\t\t\tchanges: [\n\t\t\t\t\t\t{ kind: 'createTable', name: 'users', pk: { name: 'id', type: 'string' }, fields: [{ name: 'email', type: 'string' }] },\n\t\t\t\t\t\t{ kind: 'createTable', name: 'posts', pk: { name: 'id', type: 'string' }, fields: [{ name: 'title', type: 'string' }, { name: 'authorId', type: 'string' }] },\n\t\t\t\t\t\t{ kind: 'addIndex', table: 'users', on: ['email'], unique: true },\n\t\t\t\t\t\t{ kind: 'addForeignKey', table: 'posts', on: 'authorId', references: { table: 'users', column: 'id' } },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tid: '0002-evolve',\n\t\t\t\t\tchanges: [\n\t\t\t\t\t\t{ kind: 'addField', table: 'users', field: { name: 'age', type: 'number', nullable: true } },\n\t\t\t\t\t\t{ kind: 'modifyField', table: 'users', name: 'email', to: { name: 'email', type: 'string', unique: true, nullable: true } },\n\t\t\t\t\t\t{ kind: 'renameField', table: 'posts', from: 'title', to: 'headline' },\n\t\t\t\t\t\t{ kind: 'renameTable', from: 'posts', to: 'articles' },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tid: '0003-cleanup',\n\t\t\t\t\tchanges: [\n\t\t\t\t\t\t{ kind: 'dropField', table: 'users', name: 'age' },\n\t\t\t\t\t\t{ kind: 'dropForeignKey', table: 'posts', name: 'posts_authorId_fk' },\n\t\t\t\t\t\t{ kind: 'dropIndex', name: 'users_email_idx' },\n\t\t\t\t\t\t{ kind: 'dropTable', name: 'articles' },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tid: '0004-seed',\n\t\t\t\t\tchanges: [{\n\t\t\t\t\t\tkind: 'execute',\n\t\t\t\t\t\tup: async (r) => {\n\t\t\t\t\t\t\tawait r.on(UserSchema).one().create({ id: 'seed-1', email: 'test@example.com' })\n\t\t\t\t\t\t},\n\t\t\t\t\t}],\n\t\t\t\t},\n\t\t\t]\n\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations(migrations).build()\n\t\t\tconst result = await migrator.up()\n\t\t\texpect(result.ran).toEqual(['0001-create', '0002-evolve', '0003-cleanup', '0004-seed'])\n\n\t\t\tconst recorded = await adapter.loadMigrations()\n\t\t\texpect(recorded).toHaveLength(4)\n\n\t\t\tconst row = await repo.on(UserSchema).one().id('seed-1').find()\n\t\t\texpect(row).not.toBeNull()\n\t\t\texpect(row!.email).toBe('test@example.com')\n\n\t\t\t// Verify via re-reading the file\n\t\t\tawait adapter.disconnect()\n\t\t\tconst { adapter: a2, repo: r2 } = makeEnv()\n\t\t\tawait a2.connect()\n\t\t\tconst recorded2 = await a2.loadMigrations()\n\t\t\texpect(recorded2).toHaveLength(4)\n\n\t\t\tconst row2 = await r2.on(UserSchema).one().id('seed-1').find()\n\t\t\texpect(row2!.email).toBe('test@example.com')\n\t\t\tawait a2.disconnect()\n\t\t})\n\n\t\ttest('file lock serialises concurrent migrator.up() runs', async () => {\n\t\t\tconst order: string[] = []\n\t\t\tlet resolveGate!: () => void\n\t\t\tconst gate = new Promise<void>((r) => { resolveGate = r })\n\n\t\t\tconst migrations: Migration<JsonAdapter>[] = [\n\t\t\t\t{\n\t\t\t\t\tid: '0001',\n\t\t\t\t\tchanges: [{\n\t\t\t\t\t\tkind: 'execute',\n\t\t\t\t\t\tup: async () => {\n\t\t\t\t\t\t\torder.push('0001-start')\n\t\t\t\t\t\t\tawait gate\n\t\t\t\t\t\t\torder.push('0001-end')\n\t\t\t\t\t\t},\n\t\t\t\t\t}],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tid: '0002',\n\t\t\t\t\tchanges: [{ kind: 'execute', up: async () => { order.push('0002') } }],\n\t\t\t\t},\n\t\t\t]\n\n\t\t\tconst adapter1 = JsonAdapter.create({ filePath })\n\t\t\tawait adapter1.connect()\n\t\t\tconst repo1 = new Repo({ adapter: adapter1, resolve: (s) => ({ table: s.name }) })\n\t\t\tconst migrator1 = Migrator.from(repo1, adapter1).migrations(migrations).build()\n\n\t\t\tconst adapter2 = JsonAdapter.create({ filePath })\n\t\t\tawait adapter2.connect()\n\t\t\tconst repo2 = new Repo({ adapter: adapter2, resolve: (s) => ({ table: s.name }) })\n\t\t\tconst migrator2 = Migrator.from(repo2, adapter2).migrations(migrations).build()\n\n\t\t\tconst p1 = migrator1.up()\n\t\t\tconst p2 = migrator2.up()\n\n\t\t\tawait new Promise((r) => setTimeout(r, 200))\n\t\t\texpect(order).toEqual(['0001-start'])\n\n\t\t\tresolveGate()\n\t\t\tawait Promise.all([p1, p2])\n\n\t\t\texpect(order).toEqual(['0001-start', '0001-end', '0002'])\n\n\t\t\tawait adapter1.disconnect()\n\t\t\tawait adapter2.disconnect()\n\t\t})\n\n\t\ttest('tx:true rollback leaves file unchanged on failure', async () => {\n\t\t\tconst { adapter } = makeEnv()\n\t\t\tawait adapter.connect()\n\n\t\t\tawait adapter.applyCreateTable({ kind: 'createTable', name: 'users', pk: { name: 'id', type: 'string' }, fields: [] })\n\t\t\tawait adapter.disconnect()\n\n\t\t\tconst { adapter: a2, repo: r2 } = makeEnv()\n\t\t\tawait a2.connect()\n\n\t\t\tconst m: Migration<typeof a2> = {\n\t\t\t\tid: '0001-will-fail',\n\t\t\t\tchanges: [\n\t\t\t\t\t{ kind: 'addIndex', table: 'users', on: ['email'] },\n\t\t\t\t\t{\n\t\t\t\t\t\tkind: 'execute',\n\t\t\t\t\t\tup: async (r) => {\n\t\t\t\t\t\t\tawait r.on(UserSchema).one().create({ id: 'partial', email: 'fail@test.com' })\n\t\t\t\t\t\t\tthrow new Error('mid-migration boom')\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t}\n\t\t\tconst migrator = Migrator.from(r2, a2).migrations([m]).build()\n\t\t\tawait expect(migrator.up()).rejects.toThrow(OrmMigrationError)\n\t\t\tawait a2.disconnect()\n\n\t\t\tconst { adapter: a3, repo: r3 } = makeEnv()\n\t\t\tawait a3.connect()\n\t\t\tconst recorded = await a3.loadMigrations()\n\t\t\texpect(recorded).toHaveLength(0)\n\t\t\tconst schemas = await a3.introspect()\n\t\t\tconst users = schemas.find((s) => s.name === 'users')!\n\t\t\texpect(users.indexes).toEqual([])\n\t\t\tconst row = await r3.on(UserSchema).one().id('partial').find()\n\t\t\texpect(row).toBeNull()\n\t\t\tawait a3.disconnect()\n\t\t})\n\n\t\ttest('introspect() round-trip: apply* then introspect returns matching descriptors', async () => {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\n\t\t\tawait adapter.applyCreateTable({\n\t\t\t\tkind: 'createTable',\n\t\t\t\tname: 'users',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [\n\t\t\t\t\t{ name: 'email', type: 'string', unique: true },\n\t\t\t\t\t{ name: 'age', type: 'number', nullable: true },\n\t\t\t\t],\n\t\t\t})\n\t\t\tawait adapter.applyAddIndex({ kind: 'addIndex', table: 'users', on: ['email'], unique: true })\n\t\t\tawait adapter.applyCreateTable({\n\t\t\t\tkind: 'createTable',\n\t\t\t\tname: 'posts',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [{ name: 'authorId', type: 'string' }],\n\t\t\t})\n\t\t\tawait adapter.applyAddForeignKey({\n\t\t\t\tkind: 'addForeignKey',\n\t\t\t\ttable: 'posts',\n\t\t\t\ton: 'authorId',\n\t\t\t\treferences: { table: 'users', column: 'id' },\n\t\t\t\tonDelete: 'cascade',\n\t\t\t})\n\t\t\tawait adapter.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst schemas = await a2.introspect()\n\n\t\t\tconst users = schemas.find((s) => s.name === 'users')!\n\t\t\texpect(users.pk).toEqual({ name: 'id', type: 'string' })\n\t\t\texpect(users.fields).toEqual([\n\t\t\t\t{ name: 'email', type: 'string', nullable: false, unique: true },\n\t\t\t\t{ name: 'age', type: 'number', nullable: true },\n\t\t\t])\n\t\t\texpect(users.indexes).toEqual([{ name: 'users_email_idx', on: ['email'], unique: true }])\n\n\t\t\tconst posts = schemas.find((s) => s.name === 'posts')!\n\t\t\texpect(posts.pk).toEqual({ name: 'id', type: 'string' })\n\t\t\texpect(posts.fields).toEqual([{ name: 'authorId', type: 'string', nullable: false }])\n\t\t\texpect(posts.foreignKeys).toEqual([{\n\t\t\t\tname: 'posts_authorId_fk',\n\t\t\t\ton: 'authorId',\n\t\t\t\treferences: { table: 'users', column: 'id' },\n\t\t\t\tonDelete: 'cascade',\n\t\t\t}])\n\n\t\t\tawait a2.disconnect()\n\t\t})\n\n\t\ttest('metadata keys do not leak into data stores', async () => {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tawait adapter.recordMigration('m-001', 1000)\n\t\t\tawait adapter.applyCreateTable({ kind: 'createTable', name: 'users', pk: { name: 'id', type: 'string' }, fields: [] })\n\n\t\t\texpect(adapter.stores.has('__migrations')).toBe(false)\n\t\t\texpect(adapter.stores.has('__tables')).toBe(false)\n\t\t\tawait adapter.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\texpect(a2.stores.has('__migrations')).toBe(false)\n\t\t\texpect(a2.stores.has('__tables')).toBe(false)\n\t\t\tawait a2.disconnect()\n\t\t})\n\t})\n}\n"],"mappings":"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","names":["open","readFile","rename","unlink","writeFile","v","jsonConnectionPipe","v","JsonAdapter","configurable","OrmAdapter","#inMemory","InMemoryAdapter","#writeQueue","config","#serialize","obj","migrations","tables","name","meta","indexes","foreignKeys","store","records","pk","doc","#atomicWrite","data","tmpPath","writeFile","rename","#persistToDisk","next","raw","readFile","m","fields","fieldName","fspec","schema","result","ops","group","options","filter","create","spec","fn","id","appliedAt","lockPath","deadline","open","err","unlink","change"]}
@@ -1,12 +1,13 @@
1
1
  import {
2
2
  InMemoryAdapter
3
- } from "../../../chunk-TSVHZZE4.mjs";
4
- import {
5
- OrmAdapter
6
- } from "../../../chunk-3TP7NINX.mjs";
3
+ } from "../../../chunk-VQO4MSEC.mjs";
7
4
  import {
8
5
  configurable
9
- } from "../../../chunk-A4P7P6BU.mjs";
6
+ } from "../../../chunk-NEF3UYFA.mjs";
7
+ import {
8
+ OrmAdapter
9
+ } from "../../../chunk-RIXTQQUU.mjs";
10
+ import "../../../chunk-CEYT5YBA.mjs";
10
11
  import "../../../chunk-VCCS7CAI.mjs";
11
12
  import "../../../chunk-J5LGTIGS.mjs";
12
13