@edge-base/server 0.1.1

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 (309) hide show
  1. package/admin-build/.gitkeep +0 -0
  2. package/admin-build/_app/env.js +1 -0
  3. package/admin-build/_app/immutable/assets/0.Bm6cF078.css +1 -0
  4. package/admin-build/_app/immutable/assets/1.BfW3pUNa.css +1 -0
  5. package/admin-build/_app/immutable/assets/11.CVmQOewb.css +1 -0
  6. package/admin-build/_app/immutable/assets/12.B1EhbRZT.css +1 -0
  7. package/admin-build/_app/immutable/assets/13.BvwYeuwE.css +1 -0
  8. package/admin-build/_app/immutable/assets/14.CdVfcO0R.css +1 -0
  9. package/admin-build/_app/immutable/assets/15.2yeZ66b-.css +1 -0
  10. package/admin-build/_app/immutable/assets/17.BVg0JEVu.css +1 -0
  11. package/admin-build/_app/immutable/assets/18.Rwnl3x_i.css +1 -0
  12. package/admin-build/_app/immutable/assets/20.DsPWA9AV.css +1 -0
  13. package/admin-build/_app/immutable/assets/21.Dz2RJ56c.css +1 -0
  14. package/admin-build/_app/immutable/assets/22.DwNLk5Ai.css +1 -0
  15. package/admin-build/_app/immutable/assets/23.CFpu0gOO.css +1 -0
  16. package/admin-build/_app/immutable/assets/24.Cy5LBeoJ.css +1 -0
  17. package/admin-build/_app/immutable/assets/25.pUyLVf-h.css +1 -0
  18. package/admin-build/_app/immutable/assets/26.DBcGrlXa.css +1 -0
  19. package/admin-build/_app/immutable/assets/27.BswYyAJD.css +1 -0
  20. package/admin-build/_app/immutable/assets/28.B4ueB1Kf.css +1 -0
  21. package/admin-build/_app/immutable/assets/29.B-qU6PdF.css +1 -0
  22. package/admin-build/_app/immutable/assets/3.Dg81Pgmd.css +1 -0
  23. package/admin-build/_app/immutable/assets/30.CsdWum94.css +1 -0
  24. package/admin-build/_app/immutable/assets/31.U6OwIp50.css +1 -0
  25. package/admin-build/_app/immutable/assets/4.CyawCCux.css +1 -0
  26. package/admin-build/_app/immutable/assets/5.C0YO2HTk.css +1 -0
  27. package/admin-build/_app/immutable/assets/8.Br5jd6kD.css +1 -0
  28. package/admin-build/_app/immutable/assets/Badge.EMYLHBxE.css +1 -0
  29. package/admin-build/_app/immutable/assets/Button.DpzMRTjK.css +1 -0
  30. package/admin-build/_app/immutable/assets/ConfirmDialog.DAnaWRRk.css +1 -0
  31. package/admin-build/_app/immutable/assets/EmptyState.CwKsu57Y.css +1 -0
  32. package/admin-build/_app/immutable/assets/Input.BDUSenmU.css +1 -0
  33. package/admin-build/_app/immutable/assets/Modal.Dm5B0Xie.css +1 -0
  34. package/admin-build/_app/immutable/assets/PageShell.CmU-Xh-b.css +1 -0
  35. package/admin-build/_app/immutable/assets/SchemaFieldEditor.g4NsCdno.css +1 -0
  36. package/admin-build/_app/immutable/assets/Select.BW4Keufm.css +1 -0
  37. package/admin-build/_app/immutable/assets/Skeleton.KWUulTKJ.css +1 -0
  38. package/admin-build/_app/immutable/assets/Tabs.CniGYb67.css +1 -0
  39. package/admin-build/_app/immutable/assets/TimeChart.BTCDAvmT.css +1 -0
  40. package/admin-build/_app/immutable/assets/Toggle.Cy_K12OM.css +1 -0
  41. package/admin-build/_app/immutable/assets/TopList.ClFzmPlA.css +1 -0
  42. package/admin-build/_app/immutable/chunks/7B47DvSx.js +1 -0
  43. package/admin-build/_app/immutable/chunks/7f08Id8e.js +1 -0
  44. package/admin-build/_app/immutable/chunks/8wJeQ7LN.js +1 -0
  45. package/admin-build/_app/immutable/chunks/B-h2afW5.js +1 -0
  46. package/admin-build/_app/immutable/chunks/B8vJP3wz.js +1 -0
  47. package/admin-build/_app/immutable/chunks/BR_fL5Yv.js +1 -0
  48. package/admin-build/_app/immutable/chunks/BY92tFS2.js +1 -0
  49. package/admin-build/_app/immutable/chunks/BcR-Rdj9.js +1 -0
  50. package/admin-build/_app/immutable/chunks/BdrwyZv8.js +1 -0
  51. package/admin-build/_app/immutable/chunks/Bh56EfQ_.js +1 -0
  52. package/admin-build/_app/immutable/chunks/BkrCkgYp.js +1 -0
  53. package/admin-build/_app/immutable/chunks/BmRjiP5k.js +1 -0
  54. package/admin-build/_app/immutable/chunks/BsokvhWC.js +1 -0
  55. package/admin-build/_app/immutable/chunks/C4D51vTW.js +1 -0
  56. package/admin-build/_app/immutable/chunks/C6puvcoR.js +2 -0
  57. package/admin-build/_app/immutable/chunks/CCKNu7m7.js +1 -0
  58. package/admin-build/_app/immutable/chunks/CWj6FrbW.js +1 -0
  59. package/admin-build/_app/immutable/chunks/Ce-ngf4p.js +5 -0
  60. package/admin-build/_app/immutable/chunks/Cs0GwzJA.js +1 -0
  61. package/admin-build/_app/immutable/chunks/CwROoZK0.js +1 -0
  62. package/admin-build/_app/immutable/chunks/CxCPv_Ut.js +1 -0
  63. package/admin-build/_app/immutable/chunks/CxbRue-5.js +1 -0
  64. package/admin-build/_app/immutable/chunks/CyqB6g-D.js +1 -0
  65. package/admin-build/_app/immutable/chunks/D5h5A1cc.js +2 -0
  66. package/admin-build/_app/immutable/chunks/DnyL7Zq-.js +1 -0
  67. package/admin-build/_app/immutable/chunks/DoPXzH7F.js +1 -0
  68. package/admin-build/_app/immutable/chunks/DrQSgw-f.js +1 -0
  69. package/admin-build/_app/immutable/chunks/DttM2zNO.js +1 -0
  70. package/admin-build/_app/immutable/chunks/DuXuUBWN.js +1 -0
  71. package/admin-build/_app/immutable/chunks/MdeqaOQx.js +10 -0
  72. package/admin-build/_app/immutable/chunks/NuUjtcO2.js +1 -0
  73. package/admin-build/_app/immutable/chunks/Q2nPFxS6.js +1 -0
  74. package/admin-build/_app/immutable/chunks/R6arueIl.js +1 -0
  75. package/admin-build/_app/immutable/chunks/UUazaC_N.js +1 -0
  76. package/admin-build/_app/immutable/chunks/cOYbrQxx.js +1 -0
  77. package/admin-build/_app/immutable/chunks/eFQHTGwA.js +1 -0
  78. package/admin-build/_app/immutable/chunks/ehbppgYb.js +1 -0
  79. package/admin-build/_app/immutable/chunks/glwixJlP.js +1 -0
  80. package/admin-build/_app/immutable/chunks/vApWTCBs.js +1 -0
  81. package/admin-build/_app/immutable/chunks/w89G9Xpi.js +1 -0
  82. package/admin-build/_app/immutable/chunks/wJsUhbfZ.js +1 -0
  83. package/admin-build/_app/immutable/chunks/zfauFM8P.js +1 -0
  84. package/admin-build/_app/immutable/entry/app.CcO-Uos3.js +2 -0
  85. package/admin-build/_app/immutable/entry/start.COebYq3I.js +1 -0
  86. package/admin-build/_app/immutable/nodes/0.CjtHKU-6.js +1 -0
  87. package/admin-build/_app/immutable/nodes/1.DEisjlM0.js +1 -0
  88. package/admin-build/_app/immutable/nodes/10.CvhdyWVB.js +1 -0
  89. package/admin-build/_app/immutable/nodes/11.DjHqcOvy.js +1 -0
  90. package/admin-build/_app/immutable/nodes/12.mQLz4Mj_.js +1 -0
  91. package/admin-build/_app/immutable/nodes/13.CBonZZyP.js +110 -0
  92. package/admin-build/_app/immutable/nodes/14.d-oiZL0j.js +3 -0
  93. package/admin-build/_app/immutable/nodes/15.CKPQsUYF.js +1 -0
  94. package/admin-build/_app/immutable/nodes/16.wPzAPQGx.js +1 -0
  95. package/admin-build/_app/immutable/nodes/17.DayhKyEZ.js +1 -0
  96. package/admin-build/_app/immutable/nodes/18.DKwS0Ir0.js +1 -0
  97. package/admin-build/_app/immutable/nodes/19.wPzAPQGx.js +1 -0
  98. package/admin-build/_app/immutable/nodes/2.BKoKrw1i.js +1 -0
  99. package/admin-build/_app/immutable/nodes/20.BvIkkkrW.js +1 -0
  100. package/admin-build/_app/immutable/nodes/21.DMaFhdHk.js +128 -0
  101. package/admin-build/_app/immutable/nodes/22.3xdgwuK1.js +1 -0
  102. package/admin-build/_app/immutable/nodes/23.8Bvgjbsl.js +112 -0
  103. package/admin-build/_app/immutable/nodes/24.DzSSzRhG.js +2 -0
  104. package/admin-build/_app/immutable/nodes/25.9KKYBnAE.js +2 -0
  105. package/admin-build/_app/immutable/nodes/26.Bhn9dfhY.js +1 -0
  106. package/admin-build/_app/immutable/nodes/27.kRLiC24G.js +1 -0
  107. package/admin-build/_app/immutable/nodes/28.BVIN1-7N.js +1 -0
  108. package/admin-build/_app/immutable/nodes/29.3yabZWj4.js +1 -0
  109. package/admin-build/_app/immutable/nodes/3.BFtSOkX7.js +2 -0
  110. package/admin-build/_app/immutable/nodes/30.CyCQlwaP.js +1 -0
  111. package/admin-build/_app/immutable/nodes/31.C4LDXjES.js +1 -0
  112. package/admin-build/_app/immutable/nodes/4.CvbiMlCa.js +1 -0
  113. package/admin-build/_app/immutable/nodes/5.C6BLv2eM.js +1 -0
  114. package/admin-build/_app/immutable/nodes/6.BcXvfl2P.js +1 -0
  115. package/admin-build/_app/immutable/nodes/7.CIuqhPiK.js +1 -0
  116. package/admin-build/_app/immutable/nodes/8.BQOR_JfO.js +1 -0
  117. package/admin-build/_app/immutable/nodes/9.NZqXQxPy.js +1 -0
  118. package/admin-build/_app/version.json +1 -0
  119. package/admin-build/favicon.svg +26 -0
  120. package/admin-build/index.html +45 -0
  121. package/openapi.json +19543 -0
  122. package/package.json +66 -0
  123. package/src/__tests__/admin-assets.test.ts +55 -0
  124. package/src/__tests__/admin-data-routes.test.ts +488 -0
  125. package/src/__tests__/admin-db-target.test.ts +103 -0
  126. package/src/__tests__/admin-routing.test.ts +31 -0
  127. package/src/__tests__/admin-user-management.test.ts +311 -0
  128. package/src/__tests__/analytics-query.test.ts +75 -0
  129. package/src/__tests__/auth-d1.test.ts +749 -0
  130. package/src/__tests__/auth-db-adapter.test.ts +73 -0
  131. package/src/__tests__/auth-jwt.test.ts +440 -0
  132. package/src/__tests__/auth-oauth.test.ts +389 -0
  133. package/src/__tests__/auth-password.test.ts +367 -0
  134. package/src/__tests__/auth-redirect.test.ts +87 -0
  135. package/src/__tests__/backup-restore.test.ts +711 -0
  136. package/src/__tests__/broadcast.test.ts +128 -0
  137. package/src/__tests__/cli.test.ts +178 -0
  138. package/src/__tests__/cloudflare-realtime.test.ts +113 -0
  139. package/src/__tests__/config.test.ts +469 -0
  140. package/src/__tests__/cors.test.ts +154 -0
  141. package/src/__tests__/cron.test.ts +302 -0
  142. package/src/__tests__/d1-handler.test.ts +402 -0
  143. package/src/__tests__/d1-sql.test.ts +120 -0
  144. package/src/__tests__/database-live-config.test.ts +42 -0
  145. package/src/__tests__/database-live-emitter.test.ts +56 -0
  146. package/src/__tests__/database-live-filters.test.ts +63 -0
  147. package/src/__tests__/database-live-route.test.ts +113 -0
  148. package/src/__tests__/db-sql.test.ts +163 -0
  149. package/src/__tests__/do-lifecycle.test.ts +263 -0
  150. package/src/__tests__/do-router.test.ts +729 -0
  151. package/src/__tests__/email-provider.test.ts +128 -0
  152. package/src/__tests__/email-templates.test.ts +528 -0
  153. package/src/__tests__/error-format.test.ts +250 -0
  154. package/src/__tests__/field-ops.test.ts +242 -0
  155. package/src/__tests__/functions-context.test.ts +334 -0
  156. package/src/__tests__/functions-d1-proxy.test.ts +229 -0
  157. package/src/__tests__/functions-registry-runtime-config.test.ts +17 -0
  158. package/src/__tests__/functions-route.test.ts +139 -0
  159. package/src/__tests__/internal-request.test.ts +77 -0
  160. package/src/__tests__/log-writer.test.ts +44 -0
  161. package/src/__tests__/logger.test.ts +58 -0
  162. package/src/__tests__/meta-admin-proxy.test.ts +48 -0
  163. package/src/__tests__/meta-export-coverage.test.ts +191 -0
  164. package/src/__tests__/meta-route-registration.test.ts +47 -0
  165. package/src/__tests__/namespace-dump.test.ts +28 -0
  166. package/src/__tests__/oauth-providers.test.ts +337 -0
  167. package/src/__tests__/openapi-coverage.test.ts +144 -0
  168. package/src/__tests__/pagination.test.ts +59 -0
  169. package/src/__tests__/password-policy.test.ts +191 -0
  170. package/src/__tests__/plugin-migrations.test.ts +379 -0
  171. package/src/__tests__/postgres-batch-compat.test.ts +133 -0
  172. package/src/__tests__/postgres-dialect.test.ts +328 -0
  173. package/src/__tests__/postgres-executor.test.ts +79 -0
  174. package/src/__tests__/postgres-field-ops-compat.test.ts +222 -0
  175. package/src/__tests__/postgres-schema-init.test.ts +105 -0
  176. package/src/__tests__/postgres-table-utils.test.ts +107 -0
  177. package/src/__tests__/presence.test.ts +199 -0
  178. package/src/__tests__/provider.test.ts +550 -0
  179. package/src/__tests__/public-user-profile.test.ts +339 -0
  180. package/src/__tests__/push-handlers.test.ts +179 -0
  181. package/src/__tests__/push-provider.test.ts +80 -0
  182. package/src/__tests__/push-token.test.ts +418 -0
  183. package/src/__tests__/query.test.ts +771 -0
  184. package/src/__tests__/rate-limit.test.ts +260 -0
  185. package/src/__tests__/room-access-policy.test.ts +101 -0
  186. package/src/__tests__/room-handler-context.test.ts +130 -0
  187. package/src/__tests__/room-monitoring.test.ts +138 -0
  188. package/src/__tests__/room-runtime-routing.test.ts +222 -0
  189. package/src/__tests__/room.test.ts +254 -0
  190. package/src/__tests__/route-parser.test.ts +490 -0
  191. package/src/__tests__/rules.test.ts +234 -0
  192. package/src/__tests__/runtime-surface-accounting.test.ts +120 -0
  193. package/src/__tests__/scheduled.test.ts +80 -0
  194. package/src/__tests__/schema.test.ts +1273 -0
  195. package/src/__tests__/security-hardening.test.ts +312 -0
  196. package/src/__tests__/server.unit.test.ts +333 -0
  197. package/src/__tests__/service-key-db-proxy.test.ts +650 -0
  198. package/src/__tests__/service-key-provider-bypass.test.ts +138 -0
  199. package/src/__tests__/service-key.test.ts +757 -0
  200. package/src/__tests__/smoke-skip-report.test.ts +72 -0
  201. package/src/__tests__/sms-provider.test.ts +39 -0
  202. package/src/__tests__/sql-route.test.ts +218 -0
  203. package/src/__tests__/storage-hook-context.test.ts +115 -0
  204. package/src/__tests__/totp.test.ts +200 -0
  205. package/src/__tests__/uuid.test.ts +144 -0
  206. package/src/__tests__/validation.test.ts +773 -0
  207. package/src/__tests__/websocket-pending.test.ts +163 -0
  208. package/src/_functions-registry.ts +51 -0
  209. package/src/bench-entry.ts +9 -0
  210. package/src/cloudflare-test.d.ts +1 -0
  211. package/src/durable-objects/auth-do.ts +49 -0
  212. package/src/durable-objects/database-do.ts +2240 -0
  213. package/src/durable-objects/database-live-do.ts +949 -0
  214. package/src/durable-objects/logs-do.ts +1200 -0
  215. package/src/durable-objects/room-runtime-base.ts +1604 -0
  216. package/src/durable-objects/rooms-do.ts +2191 -0
  217. package/src/generated-config.ts +6 -0
  218. package/src/index.ts +382 -0
  219. package/src/lib/admin-assets.ts +54 -0
  220. package/src/lib/admin-db-target.ts +301 -0
  221. package/src/lib/admin-routing.ts +35 -0
  222. package/src/lib/admin-user-management.ts +464 -0
  223. package/src/lib/analytics-adapter.ts +103 -0
  224. package/src/lib/analytics-query.ts +579 -0
  225. package/src/lib/auth-d1-service.ts +1193 -0
  226. package/src/lib/auth-d1.ts +1056 -0
  227. package/src/lib/auth-db-adapter.ts +289 -0
  228. package/src/lib/auth-redirect.ts +116 -0
  229. package/src/lib/cidr.ts +115 -0
  230. package/src/lib/client-ip.ts +51 -0
  231. package/src/lib/cloudflare-realtime.ts +251 -0
  232. package/src/lib/control-db.ts +36 -0
  233. package/src/lib/cron.ts +163 -0
  234. package/src/lib/d1-handler.ts +1425 -0
  235. package/src/lib/d1-schema-init.ts +255 -0
  236. package/src/lib/d1-sql.ts +33 -0
  237. package/src/lib/database-live-config.ts +24 -0
  238. package/src/lib/database-live-emitter.ts +111 -0
  239. package/src/lib/db-sql.ts +66 -0
  240. package/src/lib/do-retry.ts +36 -0
  241. package/src/lib/do-router.ts +270 -0
  242. package/src/lib/do-sql.ts +73 -0
  243. package/src/lib/email-provider.ts +379 -0
  244. package/src/lib/email-templates.ts +285 -0
  245. package/src/lib/email-translations.ts +422 -0
  246. package/src/lib/errors.ts +151 -0
  247. package/src/lib/functions.ts +2091 -0
  248. package/src/lib/hono.ts +56 -0
  249. package/src/lib/internal-request.ts +56 -0
  250. package/src/lib/jwt.ts +354 -0
  251. package/src/lib/log-writer.ts +272 -0
  252. package/src/lib/namespace-dump.ts +125 -0
  253. package/src/lib/oauth-providers.ts +1225 -0
  254. package/src/lib/op-parser.ts +99 -0
  255. package/src/lib/openapi.ts +146 -0
  256. package/src/lib/pagination.ts +19 -0
  257. package/src/lib/password-policy.ts +102 -0
  258. package/src/lib/password.ts +145 -0
  259. package/src/lib/plugin-migrations.ts +612 -0
  260. package/src/lib/postgres-executor.ts +203 -0
  261. package/src/lib/postgres-handler.ts +1102 -0
  262. package/src/lib/postgres-schema-init.ts +341 -0
  263. package/src/lib/postgres-table-utils.ts +87 -0
  264. package/src/lib/public-user-profile.ts +187 -0
  265. package/src/lib/push-provider.ts +409 -0
  266. package/src/lib/push-token.ts +294 -0
  267. package/src/lib/query-engine.ts +768 -0
  268. package/src/lib/room-monitoring.ts +97 -0
  269. package/src/lib/room-runtime.ts +14 -0
  270. package/src/lib/route-parser.ts +434 -0
  271. package/src/lib/schema.ts +538 -0
  272. package/src/lib/schemas.ts +152 -0
  273. package/src/lib/service-key.ts +419 -0
  274. package/src/lib/sms-provider.ts +230 -0
  275. package/src/lib/startup-config.ts +99 -0
  276. package/src/lib/totp.ts +242 -0
  277. package/src/lib/uuid.ts +87 -0
  278. package/src/lib/validation.ts +205 -0
  279. package/src/lib/version.ts +2 -0
  280. package/src/lib/websocket-pending.ts +40 -0
  281. package/src/middleware/auth.ts +169 -0
  282. package/src/middleware/captcha-verify.ts +217 -0
  283. package/src/middleware/cors.ts +159 -0
  284. package/src/middleware/error-handler.ts +54 -0
  285. package/src/middleware/internal-guard.ts +26 -0
  286. package/src/middleware/logger.ts +126 -0
  287. package/src/middleware/rate-limit.ts +283 -0
  288. package/src/middleware/rules.ts +475 -0
  289. package/src/routes/admin-auth.ts +447 -0
  290. package/src/routes/admin.ts +3501 -0
  291. package/src/routes/analytics-api.ts +290 -0
  292. package/src/routes/auth.ts +4222 -0
  293. package/src/routes/backup.ts +1466 -0
  294. package/src/routes/config.ts +53 -0
  295. package/src/routes/d1.ts +109 -0
  296. package/src/routes/database-live.ts +281 -0
  297. package/src/routes/functions.ts +155 -0
  298. package/src/routes/health.ts +32 -0
  299. package/src/routes/kv.ts +167 -0
  300. package/src/routes/oauth.ts +1055 -0
  301. package/src/routes/push.ts +1465 -0
  302. package/src/routes/room.ts +639 -0
  303. package/src/routes/schema-endpoint.ts +76 -0
  304. package/src/routes/sql.ts +176 -0
  305. package/src/routes/storage.ts +1674 -0
  306. package/src/routes/tables.ts +699 -0
  307. package/src/routes/users.ts +21 -0
  308. package/src/routes/vectorize.ts +372 -0
  309. package/src/types.ts +99 -0
@@ -0,0 +1,255 @@
1
+ /**
2
+ * D1 lazy schema initializer.
3
+ *
4
+ * Mirrors postgres-schema-init.ts but for Cloudflare D1 (SQLite):
5
+ * 1. Creates _meta table if not exists
6
+ * 2. For each table: compute schema hash, compare with stored hash
7
+ * 3. If new: CREATE TABLE + indexes + FTS5
8
+ * 4. If changed: ADD COLUMN for new fields (non-destructive)
9
+ * 5. Stores schema hash in _meta
10
+ *
11
+ * Called once per Worker lifetime per namespace (cached in memory set).
12
+ */
13
+ import type { TableConfig, MigrationConfig } from '@edge-base/shared';
14
+ import {
15
+ META_TABLE_DDL,
16
+ generateTableDDL,
17
+ generateAddColumnDDL,
18
+ generateFTS5DDL,
19
+ generateFTS5Triggers,
20
+ generateIndexDDL,
21
+ buildEffectiveSchema,
22
+ computeSchemaHashSync,
23
+ } from './schema.js';
24
+ import type { SchemaField } from '@edge-base/shared';
25
+
26
+ // Track initialized namespaces to avoid redundant checks per Worker process.
27
+ const _initialized = new Set<string>();
28
+
29
+ /**
30
+ * Ensure D1 schema is up-to-date for a given namespace.
31
+ * Called once per Worker lifetime per namespace (cached in memory).
32
+ */
33
+ export async function ensureD1Schema(
34
+ db: D1Database,
35
+ namespace: string,
36
+ tables: Record<string, TableConfig>,
37
+ ): Promise<void> {
38
+ if (_initialized.has(namespace)) {
39
+ return;
40
+ }
41
+
42
+ await db.prepare(META_TABLE_DDL).run();
43
+ await db.prepare('PRAGMA foreign_keys = ON;').run();
44
+ for (const [tableName, config] of Object.entries(tables)) {
45
+ await initD1Table(db, tableName, config);
46
+ }
47
+ _initialized.add(namespace);
48
+ }
49
+
50
+ /**
51
+ * Initialize or update a single D1 table.
52
+ */
53
+ async function initD1Table(
54
+ db: D1Database,
55
+ tableName: string,
56
+ config: TableConfig,
57
+ ): Promise<void> {
58
+ const currentHash = computeSchemaHashSync(config);
59
+
60
+ // Check stored hash
61
+ const storedHash = await getD1Meta(db, `schemaHash:${tableName}`);
62
+
63
+ if (storedHash === currentHash) {
64
+ // No schema change — still check migrations
65
+ await ensureD1FTSAndIndexes(db, tableName, config);
66
+ await runD1Migrations(db, tableName, config);
67
+ return;
68
+ }
69
+
70
+ if (!storedHash) {
71
+ // First time — create table + indexes + FTS5
72
+ const ddls = generateTableDDL(tableName, config);
73
+ const stmts = ddls.map(ddl => db.prepare(ddl));
74
+ if (stmts.length > 0) {
75
+ await db.batch(stmts);
76
+ }
77
+
78
+ // Set initial migration version if migrations exist (skip running them —
79
+ // fresh table already has the latest schema)
80
+ if (config.migrations?.length) {
81
+ const maxVersion = Math.max(...config.migrations.map((m: MigrationConfig) => m.version));
82
+ await setD1Meta(db, `migration_version:${tableName}`, String(maxVersion));
83
+ }
84
+ } else {
85
+ // Schema changed — detect new columns and add them (non-destructive)
86
+ await handleD1SchemaUpdate(db, tableName, config);
87
+ // Re-apply FTS and indexes to pick up new field additions
88
+ await ensureD1FTSAndIndexes(db, tableName, config);
89
+ // Run pending migrations after schema update
90
+ await runD1Migrations(db, tableName, config);
91
+ }
92
+
93
+ // Store new hash
94
+ await setD1Meta(db, `schemaHash:${tableName}`, currentHash);
95
+ }
96
+
97
+ /**
98
+ * Non-destructive schema update: detect new columns and ADD COLUMN.
99
+ * Does NOT drop columns (data safety) — mirrors database-do.ts handleSchemaUpdate().
100
+ */
101
+ async function handleD1SchemaUpdate(
102
+ db: D1Database,
103
+ tableName: string,
104
+ config: TableConfig,
105
+ ): Promise<void> {
106
+ // Get existing columns from PRAGMA table_info
107
+ const colResult = await db.prepare(`PRAGMA table_info("${tableName.replace(/"/g, '""')}")`).all();
108
+ const existingCols = new Set(
109
+ (colResult.results ?? []).map((r: Record<string, unknown>) => r.name as string),
110
+ );
111
+
112
+ // Build effective schema with auto-fields
113
+ const effectiveSchema = buildEffectiveSchema(config.schema);
114
+
115
+ // Add missing columns
116
+ const stmts: D1PreparedStatement[] = [];
117
+ const indexDDLs: string[] = [];
118
+ for (const [colName, field] of Object.entries(effectiveSchema)) {
119
+ if (!existingCols.has(colName)) {
120
+ const columnField = normalizeD1AddColumnField(field);
121
+ const ddl = generateAddColumnDDL(tableName, colName, columnField);
122
+ stmts.push(db.prepare(ddl));
123
+ if (field.unique) {
124
+ indexDDLs.push(
125
+ `CREATE UNIQUE INDEX IF NOT EXISTS "${`idx_${tableName}_${colName}`.replace(/"/g, '""')}" ON "${tableName.replace(/"/g, '""')}"("${colName.replace(/"/g, '""')}");`,
126
+ );
127
+ }
128
+ }
129
+ }
130
+ if (stmts.length > 0) {
131
+ await db.batch(stmts);
132
+ }
133
+ if (indexDDLs.length > 0) {
134
+ await db.batch(indexDDLs.map((ddl) => db.prepare(ddl)));
135
+ }
136
+ }
137
+
138
+ function normalizeD1AddColumnField(field: SchemaField): SchemaField {
139
+ return {
140
+ ...field,
141
+ // SQLite/D1 cannot add UNIQUE columns inline via ALTER TABLE.
142
+ unique: false,
143
+ // Existing rows make NOT NULL additions fail unless a default is supplied.
144
+ required: field.required && field.default !== undefined,
145
+ // Primary keys are also unsupported on ALTER TABLE ADD COLUMN.
146
+ primaryKey: false,
147
+ };
148
+ }
149
+
150
+ /**
151
+ * Ensure FTS5 and indexes are up-to-date after schema changes.
152
+ */
153
+ async function ensureD1FTSAndIndexes(
154
+ db: D1Database,
155
+ tableName: string,
156
+ config: TableConfig,
157
+ ): Promise<void> {
158
+ const stmts: D1PreparedStatement[] = [];
159
+ let needsFtsRebuild = false;
160
+
161
+ // Re-apply indexes (CREATE IF NOT EXISTS is idempotent)
162
+ if (config.indexes?.length) {
163
+ const indexDDLs = generateIndexDDL(tableName, config.indexes);
164
+ for (const ddl of indexDDLs) {
165
+ stmts.push(db.prepare(ddl));
166
+ }
167
+ }
168
+
169
+ // Re-apply FTS5
170
+ if (config.fts?.length) {
171
+ const ftsArtifacts = [`${tableName}_fts`, `${tableName}_ai`, `${tableName}_ad`, `${tableName}_au`];
172
+ const placeholders = ftsArtifacts.map(() => '?').join(', ');
173
+ const artifactRows = await db
174
+ .prepare(`SELECT name FROM sqlite_master WHERE name IN (${placeholders})`)
175
+ .bind(...ftsArtifacts)
176
+ .all();
177
+ const existingArtifacts = new Set(
178
+ (artifactRows.results ?? []).map((row: Record<string, unknown>) => row.name as string),
179
+ );
180
+ needsFtsRebuild = ftsArtifacts.some((name) => !existingArtifacts.has(name));
181
+
182
+ stmts.push(db.prepare(generateFTS5DDL(tableName, config.fts)));
183
+ const triggerDDLs = generateFTS5Triggers(tableName, config.fts);
184
+ for (const ddl of triggerDDLs) {
185
+ stmts.push(db.prepare(ddl));
186
+ }
187
+ }
188
+
189
+ if (stmts.length > 0) {
190
+ await db.batch(stmts);
191
+ }
192
+
193
+ if (config.fts?.length && needsFtsRebuild) {
194
+ const ftsTableName = `${tableName}_fts`.replace(/"/g, '""');
195
+ await db.prepare(`INSERT INTO "${ftsTableName}"("${ftsTableName}") VALUES ('rebuild')`).run();
196
+ }
197
+ }
198
+
199
+ // ─── Migration Engine ───
200
+
201
+ /**
202
+ * Run pending migrations for a D1 table.
203
+ * Mirrors database-do.ts runMigrations() — uses SQLite `up` field.
204
+ */
205
+ async function runD1Migrations(
206
+ db: D1Database,
207
+ tableName: string,
208
+ config: TableConfig,
209
+ ): Promise<void> {
210
+ if (!config.migrations?.length) return;
211
+
212
+ const versionKey = `migration_version:${tableName}`;
213
+ const currentVersionStr = await getD1Meta(db, versionKey);
214
+ const currentVersion = parseInt(currentVersionStr || '1', 10);
215
+
216
+ const pending = config.migrations
217
+ .filter((m: MigrationConfig) => m.version > currentVersion)
218
+ .sort((a: MigrationConfig, b: MigrationConfig) => a.version - b.version);
219
+
220
+ for (const migration of pending) {
221
+ try {
222
+ // D1 uses SQLite — use `up` field (not upPg)
223
+ await db.prepare(migration.up).run();
224
+ await setD1Meta(db, versionKey, String(migration.version));
225
+ } catch (err) {
226
+ console.error(`D1 Migration v${migration.version} failed for ${tableName}:`, err);
227
+ throw new Error(`D1 Migration v${migration.version} failed: ${(err as Error).message}`);
228
+ }
229
+ }
230
+ }
231
+
232
+ // ─── _meta Helpers ───
233
+
234
+ async function getD1Meta(
235
+ db: D1Database,
236
+ key: string,
237
+ ): Promise<string | null> {
238
+ const row = await db.prepare('SELECT "value" FROM "_meta" WHERE "key" = ?').bind(key).first<{ value: string }>();
239
+ return row?.value ?? null;
240
+ }
241
+
242
+ async function setD1Meta(
243
+ db: D1Database,
244
+ key: string,
245
+ value: string,
246
+ ): Promise<void> {
247
+ await db.prepare(
248
+ 'INSERT INTO "_meta" ("key", "value") VALUES (?, ?) ON CONFLICT ("key") DO UPDATE SET "value" = ?',
249
+ ).bind(key, value, value).run();
250
+ }
251
+
252
+ /** Reset initialized state (for testing). */
253
+ export function _resetD1SchemaCache(): void {
254
+ _initialized.clear();
255
+ }
@@ -0,0 +1,33 @@
1
+ export interface D1SqlResult {
2
+ rows: Record<string, unknown>[];
3
+ rowCount: number;
4
+ }
5
+
6
+ function isRowsQuery(sql: string): boolean {
7
+ const normalized = sql.trim().replace(/^\(+/, '').toUpperCase();
8
+ return /^(SELECT|WITH|PRAGMA|EXPLAIN|VALUES)\b/.test(normalized) || /\bRETURNING\b/i.test(sql);
9
+ }
10
+
11
+ export async function executeD1Sql(
12
+ db: D1Database,
13
+ sql: string,
14
+ params: unknown[] = [],
15
+ ): Promise<D1SqlResult> {
16
+ const stmt = db.prepare(sql);
17
+ const bound = params.length > 0 ? stmt.bind(...params) : stmt;
18
+
19
+ if (isRowsQuery(sql)) {
20
+ const result = await bound.all();
21
+ const rows = (result.results ?? []) as Record<string, unknown>[];
22
+ return {
23
+ rows,
24
+ rowCount: rows.length,
25
+ };
26
+ }
27
+
28
+ const result = await bound.run();
29
+ return {
30
+ rows: [],
31
+ rowCount: result.meta?.changes ?? 0,
32
+ };
33
+ }
@@ -0,0 +1,24 @@
1
+ import type { EdgeBaseConfig } from '@edge-base/shared';
2
+
3
+ export const DEFAULT_DB_LIVE_AUTH_TIMEOUT_MS = 5000;
4
+ export const DEFAULT_DB_LIVE_BATCH_THRESHOLD = 10;
5
+
6
+ function normalizePositiveInteger(value: unknown, fallback: number): number {
7
+ if (typeof value !== 'number' || !Number.isFinite(value)) return fallback;
8
+ const normalized = Math.floor(value);
9
+ return normalized > 0 ? normalized : fallback;
10
+ }
11
+
12
+ export function resolveDbLiveAuthTimeoutMs(config?: EdgeBaseConfig): number {
13
+ return normalizePositiveInteger(
14
+ config?.databaseLive?.authTimeoutMs,
15
+ DEFAULT_DB_LIVE_AUTH_TIMEOUT_MS,
16
+ );
17
+ }
18
+
19
+ export function resolveDbLiveBatchThreshold(config?: EdgeBaseConfig): number {
20
+ return normalizePositiveInteger(
21
+ config?.databaseLive?.batchThreshold,
22
+ DEFAULT_DB_LIVE_BATCH_THRESHOLD,
23
+ );
24
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Shared database live event emission helpers.
3
+ *
4
+ * Used by d1-handler.ts and postgres-handler.ts for fire-and-forget
5
+ * event delivery to DatabaseLiveDO after successful CUD operations.
6
+ *
7
+ * database-do.ts keeps its own internal version (uses DO env).
8
+ */
9
+ import type { Env } from '../types.js';
10
+
11
+ export const DATABASE_LIVE_HUB_DO_NAME = 'database-live:hub';
12
+
13
+ export function buildDbLiveChannel(
14
+ namespace: string,
15
+ table: string,
16
+ instanceId?: string,
17
+ docId?: string,
18
+ ): string {
19
+ const base = instanceId
20
+ ? `dblive:${namespace}:${instanceId}:${table}`
21
+ : `dblive:${namespace}:${table}`;
22
+ return docId ? `${base}:${docId}` : base;
23
+ }
24
+
25
+ export function isDbLiveChannel(channel: string): boolean {
26
+ if (!channel.startsWith('dblive:')) return false;
27
+ if (channel.startsWith('dblive:presence:') || channel.startsWith('dblive:broadcast:')) {
28
+ return false;
29
+ }
30
+ const parts = channel.split(':');
31
+ if (parts.length < 3 || parts.length > 5) return false;
32
+ return parts.every(part => part.length > 0);
33
+ }
34
+
35
+ /**
36
+ * Emit a single CUD event to DatabaseLiveDO.
37
+ * Mirrors database-do.ts emitDbLiveEvent() — fire-and-forget.
38
+ */
39
+ export function emitDbLiveEvent(
40
+ env: Env,
41
+ namespace: string,
42
+ table: string,
43
+ type: 'added' | 'modified' | 'removed',
44
+ docId: string,
45
+ data: Record<string, unknown> | null,
46
+ instanceId?: string,
47
+ ): Promise<void> {
48
+ const tableChannel = buildDbLiveChannel(namespace, table, instanceId);
49
+ const event = { type, table, docId, data, timestamp: new Date().toISOString() };
50
+ const deliveries = [
51
+ sendToDatabaseLiveDO(env, { ...event, channel: tableChannel }),
52
+ ];
53
+
54
+ // Document channel: dblive:{namespace}:{table}:{docId}
55
+ if (docId !== '_bulk') {
56
+ const docChannel = buildDbLiveChannel(namespace, table, instanceId, docId);
57
+ deliveries.push(sendToDatabaseLiveDO(env, { ...event, channel: docChannel }));
58
+ }
59
+
60
+ return Promise.all(deliveries).then(() => undefined);
61
+ }
62
+
63
+ /**
64
+ * Emit batch CUD events as a single batch_changes message.
65
+ * Mirrors database-do.ts emitDbLiveBatchEvent().
66
+ */
67
+ export function emitDbLiveBatchEvent(
68
+ env: Env,
69
+ namespace: string,
70
+ table: string,
71
+ changes: Array<{ type: 'added' | 'modified' | 'removed'; docId: string; data: Record<string, unknown> | null }>,
72
+ instanceId?: string,
73
+ ): Promise<void> {
74
+ const tableChannel = buildDbLiveChannel(namespace, table, instanceId);
75
+ const event = {
76
+ type: 'batch_changes' as const,
77
+ channel: tableChannel,
78
+ table,
79
+ changes: changes.map(ch => ({
80
+ type: ch.type,
81
+ docId: ch.docId,
82
+ data: ch.data,
83
+ timestamp: new Date().toISOString(),
84
+ })),
85
+ total: changes.length,
86
+ };
87
+ return sendToDatabaseLiveDO(env, event, '/internal/batch-event');
88
+ }
89
+
90
+ /**
91
+ * Fire-and-forget: send event to DatabaseLiveDO via stub.fetch().
92
+ * Never blocks CRUD response — errors are silently ignored.
93
+ */
94
+ export function sendToDatabaseLiveDO(
95
+ env: Env,
96
+ event: Record<string, unknown>,
97
+ path = '/internal/event',
98
+ ): Promise<void> {
99
+ try {
100
+ const doId = env.DATABASE_LIVE.idFromName(DATABASE_LIVE_HUB_DO_NAME);
101
+ const stub = env.DATABASE_LIVE.get(doId);
102
+ return stub.fetch(`http://internal${path}`, {
103
+ method: 'POST',
104
+ headers: { 'Content-Type': 'application/json' },
105
+ body: JSON.stringify(event),
106
+ }).then(() => undefined).catch(() => undefined);
107
+ } catch {
108
+ // Ignore — database live delivery should not block database operations
109
+ return Promise.resolve();
110
+ }
111
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * db.sql tagged template literal → positional `?` prepared statement.
3
+ * M1.3 벤치마크 결과: positional `?` 채택 (strings.join('?') 패턴).
4
+ */
5
+
6
+ /**
7
+ * A raw SQL fragment that bypasses parameterization.
8
+ * Use with extreme caution — only for whitelisted table/column names.
9
+ */
10
+ export class RawSql {
11
+ constructor(public readonly value: string) {}
12
+ }
13
+
14
+ /**
15
+ * Mark a value as raw SQL (no parameterization).
16
+ * Warning: potential SQL injection if used with unvalidated input.
17
+ */
18
+ export function raw(value: string): RawSql {
19
+ return new RawSql(value);
20
+ }
21
+
22
+ /**
23
+ * Build a parameterized query from a tagged template literal.
24
+ * Returns { query, params } for use with `this.ctx.storage.sql.exec()`.
25
+ */
26
+ export function buildSqlQuery(
27
+ strings: TemplateStringsArray,
28
+ ...values: unknown[]
29
+ ): { query: string; params: unknown[] } {
30
+ const params: unknown[] = [];
31
+ let query = strings[0];
32
+
33
+ for (let i = 0; i < values.length; i++) {
34
+ const val = values[i];
35
+ if (val instanceof RawSql) {
36
+ // Raw SQL fragment — inject directly (no parameterization)
37
+ query += val.value + strings[i + 1];
38
+ } else {
39
+ query += '?' + strings[i + 1];
40
+ params.push(val);
41
+ }
42
+ }
43
+
44
+ return { query, params };
45
+ }
46
+
47
+ /**
48
+ * Create a `db.sql` tagged template function bound to a DO's SQLite storage.
49
+ */
50
+ export function createSqlTaggedTemplate(
51
+ sqlExec: (query: string, ...params: unknown[]) => SqlStorageCursor,
52
+ ) {
53
+ return function sql(
54
+ strings: TemplateStringsArray,
55
+ ...values: unknown[]
56
+ ): Record<string, unknown>[] {
57
+ const { query, params } = buildSqlQuery(strings, ...values);
58
+ return [...sqlExec(query, ...params)];
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Minimal cursor interface for DO SQLite.
64
+ * The actual type is provided by @cloudflare/workers-types.
65
+ */
66
+ type SqlStorageCursor = Iterable<Record<string, unknown>>;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Durable Object fetch with single retry on transient failure (DO reset).
3
+ *
4
+ * Body must be pre-read as a string — ReadableStream cannot be replayed.
5
+ * Headers are cloned on retry to avoid mutation issues.
6
+ *
7
+ * When `safeToRetry` is false (default), non-idempotent requests are NOT
8
+ * retried to avoid duplicate writes, side-effect duplication (hooks,
9
+ * triggers, database-live events), and non-idempotent ops like `$op: increment`.
10
+ */
11
+ export async function fetchDOWithRetry(
12
+ stub: { fetch: (input: RequestInfo, init?: RequestInit) => Promise<Response> },
13
+ url: string,
14
+ init: { method: string; headers: HeadersInit; body?: string | null },
15
+ options?: { safeToRetry?: boolean },
16
+ ): Promise<Response> {
17
+ try {
18
+ return await stub.fetch(url, {
19
+ method: init.method,
20
+ headers: init.headers,
21
+ body: init.body ?? undefined,
22
+ });
23
+ } catch (err) {
24
+ // Only retry if the caller explicitly marked the request as safe to retry.
25
+ // Non-idempotent writes (POST, PATCH with increment, DELETE with hooks)
26
+ // must NOT be silently retried — let the error propagate to the client.
27
+ if (!options?.safeToRetry) throw err;
28
+
29
+ // DO may have reset — retry once with cloned headers
30
+ return stub.fetch(url, {
31
+ method: init.method,
32
+ headers: new Headers(init.headers),
33
+ body: init.body ?? undefined,
34
+ });
35
+ }
36
+ }