emdash 0.4.0 → 0.6.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 (212) hide show
  1. package/dist/{adapters-C2BzVy0p.d.mts → adapters-Di31kZ28.d.mts} +16 -1
  2. package/dist/adapters-Di31kZ28.d.mts.map +1 -0
  3. package/dist/{apply-Cma_PiF6.mjs → apply-B4MsLM-w.mjs} +27 -12
  4. package/dist/apply-B4MsLM-w.mjs.map +1 -0
  5. package/dist/astro/index.d.mts +6 -6
  6. package/dist/astro/index.d.mts.map +1 -1
  7. package/dist/astro/index.mjs +208 -34
  8. package/dist/astro/index.mjs.map +1 -1
  9. package/dist/astro/middleware/auth.d.mts +5 -5
  10. package/dist/astro/middleware/auth.d.mts.map +1 -1
  11. package/dist/astro/middleware/auth.mjs +34 -9
  12. package/dist/astro/middleware/auth.mjs.map +1 -1
  13. package/dist/astro/middleware/redirect.mjs +1 -1
  14. package/dist/astro/middleware/request-context.d.mts.map +1 -1
  15. package/dist/astro/middleware/request-context.mjs +5 -3
  16. package/dist/astro/middleware/request-context.mjs.map +1 -1
  17. package/dist/astro/middleware/setup.mjs +1 -1
  18. package/dist/astro/middleware.d.mts.map +1 -1
  19. package/dist/astro/middleware.mjs +460 -180
  20. package/dist/astro/middleware.mjs.map +1 -1
  21. package/dist/astro/types.d.mts +8 -8
  22. package/dist/{byline-WuOq9MFJ.mjs → byline-C4OVd8b3.mjs} +3 -19
  23. package/dist/byline-C4OVd8b3.mjs.map +1 -0
  24. package/dist/{bylines-C_Wsnz4L.mjs → bylines-hPTW79hw.mjs} +20 -33
  25. package/dist/bylines-hPTW79hw.mjs.map +1 -0
  26. package/dist/{cache-E3Dts-yT.mjs → cache-BkKBuIvS.mjs} +1 -1
  27. package/dist/{cache-E3Dts-yT.mjs.map → cache-BkKBuIvS.mjs.map} +1 -1
  28. package/dist/chunks-HGz06Soa.mjs +19 -0
  29. package/dist/chunks-HGz06Soa.mjs.map +1 -0
  30. package/dist/cli/index.mjs +9 -8
  31. package/dist/cli/index.mjs.map +1 -1
  32. package/dist/client/cf-access.d.mts +1 -1
  33. package/dist/client/index.d.mts +1 -1
  34. package/dist/client/index.mjs +1 -1
  35. package/dist/{config-DkxPrM9l.mjs → config-BXwuX8Bx.mjs} +1 -1
  36. package/dist/{config-DkxPrM9l.mjs.map → config-BXwuX8Bx.mjs.map} +1 -1
  37. package/dist/{connection-B4zVnQIa.mjs → connection-2igzM-AT.mjs} +19 -2
  38. package/dist/connection-2igzM-AT.mjs.map +1 -0
  39. package/dist/database/instrumentation.d.mts +45 -0
  40. package/dist/database/instrumentation.d.mts.map +1 -0
  41. package/dist/database/instrumentation.mjs +61 -0
  42. package/dist/database/instrumentation.mjs.map +1 -0
  43. package/dist/db/index.d.mts +3 -3
  44. package/dist/db/index.mjs.map +1 -1
  45. package/dist/db/libsql.d.mts +1 -1
  46. package/dist/db/postgres.d.mts +1 -1
  47. package/dist/db/sqlite.d.mts +1 -1
  48. package/dist/db-errors-D0UT85nC.mjs +41 -0
  49. package/dist/db-errors-D0UT85nC.mjs.map +1 -0
  50. package/dist/{default-PUx9RK6u.mjs → default-CME5YdZ3.mjs} +1 -1
  51. package/dist/{default-PUx9RK6u.mjs.map → default-CME5YdZ3.mjs.map} +1 -1
  52. package/dist/{error-HBeQbVhV.mjs → error-CiYn9yDu.mjs} +1 -1
  53. package/dist/{error-HBeQbVhV.mjs.map → error-CiYn9yDu.mjs.map} +1 -1
  54. package/dist/{index-CRg3PWfZ.d.mts → index-BYv0mB9g.d.mts} +135 -19
  55. package/dist/index-BYv0mB9g.d.mts.map +1 -0
  56. package/dist/index.d.mts +11 -11
  57. package/dist/index.mjs +20 -18
  58. package/dist/{load-BhSSm-TS.mjs → load-CBcmDIot.mjs} +1 -1
  59. package/dist/{load-BhSSm-TS.mjs.map → load-CBcmDIot.mjs.map} +1 -1
  60. package/dist/{loader-BYzwzORf.mjs → loader-DeiBJEMe.mjs} +18 -12
  61. package/dist/loader-DeiBJEMe.mjs.map +1 -0
  62. package/dist/{manifest-schema-BsXINkQD.mjs → manifest-schema-V30qsMft.mjs} +1 -1
  63. package/dist/{manifest-schema-BsXINkQD.mjs.map → manifest-schema-V30qsMft.mjs.map} +1 -1
  64. package/dist/media/index.d.mts +1 -1
  65. package/dist/media/index.mjs +1 -1
  66. package/dist/media/local-runtime.d.mts +7 -7
  67. package/dist/{mode-CyPLdO3C.mjs → mode-CpNnGkPz.mjs} +1 -1
  68. package/dist/{mode-CyPLdO3C.mjs.map → mode-CpNnGkPz.mjs.map} +1 -1
  69. package/dist/page/index.d.mts +11 -2
  70. package/dist/page/index.d.mts.map +1 -1
  71. package/dist/page/index.mjs +23 -1
  72. package/dist/page/index.mjs.map +1 -1
  73. package/dist/{placeholder-DntBEQo7.mjs → placeholder-C-fk5hYI.mjs} +1 -1
  74. package/dist/{placeholder-DntBEQo7.mjs.map → placeholder-C-fk5hYI.mjs.map} +1 -1
  75. package/dist/{placeholder-BBCtpTES.d.mts → placeholder-tzpqGWII.d.mts} +1 -1
  76. package/dist/{placeholder-BBCtpTES.d.mts.map → placeholder-tzpqGWII.d.mts.map} +1 -1
  77. package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
  78. package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
  79. package/dist/{query-B6Vu0d2i.mjs → query-Bk_3vKvU.mjs} +78 -11
  80. package/dist/query-Bk_3vKvU.mjs.map +1 -0
  81. package/dist/{registry-BgnP3ysR.mjs → registry-Ci3WxVAr.mjs} +133 -97
  82. package/dist/registry-Ci3WxVAr.mjs.map +1 -0
  83. package/dist/request-cache-DiR961CV.mjs +79 -0
  84. package/dist/request-cache-DiR961CV.mjs.map +1 -0
  85. package/dist/request-context.d.mts +19 -16
  86. package/dist/request-context.d.mts.map +1 -1
  87. package/dist/request-context.mjs.map +1 -1
  88. package/dist/{runner-DYv3rX8P.d.mts → runner-Fl2NcUUz.d.mts} +2 -2
  89. package/dist/{runner-DYv3rX8P.d.mts.map → runner-Fl2NcUUz.d.mts.map} +1 -1
  90. package/dist/runtime.d.mts +6 -6
  91. package/dist/runtime.mjs +1 -1
  92. package/dist/{search-B5p9D36n.mjs → search-DI4bM2w9.mjs} +110 -209
  93. package/dist/search-DI4bM2w9.mjs.map +1 -0
  94. package/dist/seed/index.d.mts +2 -2
  95. package/dist/seed/index.mjs +8 -7
  96. package/dist/seo/index.d.mts +1 -1
  97. package/dist/storage/local.d.mts +1 -1
  98. package/dist/storage/local.mjs +1 -1
  99. package/dist/storage/s3.d.mts +1 -1
  100. package/dist/storage/s3.mjs +1 -1
  101. package/dist/taxonomies-DbrKzDju.mjs +308 -0
  102. package/dist/taxonomies-DbrKzDju.mjs.map +1 -0
  103. package/dist/{tokens-DKHiCYCB.mjs → tokens-BFPFx3CA.mjs} +1 -1
  104. package/dist/{tokens-DKHiCYCB.mjs.map → tokens-BFPFx3CA.mjs.map} +1 -1
  105. package/dist/{transport-BtcQ-Z7T.mjs → transport-BykRfpyy.mjs} +1 -1
  106. package/dist/{transport-BtcQ-Z7T.mjs.map → transport-BykRfpyy.mjs.map} +1 -1
  107. package/dist/{transport-CKQA_G44.d.mts → transport-H4Iwx7tC.d.mts} +1 -1
  108. package/dist/{transport-CKQA_G44.d.mts.map → transport-H4Iwx7tC.d.mts.map} +1 -1
  109. package/dist/{types-BmkQR1En.d.mts → types-6CUZRrZP.d.mts} +1 -1
  110. package/dist/{types-BmkQR1En.d.mts.map → types-6CUZRrZP.d.mts.map} +1 -1
  111. package/dist/{types-B6BzlZxx.d.mts → types-8xrvl_68.d.mts} +1 -1
  112. package/dist/{types-B6BzlZxx.d.mts.map → types-8xrvl_68.d.mts.map} +1 -1
  113. package/dist/{types-Dz9_WMS6.mjs → types-BH2L167P.mjs} +1 -1
  114. package/dist/{types-Dz9_WMS6.mjs.map → types-BH2L167P.mjs.map} +1 -1
  115. package/dist/{types-DNZpaCBk.d.mts → types-CFWjXmus.d.mts} +1 -1
  116. package/dist/{types-DNZpaCBk.d.mts.map → types-CFWjXmus.d.mts.map} +1 -1
  117. package/dist/{types-gLYVCXCQ.d.mts → types-CnZYHyLW.d.mts} +55 -5
  118. package/dist/types-CnZYHyLW.d.mts.map +1 -0
  119. package/dist/{types-xxCWI3j0.mjs → types-DDS4MxsT.mjs} +11 -3
  120. package/dist/types-DDS4MxsT.mjs.map +1 -0
  121. package/dist/{types-BYWYxLcp.d.mts → types-DgrIP0tF.d.mts} +9 -2
  122. package/dist/types-DgrIP0tF.d.mts.map +1 -0
  123. package/dist/{validate-CcNRWH6I.d.mts → validate-CaLH1Ia2.d.mts} +5 -52
  124. package/dist/validate-CaLH1Ia2.d.mts.map +1 -0
  125. package/dist/{validate-DuZDIxfy.mjs → validate-CqsNItbt.mjs} +2 -2
  126. package/dist/{validate-DuZDIxfy.mjs.map → validate-CqsNItbt.mjs.map} +1 -1
  127. package/dist/version-Uaf2ynPX.mjs +7 -0
  128. package/dist/{version-DlTDRdpv.mjs.map → version-Uaf2ynPX.mjs.map} +1 -1
  129. package/package.json +10 -5
  130. package/src/after.ts +62 -0
  131. package/src/api/handlers/oauth-authorization.ts +2 -32
  132. package/src/api/handlers/oauth-clients.ts +40 -4
  133. package/src/api/handlers/taxonomies.ts +13 -0
  134. package/src/api/oauth/redirect-uri.ts +34 -0
  135. package/src/api/openapi/document.ts +126 -118
  136. package/src/api/schemas/auth.ts +7 -0
  137. package/src/api/schemas/media.ts +26 -15
  138. package/src/api/schemas/schema.ts +1 -0
  139. package/src/astro/integration/font-provider.ts +176 -0
  140. package/src/astro/integration/index.ts +42 -0
  141. package/src/astro/integration/routes.ts +17 -1
  142. package/src/astro/integration/runtime.ts +63 -0
  143. package/src/astro/integration/virtual-modules.ts +41 -39
  144. package/src/astro/integration/vite-config.ts +16 -5
  145. package/src/astro/middleware/auth.ts +39 -6
  146. package/src/astro/middleware/request-context.ts +15 -3
  147. package/src/astro/middleware.ts +340 -263
  148. package/src/astro/routes/admin.astro +10 -5
  149. package/src/astro/routes/api/auth/invite/register-options.ts +78 -0
  150. package/src/astro/routes/api/auth/passkey/verify.ts +5 -1
  151. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +5 -0
  152. package/src/astro/routes/api/import/wordpress/execute.ts +1 -1
  153. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +1 -1
  154. package/src/astro/routes/api/media/upload-url.ts +10 -2
  155. package/src/astro/routes/api/media.ts +10 -7
  156. package/src/astro/routes/api/oauth/register.ts +178 -0
  157. package/src/astro/routes/api/oauth/token.ts +15 -0
  158. package/src/astro/routes/api/openapi.json.ts +15 -5
  159. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +2 -0
  160. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +1 -0
  161. package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +1 -0
  162. package/src/astro/routes/api/search/index.ts +5 -0
  163. package/src/astro/routes/api/search/suggest.ts +3 -0
  164. package/src/astro/routes/api/taxonomies/index.ts +1 -0
  165. package/src/astro/routes/api/well-known/oauth-authorization-server.ts +6 -4
  166. package/src/bylines/index.ts +22 -45
  167. package/src/components/EmDashHead.astro +23 -7
  168. package/src/components/Table.astro +73 -41
  169. package/src/components/index.ts +2 -12
  170. package/src/components/marks.ts +20 -0
  171. package/src/database/connection.ts +23 -1
  172. package/src/database/instrumentation.ts +98 -0
  173. package/src/db/adapters.ts +15 -0
  174. package/src/emdash-runtime.ts +309 -91
  175. package/src/index.ts +6 -0
  176. package/src/loader.ts +19 -24
  177. package/src/menus/index.ts +6 -3
  178. package/src/page/index.ts +1 -1
  179. package/src/page/seo-contributions.ts +36 -0
  180. package/src/plugins/context.ts +1 -0
  181. package/src/plugins/email-console.ts +9 -2
  182. package/src/plugins/types.ts +8 -0
  183. package/src/query.ts +104 -7
  184. package/src/request-cache.ts +106 -0
  185. package/src/request-context.ts +19 -0
  186. package/src/schema/query.ts +5 -2
  187. package/src/schema/registry.ts +243 -166
  188. package/src/schema/types.ts +13 -2
  189. package/src/schema/zod-generator.ts +4 -0
  190. package/src/search/fts-manager.ts +19 -5
  191. package/src/search/query.ts +4 -3
  192. package/src/seed/apply.ts +15 -1
  193. package/src/settings/index.ts +24 -5
  194. package/src/taxonomies/index.ts +324 -124
  195. package/src/utils/db-errors.ts +46 -0
  196. package/src/virtual-modules.d.ts +31 -10
  197. package/src/widgets/index.ts +54 -25
  198. package/dist/adapters-C2BzVy0p.d.mts.map +0 -1
  199. package/dist/apply-Cma_PiF6.mjs.map +0 -1
  200. package/dist/byline-WuOq9MFJ.mjs.map +0 -1
  201. package/dist/bylines-C_Wsnz4L.mjs.map +0 -1
  202. package/dist/connection-B4zVnQIa.mjs.map +0 -1
  203. package/dist/index-CRg3PWfZ.d.mts.map +0 -1
  204. package/dist/loader-BYzwzORf.mjs.map +0 -1
  205. package/dist/query-B6Vu0d2i.mjs.map +0 -1
  206. package/dist/registry-BgnP3ysR.mjs.map +0 -1
  207. package/dist/search-B5p9D36n.mjs.map +0 -1
  208. package/dist/types-BYWYxLcp.d.mts.map +0 -1
  209. package/dist/types-gLYVCXCQ.d.mts.map +0 -1
  210. package/dist/types-xxCWI3j0.mjs.map +0 -1
  211. package/dist/validate-CcNRWH6I.d.mts.map +0 -1
  212. package/dist/version-DlTDRdpv.mjs +0 -7
@@ -1,4 +1,4 @@
1
- import { t as Interceptor } from "../transport-CKQA_G44.mjs";
1
+ import { t as Interceptor } from "../transport-H4Iwx7tC.mjs";
2
2
 
3
3
  //#region src/client/cf-access.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { a as tokenInterceptor, i as devBypassInterceptor, n as createTransport, r as csrfInterceptor, t as Interceptor } from "../transport-CKQA_G44.mjs";
1
+ import { a as tokenInterceptor, i as devBypassInterceptor, n as createTransport, r as csrfInterceptor, t as Interceptor } from "../transport-H4Iwx7tC.mjs";
2
2
 
3
3
  //#region src/client/portable-text.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { a as tokenInterceptor, c as markdownToPortableText, i as refreshInterceptor, l as portableTextToMarkdown, n as csrfInterceptor, o as convertDataForRead, r as devBypassInterceptor, s as convertDataForWrite, t as createTransport } from "../transport-BtcQ-Z7T.mjs";
1
+ import { a as tokenInterceptor, c as markdownToPortableText, i as refreshInterceptor, l as portableTextToMarkdown, n as csrfInterceptor, o as convertDataForRead, r as devBypassInterceptor, s as convertDataForWrite, t as createTransport } from "../transport-BykRfpyy.mjs";
2
2
  import mime from "mime/lite";
3
3
 
4
4
  //#region src/client/index.ts
@@ -44,4 +44,4 @@ function getFallbackChain(locale) {
44
44
 
45
45
  //#endregion
46
46
  export { setI18nConfig as i, getI18nConfig as n, isI18nEnabled as r, getFallbackChain as t };
47
- //# sourceMappingURL=config-DkxPrM9l.mjs.map
47
+ //# sourceMappingURL=config-BXwuX8Bx.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"config-DkxPrM9l.mjs","names":[],"sources":["../src/i18n/config.ts"],"sourcesContent":["/**\n * EmDash i18n Configuration\n *\n * Reads locale configuration from the virtual module (sourced from Astro config).\n * Initialized during runtime startup, then available via getI18nConfig().\n */\n\nexport interface I18nConfig {\n\tdefaultLocale: string;\n\tlocales: string[];\n\tfallback?: Record<string, string>;\n\tprefixDefaultLocale?: boolean;\n}\n\nlet _config: I18nConfig | null | undefined;\n\n/**\n * Initialize i18n config from virtual module data.\n * Called during runtime initialization.\n */\nexport function setI18nConfig(config: I18nConfig | null): void {\n\t_config = config;\n}\n\n/**\n * Get the current i18n config.\n * Returns null if i18n is not configured.\n */\nexport function getI18nConfig(): I18nConfig | null {\n\treturn _config ?? null;\n}\n\n/**\n * Check if i18n is enabled.\n * Returns true when multiple locales are configured.\n */\nexport function isI18nEnabled(): boolean {\n\treturn _config != null && _config.locales.length > 1;\n}\n\n/**\n * Resolve fallback locale chain for a given locale.\n * Returns array of locales to try, from most preferred to least.\n * Always ends with defaultLocale.\n */\nexport function getFallbackChain(locale: string): string[] {\n\tif (!_config) return [locale];\n\n\tconst chain: string[] = [locale];\n\tlet current = locale;\n\tconst visited = new Set<string>([locale]);\n\n\twhile (_config.fallback?.[current]) {\n\t\t// eslint-disable-next-line typescript-eslint(no-unnecessary-type-assertion) -- noUncheckedIndexedAccess\n\t\tconst next = _config.fallback[current]!;\n\t\tif (visited.has(next)) break; // prevent cycles\n\t\tchain.push(next);\n\t\tvisited.add(next);\n\t\tcurrent = next;\n\t}\n\n\t// Always end with defaultLocale if not already in chain\n\tif (!visited.has(_config.defaultLocale)) {\n\t\tchain.push(_config.defaultLocale);\n\t}\n\n\treturn chain;\n}\n"],"mappings":";AAcA,IAAI;;;;;AAMJ,SAAgB,cAAc,QAAiC;AAC9D,WAAU;;;;;;AAOX,SAAgB,gBAAmC;AAClD,QAAO,WAAW;;;;;;AAOnB,SAAgB,gBAAyB;AACxC,QAAO,WAAW,QAAQ,QAAQ,QAAQ,SAAS;;;;;;;AAQpD,SAAgB,iBAAiB,QAA0B;AAC1D,KAAI,CAAC,QAAS,QAAO,CAAC,OAAO;CAE7B,MAAM,QAAkB,CAAC,OAAO;CAChC,IAAI,UAAU;CACd,MAAM,UAAU,IAAI,IAAY,CAAC,OAAO,CAAC;AAEzC,QAAO,QAAQ,WAAW,UAAU;EAEnC,MAAM,OAAO,QAAQ,SAAS;AAC9B,MAAI,QAAQ,IAAI,KAAK,CAAE;AACvB,QAAM,KAAK,KAAK;AAChB,UAAQ,IAAI,KAAK;AACjB,YAAU;;AAIX,KAAI,CAAC,QAAQ,IAAI,QAAQ,cAAc,CACtC,OAAM,KAAK,QAAQ,cAAc;AAGlC,QAAO"}
1
+ {"version":3,"file":"config-BXwuX8Bx.mjs","names":[],"sources":["../src/i18n/config.ts"],"sourcesContent":["/**\n * EmDash i18n Configuration\n *\n * Reads locale configuration from the virtual module (sourced from Astro config).\n * Initialized during runtime startup, then available via getI18nConfig().\n */\n\nexport interface I18nConfig {\n\tdefaultLocale: string;\n\tlocales: string[];\n\tfallback?: Record<string, string>;\n\tprefixDefaultLocale?: boolean;\n}\n\nlet _config: I18nConfig | null | undefined;\n\n/**\n * Initialize i18n config from virtual module data.\n * Called during runtime initialization.\n */\nexport function setI18nConfig(config: I18nConfig | null): void {\n\t_config = config;\n}\n\n/**\n * Get the current i18n config.\n * Returns null if i18n is not configured.\n */\nexport function getI18nConfig(): I18nConfig | null {\n\treturn _config ?? null;\n}\n\n/**\n * Check if i18n is enabled.\n * Returns true when multiple locales are configured.\n */\nexport function isI18nEnabled(): boolean {\n\treturn _config != null && _config.locales.length > 1;\n}\n\n/**\n * Resolve fallback locale chain for a given locale.\n * Returns array of locales to try, from most preferred to least.\n * Always ends with defaultLocale.\n */\nexport function getFallbackChain(locale: string): string[] {\n\tif (!_config) return [locale];\n\n\tconst chain: string[] = [locale];\n\tlet current = locale;\n\tconst visited = new Set<string>([locale]);\n\n\twhile (_config.fallback?.[current]) {\n\t\t// eslint-disable-next-line typescript-eslint(no-unnecessary-type-assertion) -- noUncheckedIndexedAccess\n\t\tconst next = _config.fallback[current]!;\n\t\tif (visited.has(next)) break; // prevent cycles\n\t\tchain.push(next);\n\t\tvisited.add(next);\n\t\tcurrent = next;\n\t}\n\n\t// Always end with defaultLocale if not already in chain\n\tif (!visited.has(_config.defaultLocale)) {\n\t\tchain.push(_config.defaultLocale);\n\t}\n\n\treturn chain;\n}\n"],"mappings":";AAcA,IAAI;;;;;AAMJ,SAAgB,cAAc,QAAiC;AAC9D,WAAU;;;;;;AAOX,SAAgB,gBAAmC;AAClD,QAAO,WAAW;;;;;;AAOnB,SAAgB,gBAAyB;AACxC,QAAO,WAAW,QAAQ,QAAQ,QAAQ,SAAS;;;;;;;AAQpD,SAAgB,iBAAiB,QAA0B;AAC1D,KAAI,CAAC,QAAS,QAAO,CAAC,OAAO;CAE7B,MAAM,QAAkB,CAAC,OAAO;CAChC,IAAI,UAAU;CACd,MAAM,UAAU,IAAI,IAAY,CAAC,OAAO,CAAC;AAEzC,QAAO,QAAQ,WAAW,UAAU;EAEnC,MAAM,OAAO,QAAQ,SAAS;AAC9B,MAAI,QAAQ,IAAI,KAAK,CAAE;AACvB,QAAM,KAAK,KAAK;AAChB,UAAQ,IAAI,KAAK;AACjB,YAAU;;AAIX,KAAI,CAAC,QAAQ,IAAI,QAAQ,cAAc,CACtC,OAAM,KAAK,QAAQ,cAAc;AAGlC,QAAO"}
@@ -1,3 +1,4 @@
1
+ import { kyselyLogOption } from "./database/instrumentation.mjs";
1
2
  import BetterSqlite3 from "better-sqlite3";
2
3
  import { Kysely, SqliteDialect } from "kysely";
3
4
 
@@ -10,6 +11,17 @@ var EmDashDatabaseError = class extends Error {
10
11
  }
11
12
  };
12
13
  /**
14
+ * Returns a helpful, actionable message when better-sqlite3's native binary
15
+ * was compiled against a different Node.js version than the one running. This
16
+ * happens after upgrading Node without rebuilding native deps.
17
+ *
18
+ * Returns null if the error is not a NODE_MODULE_VERSION mismatch.
19
+ */
20
+ function formatNativeModuleVersionError(error) {
21
+ if (!(error instanceof Error ? error.message : String(error)).includes("NODE_MODULE_VERSION")) return null;
22
+ return "better-sqlite3's native binary was compiled against a different Node.js version. Rebuild it with `pnpm rebuild better-sqlite3` (or `npm rebuild better-sqlite3`), or reinstall dependencies with your current Node.js version.";
23
+ }
24
+ /**
13
25
  * Creates a Kysely database instance
14
26
  * Supports:
15
27
  * - file:./path/to/db.sqlite (local SQLite)
@@ -22,7 +34,10 @@ function createDatabase(config) {
22
34
  const sqlite = new BetterSqlite3(config.url === ":memory:" ? ":memory:" : config.url.replace("file:", ""));
23
35
  sqlite.pragma("journal_mode = WAL");
24
36
  sqlite.pragma("foreign_keys = ON");
25
- return new Kysely({ dialect: new SqliteDialect({ database: sqlite }) });
37
+ return new Kysely({
38
+ dialect: new SqliteDialect({ database: sqlite }),
39
+ log: kyselyLogOption()
40
+ });
26
41
  }
27
42
  if (config.url.startsWith("libsql:")) {
28
43
  if (!config.authToken) throw new EmDashDatabaseError("Auth token required for remote libSQL database");
@@ -31,10 +46,12 @@ function createDatabase(config) {
31
46
  throw new EmDashDatabaseError(`Unsupported database URL scheme: ${config.url}`);
32
47
  } catch (error) {
33
48
  if (error instanceof EmDashDatabaseError) throw error;
49
+ const nativeVersionHint = formatNativeModuleVersionError(error);
50
+ if (nativeVersionHint) throw new EmDashDatabaseError(nativeVersionHint, error);
34
51
  throw new EmDashDatabaseError("Failed to create database", error);
35
52
  }
36
53
  }
37
54
 
38
55
  //#endregion
39
56
  export { createDatabase as n, EmDashDatabaseError as t };
40
- //# sourceMappingURL=connection-B4zVnQIa.mjs.map
57
+ //# sourceMappingURL=connection-2igzM-AT.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-2igzM-AT.mjs","names":[],"sources":["../src/database/connection.ts"],"sourcesContent":["import BetterSqlite3 from \"better-sqlite3\";\nimport { Kysely, SqliteDialect } from \"kysely\";\n\nimport { kyselyLogOption } from \"./instrumentation.js\";\nimport type { Database } from \"./types.js\";\n\nexport interface DatabaseConfig {\n\turl: string;\n\tauthToken?: string;\n}\n\nexport class EmDashDatabaseError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic override cause?: unknown,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"EmDashDatabaseError\";\n\t}\n}\n\n/**\n * Returns a helpful, actionable message when better-sqlite3's native binary\n * was compiled against a different Node.js version than the one running. This\n * happens after upgrading Node without rebuilding native deps.\n *\n * Returns null if the error is not a NODE_MODULE_VERSION mismatch.\n */\nexport function formatNativeModuleVersionError(error: unknown): string | null {\n\tconst message = error instanceof Error ? error.message : String(error);\n\tif (!message.includes(\"NODE_MODULE_VERSION\")) return null;\n\treturn (\n\t\t\"better-sqlite3's native binary was compiled against a different Node.js version. \" +\n\t\t\"Rebuild it with `pnpm rebuild better-sqlite3` (or `npm rebuild better-sqlite3`), \" +\n\t\t\"or reinstall dependencies with your current Node.js version.\"\n\t);\n}\n\n/**\n * Creates a Kysely database instance\n * Supports:\n * - file:./path/to/db.sqlite (local SQLite)\n * - :memory: (in-memory SQLite for testing)\n * - libsql://... (Turso/libSQL with auth token) - TODO\n */\nexport function createDatabase(config: DatabaseConfig): Kysely<Database> {\n\ttry {\n\t\t// Handle file-based SQLite\n\t\tif (config.url.startsWith(\"file:\") || config.url === \":memory:\") {\n\t\t\tconst dbPath = config.url === \":memory:\" ? \":memory:\" : config.url.replace(\"file:\", \"\");\n\n\t\t\tconst sqlite = new BetterSqlite3(dbPath);\n\n\t\t\t// Enable WAL mode for crash safety — writes go to a write-ahead log\n\t\t\t// before being applied, preventing FTS5 shadow table corruption on\n\t\t\t// process kill during content writes. No-op for :memory: databases.\n\t\t\tsqlite.pragma(\"journal_mode = WAL\");\n\n\t\t\t// Enable foreign key constraints\n\t\t\tsqlite.pragma(\"foreign_keys = ON\");\n\n\t\t\tconst dialect = new SqliteDialect({\n\t\t\t\tdatabase: sqlite,\n\t\t\t});\n\n\t\t\treturn new Kysely<Database>({ dialect, log: kyselyLogOption() });\n\t\t}\n\n\t\t// Handle libSQL (Turso)\n\t\tif (config.url.startsWith(\"libsql:\")) {\n\t\t\tif (!config.authToken) {\n\t\t\t\tthrow new EmDashDatabaseError(\"Auth token required for remote libSQL database\");\n\t\t\t}\n\t\t\t// LibSQL implementation would use @libsql/kysely-libsql\n\t\t\tthrow new EmDashDatabaseError(\"LibSQL not yet implemented\");\n\t\t}\n\n\t\tthrow new EmDashDatabaseError(`Unsupported database URL scheme: ${config.url}`);\n\t} catch (error) {\n\t\tif (error instanceof EmDashDatabaseError) {\n\t\t\tthrow error;\n\t\t}\n\t\tconst nativeVersionHint = formatNativeModuleVersionError(error);\n\t\tif (nativeVersionHint) {\n\t\t\tthrow new EmDashDatabaseError(nativeVersionHint, error);\n\t\t}\n\t\tthrow new EmDashDatabaseError(\"Failed to create database\", error);\n\t}\n}\n"],"mappings":";;;;;AAWA,IAAa,sBAAb,cAAyC,MAAM;CAC9C,YACC,SACA,AAAgB,OACf;AACD,QAAM,QAAQ;EAFE;AAGhB,OAAK,OAAO;;;;;;;;;;AAWd,SAAgB,+BAA+B,OAA+B;AAE7E,KAAI,EADY,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,EACzD,SAAS,sBAAsB,CAAE,QAAO;AACrD,QACC;;;;;;;;;AAaF,SAAgB,eAAe,QAA0C;AACxE,KAAI;AAEH,MAAI,OAAO,IAAI,WAAW,QAAQ,IAAI,OAAO,QAAQ,YAAY;GAGhE,MAAM,SAAS,IAAI,cAFJ,OAAO,QAAQ,aAAa,aAAa,OAAO,IAAI,QAAQ,SAAS,GAAG,CAE/C;AAKxC,UAAO,OAAO,qBAAqB;AAGnC,UAAO,OAAO,oBAAoB;AAMlC,UAAO,IAAI,OAAiB;IAAE,SAJd,IAAI,cAAc,EACjC,UAAU,QACV,CAAC;IAEqC,KAAK,iBAAiB;IAAE,CAAC;;AAIjE,MAAI,OAAO,IAAI,WAAW,UAAU,EAAE;AACrC,OAAI,CAAC,OAAO,UACX,OAAM,IAAI,oBAAoB,iDAAiD;AAGhF,SAAM,IAAI,oBAAoB,6BAA6B;;AAG5D,QAAM,IAAI,oBAAoB,oCAAoC,OAAO,MAAM;UACvE,OAAO;AACf,MAAI,iBAAiB,oBACpB,OAAM;EAEP,MAAM,oBAAoB,+BAA+B,MAAM;AAC/D,MAAI,kBACH,OAAM,IAAI,oBAAoB,mBAAmB,MAAM;AAExD,QAAM,IAAI,oBAAoB,6BAA6B,MAAM"}
@@ -0,0 +1,45 @@
1
+ import { Logger } from "kysely";
2
+
3
+ //#region src/database/instrumentation.d.ts
4
+ declare const QUERY_LOG_ENV = "EMDASH_QUERY_LOG";
5
+ declare const QUERY_LOG_PREFIX = "[emdash-query-log]";
6
+ interface QueryEvent {
7
+ sql: string;
8
+ params: readonly unknown[];
9
+ durationMs: number;
10
+ route: string;
11
+ method: string;
12
+ phase: string;
13
+ }
14
+ interface QueryRecorder {
15
+ events: QueryEvent[];
16
+ route: string;
17
+ method: string;
18
+ phase: string;
19
+ }
20
+ declare function createRecorder(route: string, method: string, phase: string): QueryRecorder;
21
+ declare function recordEvent(rec: QueryRecorder, sql: string, params: readonly unknown[], durationMs: number): void;
22
+ /**
23
+ * Emit all events from a recorder as prefixed NDJSON on stdout. The
24
+ * harness pipes the child's stdout, filters lines beginning with
25
+ * QUERY_LOG_PREFIX, and writes them to its own file. Using stdout means
26
+ * the sink works uniformly in Node and in workerd (which has no fs).
27
+ */
28
+ declare function flushRecorder(rec: QueryRecorder): void;
29
+ /**
30
+ * Whether query instrumentation is enabled. Read at Kysely construction
31
+ * time and middleware entry — the env var is a process-lifetime flag, not
32
+ * per-request. Gated via `process.env` so adapters that ship env through
33
+ * to the worker (e.g. Miniflare via wrangler.jsonc `vars` or host env
34
+ * pass-through) can enable it at runtime.
35
+ */
36
+ declare function isInstrumentationEnabled(): boolean;
37
+ /**
38
+ * Returns a Kysely `log` option when instrumentation is enabled, or undefined.
39
+ * Pass as `new Kysely({ dialect, log: kyselyLogOption() })` so disabled mode
40
+ * has zero overhead — Kysely skips query timing entirely when `log` is absent.
41
+ */
42
+ declare function kyselyLogOption(): Logger | undefined;
43
+ //#endregion
44
+ export { QUERY_LOG_ENV, QUERY_LOG_PREFIX, QueryEvent, QueryRecorder, createRecorder, flushRecorder, isInstrumentationEnabled, kyselyLogOption, recordEvent };
45
+ //# sourceMappingURL=instrumentation.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrumentation.d.mts","names":[],"sources":["../../src/database/instrumentation.ts"],"mappings":";;;cAkBa,aAAA;AAAA,cACA,gBAAA;AAAA,UAEI,UAAA;EAChB,GAAA;EACA,MAAA;EACA,UAAA;EACA,KAAA;EACA,MAAA;EACA,KAAA;AAAA;AAAA,UAGgB,aAAA;EAChB,MAAA,EAAQ,UAAA;EACR,KAAA;EACA,MAAA;EACA,KAAA;AAAA;AAAA,iBAGe,cAAA,CAAe,KAAA,UAAe,MAAA,UAAgB,KAAA,WAAgB,aAAA;AAAA,iBAI9D,WAAA,CACf,GAAA,EAAK,aAAA,EACL,GAAA,UACA,MAAA,sBACA,UAAA;;;;;AARD;;iBA0BgB,aAAA,CAAc,GAAA,EAAK,aAAA;;;;;;;;iBAcnB,wBAAA,CAAA;;;;;;iBAkBA,eAAA,CAAA,GAAmB,MAAA"}
@@ -0,0 +1,61 @@
1
+ import { getRequestContext } from "../request-context.mjs";
2
+
3
+ //#region src/database/instrumentation.ts
4
+ const QUERY_LOG_ENV = "EMDASH_QUERY_LOG";
5
+ const QUERY_LOG_PREFIX = "[emdash-query-log]";
6
+ function createRecorder(route, method, phase) {
7
+ return {
8
+ events: [],
9
+ route,
10
+ method,
11
+ phase
12
+ };
13
+ }
14
+ function recordEvent(rec, sql, params, durationMs) {
15
+ rec.events.push({
16
+ sql,
17
+ params,
18
+ durationMs,
19
+ route: rec.route,
20
+ method: rec.method,
21
+ phase: rec.phase
22
+ });
23
+ }
24
+ /**
25
+ * Emit all events from a recorder as prefixed NDJSON on stdout. The
26
+ * harness pipes the child's stdout, filters lines beginning with
27
+ * QUERY_LOG_PREFIX, and writes them to its own file. Using stdout means
28
+ * the sink works uniformly in Node and in workerd (which has no fs).
29
+ */
30
+ function flushRecorder(rec) {
31
+ if (rec.events.length === 0) return;
32
+ for (const e of rec.events) console.log(`${QUERY_LOG_PREFIX} ${JSON.stringify(e)}`);
33
+ }
34
+ /**
35
+ * Whether query instrumentation is enabled. Read at Kysely construction
36
+ * time and middleware entry — the env var is a process-lifetime flag, not
37
+ * per-request. Gated via `process.env` so adapters that ship env through
38
+ * to the worker (e.g. Miniflare via wrangler.jsonc `vars` or host env
39
+ * pass-through) can enable it at runtime.
40
+ */
41
+ function isInstrumentationEnabled() {
42
+ return Boolean(typeof process !== "undefined" && process.env && process.env[QUERY_LOG_ENV] === "1");
43
+ }
44
+ function kyselyLog(event) {
45
+ if (event.level !== "query") return;
46
+ const rec = getRequestContext()?.queryRecorder;
47
+ if (!rec) return;
48
+ recordEvent(rec, event.query.sql, event.query.parameters, event.queryDurationMillis);
49
+ }
50
+ /**
51
+ * Returns a Kysely `log` option when instrumentation is enabled, or undefined.
52
+ * Pass as `new Kysely({ dialect, log: kyselyLogOption() })` so disabled mode
53
+ * has zero overhead — Kysely skips query timing entirely when `log` is absent.
54
+ */
55
+ function kyselyLogOption() {
56
+ return isInstrumentationEnabled() ? kyselyLog : void 0;
57
+ }
58
+
59
+ //#endregion
60
+ export { QUERY_LOG_ENV, QUERY_LOG_PREFIX, createRecorder, flushRecorder, isInstrumentationEnabled, kyselyLogOption, recordEvent };
61
+ //# sourceMappingURL=instrumentation.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrumentation.mjs","names":[],"sources":["../../src/database/instrumentation.ts"],"sourcesContent":["/**\n * Query instrumentation\n *\n * Dev/test-only: captures every Kysely query executed inside a request,\n * tagged with the route, method, and a caller-supplied phase (e.g. \"cold\"\n * or \"warm\"). Events are emitted as prefixed NDJSON on stdout so the\n * harness can capture them from both Node and workerd — workerd has no\n * filesystem access, but `console.log` is portable.\n *\n * The recorder lives on the request context (AsyncLocalStorage). The\n * Kysely `log` hook reads the recorder at query time and appends an\n * event. When no recorder is attached, the hook is a null check.\n */\n\nimport type { LogEvent, Logger } from \"kysely\";\n\nimport { getRequestContext } from \"../request-context.js\";\n\nexport const QUERY_LOG_ENV = \"EMDASH_QUERY_LOG\";\nexport const QUERY_LOG_PREFIX = \"[emdash-query-log]\";\n\nexport interface QueryEvent {\n\tsql: string;\n\tparams: readonly unknown[];\n\tdurationMs: number;\n\troute: string;\n\tmethod: string;\n\tphase: string;\n}\n\nexport interface QueryRecorder {\n\tevents: QueryEvent[];\n\troute: string;\n\tmethod: string;\n\tphase: string;\n}\n\nexport function createRecorder(route: string, method: string, phase: string): QueryRecorder {\n\treturn { events: [], route, method, phase };\n}\n\nexport function recordEvent(\n\trec: QueryRecorder,\n\tsql: string,\n\tparams: readonly unknown[],\n\tdurationMs: number,\n): void {\n\trec.events.push({\n\t\tsql,\n\t\tparams,\n\t\tdurationMs,\n\t\troute: rec.route,\n\t\tmethod: rec.method,\n\t\tphase: rec.phase,\n\t});\n}\n\n/**\n * Emit all events from a recorder as prefixed NDJSON on stdout. The\n * harness pipes the child's stdout, filters lines beginning with\n * QUERY_LOG_PREFIX, and writes them to its own file. Using stdout means\n * the sink works uniformly in Node and in workerd (which has no fs).\n */\nexport function flushRecorder(rec: QueryRecorder): void {\n\tif (rec.events.length === 0) return;\n\tfor (const e of rec.events) {\n\t\tconsole.log(`${QUERY_LOG_PREFIX} ${JSON.stringify(e)}`);\n\t}\n}\n\n/**\n * Whether query instrumentation is enabled. Read at Kysely construction\n * time and middleware entry — the env var is a process-lifetime flag, not\n * per-request. Gated via `process.env` so adapters that ship env through\n * to the worker (e.g. Miniflare via wrangler.jsonc `vars` or host env\n * pass-through) can enable it at runtime.\n */\nexport function isInstrumentationEnabled(): boolean {\n\treturn Boolean(\n\t\ttypeof process !== \"undefined\" && process.env && process.env[QUERY_LOG_ENV] === \"1\",\n\t);\n}\n\nfunction kyselyLog(event: LogEvent): void {\n\tif (event.level !== \"query\") return;\n\tconst rec = getRequestContext()?.queryRecorder;\n\tif (!rec) return;\n\trecordEvent(rec, event.query.sql, event.query.parameters, event.queryDurationMillis);\n}\n\n/**\n * Returns a Kysely `log` option when instrumentation is enabled, or undefined.\n * Pass as `new Kysely({ dialect, log: kyselyLogOption() })` so disabled mode\n * has zero overhead — Kysely skips query timing entirely when `log` is absent.\n */\nexport function kyselyLogOption(): Logger | undefined {\n\treturn isInstrumentationEnabled() ? kyselyLog : undefined;\n}\n"],"mappings":";;;AAkBA,MAAa,gBAAgB;AAC7B,MAAa,mBAAmB;AAkBhC,SAAgB,eAAe,OAAe,QAAgB,OAA8B;AAC3F,QAAO;EAAE,QAAQ,EAAE;EAAE;EAAO;EAAQ;EAAO;;AAG5C,SAAgB,YACf,KACA,KACA,QACA,YACO;AACP,KAAI,OAAO,KAAK;EACf;EACA;EACA;EACA,OAAO,IAAI;EACX,QAAQ,IAAI;EACZ,OAAO,IAAI;EACX,CAAC;;;;;;;;AASH,SAAgB,cAAc,KAA0B;AACvD,KAAI,IAAI,OAAO,WAAW,EAAG;AAC7B,MAAK,MAAM,KAAK,IAAI,OACnB,SAAQ,IAAI,GAAG,iBAAiB,GAAG,KAAK,UAAU,EAAE,GAAG;;;;;;;;;AAWzD,SAAgB,2BAAoC;AACnD,QAAO,QACN,OAAO,YAAY,eAAe,QAAQ,OAAO,QAAQ,IAAI,mBAAmB,IAChF;;AAGF,SAAS,UAAU,OAAuB;AACzC,KAAI,MAAM,UAAU,QAAS;CAC7B,MAAM,MAAM,mBAAmB,EAAE;AACjC,KAAI,CAAC,IAAK;AACV,aAAY,KAAK,MAAM,MAAM,KAAK,MAAM,MAAM,YAAY,MAAM,oBAAoB;;;;;;;AAQrF,SAAgB,kBAAsC;AACrD,QAAO,0BAA0B,GAAG,YAAY"}
@@ -1,4 +1,4 @@
1
- import "../types-B6BzlZxx.mjs";
2
- import { i as runMigrations, n as getMigrationStatus, r as rollbackMigration, t as MigrationStatus } from "../runner-DYv3rX8P.mjs";
3
- import { a as SqliteConfig, c as sqlite, i as PostgresConfig, n as DatabaseDialectType, o as libsql, r as LibsqlConfig, s as postgres, t as DatabaseDescriptor } from "../adapters-C2BzVy0p.mjs";
1
+ import "../types-8xrvl_68.mjs";
2
+ import { i as runMigrations, n as getMigrationStatus, r as rollbackMigration, t as MigrationStatus } from "../runner-Fl2NcUUz.mjs";
3
+ import { a as SqliteConfig, c as sqlite, i as PostgresConfig, n as DatabaseDialectType, o as libsql, r as LibsqlConfig, s as postgres, t as DatabaseDescriptor } from "../adapters-Di31kZ28.mjs";
4
4
  export { type DatabaseDescriptor, type DatabaseDialectType, type LibsqlConfig, type MigrationStatus, type PostgresConfig, type SqliteConfig, getMigrationStatus, libsql, postgres, rollbackMigration, runMigrations, sqlite };
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/db/adapters.ts"],"sourcesContent":["/**\n * Database Adapter Functions\n *\n * These run at config time (astro.config.mjs) and return serializable descriptors.\n * The actual dialect is created at runtime by loading the entrypoint.\n *\n * @example\n * ```ts\n * // astro.config.mjs\n * import emdash from \"emdash/astro\";\n * import { sqlite } from \"emdash/db\";\n *\n * export default defineConfig({\n * integrations: [\n * emdash({\n * database: sqlite({ url: \"file:./data.db\" }),\n * }),\n * ],\n * });\n * ```\n */\n\n/**\n * Dialect family identifier.\n * Used at runtime to select dialect-specific SQL fragments.\n */\nexport type DatabaseDialectType = \"sqlite\" | \"postgres\";\n\n/**\n * Database descriptor - serializable config for virtual modules\n */\nexport interface DatabaseDescriptor {\n\tentrypoint: string;\n\tconfig: unknown;\n\ttype: DatabaseDialectType;\n}\n\nexport interface SqliteConfig {\n\t/**\n\t * Database URL (e.g., \"file:./data.db\")\n\t */\n\turl: string;\n}\n\nexport interface LibsqlConfig {\n\t/**\n\t * Database URL (e.g., \"file:./data.db\" or \"libsql://...\")\n\t */\n\turl: string;\n\t/**\n\t * Auth token for remote libSQL\n\t */\n\tauthToken?: string;\n}\n\n/**\n * SQLite database adapter (better-sqlite3)\n *\n * For local development and Node.js deployments.\n *\n * @example\n * ```ts\n * database: sqlite({ url: \"file:./data.db\" })\n * ```\n */\nexport function sqlite(config: SqliteConfig): DatabaseDescriptor {\n\treturn {\n\t\tentrypoint: \"emdash/db/sqlite\",\n\t\tconfig,\n\t\ttype: \"sqlite\",\n\t};\n}\n\n/**\n * libSQL database adapter (Turso)\n *\n * For Turso hosted databases or local libSQL.\n *\n * @example\n * ```ts\n * database: libsql({\n * url: \"libsql://my-db.turso.io\",\n * authToken: process.env.TURSO_AUTH_TOKEN,\n * })\n * ```\n */\nexport function libsql(config: LibsqlConfig): DatabaseDescriptor {\n\treturn {\n\t\tentrypoint: \"emdash/db/libsql\",\n\t\tconfig,\n\t\ttype: \"sqlite\",\n\t};\n}\n\n/**\n * PostgreSQL connection configuration\n */\nexport interface PostgresConfig {\n\tconnectionString?: string;\n\thost?: string;\n\tport?: number;\n\tdatabase?: string;\n\tuser?: string;\n\tpassword?: string;\n\tssl?: boolean;\n\tpool?: { min?: number; max?: number };\n}\n\n/**\n * PostgreSQL database adapter\n *\n * For PostgreSQL deployments with connection pooling.\n *\n * @example\n * ```ts\n * database: postgres({ connectionString: process.env.DATABASE_URL })\n * ```\n */\nexport function postgres(config: PostgresConfig): DatabaseDescriptor {\n\treturn {\n\t\tentrypoint: \"emdash/db/postgres\",\n\t\tconfig,\n\t\ttype: \"postgres\",\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;AAiEA,SAAgB,OAAO,QAA0C;AAChE,QAAO;EACN,YAAY;EACZ;EACA,MAAM;EACN;;;;;;;;;;;;;;;AAgBF,SAAgB,OAAO,QAA0C;AAChE,QAAO;EACN,YAAY;EACZ;EACA,MAAM;EACN;;;;;;;;;;;;AA2BF,SAAgB,SAAS,QAA4C;AACpE,QAAO;EACN,YAAY;EACZ;EACA,MAAM;EACN"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/db/adapters.ts"],"sourcesContent":["/**\n * Database Adapter Functions\n *\n * These run at config time (astro.config.mjs) and return serializable descriptors.\n * The actual dialect is created at runtime by loading the entrypoint.\n *\n * @example\n * ```ts\n * // astro.config.mjs\n * import emdash from \"emdash/astro\";\n * import { sqlite } from \"emdash/db\";\n *\n * export default defineConfig({\n * integrations: [\n * emdash({\n * database: sqlite({ url: \"file:./data.db\" }),\n * }),\n * ],\n * });\n * ```\n */\n\n/**\n * Dialect family identifier.\n * Used at runtime to select dialect-specific SQL fragments.\n */\nexport type DatabaseDialectType = \"sqlite\" | \"postgres\";\n\n/**\n * Database descriptor - serializable config for virtual modules\n */\nexport interface DatabaseDescriptor {\n\tentrypoint: string;\n\tconfig: unknown;\n\ttype: DatabaseDialectType;\n\t/**\n\t * When true, the adapter's runtime entrypoint MUST export a named\n\t * `createRequestScopedDb` function matching the signature declared in\n\t * `virtual:emdash/dialect`. The virtual-module generator re-exports it\n\t * by name, so a missing export becomes a build-time bundler error.\n\t *\n\t * The function is called once per request and decides — based on its own\n\t * runtime config (e.g. whether the user opted into D1 sessions) — whether\n\t * to return a per-request Kysely or null. Use this for features like D1\n\t * read-replica sessions, bookmark cookies, or any per-request DB handle.\n\t *\n\t * When false or absent, the generator emits a stub that returns null and\n\t * the middleware takes its default (singleton) path.\n\t */\n\tsupportsRequestScope?: boolean;\n}\n\nexport interface SqliteConfig {\n\t/**\n\t * Database URL (e.g., \"file:./data.db\")\n\t */\n\turl: string;\n}\n\nexport interface LibsqlConfig {\n\t/**\n\t * Database URL (e.g., \"file:./data.db\" or \"libsql://...\")\n\t */\n\turl: string;\n\t/**\n\t * Auth token for remote libSQL\n\t */\n\tauthToken?: string;\n}\n\n/**\n * SQLite database adapter (better-sqlite3)\n *\n * For local development and Node.js deployments.\n *\n * @example\n * ```ts\n * database: sqlite({ url: \"file:./data.db\" })\n * ```\n */\nexport function sqlite(config: SqliteConfig): DatabaseDescriptor {\n\treturn {\n\t\tentrypoint: \"emdash/db/sqlite\",\n\t\tconfig,\n\t\ttype: \"sqlite\",\n\t};\n}\n\n/**\n * libSQL database adapter (Turso)\n *\n * For Turso hosted databases or local libSQL.\n *\n * @example\n * ```ts\n * database: libsql({\n * url: \"libsql://my-db.turso.io\",\n * authToken: process.env.TURSO_AUTH_TOKEN,\n * })\n * ```\n */\nexport function libsql(config: LibsqlConfig): DatabaseDescriptor {\n\treturn {\n\t\tentrypoint: \"emdash/db/libsql\",\n\t\tconfig,\n\t\ttype: \"sqlite\",\n\t};\n}\n\n/**\n * PostgreSQL connection configuration\n */\nexport interface PostgresConfig {\n\tconnectionString?: string;\n\thost?: string;\n\tport?: number;\n\tdatabase?: string;\n\tuser?: string;\n\tpassword?: string;\n\tssl?: boolean;\n\tpool?: { min?: number; max?: number };\n}\n\n/**\n * PostgreSQL database adapter\n *\n * For PostgreSQL deployments with connection pooling.\n *\n * @example\n * ```ts\n * database: postgres({ connectionString: process.env.DATABASE_URL })\n * ```\n */\nexport function postgres(config: PostgresConfig): DatabaseDescriptor {\n\treturn {\n\t\tentrypoint: \"emdash/db/postgres\",\n\t\tconfig,\n\t\ttype: \"postgres\",\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;AAgFA,SAAgB,OAAO,QAA0C;AAChE,QAAO;EACN,YAAY;EACZ;EACA,MAAM;EACN;;;;;;;;;;;;;;;AAgBF,SAAgB,OAAO,QAA0C;AAChE,QAAO;EACN,YAAY;EACZ;EACA,MAAM;EACN;;;;;;;;;;;;AA2BF,SAAgB,SAAS,QAA4C;AACpE,QAAO;EACN,YAAY;EACZ;EACA,MAAM;EACN"}
@@ -1,4 +1,4 @@
1
- import { r as LibsqlConfig } from "../adapters-C2BzVy0p.mjs";
1
+ import { r as LibsqlConfig } from "../adapters-Di31kZ28.mjs";
2
2
  import { Dialect } from "kysely";
3
3
 
4
4
  //#region src/db/libsql.d.ts
@@ -1,4 +1,4 @@
1
- import { i as PostgresConfig } from "../adapters-C2BzVy0p.mjs";
1
+ import { i as PostgresConfig } from "../adapters-Di31kZ28.mjs";
2
2
  import { PostgresDialect } from "kysely";
3
3
 
4
4
  //#region src/db/postgres.d.ts
@@ -1,4 +1,4 @@
1
- import { a as SqliteConfig } from "../adapters-C2BzVy0p.mjs";
1
+ import { a as SqliteConfig } from "../adapters-Di31kZ28.mjs";
2
2
  import { Dialect } from "kysely";
3
3
 
4
4
  //#region src/db/sqlite.d.ts
@@ -0,0 +1,41 @@
1
+ //#region src/utils/db-errors.ts
2
+ /**
3
+ * Shared detection helpers for database-layer error messages.
4
+ *
5
+ * Different SQL dialects phrase "table or relation does not exist" differently:
6
+ *
7
+ * - SQLite / D1: "no such table: foo"
8
+ * - PostgreSQL: 'relation "foo" does not exist'
9
+ * 'table "foo" does not exist'
10
+ * - MySQL (future): "Table 'db.foo' doesn't exist"
11
+ *
12
+ * Runtime code paths that short-circuit on missing tables (pre-migration
13
+ * probes, optional feature tables, etc.) should use these helpers rather
14
+ * than hand-rolling string matches per call-site.
15
+ */
16
+ /**
17
+ * Extract a lowercase error message from any unknown value, safely.
18
+ */
19
+ function messageOf(error) {
20
+ if (error instanceof Error) return error.message.toLowerCase();
21
+ if (typeof error === "string") return error.toLowerCase();
22
+ return "";
23
+ }
24
+ /**
25
+ * Returns true when `error` is a "table does not exist" error across the
26
+ * dialects EmDash supports (D1/SQLite and PostgreSQL). Used by runtime
27
+ * probes to treat pre-migration databases as empty without logging a scary
28
+ * warning, while still propagating unrelated errors (permissions, connection
29
+ * loss, syntax issues) to callers.
30
+ */
31
+ function isMissingTableError(error) {
32
+ const message = messageOf(error);
33
+ if (!message) return false;
34
+ if (message.includes("no such table")) return true;
35
+ if (message.includes("does not exist") || message.includes("doesn't exist")) return message.includes("relation") || message.includes("table");
36
+ return false;
37
+ }
38
+
39
+ //#endregion
40
+ export { isMissingTableError as t };
41
+ //# sourceMappingURL=db-errors-D0UT85nC.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-errors-D0UT85nC.mjs","names":[],"sources":["../src/utils/db-errors.ts"],"sourcesContent":["/**\n * Shared detection helpers for database-layer error messages.\n *\n * Different SQL dialects phrase \"table or relation does not exist\" differently:\n *\n * - SQLite / D1: \"no such table: foo\"\n * - PostgreSQL: 'relation \"foo\" does not exist'\n * 'table \"foo\" does not exist'\n * - MySQL (future): \"Table 'db.foo' doesn't exist\"\n *\n * Runtime code paths that short-circuit on missing tables (pre-migration\n * probes, optional feature tables, etc.) should use these helpers rather\n * than hand-rolling string matches per call-site.\n */\n\n/**\n * Extract a lowercase error message from any unknown value, safely.\n */\nfunction messageOf(error: unknown): string {\n\tif (error instanceof Error) return error.message.toLowerCase();\n\tif (typeof error === \"string\") return error.toLowerCase();\n\treturn \"\";\n}\n\n/**\n * Returns true when `error` is a \"table does not exist\" error across the\n * dialects EmDash supports (D1/SQLite and PostgreSQL). Used by runtime\n * probes to treat pre-migration databases as empty without logging a scary\n * warning, while still propagating unrelated errors (permissions, connection\n * loss, syntax issues) to callers.\n */\nexport function isMissingTableError(error: unknown): boolean {\n\tconst message = messageOf(error);\n\tif (!message) return false;\n\n\t// SQLite / D1\n\tif (message.includes(\"no such table\")) return true;\n\n\t// PostgreSQL (and some MySQL variants): \"relation ... does not exist\" /\n\t// \"table ... does not exist\" / \"doesn't exist\".\n\tif (message.includes(\"does not exist\") || message.includes(\"doesn't exist\")) {\n\t\treturn message.includes(\"relation\") || message.includes(\"table\");\n\t}\n\n\treturn false;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,SAAS,UAAU,OAAwB;AAC1C,KAAI,iBAAiB,MAAO,QAAO,MAAM,QAAQ,aAAa;AAC9D,KAAI,OAAO,UAAU,SAAU,QAAO,MAAM,aAAa;AACzD,QAAO;;;;;;;;;AAUR,SAAgB,oBAAoB,OAAyB;CAC5D,MAAM,UAAU,UAAU,MAAM;AAChC,KAAI,CAAC,QAAS,QAAO;AAGrB,KAAI,QAAQ,SAAS,gBAAgB,CAAE,QAAO;AAI9C,KAAI,QAAQ,SAAS,iBAAiB,IAAI,QAAQ,SAAS,gBAAgB,CAC1E,QAAO,QAAQ,SAAS,WAAW,IAAI,QAAQ,SAAS,QAAQ;AAGjE,QAAO"}
@@ -78,4 +78,4 @@ const defaultSeed = {
78
78
 
79
79
  //#endregion
80
80
  export { defaultSeed as t };
81
- //# sourceMappingURL=default-PUx9RK6u.mjs.map
81
+ //# sourceMappingURL=default-CME5YdZ3.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"default-PUx9RK6u.mjs","names":[],"sources":["../src/seed/default.ts"],"sourcesContent":["/**\n * Default seed applied when no user seed file exists.\n *\n * Provides the baseline schema every EmDash site needs:\n * posts, pages, categories, and tags.\n */\n\nimport type { SeedFile } from \"./types.js\";\n\nexport const defaultSeed: SeedFile = {\n\tversion: \"1\",\n\tmeta: {\n\t\tname: \"Default\",\n\t\tdescription: \"Posts and pages with categories and tags\",\n\t},\n\tcollections: [\n\t\t{\n\t\t\tslug: \"posts\",\n\t\t\tlabel: \"Posts\",\n\t\t\tlabelSingular: \"Post\",\n\t\t\tsupports: [\"drafts\", \"revisions\", \"search\"],\n\t\t\tfields: [\n\t\t\t\t{\n\t\t\t\t\tslug: \"title\",\n\t\t\t\t\tlabel: \"Title\",\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"featured_image\",\n\t\t\t\t\tlabel: \"Featured Image\",\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"content\",\n\t\t\t\t\tlabel: \"Content\",\n\t\t\t\t\ttype: \"portableText\",\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"excerpt\",\n\t\t\t\t\tlabel: \"Excerpt\",\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tslug: \"pages\",\n\t\t\tlabel: \"Pages\",\n\t\t\tlabelSingular: \"Page\",\n\t\t\tsupports: [\"drafts\", \"revisions\", \"search\"],\n\t\t\tfields: [\n\t\t\t\t{\n\t\t\t\t\tslug: \"title\",\n\t\t\t\t\tlabel: \"Title\",\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"content\",\n\t\t\t\t\tlabel: \"Content\",\n\t\t\t\t\ttype: \"portableText\",\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t],\n\ttaxonomies: [\n\t\t{\n\t\t\tname: \"category\",\n\t\t\tlabel: \"Categories\",\n\t\t\tlabelSingular: \"Category\",\n\t\t\thierarchical: true,\n\t\t\tcollections: [\"posts\"],\n\t\t},\n\t\t{\n\t\t\tname: \"tag\",\n\t\t\tlabel: \"Tags\",\n\t\t\tlabelSingular: \"Tag\",\n\t\t\thierarchical: false,\n\t\t\tcollections: [\"posts\"],\n\t\t},\n\t],\n};\n"],"mappings":";AASA,MAAa,cAAwB;CACpC,SAAS;CACT,MAAM;EACL,MAAM;EACN,aAAa;EACb;CACD,aAAa,CACZ;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,UAAU;GAAC;GAAU;GAAa;GAAS;EAC3C,QAAQ;GACP;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN,UAAU;IACV,YAAY;IACZ;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN,YAAY;IACZ;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN;GACD;EACD,EACD;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,UAAU;GAAC;GAAU;GAAa;GAAS;EAC3C,QAAQ,CACP;GACC,MAAM;GACN,OAAO;GACP,MAAM;GACN,UAAU;GACV,YAAY;GACZ,EACD;GACC,MAAM;GACN,OAAO;GACP,MAAM;GACN,YAAY;GACZ,CACD;EACD,CACD;CACD,YAAY,CACX;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,cAAc;EACd,aAAa,CAAC,QAAQ;EACtB,EACD;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,cAAc;EACd,aAAa,CAAC,QAAQ;EACtB,CACD;CACD"}
1
+ {"version":3,"file":"default-CME5YdZ3.mjs","names":[],"sources":["../src/seed/default.ts"],"sourcesContent":["/**\n * Default seed applied when no user seed file exists.\n *\n * Provides the baseline schema every EmDash site needs:\n * posts, pages, categories, and tags.\n */\n\nimport type { SeedFile } from \"./types.js\";\n\nexport const defaultSeed: SeedFile = {\n\tversion: \"1\",\n\tmeta: {\n\t\tname: \"Default\",\n\t\tdescription: \"Posts and pages with categories and tags\",\n\t},\n\tcollections: [\n\t\t{\n\t\t\tslug: \"posts\",\n\t\t\tlabel: \"Posts\",\n\t\t\tlabelSingular: \"Post\",\n\t\t\tsupports: [\"drafts\", \"revisions\", \"search\"],\n\t\t\tfields: [\n\t\t\t\t{\n\t\t\t\t\tslug: \"title\",\n\t\t\t\t\tlabel: \"Title\",\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"featured_image\",\n\t\t\t\t\tlabel: \"Featured Image\",\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"content\",\n\t\t\t\t\tlabel: \"Content\",\n\t\t\t\t\ttype: \"portableText\",\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"excerpt\",\n\t\t\t\t\tlabel: \"Excerpt\",\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tslug: \"pages\",\n\t\t\tlabel: \"Pages\",\n\t\t\tlabelSingular: \"Page\",\n\t\t\tsupports: [\"drafts\", \"revisions\", \"search\"],\n\t\t\tfields: [\n\t\t\t\t{\n\t\t\t\t\tslug: \"title\",\n\t\t\t\t\tlabel: \"Title\",\n\t\t\t\t\ttype: \"string\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tslug: \"content\",\n\t\t\t\t\tlabel: \"Content\",\n\t\t\t\t\ttype: \"portableText\",\n\t\t\t\t\tsearchable: true,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t],\n\ttaxonomies: [\n\t\t{\n\t\t\tname: \"category\",\n\t\t\tlabel: \"Categories\",\n\t\t\tlabelSingular: \"Category\",\n\t\t\thierarchical: true,\n\t\t\tcollections: [\"posts\"],\n\t\t},\n\t\t{\n\t\t\tname: \"tag\",\n\t\t\tlabel: \"Tags\",\n\t\t\tlabelSingular: \"Tag\",\n\t\t\thierarchical: false,\n\t\t\tcollections: [\"posts\"],\n\t\t},\n\t],\n};\n"],"mappings":";AASA,MAAa,cAAwB;CACpC,SAAS;CACT,MAAM;EACL,MAAM;EACN,aAAa;EACb;CACD,aAAa,CACZ;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,UAAU;GAAC;GAAU;GAAa;GAAS;EAC3C,QAAQ;GACP;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN,UAAU;IACV,YAAY;IACZ;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN,YAAY;IACZ;GACD;IACC,MAAM;IACN,OAAO;IACP,MAAM;IACN;GACD;EACD,EACD;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,UAAU;GAAC;GAAU;GAAa;GAAS;EAC3C,QAAQ,CACP;GACC,MAAM;GACN,OAAO;GACP,MAAM;GACN,UAAU;GACV,YAAY;GACZ,EACD;GACC,MAAM;GACN,OAAO;GACP,MAAM;GACN,YAAY;GACZ,CACD;EACD,CACD;CACD,YAAY,CACX;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,cAAc;EACd,aAAa,CAAC,QAAQ;EACtB,EACD;EACC,MAAM;EACN,OAAO;EACP,eAAe;EACf,cAAc;EACd,aAAa,CAAC,QAAQ;EACtB,CACD;CACD"}
@@ -24,4 +24,4 @@ function apiError(code, message, status) {
24
24
 
25
25
  //#endregion
26
26
  export { apiError as t };
27
- //# sourceMappingURL=error-HBeQbVhV.mjs.map
27
+ //# sourceMappingURL=error-CiYn9yDu.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"error-HBeQbVhV.mjs","names":[],"sources":["../src/api/error.ts"],"sourcesContent":["/**\n * Standardized API error responses.\n *\n * All API routes should use these utilities instead of inline\n * `new Response(JSON.stringify({ error: ... }), ...)` patterns.\n */\n\nimport { mapErrorStatus } from \"./errors.js\";\nimport type { ApiResult } from \"./types.js\";\n\n// Re-export everything from errors.ts so existing `import { mapErrorStatus } from \"./error.js\"` still works\nexport * from \"./errors.js\";\n\n/**\n * Standard cache headers for all API responses.\n *\n * Cache-Control: private, no-store -- prevents CDN/proxy caching of authenticated data.\n * no-store already tells caches not to store the response, so Vary is unnecessary.\n */\nconst API_CACHE_HEADERS: HeadersInit = {\n\t\"Cache-Control\": \"private, no-store\",\n};\n\n/**\n * Create a standardized error response.\n *\n * Always returns `{ error: { code, message } }` with correct Content-Type.\n * Use this for all error responses in API routes.\n */\nexport function apiError(code: string, message: string, status: number): Response {\n\treturn Response.json({ error: { code, message } }, { status, headers: API_CACHE_HEADERS });\n}\n\n/**\n * Create a standardized success response.\n *\n * Always returns `{ data: T }` with correct status code.\n * Use this for all success responses in API routes.\n */\nexport function apiSuccess<T>(data: T, status = 200): Response {\n\treturn Response.json({ data }, { status, headers: API_CACHE_HEADERS });\n}\n\n/**\n * Handle an unknown error in a catch block.\n *\n * - Logs the full error server-side\n * - Returns a generic message to the client (never leaks error.message)\n * - Use `fallbackMessage` for the public-facing message\n * - Use `fallbackCode` for the error code\n */\nexport function handleError(\n\terror: unknown,\n\tfallbackMessage: string,\n\tfallbackCode: string,\n): Response {\n\tconsole.error(`[${fallbackCode}]`, error);\n\treturn apiError(fallbackCode, fallbackMessage, 500);\n}\n\n/**\n * Standard initialization check.\n *\n * Returns an error response if EmDash is not initialized, or null if OK.\n * Usage: `const err = requireInit(emdash); if (err) return err;`\n */\nexport function requireInit(emdash: unknown): Response | null {\n\tif (!emdash || typeof emdash !== \"object\") {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\treturn null;\n}\n\n/**\n * Standard database check.\n *\n * Returns an error response if the database is not available, or null if OK.\n * Usage: `const err = requireDb(emdash?.db); if (err) return err;`\n */\nexport function requireDb(db: unknown): Response | null {\n\tif (!db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\treturn null;\n}\n\n/**\n * Convert an ApiResult into an HTTP Response.\n *\n * Collapses the handler-to-response boilerplate:\n * - Success: returns `apiSuccess(result.data, successStatus)`\n * - Error: returns `apiError(code, message, mapErrorStatus(code))`\n */\nexport function unwrapResult<T>(result: ApiResult<T>, successStatus = 200): Response {\n\tif (!result.success) {\n\t\treturn apiError(result.error.code, result.error.message, mapErrorStatus(result.error.code));\n\t}\n\treturn apiSuccess(result.data, successStatus);\n}\n"],"mappings":";;;;;;;AAmBA,MAAM,oBAAiC,EACtC,iBAAiB,qBACjB;;;;;;;AAQD,SAAgB,SAAS,MAAc,SAAiB,QAA0B;AACjF,QAAO,SAAS,KAAK,EAAE,OAAO;EAAE;EAAM;EAAS,EAAE,EAAE;EAAE;EAAQ,SAAS;EAAmB,CAAC"}
1
+ {"version":3,"file":"error-CiYn9yDu.mjs","names":[],"sources":["../src/api/error.ts"],"sourcesContent":["/**\n * Standardized API error responses.\n *\n * All API routes should use these utilities instead of inline\n * `new Response(JSON.stringify({ error: ... }), ...)` patterns.\n */\n\nimport { mapErrorStatus } from \"./errors.js\";\nimport type { ApiResult } from \"./types.js\";\n\n// Re-export everything from errors.ts so existing `import { mapErrorStatus } from \"./error.js\"` still works\nexport * from \"./errors.js\";\n\n/**\n * Standard cache headers for all API responses.\n *\n * Cache-Control: private, no-store -- prevents CDN/proxy caching of authenticated data.\n * no-store already tells caches not to store the response, so Vary is unnecessary.\n */\nconst API_CACHE_HEADERS: HeadersInit = {\n\t\"Cache-Control\": \"private, no-store\",\n};\n\n/**\n * Create a standardized error response.\n *\n * Always returns `{ error: { code, message } }` with correct Content-Type.\n * Use this for all error responses in API routes.\n */\nexport function apiError(code: string, message: string, status: number): Response {\n\treturn Response.json({ error: { code, message } }, { status, headers: API_CACHE_HEADERS });\n}\n\n/**\n * Create a standardized success response.\n *\n * Always returns `{ data: T }` with correct status code.\n * Use this for all success responses in API routes.\n */\nexport function apiSuccess<T>(data: T, status = 200): Response {\n\treturn Response.json({ data }, { status, headers: API_CACHE_HEADERS });\n}\n\n/**\n * Handle an unknown error in a catch block.\n *\n * - Logs the full error server-side\n * - Returns a generic message to the client (never leaks error.message)\n * - Use `fallbackMessage` for the public-facing message\n * - Use `fallbackCode` for the error code\n */\nexport function handleError(\n\terror: unknown,\n\tfallbackMessage: string,\n\tfallbackCode: string,\n): Response {\n\tconsole.error(`[${fallbackCode}]`, error);\n\treturn apiError(fallbackCode, fallbackMessage, 500);\n}\n\n/**\n * Standard initialization check.\n *\n * Returns an error response if EmDash is not initialized, or null if OK.\n * Usage: `const err = requireInit(emdash); if (err) return err;`\n */\nexport function requireInit(emdash: unknown): Response | null {\n\tif (!emdash || typeof emdash !== \"object\") {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\treturn null;\n}\n\n/**\n * Standard database check.\n *\n * Returns an error response if the database is not available, or null if OK.\n * Usage: `const err = requireDb(emdash?.db); if (err) return err;`\n */\nexport function requireDb(db: unknown): Response | null {\n\tif (!db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\treturn null;\n}\n\n/**\n * Convert an ApiResult into an HTTP Response.\n *\n * Collapses the handler-to-response boilerplate:\n * - Success: returns `apiSuccess(result.data, successStatus)`\n * - Error: returns `apiError(code, message, mapErrorStatus(code))`\n */\nexport function unwrapResult<T>(result: ApiResult<T>, successStatus = 200): Response {\n\tif (!result.success) {\n\t\treturn apiError(result.error.code, result.error.message, mapErrorStatus(result.error.code));\n\t}\n\treturn apiSuccess(result.data, successStatus);\n}\n"],"mappings":";;;;;;;AAmBA,MAAM,oBAAiC,EACtC,iBAAiB,qBACjB;;;;;;;AAQD,SAAgB,SAAS,MAAc,SAAiB,QAA0B;AACjF,QAAO,SAAS,KAAK,EAAE,OAAO;EAAE;EAAM;EAAS,EAAE,EAAE;EAAE;EAAQ,SAAS;EAAmB,CAAC"}