@slopware/sloppy-darwin-x64 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (434) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +5 -0
  3. package/bin/sloppy +0 -0
  4. package/bin/sloppyc +0 -0
  5. package/docs/KNOWN_LIMITATIONS.md +16 -0
  6. package/docs/LICENSES.md +6 -0
  7. package/docs/NOTICE.md +8 -0
  8. package/examples/README.md +140 -0
  9. package/examples/auth-api/README.md +20 -0
  10. package/examples/auth-api/app.js +61 -0
  11. package/examples/auth-api/appsettings.json +7 -0
  12. package/examples/auth-api/sloppy.json +5 -0
  13. package/examples/cache-basic/README.md +9 -0
  14. package/examples/cache-basic/app.js +32 -0
  15. package/examples/cache-hybrid-postgres/README.md +10 -0
  16. package/examples/cache-hybrid-postgres/app.js +27 -0
  17. package/examples/cache-output-api/README.md +10 -0
  18. package/examples/cache-output-api/app.js +35 -0
  19. package/examples/codec-base64-hex/README.md +14 -0
  20. package/examples/codec-base64-hex/app.js +15 -0
  21. package/examples/codec-checksums/README.md +15 -0
  22. package/examples/codec-checksums/app.js +8 -0
  23. package/examples/codec-compression/README.md +13 -0
  24. package/examples/codec-compression/app.js +9 -0
  25. package/examples/codec-streaming-compression/README.md +19 -0
  26. package/examples/codec-streaming-compression/app.js +16 -0
  27. package/examples/codec-text-binary/README.md +16 -0
  28. package/examples/codec-text-binary/app.js +17 -0
  29. package/examples/compiler-hello/README.md +71 -0
  30. package/examples/compiler-hello/app.js +7 -0
  31. package/examples/compiler-hello/expected/app.js +8 -0
  32. package/examples/compiler-hello/expected/app.js.map +53 -0
  33. package/examples/compiler-hello/expected/app.plan.json +229 -0
  34. package/examples/compiler-hello/expected/routes.slrt +0 -0
  35. package/examples/config-basic/README.md +13 -0
  36. package/examples/config-basic/app.js +13 -0
  37. package/examples/config-basic/appsettings.json +7 -0
  38. package/examples/config-secrets-redaction/README.md +9 -0
  39. package/examples/config-secrets-redaction/app.js +9 -0
  40. package/examples/config-secrets-redaction/appsettings.json +5 -0
  41. package/examples/config-strict-mode/README.md +7 -0
  42. package/examples/config-strict-mode/app.js +10 -0
  43. package/examples/config-strict-mode/appsettings.json +7 -0
  44. package/examples/configured-api/README.md +38 -0
  45. package/examples/configured-api/app.js +12 -0
  46. package/examples/configured-api/appsettings.Development.json +5 -0
  47. package/examples/configured-api/appsettings.json +6 -0
  48. package/examples/configured-api/sloppy.json +5 -0
  49. package/examples/core-config-secrets/README.md +10 -0
  50. package/examples/core-config-secrets/app.js +15 -0
  51. package/examples/core-fs-time-codec/README.md +9 -0
  52. package/examples/core-fs-time-codec/app.js +8 -0
  53. package/examples/core-network-time-codec/README.md +11 -0
  54. package/examples/core-network-time-codec/app.js +20 -0
  55. package/examples/core-policy-audit/README.md +7 -0
  56. package/examples/core-policy-audit/app.js +22 -0
  57. package/examples/core-process-time-codec/README.md +8 -0
  58. package/examples/core-process-time-codec/app.js +28 -0
  59. package/examples/core-worker-time/README.md +8 -0
  60. package/examples/core-worker-time/app.js +17 -0
  61. package/examples/crypto-hash-hmac/README.md +17 -0
  62. package/examples/crypto-hash-hmac/app.js +29 -0
  63. package/examples/crypto-password/README.md +21 -0
  64. package/examples/crypto-password/app.js +12 -0
  65. package/examples/crypto-random-token/README.md +16 -0
  66. package/examples/crypto-random-token/app.js +12 -0
  67. package/examples/crypto-secret-constant-time/README.md +21 -0
  68. package/examples/crypto-secret-constant-time/app.js +15 -0
  69. package/examples/data-foundation/README.md +39 -0
  70. package/examples/data-foundation/app.js +63 -0
  71. package/examples/dependency-graph/README.md +19 -0
  72. package/examples/dependency-graph/fixtures/graph-helper/index.js +3 -0
  73. package/examples/dependency-graph/fixtures/graph-helper/package.json +6 -0
  74. package/examples/dependency-graph/package.json +7 -0
  75. package/examples/dependency-graph/public/message.txt +1 -0
  76. package/examples/dependency-graph/sloppy.json +9 -0
  77. package/examples/dependency-graph/src/main.ts +8 -0
  78. package/examples/dogfood/README.md +23 -0
  79. package/examples/dogfood/dogfood.json +136 -0
  80. package/examples/dynamic-module-include/README.md +20 -0
  81. package/examples/dynamic-module-include/public/readme.txt +1 -0
  82. package/examples/dynamic-module-include/sloppy.json +12 -0
  83. package/examples/dynamic-module-include/src/main.ts +6 -0
  84. package/examples/dynamic-module-include/src/plugins/alpha.js +3 -0
  85. package/examples/dynamic-module-include/src/plugins/beta.js +3 -0
  86. package/examples/ergonomics/README.md +42 -0
  87. package/examples/ergonomics/app.js +38 -0
  88. package/examples/framework-controller/README.md +12 -0
  89. package/examples/framework-controller/app.js +31 -0
  90. package/examples/framework-di-services/README.md +17 -0
  91. package/examples/framework-di-services/app.ts +40 -0
  92. package/examples/framework-explicit-binding/README.md +12 -0
  93. package/examples/framework-explicit-binding/app.ts +34 -0
  94. package/examples/framework-hello/README.md +16 -0
  95. package/examples/framework-hello/app.ts +16 -0
  96. package/examples/framework-postgres-crud/README.md +73 -0
  97. package/examples/framework-postgres-crud/app.ts +64 -0
  98. package/examples/framework-sqlite-crud/README.md +52 -0
  99. package/examples/framework-sqlite-crud/app.ts +90 -0
  100. package/examples/framework-sqlite-crud/appsettings.json +11 -0
  101. package/examples/framework-sqlserver-crud/README.md +73 -0
  102. package/examples/framework-sqlserver-crud/app.ts +64 -0
  103. package/examples/framework-validation-errors/README.md +12 -0
  104. package/examples/framework-validation-errors/app.ts +16 -0
  105. package/examples/fs-basic/README.md +24 -0
  106. package/examples/fs-basic/app.js +12 -0
  107. package/examples/fs-roots-policy/README.md +14 -0
  108. package/examples/fs-roots-policy/app.js +4 -0
  109. package/examples/fs-streams/README.md +18 -0
  110. package/examples/fs-streams/app.js +11 -0
  111. package/examples/fs-watch/README.md +19 -0
  112. package/examples/fs-watch/app.js +11 -0
  113. package/examples/hello/README.md +63 -0
  114. package/examples/hello/app.js +19 -0
  115. package/examples/hello-minimal/README.md +51 -0
  116. package/examples/hello-minimal/sloppy.json +5 -0
  117. package/examples/hello-minimal/src/main.ts +9 -0
  118. package/examples/http-client-basic/README.md +11 -0
  119. package/examples/http-client-basic/app.js +46 -0
  120. package/examples/http-client-generated/README.md +22 -0
  121. package/examples/http-client-generated/openapi.json +45 -0
  122. package/examples/http-client-resilience/README.md +4 -0
  123. package/examples/http-client-resilience/app.js +38 -0
  124. package/examples/http-client-runtime-loopback/README.md +24 -0
  125. package/examples/http-client-testhost/README.md +4 -0
  126. package/examples/http-client-testhost/app.js +27 -0
  127. package/examples/http-client-testhost-package-mock/README.md +26 -0
  128. package/examples/http-client-typed/README.md +5 -0
  129. package/examples/http-client-typed/app.js +33 -0
  130. package/examples/modules-api/README.md +30 -0
  131. package/examples/modules-api/app.js +9 -0
  132. package/examples/modules-api/modules/routes.js +16 -0
  133. package/examples/modules-api/sloppy.json +5 -0
  134. package/examples/modules-basic/README.md +32 -0
  135. package/examples/modules-basic/app.js +41 -0
  136. package/examples/net-deadline-cancel/README.md +13 -0
  137. package/examples/net-deadline-cancel/app.js +34 -0
  138. package/examples/net-local-ipc/README.md +12 -0
  139. package/examples/net-local-ipc/app.js +46 -0
  140. package/examples/net-policy-strict/README.md +12 -0
  141. package/examples/net-policy-strict/app.js +34 -0
  142. package/examples/net-tcp-client/README.md +10 -0
  143. package/examples/net-tcp-client/app.js +23 -0
  144. package/examples/net-tcp-echo/README.md +11 -0
  145. package/examples/net-tcp-echo/app.js +45 -0
  146. package/examples/net-tcp-server/README.md +10 -0
  147. package/examples/net-tcp-server/app.js +28 -0
  148. package/examples/node-compat-path-events/README.md +15 -0
  149. package/examples/node-compat-path-events/sloppy.json +6 -0
  150. package/examples/node-compat-path-events/src/main.ts +15 -0
  151. package/examples/ops-compiler/README.md +9 -0
  152. package/examples/ops-compiler/app.js +26 -0
  153. package/examples/ops-health-metrics-management/README.md +14 -0
  154. package/examples/ops-health-metrics-management/app.js +24 -0
  155. package/examples/orm-basic/README.md +17 -0
  156. package/examples/orm-basic/app.js +82 -0
  157. package/examples/orm-cursor-export/README.md +16 -0
  158. package/examples/orm-cursor-export/app.js +28 -0
  159. package/examples/orm-migrations/README.md +14 -0
  160. package/examples/orm-migrations/migrations/.gitkeep +1 -0
  161. package/examples/orm-migrations/sloppy.json +9 -0
  162. package/examples/orm-migrations/src/app.ts +34 -0
  163. package/examples/orm-relations-includes/README.md +10 -0
  164. package/examples/orm-relations-includes/app.js +47 -0
  165. package/examples/orm-testservices/README.md +37 -0
  166. package/examples/orm-testservices/test.mjs +32 -0
  167. package/examples/os-runtime-api/README.md +11 -0
  168. package/examples/os-runtime-api/app.js +44 -0
  169. package/examples/package-zod-like/README.md +28 -0
  170. package/examples/package-zod-like/fixtures/zod-like/index.js +48 -0
  171. package/examples/package-zod-like/fixtures/zod-like/package.json +12 -0
  172. package/examples/package-zod-like/package.json +7 -0
  173. package/examples/package-zod-like/sloppy.json +6 -0
  174. package/examples/package-zod-like/src/main.ts +16 -0
  175. package/examples/postgres-basic/README.md +31 -0
  176. package/examples/postgres-basic/app.js +50 -0
  177. package/examples/prealpha-control-plane/README.md +50 -0
  178. package/examples/prealpha-control-plane/appsettings.Development.json +11 -0
  179. package/examples/prealpha-control-plane/appsettings.json +15 -0
  180. package/examples/prealpha-control-plane/sloppy.json +5 -0
  181. package/examples/prealpha-control-plane/src/db/schema.js +7 -0
  182. package/examples/prealpha-control-plane/src/db/seed.js +6 -0
  183. package/examples/prealpha-control-plane/src/main.js +21 -0
  184. package/examples/prealpha-control-plane/src/routes/apps.js +34 -0
  185. package/examples/prealpha-control-plane/src/routes/builds.js +25 -0
  186. package/examples/prealpha-control-plane/src/routes/deployments.js +19 -0
  187. package/examples/prealpha-control-plane/src/routes/diagnostics.js +11 -0
  188. package/examples/prealpha-control-plane/src/routes/health.js +27 -0
  189. package/examples/prealpha-control-plane/src/routes/projects.js +38 -0
  190. package/examples/prealpha-control-plane/src/services/diagnosticsSink.js +11 -0
  191. package/examples/prealpha-control-plane/src/services/repositories.js +9 -0
  192. package/examples/prealpha-control-plane/src/validation/schemas.js +6 -0
  193. package/examples/program-fs-process/README.md +31 -0
  194. package/examples/program-fs-process/sloppy.json +9 -0
  195. package/examples/program-fs-process/src/main.ts +27 -0
  196. package/examples/program-hello/README.md +32 -0
  197. package/examples/program-hello/main.ts +8 -0
  198. package/examples/program-hello/message.ts +1 -0
  199. package/examples/program-hello/sloppy.json +5 -0
  200. package/examples/rate-limit-auth/README.md +3 -0
  201. package/examples/rate-limit-auth/app.js +14 -0
  202. package/examples/rate-limit-basic/README.md +3 -0
  203. package/examples/rate-limit-basic/app.js +13 -0
  204. package/examples/rate-limit-redis/README.md +5 -0
  205. package/examples/rate-limit-redis/app.js +20 -0
  206. package/examples/rate-limit-testhost/README.md +4 -0
  207. package/examples/rate-limit-testhost/app.js +13 -0
  208. package/examples/rate-limit-websocket/README.md +3 -0
  209. package/examples/rate-limit-websocket/app.js +16 -0
  210. package/examples/realtime-auth/README.md +8 -0
  211. package/examples/realtime-auth/app.js +25 -0
  212. package/examples/realtime-auth/test.mjs +43 -0
  213. package/examples/realtime-chat/README.md +8 -0
  214. package/examples/realtime-chat/app.js +32 -0
  215. package/examples/realtime-chat/test.mjs +52 -0
  216. package/examples/realtime-dashboard/README.md +20 -0
  217. package/examples/realtime-dashboard/app.js +37 -0
  218. package/examples/realtime-presence/README.md +8 -0
  219. package/examples/realtime-presence/app.js +32 -0
  220. package/examples/realtime-presence/test.mjs +50 -0
  221. package/examples/realtime-testhost/README.md +8 -0
  222. package/examples/realtime-testhost/test.mjs +31 -0
  223. package/examples/redis-basic/README.md +17 -0
  224. package/examples/redis-basic/app.js +39 -0
  225. package/examples/redis-cache/README.md +14 -0
  226. package/examples/redis-cache/app.js +36 -0
  227. package/examples/redis-locks/README.md +13 -0
  228. package/examples/redis-locks/app.js +49 -0
  229. package/examples/request-context/README.md +32 -0
  230. package/examples/request-context/app.js +15 -0
  231. package/examples/sqlite-basic/README.md +52 -0
  232. package/examples/sqlite-basic/app.js +56 -0
  233. package/examples/sqlserver-basic/README.md +36 -0
  234. package/examples/sqlserver-basic/app.js +59 -0
  235. package/examples/static-files-basic/README.md +11 -0
  236. package/examples/static-files-basic/app.js +12 -0
  237. package/examples/static-files-basic/public/app.js +1 -0
  238. package/examples/static-files-basic/public/site.css +3 -0
  239. package/examples/static-files-package/README.md +12 -0
  240. package/examples/static-files-package/app.js +10 -0
  241. package/examples/static-files-package/public/index.html +2 -0
  242. package/examples/static-files-precompressed/README.md +12 -0
  243. package/examples/static-files-precompressed/app.js +11 -0
  244. package/examples/static-files-precompressed/public/app.js +1 -0
  245. package/examples/static-files-precompressed/public/app.js.br +0 -0
  246. package/examples/static-files-precompressed/public/app.js.gz +0 -0
  247. package/examples/static-files-spa/README.md +12 -0
  248. package/examples/static-files-spa/app.js +16 -0
  249. package/examples/static-files-spa/dist/assets/app.js +1 -0
  250. package/examples/static-files-spa/dist/index.html +4 -0
  251. package/examples/static-files-testhost/README.md +8 -0
  252. package/examples/static-files-testhost/app.js +13 -0
  253. package/examples/static-files-testhost/public/app.js +1 -0
  254. package/examples/static-files-testhost/public/app.js.gz +0 -0
  255. package/examples/static-files-testhost/test.mjs +38 -0
  256. package/examples/testhost-basic/README.md +26 -0
  257. package/examples/testhost-db/README.md +31 -0
  258. package/examples/testservices-postgres/README.md +68 -0
  259. package/examples/testservices-redis/README.md +71 -0
  260. package/examples/testservices-sqlserver/README.md +75 -0
  261. package/examples/time-basic/README.md +18 -0
  262. package/examples/time-basic/app.js +12 -0
  263. package/examples/time-deadline-cancellation/README.md +11 -0
  264. package/examples/time-deadline-cancellation/app.js +27 -0
  265. package/examples/time-fake-clock/README.md +14 -0
  266. package/examples/time-fake-clock/app.js +25 -0
  267. package/examples/time-interval-schedule/README.md +13 -0
  268. package/examples/time-interval-schedule/app.js +60 -0
  269. package/examples/users-api-sqlite/README.md +74 -0
  270. package/examples/users-api-sqlite/app.js +11 -0
  271. package/examples/users-api-sqlite/appsettings.Development.json +11 -0
  272. package/examples/users-api-sqlite/appsettings.json +11 -0
  273. package/examples/users-api-sqlite/modules/users.js +40 -0
  274. package/examples/users-api-sqlite/sloppy.json +5 -0
  275. package/examples/validation-errors/README.md +36 -0
  276. package/examples/validation-errors/app.js +14 -0
  277. package/examples/validation-errors/invalid-user.http +6 -0
  278. package/examples/validation-errors/sloppy.json +5 -0
  279. package/examples/web-dynamic-routes/README.md +17 -0
  280. package/examples/web-dynamic-routes/app.ts +27 -0
  281. package/examples/webhooks-basic/README.md +11 -0
  282. package/examples/webhooks-basic/app.js +48 -0
  283. package/examples/websocket-auth/README.md +8 -0
  284. package/examples/websocket-auth/app.js +16 -0
  285. package/examples/websocket-echo/README.md +9 -0
  286. package/examples/websocket-echo/app.js +36 -0
  287. package/examples/websocket-json-schema/README.md +5 -0
  288. package/examples/websocket-json-schema/app.js +25 -0
  289. package/examples/websocket-testhost/README.md +11 -0
  290. package/examples/websocket-testhost/test.mjs +49 -0
  291. package/examples/workers-background-service/README.md +7 -0
  292. package/examples/workers-background-service/app.js +16 -0
  293. package/examples/workers-js-isolate/README.md +8 -0
  294. package/examples/workers-js-isolate/app.js +19 -0
  295. package/examples/workers-js-isolate/workers/parser.ts +11 -0
  296. package/examples/workers-shutdown/README.md +6 -0
  297. package/examples/workers-shutdown/app.js +26 -0
  298. package/examples/workers-workerpool/README.md +6 -0
  299. package/examples/workers-workerpool/app.js +23 -0
  300. package/examples/workers-workqueue/README.md +8 -0
  301. package/examples/workers-workqueue/app.js +24 -0
  302. package/manifest.json +59 -0
  303. package/package.json +31 -0
  304. package/stdlib/sloppy/README.md +177 -0
  305. package/stdlib/sloppy/app.js +2142 -0
  306. package/stdlib/sloppy/auth.js +1813 -0
  307. package/stdlib/sloppy/bootstrap.manifest.json +83 -0
  308. package/stdlib/sloppy/cache.js +1542 -0
  309. package/stdlib/sloppy/codec.js +1153 -0
  310. package/stdlib/sloppy/config.js +61 -0
  311. package/stdlib/sloppy/crypto.js +312 -0
  312. package/stdlib/sloppy/data.js +2945 -0
  313. package/stdlib/sloppy/ffi.js +185 -0
  314. package/stdlib/sloppy/fs.js +795 -0
  315. package/stdlib/sloppy/health.js +603 -0
  316. package/stdlib/sloppy/http.js +1595 -0
  317. package/stdlib/sloppy/index.js +59 -0
  318. package/stdlib/sloppy/internal/bytes.js +31 -0
  319. package/stdlib/sloppy/internal/capabilities.js +155 -0
  320. package/stdlib/sloppy/internal/config.js +640 -0
  321. package/stdlib/sloppy/internal/disposable.js +31 -0
  322. package/stdlib/sloppy/internal/headers.js +63 -0
  323. package/stdlib/sloppy/internal/intrinsics.js +2 -0
  324. package/stdlib/sloppy/internal/json.js +20 -0
  325. package/stdlib/sloppy/internal/logging.js +278 -0
  326. package/stdlib/sloppy/internal/modules.js +405 -0
  327. package/stdlib/sloppy/internal/redaction.js +87 -0
  328. package/stdlib/sloppy/internal/routes.js +2279 -0
  329. package/stdlib/sloppy/internal/runtime-classic.js +19837 -0
  330. package/stdlib/sloppy/internal/services.js +690 -0
  331. package/stdlib/sloppy/internal/shared.js +32 -0
  332. package/stdlib/sloppy/internal/testhost-diagnostics.js +88 -0
  333. package/stdlib/sloppy/internal/testhost-http-server.js +238 -0
  334. package/stdlib/sloppy/internal/testhost-http.js +118 -0
  335. package/stdlib/sloppy/internal/testhost-loopback.js +50 -0
  336. package/stdlib/sloppy/internal/testservices-docker.js +154 -0
  337. package/stdlib/sloppy/internal/validation.js +117 -0
  338. package/stdlib/sloppy/metrics.js +427 -0
  339. package/stdlib/sloppy/net.js +5208 -0
  340. package/stdlib/sloppy/node/assert/strict.js +39 -0
  341. package/stdlib/sloppy/node/assert.js +228 -0
  342. package/stdlib/sloppy/node/buffer.js +247 -0
  343. package/stdlib/sloppy/node/console.js +33 -0
  344. package/stdlib/sloppy/node/constants.js +9 -0
  345. package/stdlib/sloppy/node/crypto.js +89 -0
  346. package/stdlib/sloppy/node/diagnostics_channel.js +41 -0
  347. package/stdlib/sloppy/node/events.js +113 -0
  348. package/stdlib/sloppy/node/fs/promises.js +27 -0
  349. package/stdlib/sloppy/node/fs.js +280 -0
  350. package/stdlib/sloppy/node/http.js +11 -0
  351. package/stdlib/sloppy/node/https.js +11 -0
  352. package/stdlib/sloppy/node/module.js +40 -0
  353. package/stdlib/sloppy/node/os.js +22 -0
  354. package/stdlib/sloppy/node/path.js +78 -0
  355. package/stdlib/sloppy/node/perf_hooks.js +12 -0
  356. package/stdlib/sloppy/node/process.js +129 -0
  357. package/stdlib/sloppy/node/querystring.js +21 -0
  358. package/stdlib/sloppy/node/stream/promises.js +3 -0
  359. package/stdlib/sloppy/node/stream.js +132 -0
  360. package/stdlib/sloppy/node/string_decoder.js +23 -0
  361. package/stdlib/sloppy/node/timers.js +26 -0
  362. package/stdlib/sloppy/node/tty.js +18 -0
  363. package/stdlib/sloppy/node/url.js +17 -0
  364. package/stdlib/sloppy/node/util.js +95 -0
  365. package/stdlib/sloppy/node/zlib.js +72 -0
  366. package/stdlib/sloppy/orm.js +2188 -0
  367. package/stdlib/sloppy/os.js +580 -0
  368. package/stdlib/sloppy/problem-details.js +29 -0
  369. package/stdlib/sloppy/providers/sqlite.js +26 -0
  370. package/stdlib/sloppy/rate-limit.js +856 -0
  371. package/stdlib/sloppy/realtime.js +1508 -0
  372. package/stdlib/sloppy/redis.js +1272 -0
  373. package/stdlib/sloppy/request-id.js +184 -0
  374. package/stdlib/sloppy/request-logging.js +101 -0
  375. package/stdlib/sloppy/results.js +933 -0
  376. package/stdlib/sloppy/schema.js +546 -0
  377. package/stdlib/sloppy/testing.js +4081 -0
  378. package/stdlib/sloppy/testservices.js +1041 -0
  379. package/stdlib/sloppy/time.js +894 -0
  380. package/stdlib/sloppy/webhooks.js +1330 -0
  381. package/stdlib/sloppy/workers.js +986 -0
  382. package/templates/api/README.md +82 -0
  383. package/templates/api/appsettings.Development.json +14 -0
  384. package/templates/api/appsettings.json +13 -0
  385. package/templates/api/data/.gitkeep +1 -0
  386. package/templates/api/gitignore +4 -0
  387. package/templates/api/migrations/0001_create_users.sql +1 -0
  388. package/templates/api/package.json +16 -0
  389. package/templates/api/public/hello.txt +1 -0
  390. package/templates/api/sloppy.json +14 -0
  391. package/templates/api/src/config.ts +1 -0
  392. package/templates/api/src/db/migrate.ts +14 -0
  393. package/templates/api/src/db/schema.ts +4 -0
  394. package/templates/api/src/db/usersRepository.ts +23 -0
  395. package/templates/api/src/main.ts +18 -0
  396. package/templates/api/src/models/user.ts +7 -0
  397. package/templates/api/src/routes/health.ts +20 -0
  398. package/templates/api/src/routes/users.ts +40 -0
  399. package/templates/api/src/services/usersService.ts +21 -0
  400. package/templates/api/tsconfig.json +15 -0
  401. package/templates/cli/README.md +16 -0
  402. package/templates/cli/gitignore +2 -0
  403. package/templates/cli/package.json +13 -0
  404. package/templates/cli/sloppy.json +6 -0
  405. package/templates/cli/src/commands/echo.ts +9 -0
  406. package/templates/cli/src/commands/inspect.ts +20 -0
  407. package/templates/cli/src/main.ts +50 -0
  408. package/templates/cli/tsconfig.json +15 -0
  409. package/templates/minimal-api/README.md +14 -0
  410. package/templates/minimal-api/gitignore +3 -0
  411. package/templates/minimal-api/package.json +14 -0
  412. package/templates/minimal-api/sloppy.json +5 -0
  413. package/templates/minimal-api/src/main.ts +9 -0
  414. package/templates/minimal-api/tsconfig.json +15 -0
  415. package/templates/node-compat/README.md +40 -0
  416. package/templates/node-compat/gitignore +2 -0
  417. package/templates/node-compat/package.json +11 -0
  418. package/templates/node-compat/sloppy.json +6 -0
  419. package/templates/node-compat/src/main.ts +40 -0
  420. package/templates/package-api/README.md +44 -0
  421. package/templates/package-api/fixtures/validator-lite/index.js +7 -0
  422. package/templates/package-api/fixtures/validator-lite/package.json +6 -0
  423. package/templates/package-api/gitignore +3 -0
  424. package/templates/package-api/package.json +17 -0
  425. package/templates/package-api/sloppy.json +5 -0
  426. package/templates/package-api/src/main.ts +10 -0
  427. package/templates/package-api/src/routes/health.ts +5 -0
  428. package/templates/package-api/src/routes/users.ts +12 -0
  429. package/templates/package-api/tsconfig.json +15 -0
  430. package/templates/program/README.md +12 -0
  431. package/templates/program/gitignore +1 -0
  432. package/templates/program/package.json +10 -0
  433. package/templates/program/sloppy.json +6 -0
  434. package/templates/program/src/main.ts +9 -0
@@ -0,0 +1,1041 @@
1
+ import { Random } from "./crypto.js";
2
+ import { data, Migrations } from "./data.js";
3
+ import { File } from "./fs.js";
4
+ import {
5
+ SECRET_REDACTION,
6
+ boundedText,
7
+ redactObject,
8
+ redactTextSecrets,
9
+ } from "./internal/redaction.js";
10
+ import {
11
+ DockerCliBackend,
12
+ dockerAvailable,
13
+ dockerBackend,
14
+ dockerRequire,
15
+ dockerRunOk,
16
+ ensureImage,
17
+ inspectContainer,
18
+ } from "./internal/testservices-docker.js";
19
+ import { isPlainObject, requirePositiveFiniteNumber } from "./internal/validation.js";
20
+ import { Redis } from "./redis.js";
21
+
22
+ const DEFAULT_POSTGRES_IMAGE = "postgres:17";
23
+ const DEFAULT_SQLSERVER_IMAGE = "mcr.microsoft.com/mssql/server:2022-latest";
24
+ const DEFAULT_REDIS_IMAGE = "redis:7-alpine";
25
+ const DEFAULT_SQLSERVER_ODBC_DRIVER = "ODBC Driver 17 for SQL Server";
26
+ const LOCALHOST = "127.0.0.1";
27
+ const POSTGRES_PORT = 5432;
28
+ const SQLSERVER_PORT = 1433;
29
+ const REDIS_PORT = 6379;
30
+ const DEFAULT_STARTUP_TIMEOUT_MS = 30000;
31
+ const DEFAULT_SQLSERVER_STARTUP_TIMEOUT_MS = 60000;
32
+ const DEFAULT_STOP_TIMEOUT_MS = 10000;
33
+ const DEFAULT_LOG_TAIL = 120;
34
+ const ASYNC_DISPOSE = Symbol.asyncDispose;
35
+ let nonSecurityNameCounter = 0;
36
+
37
+ function sleep(ms) {
38
+ return new Promise((resolve) => setTimeout(resolve, ms));
39
+ }
40
+
41
+ function normalizeTimeout(value, fallback, subject) {
42
+ if (value === undefined) {
43
+ return fallback;
44
+ }
45
+ return requirePositiveFiniteNumber(value, `Sloppy TestServices ${subject} must be a positive finite number.`);
46
+ }
47
+
48
+ function normalizePort(value, fallback, subject) {
49
+ if (value === undefined) {
50
+ return fallback;
51
+ }
52
+ if (!Number.isInteger(value) || value < 1 || value > 65535) {
53
+ throw new TypeError(`Sloppy TestServices ${subject} must be an integer from 1 to 65535.`);
54
+ }
55
+ return value;
56
+ }
57
+
58
+ function normalizeRedisDatabase(value, fallback) {
59
+ const selected = value ?? fallback;
60
+ if (!Number.isInteger(selected) || selected < 0 || selected > 15) {
61
+ throw new TypeError("Sloppy TestServices Redis database must be from 0 to 15.");
62
+ }
63
+ return selected;
64
+ }
65
+
66
+ function normalizeNonEmptyString(value, fallback, subject) {
67
+ const selected = value ?? fallback;
68
+ if (typeof selected !== "string" || selected.length === 0 || selected.includes("\0")) {
69
+ throw new TypeError(`Sloppy TestServices ${subject} must be a non-empty string without NUL.`);
70
+ }
71
+ return selected;
72
+ }
73
+
74
+ function randomHex(length) {
75
+ try {
76
+ return Array.from(Random.bytes(length), (byte) => byte.toString(16).padStart(2, "0")).join("");
77
+ } catch (error) {
78
+ const bytes = new Uint8Array(length);
79
+ const crypto = globalThis.crypto;
80
+ if (crypto?.getRandomValues !== undefined) {
81
+ crypto.getRandomValues(bytes);
82
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
83
+ }
84
+ throw new Error(`SLOPPY_E_TESTSERVICES_SECURE_RANDOM_UNAVAILABLE: ${error?.message ?? error}`);
85
+ }
86
+ }
87
+
88
+ function nonSecuritySuffix() {
89
+ nonSecurityNameCounter += 1;
90
+ return `${Date.now().toString(16)}-${nonSecurityNameCounter.toString(16)}`;
91
+ }
92
+
93
+ function randomContainerName(kind) {
94
+ return `sloppy-testservices-${kind}-${nonSecuritySuffix()}`;
95
+ }
96
+
97
+ function generatedSqlServerPassword() {
98
+ return `Sloppy_${randomHex(10)}_Aa1!`;
99
+ }
100
+
101
+ function redactWithSecrets(value, secrets) {
102
+ let text = redactTextSecrets(value, secrets);
103
+ text = data.postgres.redactConnectionString(text);
104
+ text = data.sqlserver.redactConnectionString(text);
105
+ text = Redis._redactUrl(text);
106
+ return text;
107
+ }
108
+
109
+ function safeObject(value, secrets) {
110
+ return redactObject(value, {
111
+ secrets,
112
+ redactText: (text) => redactWithSecrets(text, secrets),
113
+ });
114
+ }
115
+
116
+ function createDiagnosticState(kind, image, name, secrets) {
117
+ return {
118
+ kind,
119
+ image,
120
+ name,
121
+ containerId: undefined,
122
+ host: LOCALHOST,
123
+ port: undefined,
124
+ startupState: "created",
125
+ readinessAttempts: 0,
126
+ lastReadinessError: undefined,
127
+ logTail: "",
128
+ timings: {
129
+ createdAt: new Date().toISOString(),
130
+ startedAt: undefined,
131
+ readyAt: undefined,
132
+ disposedAt: undefined,
133
+ },
134
+ cleanupErrors: [],
135
+ secrets,
136
+ };
137
+ }
138
+
139
+ function providerUnavailableError(kind) {
140
+ const provider = kind === "postgres" ? "PostgreSQL" : "SQL Server";
141
+ const error = new Error(`SLOPPY_E_TESTSERVICES_PROVIDER_UNAVAILABLE: ${provider} TestServices require the matching Sloppy data provider bridge.
142
+
143
+ Provider:
144
+ ${kind}
145
+
146
+ Reason:
147
+ The active runtime does not expose the native ${kind} provider bridge, so TestServices cannot prove real database readiness.
148
+
149
+ Fix:
150
+ Run this lane under a V8/native-provider build, or skip the TestServices container lane with this reason.`);
151
+ error.code = "SLOPPY_E_TESTSERVICES_PROVIDER_UNAVAILABLE";
152
+ return error;
153
+ }
154
+
155
+ async function dockerLogs(backend, containerId, tail, options) {
156
+ if (containerId === undefined) {
157
+ return "";
158
+ }
159
+ try {
160
+ const result = await backend.run(["logs", "--tail", String(tail), containerId], {
161
+ timeoutMs: options.dockerTimeoutMs ?? 15000,
162
+ maxStdoutBytes: 256 * 1024,
163
+ maxStderrBytes: 256 * 1024,
164
+ });
165
+ return boundedText(`${result.stdout}${result.stderr}`);
166
+ } catch {
167
+ return "";
168
+ }
169
+ }
170
+
171
+ function cleanupFailure(operation, args, resultOrError, secrets) {
172
+ const details = resultOrError instanceof Error
173
+ ? resultOrError.message
174
+ : `${resultOrError.timedOut === true ? "timed out" : `exit code ${resultOrError.exitCode}`}: ${resultOrError.stderr || resultOrError.stdout}`;
175
+ return Object.freeze({
176
+ operation,
177
+ command: `docker ${args.join(" ")}`,
178
+ message: redactWithSecrets(boundedText(details, 2000), secrets),
179
+ });
180
+ }
181
+
182
+ async function cleanupDockerCommand(backend, args, options, state, required) {
183
+ try {
184
+ const result = await backend.run(args, options);
185
+ if (result.exitCode === 0 && result.timedOut !== true) {
186
+ return;
187
+ }
188
+ const failure = cleanupFailure(args[0], args, result, state.secrets);
189
+ state.cleanupErrors.push(failure);
190
+ if (required) {
191
+ throw new Error(`SLOPPY_E_TESTSERVICES_CLEANUP_FAILED: ${failure.message}`);
192
+ }
193
+ } catch (error) {
194
+ if (String(error?.message ?? error).startsWith("SLOPPY_E_TESTSERVICES_CLEANUP_FAILED:")) {
195
+ throw error;
196
+ }
197
+ const failure = cleanupFailure(args[0], args, error, state.secrets);
198
+ state.cleanupErrors.push(failure);
199
+ if (required) {
200
+ throw new Error(`SLOPPY_E_TESTSERVICES_CLEANUP_FAILED: ${failure.message}`, { cause: error });
201
+ }
202
+ }
203
+ }
204
+
205
+ async function removeContainer(backend, containerId, options, state) {
206
+ if (containerId === undefined) {
207
+ return;
208
+ }
209
+ await cleanupDockerCommand(backend, ["stop", "--time", String(Math.ceil((options.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS) / 1000)), containerId], {
210
+ timeoutMs: options.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS,
211
+ maxStdoutBytes: 64 * 1024,
212
+ maxStderrBytes: 64 * 1024,
213
+ }, state, false);
214
+ await cleanupDockerCommand(backend, ["rm", "--force", containerId], {
215
+ timeoutMs: options.rmTimeoutMs ?? 15000,
216
+ maxStdoutBytes: 64 * 1024,
217
+ maxStderrBytes: 64 * 1024,
218
+ }, state, options.keepContainerOnFailure !== true);
219
+ }
220
+
221
+ function postgresConnectionString(options, host, port) {
222
+ const user = encodeURIComponent(options.username);
223
+ const password = encodeURIComponent(options.password);
224
+ const database = encodeURIComponent(options.database);
225
+ return `postgresql://${user}:${password}@${host}:${port}/${database}`;
226
+ }
227
+
228
+ function odbcEscapeValue(value) {
229
+ const text = String(value);
230
+ const escaped = text.replaceAll("}", "}}");
231
+ if (/[;{}]/u.test(text) || /^\s|\s$/u.test(text)) {
232
+ return "{" + escaped + "}";
233
+ }
234
+ return text;
235
+ }
236
+
237
+ function odbcBraceValue(value) {
238
+ return "{" + String(value).replaceAll("}", "}}") + "}";
239
+ }
240
+
241
+ function sqlServerConnectionString(options, host, port) {
242
+ return [
243
+ `Driver=${odbcBraceValue(options.driver)}`,
244
+ `Server=${host},${port}`,
245
+ `Database=${odbcEscapeValue(options.database)}`,
246
+ `UID=${odbcEscapeValue(options.username)}`,
247
+ `PWD=${odbcEscapeValue(options.password)}`,
248
+ "Encrypt=yes",
249
+ "TrustServerCertificate=yes",
250
+ ].join(";");
251
+ }
252
+
253
+ function normalizedPostgresOptions(options = {}) {
254
+ if (!isPlainObject(options)) {
255
+ throw new TypeError("Sloppy TestServices.postgres options must be a plain object.");
256
+ }
257
+ const username = normalizeNonEmptyString(options.username, "sloppy", "PostgreSQL username");
258
+ const password = normalizeNonEmptyString(options.password, "sloppy", "PostgreSQL password");
259
+ const database = normalizeNonEmptyString(options.database, "app_test", "PostgreSQL database");
260
+ const image = normalizeNonEmptyString(options.image, DEFAULT_POSTGRES_IMAGE, "PostgreSQL image");
261
+ const hostPort = normalizePort(options.hostPort, undefined, "PostgreSQL hostPort");
262
+ const startupTimeoutMs = normalizeTimeout(options.startupTimeoutMs, DEFAULT_STARTUP_TIMEOUT_MS, "PostgreSQL startupTimeoutMs");
263
+ const stopTimeoutMs = normalizeTimeout(options.stopTimeoutMs, DEFAULT_STOP_TIMEOUT_MS, "PostgreSQL stopTimeoutMs");
264
+ const dockerTimeoutMs = normalizeTimeout(options.dockerTimeoutMs, undefined, "PostgreSQL dockerTimeoutMs");
265
+ const pullTimeoutMs = normalizeTimeout(options.pullTimeoutMs, undefined, "PostgreSQL pullTimeoutMs");
266
+ const rmTimeoutMs = normalizeTimeout(options.rmTimeoutMs, undefined, "PostgreSQL rmTimeoutMs");
267
+ const name = options.containerName === undefined
268
+ ? randomContainerName("postgres")
269
+ : normalizeNonEmptyString(options.containerName, undefined, "PostgreSQL containerName");
270
+ return Object.freeze({
271
+ kind: "postgres",
272
+ image,
273
+ username,
274
+ password,
275
+ database,
276
+ host: LOCALHOST,
277
+ containerName: name,
278
+ hostPort,
279
+ startupTimeoutMs,
280
+ stopTimeoutMs,
281
+ dockerTimeoutMs,
282
+ pullTimeoutMs,
283
+ rmTimeoutMs,
284
+ keepContainerOnFailure: options.keepContainerOnFailure === true,
285
+ migrations: options.migrations,
286
+ dockerBackend: options.dockerBackend,
287
+ docker: options.docker,
288
+ });
289
+ }
290
+
291
+ function normalizedSqlServerOptions(options = {}) {
292
+ if (!isPlainObject(options)) {
293
+ throw new TypeError("Sloppy TestServices.sqlServer options must be a plain object.");
294
+ }
295
+ const database = normalizeNonEmptyString(options.database, "app_test", "SQL Server database");
296
+ const driver = normalizeNonEmptyString(options.driver, DEFAULT_SQLSERVER_ODBC_DRIVER, "SQL Server ODBC driver");
297
+ const username = normalizeNonEmptyString(options.username, "sa", "SQL Server username");
298
+ if (username !== "sa") {
299
+ throw new TypeError('Sloppy TestServices SQL Server currently supports only username "sa".');
300
+ }
301
+ const image = normalizeNonEmptyString(options.image, DEFAULT_SQLSERVER_IMAGE, "SQL Server image");
302
+ const hostPort = normalizePort(options.hostPort, undefined, "SQL Server hostPort");
303
+ const startupTimeoutMs = normalizeTimeout(options.startupTimeoutMs, DEFAULT_SQLSERVER_STARTUP_TIMEOUT_MS, "SQL Server startupTimeoutMs");
304
+ const stopTimeoutMs = normalizeTimeout(options.stopTimeoutMs, DEFAULT_STOP_TIMEOUT_MS, "SQL Server stopTimeoutMs");
305
+ const dockerTimeoutMs = normalizeTimeout(options.dockerTimeoutMs, undefined, "SQL Server dockerTimeoutMs");
306
+ const pullTimeoutMs = normalizeTimeout(options.pullTimeoutMs, undefined, "SQL Server pullTimeoutMs");
307
+ const rmTimeoutMs = normalizeTimeout(options.rmTimeoutMs, undefined, "SQL Server rmTimeoutMs");
308
+ const password = options.password === undefined
309
+ ? generatedSqlServerPassword()
310
+ : normalizeNonEmptyString(options.password, undefined, "SQL Server password");
311
+ const name = options.containerName === undefined
312
+ ? randomContainerName("sqlserver")
313
+ : normalizeNonEmptyString(options.containerName, undefined, "SQL Server containerName");
314
+ return Object.freeze({
315
+ kind: "sqlserver",
316
+ image,
317
+ username,
318
+ password,
319
+ database,
320
+ driver,
321
+ host: LOCALHOST,
322
+ containerName: name,
323
+ hostPort,
324
+ startupTimeoutMs,
325
+ stopTimeoutMs,
326
+ dockerTimeoutMs,
327
+ pullTimeoutMs,
328
+ rmTimeoutMs,
329
+ keepContainerOnFailure: options.keepContainerOnFailure === true,
330
+ migrations: options.migrations,
331
+ dockerBackend: options.dockerBackend,
332
+ docker: options.docker,
333
+ });
334
+ }
335
+
336
+ function normalizedRedisOptions(options = {}) {
337
+ if (!isPlainObject(options)) {
338
+ throw new TypeError("Sloppy TestServices.redis options must be a plain object.");
339
+ }
340
+ const image = normalizeNonEmptyString(options.image, DEFAULT_REDIS_IMAGE, "Redis image");
341
+ const hostPort = normalizePort(options.hostPort, undefined, "Redis hostPort");
342
+ const startupTimeoutMs = normalizeTimeout(options.startupTimeoutMs, DEFAULT_STARTUP_TIMEOUT_MS, "Redis startupTimeoutMs");
343
+ const readinessTimeoutMs = normalizeTimeout(options.readinessTimeoutMs, startupTimeoutMs, "Redis readinessTimeoutMs");
344
+ const stopTimeoutMs = normalizeTimeout(options.stopTimeoutMs, DEFAULT_STOP_TIMEOUT_MS, "Redis stopTimeoutMs");
345
+ const dockerTimeoutMs = normalizeTimeout(options.dockerTimeoutMs, undefined, "Redis dockerTimeoutMs");
346
+ const pullTimeoutMs = normalizeTimeout(options.pullTimeoutMs, undefined, "Redis pullTimeoutMs");
347
+ const rmTimeoutMs = normalizeTimeout(options.rmTimeoutMs, undefined, "Redis rmTimeoutMs");
348
+ const database = normalizeRedisDatabase(options.database, 0);
349
+ const password = options.password === undefined
350
+ ? undefined
351
+ : normalizeNonEmptyString(options.password, undefined, "Redis password");
352
+ const name = options.containerName === undefined
353
+ ? randomContainerName("redis")
354
+ : normalizeNonEmptyString(options.containerName, undefined, "Redis containerName");
355
+ return Object.freeze({
356
+ kind: "redis",
357
+ image,
358
+ database,
359
+ password,
360
+ host: LOCALHOST,
361
+ containerName: name,
362
+ hostPort,
363
+ startupTimeoutMs,
364
+ readinessTimeoutMs,
365
+ stopTimeoutMs,
366
+ dockerTimeoutMs,
367
+ pullTimeoutMs,
368
+ rmTimeoutMs,
369
+ keepContainerOnFailure: options.keepContainerOnFailure === true,
370
+ dockerBackend: options.dockerBackend,
371
+ docker: options.docker,
372
+ });
373
+ }
374
+
375
+ function providerBridgeAvailable(kind) {
376
+ return kind === "postgres"
377
+ ? data.postgres.supports.nativeStdlibBridge === true
378
+ : data.sqlserver.supports.nativeStdlibBridge === true;
379
+ }
380
+
381
+ function openProvider(kind, connectionString) {
382
+ if (!providerBridgeAvailable(kind)) {
383
+ throw providerUnavailableError(kind);
384
+ }
385
+ return kind === "postgres"
386
+ ? data.postgres.open({ connectionString })
387
+ : data.sqlserver.open({ connectionString });
388
+ }
389
+
390
+ async function withDb(kind, connectionString, callback) {
391
+ const db = openProvider(kind, connectionString);
392
+ try {
393
+ return await callback(db);
394
+ } finally {
395
+ await Promise.resolve(db.close?.()).catch(() => {});
396
+ }
397
+ }
398
+
399
+ async function waitForReady(kind, state, connectionString, options) {
400
+ const startedAt = Date.now();
401
+ state.startupState = "waiting";
402
+ while (Date.now() - startedAt < options.startupTimeoutMs) {
403
+ const remainingMs = options.startupTimeoutMs - (Date.now() - startedAt);
404
+ state.readinessAttempts += 1;
405
+ try {
406
+ if (kind === "sqlserver") {
407
+ const masterConnectionString = sqlServerConnectionString({
408
+ ...options,
409
+ database: "master",
410
+ }, LOCALHOST, state.port);
411
+ await withDb(kind, masterConnectionString, async (db) => {
412
+ await db.exec(`if db_id(N'${options.database.replaceAll("'", "''")}') is null create database [${options.database.replaceAll("]", "]]")}]`, [], { timeoutMs: remainingMs });
413
+ });
414
+ }
415
+ await withDb(kind, connectionString, async (db) => {
416
+ await db.queryOne("select 1 as ok", [], { timeoutMs: remainingMs });
417
+ });
418
+ state.startupState = "ready";
419
+ state.timings.readyAt = new Date().toISOString();
420
+ return;
421
+ } catch (error) {
422
+ state.lastReadinessError = String(error?.message ?? error);
423
+ const retryDelayMs = Math.min(1000, 100 + state.readinessAttempts * 100, Math.max(0, options.startupTimeoutMs - (Date.now() - startedAt)));
424
+ if (retryDelayMs > 0) {
425
+ await sleep(retryDelayMs);
426
+ }
427
+ }
428
+ }
429
+ throw new Error(`readiness timed out after ${options.startupTimeoutMs}ms: ${state.lastReadinessError ?? "no readiness result"}`);
430
+ }
431
+
432
+ function containerCreateArgs(kind, options) {
433
+ const port = kind === "postgres" ? POSTGRES_PORT : SQLSERVER_PORT;
434
+ const publish = options.hostPort === undefined
435
+ ? `${LOCALHOST}::${port}`
436
+ : `${LOCALHOST}:${options.hostPort}:${port}`;
437
+ if (kind === "postgres") {
438
+ return [
439
+ "create",
440
+ "--name",
441
+ options.containerName,
442
+ "-e",
443
+ `POSTGRES_USER=${options.username}`,
444
+ "-e",
445
+ `POSTGRES_PASSWORD=${options.password}`,
446
+ "-e",
447
+ `POSTGRES_DB=${options.database}`,
448
+ "-p",
449
+ publish,
450
+ options.image,
451
+ ];
452
+ }
453
+ return [
454
+ "create",
455
+ "--name",
456
+ options.containerName,
457
+ "-e",
458
+ "ACCEPT_EULA=Y",
459
+ "-e",
460
+ `MSSQL_SA_PASSWORD=${options.password}`,
461
+ "-e",
462
+ "MSSQL_PID=Developer",
463
+ "-p",
464
+ publish,
465
+ options.image,
466
+ ];
467
+ }
468
+
469
+ function providerPlaceholder(kind) {
470
+ return kind === "postgres" ? "postgres" : "named";
471
+ }
472
+
473
+ function envWithPrefix(entries, prefix) {
474
+ if (prefix === undefined || prefix === "") {
475
+ return Object.freeze(entries);
476
+ }
477
+ const normalizedPrefix = String(prefix).replace(/_$/u, "");
478
+ return Object.freeze(Object.fromEntries(
479
+ Object.entries(entries).map(([key, value]) => [`${normalizedPrefix}_${key}`, value]),
480
+ ));
481
+ }
482
+
483
+ function normalizeMigrationList(pathOrGlob) {
484
+ if (typeof pathOrGlob === "string") {
485
+ if (pathOrGlob.length === 0 || pathOrGlob.includes("\0")) {
486
+ throw new TypeError("Sloppy TestServices migration path must be a non-empty string without NUL.");
487
+ }
488
+ return [pathOrGlob];
489
+ }
490
+ if (!Array.isArray(pathOrGlob) || pathOrGlob.length === 0) {
491
+ throw new TypeError("Sloppy TestServices migrate expects a path string or non-empty path array.");
492
+ }
493
+ return pathOrGlob.map((entry) => {
494
+ if (typeof entry !== "string" || entry.length === 0 || entry.includes("\0")) {
495
+ throw new TypeError("Sloppy TestServices migration paths must be non-empty strings without NUL.");
496
+ }
497
+ return entry;
498
+ });
499
+ }
500
+
501
+ function isSqlGlob(path) {
502
+ return /(?:^|[\\/])\*\.sql$/u.test(path);
503
+ }
504
+
505
+ async function applyMigrationPath(db, kind, path) {
506
+ if (isSqlGlob(path)) {
507
+ await Migrations.apply(db, { provider: kind, path });
508
+ return;
509
+ }
510
+ if (!path.endsWith(".sql")) {
511
+ throw new Error(`SLOPPY_E_TESTSERVICES_MIGRATION_PATH: migration path must be a .sql file or directory glob ending in *.sql.
512
+
513
+ Path:
514
+ ${path}`);
515
+ }
516
+ let sqlText;
517
+ try {
518
+ sqlText = await File.readText(path);
519
+ } catch (error) {
520
+ throw new Error(`SLOPPY_E_TESTSERVICES_MIGRATION_MISSING: migration file is missing or unreadable.
521
+
522
+ Migration:
523
+ ${path}`, { cause: error });
524
+ }
525
+ try {
526
+ await db.exec(sqlText, []);
527
+ } catch (error) {
528
+ throw new Error(`SLOPPY_E_TESTSERVICES_MIGRATION_FAILED: migration failed.
529
+
530
+ Migration:
531
+ ${path}
532
+
533
+ Reason:
534
+ ${String(error?.message ?? error)}`, { cause: error });
535
+ }
536
+ }
537
+
538
+ function resetSql(kind) {
539
+ if (kind === "postgres") {
540
+ return Object.freeze([
541
+ "drop schema if exists public cascade",
542
+ "create schema public",
543
+ ]);
544
+ }
545
+ throw new Error("SQL Server reset uses database recreation from master.");
546
+ }
547
+
548
+ function sqlServerIdentifier(value) {
549
+ return `[${String(value).replaceAll("]", "]]")}]`;
550
+ }
551
+
552
+ function sqlServerStringLiteral(value) {
553
+ return `N'${String(value).replaceAll("'", "''")}'`;
554
+ }
555
+
556
+ function resetSqlServerDatabaseSql(database) {
557
+ const name = sqlServerIdentifier(database);
558
+ const literal = sqlServerStringLiteral(database);
559
+ return `
560
+ if db_id(${literal}) is not null
561
+ begin
562
+ alter database ${name} set single_user with rollback immediate;
563
+ drop database ${name};
564
+ end;
565
+ create database ${name};`;
566
+ }
567
+
568
+ async function resetSqlServerDatabase(options, port) {
569
+ const masterConnectionString = sqlServerConnectionString({
570
+ ...options,
571
+ database: "master",
572
+ }, LOCALHOST, port);
573
+ await withDb("sqlserver", masterConnectionString, (db) =>
574
+ db.exec(resetSqlServerDatabaseSql(options.database), []));
575
+ }
576
+
577
+ function createService(kind, options, backend, state, connectionString, port) {
578
+ const ownedProviders = new Set();
579
+ const migrations = [];
580
+ let disposed = false;
581
+ state.port = port;
582
+
583
+ const service = {
584
+ kind,
585
+ id: state.containerId,
586
+ image: options.image,
587
+ host: LOCALHOST,
588
+ port,
589
+ connectionString,
590
+ async start() {
591
+ return service;
592
+ },
593
+ async stop() {
594
+ await service.dispose();
595
+ },
596
+ async dispose() {
597
+ if (disposed) {
598
+ return;
599
+ }
600
+ state.startupState = "disposing";
601
+ for (const provider of ownedProviders) {
602
+ await Promise.resolve(provider.close?.()).catch(() => {});
603
+ }
604
+ ownedProviders.clear();
605
+ await removeContainer(backend, state.containerId, options, state);
606
+ disposed = true;
607
+ state.startupState = "disposed";
608
+ state.timings.disposedAt = new Date().toISOString();
609
+ },
610
+ exec(sql, params = []) {
611
+ if (typeof sql !== "string" || sql.length === 0) {
612
+ throw new TypeError("Sloppy TestServices exec SQL must be a non-empty string.");
613
+ }
614
+ if (!Array.isArray(params)) {
615
+ throw new TypeError("Sloppy TestServices exec params must be an array.");
616
+ }
617
+ return withDb(kind, connectionString, (db) => db.exec(sql, params));
618
+ },
619
+ async migrate(pathOrGlob) {
620
+ const paths = normalizeMigrationList(pathOrGlob);
621
+ await withDb(kind, connectionString, async (db) => {
622
+ for (const path of paths) {
623
+ await applyMigrationPath(db, kind, path);
624
+ migrations.push(path);
625
+ }
626
+ });
627
+ },
628
+ async seed(fn) {
629
+ if (typeof fn !== "function") {
630
+ throw new TypeError("Sloppy TestServices seed callback must be a function.");
631
+ }
632
+ await withDb(kind, connectionString, (db) => fn(db));
633
+ },
634
+ async reset(resetOptions = {}) {
635
+ if (!isPlainObject(resetOptions)) {
636
+ throw new TypeError("Sloppy TestServices reset options must be a plain object.");
637
+ }
638
+ const rerun = resetOptions.rerunMigrations === true || resetOptions.migrate === true;
639
+ const selectedMigrations = resetOptions.migrations === undefined
640
+ ? migrations
641
+ : normalizeMigrationList(resetOptions.migrations);
642
+ if (kind === "sqlserver") {
643
+ await resetSqlServerDatabase(options, port);
644
+ if (rerun) {
645
+ await withDb(kind, connectionString, async (db) => {
646
+ for (const path of selectedMigrations) {
647
+ await applyMigrationPath(db, kind, path);
648
+ }
649
+ });
650
+ }
651
+ return;
652
+ }
653
+ await withDb(kind, connectionString, async (db) => {
654
+ for (const sqlText of resetSql(kind)) {
655
+ await db.exec(sqlText, []);
656
+ }
657
+ if (rerun) {
658
+ for (const path of selectedMigrations) {
659
+ await applyMigrationPath(db, kind, path);
660
+ }
661
+ }
662
+ });
663
+ },
664
+ provider() {
665
+ const provider = openProvider(kind, connectionString);
666
+ ownedProviders.add(provider);
667
+ return provider;
668
+ },
669
+ env(prefix = undefined) {
670
+ if (kind === "postgres") {
671
+ return envWithPrefix({
672
+ POSTGRES_HOST: LOCALHOST,
673
+ POSTGRES_PORT: String(port),
674
+ POSTGRES_USER: options.username,
675
+ POSTGRES_PASSWORD: options.password,
676
+ POSTGRES_DB: options.database,
677
+ DATABASE_URL: connectionString,
678
+ }, prefix);
679
+ }
680
+ return envWithPrefix({
681
+ SQLSERVER_HOST: LOCALHOST,
682
+ SQLSERVER_PORT: String(port),
683
+ SQLSERVER_USER: options.username,
684
+ SQLSERVER_PASSWORD: options.password,
685
+ SQLSERVER_DATABASE: options.database,
686
+ SQLSERVER_DRIVER: options.driver,
687
+ SQLSERVER_CONNECTION_STRING: connectionString,
688
+ }, prefix);
689
+ },
690
+ async logs(logOptions = {}) {
691
+ const tail = normalizePort(logOptions.tail, DEFAULT_LOG_TAIL, "logs tail");
692
+ const logs = await dockerLogs(backend, state.containerId, tail, options);
693
+ state.logTail = redactWithSecrets(logs, state.secrets);
694
+ return state.logTail;
695
+ },
696
+ diagnostics() {
697
+ return safeObject({
698
+ kind,
699
+ image: options.image,
700
+ containerId: state.containerId?.slice(0, 12),
701
+ containerName: options.containerName,
702
+ host: LOCALHOST,
703
+ port,
704
+ startupState: state.startupState,
705
+ readinessAttempts: state.readinessAttempts,
706
+ lastReadinessError: state.lastReadinessError,
707
+ cleanupErrors: state.cleanupErrors,
708
+ logTail: state.logTail,
709
+ timings: state.timings,
710
+ provider: {
711
+ kind,
712
+ placeholderStyle: providerPlaceholder(kind),
713
+ nativeStdlibBridge: providerBridgeAvailable(kind),
714
+ },
715
+ }, state.secrets);
716
+ },
717
+ };
718
+ if (ASYNC_DISPOSE !== undefined) {
719
+ service[ASYNC_DISPOSE] = service.dispose;
720
+ }
721
+ return Object.freeze(service);
722
+ }
723
+
724
+ function startupFailureMessage(kind, options, state, reason) {
725
+ const provider = kind === "postgres" ? "PostgreSQL" : "SQL Server";
726
+ const logTail = redactWithSecrets(state.logTail, state.secrets);
727
+ const lastReadinessError = redactWithSecrets(state.lastReadinessError ?? reason, state.secrets);
728
+ return `SLOPPY_E_TESTSERVICES_STARTUP_FAILED: ${provider} TestServices container did not become ready.
729
+
730
+ Image:
731
+ ${options.image}
732
+
733
+ Container:
734
+ ${state.containerId === undefined ? options.containerName : `${options.containerName} (${state.containerId.slice(0, 12)})`}
735
+
736
+ Mapped port:
737
+ ${state.port ?? "unknown"}
738
+
739
+ Reason:
740
+ ${lastReadinessError}
741
+
742
+ Cleanup failures:
743
+ ${state.cleanupErrors.length === 0 ? " <none>" : state.cleanupErrors.map((entry) => ` - ${entry.operation}: ${entry.message}`).join("\n")}
744
+
745
+ Docker logs tail:
746
+ ${logTail.length === 0 ? " <empty>" : logTail}
747
+
748
+ Suggested checks:
749
+ - Docker is running.
750
+ - The image can be pulled.
751
+ - The mapped port is not blocked.
752
+ - SQL Server startup can be slow on cold machines.`;
753
+ }
754
+
755
+ async function startService(kind, rawOptions) {
756
+ const options = kind === "postgres"
757
+ ? normalizedPostgresOptions(rawOptions)
758
+ : normalizedSqlServerOptions(rawOptions);
759
+ const backend = dockerBackend(options);
760
+ const state = createDiagnosticState(kind, options.image, options.containerName, [options.password]);
761
+ let connectionString = undefined;
762
+ try {
763
+ if (!providerBridgeAvailable(kind)) {
764
+ throw providerUnavailableError(kind);
765
+ }
766
+ await dockerRequire({ dockerBackend: backend });
767
+ await ensureImage(backend, options.image, options);
768
+ const create = await dockerRunOk(backend, containerCreateArgs(kind, options), {
769
+ timeoutMs: options.dockerTimeoutMs ?? 30000,
770
+ maxStdoutBytes: 64 * 1024,
771
+ maxStderrBytes: 64 * 1024,
772
+ });
773
+ state.containerId = create.stdout.trim();
774
+ state.startupState = "starting";
775
+ state.timings.startedAt = new Date().toISOString();
776
+ await dockerRunOk(backend, ["start", state.containerId], {
777
+ timeoutMs: options.dockerTimeoutMs ?? 30000,
778
+ maxStdoutBytes: 64 * 1024,
779
+ maxStderrBytes: 64 * 1024,
780
+ });
781
+ const inspected = await inspectContainer(
782
+ backend,
783
+ state.containerId,
784
+ kind === "postgres" ? POSTGRES_PORT : SQLSERVER_PORT,
785
+ options,
786
+ );
787
+ state.port = inspected.port;
788
+ connectionString = kind === "postgres"
789
+ ? postgresConnectionString(options, LOCALHOST, inspected.port)
790
+ : sqlServerConnectionString(options, LOCALHOST, inspected.port);
791
+ await waitForReady(kind, state, connectionString, options);
792
+ if (options.migrations !== undefined) {
793
+ const pendingService = createService(kind, options, backend, state, connectionString, inspected.port);
794
+ await pendingService.migrate(options.migrations);
795
+ return pendingService;
796
+ }
797
+ return createService(kind, options, backend, state, connectionString, inspected.port);
798
+ } catch (error) {
799
+ state.startupState = "failed";
800
+ state.lastReadinessError = String(error?.message ?? error);
801
+ state.logTail = await dockerLogs(backend, state.containerId, DEFAULT_LOG_TAIL, options);
802
+ if (!options.keepContainerOnFailure) {
803
+ await removeContainer(backend, state.containerId, options, state).catch(() => {});
804
+ }
805
+ const startupError = error?.code === "SLOPPY_E_TESTSERVICES_DOCKER_UNAVAILABLE" ||
806
+ error?.code === "SLOPPY_E_TESTSERVICES_PROVIDER_UNAVAILABLE"
807
+ ? error
808
+ : new Error(startupFailureMessage(kind, options, state, error?.message ?? error), { cause: error });
809
+ throw startupError;
810
+ }
811
+ }
812
+
813
+ function redisUrl(options, host, port) {
814
+ const auth = options.password === undefined ? "" : `:${encodeURIComponent(options.password)}@`;
815
+ return `redis://${auth}${host}:${port}/${options.database}`;
816
+ }
817
+
818
+ function redisContainerCreateArgs(options) {
819
+ const publish = options.hostPort === undefined
820
+ ? `${LOCALHOST}::${REDIS_PORT}`
821
+ : `${LOCALHOST}:${options.hostPort}:${REDIS_PORT}`;
822
+ const args = [
823
+ "create",
824
+ "--name",
825
+ options.containerName,
826
+ "-p",
827
+ publish,
828
+ options.image,
829
+ ];
830
+ if (options.password !== undefined) {
831
+ args.push("redis-server", "--requirepass", options.password);
832
+ }
833
+ return args;
834
+ }
835
+
836
+ async function waitForRedisReady(state, url, options) {
837
+ const startedAt = Date.now();
838
+ state.startupState = "waiting";
839
+ while (Date.now() - startedAt < options.readinessTimeoutMs) {
840
+ const remainingMs = options.readinessTimeoutMs - (Date.now() - startedAt);
841
+ state.readinessAttempts += 1;
842
+ let client;
843
+ try {
844
+ client = Redis.client("testservices-readiness", {
845
+ url,
846
+ database: options.database,
847
+ connectTimeoutMs: Math.min(1000, Math.max(1, remainingMs)),
848
+ commandTimeoutMs: Math.min(1000, Math.max(1, remainingMs)),
849
+ pool: { maxConnections: 1, pendingQueueLimit: 1 },
850
+ });
851
+ await client.ping();
852
+ state.startupState = "ready";
853
+ state.timings.readyAt = new Date().toISOString();
854
+ return;
855
+ } catch (error) {
856
+ state.lastReadinessError = String(error?.message ?? error);
857
+ const retryDelayMs = Math.min(500, Math.max(0, options.readinessTimeoutMs - (Date.now() - startedAt)));
858
+ if (retryDelayMs > 0) {
859
+ await sleep(retryDelayMs);
860
+ }
861
+ } finally {
862
+ await client?.dispose?.().catch(() => {});
863
+ }
864
+ }
865
+ throw new Error(`Redis readiness timed out after ${options.readinessTimeoutMs}ms: ${state.lastReadinessError ?? "no readiness result"}`);
866
+ }
867
+
868
+ function createRedisService(options, backend, state, url, port) {
869
+ const ownedClients = new Set();
870
+ let disposed = false;
871
+ state.port = port;
872
+ const service = {
873
+ kind: "redis",
874
+ id: state.containerId,
875
+ image: options.image,
876
+ host: LOCALHOST,
877
+ port,
878
+ url,
879
+ connectionString: url,
880
+ async start() {
881
+ return service;
882
+ },
883
+ async stop() {
884
+ await service.dispose();
885
+ },
886
+ client(name = "test") {
887
+ const client = Redis.client(name, {
888
+ url,
889
+ database: options.database,
890
+ connectTimeoutMs: 2000,
891
+ commandTimeoutMs: 2000,
892
+ pool: { maxConnections: 2, pendingQueueLimit: 8 },
893
+ });
894
+ ownedClients.add(client);
895
+ return client;
896
+ },
897
+ async flush() {
898
+ const client = service.client("testservices-flush");
899
+ try {
900
+ await client.command("FLUSHDB");
901
+ } finally {
902
+ await client.dispose();
903
+ ownedClients.delete(client);
904
+ }
905
+ },
906
+ async reset() {
907
+ await service.flush();
908
+ },
909
+ env(prefix = undefined) {
910
+ const values = {
911
+ REDIS_HOST: LOCALHOST,
912
+ REDIS_PORT: String(port),
913
+ REDIS_DATABASE: String(options.database),
914
+ REDIS_URL: url,
915
+ "Redis:Url": url,
916
+ "Sloppy__Redis__main__url": url,
917
+ };
918
+ if (options.password !== undefined) {
919
+ values.REDIS_PASSWORD = options.password;
920
+ }
921
+ return envWithPrefix(values, prefix);
922
+ },
923
+ async logs(logOptions = {}) {
924
+ const tail = normalizePort(logOptions.tail, DEFAULT_LOG_TAIL, "logs tail");
925
+ const logs = await dockerLogs(backend, state.containerId, tail, options);
926
+ state.logTail = redactWithSecrets(logs, state.secrets);
927
+ return state.logTail;
928
+ },
929
+ diagnostics() {
930
+ return safeObject({
931
+ kind: "redis",
932
+ image: options.image,
933
+ containerId: state.containerId?.slice(0, 12),
934
+ containerName: options.containerName,
935
+ host: LOCALHOST,
936
+ port,
937
+ url,
938
+ database: options.database,
939
+ startupState: state.startupState,
940
+ readinessAttempts: state.readinessAttempts,
941
+ lastReadinessError: state.lastReadinessError,
942
+ cleanupErrors: state.cleanupErrors,
943
+ logTail: state.logTail,
944
+ timings: state.timings,
945
+ }, state.secrets);
946
+ },
947
+ async dispose() {
948
+ if (disposed) {
949
+ return;
950
+ }
951
+ state.startupState = "disposing";
952
+ for (const client of ownedClients) {
953
+ await client.dispose().catch(() => {});
954
+ }
955
+ ownedClients.clear();
956
+ await removeContainer(backend, state.containerId, options, state);
957
+ disposed = true;
958
+ state.startupState = "disposed";
959
+ state.timings.disposedAt = new Date().toISOString();
960
+ },
961
+ };
962
+ if (ASYNC_DISPOSE !== undefined) {
963
+ service[ASYNC_DISPOSE] = service.dispose;
964
+ }
965
+ return Object.freeze(service);
966
+ }
967
+
968
+ async function startRedisService(rawOptions) {
969
+ const options = normalizedRedisOptions(rawOptions);
970
+ const backend = dockerBackend(options);
971
+ const state = createDiagnosticState("redis", options.image, options.containerName, [options.password].filter(Boolean));
972
+ try {
973
+ await dockerRequire({ dockerBackend: backend });
974
+ await ensureImage(backend, options.image, options);
975
+ const create = await dockerRunOk(backend, redisContainerCreateArgs(options), {
976
+ timeoutMs: options.dockerTimeoutMs ?? 30000,
977
+ maxStdoutBytes: 64 * 1024,
978
+ maxStderrBytes: 64 * 1024,
979
+ });
980
+ state.containerId = create.stdout.trim();
981
+ state.startupState = "starting";
982
+ state.timings.startedAt = new Date().toISOString();
983
+ await dockerRunOk(backend, ["start", state.containerId], {
984
+ timeoutMs: options.dockerTimeoutMs ?? 30000,
985
+ maxStdoutBytes: 64 * 1024,
986
+ maxStderrBytes: 64 * 1024,
987
+ });
988
+ const inspected = await inspectContainer(backend, state.containerId, REDIS_PORT, options);
989
+ state.port = inspected.port;
990
+ const url = redisUrl(options, LOCALHOST, inspected.port);
991
+ await waitForRedisReady(state, url, options);
992
+ return createRedisService(options, backend, state, url, inspected.port);
993
+ } catch (error) {
994
+ state.startupState = "failed";
995
+ state.lastReadinessError = String(error?.message ?? error);
996
+ state.logTail = await dockerLogs(backend, state.containerId, DEFAULT_LOG_TAIL, options);
997
+ if (!options.keepContainerOnFailure) {
998
+ await removeContainer(backend, state.containerId, options, state).catch(() => {});
999
+ }
1000
+ if (error?.code === "SLOPPY_E_TESTSERVICES_DOCKER_UNAVAILABLE") {
1001
+ throw error;
1002
+ }
1003
+ throw new Error(`SLOPPY_E_TESTSERVICES_STARTUP_FAILED: Redis TestServices container did not become ready.
1004
+
1005
+ Image:
1006
+ ${options.image}
1007
+
1008
+ Container:
1009
+ ${state.containerId === undefined ? options.containerName : `${options.containerName} (${state.containerId.slice(0, 12)})`}
1010
+
1011
+ Mapped port:
1012
+ ${state.port ?? "unknown"}
1013
+
1014
+ Reason:
1015
+ ${redactWithSecrets(state.lastReadinessError, state.secrets)}
1016
+
1017
+ Cleanup failures:
1018
+ ${state.cleanupErrors.length === 0 ? " <none>" : state.cleanupErrors.map((entry) => ` - ${entry.operation}: ${redactWithSecrets(entry.message, state.secrets)}`).join("\n")}
1019
+
1020
+ Docker logs tail:
1021
+ ${state.logTail.length === 0 ? " <empty>" : redactWithSecrets(state.logTail, state.secrets)}`, { cause: error });
1022
+ }
1023
+ }
1024
+
1025
+ const TestServices = Object.freeze({
1026
+ docker: Object.freeze({
1027
+ available: dockerAvailable,
1028
+ require: dockerRequire,
1029
+ }),
1030
+ postgres(options = {}) {
1031
+ return startService("postgres", options);
1032
+ },
1033
+ sqlServer(options = {}) {
1034
+ return startService("sqlserver", options);
1035
+ },
1036
+ redis(options = {}) {
1037
+ return startRedisService(options);
1038
+ },
1039
+ });
1040
+
1041
+ export { DockerCliBackend, TestServices };