emdash 0.2.0 → 0.3.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 (126) hide show
  1. package/dist/{adapters-N6BF7RCD.d.mts → adapters-BLMa4JGD.d.mts} +1 -1
  2. package/dist/{adapters-N6BF7RCD.d.mts.map → adapters-BLMa4JGD.d.mts.map} +1 -1
  3. package/dist/{apply-wmVEOSbR.mjs → apply-Bqoekfbe.mjs} +6 -6
  4. package/dist/{apply-wmVEOSbR.mjs.map → apply-Bqoekfbe.mjs.map} +1 -1
  5. package/dist/astro/index.d.mts +25 -11
  6. package/dist/astro/index.d.mts.map +1 -1
  7. package/dist/astro/index.mjs +31 -19
  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.mjs +1 -1
  11. package/dist/astro/middleware/redirect.mjs +2 -2
  12. package/dist/astro/middleware/setup.mjs +1 -1
  13. package/dist/astro/middleware.mjs +20 -16
  14. package/dist/astro/middleware.mjs.map +1 -1
  15. package/dist/astro/types.d.mts +9 -9
  16. package/dist/astro/types.d.mts.map +1 -1
  17. package/dist/{byline-1WQPlISL.mjs → byline-BGj9p9Ht.mjs} +3 -3
  18. package/dist/{byline-1WQPlISL.mjs.map → byline-BGj9p9Ht.mjs.map} +1 -1
  19. package/dist/{bylines-BYdTYmia.mjs → bylines-BihaoIDY.mjs} +5 -5
  20. package/dist/{bylines-BYdTYmia.mjs.map → bylines-BihaoIDY.mjs.map} +1 -1
  21. package/dist/cli/index.mjs +8 -8
  22. package/dist/client/cf-access.d.mts +1 -1
  23. package/dist/client/index.d.mts +1 -1
  24. package/dist/client/index.mjs +1 -1
  25. package/dist/{content-BmXndhdi.mjs → content-BsBoyj8G.mjs} +20 -3
  26. package/dist/content-BsBoyj8G.mjs.map +1 -0
  27. package/dist/db/index.d.mts +3 -3
  28. package/dist/db/index.mjs +2 -2
  29. package/dist/db/libsql.d.mts +1 -1
  30. package/dist/db/postgres.d.mts +1 -1
  31. package/dist/db/sqlite.d.mts +1 -1
  32. package/dist/{dialect-helpers-B9uSp2GJ.mjs → dialect-helpers-DhTzaUxP.mjs} +4 -1
  33. package/dist/dialect-helpers-DhTzaUxP.mjs.map +1 -0
  34. package/dist/{index-UHEVQMus.d.mts → index-Cff7AimE.d.mts} +40 -16
  35. package/dist/index-Cff7AimE.d.mts.map +1 -0
  36. package/dist/index.d.mts +11 -11
  37. package/dist/index.mjs +12 -12
  38. package/dist/{loader-CHb2v0jm.mjs → loader-BmYdf3Dr.mjs} +4 -2
  39. package/dist/loader-BmYdf3Dr.mjs.map +1 -0
  40. package/dist/media/index.d.mts +1 -1
  41. package/dist/media/local-runtime.d.mts +7 -7
  42. package/dist/{mode-CYeM2rPt.mjs → mode-C2EzN1uE.mjs} +1 -1
  43. package/dist/{mode-CYeM2rPt.mjs.map → mode-C2EzN1uE.mjs.map} +1 -1
  44. package/dist/page/index.d.mts +1 -1
  45. package/dist/{placeholder-bOx1xCTY.d.mts → placeholder-SvFCKbz_.d.mts} +1 -1
  46. package/dist/{placeholder-bOx1xCTY.d.mts.map → placeholder-SvFCKbz_.d.mts.map} +1 -1
  47. package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
  48. package/dist/{query-5Hcv_5ER.mjs → query-sesiOndV.mjs} +6 -6
  49. package/dist/{query-5Hcv_5ER.mjs.map → query-sesiOndV.mjs.map} +1 -1
  50. package/dist/{redirect-DIfIni3r.mjs → redirect-DUAk-Yl_.mjs} +9 -2
  51. package/dist/redirect-DUAk-Yl_.mjs.map +1 -0
  52. package/dist/{registry-1EvbAfsC.mjs → registry-DU18yVo0.mjs} +9 -3
  53. package/dist/registry-DU18yVo0.mjs.map +1 -0
  54. package/dist/{runner-BoN0-FPi.mjs → runner-Biufrii2.mjs} +3 -3
  55. package/dist/{runner-BoN0-FPi.mjs.map → runner-Biufrii2.mjs.map} +1 -1
  56. package/dist/{runner-DTqkzOzc.d.mts → runner-EAtf0ZIe.d.mts} +2 -2
  57. package/dist/{runner-DTqkzOzc.d.mts.map → runner-EAtf0ZIe.d.mts.map} +1 -1
  58. package/dist/runtime.d.mts +6 -6
  59. package/dist/runtime.mjs +2 -2
  60. package/dist/{search-BsYMed12.mjs → search-BXB-jfu2.mjs} +13 -11
  61. package/dist/search-BXB-jfu2.mjs.map +1 -0
  62. package/dist/seed/index.d.mts +2 -2
  63. package/dist/seed/index.mjs +7 -7
  64. package/dist/seo/index.d.mts +1 -1
  65. package/dist/storage/local.d.mts +1 -1
  66. package/dist/storage/s3.d.mts +11 -3
  67. package/dist/storage/s3.d.mts.map +1 -1
  68. package/dist/storage/s3.mjs +75 -14
  69. package/dist/storage/s3.mjs.map +1 -1
  70. package/dist/{transport-COOs9GSE.d.mts → transport-BFGblqwG.d.mts} +1 -1
  71. package/dist/{transport-COOs9GSE.d.mts.map → transport-BFGblqwG.d.mts.map} +1 -1
  72. package/dist/{transport-Bl8cTdYt.mjs → transport-yxiQsi8I.mjs} +1 -1
  73. package/dist/{transport-Bl8cTdYt.mjs.map → transport-yxiQsi8I.mjs.map} +1 -1
  74. package/dist/{types-CIsTnQvJ.d.mts → types-BbsYgi_R.d.mts} +1 -1
  75. package/dist/{types-CIsTnQvJ.d.mts.map → types-BbsYgi_R.d.mts.map} +1 -1
  76. package/dist/types-Bec-r_3_.mjs.map +1 -1
  77. package/dist/{types-BljtYPSd.d.mts → types-C1-PVaS_.d.mts} +14 -6
  78. package/dist/types-C1-PVaS_.d.mts.map +1 -0
  79. package/dist/{types-6dqxBqsH.d.mts → types-CaKte3hR.d.mts} +102 -4
  80. package/dist/types-CaKte3hR.d.mts.map +1 -0
  81. package/dist/{types-CcreFIIH.d.mts → types-DPfzHnjW.d.mts} +1 -1
  82. package/dist/{types-CcreFIIH.d.mts.map → types-DPfzHnjW.d.mts.map} +1 -1
  83. package/dist/{types-7-UjSEyB.d.mts → types-DRjfYOEv.d.mts} +1 -1
  84. package/dist/{types-7-UjSEyB.d.mts.map → types-DRjfYOEv.d.mts.map} +1 -1
  85. package/dist/{validate-CqRJb_xU.mjs → validate-VPnKoIzW.mjs} +11 -11
  86. package/dist/{validate-CqRJb_xU.mjs.map → validate-VPnKoIzW.mjs.map} +1 -1
  87. package/dist/{validate-B7KP7VLM.d.mts → validate-bfg9OR6N.d.mts} +4 -4
  88. package/dist/{validate-B7KP7VLM.d.mts.map → validate-bfg9OR6N.d.mts.map} +1 -1
  89. package/dist/version-REAapfsU.mjs +7 -0
  90. package/dist/version-REAapfsU.mjs.map +1 -0
  91. package/package.json +5 -5
  92. package/src/api/handlers/redirects.ts +95 -3
  93. package/src/api/schemas/redirects.ts +1 -0
  94. package/src/astro/integration/vite-config.ts +7 -4
  95. package/src/astro/routes/admin.astro +2 -2
  96. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +2 -0
  97. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +2 -0
  98. package/src/astro/routes/api/manifest.ts +3 -1
  99. package/src/astro/storage/adapters.ts +19 -5
  100. package/src/astro/storage/types.ts +12 -4
  101. package/src/astro/types.ts +1 -0
  102. package/src/bylines/index.ts +2 -2
  103. package/src/database/dialect-helpers.ts +3 -0
  104. package/src/database/repositories/content.ts +5 -0
  105. package/src/database/repositories/redirect.ts +13 -0
  106. package/src/database/validate.ts +10 -10
  107. package/src/emdash-runtime.ts +5 -1
  108. package/src/index.ts +1 -0
  109. package/src/loader.ts +2 -0
  110. package/src/menus/index.ts +4 -0
  111. package/src/redirects/loops.ts +318 -0
  112. package/src/schema/registry.ts +3 -0
  113. package/src/search/fts-manager.ts +4 -0
  114. package/src/storage/s3.ts +94 -25
  115. package/src/storage/types.ts +13 -5
  116. package/src/utils/slugify.ts +11 -0
  117. package/src/version.ts +12 -0
  118. package/dist/content-BmXndhdi.mjs.map +0 -1
  119. package/dist/dialect-helpers-B9uSp2GJ.mjs.map +0 -1
  120. package/dist/index-UHEVQMus.d.mts.map +0 -1
  121. package/dist/loader-CHb2v0jm.mjs.map +0 -1
  122. package/dist/redirect-DIfIni3r.mjs.map +0 -1
  123. package/dist/registry-1EvbAfsC.mjs.map +0 -1
  124. package/dist/search-BsYMed12.mjs.map +0 -1
  125. package/dist/types-6dqxBqsH.d.mts.map +0 -1
  126. package/dist/types-BljtYPSd.d.mts.map +0 -1
@@ -1,3 +1,3 @@
1
- import "../types-7-UjSEyB.mjs";
2
- import { _ as SeedTaxonomyTerm, a as applySeed, b as ValidationResult, c as SeedCollection, d as SeedFile, f as SeedMenu, g as SeedTaxonomy, h as SeedSection, i as defaultSeed, l as SeedContentEntry, m as SeedRedirect, n as loadSeed, o as SeedApplyOptions, p as SeedMenuItem, r as loadUserSeed, s as SeedApplyResult, t as validateSeed, u as SeedField, v as SeedWidget, y as SeedWidgetArea } from "../validate-B7KP7VLM.mjs";
1
+ import "../types-DRjfYOEv.mjs";
2
+ import { _ as SeedTaxonomyTerm, a as applySeed, b as ValidationResult, c as SeedCollection, d as SeedFile, f as SeedMenu, g as SeedTaxonomy, h as SeedSection, i as defaultSeed, l as SeedContentEntry, m as SeedRedirect, n as loadSeed, o as SeedApplyOptions, p as SeedMenuItem, r as loadUserSeed, s as SeedApplyResult, t as validateSeed, u as SeedField, v as SeedWidget, y as SeedWidgetArea } from "../validate-bfg9OR6N.mjs";
3
3
  export { type SeedApplyOptions, type SeedApplyResult, type SeedCollection, type SeedContentEntry, type SeedField, type SeedFile, type SeedMenu, type SeedMenuItem, type SeedRedirect, type SeedSection, type SeedTaxonomy, type SeedTaxonomyTerm, type SeedWidget, type SeedWidgetArea, type ValidationResult, applySeed, defaultSeed, loadSeed, loadUserSeed, validateSeed };
@@ -1,13 +1,13 @@
1
- import "../dialect-helpers-B9uSp2GJ.mjs";
2
- import "../content-BmXndhdi.mjs";
1
+ import "../dialect-helpers-DhTzaUxP.mjs";
2
+ import "../content-BsBoyj8G.mjs";
3
3
  import "../base64-MBPo9ozB.mjs";
4
4
  import "../types-CMMN0pNg.mjs";
5
5
  import "../media-DqHVh136.mjs";
6
- import { t as applySeed } from "../apply-wmVEOSbR.mjs";
7
- import "../registry-1EvbAfsC.mjs";
8
- import "../redirect-DIfIni3r.mjs";
9
- import "../byline-1WQPlISL.mjs";
10
- import "../loader-CHb2v0jm.mjs";
6
+ import { t as applySeed } from "../apply-Bqoekfbe.mjs";
7
+ import "../registry-DU18yVo0.mjs";
8
+ import "../redirect-DUAk-Yl_.mjs";
9
+ import "../byline-BGj9p9Ht.mjs";
10
+ import "../loader-BmYdf3Dr.mjs";
11
11
  import { t as validateSeed } from "../validate-CXnRKfJK.mjs";
12
12
  import { t as defaultSeed } from "../default-WYlzADZL.mjs";
13
13
  import { n as loadUserSeed, t as loadSeed } from "../load-Veizk2cT.mjs";
@@ -1,4 +1,4 @@
1
- import { i as ContentSeo } from "../types-CIsTnQvJ.mjs";
1
+ import { i as ContentSeo } from "../types-BbsYgi_R.mjs";
2
2
 
3
3
  //#region src/seo/index.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { a as ListOptions, d as Storage, l as SignedUploadOptions, n as DownloadResult, o as ListResult, p as UploadResult, s as LocalStorageConfig, u as SignedUploadUrl } from "../types-BljtYPSd.mjs";
1
+ import { a as ListOptions, d as Storage, l as SignedUploadOptions, n as DownloadResult, o as ListResult, p as UploadResult, s as LocalStorageConfig, u as SignedUploadUrl } from "../types-C1-PVaS_.mjs";
2
2
 
3
3
  //#region src/storage/local.d.ts
4
4
  /**
@@ -1,6 +1,13 @@
1
- import { a as ListOptions, c as S3StorageConfig, d as Storage, l as SignedUploadOptions, n as DownloadResult, o as ListResult, p as UploadResult, u as SignedUploadUrl } from "../types-BljtYPSd.mjs";
1
+ import { a as ListOptions, c as S3StorageConfig, d as Storage, l as SignedUploadOptions, n as DownloadResult, o as ListResult, p as UploadResult, u as SignedUploadUrl } from "../types-C1-PVaS_.mjs";
2
2
 
3
3
  //#region src/storage/s3.d.ts
4
+ /**
5
+ * Build the merged config: for each field, use the explicit value if present,
6
+ * otherwise fall back to the corresponding S3_* env var. Validate once on the
7
+ * final merged result so a malformed env var never breaks the build when the
8
+ * caller provides that field explicitly.
9
+ */
10
+ declare function resolveS3Config(partial: Record<string, unknown>): S3StorageConfig;
4
11
  /**
5
12
  * S3-compatible storage implementation
6
13
  */
@@ -24,9 +31,10 @@ declare class S3Storage implements Storage {
24
31
  }
25
32
  /**
26
33
  * Create S3 storage adapter
27
- * This is the factory function called at runtime
34
+ * This is the factory function called at runtime.
35
+ * Config fields are merged with S3_* env vars; env vars fill in any missing fields.
28
36
  */
29
37
  declare function createStorage(config: Record<string, unknown>): Storage;
30
38
  //#endregion
31
- export { S3Storage, createStorage };
39
+ export { S3Storage, createStorage, resolveS3Config };
32
40
  //# sourceMappingURL=s3.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"s3.d.mts","names":[],"sources":["../../src/storage/s3.ts"],"mappings":";;;;;;cAwCa,SAAA,YAAqB,OAAA;EAAA,QACzB,MAAA;EAAA,QACA,MAAA;EAAA,QACA,SAAA;EAAA,QACA,QAAA;cAEI,MAAA,EAAQ,eAAA;EAiBd,MAAA,CAAO,OAAA;IACZ,GAAA;IACA,IAAA,EAAM,MAAA,GAAS,UAAA,GAAa,cAAA,CAAe,UAAA;IAC3C,WAAA;EAAA,IACG,OAAA,CAAQ,YAAA;EAoCN,QAAA,CAAS,GAAA,WAAc,OAAA,CAAQ,cAAA;EAgC/B,MAAA,CAAO,GAAA,WAAc,OAAA;EAgBrB,MAAA,CAAO,GAAA,WAAc,OAAA;EAiBrB,IAAA,CAAK,OAAA,GAAS,WAAA,GAAmB,OAAA,CAAQ,UAAA;EA4BzC,kBAAA,CAAmB,OAAA,EAAS,mBAAA,GAAsB,OAAA,CAAQ,eAAA;EAiChE,YAAA,CAAa,GAAA;AAAA;;;;;iBAaE,aAAA,CAAc,MAAA,EAAQ,MAAA,oBAA0B,OAAA"}
1
+ {"version":3,"file":"s3.d.mts","names":[],"sources":["../../src/storage/s3.ts"],"mappings":";;;;;;;;AA0HA;iBA3DgB,eAAA,CAAgB,OAAA,EAAS,MAAA,oBAA0B,eAAA;;;;cA2DtD,SAAA,YAAqB,OAAA;EAAA,QACzB,MAAA;EAAA,QACA,MAAA;EAAA,QACA,SAAA;EAAA,QACA,QAAA;cAEI,MAAA,EAAQ,eAAA;EAqBd,MAAA,CAAO,OAAA;IACZ,GAAA;IACA,IAAA,EAAM,MAAA,GAAS,UAAA,GAAa,cAAA,CAAe,UAAA;IAC3C,WAAA;EAAA,IACG,OAAA,CAAQ,YAAA;EAoCN,QAAA,CAAS,GAAA,WAAc,OAAA,CAAQ,cAAA;EAgC/B,MAAA,CAAO,GAAA,WAAc,OAAA;EAgBrB,MAAA,CAAO,GAAA,WAAc,OAAA;EAiBrB,IAAA,CAAK,OAAA,GAAS,WAAA,GAAmB,OAAA,CAAQ,UAAA;EA4BzC,kBAAA,CAAmB,OAAA,EAAS,mBAAA,GAAsB,OAAA,CAAQ,eAAA;EAiChE,YAAA,CAAa,GAAA;AAAA;;;;;;iBAcE,aAAA,CAAc,MAAA,EAAQ,MAAA,oBAA0B,OAAA"}
@@ -1,4 +1,5 @@
1
1
  import { t as EmDashStorageError } from "../types-Bec-r_3_.mjs";
2
+ import { z } from "zod";
2
3
  import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
3
4
  import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
4
5
 
@@ -9,6 +10,74 @@ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
9
10
  * Uses the AWS SDK v3 for S3 operations.
10
11
  * Works with AWS S3, Cloudflare R2, Minio, and other S3-compatible services.
11
12
  */
13
+ const ENV_KEYS = {
14
+ endpoint: "S3_ENDPOINT",
15
+ bucket: "S3_BUCKET",
16
+ accessKeyId: "S3_ACCESS_KEY_ID",
17
+ secretAccessKey: "S3_SECRET_ACCESS_KEY",
18
+ region: "S3_REGION",
19
+ publicUrl: "S3_PUBLIC_URL"
20
+ };
21
+ function fail(msg) {
22
+ throw new EmDashStorageError(msg, "MISSING_S3_CONFIG");
23
+ }
24
+ const s3ConfigSchema = z.object({
25
+ endpoint: z.url({
26
+ protocol: /^https?$/,
27
+ error: "is not a valid http/https URL"
28
+ }).optional(),
29
+ bucket: z.string().optional(),
30
+ accessKeyId: z.string().optional(),
31
+ secretAccessKey: z.string().optional(),
32
+ region: z.string().optional(),
33
+ publicUrl: z.string().optional()
34
+ });
35
+ function isConfigKey(key) {
36
+ return typeof key === "string" && key in ENV_KEYS;
37
+ }
38
+ /**
39
+ * Build the merged config: for each field, use the explicit value if present,
40
+ * otherwise fall back to the corresponding S3_* env var. Validate once on the
41
+ * final merged result so a malformed env var never breaks the build when the
42
+ * caller provides that field explicitly.
43
+ */
44
+ function resolveS3Config(partial) {
45
+ const raw = {};
46
+ for (const [field, envKey] of Object.entries(ENV_KEYS)) {
47
+ const explicit = partial[field];
48
+ if (explicit !== void 0 && explicit !== "") {
49
+ raw[field] = explicit;
50
+ continue;
51
+ }
52
+ const envVal = typeof process !== "undefined" && process.env ? process.env[envKey] : void 0;
53
+ if (envVal !== void 0 && envVal !== "") raw[field] = envVal;
54
+ }
55
+ const result = s3ConfigSchema.safeParse(raw);
56
+ if (!result.success) {
57
+ const issue = result.error.issues[0];
58
+ const pathKey = issue?.path[0];
59
+ if (!issue || !isConfigKey(pathKey)) fail("S3 config validation failed");
60
+ fail(`${partial[pathKey] !== void 0 && partial[pathKey] !== "" ? `s3({ ${pathKey} })` : ENV_KEYS[pathKey]} ${issue.message}`);
61
+ }
62
+ const merged = result.data;
63
+ const endpoint = merged.endpoint;
64
+ const bucket = merged.bucket;
65
+ if (!endpoint || !bucket) {
66
+ const missing = [];
67
+ if (!endpoint) missing.push(`endpoint: set ${ENV_KEYS.endpoint} or pass endpoint to s3({...})`);
68
+ if (!bucket) missing.push(`bucket: set ${ENV_KEYS.bucket} or pass bucket to s3({...})`);
69
+ fail(`missing required S3 config: ${missing.join("; ")}`);
70
+ }
71
+ const accessKeyId = merged.accessKeyId;
72
+ const secretAccessKey = merged.secretAccessKey;
73
+ if (accessKeyId && !secretAccessKey) fail(`S3 credentials incomplete: accessKeyId is set but secretAccessKey is missing (set ${ENV_KEYS.secretAccessKey} or pass secretAccessKey to s3({...}))`);
74
+ if (secretAccessKey && !accessKeyId) fail(`S3 credentials incomplete: secretAccessKey is set but accessKeyId is missing (set ${ENV_KEYS.accessKeyId} or pass accessKeyId to s3({...}))`);
75
+ return {
76
+ ...merged,
77
+ endpoint,
78
+ bucket
79
+ };
80
+ }
12
81
  const TRAILING_SLASH_PATTERN = /\/$/;
13
82
  /** Type guard for AWS SDK errors (have a `name` property) */
14
83
  function hasErrorName(error) {
@@ -29,10 +98,10 @@ var S3Storage = class {
29
98
  this.client = new S3Client({
30
99
  endpoint: config.endpoint,
31
100
  region: config.region || "auto",
32
- credentials: {
101
+ ...config.accessKeyId && config.secretAccessKey ? { credentials: {
33
102
  accessKeyId: config.accessKeyId,
34
103
  secretAccessKey: config.secretAccessKey
35
- },
104
+ } } : {},
36
105
  forcePathStyle: true
37
106
  });
38
107
  }
@@ -155,21 +224,13 @@ var S3Storage = class {
155
224
  };
156
225
  /**
157
226
  * Create S3 storage adapter
158
- * This is the factory function called at runtime
227
+ * This is the factory function called at runtime.
228
+ * Config fields are merged with S3_* env vars; env vars fill in any missing fields.
159
229
  */
160
230
  function createStorage(config) {
161
- const { endpoint, bucket, accessKeyId, secretAccessKey, region, publicUrl } = config;
162
- if (typeof endpoint !== "string" || typeof bucket !== "string" || typeof accessKeyId !== "string" || typeof secretAccessKey !== "string") throw new Error("S3Storage requires 'endpoint', 'bucket', 'accessKeyId', and 'secretAccessKey' string config values");
163
- return new S3Storage({
164
- endpoint,
165
- bucket,
166
- accessKeyId,
167
- secretAccessKey,
168
- region: typeof region === "string" ? region : void 0,
169
- publicUrl: typeof publicUrl === "string" ? publicUrl : void 0
170
- });
231
+ return new S3Storage(resolveS3Config(config));
171
232
  }
172
233
 
173
234
  //#endregion
174
- export { S3Storage, createStorage };
235
+ export { S3Storage, createStorage, resolveS3Config };
175
236
  //# sourceMappingURL=s3.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"s3.mjs","names":[],"sources":["../../src/storage/s3.ts"],"sourcesContent":["/**\n * S3-Compatible Storage Implementation\n *\n * Uses the AWS SDK v3 for S3 operations.\n * Works with AWS S3, Cloudflare R2, Minio, and other S3-compatible services.\n */\n\nimport {\n\tS3Client,\n\tPutObjectCommand,\n\tGetObjectCommand,\n\tDeleteObjectCommand,\n\tHeadObjectCommand,\n\tListObjectsV2Command,\n\ttype ListObjectsV2Response,\n} from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\n\nimport type {\n\tStorage,\n\tS3StorageConfig,\n\tUploadResult,\n\tDownloadResult,\n\tListResult,\n\tListOptions,\n\tSignedUploadUrl,\n\tSignedUploadOptions,\n} from \"./types.js\";\nimport { EmDashStorageError } from \"./types.js\";\n\nconst TRAILING_SLASH_PATTERN = /\\/$/;\n\n/** Type guard for AWS SDK errors (have a `name` property) */\nfunction hasErrorName(error: unknown): error is Error & { name: string } {\n\treturn error instanceof Error && typeof error.name === \"string\";\n}\n\n/**\n * S3-compatible storage implementation\n */\nexport class S3Storage implements Storage {\n\tprivate client: S3Client;\n\tprivate bucket: string;\n\tprivate publicUrl?: string;\n\tprivate endpoint: string;\n\n\tconstructor(config: S3StorageConfig) {\n\t\tthis.bucket = config.bucket;\n\t\tthis.publicUrl = config.publicUrl;\n\t\tthis.endpoint = config.endpoint;\n\n\t\tthis.client = new S3Client({\n\t\t\tendpoint: config.endpoint,\n\t\t\tregion: config.region || \"auto\",\n\t\t\tcredentials: {\n\t\t\t\taccessKeyId: config.accessKeyId,\n\t\t\t\tsecretAccessKey: config.secretAccessKey,\n\t\t\t},\n\t\t\t// Required for R2 and some S3-compatible services\n\t\t\tforcePathStyle: true,\n\t\t});\n\t}\n\n\tasync upload(options: {\n\t\tkey: string;\n\t\tbody: Buffer | Uint8Array | ReadableStream<Uint8Array>;\n\t\tcontentType: string;\n\t}): Promise<UploadResult> {\n\t\ttry {\n\t\t\t// Convert ReadableStream to Buffer if needed\n\t\t\tlet body: Buffer | Uint8Array;\n\t\t\tif (options.body instanceof ReadableStream) {\n\t\t\t\tconst chunks: Uint8Array[] = [];\n\t\t\t\tconst reader = options.body.getReader();\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\tif (done) break;\n\t\t\t\t\tchunks.push(value);\n\t\t\t\t}\n\t\t\t\tbody = Buffer.concat(chunks);\n\t\t\t} else {\n\t\t\t\tbody = options.body;\n\t\t\t}\n\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: options.key,\n\t\t\t\t\tBody: body,\n\t\t\t\t\tContentType: options.contentType,\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\treturn {\n\t\t\t\tkey: options.key,\n\t\t\t\turl: this.getPublicUrl(options.key),\n\t\t\t\tsize: body.length,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new EmDashStorageError(`Failed to upload file: ${options.key}`, \"UPLOAD_FAILED\", error);\n\t\t}\n\t}\n\n\tasync download(key: string): Promise<DownloadResult> {\n\t\ttry {\n\t\t\tconst response = await this.client.send(\n\t\t\t\tnew GetObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: key,\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\tif (!response.Body) {\n\t\t\t\tthrow new EmDashStorageError(`File not found: ${key}`, \"NOT_FOUND\");\n\t\t\t}\n\n\t\t\t// Convert SDK stream to web ReadableStream\n\t\t\tconst body = response.Body.transformToWebStream();\n\n\t\t\treturn {\n\t\t\t\tbody,\n\t\t\t\tcontentType: response.ContentType || \"application/octet-stream\",\n\t\t\t\tsize: response.ContentLength || 0,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tif (\n\t\t\t\terror instanceof EmDashStorageError ||\n\t\t\t\t(hasErrorName(error) && error.name === \"NoSuchKey\")\n\t\t\t) {\n\t\t\t\tthrow new EmDashStorageError(`File not found: ${key}`, \"NOT_FOUND\", error);\n\t\t\t}\n\t\t\tthrow new EmDashStorageError(`Failed to download file: ${key}`, \"DOWNLOAD_FAILED\", error);\n\t\t}\n\t}\n\n\tasync delete(key: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew DeleteObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: key,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// S3 delete is idempotent, so we ignore \"not found\" errors\n\t\t\tif (!hasErrorName(error) || error.name !== \"NoSuchKey\") {\n\t\t\t\tthrow new EmDashStorageError(`Failed to delete file: ${key}`, \"DELETE_FAILED\", error);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync exists(key: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew HeadObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: key,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn true;\n\t\t} catch (error) {\n\t\t\tif (hasErrorName(error) && error.name === \"NotFound\") {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tthrow new EmDashStorageError(`Failed to check file existence: ${key}`, \"HEAD_FAILED\", error);\n\t\t}\n\t}\n\n\tasync list(options: ListOptions = {}): Promise<ListResult> {\n\t\ttry {\n\t\t\t// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- S3 client.send returns generic output; narrowing to ListObjectsV2Response\n\t\t\tconst response = (await this.client.send(\n\t\t\t\tnew ListObjectsV2Command({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tPrefix: options.prefix,\n\t\t\t\t\tMaxKeys: options.limit,\n\t\t\t\t\tContinuationToken: options.cursor,\n\t\t\t\t}),\n\t\t\t)) as ListObjectsV2Response;\n\n\t\t\treturn {\n\t\t\t\tfiles: (response.Contents || []).map(\n\t\t\t\t\t(item: { Key?: string; Size?: number; LastModified?: Date; ETag?: string }) => ({\n\t\t\t\t\t\tkey: item.Key!,\n\t\t\t\t\t\tsize: item.Size || 0,\n\t\t\t\t\t\tlastModified: item.LastModified || new Date(),\n\t\t\t\t\t\tetag: item.ETag,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t\tnextCursor: response.NextContinuationToken,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new EmDashStorageError(\"Failed to list files\", \"LIST_FAILED\", error);\n\t\t}\n\t}\n\n\tasync getSignedUploadUrl(options: SignedUploadOptions): Promise<SignedUploadUrl> {\n\t\ttry {\n\t\t\tconst expiresIn = options.expiresIn || 3600; // 1 hour default\n\n\t\t\tconst command = new PutObjectCommand({\n\t\t\t\tBucket: this.bucket,\n\t\t\t\tKey: options.key,\n\t\t\t\tContentType: options.contentType,\n\t\t\t\tContentLength: options.size,\n\t\t\t});\n\n\t\t\tconst url = await getSignedUrl(this.client, command, { expiresIn });\n\n\t\t\tconst expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString();\n\n\t\t\treturn {\n\t\t\t\turl,\n\t\t\t\tmethod: \"PUT\",\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": options.contentType,\n\t\t\t\t\t...(options.size ? { \"Content-Length\": String(options.size) } : {}),\n\t\t\t\t},\n\t\t\t\texpiresAt,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new EmDashStorageError(\n\t\t\t\t`Failed to generate signed URL for: ${options.key}`,\n\t\t\t\t\"SIGNED_URL_FAILED\",\n\t\t\t\terror,\n\t\t\t);\n\t\t}\n\t}\n\n\tgetPublicUrl(key: string): string {\n\t\tif (this.publicUrl) {\n\t\t\treturn `${this.publicUrl.replace(TRAILING_SLASH_PATTERN, \"\")}/${key}`;\n\t\t}\n\t\t// Default to endpoint + bucket + key\n\t\treturn `${this.endpoint.replace(TRAILING_SLASH_PATTERN, \"\")}/${this.bucket}/${key}`;\n\t}\n}\n\n/**\n * Create S3 storage adapter\n * This is the factory function called at runtime\n */\nexport function createStorage(config: Record<string, unknown>): Storage {\n\tconst { endpoint, bucket, accessKeyId, secretAccessKey, region, publicUrl } = config;\n\tif (\n\t\ttypeof endpoint !== \"string\" ||\n\t\ttypeof bucket !== \"string\" ||\n\t\ttypeof accessKeyId !== \"string\" ||\n\t\ttypeof secretAccessKey !== \"string\"\n\t) {\n\t\tthrow new Error(\n\t\t\t\"S3Storage requires 'endpoint', 'bucket', 'accessKeyId', and 'secretAccessKey' string config values\",\n\t\t);\n\t}\n\treturn new S3Storage({\n\t\tendpoint,\n\t\tbucket,\n\t\taccessKeyId,\n\t\tsecretAccessKey,\n\t\tregion: typeof region === \"string\" ? region : undefined,\n\t\tpublicUrl: typeof publicUrl === \"string\" ? publicUrl : undefined,\n\t});\n}\n"],"mappings":";;;;;;;;;;;AA8BA,MAAM,yBAAyB;;AAG/B,SAAS,aAAa,OAAmD;AACxE,QAAO,iBAAiB,SAAS,OAAO,MAAM,SAAS;;;;;AAMxD,IAAa,YAAb,MAA0C;CACzC,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,YAAY,QAAyB;AACpC,OAAK,SAAS,OAAO;AACrB,OAAK,YAAY,OAAO;AACxB,OAAK,WAAW,OAAO;AAEvB,OAAK,SAAS,IAAI,SAAS;GAC1B,UAAU,OAAO;GACjB,QAAQ,OAAO,UAAU;GACzB,aAAa;IACZ,aAAa,OAAO;IACpB,iBAAiB,OAAO;IACxB;GAED,gBAAgB;GAChB,CAAC;;CAGH,MAAM,OAAO,SAIa;AACzB,MAAI;GAEH,IAAI;AACJ,OAAI,QAAQ,gBAAgB,gBAAgB;IAC3C,MAAM,SAAuB,EAAE;IAC/B,MAAM,SAAS,QAAQ,KAAK,WAAW;AACvC,WAAO,MAAM;KACZ,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,MAAM;AAC3C,SAAI,KAAM;AACV,YAAO,KAAK,MAAM;;AAEnB,WAAO,OAAO,OAAO,OAAO;SAE5B,QAAO,QAAQ;AAGhB,SAAM,KAAK,OAAO,KACjB,IAAI,iBAAiB;IACpB,QAAQ,KAAK;IACb,KAAK,QAAQ;IACb,MAAM;IACN,aAAa,QAAQ;IACrB,CAAC,CACF;AAED,UAAO;IACN,KAAK,QAAQ;IACb,KAAK,KAAK,aAAa,QAAQ,IAAI;IACnC,MAAM,KAAK;IACX;WACO,OAAO;AACf,SAAM,IAAI,mBAAmB,0BAA0B,QAAQ,OAAO,iBAAiB,MAAM;;;CAI/F,MAAM,SAAS,KAAsC;AACpD,MAAI;GACH,MAAM,WAAW,MAAM,KAAK,OAAO,KAClC,IAAI,iBAAiB;IACpB,QAAQ,KAAK;IACb,KAAK;IACL,CAAC,CACF;AAED,OAAI,CAAC,SAAS,KACb,OAAM,IAAI,mBAAmB,mBAAmB,OAAO,YAAY;AAMpE,UAAO;IACN,MAHY,SAAS,KAAK,sBAAsB;IAIhD,aAAa,SAAS,eAAe;IACrC,MAAM,SAAS,iBAAiB;IAChC;WACO,OAAO;AACf,OACC,iBAAiB,sBAChB,aAAa,MAAM,IAAI,MAAM,SAAS,YAEvC,OAAM,IAAI,mBAAmB,mBAAmB,OAAO,aAAa,MAAM;AAE3E,SAAM,IAAI,mBAAmB,4BAA4B,OAAO,mBAAmB,MAAM;;;CAI3F,MAAM,OAAO,KAA4B;AACxC,MAAI;AACH,SAAM,KAAK,OAAO,KACjB,IAAI,oBAAoB;IACvB,QAAQ,KAAK;IACb,KAAK;IACL,CAAC,CACF;WACO,OAAO;AAEf,OAAI,CAAC,aAAa,MAAM,IAAI,MAAM,SAAS,YAC1C,OAAM,IAAI,mBAAmB,0BAA0B,OAAO,iBAAiB,MAAM;;;CAKxF,MAAM,OAAO,KAA+B;AAC3C,MAAI;AACH,SAAM,KAAK,OAAO,KACjB,IAAI,kBAAkB;IACrB,QAAQ,KAAK;IACb,KAAK;IACL,CAAC,CACF;AACD,UAAO;WACC,OAAO;AACf,OAAI,aAAa,MAAM,IAAI,MAAM,SAAS,WACzC,QAAO;AAER,SAAM,IAAI,mBAAmB,mCAAmC,OAAO,eAAe,MAAM;;;CAI9F,MAAM,KAAK,UAAuB,EAAE,EAAuB;AAC1D,MAAI;GAEH,MAAM,WAAY,MAAM,KAAK,OAAO,KACnC,IAAI,qBAAqB;IACxB,QAAQ,KAAK;IACb,QAAQ,QAAQ;IAChB,SAAS,QAAQ;IACjB,mBAAmB,QAAQ;IAC3B,CAAC,CACF;AAED,UAAO;IACN,QAAQ,SAAS,YAAY,EAAE,EAAE,KAC/B,UAA+E;KAC/E,KAAK,KAAK;KACV,MAAM,KAAK,QAAQ;KACnB,cAAc,KAAK,gCAAgB,IAAI,MAAM;KAC7C,MAAM,KAAK;KACX,EACD;IACD,YAAY,SAAS;IACrB;WACO,OAAO;AACf,SAAM,IAAI,mBAAmB,wBAAwB,eAAe,MAAM;;;CAI5E,MAAM,mBAAmB,SAAwD;AAChF,MAAI;GACH,MAAM,YAAY,QAAQ,aAAa;GAEvC,MAAM,UAAU,IAAI,iBAAiB;IACpC,QAAQ,KAAK;IACb,KAAK,QAAQ;IACb,aAAa,QAAQ;IACrB,eAAe,QAAQ;IACvB,CAAC;GAEF,MAAM,MAAM,MAAM,aAAa,KAAK,QAAQ,SAAS,EAAE,WAAW,CAAC;GAEnE,MAAM,YAAY,IAAI,KAAK,KAAK,KAAK,GAAG,YAAY,IAAK,CAAC,aAAa;AAEvE,UAAO;IACN;IACA,QAAQ;IACR,SAAS;KACR,gBAAgB,QAAQ;KACxB,GAAI,QAAQ,OAAO,EAAE,kBAAkB,OAAO,QAAQ,KAAK,EAAE,GAAG,EAAE;KAClE;IACD;IACA;WACO,OAAO;AACf,SAAM,IAAI,mBACT,sCAAsC,QAAQ,OAC9C,qBACA,MACA;;;CAIH,aAAa,KAAqB;AACjC,MAAI,KAAK,UACR,QAAO,GAAG,KAAK,UAAU,QAAQ,wBAAwB,GAAG,CAAC,GAAG;AAGjE,SAAO,GAAG,KAAK,SAAS,QAAQ,wBAAwB,GAAG,CAAC,GAAG,KAAK,OAAO,GAAG;;;;;;;AAQhF,SAAgB,cAAc,QAA0C;CACvE,MAAM,EAAE,UAAU,QAAQ,aAAa,iBAAiB,QAAQ,cAAc;AAC9E,KACC,OAAO,aAAa,YACpB,OAAO,WAAW,YAClB,OAAO,gBAAgB,YACvB,OAAO,oBAAoB,SAE3B,OAAM,IAAI,MACT,qGACA;AAEF,QAAO,IAAI,UAAU;EACpB;EACA;EACA;EACA;EACA,QAAQ,OAAO,WAAW,WAAW,SAAS;EAC9C,WAAW,OAAO,cAAc,WAAW,YAAY;EACvD,CAAC"}
1
+ {"version":3,"file":"s3.mjs","names":[],"sources":["../../src/storage/s3.ts"],"sourcesContent":["/**\n * S3-Compatible Storage Implementation\n *\n * Uses the AWS SDK v3 for S3 operations.\n * Works with AWS S3, Cloudflare R2, Minio, and other S3-compatible services.\n */\n\nimport {\n\tS3Client,\n\tPutObjectCommand,\n\tGetObjectCommand,\n\tDeleteObjectCommand,\n\tHeadObjectCommand,\n\tListObjectsV2Command,\n\ttype ListObjectsV2Response,\n} from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport { z } from \"zod\";\n\nimport type {\n\tStorage,\n\tS3StorageConfig,\n\tUploadResult,\n\tDownloadResult,\n\tListResult,\n\tListOptions,\n\tSignedUploadUrl,\n\tSignedUploadOptions,\n} from \"./types.js\";\nimport { EmDashStorageError } from \"./types.js\";\n\nconst ENV_KEYS = {\n\tendpoint: \"S3_ENDPOINT\",\n\tbucket: \"S3_BUCKET\",\n\taccessKeyId: \"S3_ACCESS_KEY_ID\",\n\tsecretAccessKey: \"S3_SECRET_ACCESS_KEY\",\n\tregion: \"S3_REGION\",\n\tpublicUrl: \"S3_PUBLIC_URL\",\n} as const satisfies Record<keyof S3StorageConfig, string>;\n\nfunction fail(msg: string): never {\n\tthrow new EmDashStorageError(msg, \"MISSING_S3_CONFIG\");\n}\n\nconst s3ConfigSchema = z.object({\n\tendpoint: z.url({ protocol: /^https?$/, error: \"is not a valid http/https URL\" }).optional(),\n\tbucket: z.string().optional(),\n\taccessKeyId: z.string().optional(),\n\tsecretAccessKey: z.string().optional(),\n\tregion: z.string().optional(),\n\tpublicUrl: z.string().optional(),\n});\n\nfunction isConfigKey(key: unknown): key is keyof S3StorageConfig {\n\treturn typeof key === \"string\" && key in ENV_KEYS;\n}\n\n/**\n * Build the merged config: for each field, use the explicit value if present,\n * otherwise fall back to the corresponding S3_* env var. Validate once on the\n * final merged result so a malformed env var never breaks the build when the\n * caller provides that field explicitly.\n */\nexport function resolveS3Config(partial: Record<string, unknown>): S3StorageConfig {\n\tconst raw: Record<string, unknown> = {};\n\tfor (const [field, envKey] of Object.entries(ENV_KEYS)) {\n\t\tconst explicit = partial[field];\n\t\tif (explicit !== undefined && explicit !== \"\") {\n\t\t\traw[field] = explicit;\n\t\t\tcontinue;\n\t\t}\n\t\tconst envVal = typeof process !== \"undefined\" && process.env ? process.env[envKey] : undefined;\n\t\tif (envVal !== undefined && envVal !== \"\") {\n\t\t\traw[field] = envVal;\n\t\t}\n\t}\n\n\tconst result = s3ConfigSchema.safeParse(raw);\n\tif (!result.success) {\n\t\tconst issue = result.error.issues[0];\n\t\tconst pathKey = issue?.path[0];\n\t\tif (!issue || !isConfigKey(pathKey)) fail(\"S3 config validation failed\");\n\t\tconst fromExplicit = partial[pathKey] !== undefined && partial[pathKey] !== \"\";\n\t\tconst label = fromExplicit ? `s3({ ${pathKey} })` : ENV_KEYS[pathKey];\n\t\tfail(`${label} ${issue.message}`);\n\t}\n\tconst merged = result.data;\n\n\tconst endpoint = merged.endpoint;\n\tconst bucket = merged.bucket;\n\tif (!endpoint || !bucket) {\n\t\tconst missing: string[] = [];\n\t\tif (!endpoint) missing.push(`endpoint: set ${ENV_KEYS.endpoint} or pass endpoint to s3({...})`);\n\t\tif (!bucket) missing.push(`bucket: set ${ENV_KEYS.bucket} or pass bucket to s3({...})`);\n\t\tfail(`missing required S3 config: ${missing.join(\"; \")}`);\n\t}\n\tconst accessKeyId = merged.accessKeyId;\n\tconst secretAccessKey = merged.secretAccessKey;\n\tif (accessKeyId && !secretAccessKey) {\n\t\tfail(\n\t\t\t`S3 credentials incomplete: accessKeyId is set but secretAccessKey is missing (set ${ENV_KEYS.secretAccessKey} or pass secretAccessKey to s3({...}))`,\n\t\t);\n\t}\n\tif (secretAccessKey && !accessKeyId) {\n\t\tfail(\n\t\t\t`S3 credentials incomplete: secretAccessKey is set but accessKeyId is missing (set ${ENV_KEYS.accessKeyId} or pass accessKeyId to s3({...}))`,\n\t\t);\n\t}\n\n\treturn { ...merged, endpoint, bucket };\n}\n\nconst TRAILING_SLASH_PATTERN = /\\/$/;\n\n/** Type guard for AWS SDK errors (have a `name` property) */\nfunction hasErrorName(error: unknown): error is Error & { name: string } {\n\treturn error instanceof Error && typeof error.name === \"string\";\n}\n\n/**\n * S3-compatible storage implementation\n */\nexport class S3Storage implements Storage {\n\tprivate client: S3Client;\n\tprivate bucket: string;\n\tprivate publicUrl?: string;\n\tprivate endpoint: string;\n\n\tconstructor(config: S3StorageConfig) {\n\t\tthis.bucket = config.bucket;\n\t\tthis.publicUrl = config.publicUrl;\n\t\tthis.endpoint = config.endpoint;\n\n\t\tthis.client = new S3Client({\n\t\t\tendpoint: config.endpoint,\n\t\t\tregion: config.region || \"auto\",\n\t\t\t...(config.accessKeyId && config.secretAccessKey\n\t\t\t\t? {\n\t\t\t\t\t\tcredentials: {\n\t\t\t\t\t\t\taccessKeyId: config.accessKeyId,\n\t\t\t\t\t\t\tsecretAccessKey: config.secretAccessKey,\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t// Required for R2 and some S3-compatible services\n\t\t\tforcePathStyle: true,\n\t\t} as ConstructorParameters<typeof S3Client>[0]);\n\t}\n\n\tasync upload(options: {\n\t\tkey: string;\n\t\tbody: Buffer | Uint8Array | ReadableStream<Uint8Array>;\n\t\tcontentType: string;\n\t}): Promise<UploadResult> {\n\t\ttry {\n\t\t\t// Convert ReadableStream to Buffer if needed\n\t\t\tlet body: Buffer | Uint8Array;\n\t\t\tif (options.body instanceof ReadableStream) {\n\t\t\t\tconst chunks: Uint8Array[] = [];\n\t\t\t\tconst reader = options.body.getReader();\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\tif (done) break;\n\t\t\t\t\tchunks.push(value);\n\t\t\t\t}\n\t\t\t\tbody = Buffer.concat(chunks);\n\t\t\t} else {\n\t\t\t\tbody = options.body;\n\t\t\t}\n\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: options.key,\n\t\t\t\t\tBody: body,\n\t\t\t\t\tContentType: options.contentType,\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\treturn {\n\t\t\t\tkey: options.key,\n\t\t\t\turl: this.getPublicUrl(options.key),\n\t\t\t\tsize: body.length,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new EmDashStorageError(`Failed to upload file: ${options.key}`, \"UPLOAD_FAILED\", error);\n\t\t}\n\t}\n\n\tasync download(key: string): Promise<DownloadResult> {\n\t\ttry {\n\t\t\tconst response = await this.client.send(\n\t\t\t\tnew GetObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: key,\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\tif (!response.Body) {\n\t\t\t\tthrow new EmDashStorageError(`File not found: ${key}`, \"NOT_FOUND\");\n\t\t\t}\n\n\t\t\t// Convert SDK stream to web ReadableStream\n\t\t\tconst body = response.Body.transformToWebStream();\n\n\t\t\treturn {\n\t\t\t\tbody,\n\t\t\t\tcontentType: response.ContentType || \"application/octet-stream\",\n\t\t\t\tsize: response.ContentLength || 0,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tif (\n\t\t\t\terror instanceof EmDashStorageError ||\n\t\t\t\t(hasErrorName(error) && error.name === \"NoSuchKey\")\n\t\t\t) {\n\t\t\t\tthrow new EmDashStorageError(`File not found: ${key}`, \"NOT_FOUND\", error);\n\t\t\t}\n\t\t\tthrow new EmDashStorageError(`Failed to download file: ${key}`, \"DOWNLOAD_FAILED\", error);\n\t\t}\n\t}\n\n\tasync delete(key: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew DeleteObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: key,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// S3 delete is idempotent, so we ignore \"not found\" errors\n\t\t\tif (!hasErrorName(error) || error.name !== \"NoSuchKey\") {\n\t\t\t\tthrow new EmDashStorageError(`Failed to delete file: ${key}`, \"DELETE_FAILED\", error);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync exists(key: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew HeadObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: key,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn true;\n\t\t} catch (error) {\n\t\t\tif (hasErrorName(error) && error.name === \"NotFound\") {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tthrow new EmDashStorageError(`Failed to check file existence: ${key}`, \"HEAD_FAILED\", error);\n\t\t}\n\t}\n\n\tasync list(options: ListOptions = {}): Promise<ListResult> {\n\t\ttry {\n\t\t\t// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- S3 client.send returns generic output; narrowing to ListObjectsV2Response\n\t\t\tconst response = (await this.client.send(\n\t\t\t\tnew ListObjectsV2Command({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tPrefix: options.prefix,\n\t\t\t\t\tMaxKeys: options.limit,\n\t\t\t\t\tContinuationToken: options.cursor,\n\t\t\t\t}),\n\t\t\t)) as ListObjectsV2Response;\n\n\t\t\treturn {\n\t\t\t\tfiles: (response.Contents || []).map(\n\t\t\t\t\t(item: { Key?: string; Size?: number; LastModified?: Date; ETag?: string }) => ({\n\t\t\t\t\t\tkey: item.Key!,\n\t\t\t\t\t\tsize: item.Size || 0,\n\t\t\t\t\t\tlastModified: item.LastModified || new Date(),\n\t\t\t\t\t\tetag: item.ETag,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t\tnextCursor: response.NextContinuationToken,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new EmDashStorageError(\"Failed to list files\", \"LIST_FAILED\", error);\n\t\t}\n\t}\n\n\tasync getSignedUploadUrl(options: SignedUploadOptions): Promise<SignedUploadUrl> {\n\t\ttry {\n\t\t\tconst expiresIn = options.expiresIn || 3600; // 1 hour default\n\n\t\t\tconst command = new PutObjectCommand({\n\t\t\t\tBucket: this.bucket,\n\t\t\t\tKey: options.key,\n\t\t\t\tContentType: options.contentType,\n\t\t\t\tContentLength: options.size,\n\t\t\t});\n\n\t\t\tconst url = await getSignedUrl(this.client, command, { expiresIn });\n\n\t\t\tconst expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString();\n\n\t\t\treturn {\n\t\t\t\turl,\n\t\t\t\tmethod: \"PUT\",\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": options.contentType,\n\t\t\t\t\t...(options.size ? { \"Content-Length\": String(options.size) } : {}),\n\t\t\t\t},\n\t\t\t\texpiresAt,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tthrow new EmDashStorageError(\n\t\t\t\t`Failed to generate signed URL for: ${options.key}`,\n\t\t\t\t\"SIGNED_URL_FAILED\",\n\t\t\t\terror,\n\t\t\t);\n\t\t}\n\t}\n\n\tgetPublicUrl(key: string): string {\n\t\tif (this.publicUrl) {\n\t\t\treturn `${this.publicUrl.replace(TRAILING_SLASH_PATTERN, \"\")}/${key}`;\n\t\t}\n\t\t// Default to endpoint + bucket + key\n\t\treturn `${this.endpoint.replace(TRAILING_SLASH_PATTERN, \"\")}/${this.bucket}/${key}`;\n\t}\n}\n\n/**\n * Create S3 storage adapter\n * This is the factory function called at runtime.\n * Config fields are merged with S3_* env vars; env vars fill in any missing fields.\n */\nexport function createStorage(config: Record<string, unknown>): Storage {\n\treturn new S3Storage(resolveS3Config(config));\n}\n"],"mappings":"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"}
@@ -39,4 +39,4 @@ declare function tokenInterceptor(token: string): Interceptor;
39
39
  declare function devBypassInterceptor(baseUrl: string): Interceptor;
40
40
  //#endregion
41
41
  export { tokenInterceptor as a, devBypassInterceptor as i, createTransport as n, csrfInterceptor as r, Interceptor as t };
42
- //# sourceMappingURL=transport-COOs9GSE.d.mts.map
42
+ //# sourceMappingURL=transport-BFGblqwG.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transport-COOs9GSE.d.mts","names":[],"sources":["../src/client/transport.ts"],"mappings":";;AAeA;;;;;;;;;;KAAY,WAAA,IACX,OAAA,EAAS,OAAA,EACT,IAAA,GAAO,OAAA,EAAS,OAAA,KAAY,OAAA,CAAQ,QAAA,MAChC,OAAA,CAAQ,QAAA;AAAA,UAEI,gBAAA;EAChB,YAAA,GAAe,WAAA;AAAA;;;;iBAUA,eAAA,CAAgB,OAAA,GAAS,gBAAA;EACxC,KAAA,GAAQ,OAAA,EAAS,OAAA,KAAY,OAAA,CAAQ,QAAA;AAAA;;;AAZtC;;;;iBAqCgB,eAAA,CAAA,GAAmB,WAAA;AA1BnC;;;AAAA,iBA8CgB,gBAAA,CAAiB,KAAA,WAAgB,WAAA;;;;;;iBAajC,oBAAA,CAAqB,OAAA,WAAkB,WAAA"}
1
+ {"version":3,"file":"transport-BFGblqwG.d.mts","names":[],"sources":["../src/client/transport.ts"],"mappings":";;AAeA;;;;;;;;;;KAAY,WAAA,IACX,OAAA,EAAS,OAAA,EACT,IAAA,GAAO,OAAA,EAAS,OAAA,KAAY,OAAA,CAAQ,QAAA,MAChC,OAAA,CAAQ,QAAA;AAAA,UAEI,gBAAA;EAChB,YAAA,GAAe,WAAA;AAAA;;;;iBAUA,eAAA,CAAgB,OAAA,GAAS,gBAAA;EACxC,KAAA,GAAQ,OAAA,EAAS,OAAA,KAAY,OAAA,CAAQ,QAAA;AAAA;;;AAZtC;;;;iBAqCgB,eAAA,CAAA,GAAmB,WAAA;AA1BnC;;;AAAA,iBA8CgB,gBAAA,CAAiB,KAAA,WAAgB,WAAA;;;;;;iBAajC,oBAAA,CAAqB,OAAA,WAAkB,WAAA"}
@@ -415,4 +415,4 @@ function refreshInterceptor(options) {
415
415
 
416
416
  //#endregion
417
417
  export { tokenInterceptor as a, markdownToPortableText as c, refreshInterceptor as i, portableTextToMarkdown as l, csrfInterceptor as n, convertDataForRead as o, devBypassInterceptor as r, convertDataForWrite as s, createTransport as t };
418
- //# sourceMappingURL=transport-Bl8cTdYt.mjs.map
418
+ //# sourceMappingURL=transport-yxiQsi8I.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"transport-Bl8cTdYt.mjs","names":[],"sources":["../src/client/portable-text.ts","../src/client/transport.ts"],"sourcesContent":["/**\n * Portable Text <-> Markdown conversion layer.\n *\n * Three tiers of block handling:\n * Tier 1: Standard PT blocks <-> standard Markdown (headings, paragraphs, lists, etc.)\n * Tier 2: EmDash custom blocks <-> Markdown directives (future)\n * Tier 3: Unknown blocks <-> opaque HTML comment fences (preserved, not editable)\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Minimal Portable Text block shape */\nexport interface PortableTextBlock {\n\t_type: string;\n\t_key?: string;\n\tstyle?: string;\n\tlevel?: number;\n\tlistItem?: string;\n\tmarkDefs?: MarkDef[];\n\tchildren?: PortableTextSpan[];\n\t[key: string]: unknown;\n}\n\ninterface PortableTextSpan {\n\t_type: string;\n\t_key?: string;\n\ttext?: string;\n\tmarks?: string[];\n\t[key: string]: unknown;\n}\n\ninterface MarkDef {\n\t_key: string;\n\t_type: string;\n\thref?: string;\n\t[key: string]: unknown;\n}\n\ninterface ParsedInline {\n\tspans: PortableTextSpan[];\n\tmarkDefs: MarkDef[];\n}\n\n// ---------------------------------------------------------------------------\n// PT -> Markdown\n// ---------------------------------------------------------------------------\n\n/**\n * Convert Portable Text blocks to Markdown.\n * Unknown block types are serialized as opaque fences.\n */\nexport function portableTextToMarkdown(blocks: PortableTextBlock[]): string {\n\tconst lines: string[] = [];\n\tlet prevWasList = false;\n\n\tfor (let i = 0; i < blocks.length; i++) {\n\t\tconst block = blocks[i];\n\n\t\tif (block._type === \"block\") {\n\t\t\tconst isList = !!block.listItem;\n\n\t\t\t// Blank line between non-contiguous block types\n\t\t\tif (i > 0 && (!isList || !prevWasList)) {\n\t\t\t\tlines.push(\"\");\n\t\t\t}\n\n\t\t\tlines.push(renderStandardBlock(block));\n\t\t\tprevWasList = isList;\n\t\t} else if (block._type === \"code\") {\n\t\t\tif (i > 0) lines.push(\"\");\n\t\t\tconst lang = (block.language as string) || \"\";\n\t\t\tconst code = (block.code as string) || \"\";\n\t\t\tlines.push(\"```\" + lang);\n\t\t\tlines.push(code);\n\t\t\tlines.push(\"```\");\n\t\t\tprevWasList = false;\n\t\t} else if (block._type === \"image\") {\n\t\t\tif (i > 0) lines.push(\"\");\n\t\t\tconst alt = (block.alt as string) || \"\";\n\t\t\tconst url = (block.asset as { url?: string })?.url || \"\";\n\t\t\tlines.push(`![${alt}](${url})`);\n\t\t\tprevWasList = false;\n\t\t} else {\n\t\t\t// Tier 3: Unknown block -> opaque fence\n\t\t\tif (i > 0) lines.push(\"\");\n\t\t\tlines.push(`<!--ec:block ${JSON.stringify(block)} -->`);\n\t\t\tprevWasList = false;\n\t\t}\n\t}\n\n\treturn lines.join(\"\\n\") + \"\\n\";\n}\n\nfunction renderStandardBlock(block: PortableTextBlock): string {\n\tconst text = renderSpans(block.children ?? [], block.markDefs ?? []);\n\n\t// List items\n\tif (block.listItem) {\n\t\tconst indent = \" \".repeat(Math.max(0, (block.level ?? 1) - 1));\n\t\tconst marker = block.listItem === \"number\" ? \"1.\" : \"-\";\n\t\treturn `${indent}${marker} ${text}`;\n\t}\n\n\t// Headings\n\tif (block.style && block.style.startsWith(\"h\")) {\n\t\tconst level = parseInt(block.style.substring(1), 10);\n\t\tif (level >= 1 && level <= 6) {\n\t\t\treturn `${\"#\".repeat(level)} ${text}`;\n\t\t}\n\t}\n\n\t// Blockquote\n\tif (block.style === \"blockquote\") {\n\t\treturn `> ${text}`;\n\t}\n\n\treturn text;\n}\n\nfunction renderSpans(spans: PortableTextSpan[], markDefs: MarkDef[]): string {\n\tlet result = \"\";\n\n\tfor (const span of spans) {\n\t\tif (span._type !== \"span\") continue;\n\n\t\tlet text = span.text ?? \"\";\n\t\tconst marks = span.marks ?? [];\n\n\t\tfor (const mark of marks) {\n\t\t\tconst def = markDefs.find((d) => d._key === mark);\n\t\t\tif (def) {\n\t\t\t\tif (def._type === \"link\") {\n\t\t\t\t\ttext = `[${text}](${def.href ?? \"\"})`;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tswitch (mark) {\n\t\t\t\t\tcase \"strong\":\n\t\t\t\t\t\ttext = `**${text}**`;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"em\":\n\t\t\t\t\t\ttext = `_${text}_`;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"code\":\n\t\t\t\t\t\ttext = `\\`${text}\\``;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"strike-through\":\n\t\t\t\t\tcase \"strikethrough\":\n\t\t\t\t\t\ttext = `~~${text}~~`;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresult += text;\n\t}\n\n\treturn result;\n}\n\n// ---------------------------------------------------------------------------\n// Markdown -> PT\n// ---------------------------------------------------------------------------\n\n// Regex patterns for markdown parsing\nconst OPAQUE_FENCE_PATTERN = /^<!--ec:block (.+) -->$/;\nconst HEADING_PATTERN = /^(#{1,6})\\s+(.+)$/;\nconst UNORDERED_LIST_PATTERN = /^(\\s*)[-*+]\\s+(.+)$/;\nconst ORDERED_LIST_PATTERN = /^(\\s*)\\d+\\.\\s+(.+)$/;\nconst IMAGE_PATTERN = /^!\\[([^\\]]*)\\]\\(([^)]+)\\)$/;\nconst INLINE_MARKDOWN_PATTERN =\n\t/(\\*\\*(.+?)\\*\\*)|(_(.+?)_)|(`(.+?)`)|(\\[(.+?)\\]\\((.+?)\\))|(~~(.+?)~~)/g;\n\n/**\n * Convert Markdown to Portable Text blocks.\n * Opaque fences (<!--ec:block ... -->) are deserialized and spliced back in.\n */\nexport function markdownToPortableText(markdown: string): PortableTextBlock[] {\n\tconst blocks: PortableTextBlock[] = [];\n\tconst lines = markdown.split(\"\\n\");\n\tlet i = 0;\n\n\twhile (i < lines.length) {\n\t\tconst line = lines[i];\n\n\t\t// Opaque fence\n\t\tconst opaqueMatch = line.match(OPAQUE_FENCE_PATTERN);\n\t\tif (opaqueMatch) {\n\t\t\ttry {\n\t\t\t\tblocks.push(JSON.parse(opaqueMatch[1]) as PortableTextBlock);\n\t\t\t} catch {\n\t\t\t\tblocks.push(makeBlock(line));\n\t\t\t}\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Code fence\n\t\tif (line.startsWith(\"```\")) {\n\t\t\tconst lang = line.slice(3).trim();\n\t\t\tconst codeLines: string[] = [];\n\t\t\ti++;\n\t\t\twhile (i < lines.length && !lines[i].startsWith(\"```\")) {\n\t\t\t\tcodeLines.push(lines[i]);\n\t\t\t\ti++;\n\t\t\t}\n\t\t\tblocks.push({\n\t\t\t\t_type: \"code\",\n\t\t\t\t_key: generateKey(),\n\t\t\t\tlanguage: lang || undefined,\n\t\t\t\tcode: codeLines.join(\"\\n\"),\n\t\t\t});\n\t\t\ti++; // skip closing ```\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Blank line\n\t\tif (line.trim() === \"\") {\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Heading\n\t\tconst headingMatch = line.match(HEADING_PATTERN);\n\t\tif (headingMatch) {\n\t\t\tblocks.push(makeBlock(headingMatch[2], `h${headingMatch[1].length}`));\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Blockquote\n\t\tif (line.startsWith(\"> \")) {\n\t\t\tblocks.push(makeBlock(line.slice(2), \"blockquote\"));\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Unordered list\n\t\tconst ulMatch = line.match(UNORDERED_LIST_PATTERN);\n\t\tif (ulMatch) {\n\t\t\tconst level = Math.floor(ulMatch[1].length / 2) + 1;\n\t\t\tblocks.push(makeListBlock(ulMatch[2], \"bullet\", level));\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Ordered list\n\t\tconst olMatch = line.match(ORDERED_LIST_PATTERN);\n\t\tif (olMatch) {\n\t\t\tconst level = Math.floor(olMatch[1].length / 2) + 1;\n\t\t\tblocks.push(makeListBlock(olMatch[2], \"number\", level));\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Image\n\t\tconst imgMatch = line.match(IMAGE_PATTERN);\n\t\tif (imgMatch) {\n\t\t\tblocks.push({\n\t\t\t\t_type: \"image\",\n\t\t\t\t_key: generateKey(),\n\t\t\t\talt: imgMatch[1],\n\t\t\t\tasset: { url: imgMatch[2] },\n\t\t\t});\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Paragraph\n\t\tblocks.push(makeBlock(line));\n\t\ti++;\n\t}\n\n\treturn blocks;\n}\n\n// ---------------------------------------------------------------------------\n// Block builders\n// ---------------------------------------------------------------------------\n\nfunction makeBlock(text: string, style: string = \"normal\"): PortableTextBlock {\n\tconst { spans, markDefs } = parseInline(text);\n\treturn { _type: \"block\", _key: generateKey(), style, markDefs, children: spans };\n}\n\nfunction makeListBlock(text: string, listItem: string, level: number): PortableTextBlock {\n\tconst { spans, markDefs } = parseInline(text);\n\treturn {\n\t\t_type: \"block\",\n\t\t_key: generateKey(),\n\t\tstyle: \"normal\",\n\t\tlistItem,\n\t\tlevel,\n\t\tmarkDefs,\n\t\tchildren: spans,\n\t};\n}\n\n/**\n * Parse inline markdown (bold, italic, code, links, strikethrough) into PT spans + markDefs.\n */\nfunction parseInline(text: string): ParsedInline {\n\tconst spans: PortableTextSpan[] = [];\n\tconst markDefs: MarkDef[] = [];\n\tconst regex = INLINE_MARKDOWN_PATTERN;\n\n\tlet lastIndex = 0;\n\tlet match: RegExpExecArray | null;\n\n\twhile ((match = regex.exec(text)) !== null) {\n\t\tif (match.index > lastIndex) {\n\t\t\tspans.push({\n\t\t\t\t_type: \"span\",\n\t\t\t\t_key: generateKey(),\n\t\t\t\ttext: text.slice(lastIndex, match.index),\n\t\t\t\tmarks: [],\n\t\t\t});\n\t\t}\n\n\t\tif (match[2] != null) {\n\t\t\tspans.push({ _type: \"span\", _key: generateKey(), text: match[2], marks: [\"strong\"] });\n\t\t} else if (match[4] != null) {\n\t\t\tspans.push({ _type: \"span\", _key: generateKey(), text: match[4], marks: [\"em\"] });\n\t\t} else if (match[6] != null) {\n\t\t\tspans.push({ _type: \"span\", _key: generateKey(), text: match[6], marks: [\"code\"] });\n\t\t} else if (match[8] != null && match[9] != null) {\n\t\t\tconst key = generateKey();\n\t\t\tmarkDefs.push({ _key: key, _type: \"link\", href: match[9] });\n\t\t\tspans.push({ _type: \"span\", _key: generateKey(), text: match[8], marks: [key] });\n\t\t} else if (match[11] != null) {\n\t\t\tspans.push({\n\t\t\t\t_type: \"span\",\n\t\t\t\t_key: generateKey(),\n\t\t\t\ttext: match[11],\n\t\t\t\tmarks: [\"strike-through\"],\n\t\t\t});\n\t\t}\n\n\t\tlastIndex = match.index + match[0].length;\n\t}\n\n\tif (lastIndex < text.length) {\n\t\tspans.push({ _type: \"span\", _key: generateKey(), text: text.slice(lastIndex), marks: [] });\n\t}\n\n\tif (spans.length === 0) {\n\t\tspans.push({ _type: \"span\", _key: generateKey(), text, marks: [] });\n\t}\n\n\treturn { spans, markDefs };\n}\n\n// ---------------------------------------------------------------------------\n// Key generation\n// ---------------------------------------------------------------------------\n\nlet keyCounter = 0;\n\nfunction generateKey(): string {\n\treturn `k${(keyCounter++).toString(36)}`;\n}\n\n/** Reset key counter (useful for testing) */\nexport function resetKeyCounter(): void {\n\tkeyCounter = 0;\n}\n\n// ---------------------------------------------------------------------------\n// Schema-aware conversion helpers\n// ---------------------------------------------------------------------------\n\nexport interface FieldSchema {\n\tslug: string;\n\ttype: string;\n}\n\n/**\n * Convert content data for reading: PT fields -> markdown strings.\n * Only converts fields with type \"portableText\" that contain arrays.\n */\nexport function convertDataForRead(\n\tdata: Record<string, unknown>,\n\tfields: FieldSchema[],\n\traw: boolean = false,\n): Record<string, unknown> {\n\tif (raw) return data;\n\n\tconst result = { ...data };\n\tfor (const field of fields) {\n\t\tif (field.type === \"portableText\" && Array.isArray(result[field.slug])) {\n\t\t\tresult[field.slug] = portableTextToMarkdown(result[field.slug] as PortableTextBlock[]);\n\t\t}\n\t}\n\treturn result;\n}\n\n/**\n * Convert content data for writing: markdown strings -> PT arrays.\n * Only converts fields with type \"portableText\" that contain strings.\n */\nexport function convertDataForWrite(\n\tdata: Record<string, unknown>,\n\tfields: FieldSchema[],\n): Record<string, unknown> {\n\tconst result = { ...data };\n\tfor (const field of fields) {\n\t\tif (field.type === \"portableText\" && typeof result[field.slug] === \"string\") {\n\t\t\tresult[field.slug] = markdownToPortableText(result[field.slug] as string);\n\t\t}\n\t}\n\treturn result;\n}\n","/**\n * Transport layer for the EmDash client.\n *\n * Implements a composable interceptor pipeline that modifies requests\n * and responses. The client calls `transport.fetch(request)` — everything\n * else (auth, CSRF, retry) is handled by interceptors.\n */\n\n// Regex patterns for transport utilities\nconst COOKIE_NAME_VALUE_PATTERN = /^([^;]+)/;\n\n/**\n * An interceptor can modify the request, call next(), inspect\n * the response, and optionally retry.\n */\nexport type Interceptor = (\n\trequest: Request,\n\tnext: (request: Request) => Promise<Response>,\n) => Promise<Response>;\n\nexport interface TransportOptions {\n\tinterceptors?: Interceptor[];\n}\n\nfunction baseFetch(request: Request): Promise<Response> {\n\treturn globalThis.fetch(request);\n}\n\n/**\n * Creates a fetch function that runs requests through an interceptor pipeline.\n */\nexport function createTransport(options: TransportOptions = {}): {\n\tfetch: (request: Request) => Promise<Response>;\n} {\n\tconst interceptors = options.interceptors ?? [];\n\n\t// Build the chain once — interceptors don't change after construction\n\tlet chain: (request: Request) => Promise<Response> = baseFetch;\n\tfor (let i = interceptors.length - 1; i >= 0; i--) {\n\t\tconst interceptor = interceptors[i];\n\t\tconst next = chain;\n\t\tchain = (req) => interceptor(req, next);\n\t}\n\n\treturn { fetch: chain };\n}\n\n// ---------------------------------------------------------------------------\n// Built-in interceptors\n// ---------------------------------------------------------------------------\n\n/**\n * Adds X-EmDash-Request: 1 and Origin headers to mutation requests\n * (POST, PUT, DELETE). The custom header satisfies EmDash's CSRF check;\n * the Origin header satisfies Astro's built-in origin verification which\n * rejects server-side POST requests that lack a matching Origin.\n */\nexport function csrfInterceptor(): Interceptor {\n\tconst MUTATION_METHODS = new Set([\"POST\", \"PUT\", \"DELETE\", \"PATCH\"]);\n\n\treturn (request, next) => {\n\t\tif (MUTATION_METHODS.has(request.method)) {\n\t\t\tconst headers = new Headers(request.headers);\n\t\t\theaders.set(\"X-EmDash-Request\", \"1\");\n\t\t\tif (!headers.has(\"Origin\")) {\n\t\t\t\tconst url = new URL(request.url);\n\t\t\t\theaders.set(\"Origin\", url.origin);\n\t\t\t}\n\t\t\treturn next(new Request(request, { headers }));\n\t\t}\n\t\treturn next(request);\n\t};\n}\n\n/**\n * Adds Authorization: Bearer header from a static token.\n */\nexport function tokenInterceptor(token: string): Interceptor {\n\treturn (request, next) => {\n\t\tconst headers = new Headers(request.headers);\n\t\theaders.set(\"Authorization\", `Bearer ${token}`);\n\t\treturn next(new Request(request, { headers }));\n\t};\n}\n\n/**\n * Dev bypass interceptor. Calls the dev-bypass endpoint on first request\n * to establish a session, then forwards the session cookie on subsequent\n * requests.\n */\nexport function devBypassInterceptor(baseUrl: string): Interceptor {\n\tlet sessionCookie: string | null = null;\n\tlet initializing: Promise<void> | null = null;\n\n\tasync function init(): Promise<void> {\n\t\tconst bypassUrl = new URL(\"/_emdash/api/auth/dev-bypass\", baseUrl);\n\t\tconst res = await globalThis.fetch(bypassUrl, { redirect: \"manual\" });\n\n\t\t// Extract session cookie from Set-Cookie header\n\t\tconst setCookie = res.headers.get(\"set-cookie\");\n\t\tif (setCookie) {\n\t\t\t// Extract just the cookie name=value part\n\t\t\tconst match = setCookie.match(COOKIE_NAME_VALUE_PATTERN);\n\t\t\tif (match) {\n\t\t\t\tsessionCookie = match[1]!;\n\t\t\t}\n\t\t}\n\n\t\t// Consume the response body\n\t\tif (res.body) {\n\t\t\tawait res.text().catch(() => {});\n\t\t}\n\t}\n\n\treturn async (request, next) => {\n\t\t// Ensure we've initialized (only once, even with concurrent requests)\n\t\tif (!sessionCookie) {\n\t\t\tif (!initializing) {\n\t\t\t\tinitializing = init();\n\t\t\t}\n\t\t\tawait initializing;\n\t\t}\n\n\t\tif (sessionCookie) {\n\t\t\tconst headers = new Headers(request.headers);\n\t\t\tconst existing = headers.get(\"cookie\");\n\t\t\theaders.set(\"cookie\", existing ? `${existing}; ${sessionCookie}` : sessionCookie);\n\t\t\treturn next(new Request(request, { headers }));\n\t\t}\n\n\t\treturn next(request);\n\t};\n}\n\n/**\n * Auto-refreshes expired OAuth tokens on 401 responses.\n * Requires a refresh token and the token endpoint URL.\n */\nexport function refreshInterceptor(options: {\n\trefreshToken: string;\n\ttokenEndpoint: string;\n\tonTokenRefreshed?: (accessToken: string, refreshToken: string, expiresAt: string) => void;\n}): Interceptor {\n\tlet refreshing: Promise<string | null> | null = null;\n\n\tasync function refresh(): Promise<string | null> {\n\t\tconst res = await globalThis.fetch(options.tokenEndpoint, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({\n\t\t\t\tgrant_type: \"refresh_token\",\n\t\t\t\trefresh_token: options.refreshToken,\n\t\t\t}),\n\t\t});\n\n\t\tif (!res.ok) return null;\n\n\t\tconst data = (await res.json()) as {\n\t\t\taccess_token: string;\n\t\t\trefresh_token?: string;\n\t\t\texpires_in?: number;\n\t\t};\n\t\tconst expiresAt = data.expires_in\n\t\t\t? new Date(Date.now() + data.expires_in * 1000).toISOString()\n\t\t\t: new Date(Date.now() + 3600_000).toISOString();\n\n\t\tif (options.onTokenRefreshed) {\n\t\t\toptions.onTokenRefreshed(\n\t\t\t\tdata.access_token,\n\t\t\t\tdata.refresh_token ?? options.refreshToken,\n\t\t\t\texpiresAt,\n\t\t\t);\n\t\t}\n\n\t\treturn data.access_token;\n\t}\n\n\treturn async (request, next) => {\n\t\tconst response = await next(request);\n\n\t\tif (response.status === 401) {\n\t\t\t// Try to refresh\n\t\t\tif (!refreshing) {\n\t\t\t\trefreshing = refresh().finally(() => {\n\t\t\t\t\trefreshing = null;\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst newToken = await refreshing;\n\t\t\tif (newToken) {\n\t\t\t\t// Retry with new token\n\t\t\t\tconst headers = new Headers(request.headers);\n\t\t\t\theaders.set(\"Authorization\", `Bearer ${newToken}`);\n\t\t\t\treturn next(new Request(request, { headers }));\n\t\t\t}\n\t\t}\n\n\t\treturn response;\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"transport-yxiQsi8I.mjs","names":[],"sources":["../src/client/portable-text.ts","../src/client/transport.ts"],"sourcesContent":["/**\n * Portable Text <-> Markdown conversion layer.\n *\n * Three tiers of block handling:\n * Tier 1: Standard PT blocks <-> standard Markdown (headings, paragraphs, lists, etc.)\n * Tier 2: EmDash custom blocks <-> Markdown directives (future)\n * Tier 3: Unknown blocks <-> opaque HTML comment fences (preserved, not editable)\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Minimal Portable Text block shape */\nexport interface PortableTextBlock {\n\t_type: string;\n\t_key?: string;\n\tstyle?: string;\n\tlevel?: number;\n\tlistItem?: string;\n\tmarkDefs?: MarkDef[];\n\tchildren?: PortableTextSpan[];\n\t[key: string]: unknown;\n}\n\ninterface PortableTextSpan {\n\t_type: string;\n\t_key?: string;\n\ttext?: string;\n\tmarks?: string[];\n\t[key: string]: unknown;\n}\n\ninterface MarkDef {\n\t_key: string;\n\t_type: string;\n\thref?: string;\n\t[key: string]: unknown;\n}\n\ninterface ParsedInline {\n\tspans: PortableTextSpan[];\n\tmarkDefs: MarkDef[];\n}\n\n// ---------------------------------------------------------------------------\n// PT -> Markdown\n// ---------------------------------------------------------------------------\n\n/**\n * Convert Portable Text blocks to Markdown.\n * Unknown block types are serialized as opaque fences.\n */\nexport function portableTextToMarkdown(blocks: PortableTextBlock[]): string {\n\tconst lines: string[] = [];\n\tlet prevWasList = false;\n\n\tfor (let i = 0; i < blocks.length; i++) {\n\t\tconst block = blocks[i];\n\n\t\tif (block._type === \"block\") {\n\t\t\tconst isList = !!block.listItem;\n\n\t\t\t// Blank line between non-contiguous block types\n\t\t\tif (i > 0 && (!isList || !prevWasList)) {\n\t\t\t\tlines.push(\"\");\n\t\t\t}\n\n\t\t\tlines.push(renderStandardBlock(block));\n\t\t\tprevWasList = isList;\n\t\t} else if (block._type === \"code\") {\n\t\t\tif (i > 0) lines.push(\"\");\n\t\t\tconst lang = (block.language as string) || \"\";\n\t\t\tconst code = (block.code as string) || \"\";\n\t\t\tlines.push(\"```\" + lang);\n\t\t\tlines.push(code);\n\t\t\tlines.push(\"```\");\n\t\t\tprevWasList = false;\n\t\t} else if (block._type === \"image\") {\n\t\t\tif (i > 0) lines.push(\"\");\n\t\t\tconst alt = (block.alt as string) || \"\";\n\t\t\tconst url = (block.asset as { url?: string })?.url || \"\";\n\t\t\tlines.push(`![${alt}](${url})`);\n\t\t\tprevWasList = false;\n\t\t} else {\n\t\t\t// Tier 3: Unknown block -> opaque fence\n\t\t\tif (i > 0) lines.push(\"\");\n\t\t\tlines.push(`<!--ec:block ${JSON.stringify(block)} -->`);\n\t\t\tprevWasList = false;\n\t\t}\n\t}\n\n\treturn lines.join(\"\\n\") + \"\\n\";\n}\n\nfunction renderStandardBlock(block: PortableTextBlock): string {\n\tconst text = renderSpans(block.children ?? [], block.markDefs ?? []);\n\n\t// List items\n\tif (block.listItem) {\n\t\tconst indent = \" \".repeat(Math.max(0, (block.level ?? 1) - 1));\n\t\tconst marker = block.listItem === \"number\" ? \"1.\" : \"-\";\n\t\treturn `${indent}${marker} ${text}`;\n\t}\n\n\t// Headings\n\tif (block.style && block.style.startsWith(\"h\")) {\n\t\tconst level = parseInt(block.style.substring(1), 10);\n\t\tif (level >= 1 && level <= 6) {\n\t\t\treturn `${\"#\".repeat(level)} ${text}`;\n\t\t}\n\t}\n\n\t// Blockquote\n\tif (block.style === \"blockquote\") {\n\t\treturn `> ${text}`;\n\t}\n\n\treturn text;\n}\n\nfunction renderSpans(spans: PortableTextSpan[], markDefs: MarkDef[]): string {\n\tlet result = \"\";\n\n\tfor (const span of spans) {\n\t\tif (span._type !== \"span\") continue;\n\n\t\tlet text = span.text ?? \"\";\n\t\tconst marks = span.marks ?? [];\n\n\t\tfor (const mark of marks) {\n\t\t\tconst def = markDefs.find((d) => d._key === mark);\n\t\t\tif (def) {\n\t\t\t\tif (def._type === \"link\") {\n\t\t\t\t\ttext = `[${text}](${def.href ?? \"\"})`;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tswitch (mark) {\n\t\t\t\t\tcase \"strong\":\n\t\t\t\t\t\ttext = `**${text}**`;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"em\":\n\t\t\t\t\t\ttext = `_${text}_`;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"code\":\n\t\t\t\t\t\ttext = `\\`${text}\\``;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"strike-through\":\n\t\t\t\t\tcase \"strikethrough\":\n\t\t\t\t\t\ttext = `~~${text}~~`;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresult += text;\n\t}\n\n\treturn result;\n}\n\n// ---------------------------------------------------------------------------\n// Markdown -> PT\n// ---------------------------------------------------------------------------\n\n// Regex patterns for markdown parsing\nconst OPAQUE_FENCE_PATTERN = /^<!--ec:block (.+) -->$/;\nconst HEADING_PATTERN = /^(#{1,6})\\s+(.+)$/;\nconst UNORDERED_LIST_PATTERN = /^(\\s*)[-*+]\\s+(.+)$/;\nconst ORDERED_LIST_PATTERN = /^(\\s*)\\d+\\.\\s+(.+)$/;\nconst IMAGE_PATTERN = /^!\\[([^\\]]*)\\]\\(([^)]+)\\)$/;\nconst INLINE_MARKDOWN_PATTERN =\n\t/(\\*\\*(.+?)\\*\\*)|(_(.+?)_)|(`(.+?)`)|(\\[(.+?)\\]\\((.+?)\\))|(~~(.+?)~~)/g;\n\n/**\n * Convert Markdown to Portable Text blocks.\n * Opaque fences (<!--ec:block ... -->) are deserialized and spliced back in.\n */\nexport function markdownToPortableText(markdown: string): PortableTextBlock[] {\n\tconst blocks: PortableTextBlock[] = [];\n\tconst lines = markdown.split(\"\\n\");\n\tlet i = 0;\n\n\twhile (i < lines.length) {\n\t\tconst line = lines[i];\n\n\t\t// Opaque fence\n\t\tconst opaqueMatch = line.match(OPAQUE_FENCE_PATTERN);\n\t\tif (opaqueMatch) {\n\t\t\ttry {\n\t\t\t\tblocks.push(JSON.parse(opaqueMatch[1]) as PortableTextBlock);\n\t\t\t} catch {\n\t\t\t\tblocks.push(makeBlock(line));\n\t\t\t}\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Code fence\n\t\tif (line.startsWith(\"```\")) {\n\t\t\tconst lang = line.slice(3).trim();\n\t\t\tconst codeLines: string[] = [];\n\t\t\ti++;\n\t\t\twhile (i < lines.length && !lines[i].startsWith(\"```\")) {\n\t\t\t\tcodeLines.push(lines[i]);\n\t\t\t\ti++;\n\t\t\t}\n\t\t\tblocks.push({\n\t\t\t\t_type: \"code\",\n\t\t\t\t_key: generateKey(),\n\t\t\t\tlanguage: lang || undefined,\n\t\t\t\tcode: codeLines.join(\"\\n\"),\n\t\t\t});\n\t\t\ti++; // skip closing ```\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Blank line\n\t\tif (line.trim() === \"\") {\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Heading\n\t\tconst headingMatch = line.match(HEADING_PATTERN);\n\t\tif (headingMatch) {\n\t\t\tblocks.push(makeBlock(headingMatch[2], `h${headingMatch[1].length}`));\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Blockquote\n\t\tif (line.startsWith(\"> \")) {\n\t\t\tblocks.push(makeBlock(line.slice(2), \"blockquote\"));\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Unordered list\n\t\tconst ulMatch = line.match(UNORDERED_LIST_PATTERN);\n\t\tif (ulMatch) {\n\t\t\tconst level = Math.floor(ulMatch[1].length / 2) + 1;\n\t\t\tblocks.push(makeListBlock(ulMatch[2], \"bullet\", level));\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Ordered list\n\t\tconst olMatch = line.match(ORDERED_LIST_PATTERN);\n\t\tif (olMatch) {\n\t\t\tconst level = Math.floor(olMatch[1].length / 2) + 1;\n\t\t\tblocks.push(makeListBlock(olMatch[2], \"number\", level));\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Image\n\t\tconst imgMatch = line.match(IMAGE_PATTERN);\n\t\tif (imgMatch) {\n\t\t\tblocks.push({\n\t\t\t\t_type: \"image\",\n\t\t\t\t_key: generateKey(),\n\t\t\t\talt: imgMatch[1],\n\t\t\t\tasset: { url: imgMatch[2] },\n\t\t\t});\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Paragraph\n\t\tblocks.push(makeBlock(line));\n\t\ti++;\n\t}\n\n\treturn blocks;\n}\n\n// ---------------------------------------------------------------------------\n// Block builders\n// ---------------------------------------------------------------------------\n\nfunction makeBlock(text: string, style: string = \"normal\"): PortableTextBlock {\n\tconst { spans, markDefs } = parseInline(text);\n\treturn { _type: \"block\", _key: generateKey(), style, markDefs, children: spans };\n}\n\nfunction makeListBlock(text: string, listItem: string, level: number): PortableTextBlock {\n\tconst { spans, markDefs } = parseInline(text);\n\treturn {\n\t\t_type: \"block\",\n\t\t_key: generateKey(),\n\t\tstyle: \"normal\",\n\t\tlistItem,\n\t\tlevel,\n\t\tmarkDefs,\n\t\tchildren: spans,\n\t};\n}\n\n/**\n * Parse inline markdown (bold, italic, code, links, strikethrough) into PT spans + markDefs.\n */\nfunction parseInline(text: string): ParsedInline {\n\tconst spans: PortableTextSpan[] = [];\n\tconst markDefs: MarkDef[] = [];\n\tconst regex = INLINE_MARKDOWN_PATTERN;\n\n\tlet lastIndex = 0;\n\tlet match: RegExpExecArray | null;\n\n\twhile ((match = regex.exec(text)) !== null) {\n\t\tif (match.index > lastIndex) {\n\t\t\tspans.push({\n\t\t\t\t_type: \"span\",\n\t\t\t\t_key: generateKey(),\n\t\t\t\ttext: text.slice(lastIndex, match.index),\n\t\t\t\tmarks: [],\n\t\t\t});\n\t\t}\n\n\t\tif (match[2] != null) {\n\t\t\tspans.push({ _type: \"span\", _key: generateKey(), text: match[2], marks: [\"strong\"] });\n\t\t} else if (match[4] != null) {\n\t\t\tspans.push({ _type: \"span\", _key: generateKey(), text: match[4], marks: [\"em\"] });\n\t\t} else if (match[6] != null) {\n\t\t\tspans.push({ _type: \"span\", _key: generateKey(), text: match[6], marks: [\"code\"] });\n\t\t} else if (match[8] != null && match[9] != null) {\n\t\t\tconst key = generateKey();\n\t\t\tmarkDefs.push({ _key: key, _type: \"link\", href: match[9] });\n\t\t\tspans.push({ _type: \"span\", _key: generateKey(), text: match[8], marks: [key] });\n\t\t} else if (match[11] != null) {\n\t\t\tspans.push({\n\t\t\t\t_type: \"span\",\n\t\t\t\t_key: generateKey(),\n\t\t\t\ttext: match[11],\n\t\t\t\tmarks: [\"strike-through\"],\n\t\t\t});\n\t\t}\n\n\t\tlastIndex = match.index + match[0].length;\n\t}\n\n\tif (lastIndex < text.length) {\n\t\tspans.push({ _type: \"span\", _key: generateKey(), text: text.slice(lastIndex), marks: [] });\n\t}\n\n\tif (spans.length === 0) {\n\t\tspans.push({ _type: \"span\", _key: generateKey(), text, marks: [] });\n\t}\n\n\treturn { spans, markDefs };\n}\n\n// ---------------------------------------------------------------------------\n// Key generation\n// ---------------------------------------------------------------------------\n\nlet keyCounter = 0;\n\nfunction generateKey(): string {\n\treturn `k${(keyCounter++).toString(36)}`;\n}\n\n/** Reset key counter (useful for testing) */\nexport function resetKeyCounter(): void {\n\tkeyCounter = 0;\n}\n\n// ---------------------------------------------------------------------------\n// Schema-aware conversion helpers\n// ---------------------------------------------------------------------------\n\nexport interface FieldSchema {\n\tslug: string;\n\ttype: string;\n}\n\n/**\n * Convert content data for reading: PT fields -> markdown strings.\n * Only converts fields with type \"portableText\" that contain arrays.\n */\nexport function convertDataForRead(\n\tdata: Record<string, unknown>,\n\tfields: FieldSchema[],\n\traw: boolean = false,\n): Record<string, unknown> {\n\tif (raw) return data;\n\n\tconst result = { ...data };\n\tfor (const field of fields) {\n\t\tif (field.type === \"portableText\" && Array.isArray(result[field.slug])) {\n\t\t\tresult[field.slug] = portableTextToMarkdown(result[field.slug] as PortableTextBlock[]);\n\t\t}\n\t}\n\treturn result;\n}\n\n/**\n * Convert content data for writing: markdown strings -> PT arrays.\n * Only converts fields with type \"portableText\" that contain strings.\n */\nexport function convertDataForWrite(\n\tdata: Record<string, unknown>,\n\tfields: FieldSchema[],\n): Record<string, unknown> {\n\tconst result = { ...data };\n\tfor (const field of fields) {\n\t\tif (field.type === \"portableText\" && typeof result[field.slug] === \"string\") {\n\t\t\tresult[field.slug] = markdownToPortableText(result[field.slug] as string);\n\t\t}\n\t}\n\treturn result;\n}\n","/**\n * Transport layer for the EmDash client.\n *\n * Implements a composable interceptor pipeline that modifies requests\n * and responses. The client calls `transport.fetch(request)` — everything\n * else (auth, CSRF, retry) is handled by interceptors.\n */\n\n// Regex patterns for transport utilities\nconst COOKIE_NAME_VALUE_PATTERN = /^([^;]+)/;\n\n/**\n * An interceptor can modify the request, call next(), inspect\n * the response, and optionally retry.\n */\nexport type Interceptor = (\n\trequest: Request,\n\tnext: (request: Request) => Promise<Response>,\n) => Promise<Response>;\n\nexport interface TransportOptions {\n\tinterceptors?: Interceptor[];\n}\n\nfunction baseFetch(request: Request): Promise<Response> {\n\treturn globalThis.fetch(request);\n}\n\n/**\n * Creates a fetch function that runs requests through an interceptor pipeline.\n */\nexport function createTransport(options: TransportOptions = {}): {\n\tfetch: (request: Request) => Promise<Response>;\n} {\n\tconst interceptors = options.interceptors ?? [];\n\n\t// Build the chain once — interceptors don't change after construction\n\tlet chain: (request: Request) => Promise<Response> = baseFetch;\n\tfor (let i = interceptors.length - 1; i >= 0; i--) {\n\t\tconst interceptor = interceptors[i];\n\t\tconst next = chain;\n\t\tchain = (req) => interceptor(req, next);\n\t}\n\n\treturn { fetch: chain };\n}\n\n// ---------------------------------------------------------------------------\n// Built-in interceptors\n// ---------------------------------------------------------------------------\n\n/**\n * Adds X-EmDash-Request: 1 and Origin headers to mutation requests\n * (POST, PUT, DELETE). The custom header satisfies EmDash's CSRF check;\n * the Origin header satisfies Astro's built-in origin verification which\n * rejects server-side POST requests that lack a matching Origin.\n */\nexport function csrfInterceptor(): Interceptor {\n\tconst MUTATION_METHODS = new Set([\"POST\", \"PUT\", \"DELETE\", \"PATCH\"]);\n\n\treturn (request, next) => {\n\t\tif (MUTATION_METHODS.has(request.method)) {\n\t\t\tconst headers = new Headers(request.headers);\n\t\t\theaders.set(\"X-EmDash-Request\", \"1\");\n\t\t\tif (!headers.has(\"Origin\")) {\n\t\t\t\tconst url = new URL(request.url);\n\t\t\t\theaders.set(\"Origin\", url.origin);\n\t\t\t}\n\t\t\treturn next(new Request(request, { headers }));\n\t\t}\n\t\treturn next(request);\n\t};\n}\n\n/**\n * Adds Authorization: Bearer header from a static token.\n */\nexport function tokenInterceptor(token: string): Interceptor {\n\treturn (request, next) => {\n\t\tconst headers = new Headers(request.headers);\n\t\theaders.set(\"Authorization\", `Bearer ${token}`);\n\t\treturn next(new Request(request, { headers }));\n\t};\n}\n\n/**\n * Dev bypass interceptor. Calls the dev-bypass endpoint on first request\n * to establish a session, then forwards the session cookie on subsequent\n * requests.\n */\nexport function devBypassInterceptor(baseUrl: string): Interceptor {\n\tlet sessionCookie: string | null = null;\n\tlet initializing: Promise<void> | null = null;\n\n\tasync function init(): Promise<void> {\n\t\tconst bypassUrl = new URL(\"/_emdash/api/auth/dev-bypass\", baseUrl);\n\t\tconst res = await globalThis.fetch(bypassUrl, { redirect: \"manual\" });\n\n\t\t// Extract session cookie from Set-Cookie header\n\t\tconst setCookie = res.headers.get(\"set-cookie\");\n\t\tif (setCookie) {\n\t\t\t// Extract just the cookie name=value part\n\t\t\tconst match = setCookie.match(COOKIE_NAME_VALUE_PATTERN);\n\t\t\tif (match) {\n\t\t\t\tsessionCookie = match[1]!;\n\t\t\t}\n\t\t}\n\n\t\t// Consume the response body\n\t\tif (res.body) {\n\t\t\tawait res.text().catch(() => {});\n\t\t}\n\t}\n\n\treturn async (request, next) => {\n\t\t// Ensure we've initialized (only once, even with concurrent requests)\n\t\tif (!sessionCookie) {\n\t\t\tif (!initializing) {\n\t\t\t\tinitializing = init();\n\t\t\t}\n\t\t\tawait initializing;\n\t\t}\n\n\t\tif (sessionCookie) {\n\t\t\tconst headers = new Headers(request.headers);\n\t\t\tconst existing = headers.get(\"cookie\");\n\t\t\theaders.set(\"cookie\", existing ? `${existing}; ${sessionCookie}` : sessionCookie);\n\t\t\treturn next(new Request(request, { headers }));\n\t\t}\n\n\t\treturn next(request);\n\t};\n}\n\n/**\n * Auto-refreshes expired OAuth tokens on 401 responses.\n * Requires a refresh token and the token endpoint URL.\n */\nexport function refreshInterceptor(options: {\n\trefreshToken: string;\n\ttokenEndpoint: string;\n\tonTokenRefreshed?: (accessToken: string, refreshToken: string, expiresAt: string) => void;\n}): Interceptor {\n\tlet refreshing: Promise<string | null> | null = null;\n\n\tasync function refresh(): Promise<string | null> {\n\t\tconst res = await globalThis.fetch(options.tokenEndpoint, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({\n\t\t\t\tgrant_type: \"refresh_token\",\n\t\t\t\trefresh_token: options.refreshToken,\n\t\t\t}),\n\t\t});\n\n\t\tif (!res.ok) return null;\n\n\t\tconst data = (await res.json()) as {\n\t\t\taccess_token: string;\n\t\t\trefresh_token?: string;\n\t\t\texpires_in?: number;\n\t\t};\n\t\tconst expiresAt = data.expires_in\n\t\t\t? new Date(Date.now() + data.expires_in * 1000).toISOString()\n\t\t\t: new Date(Date.now() + 3600_000).toISOString();\n\n\t\tif (options.onTokenRefreshed) {\n\t\t\toptions.onTokenRefreshed(\n\t\t\t\tdata.access_token,\n\t\t\t\tdata.refresh_token ?? options.refreshToken,\n\t\t\t\texpiresAt,\n\t\t\t);\n\t\t}\n\n\t\treturn data.access_token;\n\t}\n\n\treturn async (request, next) => {\n\t\tconst response = await next(request);\n\n\t\tif (response.status === 401) {\n\t\t\t// Try to refresh\n\t\t\tif (!refreshing) {\n\t\t\t\trefreshing = refresh().finally(() => {\n\t\t\t\t\trefreshing = null;\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst newToken = await refreshing;\n\t\t\tif (newToken) {\n\t\t\t\t// Retry with new token\n\t\t\t\tconst headers = new Headers(request.headers);\n\t\t\t\theaders.set(\"Authorization\", `Bearer ${newToken}`);\n\t\t\t\treturn next(new Request(request, { headers }));\n\t\t\t}\n\t\t}\n\n\t\treturn response;\n\t};\n}\n"],"mappings":"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"}
@@ -101,4 +101,4 @@ declare class EmDashValidationError extends Error {
101
101
  }
102
102
  //#endregion
103
103
  export { ContentSeoInput as a, FindManyOptions as c, ContentSeo as i, FindManyResult as l, ContentBylineCredit as n, CreateContentInput as o, ContentItem as r, EmDashValidationError as s, BylineSummary as t, UpdateContentInput as u };
104
- //# sourceMappingURL=types-CIsTnQvJ.d.mts.map
104
+ //# sourceMappingURL=types-BbsYgi_R.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types-CIsTnQvJ.d.mts","names":[],"sources":["../src/database/repositories/types.ts"],"mappings":";UAEiB,kBAAA;EAChB,IAAA;EACA,IAAA;EACA,IAAA,EAAM,MAAA;EACN,MAAA;EACA,QAAA;EACA,eAAA;EACA,MAAA;EACA,aAAA;EACA,WAAA;EAJA;EAMA,SAAA;AAAA;AAAA,UAGgB,kBAAA;EAChB,IAAA,GAAO,MAAA;EACP,MAAA;EACA,IAAA;EACA,WAAA;EACA,WAAA;EACA,QAAA;EACA,eAAA;AAAA;;UAIgB,UAAA;EAChB,KAAA;EACA,WAAA;EACA,KAAA;EACA,SAAA;EACA,OAAA;AAAA;;UAIgB,eAAA;EAChB,KAAA;EACA,WAAA;EACA,KAAA;EACA,SAAA;EACA,OAAA;AAAA;AAAA,UAGgB,aAAA;EAChB,EAAA;EACA,IAAA;EACA,WAAA;EACA,GAAA;EACA,aAAA;EACA,UAAA;EACA,MAAA;EACA,OAAA;EACA,SAAA;EACA,SAAA;AAAA;AAAA,UAGgB,mBAAA;EAChB,MAAA,EAAQ,aAAA;EACR,SAAA;EACA,SAAA;EAhBgB;EAkBhB,MAAA;AAAA;AAAA,UAGgB,eAAA;EAChB,KAAA;IACC,MAAA;IACA,QAAA;IACA,MAAA;EAAA;EAED,OAAA;IACC,KAAA;IACA,SAAA;EAAA;EAED,KAAA;EACA,MAAA;AAAA;AAAA,UAGgB,cAAA;EAChB,KAAA,EAAO,CAAA;EACP,UAAA;AAAA;AAAA,UAqBgB,WAAA;EAChB,EAAA;EACA,IAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA,EAAM,MAAA;EACN,QAAA;EACA,eAAA;EACA,MAAA,GAAS,aAAA;EACT,OAAA,GAAU,mBAAA;EACV,SAAA;EACA,SAAA;EACA,WAAA;EACA,WAAA;EACA,cAAA;EACA,eAAA;EACA,OAAA;EACA,MAAA;EACA,gBAAA;EAzC+B;EA2C/B,GAAA,GAAM,UAAA;AAAA;AAAA,cAGM,qBAAA,SAA8B,KAAA;EAGlC,OAAA;cADP,OAAA,UACO,OAAA;AAAA"}
1
+ {"version":3,"file":"types-BbsYgi_R.d.mts","names":[],"sources":["../src/database/repositories/types.ts"],"mappings":";UAEiB,kBAAA;EAChB,IAAA;EACA,IAAA;EACA,IAAA,EAAM,MAAA;EACN,MAAA;EACA,QAAA;EACA,eAAA;EACA,MAAA;EACA,aAAA;EACA,WAAA;EAJA;EAMA,SAAA;AAAA;AAAA,UAGgB,kBAAA;EAChB,IAAA,GAAO,MAAA;EACP,MAAA;EACA,IAAA;EACA,WAAA;EACA,WAAA;EACA,QAAA;EACA,eAAA;AAAA;;UAIgB,UAAA;EAChB,KAAA;EACA,WAAA;EACA,KAAA;EACA,SAAA;EACA,OAAA;AAAA;;UAIgB,eAAA;EAChB,KAAA;EACA,WAAA;EACA,KAAA;EACA,SAAA;EACA,OAAA;AAAA;AAAA,UAGgB,aAAA;EAChB,EAAA;EACA,IAAA;EACA,WAAA;EACA,GAAA;EACA,aAAA;EACA,UAAA;EACA,MAAA;EACA,OAAA;EACA,SAAA;EACA,SAAA;AAAA;AAAA,UAGgB,mBAAA;EAChB,MAAA,EAAQ,aAAA;EACR,SAAA;EACA,SAAA;EAhBgB;EAkBhB,MAAA;AAAA;AAAA,UAGgB,eAAA;EAChB,KAAA;IACC,MAAA;IACA,QAAA;IACA,MAAA;EAAA;EAED,OAAA;IACC,KAAA;IACA,SAAA;EAAA;EAED,KAAA;EACA,MAAA;AAAA;AAAA,UAGgB,cAAA;EAChB,KAAA,EAAO,CAAA;EACP,UAAA;AAAA;AAAA,UAqBgB,WAAA;EAChB,EAAA;EACA,IAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA,EAAM,MAAA;EACN,QAAA;EACA,eAAA;EACA,MAAA,GAAS,aAAA;EACT,OAAA,GAAU,mBAAA;EACV,SAAA;EACA,SAAA;EACA,WAAA;EACA,WAAA;EACA,cAAA;EACA,eAAA;EACA,OAAA;EACA,MAAA;EACA,gBAAA;EAzC+B;EA2C/B,GAAA,GAAM,UAAA;AAAA;AAAA,cAGM,qBAAA,SAA8B,KAAA;EAGlC,OAAA;cADP,OAAA,UACO,OAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"types-Bec-r_3_.mjs","names":[],"sources":["../src/storage/types.ts"],"sourcesContent":["/**\n * Storage Layer Types\n *\n * Defines the interface for S3-compatible storage backends.\n * Works with R2, AWS S3, Minio, and other S3-compatible services.\n */\n\n/**\n * Storage configuration for S3-compatible backends\n */\nexport interface S3StorageConfig {\n\t/** S3 endpoint URL (e.g., \"https://xxx.r2.cloudflarestorage.com\") */\n\tendpoint: string;\n\t/** Bucket name */\n\tbucket: string;\n\t/** AWS access key ID */\n\taccessKeyId: string;\n\t/** AWS secret access key */\n\tsecretAccessKey: string;\n\t/** Optional region (defaults to \"auto\" for R2) */\n\tregion?: string;\n\t/** Optional public URL prefix for generated URLs (e.g., CDN URL) */\n\tpublicUrl?: string;\n}\n\n/**\n * Local filesystem storage for development\n */\nexport interface LocalStorageConfig {\n\t/** Directory path for storing files */\n\tdirectory: string;\n\t/** Base URL for serving files */\n\tbaseUrl: string;\n}\n\n/**\n * Storage adapter descriptor (serializable config)\n */\nexport interface StorageDescriptor {\n\t/** Module path exporting createStorage function */\n\tentrypoint: string;\n\t/** Serializable config passed to createStorage at runtime */\n\tconfig: Record<string, unknown>;\n}\n\n/**\n * Factory function signature for storage adapters\n *\n * Each adapter accesses its own bindings directly:\n * - R2: imports from cloudflare:workers\n * - S3: uses credentials from config\n * - Local: uses filesystem path from config\n */\nexport type CreateStorageFn = (config: Record<string, unknown>) => Storage;\n\n/**\n * Upload result\n */\nexport interface UploadResult {\n\t/** Storage key (path within bucket) */\n\tkey: string;\n\t/** Public URL to access the file */\n\turl: string;\n\t/** File size in bytes */\n\tsize: number;\n}\n\n/**\n * Download result\n */\nexport interface DownloadResult {\n\t/** File content as readable stream */\n\tbody: ReadableStream<Uint8Array>;\n\t/** MIME type */\n\tcontentType: string;\n\t/** File size in bytes */\n\tsize: number;\n}\n\n/**\n * Signed URL for direct upload\n */\nexport interface SignedUploadUrl {\n\t/** Signed URL for PUT request */\n\turl: string;\n\t/** HTTP method (always PUT) */\n\tmethod: \"PUT\";\n\t/** Headers to include in the upload request */\n\theaders: Record<string, string>;\n\t/** URL expiration time (ISO string) */\n\texpiresAt: string;\n}\n\n/**\n * Options for generating signed upload URL\n */\nexport interface SignedUploadOptions {\n\t/** Storage key (path within bucket) */\n\tkey: string;\n\t/** MIME type of the file */\n\tcontentType: string;\n\t/** File size in bytes (for content-length validation) */\n\tsize?: number;\n\t/** URL expiration in seconds (default: 3600) */\n\texpiresIn?: number;\n}\n\n/**\n * File listing result\n */\nexport interface ListResult {\n\t/** List of files */\n\tfiles: FileInfo[];\n\t/** Cursor for next page (if more results) */\n\tnextCursor?: string;\n}\n\n/**\n * File info from listing\n */\nexport interface FileInfo {\n\t/** Storage key */\n\tkey: string;\n\t/** File size in bytes */\n\tsize: number;\n\t/** Last modified date */\n\tlastModified: Date;\n\t/** ETag (content hash) */\n\tetag?: string;\n}\n\n/**\n * Options for listing files\n */\nexport interface ListOptions {\n\t/** Filter by key prefix */\n\tprefix?: string;\n\t/** Maximum results per page */\n\tlimit?: number;\n\t/** Cursor from previous list call */\n\tcursor?: string;\n}\n\n/**\n * Storage interface\n *\n * All storage backends must implement this interface.\n */\nexport interface Storage {\n\t/**\n\t * Upload a file to storage\n\t */\n\tupload(options: {\n\t\tkey: string;\n\t\tbody: Buffer | Uint8Array | ReadableStream<Uint8Array>;\n\t\tcontentType: string;\n\t}): Promise<UploadResult>;\n\n\t/**\n\t * Download a file from storage\n\t */\n\tdownload(key: string): Promise<DownloadResult>;\n\n\t/**\n\t * Delete a file from storage\n\t * Idempotent - does not throw if file doesn't exist\n\t */\n\tdelete(key: string): Promise<void>;\n\n\t/**\n\t * Check if a file exists\n\t */\n\texists(key: string): Promise<boolean>;\n\n\t/**\n\t * List files in storage\n\t */\n\tlist(options?: ListOptions): Promise<ListResult>;\n\n\t/**\n\t * Generate a signed URL for direct upload\n\t * Client uploads directly to storage, bypassing the server\n\t */\n\tgetSignedUploadUrl(options: SignedUploadOptions): Promise<SignedUploadUrl>;\n\n\t/**\n\t * Get public URL for a file\n\t */\n\tgetPublicUrl(key: string): string;\n}\n\n/**\n * Storage error with additional context\n */\nexport class EmDashStorageError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic code: string,\n\t\tpublic override cause?: unknown,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"EmDashStorageError\";\n\t}\n}\n"],"mappings":";;;;AAkMA,IAAa,qBAAb,cAAwC,MAAM;CAC7C,YACC,SACA,AAAO,MACP,AAAgB,OACf;AACD,QAAM,QAAQ;EAHP;EACS;AAGhB,OAAK,OAAO"}
1
+ {"version":3,"file":"types-Bec-r_3_.mjs","names":[],"sources":["../src/storage/types.ts"],"sourcesContent":["/**\n * Storage Layer Types\n *\n * Defines the interface for S3-compatible storage backends.\n * Works with R2, AWS S3, Minio, and other S3-compatible services.\n */\n\n/**\n * Storage configuration for S3-compatible backends\n */\nexport interface S3StorageConfig {\n\t/** S3 endpoint URL (e.g., \"https://xxx.r2.cloudflarestorage.com\") */\n\tendpoint: string;\n\t/** Bucket name */\n\tbucket: string;\n\t/**\n\t * AWS access key ID.\n\t * May be resolved from the `S3_ACCESS_KEY_ID` env var at runtime on Node.\n\t * Must be provided together with `secretAccessKey`, or both omitted.\n\t */\n\taccessKeyId?: string;\n\t/**\n\t * AWS secret access key.\n\t * May be resolved from the `S3_SECRET_ACCESS_KEY` env var at runtime on Node.\n\t * Must be provided together with `accessKeyId`, or both omitted.\n\t */\n\tsecretAccessKey?: string;\n\t/** Optional region (defaults to \"auto\") */\n\tregion?: string;\n\t/** Optional public URL prefix for generated URLs (e.g., CDN URL) */\n\tpublicUrl?: string;\n}\n\n/**\n * Local filesystem storage for development\n */\nexport interface LocalStorageConfig {\n\t/** Directory path for storing files */\n\tdirectory: string;\n\t/** Base URL for serving files */\n\tbaseUrl: string;\n}\n\n/**\n * Storage adapter descriptor (serializable config)\n */\nexport interface StorageDescriptor {\n\t/** Module path exporting createStorage function */\n\tentrypoint: string;\n\t/** Serializable config passed to createStorage at runtime */\n\tconfig: Record<string, unknown>;\n}\n\n/**\n * Factory function signature for storage adapters\n *\n * Each adapter accesses its own bindings directly:\n * - R2: imports from cloudflare:workers\n * - S3: uses credentials from config\n * - Local: uses filesystem path from config\n */\nexport type CreateStorageFn = (config: Record<string, unknown>) => Storage;\n\n/**\n * Upload result\n */\nexport interface UploadResult {\n\t/** Storage key (path within bucket) */\n\tkey: string;\n\t/** Public URL to access the file */\n\turl: string;\n\t/** File size in bytes */\n\tsize: number;\n}\n\n/**\n * Download result\n */\nexport interface DownloadResult {\n\t/** File content as readable stream */\n\tbody: ReadableStream<Uint8Array>;\n\t/** MIME type */\n\tcontentType: string;\n\t/** File size in bytes */\n\tsize: number;\n}\n\n/**\n * Signed URL for direct upload\n */\nexport interface SignedUploadUrl {\n\t/** Signed URL for PUT request */\n\turl: string;\n\t/** HTTP method (always PUT) */\n\tmethod: \"PUT\";\n\t/** Headers to include in the upload request */\n\theaders: Record<string, string>;\n\t/** URL expiration time (ISO string) */\n\texpiresAt: string;\n}\n\n/**\n * Options for generating signed upload URL\n */\nexport interface SignedUploadOptions {\n\t/** Storage key (path within bucket) */\n\tkey: string;\n\t/** MIME type of the file */\n\tcontentType: string;\n\t/** File size in bytes (for content-length validation) */\n\tsize?: number;\n\t/** URL expiration in seconds (default: 3600) */\n\texpiresIn?: number;\n}\n\n/**\n * File listing result\n */\nexport interface ListResult {\n\t/** List of files */\n\tfiles: FileInfo[];\n\t/** Cursor for next page (if more results) */\n\tnextCursor?: string;\n}\n\n/**\n * File info from listing\n */\nexport interface FileInfo {\n\t/** Storage key */\n\tkey: string;\n\t/** File size in bytes */\n\tsize: number;\n\t/** Last modified date */\n\tlastModified: Date;\n\t/** ETag (content hash) */\n\tetag?: string;\n}\n\n/**\n * Options for listing files\n */\nexport interface ListOptions {\n\t/** Filter by key prefix */\n\tprefix?: string;\n\t/** Maximum results per page */\n\tlimit?: number;\n\t/** Cursor from previous list call */\n\tcursor?: string;\n}\n\n/**\n * Storage interface\n *\n * All storage backends must implement this interface.\n */\nexport interface Storage {\n\t/**\n\t * Upload a file to storage\n\t */\n\tupload(options: {\n\t\tkey: string;\n\t\tbody: Buffer | Uint8Array | ReadableStream<Uint8Array>;\n\t\tcontentType: string;\n\t}): Promise<UploadResult>;\n\n\t/**\n\t * Download a file from storage\n\t */\n\tdownload(key: string): Promise<DownloadResult>;\n\n\t/**\n\t * Delete a file from storage\n\t * Idempotent - does not throw if file doesn't exist\n\t */\n\tdelete(key: string): Promise<void>;\n\n\t/**\n\t * Check if a file exists\n\t */\n\texists(key: string): Promise<boolean>;\n\n\t/**\n\t * List files in storage\n\t */\n\tlist(options?: ListOptions): Promise<ListResult>;\n\n\t/**\n\t * Generate a signed URL for direct upload\n\t * Client uploads directly to storage, bypassing the server\n\t */\n\tgetSignedUploadUrl(options: SignedUploadOptions): Promise<SignedUploadUrl>;\n\n\t/**\n\t * Get public URL for a file\n\t */\n\tgetPublicUrl(key: string): string;\n}\n\n/**\n * Storage error with additional context\n */\nexport class EmDashStorageError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic code: string,\n\t\tpublic override cause?: unknown,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"EmDashStorageError\";\n\t}\n}\n"],"mappings":";;;;AA0MA,IAAa,qBAAb,cAAwC,MAAM;CAC7C,YACC,SACA,AAAO,MACP,AAAgB,OACf;AACD,QAAM,QAAQ;EAHP;EACS;AAGhB,OAAK,OAAO"}
@@ -13,11 +13,19 @@ interface S3StorageConfig {
13
13
  endpoint: string;
14
14
  /** Bucket name */
15
15
  bucket: string;
16
- /** AWS access key ID */
17
- accessKeyId: string;
18
- /** AWS secret access key */
19
- secretAccessKey: string;
20
- /** Optional region (defaults to "auto" for R2) */
16
+ /**
17
+ * AWS access key ID.
18
+ * May be resolved from the `S3_ACCESS_KEY_ID` env var at runtime on Node.
19
+ * Must be provided together with `secretAccessKey`, or both omitted.
20
+ */
21
+ accessKeyId?: string;
22
+ /**
23
+ * AWS secret access key.
24
+ * May be resolved from the `S3_SECRET_ACCESS_KEY` env var at runtime on Node.
25
+ * Must be provided together with `accessKeyId`, or both omitted.
26
+ */
27
+ secretAccessKey?: string;
28
+ /** Optional region (defaults to "auto") */
21
29
  region?: string;
22
30
  /** Optional public URL prefix for generated URLs (e.g., CDN URL) */
23
31
  publicUrl?: string;
@@ -181,4 +189,4 @@ declare class EmDashStorageError extends Error {
181
189
  }
182
190
  //#endregion
183
191
  export { ListOptions as a, S3StorageConfig as c, Storage as d, StorageDescriptor as f, FileInfo as i, SignedUploadOptions as l, DownloadResult as n, ListResult as o, UploadResult as p, EmDashStorageError as r, LocalStorageConfig as s, CreateStorageFn as t, SignedUploadUrl as u };
184
- //# sourceMappingURL=types-BljtYPSd.d.mts.map
192
+ //# sourceMappingURL=types-C1-PVaS_.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-C1-PVaS_.d.mts","names":[],"sources":["../src/storage/types.ts"],"mappings":";;AAUA;;;;;;;;UAAiB,eAAA;EAoBhB;EAlBA,QAAA;EAkBS;EAhBT,MAAA;EAsBkC;;;;AAUnC;EA1BC,WAAA;;;;;;EAMA,eAAA;EAwBc;EAtBd,MAAA;EAiC0B;EA/B1B,SAAA;AAAA;;;;UAMgB,kBAAA;EAyByD;EAvBzE,SAAA;EA4B4B;EA1B5B,OAAA;AAAA;;;;UAMgB,iBAAA;EA0BZ;EAxBJ,UAAA;EA8B8B;EA5B9B,MAAA,EAAQ,MAAA;AAAA;;;;;;;;AAwCT;KA7BY,eAAA,IAAmB,MAAA,EAAQ,MAAA,sBAA4B,OAAA;;;;UAKlD,YAAA;EA8BhB;EA5BA,GAAA;EA8BA;EA5BA,GAAA;EA4BS;EA1BT,IAAA;AAAA;;;;UAMgB,cAAA;EAgChB;EA9BA,IAAA,EAAM,cAAA,CAAe,UAAA;EAgCZ;EA9BT,WAAA;EAoCgB;EAlChB,IAAA;AAAA;;;;UAMgB,eAAA;EAgCN;EA9BV,GAAA;EAoCgB;EAlChB,MAAA;;EAEA,OAAA,EAAS,MAAA;EAkCT;EAhCA,SAAA;AAAA;;;;UAMgB,mBAAA;EAsCA;EApChB,GAAA;;EAEA,WAAA;EAoCA;EAlCA,IAAA;EAsCA;EApCA,SAAA;AAAA;AA4CD;;;AAAA,UAtCiB,UAAA;EA4CA;EA1ChB,KAAA,EAAO,QAAA;EA0CsB;EAxC7B,UAAA;AAAA;;;;UAMgB,QAAA;EAyDD;EAvDf,GAAA;EAuD6B;EArD7B,IAAA;EA2D0D;EAzD1D,YAAA,EAAc,IAAA;EAyD2C;EAvDzD,IAAA;AAAA;;;;UAMgB,WAAA;EAoBa;EAlB7B,MAAA;EAmBC;EAjBD,KAAA;EAkBI;EAhBJ,MAAA;AAAA;;;;;;UAQgB,OAAA;EAwBhB;;;EApBA,MAAA,CAAO,OAAA;IACN,GAAA;IACA,IAAA,EAAM,MAAA,GAAS,UAAA,GAAa,cAAA,CAAe,UAAA;IAC3C,WAAA;EAAA,IACG,OAAA,CAAQ,YAAA;EA2BZ;;;EAtBA,QAAA,CAAS,GAAA,WAAc,OAAA,CAAQ,cAAA;EAsB2B;;;;EAhB1D,MAAA,CAAO,GAAA,WAAc,OAAA;EA2BT;;;EAtBZ,MAAA,CAAO,GAAA,WAAc,OAAA;EAsBkB;;;EAjBvC,IAAA,CAAK,OAAA,GAAU,WAAA,GAAc,OAAA,CAAQ,UAAA;EAmBpC;;;;EAbD,kBAAA,CAAmB,OAAA,EAAS,mBAAA,GAAsB,OAAA,CAAQ,eAAA;;;;EAK1D,YAAA,CAAa,GAAA;AAAA;;;;cAMD,kBAAA,SAA2B,KAAA;EAG/B,IAAA;EACS,KAAA;cAFhB,OAAA,UACO,IAAA,UACS,KAAA;AAAA"}
@@ -1,8 +1,106 @@
1
- import { u as FieldType } from "./types-CcreFIIH.mjs";
1
+ import { u as FieldType } from "./types-DPfzHnjW.mjs";
2
2
  import { z } from "astro/zod";
3
- import { Element } from "@emdash-cms/blocks";
4
3
  import { JSX } from "astro/jsx-runtime";
5
4
 
5
+ //#region ../blocks/dist/validation-BG2u9jAE.d.ts
6
+ //#region src/types.d.ts
7
+ interface ConfirmDialog {
8
+ title: string;
9
+ text: string;
10
+ confirm: string;
11
+ deny: string;
12
+ style?: "danger";
13
+ }
14
+ interface ButtonElement {
15
+ type: "button";
16
+ action_id: string;
17
+ label: string;
18
+ style?: "primary" | "danger" | "secondary";
19
+ value?: unknown;
20
+ confirm?: ConfirmDialog;
21
+ }
22
+ interface TextInputElement {
23
+ type: "text_input";
24
+ action_id: string;
25
+ label: string;
26
+ placeholder?: string;
27
+ initial_value?: string;
28
+ multiline?: boolean;
29
+ }
30
+ interface NumberInputElement {
31
+ type: "number_input";
32
+ action_id: string;
33
+ label: string;
34
+ initial_value?: number;
35
+ min?: number;
36
+ max?: number;
37
+ }
38
+ interface SelectElement {
39
+ type: "select";
40
+ action_id: string;
41
+ label: string;
42
+ options: Array<{
43
+ label: string;
44
+ value: string;
45
+ }>;
46
+ initial_value?: string;
47
+ /** Plugin route that returns `{ items: Array<{ id, name }> }` to populate options dynamically */
48
+ optionsRoute?: string;
49
+ }
50
+ interface ToggleElement {
51
+ type: "toggle";
52
+ action_id: string;
53
+ label: string;
54
+ description?: string;
55
+ initial_value?: boolean;
56
+ }
57
+ interface SecretInputElement {
58
+ type: "secret_input";
59
+ action_id: string;
60
+ label: string;
61
+ placeholder?: string;
62
+ has_value?: boolean;
63
+ }
64
+ interface CheckboxElement {
65
+ type: "checkbox";
66
+ action_id: string;
67
+ label: string;
68
+ options: Array<{
69
+ label: string;
70
+ value: string;
71
+ }>;
72
+ initial_value?: string[];
73
+ }
74
+ interface DateInputElement {
75
+ type: "date_input";
76
+ action_id: string;
77
+ label: string;
78
+ initial_value?: string;
79
+ placeholder?: string;
80
+ }
81
+ interface ComboboxElement {
82
+ type: "combobox";
83
+ action_id: string;
84
+ label: string;
85
+ options: Array<{
86
+ label: string;
87
+ value: string;
88
+ }>;
89
+ initial_value?: string;
90
+ placeholder?: string;
91
+ }
92
+ interface RadioElement {
93
+ type: "radio";
94
+ action_id: string;
95
+ label: string;
96
+ options: Array<{
97
+ label: string;
98
+ value: string;
99
+ }>;
100
+ initial_value?: string;
101
+ }
102
+ type Element = ButtonElement | TextInputElement | NumberInputElement | SelectElement | ToggleElement | SecretInputElement | CheckboxElement | DateInputElement | ComboboxElement | RadioElement;
103
+ //#endregion
6
104
  //#region src/plugins/types.d.ts
7
105
  /**
8
106
  * Plugin capabilities determine what APIs are available in context
@@ -1094,5 +1192,5 @@ interface PluginManifest {
1094
1192
  admin: PluginAdminConfig;
1095
1193
  }
1096
1194
  //#endregion
1097
- export { StandardPluginDefinition as $, PageMetadataHandler as A, PluginManifest as B, MediaUploadEvent as C, PageFragmentHandler as D, PageFragmentEvent as E, PluginAdminPage as F, PublicPageContext as G, PluginStorageConfig as H, PluginCapability as I, ResolvedPlugin as J, RequestMeta as K, PluginContext as L, PagePlacement as M, PluginAdminConfig as N, PageMetadataContribution as O, PluginAdminExports as P, StandardHookHandler as Q, PluginDefinition as R, MediaItem as S, PageFragmentContribution as T, PortableTextBlockConfig as U, PluginRoute as V, PortableTextBlockField as W, RouteContext as X, ResolvedPluginHooks as Y, StandardHookEntry as Z, HookName as _, CommentAfterModerateEvent as a, LogAccess as b, CommentBeforeCreateHandler as c, ContentAccess as d, StandardRouteEntry as et, ContentHookEvent as f, HookConfig as g, FieldWidgetConfig as h, CommentAfterCreateHandler as i, isStandardPluginDefinition as it, PageMetadataLinkRel as j, PageMetadataEvent as k, CommentModerateEvent as l, EmailMessage as m, CollectionCommentSettings as n, StorageCollection as nt, CommentAfterModerateHandler as o, CronEvent as p, ResolvedHook as q, CommentAfterCreateEvent as r, StoredComment as rt, CommentBeforeCreateEvent as s, BreadcrumbItem as t, StandardRouteHandler as tt, CommentModerateHandler as u, HttpAccess as v, ModerationDecision as w, MediaAccess as x, KVAccess as y, PluginHooks as z };
1098
- //# sourceMappingURL=types-6dqxBqsH.d.mts.map
1195
+ export { StandardPluginDefinition as $, PageMetadataHandler as A, PluginManifest as B, MediaUploadEvent as C, PageFragmentHandler as D, PageFragmentEvent as E, PluginAdminPage as F, PublicPageContext as G, PluginStorageConfig as H, PluginCapability as I, ResolvedPlugin as J, RequestMeta as K, PluginContext as L, PagePlacement as M, PluginAdminConfig as N, PageMetadataContribution as O, PluginAdminExports as P, StandardHookHandler as Q, PluginDefinition as R, MediaItem as S, PageFragmentContribution as T, PortableTextBlockConfig as U, PluginRoute as V, PortableTextBlockField as W, RouteContext as X, ResolvedPluginHooks as Y, StandardHookEntry as Z, HookName as _, CommentAfterModerateEvent as a, Element as at, LogAccess as b, CommentBeforeCreateHandler as c, ContentAccess as d, StandardRouteEntry as et, ContentHookEvent as f, HookConfig as g, FieldWidgetConfig as h, CommentAfterCreateHandler as i, isStandardPluginDefinition as it, PageMetadataLinkRel as j, PageMetadataEvent as k, CommentModerateEvent as l, EmailMessage as m, CollectionCommentSettings as n, StorageCollection as nt, CommentAfterModerateHandler as o, CronEvent as p, ResolvedHook as q, CommentAfterCreateEvent as r, StoredComment as rt, CommentBeforeCreateEvent as s, BreadcrumbItem as t, StandardRouteHandler as tt, CommentModerateHandler as u, HttpAccess as v, ModerationDecision as w, MediaAccess as x, KVAccess as y, PluginHooks as z };
1196
+ //# sourceMappingURL=types-CaKte3hR.d.mts.map