create-feedbax 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/README.md +17 -5
  2. package/dist/cli.js +2610 -291
  3. package/dist/index.js +2375 -200
  4. package/package.json +7 -7
  5. package/template/.feedbax-template.sha256 +1 -1
  6. package/template/.gitignore.template +1 -0
  7. package/template/.npmignore.template +1 -0
  8. package/template/.nvmrc +1 -0
  9. package/template/AGENTS.md +13 -0
  10. package/template/CONNECTED.md +418 -0
  11. package/template/README.md +97 -0
  12. package/template/components.json +1 -1
  13. package/template/eslint.config.mjs +23 -0
  14. package/template/feedbax-vite.ts +79 -0
  15. package/template/migrations/0001_connected.sql +6 -0
  16. package/template/migrations/0002_participation.sql +8 -0
  17. package/template/migrations/0003_images.sql +3 -0
  18. package/template/migrations/0004_followed_status.sql +9 -0
  19. package/template/migrations/0005_github.sql +4 -0
  20. package/template/migrations/0006_integration.sql +3 -0
  21. package/template/migrations/0007_ownership.sql +2 -0
  22. package/template/migrations/0008_integration_recovery.sql +5 -0
  23. package/template/migrations/0009_observed_history.sql +3 -0
  24. package/template/package.json +39 -34
  25. package/template/src/authorized-draft-post.tsx +1 -0
  26. package/template/src/changelog-page.tsx +63 -54
  27. package/template/src/changelog-runtime.ts +2 -1
  28. package/template/src/cloudflare-workers.d.ts +9 -0
  29. package/template/src/comment-creation.test.tsx +95 -7
  30. package/template/src/comment-thread-page.ts +11 -1
  31. package/template/src/components/command-palette.tsx +59 -30
  32. package/template/src/components/connected-identity-provider.tsx +135 -0
  33. package/template/src/components/device-profile-provider.tsx +41 -26
  34. package/template/src/components/follow-post-button.tsx +78 -0
  35. package/template/src/components/header.tsx +2 -0
  36. package/template/src/components/image-attachments.tsx +187 -0
  37. package/template/src/components/loader.tsx +48 -5
  38. package/template/src/components/notification-inbox.tsx +232 -0
  39. package/template/src/components/portal-footer.tsx +14 -0
  40. package/template/src/components/post-history.tsx +101 -0
  41. package/template/src/components/profile-menu.tsx +37 -0
  42. package/template/src/components/ui/components/command.tsx +30 -29
  43. package/template/src/components/ui/components/message-scroller.tsx +2 -2
  44. package/template/src/components/ui/components/scroll-area.tsx +0 -1
  45. package/template/src/components/ui/components/switch.tsx +29 -0
  46. package/template/src/components/ui/styles/globals.css +1 -1
  47. package/template/src/configured-feedback.test.ts +82 -0
  48. package/template/src/configured-feedback.ts +61 -0
  49. package/template/src/connected-identity.test.ts +148 -0
  50. package/template/src/connected-identity.ts +127 -0
  51. package/template/src/connected-participation.test.tsx +67 -0
  52. package/template/src/connected-runtime.test.ts +244 -0
  53. package/template/src/connected-runtime.ts +176 -0
  54. package/template/src/connected-social.test.tsx +158 -0
  55. package/template/src/feedback-cache-invalidation.ts +3 -0
  56. package/template/src/feedback-runtime.ts +123 -13
  57. package/template/src/feedbax/changelog/index.test.ts +35 -0
  58. package/template/src/feedbax/changelog/index.ts +5 -1
  59. package/template/src/feedbax/changelog/notion-changelog.test.ts +1 -1
  60. package/template/src/feedbax/changelog/notion-changelog.ts +1 -1
  61. package/template/src/feedbax/config/index.test.ts +73 -1
  62. package/template/src/feedbax/config/index.ts +205 -9
  63. package/template/src/feedbax/feedback/comment-storage.ts +38 -0
  64. package/template/src/feedbax/feedback/connected-feedback.ts +235 -0
  65. package/template/src/feedbax/feedback/connected.test.ts +272 -0
  66. package/template/src/feedbax/feedback/connected.ts +225 -0
  67. package/template/src/feedbax/feedback/email-delivery.test.ts +65 -0
  68. package/template/src/feedbax/feedback/email-delivery.ts +66 -0
  69. package/template/src/feedbax/feedback/feedback-storage.ts +1 -0
  70. package/template/src/feedbax/feedback/github-comments.ts +390 -0
  71. package/template/src/feedbax/feedback/github-feedback.test.ts +1138 -0
  72. package/template/src/feedbax/feedback/github-feedback.ts +581 -0
  73. package/template/src/feedbax/feedback/github-request.ts +120 -0
  74. package/template/src/feedbax/feedback/github-service-client.ts +56 -0
  75. package/template/src/feedbax/feedback/identity-handoff.test.ts +81 -0
  76. package/template/src/feedbax/feedback/identity-handoff.ts +119 -0
  77. package/template/src/feedbax/feedback/image-feedback.ts +114 -0
  78. package/template/src/feedbax/feedback/images.test.ts +223 -0
  79. package/template/src/feedbax/feedback/images.ts +350 -0
  80. package/template/src/feedbax/feedback/index.ts +226 -16
  81. package/template/src/feedbax/feedback/integration-auth.ts +138 -0
  82. package/template/src/feedbax/feedback/notion-comments.ts +2 -35
  83. package/template/src/feedbax/feedback/notion-image-review.ts +55 -0
  84. package/template/src/feedbax/feedback/observed-history.test.ts +150 -0
  85. package/template/src/feedbax/feedback/observed-history.ts +219 -0
  86. package/template/src/feedbax/feedback/ownership.ts +170 -0
  87. package/template/src/feedbax/feedback/participation.test.ts +653 -0
  88. package/template/src/feedbax/feedback/participation.ts +502 -0
  89. package/template/src/history-runtime.test.ts +80 -0
  90. package/template/src/history-runtime.ts +37 -0
  91. package/template/src/images-runtime.test.ts +133 -0
  92. package/template/src/images-runtime.ts +125 -0
  93. package/template/src/index.css +103 -440
  94. package/template/src/integration-runtime.test.ts +365 -0
  95. package/template/src/integration-runtime.ts +289 -0
  96. package/template/src/lazy-post-detail-overlay.tsx +13 -2
  97. package/template/src/my-feedback-page.test.tsx +152 -0
  98. package/template/src/my-feedback-page.tsx +398 -0
  99. package/template/src/my-feedback-runtime.test.ts +93 -0
  100. package/template/src/my-feedback-runtime.ts +154 -0
  101. package/template/src/overlay-post-detail.test.tsx +59 -8
  102. package/template/src/overlay-post-detail.tsx +20 -69
  103. package/template/src/participation-client.ts +63 -0
  104. package/template/src/participation-runtime.test.ts +35 -0
  105. package/template/src/participation-runtime.ts +255 -0
  106. package/template/src/portal-comments.ts +35 -5
  107. package/template/src/portal-feedback-form.tsx +29 -6
  108. package/template/src/portal-feedback-runtime.ts +3 -0
  109. package/template/src/portal-feedback-server-function.ts +6 -3
  110. package/template/src/portal-feedback-submission.ts +19 -0
  111. package/template/src/portal-server-entry.ts +13 -0
  112. package/template/src/post-creation-flow.tsx +2 -2
  113. package/template/src/post-detail-content.test.tsx +233 -0
  114. package/template/src/post-detail-content.tsx +162 -0
  115. package/template/src/post-detail-query.ts +34 -0
  116. package/template/src/post-results.tsx +1 -1
  117. package/template/src/public-cache-policy.test.ts +11 -4
  118. package/template/src/public-cache-policy.ts +1 -1
  119. package/template/src/public-feedback-index.tsx +9 -39
  120. package/template/src/public-feedback-page.test.tsx +65 -0
  121. package/template/src/public-feedback-page.ts +12 -1
  122. package/template/src/public-portal-error.tsx +1 -1
  123. package/template/src/public-post-detail-skeleton.tsx +54 -0
  124. package/template/src/public-post-detail.tsx +437 -271
  125. package/template/src/public-post-route.test.tsx +39 -3
  126. package/template/src/public-post-route.tsx +15 -28
  127. package/template/src/public-release-links.test.ts +120 -0
  128. package/template/src/public-release-links.ts +130 -0
  129. package/template/src/public-roadmap-route.test.tsx +38 -2
  130. package/template/src/public-roadmap-view.tsx +56 -23
  131. package/template/src/public-shell.test.tsx +26 -0
  132. package/template/src/routeTree.gen.ts +70 -3
  133. package/template/src/router.tsx +2 -0
  134. package/template/src/routes/__root.tsx +3 -5
  135. package/template/src/routes/email-preferences.tsx +77 -0
  136. package/template/src/routes/my-feedback.tsx +16 -0
  137. package/template/src/routes/submit.tsx +1 -1
  138. package/template/src/safe-public-failure.ts +3 -1
  139. package/template/src/server.ts +87 -2
  140. package/template/src/submission-intent.ts +26 -0
  141. package/template/src/trusted-feedback-handler.test.ts +1 -1
  142. package/template/src/vote-toggle.tsx +42 -9
  143. package/template/vite.config.ts +2 -0
package/dist/cli.js CHANGED
@@ -3,15 +3,19 @@
3
3
  // src/cli.ts
4
4
  import * as prompt from "@clack/prompts";
5
5
  import { spawn } from "node:child_process";
6
- import { readFile as readFile3 } from "node:fs/promises";
6
+ import { readFile as readFile4 } from "node:fs/promises";
7
7
  import { basename as basename2, resolve as resolve2 } from "node:path";
8
8
 
9
9
  // src/index.ts
10
- import { createHash, randomBytes } from "node:crypto";
11
- import { readFile as readFile2, readdir as readdir2, writeFile as writeFile2 } from "node:fs/promises";
10
+ import { createHash as createHash2, randomBytes as randomBytes2 } from "node:crypto";
11
+ import { readFile as readFile3, readdir as readdir2, writeFile as writeFile3 } from "node:fs/promises";
12
12
 
13
13
  // ../config/src/index.ts
14
14
  var rootKeys = [
15
+ "history",
16
+ "provider",
17
+ "github",
18
+ "connected",
15
19
  "product",
16
20
  "theme",
17
21
  "notion",
@@ -54,6 +58,8 @@ var changelogPropertyKeys = [
54
58
  function defineFeedbax(config) {
55
59
  const value = object(config, "Feedbax configuration");
56
60
  rejectUnknown(value, rootKeys, "Feedbax configuration");
61
+ if (value.history !== void 0 && typeof value.history !== "boolean")
62
+ throw new Error("history must be true or false.");
57
63
  const product = object(value.product, "product");
58
64
  rejectUnknown(
59
65
  product,
@@ -68,13 +74,81 @@ function defineFeedbax(config) {
68
74
  ...optionalPublicUrl(product, "supportUrl"),
69
75
  ...optionalPublicUrl(product, "privacyUrl")
70
76
  };
77
+ let connected;
78
+ if (value.connected !== void 0) {
79
+ const input = object(value.connected, "connected");
80
+ rejectUnknown(
81
+ input,
82
+ ["issuer", "loginUrl", "email", "images", "processing"],
83
+ "connected"
84
+ );
85
+ const issuer = publicUrl(input.issuer, "connected.issuer", false);
86
+ const loginUrl = publicUrl(input.loginUrl, "connected.loginUrl", false);
87
+ if (!validatedProduct.siteUrl)
88
+ throw new Error("Connected Profile requires product.siteUrl.");
89
+ if (new URL(loginUrl).username || new URL(loginUrl).password || new URL(loginUrl).hash)
90
+ throw new Error(
91
+ "connected.loginUrl must not contain credentials or a fragment."
92
+ );
93
+ let email;
94
+ if (input.email !== void 0) {
95
+ const sender = object(input.email, "connected.email");
96
+ rejectUnknown(
97
+ sender,
98
+ ["provider", "from", "endpoint"],
99
+ "connected.email"
100
+ );
101
+ if (!["cloudflare", "resend", "custom"].includes(String(sender.provider)))
102
+ throw new Error(
103
+ "connected.email.provider must be cloudflare, resend, or custom."
104
+ );
105
+ const from = text(sender.from, "connected.email.from");
106
+ if (from.length > 254 || !/^[^\s@<>]+@[^\s@<>]+\.[^\s@<>]+$/.test(from))
107
+ throw new Error(
108
+ "connected.email.from must be a plain sender email address."
109
+ );
110
+ const endpoint = sender.endpoint === void 0 ? void 0 : publicUrl(sender.endpoint, "connected.email.endpoint", false);
111
+ if (sender.provider === "custom" && !endpoint)
112
+ throw new Error("Custom email requires connected.email.endpoint.");
113
+ if (sender.provider !== "custom" && endpoint)
114
+ throw new Error("Only custom email uses connected.email.endpoint.");
115
+ if (endpoint && (new URL(endpoint).username || new URL(endpoint).password || new URL(endpoint).hash))
116
+ throw new Error(
117
+ "Custom email endpoint must not contain credentials or a fragment."
118
+ );
119
+ email = {
120
+ provider: sender.provider,
121
+ from,
122
+ ...endpoint ? { endpoint } : {}
123
+ };
124
+ }
125
+ if (input.images !== void 0 && typeof input.images !== "boolean")
126
+ throw new Error("connected.images must be true or false.");
127
+ if (input.processing !== void 0 && input.processing !== "small" && input.processing !== "standard")
128
+ throw new Error("connected.processing must be small or standard.");
129
+ connected = {
130
+ issuer,
131
+ loginUrl,
132
+ ...input.processing !== void 0 ? { processing: input.processing } : {},
133
+ ...email ? { email } : {},
134
+ ...input.images !== void 0 ? { images: input.images } : {}
135
+ };
136
+ }
71
137
  const theme = object(value.theme, "theme");
72
138
  rejectUnknown(theme, ["preset"], "theme");
73
- const notion = storage(
74
- value.notion,
75
- feedbackPropertyKeys,
76
- "notion"
77
- );
139
+ if (value.provider !== void 0 && !["notion", "github"].includes(String(value.provider)))
140
+ throw new Error("provider must be notion or github.");
141
+ const githubMode = value.provider === "github";
142
+ if (githubMode && value.notion !== void 0 || !githubMode && value.github !== void 0)
143
+ throw new Error("Select exactly one Content Provider: notion or github.");
144
+ const content = githubMode ? { provider: "github", github: githubConfiguration(value.github) } : {
145
+ ...value.provider ? { provider: "notion" } : {},
146
+ notion: storage(
147
+ value.notion,
148
+ feedbackPropertyKeys,
149
+ "notion"
150
+ )
151
+ };
78
152
  const features = object(value.features, "features");
79
153
  rejectUnknown(features, featureKeys, "features");
80
154
  const validatedFeatures = Object.fromEntries(
@@ -84,6 +158,10 @@ function defineFeedbax(config) {
84
158
  return [key, features[key]];
85
159
  })
86
160
  );
161
+ if (githubMode && (validatedFeatures.changelog || value.changelog !== void 0))
162
+ throw new Error(
163
+ "GitHub Installations must disable Changelog (features.changelog: false) and remove Notion Changelog configuration."
164
+ );
87
165
  const changelog = value.changelog === void 0 ? void 0 : storage(
88
166
  value.changelog,
89
167
  changelogPropertyKeys,
@@ -110,8 +188,10 @@ function defineFeedbax(config) {
110
188
  throw new Error("Turnstile is enabled but turnstile.siteKey is missing.");
111
189
  return {
112
190
  product: validatedProduct,
191
+ ...value.history !== void 0 ? { history: value.history } : {},
192
+ ...connected ? { connected } : {},
113
193
  theme: { preset: text(theme.preset, "theme.preset") },
114
- notion,
194
+ ...content,
115
195
  ...changelog ? { changelog } : {},
116
196
  features: validatedFeatures,
117
197
  submissions: {
@@ -176,9 +256,44 @@ function publicUrl(value, label, allowRelative) {
176
256
  `${label} must be ${allowRelative ? "root-relative or " : ""}an HTTPS URL.`
177
257
  );
178
258
  }
259
+ function githubConfiguration(value) {
260
+ const input = object(value, "github");
261
+ rejectUnknown(
262
+ input,
263
+ ["installationId", "serviceUrl", "repository", "botUserId"],
264
+ "github"
265
+ );
266
+ const installationId = text(input.installationId, "github.installationId");
267
+ if (!/^[a-zA-Z0-9_-]{1,128}$/.test(installationId))
268
+ throw new Error(
269
+ "github.installationId must be a stable Installation identifier."
270
+ );
271
+ const serviceUrl = publicUrl(input.serviceUrl, "github.serviceUrl", false);
272
+ const url = new URL(serviceUrl);
273
+ if (url.origin !== serviceUrl || url.username || url.password)
274
+ throw new Error(
275
+ "github.serviceUrl must be an HTTPS origin without a path or credentials."
276
+ );
277
+ const repository = object(input.repository, "github.repository");
278
+ rejectUnknown(repository, ["id", "owner", "name"], "github.repository");
279
+ const owner = text(repository.owner, "github.repository.owner");
280
+ const name = text(repository.name, "github.repository.name");
281
+ if (!/^[A-Za-z0-9-]+$/.test(owner) || !/^[A-Za-z0-9_.-]+$/.test(name))
282
+ throw new Error("Use a valid GitHub repository owner and name.");
283
+ return {
284
+ installationId,
285
+ serviceUrl,
286
+ repository: {
287
+ id: positiveInteger(repository.id, "github.repository.id"),
288
+ owner,
289
+ name
290
+ },
291
+ botUserId: positiveInteger(input.botUserId, "github.botUserId")
292
+ };
293
+ }
179
294
 
180
295
  // src/index.ts
181
- import { basename, join as join2 } from "node:path";
296
+ import { basename, join as join3 } from "node:path";
182
297
 
183
298
  // src/template.ts
184
299
  import { parse } from "jsonc-parser";
@@ -628,194 +743,2150 @@ function isPropertyMap(value) {
628
743
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
629
744
  }
630
745
 
631
- // ../feedback/src/index.ts
632
- var postCreationQueue = Promise.resolve();
746
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/entity.js
747
+ var entityKind = /* @__PURE__ */ Symbol.for("drizzle:entityKind");
748
+ function is(value, type) {
749
+ if (!value || typeof value !== "object") {
750
+ return false;
751
+ }
752
+ if (value instanceof type) {
753
+ return true;
754
+ }
755
+ if (!Object.prototype.hasOwnProperty.call(type, entityKind)) {
756
+ throw new Error(
757
+ `Class "${type.name ?? "<unknown>"}" doesn't look like a Drizzle entity. If this is incorrect and the class is provided by Drizzle, please report this as a bug.`
758
+ );
759
+ }
760
+ let cls = Object.getPrototypeOf(value).constructor;
761
+ if (cls) {
762
+ while (cls) {
763
+ if (entityKind in cls && cls[entityKind] === type[entityKind]) {
764
+ return true;
765
+ }
766
+ cls = Object.getPrototypeOf(cls);
767
+ }
768
+ }
769
+ return false;
770
+ }
633
771
 
634
- // ../changelog/src/notion-changelog.ts
635
- var NOTION_API_URL2 = "https://api.notion.com/v1";
636
- var NOTION_API_VERSION2 = "2026-03-11";
637
- var canonicalProperties2 = {
638
- title: { name: "Title", type: "title", schema: { title: {} } },
639
- slug: { name: "Slug", type: "rich_text", schema: { rich_text: {} } },
640
- date: { name: "Date", type: "date", schema: { date: {} } },
641
- summary: {
642
- name: "Summary",
643
- type: "rich_text",
644
- schema: { rich_text: {} }
645
- },
646
- body: { name: "Body", type: "rich_text", schema: { rich_text: {} } },
647
- labels: {
648
- name: "Labels",
649
- type: "multi_select",
650
- schema: { multi_select: { options: [] } }
651
- },
652
- image: { name: "Image", type: "files", schema: { files: {} } },
653
- published: {
654
- name: "Published",
655
- type: "checkbox",
656
- schema: { checkbox: {} }
657
- },
658
- createdAt: {
659
- name: "Created At",
660
- type: "created_time",
661
- schema: { created_time: {} }
662
- },
663
- updatedAt: {
664
- name: "Updated At",
665
- type: "last_edited_time",
666
- schema: { last_edited_time: {} }
772
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/column.js
773
+ var Column = class {
774
+ constructor(table, config) {
775
+ this.table = table;
776
+ this.config = config;
777
+ this.name = config.name;
778
+ this.keyAsName = config.keyAsName;
779
+ this.notNull = config.notNull;
780
+ this.default = config.default;
781
+ this.defaultFn = config.defaultFn;
782
+ this.onUpdateFn = config.onUpdateFn;
783
+ this.hasDefault = config.hasDefault;
784
+ this.primary = config.primaryKey;
785
+ this.isUnique = config.isUnique;
786
+ this.uniqueName = config.uniqueName;
787
+ this.uniqueType = config.uniqueType;
788
+ this.dataType = config.dataType;
789
+ this.columnType = config.columnType;
790
+ this.generated = config.generated;
791
+ this.generatedIdentity = config.generatedIdentity;
792
+ }
793
+ static [entityKind] = "Column";
794
+ name;
795
+ keyAsName;
796
+ primary;
797
+ notNull;
798
+ default;
799
+ defaultFn;
800
+ onUpdateFn;
801
+ hasDefault;
802
+ isUnique;
803
+ uniqueName;
804
+ uniqueType;
805
+ dataType;
806
+ columnType;
807
+ enumValues = void 0;
808
+ generated = void 0;
809
+ generatedIdentity = void 0;
810
+ config;
811
+ mapFromDriverValue(value) {
812
+ return value;
813
+ }
814
+ mapToDriverValue(value) {
815
+ return value;
816
+ }
817
+ // ** @internal */
818
+ shouldDisableInsert() {
819
+ return this.config.generated !== void 0 && this.config.generated.type !== "byDefault";
667
820
  }
668
821
  };
669
- async function createNotionChangelogDataSource({
670
- token,
671
- databaseId,
672
- request = fetch
673
- }) {
674
- const response = await request(`${NOTION_API_URL2}/data_sources`, {
675
- method: "POST",
676
- headers: notionHeaders2(token),
677
- body: JSON.stringify({
678
- parent: { database_id: databaseId },
679
- title: [{ type: "text", text: { content: "Changelog" } }],
680
- properties: Object.fromEntries(
681
- Object.values(canonicalProperties2).map(({ name, schema }) => [
682
- name,
683
- schema
684
- ])
685
- )
686
- })
687
- });
688
- return configurationFromResponse(
689
- await readObject(response, "Changelog Data Source"),
690
- databaseId
691
- );
692
- }
693
- async function validateNotionChangelogDataSource({
694
- token,
695
- dataSourceId,
696
- propertyIds: propertyIds2,
697
- request = fetch
698
- }) {
699
- const response = await request(
700
- `${NOTION_API_URL2}/data_sources/${dataSourceId}`,
701
- { headers: notionHeaders2(token) }
702
- );
703
- const dataSource = await readObject(response, "Changelog Data Source");
704
- const properties = recordAt(dataSource, "properties");
705
- const propertiesById = new Map(
706
- Object.values(properties).flatMap((property) => {
707
- if (!isRecord(property) || typeof property.id !== "string") return [];
708
- return [[property.id, property]];
709
- })
710
- );
711
- const repairs = [];
712
- for (const [key, { name, type }] of Object.entries(canonicalProperties2)) {
713
- const configuredId = propertyIds2[key];
714
- const property = propertiesById.get(configuredId);
715
- if (!property) {
716
- repairs.push(
717
- `Restore the "${name}" property with ID "${configuredId}" and type "${type}", then update the configured property ID if Notion assigns a new one.`
718
- );
719
- } else if (property.type !== type) {
720
- repairs.push(
721
- `Change the property configured as "${name}" (ID "${configuredId}") from "${String(property.type)}" to "${type}", or restore a ${type} property and configure its ID.`
722
- );
822
+
823
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/column-builder.js
824
+ var ColumnBuilder = class {
825
+ static [entityKind] = "ColumnBuilder";
826
+ config;
827
+ constructor(name, dataType, columnType) {
828
+ this.config = {
829
+ name,
830
+ keyAsName: name === "",
831
+ notNull: false,
832
+ default: void 0,
833
+ hasDefault: false,
834
+ primaryKey: false,
835
+ isUnique: false,
836
+ uniqueName: void 0,
837
+ uniqueType: void 0,
838
+ dataType,
839
+ columnType,
840
+ generated: void 0
841
+ };
842
+ }
843
+ /**
844
+ * Changes the data type of the column. Commonly used with `json` columns. Also, useful for branded types.
845
+ *
846
+ * @example
847
+ * ```ts
848
+ * const users = pgTable('users', {
849
+ * id: integer('id').$type<UserId>().primaryKey(),
850
+ * details: json('details').$type<UserDetails>().notNull(),
851
+ * });
852
+ * ```
853
+ */
854
+ $type() {
855
+ return this;
856
+ }
857
+ /**
858
+ * Adds a `not null` clause to the column definition.
859
+ *
860
+ * Affects the `select` model of the table - columns *without* `not null` will be nullable on select.
861
+ */
862
+ notNull() {
863
+ this.config.notNull = true;
864
+ return this;
865
+ }
866
+ /**
867
+ * Adds a `default <value>` clause to the column definition.
868
+ *
869
+ * Affects the `insert` model of the table - columns *with* `default` are optional on insert.
870
+ *
871
+ * If you need to set a dynamic default value, use {@link $defaultFn} instead.
872
+ */
873
+ default(value) {
874
+ this.config.default = value;
875
+ this.config.hasDefault = true;
876
+ return this;
877
+ }
878
+ /**
879
+ * Adds a dynamic default value to the column.
880
+ * The function will be called when the row is inserted, and the returned value will be used as the column value.
881
+ *
882
+ * **Note:** This value does not affect the `drizzle-kit` behavior, it is only used at runtime in `drizzle-orm`.
883
+ */
884
+ $defaultFn(fn) {
885
+ this.config.defaultFn = fn;
886
+ this.config.hasDefault = true;
887
+ return this;
888
+ }
889
+ /**
890
+ * Alias for {@link $defaultFn}.
891
+ */
892
+ $default = this.$defaultFn;
893
+ /**
894
+ * Adds a dynamic update value to the column.
895
+ * The function will be called when the row is updated, and the returned value will be used as the column value if none is provided.
896
+ * If no `default` (or `$defaultFn`) value is provided, the function will be called when the row is inserted as well, and the returned value will be used as the column value.
897
+ *
898
+ * **Note:** This value does not affect the `drizzle-kit` behavior, it is only used at runtime in `drizzle-orm`.
899
+ */
900
+ $onUpdateFn(fn) {
901
+ this.config.onUpdateFn = fn;
902
+ this.config.hasDefault = true;
903
+ return this;
904
+ }
905
+ /**
906
+ * Alias for {@link $onUpdateFn}.
907
+ */
908
+ $onUpdate = this.$onUpdateFn;
909
+ /**
910
+ * Adds a `primary key` clause to the column definition. This implicitly makes the column `not null`.
911
+ *
912
+ * In SQLite, `integer primary key` implicitly makes the column auto-incrementing.
913
+ */
914
+ primaryKey() {
915
+ this.config.primaryKey = true;
916
+ this.config.notNull = true;
917
+ return this;
918
+ }
919
+ /** @internal Sets the name of the column to the key within the table definition if a name was not given. */
920
+ setName(name) {
921
+ if (this.config.name !== "") return;
922
+ this.config.name = name;
923
+ }
924
+ };
925
+
926
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/table.utils.js
927
+ var TableName = /* @__PURE__ */ Symbol.for("drizzle:Name");
928
+
929
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/pg-core/foreign-keys.js
930
+ var ForeignKeyBuilder = class {
931
+ static [entityKind] = "PgForeignKeyBuilder";
932
+ /** @internal */
933
+ reference;
934
+ /** @internal */
935
+ _onUpdate = "no action";
936
+ /** @internal */
937
+ _onDelete = "no action";
938
+ constructor(config, actions) {
939
+ this.reference = () => {
940
+ const { name, columns, foreignColumns } = config();
941
+ return { name, columns, foreignTable: foreignColumns[0].table, foreignColumns };
942
+ };
943
+ if (actions) {
944
+ this._onUpdate = actions.onUpdate;
945
+ this._onDelete = actions.onDelete;
723
946
  }
724
947
  }
725
- if (repairs.length) throw new Error(repairs.join("\n"));
726
- return {
727
- databaseId: databaseIdFromResponse(dataSource),
728
- dataSourceId,
729
- propertyIds: { ...propertyIds2 }
730
- };
731
- }
732
- function configurationFromResponse(dataSource, databaseId) {
733
- const properties = recordAt(dataSource, "properties");
734
- return {
735
- databaseId,
736
- dataSourceId: requiredString(dataSource.id),
737
- propertyIds: Object.fromEntries(
738
- Object.entries(canonicalProperties2).map(([key, { name }]) => [
739
- key,
740
- requiredString(recordAt(properties, name).id)
741
- ])
742
- )
743
- };
744
- }
745
- function databaseIdFromResponse(dataSource) {
746
- return requiredString(recordAt(dataSource, "parent").database_id);
948
+ onUpdate(action) {
949
+ this._onUpdate = action === void 0 ? "no action" : action;
950
+ return this;
951
+ }
952
+ onDelete(action) {
953
+ this._onDelete = action === void 0 ? "no action" : action;
954
+ return this;
955
+ }
956
+ /** @internal */
957
+ build(table) {
958
+ return new ForeignKey(table, this);
959
+ }
960
+ };
961
+ var ForeignKey = class {
962
+ constructor(table, builder) {
963
+ this.table = table;
964
+ this.reference = builder.reference;
965
+ this.onUpdate = builder._onUpdate;
966
+ this.onDelete = builder._onDelete;
967
+ }
968
+ static [entityKind] = "PgForeignKey";
969
+ reference;
970
+ onUpdate;
971
+ onDelete;
972
+ getName() {
973
+ const { name, columns, foreignColumns } = this.reference();
974
+ const columnNames = columns.map((column) => column.name);
975
+ const foreignColumnNames = foreignColumns.map((column) => column.name);
976
+ const chunks = [
977
+ this.table[TableName],
978
+ ...columnNames,
979
+ foreignColumns[0].table[TableName],
980
+ ...foreignColumnNames
981
+ ];
982
+ return name ?? `${chunks.join("_")}_fk`;
983
+ }
984
+ };
985
+
986
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/tracing-utils.js
987
+ function iife(fn, ...args) {
988
+ return fn(...args);
747
989
  }
748
- function notionHeaders2(token) {
749
- return {
750
- Authorization: `Bearer ${token}`,
751
- "Content-Type": "application/json",
752
- "Notion-Version": NOTION_API_VERSION2
753
- };
990
+
991
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/pg-core/unique-constraint.js
992
+ function uniqueKeyName(table, columns) {
993
+ return `${table[TableName]}_${columns.join("_")}_unique`;
754
994
  }
755
- async function readObject(response, subject) {
756
- const body = await response.json();
757
- if (!response.ok) {
758
- throw new Error(
759
- `Notion rejected the ${subject} request (${response.status}).`
760
- );
995
+ var UniqueConstraintBuilder = class {
996
+ constructor(columns, name) {
997
+ this.name = name;
998
+ this.columns = columns;
761
999
  }
762
- if (!isRecord(body))
763
- throw new Error(`Notion returned an invalid ${subject} response.`);
764
- return body;
765
- }
766
- function recordAt(value, key) {
767
- return record(value[key]);
1000
+ static [entityKind] = "PgUniqueConstraintBuilder";
1001
+ /** @internal */
1002
+ columns;
1003
+ /** @internal */
1004
+ nullsNotDistinctConfig = false;
1005
+ nullsNotDistinct() {
1006
+ this.nullsNotDistinctConfig = true;
1007
+ return this;
1008
+ }
1009
+ /** @internal */
1010
+ build(table) {
1011
+ return new UniqueConstraint(table, this.columns, this.nullsNotDistinctConfig, this.name);
1012
+ }
1013
+ };
1014
+ var UniqueOnConstraintBuilder = class {
1015
+ static [entityKind] = "PgUniqueOnConstraintBuilder";
1016
+ /** @internal */
1017
+ name;
1018
+ constructor(name) {
1019
+ this.name = name;
1020
+ }
1021
+ on(...columns) {
1022
+ return new UniqueConstraintBuilder(columns, this.name);
1023
+ }
1024
+ };
1025
+ var UniqueConstraint = class {
1026
+ constructor(table, columns, nullsNotDistinct, name) {
1027
+ this.table = table;
1028
+ this.columns = columns;
1029
+ this.name = name ?? uniqueKeyName(this.table, this.columns.map((column) => column.name));
1030
+ this.nullsNotDistinct = nullsNotDistinct;
1031
+ }
1032
+ static [entityKind] = "PgUniqueConstraint";
1033
+ columns;
1034
+ name;
1035
+ nullsNotDistinct = false;
1036
+ getName() {
1037
+ return this.name;
1038
+ }
1039
+ };
1040
+
1041
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/pg-core/utils/array.js
1042
+ function parsePgArrayValue(arrayString, startFrom, inQuotes) {
1043
+ for (let i = startFrom; i < arrayString.length; i++) {
1044
+ const char = arrayString[i];
1045
+ if (char === "\\") {
1046
+ i++;
1047
+ continue;
1048
+ }
1049
+ if (char === '"') {
1050
+ return [arrayString.slice(startFrom, i).replace(/\\/g, ""), i + 1];
1051
+ }
1052
+ if (inQuotes) {
1053
+ continue;
1054
+ }
1055
+ if (char === "," || char === "}") {
1056
+ return [arrayString.slice(startFrom, i).replace(/\\/g, ""), i];
1057
+ }
1058
+ }
1059
+ return [arrayString.slice(startFrom).replace(/\\/g, ""), arrayString.length];
768
1060
  }
769
- function record(value) {
770
- if (!isRecord(value))
771
- throw new Error("Notion returned an invalid Changelog response.");
772
- return value;
1061
+ function parsePgNestedArray(arrayString, startFrom = 0) {
1062
+ const result = [];
1063
+ let i = startFrom;
1064
+ let lastCharIsComma = false;
1065
+ while (i < arrayString.length) {
1066
+ const char = arrayString[i];
1067
+ if (char === ",") {
1068
+ if (lastCharIsComma || i === startFrom) {
1069
+ result.push("");
1070
+ }
1071
+ lastCharIsComma = true;
1072
+ i++;
1073
+ continue;
1074
+ }
1075
+ lastCharIsComma = false;
1076
+ if (char === "\\") {
1077
+ i += 2;
1078
+ continue;
1079
+ }
1080
+ if (char === '"') {
1081
+ const [value2, startFrom2] = parsePgArrayValue(arrayString, i + 1, true);
1082
+ result.push(value2);
1083
+ i = startFrom2;
1084
+ continue;
1085
+ }
1086
+ if (char === "}") {
1087
+ return [result, i + 1];
1088
+ }
1089
+ if (char === "{") {
1090
+ const [value2, startFrom2] = parsePgNestedArray(arrayString, i + 1);
1091
+ result.push(value2);
1092
+ i = startFrom2;
1093
+ continue;
1094
+ }
1095
+ const [value, newStartFrom] = parsePgArrayValue(arrayString, i, false);
1096
+ result.push(value);
1097
+ i = newStartFrom;
1098
+ }
1099
+ return [result, i];
773
1100
  }
774
- function requiredString(value) {
775
- if (typeof value !== "string")
776
- throw new Error("Notion returned an invalid Changelog response.");
777
- return value;
1101
+ function parsePgArray(arrayString) {
1102
+ const [result] = parsePgNestedArray(arrayString, 1);
1103
+ return result;
778
1104
  }
779
- function isRecord(value) {
780
- return typeof value === "object" && value !== null && !Array.isArray(value);
1105
+ function makePgArray(array) {
1106
+ return `{${array.map((item) => {
1107
+ if (Array.isArray(item)) {
1108
+ return makePgArray(item);
1109
+ }
1110
+ if (typeof item === "string") {
1111
+ return `"${item.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
1112
+ }
1113
+ return `${item}`;
1114
+ }).join(",")}}`;
781
1115
  }
782
1116
 
783
- // src/index.ts
784
- async function provisionGuidedInstallation({
785
- destination,
786
- product,
787
- themePreset,
788
- workerName,
789
- commentProbePageId,
790
- features,
791
- feedbackSelection,
792
- changelogSelection,
793
- notionToken,
794
- turnstile,
795
- preview,
796
- request = fetch
797
- }) {
798
- try {
799
- if ((await readdir2(destination)).length)
800
- throw new Error(
801
- `Refusing to generate into non-empty destination: ${destination}`
1117
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/pg-core/columns/common.js
1118
+ var PgColumnBuilder = class extends ColumnBuilder {
1119
+ foreignKeyConfigs = [];
1120
+ static [entityKind] = "PgColumnBuilder";
1121
+ array(size) {
1122
+ return new PgArrayBuilder(this.config.name, this, size);
1123
+ }
1124
+ references(ref, actions = {}) {
1125
+ this.foreignKeyConfigs.push({ ref, actions });
1126
+ return this;
1127
+ }
1128
+ unique(name, config) {
1129
+ this.config.isUnique = true;
1130
+ this.config.uniqueName = name;
1131
+ this.config.uniqueType = config?.nulls;
1132
+ return this;
1133
+ }
1134
+ generatedAlwaysAs(as) {
1135
+ this.config.generated = {
1136
+ as,
1137
+ type: "always",
1138
+ mode: "stored"
1139
+ };
1140
+ return this;
1141
+ }
1142
+ /** @internal */
1143
+ buildForeignKeys(column, table) {
1144
+ return this.foreignKeyConfigs.map(({ ref, actions }) => {
1145
+ return iife(
1146
+ (ref2, actions2) => {
1147
+ const builder = new ForeignKeyBuilder(() => {
1148
+ const foreignColumn = ref2();
1149
+ return { columns: [column], foreignColumns: [foreignColumn] };
1150
+ });
1151
+ if (actions2.onUpdate) {
1152
+ builder.onUpdate(actions2.onUpdate);
1153
+ }
1154
+ if (actions2.onDelete) {
1155
+ builder.onDelete(actions2.onDelete);
1156
+ }
1157
+ return builder.build(table);
1158
+ },
1159
+ ref,
1160
+ actions
802
1161
  );
803
- } catch (error) {
804
- if (error.code !== "ENOENT") throw error;
1162
+ });
805
1163
  }
806
- if (product.siteUrl && new URL(product.siteUrl).protocol !== "https:")
807
- throw new Error("product.siteUrl must be an HTTPS URL.");
808
- const targetName = workerName ?? basename(destination).toLowerCase().replace(/[^a-z0-9-]/g, "-");
809
- if (!/^[a-z0-9][a-z0-9-]{0,62}$/.test(targetName))
810
- throw new Error(
811
- "Worker name must contain 1\u201363 lowercase letters, numbers, or hyphens."
812
- );
813
- const approved = await preview(
814
- feedbackSelection.kind === "create" ? `Create the canonical Feedback Data Source inside Notion page ${feedbackSelection.parentPageId}.` : `Validate existing Feedback Data Source ${feedbackSelection.configuration.dataSourceId} without changing it.`
815
- );
816
- if (!approved)
817
- throw new Error(
818
- "Feedback setup was not approved. No local files were created."
1164
+ /** @internal */
1165
+ buildExtraConfigColumn(table) {
1166
+ return new ExtraConfigColumn(table, this.config);
1167
+ }
1168
+ };
1169
+ var PgColumn = class extends Column {
1170
+ constructor(table, config) {
1171
+ if (!config.uniqueName) {
1172
+ config.uniqueName = uniqueKeyName(table, [config.name]);
1173
+ }
1174
+ super(table, config);
1175
+ this.table = table;
1176
+ }
1177
+ static [entityKind] = "PgColumn";
1178
+ };
1179
+ var ExtraConfigColumn = class extends PgColumn {
1180
+ static [entityKind] = "ExtraConfigColumn";
1181
+ getSQLType() {
1182
+ return this.getSQLType();
1183
+ }
1184
+ indexConfig = {
1185
+ order: this.config.order ?? "asc",
1186
+ nulls: this.config.nulls ?? "last",
1187
+ opClass: this.config.opClass
1188
+ };
1189
+ defaultConfig = {
1190
+ order: "asc",
1191
+ nulls: "last",
1192
+ opClass: void 0
1193
+ };
1194
+ asc() {
1195
+ this.indexConfig.order = "asc";
1196
+ return this;
1197
+ }
1198
+ desc() {
1199
+ this.indexConfig.order = "desc";
1200
+ return this;
1201
+ }
1202
+ nullsFirst() {
1203
+ this.indexConfig.nulls = "first";
1204
+ return this;
1205
+ }
1206
+ nullsLast() {
1207
+ this.indexConfig.nulls = "last";
1208
+ return this;
1209
+ }
1210
+ /**
1211
+ * ### PostgreSQL documentation quote
1212
+ *
1213
+ * > An operator class with optional parameters can be specified for each column of an index.
1214
+ * The operator class identifies the operators to be used by the index for that column.
1215
+ * For example, a B-tree index on four-byte integers would use the int4_ops class;
1216
+ * this operator class includes comparison functions for four-byte integers.
1217
+ * In practice the default operator class for the column's data type is usually sufficient.
1218
+ * The main point of having operator classes is that for some data types, there could be more than one meaningful ordering.
1219
+ * For example, we might want to sort a complex-number data type either by absolute value or by real part.
1220
+ * We could do this by defining two operator classes for the data type and then selecting the proper class when creating an index.
1221
+ * More information about operator classes check:
1222
+ *
1223
+ * ### Useful links
1224
+ * https://www.postgresql.org/docs/current/sql-createindex.html
1225
+ *
1226
+ * https://www.postgresql.org/docs/current/indexes-opclass.html
1227
+ *
1228
+ * https://www.postgresql.org/docs/current/xindex.html
1229
+ *
1230
+ * ### Additional types
1231
+ * If you have the `pg_vector` extension installed in your database, you can use the
1232
+ * `vector_l2_ops`, `vector_ip_ops`, `vector_cosine_ops`, `vector_l1_ops`, `bit_hamming_ops`, `bit_jaccard_ops`, `halfvec_l2_ops`, `sparsevec_l2_ops` options, which are predefined types.
1233
+ *
1234
+ * **You can always specify any string you want in the operator class, in case Drizzle doesn't have it natively in its types**
1235
+ *
1236
+ * @param opClass
1237
+ * @returns
1238
+ */
1239
+ op(opClass) {
1240
+ this.indexConfig.opClass = opClass;
1241
+ return this;
1242
+ }
1243
+ };
1244
+ var IndexedColumn = class {
1245
+ static [entityKind] = "IndexedColumn";
1246
+ constructor(name, keyAsName, type, indexConfig) {
1247
+ this.name = name;
1248
+ this.keyAsName = keyAsName;
1249
+ this.type = type;
1250
+ this.indexConfig = indexConfig;
1251
+ }
1252
+ name;
1253
+ keyAsName;
1254
+ type;
1255
+ indexConfig;
1256
+ };
1257
+ var PgArrayBuilder = class extends PgColumnBuilder {
1258
+ static [entityKind] = "PgArrayBuilder";
1259
+ constructor(name, baseBuilder, size) {
1260
+ super(name, "array", "PgArray");
1261
+ this.config.baseBuilder = baseBuilder;
1262
+ this.config.size = size;
1263
+ }
1264
+ /** @internal */
1265
+ build(table) {
1266
+ const baseColumn = this.config.baseBuilder.build(table);
1267
+ return new PgArray(
1268
+ table,
1269
+ this.config,
1270
+ baseColumn
1271
+ );
1272
+ }
1273
+ };
1274
+ var PgArray = class _PgArray extends PgColumn {
1275
+ constructor(table, config, baseColumn, range) {
1276
+ super(table, config);
1277
+ this.baseColumn = baseColumn;
1278
+ this.range = range;
1279
+ this.size = config.size;
1280
+ }
1281
+ size;
1282
+ static [entityKind] = "PgArray";
1283
+ getSQLType() {
1284
+ return `${this.baseColumn.getSQLType()}[${typeof this.size === "number" ? this.size : ""}]`;
1285
+ }
1286
+ mapFromDriverValue(value) {
1287
+ if (typeof value === "string") {
1288
+ value = parsePgArray(value);
1289
+ }
1290
+ return value.map((v) => this.baseColumn.mapFromDriverValue(v));
1291
+ }
1292
+ mapToDriverValue(value, isNestedArray = false) {
1293
+ const a = value.map(
1294
+ (v) => v === null ? null : is(this.baseColumn, _PgArray) ? this.baseColumn.mapToDriverValue(v, true) : this.baseColumn.mapToDriverValue(v)
1295
+ );
1296
+ if (isNestedArray) return a;
1297
+ return makePgArray(a);
1298
+ }
1299
+ };
1300
+
1301
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/pg-core/columns/enum.js
1302
+ var PgEnumObjectColumnBuilder = class extends PgColumnBuilder {
1303
+ static [entityKind] = "PgEnumObjectColumnBuilder";
1304
+ constructor(name, enumInstance) {
1305
+ super(name, "string", "PgEnumObjectColumn");
1306
+ this.config.enum = enumInstance;
1307
+ }
1308
+ /** @internal */
1309
+ build(table) {
1310
+ return new PgEnumObjectColumn(
1311
+ table,
1312
+ this.config
1313
+ );
1314
+ }
1315
+ };
1316
+ var PgEnumObjectColumn = class extends PgColumn {
1317
+ static [entityKind] = "PgEnumObjectColumn";
1318
+ enum;
1319
+ enumValues = this.config.enum.enumValues;
1320
+ constructor(table, config) {
1321
+ super(table, config);
1322
+ this.enum = config.enum;
1323
+ }
1324
+ getSQLType() {
1325
+ return this.enum.enumName;
1326
+ }
1327
+ };
1328
+ var isPgEnumSym = /* @__PURE__ */ Symbol.for("drizzle:isPgEnum");
1329
+ function isPgEnum(obj) {
1330
+ return !!obj && typeof obj === "function" && isPgEnumSym in obj && obj[isPgEnumSym] === true;
1331
+ }
1332
+ var PgEnumColumnBuilder = class extends PgColumnBuilder {
1333
+ static [entityKind] = "PgEnumColumnBuilder";
1334
+ constructor(name, enumInstance) {
1335
+ super(name, "string", "PgEnumColumn");
1336
+ this.config.enum = enumInstance;
1337
+ }
1338
+ /** @internal */
1339
+ build(table) {
1340
+ return new PgEnumColumn(
1341
+ table,
1342
+ this.config
1343
+ );
1344
+ }
1345
+ };
1346
+ var PgEnumColumn = class extends PgColumn {
1347
+ static [entityKind] = "PgEnumColumn";
1348
+ enum = this.config.enum;
1349
+ enumValues = this.config.enum.enumValues;
1350
+ constructor(table, config) {
1351
+ super(table, config);
1352
+ this.enum = config.enum;
1353
+ }
1354
+ getSQLType() {
1355
+ return this.enum.enumName;
1356
+ }
1357
+ };
1358
+
1359
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/subquery.js
1360
+ var Subquery = class {
1361
+ static [entityKind] = "Subquery";
1362
+ constructor(sql2, fields, alias, isWith = false, usedTables = []) {
1363
+ this._ = {
1364
+ brand: "Subquery",
1365
+ sql: sql2,
1366
+ selectedFields: fields,
1367
+ alias,
1368
+ isWith,
1369
+ usedTables
1370
+ };
1371
+ }
1372
+ // getSQL(): SQL<unknown> {
1373
+ // return new SQL([this]);
1374
+ // }
1375
+ };
1376
+ var WithSubquery = class extends Subquery {
1377
+ static [entityKind] = "WithSubquery";
1378
+ };
1379
+
1380
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/version.js
1381
+ var version = "0.45.4";
1382
+
1383
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/tracing.js
1384
+ var otel;
1385
+ var rawTracer;
1386
+ var tracer = {
1387
+ startActiveSpan(name, fn) {
1388
+ if (!otel) {
1389
+ return fn();
1390
+ }
1391
+ if (!rawTracer) {
1392
+ rawTracer = otel.trace.getTracer("drizzle-orm", version);
1393
+ }
1394
+ return iife(
1395
+ (otel2, rawTracer2) => rawTracer2.startActiveSpan(
1396
+ name,
1397
+ (span) => {
1398
+ try {
1399
+ return fn(span);
1400
+ } catch (e) {
1401
+ span.setStatus({
1402
+ code: otel2.SpanStatusCode.ERROR,
1403
+ message: e instanceof Error ? e.message : "Unknown error"
1404
+ // eslint-disable-line no-instanceof/no-instanceof
1405
+ });
1406
+ throw e;
1407
+ } finally {
1408
+ span.end();
1409
+ }
1410
+ }
1411
+ ),
1412
+ otel,
1413
+ rawTracer
1414
+ );
1415
+ }
1416
+ };
1417
+
1418
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/view-common.js
1419
+ var ViewBaseConfig = /* @__PURE__ */ Symbol.for("drizzle:ViewBaseConfig");
1420
+
1421
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/table.js
1422
+ var Schema = /* @__PURE__ */ Symbol.for("drizzle:Schema");
1423
+ var Columns = /* @__PURE__ */ Symbol.for("drizzle:Columns");
1424
+ var ExtraConfigColumns = /* @__PURE__ */ Symbol.for("drizzle:ExtraConfigColumns");
1425
+ var OriginalName = /* @__PURE__ */ Symbol.for("drizzle:OriginalName");
1426
+ var BaseName = /* @__PURE__ */ Symbol.for("drizzle:BaseName");
1427
+ var IsAlias = /* @__PURE__ */ Symbol.for("drizzle:IsAlias");
1428
+ var ExtraConfigBuilder = /* @__PURE__ */ Symbol.for("drizzle:ExtraConfigBuilder");
1429
+ var IsDrizzleTable = /* @__PURE__ */ Symbol.for("drizzle:IsDrizzleTable");
1430
+ var Table = class {
1431
+ static [entityKind] = "Table";
1432
+ /** @internal */
1433
+ static Symbol = {
1434
+ Name: TableName,
1435
+ Schema,
1436
+ OriginalName,
1437
+ Columns,
1438
+ ExtraConfigColumns,
1439
+ BaseName,
1440
+ IsAlias,
1441
+ ExtraConfigBuilder
1442
+ };
1443
+ /**
1444
+ * @internal
1445
+ * Can be changed if the table is aliased.
1446
+ */
1447
+ [TableName];
1448
+ /**
1449
+ * @internal
1450
+ * Used to store the original name of the table, before any aliasing.
1451
+ */
1452
+ [OriginalName];
1453
+ /** @internal */
1454
+ [Schema];
1455
+ /** @internal */
1456
+ [Columns];
1457
+ /** @internal */
1458
+ [ExtraConfigColumns];
1459
+ /**
1460
+ * @internal
1461
+ * Used to store the table name before the transformation via the `tableCreator` functions.
1462
+ */
1463
+ [BaseName];
1464
+ /** @internal */
1465
+ [IsAlias] = false;
1466
+ /** @internal */
1467
+ [IsDrizzleTable] = true;
1468
+ /** @internal */
1469
+ [ExtraConfigBuilder] = void 0;
1470
+ constructor(name, schema, baseName) {
1471
+ this[TableName] = this[OriginalName] = name;
1472
+ this[Schema] = schema;
1473
+ this[BaseName] = baseName;
1474
+ }
1475
+ };
1476
+
1477
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sql/sql.js
1478
+ var FakePrimitiveParam = class {
1479
+ static [entityKind] = "FakePrimitiveParam";
1480
+ };
1481
+ function isSQLWrapper(value) {
1482
+ return value !== null && value !== void 0 && typeof value.getSQL === "function";
1483
+ }
1484
+ function mergeQueries(queries) {
1485
+ const result = { sql: "", params: [] };
1486
+ for (const query of queries) {
1487
+ result.sql += query.sql;
1488
+ result.params.push(...query.params);
1489
+ if (query.typings?.length) {
1490
+ if (!result.typings) {
1491
+ result.typings = [];
1492
+ }
1493
+ result.typings.push(...query.typings);
1494
+ }
1495
+ }
1496
+ return result;
1497
+ }
1498
+ var StringChunk = class {
1499
+ static [entityKind] = "StringChunk";
1500
+ value;
1501
+ constructor(value) {
1502
+ this.value = Array.isArray(value) ? value : [value];
1503
+ }
1504
+ getSQL() {
1505
+ return new SQL([this]);
1506
+ }
1507
+ };
1508
+ var SQL = class _SQL {
1509
+ constructor(queryChunks) {
1510
+ this.queryChunks = queryChunks;
1511
+ for (const chunk of queryChunks) {
1512
+ if (is(chunk, Table)) {
1513
+ const schemaName = chunk[Table.Symbol.Schema];
1514
+ this.usedTables.push(
1515
+ schemaName === void 0 ? chunk[Table.Symbol.Name] : schemaName + "." + chunk[Table.Symbol.Name]
1516
+ );
1517
+ }
1518
+ }
1519
+ }
1520
+ static [entityKind] = "SQL";
1521
+ /** @internal */
1522
+ decoder = noopDecoder;
1523
+ shouldInlineParams = false;
1524
+ /** @internal */
1525
+ usedTables = [];
1526
+ append(query) {
1527
+ this.queryChunks.push(...query.queryChunks);
1528
+ return this;
1529
+ }
1530
+ toQuery(config) {
1531
+ return tracer.startActiveSpan("drizzle.buildSQL", (span) => {
1532
+ const query = this.buildQueryFromSourceParams(this.queryChunks, config);
1533
+ span?.setAttributes({
1534
+ "drizzle.query.text": query.sql,
1535
+ "drizzle.query.params": JSON.stringify(query.params)
1536
+ });
1537
+ return query;
1538
+ });
1539
+ }
1540
+ buildQueryFromSourceParams(chunks, _config) {
1541
+ const config = Object.assign({}, _config, {
1542
+ inlineParams: _config.inlineParams || this.shouldInlineParams,
1543
+ paramStartIndex: _config.paramStartIndex || { value: 0 }
1544
+ });
1545
+ const {
1546
+ casing,
1547
+ escapeName,
1548
+ escapeParam,
1549
+ prepareTyping,
1550
+ inlineParams,
1551
+ paramStartIndex
1552
+ } = config;
1553
+ return mergeQueries(chunks.map((chunk) => {
1554
+ if (is(chunk, StringChunk)) {
1555
+ return { sql: chunk.value.join(""), params: [] };
1556
+ }
1557
+ if (is(chunk, Name)) {
1558
+ return { sql: escapeName(chunk.value), params: [] };
1559
+ }
1560
+ if (chunk === void 0) {
1561
+ return { sql: "", params: [] };
1562
+ }
1563
+ if (Array.isArray(chunk)) {
1564
+ const result = [new StringChunk("(")];
1565
+ for (const [i, p] of chunk.entries()) {
1566
+ result.push(p);
1567
+ if (i < chunk.length - 1) {
1568
+ result.push(new StringChunk(", "));
1569
+ }
1570
+ }
1571
+ result.push(new StringChunk(")"));
1572
+ return this.buildQueryFromSourceParams(result, config);
1573
+ }
1574
+ if (is(chunk, _SQL)) {
1575
+ return this.buildQueryFromSourceParams(chunk.queryChunks, {
1576
+ ...config,
1577
+ inlineParams: inlineParams || chunk.shouldInlineParams
1578
+ });
1579
+ }
1580
+ if (is(chunk, Table)) {
1581
+ const schemaName = chunk[Table.Symbol.Schema];
1582
+ const tableName = chunk[Table.Symbol.Name];
1583
+ return {
1584
+ sql: schemaName === void 0 || chunk[IsAlias] ? escapeName(tableName) : escapeName(schemaName) + "." + escapeName(tableName),
1585
+ params: []
1586
+ };
1587
+ }
1588
+ if (is(chunk, Column)) {
1589
+ const columnName = casing.getColumnCasing(chunk);
1590
+ if (_config.invokeSource === "indexes") {
1591
+ return { sql: escapeName(columnName), params: [] };
1592
+ }
1593
+ const schemaName = chunk.table[Table.Symbol.Schema];
1594
+ return {
1595
+ sql: chunk.table[IsAlias] || schemaName === void 0 ? escapeName(chunk.table[Table.Symbol.Name]) + "." + escapeName(columnName) : escapeName(schemaName) + "." + escapeName(chunk.table[Table.Symbol.Name]) + "." + escapeName(columnName),
1596
+ params: []
1597
+ };
1598
+ }
1599
+ if (is(chunk, View)) {
1600
+ const schemaName = chunk[ViewBaseConfig].schema;
1601
+ const viewName = chunk[ViewBaseConfig].name;
1602
+ return {
1603
+ sql: schemaName === void 0 || chunk[ViewBaseConfig].isAlias ? escapeName(viewName) : escapeName(schemaName) + "." + escapeName(viewName),
1604
+ params: []
1605
+ };
1606
+ }
1607
+ if (is(chunk, Param)) {
1608
+ if (is(chunk.value, Placeholder)) {
1609
+ return { sql: escapeParam(paramStartIndex.value++, chunk), params: [chunk], typings: ["none"] };
1610
+ }
1611
+ const mappedValue = chunk.value === null ? null : chunk.encoder.mapToDriverValue(chunk.value);
1612
+ if (is(mappedValue, _SQL)) {
1613
+ return this.buildQueryFromSourceParams([mappedValue], config);
1614
+ }
1615
+ if (inlineParams) {
1616
+ return { sql: this.mapInlineParam(mappedValue, config), params: [] };
1617
+ }
1618
+ let typings = ["none"];
1619
+ if (prepareTyping) {
1620
+ typings = [prepareTyping(chunk.encoder)];
1621
+ }
1622
+ return { sql: escapeParam(paramStartIndex.value++, mappedValue), params: [mappedValue], typings };
1623
+ }
1624
+ if (is(chunk, Placeholder)) {
1625
+ return { sql: escapeParam(paramStartIndex.value++, chunk), params: [chunk], typings: ["none"] };
1626
+ }
1627
+ if (is(chunk, _SQL.Aliased) && chunk.fieldAlias !== void 0) {
1628
+ return { sql: escapeName(chunk.fieldAlias), params: [] };
1629
+ }
1630
+ if (is(chunk, Subquery)) {
1631
+ if (chunk._.isWith) {
1632
+ return { sql: escapeName(chunk._.alias), params: [] };
1633
+ }
1634
+ return this.buildQueryFromSourceParams([
1635
+ new StringChunk("("),
1636
+ chunk._.sql,
1637
+ new StringChunk(") "),
1638
+ new Name(chunk._.alias)
1639
+ ], config);
1640
+ }
1641
+ if (isPgEnum(chunk)) {
1642
+ if (chunk.schema) {
1643
+ return { sql: escapeName(chunk.schema) + "." + escapeName(chunk.enumName), params: [] };
1644
+ }
1645
+ return { sql: escapeName(chunk.enumName), params: [] };
1646
+ }
1647
+ if (isSQLWrapper(chunk)) {
1648
+ if (chunk.shouldOmitSQLParens?.()) {
1649
+ return this.buildQueryFromSourceParams([chunk.getSQL()], config);
1650
+ }
1651
+ return this.buildQueryFromSourceParams([
1652
+ new StringChunk("("),
1653
+ chunk.getSQL(),
1654
+ new StringChunk(")")
1655
+ ], config);
1656
+ }
1657
+ if (inlineParams) {
1658
+ return { sql: this.mapInlineParam(chunk, config), params: [] };
1659
+ }
1660
+ return { sql: escapeParam(paramStartIndex.value++, chunk), params: [chunk], typings: ["none"] };
1661
+ }));
1662
+ }
1663
+ mapInlineParam(chunk, { escapeString }) {
1664
+ if (chunk === null) {
1665
+ return "null";
1666
+ }
1667
+ if (typeof chunk === "number" || typeof chunk === "boolean") {
1668
+ return chunk.toString();
1669
+ }
1670
+ if (typeof chunk === "string") {
1671
+ return escapeString(chunk);
1672
+ }
1673
+ if (typeof chunk === "object") {
1674
+ const mappedValueAsString = chunk.toString();
1675
+ if (mappedValueAsString === "[object Object]") {
1676
+ return escapeString(JSON.stringify(chunk));
1677
+ }
1678
+ return escapeString(mappedValueAsString);
1679
+ }
1680
+ throw new Error("Unexpected param value: " + chunk);
1681
+ }
1682
+ getSQL() {
1683
+ return this;
1684
+ }
1685
+ as(alias) {
1686
+ if (alias === void 0) {
1687
+ return this;
1688
+ }
1689
+ return new _SQL.Aliased(this, alias);
1690
+ }
1691
+ mapWith(decoder) {
1692
+ this.decoder = typeof decoder === "function" ? { mapFromDriverValue: decoder } : decoder;
1693
+ return this;
1694
+ }
1695
+ inlineParams() {
1696
+ this.shouldInlineParams = true;
1697
+ return this;
1698
+ }
1699
+ /**
1700
+ * This method is used to conditionally include a part of the query.
1701
+ *
1702
+ * @param condition - Condition to check
1703
+ * @returns itself if the condition is `true`, otherwise `undefined`
1704
+ */
1705
+ if(condition) {
1706
+ return condition ? this : void 0;
1707
+ }
1708
+ };
1709
+ var Name = class {
1710
+ constructor(value) {
1711
+ this.value = value;
1712
+ }
1713
+ static [entityKind] = "Name";
1714
+ brand;
1715
+ getSQL() {
1716
+ return new SQL([this]);
1717
+ }
1718
+ };
1719
+ var noopDecoder = {
1720
+ mapFromDriverValue: (value) => value
1721
+ };
1722
+ var noopEncoder = {
1723
+ mapToDriverValue: (value) => value
1724
+ };
1725
+ var noopMapper = {
1726
+ ...noopDecoder,
1727
+ ...noopEncoder
1728
+ };
1729
+ var Param = class {
1730
+ /**
1731
+ * @param value - Parameter value
1732
+ * @param encoder - Encoder to convert the value to a driver parameter
1733
+ */
1734
+ constructor(value, encoder = noopEncoder) {
1735
+ this.value = value;
1736
+ this.encoder = encoder;
1737
+ }
1738
+ static [entityKind] = "Param";
1739
+ brand;
1740
+ getSQL() {
1741
+ return new SQL([this]);
1742
+ }
1743
+ };
1744
+ function sql(strings, ...params) {
1745
+ const queryChunks = [];
1746
+ if (params.length > 0 || strings.length > 0 && strings[0] !== "") {
1747
+ queryChunks.push(new StringChunk(strings[0]));
1748
+ }
1749
+ for (const [paramIndex, param2] of params.entries()) {
1750
+ queryChunks.push(param2, new StringChunk(strings[paramIndex + 1]));
1751
+ }
1752
+ return new SQL(queryChunks);
1753
+ }
1754
+ ((sql2) => {
1755
+ function empty() {
1756
+ return new SQL([]);
1757
+ }
1758
+ sql2.empty = empty;
1759
+ function fromList(list) {
1760
+ return new SQL(list);
1761
+ }
1762
+ sql2.fromList = fromList;
1763
+ function raw(str) {
1764
+ return new SQL([new StringChunk(str)]);
1765
+ }
1766
+ sql2.raw = raw;
1767
+ function join4(chunks, separator) {
1768
+ const result = [];
1769
+ for (const [i, chunk] of chunks.entries()) {
1770
+ if (i > 0 && separator !== void 0) {
1771
+ result.push(separator);
1772
+ }
1773
+ result.push(chunk);
1774
+ }
1775
+ return new SQL(result);
1776
+ }
1777
+ sql2.join = join4;
1778
+ function identifier(value) {
1779
+ return new Name(value);
1780
+ }
1781
+ sql2.identifier = identifier;
1782
+ function placeholder2(name2) {
1783
+ return new Placeholder(name2);
1784
+ }
1785
+ sql2.placeholder = placeholder2;
1786
+ function param2(value, encoder) {
1787
+ return new Param(value, encoder);
1788
+ }
1789
+ sql2.param = param2;
1790
+ })(sql || (sql = {}));
1791
+ ((SQL2) => {
1792
+ class Aliased {
1793
+ constructor(sql2, fieldAlias) {
1794
+ this.sql = sql2;
1795
+ this.fieldAlias = fieldAlias;
1796
+ }
1797
+ static [entityKind] = "SQL.Aliased";
1798
+ /** @internal */
1799
+ isSelectionField = false;
1800
+ getSQL() {
1801
+ return this.sql;
1802
+ }
1803
+ /** @internal */
1804
+ clone() {
1805
+ return new Aliased(this.sql, this.fieldAlias);
1806
+ }
1807
+ }
1808
+ SQL2.Aliased = Aliased;
1809
+ })(SQL || (SQL = {}));
1810
+ var Placeholder = class {
1811
+ constructor(name2) {
1812
+ this.name = name2;
1813
+ }
1814
+ static [entityKind] = "Placeholder";
1815
+ getSQL() {
1816
+ return new SQL([this]);
1817
+ }
1818
+ };
1819
+ var IsDrizzleView = /* @__PURE__ */ Symbol.for("drizzle:IsDrizzleView");
1820
+ var View = class {
1821
+ static [entityKind] = "View";
1822
+ /** @internal */
1823
+ [ViewBaseConfig];
1824
+ /** @internal */
1825
+ [IsDrizzleView] = true;
1826
+ constructor({ name: name2, schema, selectedFields, query }) {
1827
+ this[ViewBaseConfig] = {
1828
+ name: name2,
1829
+ originalName: name2,
1830
+ schema,
1831
+ selectedFields,
1832
+ query,
1833
+ isExisting: !query,
1834
+ isAlias: false
1835
+ };
1836
+ }
1837
+ getSQL() {
1838
+ return new SQL([this]);
1839
+ }
1840
+ };
1841
+ Column.prototype.getSQL = function() {
1842
+ return new SQL([this]);
1843
+ };
1844
+ Table.prototype.getSQL = function() {
1845
+ return new SQL([this]);
1846
+ };
1847
+ Subquery.prototype.getSQL = function() {
1848
+ return new SQL([this]);
1849
+ };
1850
+
1851
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/utils.js
1852
+ function getColumnNameAndConfig(a, b) {
1853
+ return {
1854
+ name: typeof a === "string" && a.length > 0 ? a : "",
1855
+ config: typeof a === "object" ? a : b
1856
+ };
1857
+ }
1858
+ var textDecoder = typeof TextDecoder === "undefined" ? null : new TextDecoder();
1859
+
1860
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/foreign-keys.js
1861
+ var ForeignKeyBuilder2 = class {
1862
+ static [entityKind] = "SQLiteForeignKeyBuilder";
1863
+ /** @internal */
1864
+ reference;
1865
+ /** @internal */
1866
+ _onUpdate;
1867
+ /** @internal */
1868
+ _onDelete;
1869
+ constructor(config, actions) {
1870
+ this.reference = () => {
1871
+ const { name, columns, foreignColumns } = config();
1872
+ return { name, columns, foreignTable: foreignColumns[0].table, foreignColumns };
1873
+ };
1874
+ if (actions) {
1875
+ this._onUpdate = actions.onUpdate;
1876
+ this._onDelete = actions.onDelete;
1877
+ }
1878
+ }
1879
+ onUpdate(action) {
1880
+ this._onUpdate = action;
1881
+ return this;
1882
+ }
1883
+ onDelete(action) {
1884
+ this._onDelete = action;
1885
+ return this;
1886
+ }
1887
+ /** @internal */
1888
+ build(table) {
1889
+ return new ForeignKey2(table, this);
1890
+ }
1891
+ };
1892
+ var ForeignKey2 = class {
1893
+ constructor(table, builder) {
1894
+ this.table = table;
1895
+ this.reference = builder.reference;
1896
+ this.onUpdate = builder._onUpdate;
1897
+ this.onDelete = builder._onDelete;
1898
+ }
1899
+ static [entityKind] = "SQLiteForeignKey";
1900
+ reference;
1901
+ onUpdate;
1902
+ onDelete;
1903
+ getName() {
1904
+ const { name, columns, foreignColumns } = this.reference();
1905
+ const columnNames = columns.map((column) => column.name);
1906
+ const foreignColumnNames = foreignColumns.map((column) => column.name);
1907
+ const chunks = [
1908
+ this.table[TableName],
1909
+ ...columnNames,
1910
+ foreignColumns[0].table[TableName],
1911
+ ...foreignColumnNames
1912
+ ];
1913
+ return name ?? `${chunks.join("_")}_fk`;
1914
+ }
1915
+ };
1916
+
1917
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/unique-constraint.js
1918
+ function uniqueKeyName2(table, columns) {
1919
+ return `${table[TableName]}_${columns.join("_")}_unique`;
1920
+ }
1921
+ var UniqueConstraintBuilder2 = class {
1922
+ constructor(columns, name) {
1923
+ this.name = name;
1924
+ this.columns = columns;
1925
+ }
1926
+ static [entityKind] = "SQLiteUniqueConstraintBuilder";
1927
+ /** @internal */
1928
+ columns;
1929
+ /** @internal */
1930
+ build(table) {
1931
+ return new UniqueConstraint2(table, this.columns, this.name);
1932
+ }
1933
+ };
1934
+ var UniqueOnConstraintBuilder2 = class {
1935
+ static [entityKind] = "SQLiteUniqueOnConstraintBuilder";
1936
+ /** @internal */
1937
+ name;
1938
+ constructor(name) {
1939
+ this.name = name;
1940
+ }
1941
+ on(...columns) {
1942
+ return new UniqueConstraintBuilder2(columns, this.name);
1943
+ }
1944
+ };
1945
+ var UniqueConstraint2 = class {
1946
+ constructor(table, columns, name) {
1947
+ this.table = table;
1948
+ this.columns = columns;
1949
+ this.name = name ?? uniqueKeyName2(this.table, this.columns.map((column) => column.name));
1950
+ }
1951
+ static [entityKind] = "SQLiteUniqueConstraint";
1952
+ columns;
1953
+ name;
1954
+ getName() {
1955
+ return this.name;
1956
+ }
1957
+ };
1958
+
1959
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/common.js
1960
+ var SQLiteColumnBuilder = class extends ColumnBuilder {
1961
+ static [entityKind] = "SQLiteColumnBuilder";
1962
+ foreignKeyConfigs = [];
1963
+ references(ref, actions = {}) {
1964
+ this.foreignKeyConfigs.push({ ref, actions });
1965
+ return this;
1966
+ }
1967
+ unique(name) {
1968
+ this.config.isUnique = true;
1969
+ this.config.uniqueName = name;
1970
+ return this;
1971
+ }
1972
+ generatedAlwaysAs(as, config) {
1973
+ this.config.generated = {
1974
+ as,
1975
+ type: "always",
1976
+ mode: config?.mode ?? "virtual"
1977
+ };
1978
+ return this;
1979
+ }
1980
+ /** @internal */
1981
+ buildForeignKeys(column, table) {
1982
+ return this.foreignKeyConfigs.map(({ ref, actions }) => {
1983
+ return ((ref2, actions2) => {
1984
+ const builder = new ForeignKeyBuilder2(() => {
1985
+ const foreignColumn = ref2();
1986
+ return { columns: [column], foreignColumns: [foreignColumn] };
1987
+ });
1988
+ if (actions2.onUpdate) {
1989
+ builder.onUpdate(actions2.onUpdate);
1990
+ }
1991
+ if (actions2.onDelete) {
1992
+ builder.onDelete(actions2.onDelete);
1993
+ }
1994
+ return builder.build(table);
1995
+ })(ref, actions);
1996
+ });
1997
+ }
1998
+ };
1999
+ var SQLiteColumn = class extends Column {
2000
+ constructor(table, config) {
2001
+ if (!config.uniqueName) {
2002
+ config.uniqueName = uniqueKeyName2(table, [config.name]);
2003
+ }
2004
+ super(table, config);
2005
+ this.table = table;
2006
+ }
2007
+ static [entityKind] = "SQLiteColumn";
2008
+ };
2009
+
2010
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/blob.js
2011
+ var SQLiteBigIntBuilder = class extends SQLiteColumnBuilder {
2012
+ static [entityKind] = "SQLiteBigIntBuilder";
2013
+ constructor(name) {
2014
+ super(name, "bigint", "SQLiteBigInt");
2015
+ }
2016
+ /** @internal */
2017
+ build(table) {
2018
+ return new SQLiteBigInt(table, this.config);
2019
+ }
2020
+ };
2021
+ var SQLiteBigInt = class extends SQLiteColumn {
2022
+ static [entityKind] = "SQLiteBigInt";
2023
+ getSQLType() {
2024
+ return "blob";
2025
+ }
2026
+ mapFromDriverValue(value) {
2027
+ if (typeof Buffer !== "undefined" && Buffer.from) {
2028
+ const buf = Buffer.isBuffer(value) ? value : value instanceof ArrayBuffer ? Buffer.from(value) : value.buffer ? Buffer.from(value.buffer, value.byteOffset, value.byteLength) : Buffer.from(value);
2029
+ return BigInt(buf.toString("utf8"));
2030
+ }
2031
+ return BigInt(textDecoder.decode(value));
2032
+ }
2033
+ mapToDriverValue(value) {
2034
+ return Buffer.from(value.toString());
2035
+ }
2036
+ };
2037
+ var SQLiteBlobJsonBuilder = class extends SQLiteColumnBuilder {
2038
+ static [entityKind] = "SQLiteBlobJsonBuilder";
2039
+ constructor(name) {
2040
+ super(name, "json", "SQLiteBlobJson");
2041
+ }
2042
+ /** @internal */
2043
+ build(table) {
2044
+ return new SQLiteBlobJson(
2045
+ table,
2046
+ this.config
2047
+ );
2048
+ }
2049
+ };
2050
+ var SQLiteBlobJson = class extends SQLiteColumn {
2051
+ static [entityKind] = "SQLiteBlobJson";
2052
+ getSQLType() {
2053
+ return "blob";
2054
+ }
2055
+ mapFromDriverValue(value) {
2056
+ if (typeof Buffer !== "undefined" && Buffer.from) {
2057
+ const buf = Buffer.isBuffer(value) ? value : value instanceof ArrayBuffer ? Buffer.from(value) : value.buffer ? Buffer.from(value.buffer, value.byteOffset, value.byteLength) : Buffer.from(value);
2058
+ return JSON.parse(buf.toString("utf8"));
2059
+ }
2060
+ return JSON.parse(textDecoder.decode(value));
2061
+ }
2062
+ mapToDriverValue(value) {
2063
+ return Buffer.from(JSON.stringify(value));
2064
+ }
2065
+ };
2066
+ var SQLiteBlobBufferBuilder = class extends SQLiteColumnBuilder {
2067
+ static [entityKind] = "SQLiteBlobBufferBuilder";
2068
+ constructor(name) {
2069
+ super(name, "buffer", "SQLiteBlobBuffer");
2070
+ }
2071
+ /** @internal */
2072
+ build(table) {
2073
+ return new SQLiteBlobBuffer(table, this.config);
2074
+ }
2075
+ };
2076
+ var SQLiteBlobBuffer = class extends SQLiteColumn {
2077
+ static [entityKind] = "SQLiteBlobBuffer";
2078
+ mapFromDriverValue(value) {
2079
+ if (Buffer.isBuffer(value)) {
2080
+ return value;
2081
+ }
2082
+ return Buffer.from(value);
2083
+ }
2084
+ getSQLType() {
2085
+ return "blob";
2086
+ }
2087
+ };
2088
+ function blob(a, b) {
2089
+ const { name, config } = getColumnNameAndConfig(a, b);
2090
+ if (config?.mode === "json") {
2091
+ return new SQLiteBlobJsonBuilder(name);
2092
+ }
2093
+ if (config?.mode === "bigint") {
2094
+ return new SQLiteBigIntBuilder(name);
2095
+ }
2096
+ return new SQLiteBlobBufferBuilder(name);
2097
+ }
2098
+
2099
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/custom.js
2100
+ var SQLiteCustomColumnBuilder = class extends SQLiteColumnBuilder {
2101
+ static [entityKind] = "SQLiteCustomColumnBuilder";
2102
+ constructor(name, fieldConfig, customTypeParams) {
2103
+ super(name, "custom", "SQLiteCustomColumn");
2104
+ this.config.fieldConfig = fieldConfig;
2105
+ this.config.customTypeParams = customTypeParams;
2106
+ }
2107
+ /** @internal */
2108
+ build(table) {
2109
+ return new SQLiteCustomColumn(
2110
+ table,
2111
+ this.config
2112
+ );
2113
+ }
2114
+ };
2115
+ var SQLiteCustomColumn = class extends SQLiteColumn {
2116
+ static [entityKind] = "SQLiteCustomColumn";
2117
+ sqlName;
2118
+ mapTo;
2119
+ mapFrom;
2120
+ constructor(table, config) {
2121
+ super(table, config);
2122
+ this.sqlName = config.customTypeParams.dataType(config.fieldConfig);
2123
+ this.mapTo = config.customTypeParams.toDriver;
2124
+ this.mapFrom = config.customTypeParams.fromDriver;
2125
+ }
2126
+ getSQLType() {
2127
+ return this.sqlName;
2128
+ }
2129
+ mapFromDriverValue(value) {
2130
+ return typeof this.mapFrom === "function" ? this.mapFrom(value) : value;
2131
+ }
2132
+ mapToDriverValue(value) {
2133
+ return typeof this.mapTo === "function" ? this.mapTo(value) : value;
2134
+ }
2135
+ };
2136
+ function customType(customTypeParams) {
2137
+ return (a, b) => {
2138
+ const { name, config } = getColumnNameAndConfig(a, b);
2139
+ return new SQLiteCustomColumnBuilder(
2140
+ name,
2141
+ config,
2142
+ customTypeParams
2143
+ );
2144
+ };
2145
+ }
2146
+
2147
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/integer.js
2148
+ var SQLiteBaseIntegerBuilder = class extends SQLiteColumnBuilder {
2149
+ static [entityKind] = "SQLiteBaseIntegerBuilder";
2150
+ constructor(name, dataType, columnType) {
2151
+ super(name, dataType, columnType);
2152
+ this.config.autoIncrement = false;
2153
+ }
2154
+ primaryKey(config) {
2155
+ if (config?.autoIncrement) {
2156
+ this.config.autoIncrement = true;
2157
+ }
2158
+ this.config.hasDefault = true;
2159
+ return super.primaryKey();
2160
+ }
2161
+ };
2162
+ var SQLiteBaseInteger = class extends SQLiteColumn {
2163
+ static [entityKind] = "SQLiteBaseInteger";
2164
+ autoIncrement = this.config.autoIncrement;
2165
+ getSQLType() {
2166
+ return "integer";
2167
+ }
2168
+ };
2169
+ var SQLiteIntegerBuilder = class extends SQLiteBaseIntegerBuilder {
2170
+ static [entityKind] = "SQLiteIntegerBuilder";
2171
+ constructor(name) {
2172
+ super(name, "number", "SQLiteInteger");
2173
+ }
2174
+ build(table) {
2175
+ return new SQLiteInteger(
2176
+ table,
2177
+ this.config
2178
+ );
2179
+ }
2180
+ };
2181
+ var SQLiteInteger = class extends SQLiteBaseInteger {
2182
+ static [entityKind] = "SQLiteInteger";
2183
+ };
2184
+ var SQLiteTimestampBuilder = class extends SQLiteBaseIntegerBuilder {
2185
+ static [entityKind] = "SQLiteTimestampBuilder";
2186
+ constructor(name, mode) {
2187
+ super(name, "date", "SQLiteTimestamp");
2188
+ this.config.mode = mode;
2189
+ }
2190
+ /**
2191
+ * @deprecated Use `default()` with your own expression instead.
2192
+ *
2193
+ * Adds `DEFAULT (cast((julianday('now') - 2440587.5)*86400000 as integer))` to the column, which is the current epoch timestamp in milliseconds.
2194
+ */
2195
+ defaultNow() {
2196
+ return this.default(sql`(cast((julianday('now') - 2440587.5)*86400000 as integer))`);
2197
+ }
2198
+ build(table) {
2199
+ return new SQLiteTimestamp(
2200
+ table,
2201
+ this.config
2202
+ );
2203
+ }
2204
+ };
2205
+ var SQLiteTimestamp = class extends SQLiteBaseInteger {
2206
+ static [entityKind] = "SQLiteTimestamp";
2207
+ mode = this.config.mode;
2208
+ mapFromDriverValue(value) {
2209
+ if (this.config.mode === "timestamp") {
2210
+ return new Date(value * 1e3);
2211
+ }
2212
+ return new Date(value);
2213
+ }
2214
+ mapToDriverValue(value) {
2215
+ const unix = value.getTime();
2216
+ if (this.config.mode === "timestamp") {
2217
+ return Math.floor(unix / 1e3);
2218
+ }
2219
+ return unix;
2220
+ }
2221
+ };
2222
+ var SQLiteBooleanBuilder = class extends SQLiteBaseIntegerBuilder {
2223
+ static [entityKind] = "SQLiteBooleanBuilder";
2224
+ constructor(name, mode) {
2225
+ super(name, "boolean", "SQLiteBoolean");
2226
+ this.config.mode = mode;
2227
+ }
2228
+ build(table) {
2229
+ return new SQLiteBoolean(
2230
+ table,
2231
+ this.config
2232
+ );
2233
+ }
2234
+ };
2235
+ var SQLiteBoolean = class extends SQLiteBaseInteger {
2236
+ static [entityKind] = "SQLiteBoolean";
2237
+ mode = this.config.mode;
2238
+ mapFromDriverValue(value) {
2239
+ return Number(value) === 1;
2240
+ }
2241
+ mapToDriverValue(value) {
2242
+ return value ? 1 : 0;
2243
+ }
2244
+ };
2245
+ function integer(a, b) {
2246
+ const { name, config } = getColumnNameAndConfig(a, b);
2247
+ if (config?.mode === "timestamp" || config?.mode === "timestamp_ms") {
2248
+ return new SQLiteTimestampBuilder(name, config.mode);
2249
+ }
2250
+ if (config?.mode === "boolean") {
2251
+ return new SQLiteBooleanBuilder(name, config.mode);
2252
+ }
2253
+ return new SQLiteIntegerBuilder(name);
2254
+ }
2255
+
2256
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/numeric.js
2257
+ var SQLiteNumericBuilder = class extends SQLiteColumnBuilder {
2258
+ static [entityKind] = "SQLiteNumericBuilder";
2259
+ constructor(name) {
2260
+ super(name, "string", "SQLiteNumeric");
2261
+ }
2262
+ /** @internal */
2263
+ build(table) {
2264
+ return new SQLiteNumeric(
2265
+ table,
2266
+ this.config
2267
+ );
2268
+ }
2269
+ };
2270
+ var SQLiteNumeric = class extends SQLiteColumn {
2271
+ static [entityKind] = "SQLiteNumeric";
2272
+ mapFromDriverValue(value) {
2273
+ if (typeof value === "string") return value;
2274
+ return String(value);
2275
+ }
2276
+ getSQLType() {
2277
+ return "numeric";
2278
+ }
2279
+ };
2280
+ var SQLiteNumericNumberBuilder = class extends SQLiteColumnBuilder {
2281
+ static [entityKind] = "SQLiteNumericNumberBuilder";
2282
+ constructor(name) {
2283
+ super(name, "number", "SQLiteNumericNumber");
2284
+ }
2285
+ /** @internal */
2286
+ build(table) {
2287
+ return new SQLiteNumericNumber(
2288
+ table,
2289
+ this.config
2290
+ );
2291
+ }
2292
+ };
2293
+ var SQLiteNumericNumber = class extends SQLiteColumn {
2294
+ static [entityKind] = "SQLiteNumericNumber";
2295
+ mapFromDriverValue(value) {
2296
+ if (typeof value === "number") return value;
2297
+ return Number(value);
2298
+ }
2299
+ mapToDriverValue = String;
2300
+ getSQLType() {
2301
+ return "numeric";
2302
+ }
2303
+ };
2304
+ var SQLiteNumericBigIntBuilder = class extends SQLiteColumnBuilder {
2305
+ static [entityKind] = "SQLiteNumericBigIntBuilder";
2306
+ constructor(name) {
2307
+ super(name, "bigint", "SQLiteNumericBigInt");
2308
+ }
2309
+ /** @internal */
2310
+ build(table) {
2311
+ return new SQLiteNumericBigInt(
2312
+ table,
2313
+ this.config
2314
+ );
2315
+ }
2316
+ };
2317
+ var SQLiteNumericBigInt = class extends SQLiteColumn {
2318
+ static [entityKind] = "SQLiteNumericBigInt";
2319
+ mapFromDriverValue = BigInt;
2320
+ mapToDriverValue = String;
2321
+ getSQLType() {
2322
+ return "numeric";
2323
+ }
2324
+ };
2325
+ function numeric(a, b) {
2326
+ const { name, config } = getColumnNameAndConfig(a, b);
2327
+ const mode = config?.mode;
2328
+ return mode === "number" ? new SQLiteNumericNumberBuilder(name) : mode === "bigint" ? new SQLiteNumericBigIntBuilder(name) : new SQLiteNumericBuilder(name);
2329
+ }
2330
+
2331
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/real.js
2332
+ var SQLiteRealBuilder = class extends SQLiteColumnBuilder {
2333
+ static [entityKind] = "SQLiteRealBuilder";
2334
+ constructor(name) {
2335
+ super(name, "number", "SQLiteReal");
2336
+ }
2337
+ /** @internal */
2338
+ build(table) {
2339
+ return new SQLiteReal(table, this.config);
2340
+ }
2341
+ };
2342
+ var SQLiteReal = class extends SQLiteColumn {
2343
+ static [entityKind] = "SQLiteReal";
2344
+ getSQLType() {
2345
+ return "real";
2346
+ }
2347
+ };
2348
+ function real(name) {
2349
+ return new SQLiteRealBuilder(name ?? "");
2350
+ }
2351
+
2352
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/text.js
2353
+ var SQLiteTextBuilder = class extends SQLiteColumnBuilder {
2354
+ static [entityKind] = "SQLiteTextBuilder";
2355
+ constructor(name, config) {
2356
+ super(name, "string", "SQLiteText");
2357
+ this.config.enumValues = config.enum;
2358
+ this.config.length = config.length;
2359
+ }
2360
+ /** @internal */
2361
+ build(table) {
2362
+ return new SQLiteText(
2363
+ table,
2364
+ this.config
2365
+ );
2366
+ }
2367
+ };
2368
+ var SQLiteText = class extends SQLiteColumn {
2369
+ static [entityKind] = "SQLiteText";
2370
+ enumValues = this.config.enumValues;
2371
+ length = this.config.length;
2372
+ constructor(table, config) {
2373
+ super(table, config);
2374
+ }
2375
+ getSQLType() {
2376
+ return `text${this.config.length ? `(${this.config.length})` : ""}`;
2377
+ }
2378
+ };
2379
+ var SQLiteTextJsonBuilder = class extends SQLiteColumnBuilder {
2380
+ static [entityKind] = "SQLiteTextJsonBuilder";
2381
+ constructor(name) {
2382
+ super(name, "json", "SQLiteTextJson");
2383
+ }
2384
+ /** @internal */
2385
+ build(table) {
2386
+ return new SQLiteTextJson(
2387
+ table,
2388
+ this.config
2389
+ );
2390
+ }
2391
+ };
2392
+ var SQLiteTextJson = class extends SQLiteColumn {
2393
+ static [entityKind] = "SQLiteTextJson";
2394
+ getSQLType() {
2395
+ return "text";
2396
+ }
2397
+ mapFromDriverValue(value) {
2398
+ return JSON.parse(value);
2399
+ }
2400
+ mapToDriverValue(value) {
2401
+ return JSON.stringify(value);
2402
+ }
2403
+ };
2404
+ function text2(a, b = {}) {
2405
+ const { name, config } = getColumnNameAndConfig(a, b);
2406
+ if (config.mode === "json") {
2407
+ return new SQLiteTextJsonBuilder(name);
2408
+ }
2409
+ return new SQLiteTextBuilder(name, config);
2410
+ }
2411
+
2412
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/columns/all.js
2413
+ function getSQLiteColumnBuilders() {
2414
+ return {
2415
+ blob,
2416
+ customType,
2417
+ integer,
2418
+ numeric,
2419
+ real,
2420
+ text: text2
2421
+ };
2422
+ }
2423
+
2424
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/table.js
2425
+ var InlineForeignKeys = /* @__PURE__ */ Symbol.for("drizzle:SQLiteInlineForeignKeys");
2426
+ var SQLiteTable = class extends Table {
2427
+ static [entityKind] = "SQLiteTable";
2428
+ /** @internal */
2429
+ static Symbol = Object.assign({}, Table.Symbol, {
2430
+ InlineForeignKeys
2431
+ });
2432
+ /** @internal */
2433
+ [Table.Symbol.Columns];
2434
+ /** @internal */
2435
+ [InlineForeignKeys] = [];
2436
+ /** @internal */
2437
+ [Table.Symbol.ExtraConfigBuilder] = void 0;
2438
+ };
2439
+ function sqliteTableBase(name, columns, extraConfig, schema, baseName = name) {
2440
+ const rawTable = new SQLiteTable(name, schema, baseName);
2441
+ const parsedColumns = typeof columns === "function" ? columns(getSQLiteColumnBuilders()) : columns;
2442
+ const builtColumns = Object.fromEntries(
2443
+ Object.entries(parsedColumns).map(([name2, colBuilderBase]) => {
2444
+ const colBuilder = colBuilderBase;
2445
+ colBuilder.setName(name2);
2446
+ const column = colBuilder.build(rawTable);
2447
+ rawTable[InlineForeignKeys].push(...colBuilder.buildForeignKeys(column, rawTable));
2448
+ return [name2, column];
2449
+ })
2450
+ );
2451
+ const table = Object.assign(rawTable, builtColumns);
2452
+ table[Table.Symbol.Columns] = builtColumns;
2453
+ table[Table.Symbol.ExtraConfigColumns] = builtColumns;
2454
+ if (extraConfig) {
2455
+ table[SQLiteTable.Symbol.ExtraConfigBuilder] = extraConfig;
2456
+ }
2457
+ return table;
2458
+ }
2459
+ var sqliteTable = (name, columns, extraConfig) => {
2460
+ return sqliteTableBase(name, columns, extraConfig);
2461
+ };
2462
+
2463
+ // ../../node_modules/.pnpm/drizzle-orm@0.45.4_@cloudflare+workers-types@5.20261015.1_kysely@0.29.6/node_modules/drizzle-orm/sqlite-core/primary-keys.js
2464
+ function primaryKey(...config) {
2465
+ if (config[0].columns) {
2466
+ return new PrimaryKeyBuilder(config[0].columns, config[0].name);
2467
+ }
2468
+ return new PrimaryKeyBuilder(config);
2469
+ }
2470
+ var PrimaryKeyBuilder = class {
2471
+ static [entityKind] = "SQLitePrimaryKeyBuilder";
2472
+ /** @internal */
2473
+ columns;
2474
+ /** @internal */
2475
+ name;
2476
+ constructor(columns, name) {
2477
+ this.columns = columns;
2478
+ this.name = name;
2479
+ }
2480
+ /** @internal */
2481
+ build(table) {
2482
+ return new PrimaryKey(table, this.columns, this.name);
2483
+ }
2484
+ };
2485
+ var PrimaryKey = class {
2486
+ constructor(table, columns, name) {
2487
+ this.table = table;
2488
+ this.columns = columns;
2489
+ this.name = name;
2490
+ }
2491
+ static [entityKind] = "SQLitePrimaryKey";
2492
+ columns;
2493
+ name;
2494
+ getName() {
2495
+ return this.name ?? `${this.table[SQLiteTable.Symbol.Name]}_${this.columns.map((column) => column.name).join("_")}_pk`;
2496
+ }
2497
+ };
2498
+
2499
+ // ../feedback/src/connected.ts
2500
+ var participants = sqliteTable("participants", {
2501
+ subject: text2("subject").primaryKey(),
2502
+ displayName: text2("display_name").notNull()
2503
+ });
2504
+ var contacts = sqliteTable("participant_contacts", {
2505
+ subject: text2("subject").primaryKey(),
2506
+ email: text2("email"),
2507
+ emailEnabled: integer("email_enabled").notNull().default(0)
2508
+ });
2509
+ var nonces = sqliteTable("identity_nonces", {
2510
+ nonce: text2("nonce").primaryKey(),
2511
+ expiresAt: integer("expires_at").notNull()
2512
+ });
2513
+ var sessions = sqliteTable("participant_sessions", {
2514
+ tokenHash: text2("token_hash").primaryKey(),
2515
+ subject: text2("subject").notNull().references(() => participants.subject),
2516
+ expiresAt: integer("expires_at").notNull()
2517
+ });
2518
+ var votes = sqliteTable(
2519
+ "participant_votes",
2520
+ {
2521
+ postSlug: text2("post_slug").notNull(),
2522
+ subject: text2("subject").notNull().references(() => participants.subject)
2523
+ },
2524
+ (t) => [primaryKey({ columns: [t.postSlug, t.subject] })]
2525
+ );
2526
+ var attribution = sqliteTable("comment_attribution", {
2527
+ commentId: text2("comment_id").primaryKey(),
2528
+ subject: text2("subject").notNull().references(() => participants.subject),
2529
+ displayName: text2("display_name").notNull()
2530
+ });
2531
+
2532
+ // ../feedback/src/images.ts
2533
+ var limit = 5 * 1024 * 1024;
2534
+
2535
+ // ../feedback/src/index.ts
2536
+ var postCreationQueue = Promise.resolve();
2537
+
2538
+ // ../changelog/src/notion-changelog.ts
2539
+ var NOTION_API_URL2 = "https://api.notion.com/v1";
2540
+ var NOTION_API_VERSION2 = "2026-03-11";
2541
+ var canonicalProperties2 = {
2542
+ title: { name: "Title", type: "title", schema: { title: {} } },
2543
+ slug: { name: "Slug", type: "rich_text", schema: { rich_text: {} } },
2544
+ date: { name: "Date", type: "date", schema: { date: {} } },
2545
+ summary: {
2546
+ name: "Summary",
2547
+ type: "rich_text",
2548
+ schema: { rich_text: {} }
2549
+ },
2550
+ body: { name: "Body", type: "rich_text", schema: { rich_text: {} } },
2551
+ labels: {
2552
+ name: "Labels",
2553
+ type: "multi_select",
2554
+ schema: { multi_select: { options: [] } }
2555
+ },
2556
+ image: { name: "Image", type: "files", schema: { files: {} } },
2557
+ published: {
2558
+ name: "Published",
2559
+ type: "checkbox",
2560
+ schema: { checkbox: {} }
2561
+ },
2562
+ createdAt: {
2563
+ name: "Created At",
2564
+ type: "created_time",
2565
+ schema: { created_time: {} }
2566
+ },
2567
+ updatedAt: {
2568
+ name: "Updated At",
2569
+ type: "last_edited_time",
2570
+ schema: { last_edited_time: {} }
2571
+ }
2572
+ };
2573
+ async function createNotionChangelogDataSource({
2574
+ token,
2575
+ databaseId,
2576
+ request = fetch
2577
+ }) {
2578
+ const response = await request(`${NOTION_API_URL2}/data_sources`, {
2579
+ method: "POST",
2580
+ headers: notionHeaders2(token),
2581
+ body: JSON.stringify({
2582
+ parent: { database_id: databaseId },
2583
+ title: [{ type: "text", text: { content: "Changelog" } }],
2584
+ properties: Object.fromEntries(
2585
+ Object.values(canonicalProperties2).map(({ name, schema }) => [
2586
+ name,
2587
+ schema
2588
+ ])
2589
+ )
2590
+ })
2591
+ });
2592
+ return configurationFromResponse(
2593
+ await readObject(response, "Changelog Data Source"),
2594
+ databaseId
2595
+ );
2596
+ }
2597
+ async function validateNotionChangelogDataSource({
2598
+ token,
2599
+ dataSourceId,
2600
+ propertyIds: propertyIds2,
2601
+ request = fetch
2602
+ }) {
2603
+ const response = await request(
2604
+ `${NOTION_API_URL2}/data_sources/${dataSourceId}`,
2605
+ { headers: notionHeaders2(token) }
2606
+ );
2607
+ const dataSource = await readObject(response, "Changelog Data Source");
2608
+ const properties = recordAt(dataSource, "properties");
2609
+ const propertiesById = new Map(
2610
+ Object.values(properties).flatMap((property) => {
2611
+ if (!isRecord2(property) || typeof property.id !== "string") return [];
2612
+ return [[property.id, property]];
2613
+ })
2614
+ );
2615
+ const repairs = [];
2616
+ for (const [key, { name, type }] of Object.entries(canonicalProperties2)) {
2617
+ const configuredId = propertyIds2[key];
2618
+ const property = propertiesById.get(configuredId);
2619
+ if (!property) {
2620
+ repairs.push(
2621
+ `Restore the "${name}" property with ID "${configuredId}" and type "${type}", then update the configured property ID if Notion assigns a new one.`
2622
+ );
2623
+ } else if (property.type !== type) {
2624
+ repairs.push(
2625
+ `Change the property configured as "${name}" (ID "${configuredId}") from "${String(property.type)}" to "${type}", or restore a ${type} property and configure its ID.`
2626
+ );
2627
+ }
2628
+ }
2629
+ if (repairs.length) throw new Error(repairs.join("\n"));
2630
+ return {
2631
+ databaseId: databaseIdFromResponse(dataSource),
2632
+ dataSourceId,
2633
+ propertyIds: { ...propertyIds2 }
2634
+ };
2635
+ }
2636
+ function configurationFromResponse(dataSource, databaseId) {
2637
+ const properties = recordAt(dataSource, "properties");
2638
+ return {
2639
+ databaseId,
2640
+ dataSourceId: requiredString(dataSource.id),
2641
+ propertyIds: Object.fromEntries(
2642
+ Object.entries(canonicalProperties2).map(([key, { name }]) => [
2643
+ key,
2644
+ requiredString(recordAt(properties, name).id)
2645
+ ])
2646
+ )
2647
+ };
2648
+ }
2649
+ function databaseIdFromResponse(dataSource) {
2650
+ return requiredString(recordAt(dataSource, "parent").database_id);
2651
+ }
2652
+ function notionHeaders2(token) {
2653
+ return {
2654
+ Authorization: `Bearer ${token}`,
2655
+ "Content-Type": "application/json",
2656
+ "Notion-Version": NOTION_API_VERSION2
2657
+ };
2658
+ }
2659
+ async function readObject(response, subject) {
2660
+ const body = await response.json();
2661
+ if (!response.ok) {
2662
+ throw new Error(
2663
+ `Notion rejected the ${subject} request (${response.status}).`
2664
+ );
2665
+ }
2666
+ if (!isRecord2(body))
2667
+ throw new Error(`Notion returned an invalid ${subject} response.`);
2668
+ return body;
2669
+ }
2670
+ function recordAt(value, key) {
2671
+ return record(value[key]);
2672
+ }
2673
+ function record(value) {
2674
+ if (!isRecord2(value))
2675
+ throw new Error("Notion returned an invalid Changelog response.");
2676
+ return value;
2677
+ }
2678
+ function requiredString(value) {
2679
+ if (typeof value !== "string")
2680
+ throw new Error("Notion returned an invalid Changelog response.");
2681
+ return value;
2682
+ }
2683
+ function isRecord2(value) {
2684
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2685
+ }
2686
+
2687
+ // src/github.ts
2688
+ import { createHash, randomBytes } from "node:crypto";
2689
+ import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
2690
+ import { join as join2 } from "node:path";
2691
+ async function provisionGitHubInstallation(options) {
2692
+ const { connected, github } = options;
2693
+ if (!/^[a-z0-9][a-z0-9-]{0,62}$/.test(options.workerName) || !/^[0-9a-f-]{36}$/i.test(options.database.id) || !options.database.name.trim())
2694
+ throw new Error(
2695
+ "GitHub setup requires a distinct Worker and an explicitly provisioned FEEDBAX_DB D1 database."
2696
+ );
2697
+ if (connected && (connected.databaseId !== options.database.id || connected.handoffSecret.length < 32 || connected.processing !== "standard" && !connected.queueName))
2698
+ throw new Error(
2699
+ "Connected setup requires the same D1 database, a handoff secret, and an existing processing queue for small processing."
2700
+ );
2701
+ if (connected?.imageBucket && !/^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/.test(connected.imageBucket))
2702
+ throw new Error("Use a dedicated private R2 bucket for images.");
2703
+ const config = defineFeedbax({
2704
+ provider: "github",
2705
+ github,
2706
+ product: { ...options.product, logo: "/feedbax-mark.svg" },
2707
+ theme: { preset: "lyra-neutral" },
2708
+ features: options.features,
2709
+ ...connected ? {
2710
+ connected: {
2711
+ processing: connected.processing ?? "small",
2712
+ issuer: connected.issuer,
2713
+ loginUrl: connected.loginUrl,
2714
+ ...connected.imageBucket ? { images: true } : {},
2715
+ ...connected.email ? {
2716
+ email: {
2717
+ provider: connected.email.provider,
2718
+ from: connected.email.from,
2719
+ ...connected.email.endpoint ? { endpoint: connected.email.endpoint } : {}
2720
+ }
2721
+ } : {}
2722
+ }
2723
+ } : {},
2724
+ submissions: {
2725
+ moderation: "required",
2726
+ maxTitleLength: 160,
2727
+ maxDescriptionLength: 5e3
2728
+ },
2729
+ turnstile: {
2730
+ enabled: Boolean(options.turnstile?.siteKey),
2731
+ ...options.turnstile?.siteKey ? { siteKey: options.turnstile.siteKey } : {}
2732
+ }
2733
+ });
2734
+ if (!await options.preview(
2735
+ "Create GitHub-backed portal source and private pairing secrets. GitHub requires D1 independently of sign-in. If the repository is public, issue titles/bodies are public immediately even while portal Posts await moderation. This does not create issues, pair the App, or deploy."
2736
+ ))
2737
+ throw new Error("GitHub setup was not approved.");
2738
+ await copyPackagedTemplate(options.destination);
2739
+ const path = join2(options.destination, "wrangler.jsonc");
2740
+ const base = JSON.parse(await readFile2(path, "utf8"));
2741
+ const wrangler = {
2742
+ ...base,
2743
+ name: options.workerName,
2744
+ compatibility_flags: [
2745
+ .../* @__PURE__ */ new Set([
2746
+ ...base.compatibility_flags ?? [],
2747
+ "global_fetch_strictly_public"
2748
+ ])
2749
+ ],
2750
+ d1_databases: [
2751
+ {
2752
+ binding: "FEEDBAX_DB",
2753
+ database_id: options.database.id,
2754
+ database_name: options.database.name,
2755
+ migrations_dir: "migrations"
2756
+ }
2757
+ ],
2758
+ triggers: { crons: ["*/5 * * * *"] },
2759
+ ...connected?.queueName ? {
2760
+ queues: {
2761
+ producers: [
2762
+ { binding: "FEEDBAX_UPDATES", queue: connected.queueName }
2763
+ ],
2764
+ consumers: [
2765
+ {
2766
+ queue: connected.queueName,
2767
+ max_batch_size: 1,
2768
+ max_concurrency: 1,
2769
+ max_retries: 3,
2770
+ retry_delay: 60
2771
+ }
2772
+ ]
2773
+ }
2774
+ } : {},
2775
+ ...connected?.imageBucket ? {
2776
+ r2_buckets: [
2777
+ { binding: "FEEDBAX_IMAGES", bucket_name: connected.imageBucket }
2778
+ ]
2779
+ } : {},
2780
+ ...connected?.email?.provider === "cloudflare" ? {
2781
+ send_email: [
2782
+ {
2783
+ name: "FEEDBAX_EMAIL",
2784
+ allowed_sender_addresses: [connected.email.from]
2785
+ }
2786
+ ]
2787
+ } : {}
2788
+ };
2789
+ const apiKey = randomBytes(32).toString("base64url");
2790
+ const pairingSecret = randomBytes(32).toString("base64url");
2791
+ const secrets = {
2792
+ FEEDBAX_API_KEY_HASH: createHash("sha256").update(apiKey).digest("base64url"),
2793
+ PARTICIPATION_SIGNING_SECRET: randomBytes(32).toString("base64url"),
2794
+ GITHUB_PAIRING_SECRET: pairingSecret,
2795
+ ...connected ? { IDENTITY_HANDOFF_SECRET: connected.handoffSecret } : {},
2796
+ ...connected?.email?.secret ? { EMAIL_PROVIDER_SECRET: connected.email.secret } : {},
2797
+ ...options.turnstile?.secretKey ? { TURNSTILE_SECRET_KEY: options.turnstile.secretKey } : {}
2798
+ };
2799
+ await writeFile2(path, JSON.stringify(wrangler, null, 2) + "\n");
2800
+ await writeFile2(
2801
+ join2(options.destination, "src/feedbax.ts"),
2802
+ `import { defineFeedbax } from "@feedbax/config";
2803
+
2804
+ export default defineFeedbax(${JSON.stringify(config, null, 2)});
2805
+ `
2806
+ );
2807
+ await writeFile2(
2808
+ join2(options.destination, ".dev.vars"),
2809
+ Object.entries(secrets).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join("\n") + "\n",
2810
+ { mode: 384 }
2811
+ );
2812
+ return { apiKey, pairingSecret, configuration: config };
2813
+ }
2814
+
2815
+ // src/index.ts
2816
+ function defaultProcessingQueueName(workerName) {
2817
+ return workerName.length <= 55 ? `${workerName}-updates` : `${workerName.slice(0, 46)}-${createHash2("sha256").update(workerName).digest("hex").slice(0, 8)}-updates`;
2818
+ }
2819
+ async function provisionGuidedInstallation({
2820
+ destination,
2821
+ product,
2822
+ themePreset,
2823
+ workerName,
2824
+ commentProbePageId,
2825
+ connected,
2826
+ features,
2827
+ feedbackSelection,
2828
+ changelogSelection,
2829
+ notionToken,
2830
+ turnstile,
2831
+ preview,
2832
+ request = fetch
2833
+ }) {
2834
+ try {
2835
+ if ((await readdir2(destination)).length)
2836
+ throw new Error(
2837
+ `Refusing to generate into non-empty destination: ${destination}`
2838
+ );
2839
+ } catch (error) {
2840
+ if (error.code !== "ENOENT") throw error;
2841
+ }
2842
+ if (product.siteUrl && new URL(product.siteUrl).protocol !== "https:")
2843
+ throw new Error("product.siteUrl must be an HTTPS URL.");
2844
+ if (connected) {
2845
+ if (!product.siteUrl || connected.handoffSecret.length < 32 || !/^[0-9a-f-]{36}$/i.test(connected.databaseId) || !connected.databaseName.trim())
2846
+ throw new Error(
2847
+ "Connected setup requires a public site URL, dedicated D1 identifiers, and a handoff secret of at least 32 characters."
2848
+ );
2849
+ for (const url of [connected.issuer, connected.loginUrl])
2850
+ if (new URL(url).protocol !== "https:" || new URL(url).username || new URL(url).password)
2851
+ throw new Error(
2852
+ "Connected identity URLs must use HTTPS without credentials."
2853
+ );
2854
+ }
2855
+ if (connected?.email) {
2856
+ const sender = connected.email;
2857
+ if (!["cloudflare", "resend", "custom"].includes(sender.provider) || !/^[^\s@<>]+@[^\s@<>]+\.[^\s@<>]+$/.test(sender.from))
2858
+ throw new Error(
2859
+ "Choose cloudflare, resend, or custom and a plain sender email address."
2860
+ );
2861
+ if (sender.provider !== "cloudflare" && !sender.secret?.trim())
2862
+ throw new Error(
2863
+ "Resend and custom delivery require a server-only email secret."
2864
+ );
2865
+ if (sender.provider === "custom" && (!sender.endpoint || new URL(sender.endpoint).protocol !== "https:" || new URL(sender.endpoint).username || new URL(sender.endpoint).password || new URL(sender.endpoint).hash))
2866
+ throw new Error(
2867
+ "Custom delivery requires an HTTPS endpoint without credentials or fragments."
2868
+ );
2869
+ }
2870
+ if (connected?.imageBucket && !/^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/.test(connected.imageBucket))
2871
+ throw new Error(
2872
+ "Use a dedicated private R2 bucket name of 3\u201363 lowercase letters, digits, or hyphens."
2873
+ );
2874
+ const targetName = workerName ?? basename(destination).toLowerCase().replace(/[^a-z0-9-]/g, "-");
2875
+ if (!/^[a-z0-9][a-z0-9-]{0,62}$/.test(targetName))
2876
+ throw new Error(
2877
+ "Worker name must contain 1\u201363 lowercase letters, numbers, or hyphens."
2878
+ );
2879
+ const processingQueue = connected && connected.processing !== "standard" ? connected.queueName ?? defaultProcessingQueueName(targetName) : void 0;
2880
+ if (processingQueue && !/^[a-z0-9][a-z0-9-]{0,62}$/.test(processingQueue))
2881
+ throw new Error(
2882
+ "Processing queue name must contain 1\u201363 lowercase letters, numbers, or hyphens."
2883
+ );
2884
+ const approved = await preview(
2885
+ feedbackSelection.kind === "create" ? `Create the canonical Feedback Data Source inside Notion page ${feedbackSelection.parentPageId}.` : `Validate existing Feedback Data Source ${feedbackSelection.configuration.dataSourceId} without changing it.`
2886
+ );
2887
+ if (!approved)
2888
+ throw new Error(
2889
+ "Feedback setup was not approved. No local files were created."
819
2890
  );
820
2891
  const feedback = feedbackSelection.kind === "create" ? await createNotionFeedbackDataSource({
821
2892
  token: notionToken,
@@ -851,21 +2922,74 @@ async function provisionGuidedInstallation({
851
2922
  preview
852
2923
  });
853
2924
  await copyPackagedTemplate(destination);
854
- const wranglerPath = join2(destination, "wrangler.jsonc");
855
- const wrangler = JSON.parse(await readFile2(wranglerPath, "utf8"));
856
- await writeFile2(
2925
+ const wranglerPath = join3(destination, "wrangler.jsonc");
2926
+ const wrangler = JSON.parse(await readFile3(wranglerPath, "utf8"));
2927
+ await writeFile3(
857
2928
  wranglerPath,
858
- JSON.stringify({ ...wrangler, name: targetName }, null, 2) + "\n"
2929
+ JSON.stringify(
2930
+ {
2931
+ ...wrangler,
2932
+ name: targetName,
2933
+ ...connected ? {
2934
+ triggers: { crons: ["*/5 * * * *"] },
2935
+ ...processingQueue ? {
2936
+ queues: {
2937
+ producers: [
2938
+ { binding: "FEEDBAX_UPDATES", queue: processingQueue }
2939
+ ],
2940
+ consumers: [
2941
+ {
2942
+ queue: processingQueue,
2943
+ max_batch_size: 1,
2944
+ max_batch_timeout: 1,
2945
+ max_retries: 3,
2946
+ max_concurrency: 1,
2947
+ retry_delay: 60
2948
+ }
2949
+ ]
2950
+ }
2951
+ } : {},
2952
+ ...connected.email?.provider === "cloudflare" ? {
2953
+ send_email: [
2954
+ {
2955
+ name: "FEEDBAX_EMAIL",
2956
+ allowed_sender_addresses: [connected.email.from]
2957
+ }
2958
+ ]
2959
+ } : {},
2960
+ ...connected.imageBucket ? {
2961
+ r2_buckets: [
2962
+ {
2963
+ binding: "FEEDBAX_IMAGES",
2964
+ bucket_name: connected.imageBucket
2965
+ }
2966
+ ]
2967
+ } : {},
2968
+ d1_databases: [
2969
+ {
2970
+ binding: "FEEDBAX_DB",
2971
+ database_id: connected.databaseId,
2972
+ database_name: connected.databaseName,
2973
+ migrations_dir: "migrations"
2974
+ }
2975
+ ]
2976
+ } : {}
2977
+ },
2978
+ null,
2979
+ 2
2980
+ ) + "\n"
859
2981
  );
860
- const apiKey = randomBytes(32).toString("base64url");
861
- const signingSecret = randomBytes(32).toString("base64url");
2982
+ const apiKey = randomBytes2(32).toString("base64url");
2983
+ const signingSecret = randomBytes2(32).toString("base64url");
862
2984
  const values = {
863
2985
  NOTION_TOKEN: notionToken,
864
2986
  NOTION_FEEDBACK_DATABASE_ID: feedback.databaseId,
865
2987
  NOTION_FEEDBACK_DATA_SOURCE_ID: feedback.dataSourceId,
866
2988
  ...prefixProperties("NOTION_FEEDBACK", feedback.propertyIds),
867
- FEEDBAX_API_KEY_HASH: createHash("sha256").update(apiKey).digest("base64url"),
2989
+ FEEDBAX_API_KEY_HASH: createHash2("sha256").update(apiKey).digest("base64url"),
868
2990
  PARTICIPATION_SIGNING_SECRET: signingSecret,
2991
+ ...connected ? { IDENTITY_HANDOFF_SECRET: connected.handoffSecret } : {},
2992
+ ...connected?.email?.secret ? { EMAIL_PROVIDER_SECRET: connected.email.secret } : {},
869
2993
  FEEDBAX_VOTING_ENABLED: String(features.voting),
870
2994
  FEEDBAX_COMMENTS_ENABLED: String(features.comments),
871
2995
  FEEDBAX_CHANGELOG_ENABLED: String(features.changelog),
@@ -877,14 +3001,29 @@ async function provisionGuidedInstallation({
877
3001
  ...prefixProperties("NOTION_CHANGELOG", changelog.propertyIds)
878
3002
  } : {}
879
3003
  };
880
- await writeFile2(
881
- join2(destination, ".dev.vars"),
3004
+ await writeFile3(
3005
+ join3(destination, ".dev.vars"),
882
3006
  Object.entries(values).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join("\n") + "\n",
883
3007
  { mode: 384 }
884
3008
  );
885
3009
  const configuration = defineFeedbax({
886
3010
  product: { ...product, logo: "/feedbax-mark.svg" },
887
3011
  theme: { preset: themePreset },
3012
+ ...connected ? {
3013
+ connected: {
3014
+ processing: connected.processing ?? "small",
3015
+ issuer: connected.issuer,
3016
+ loginUrl: connected.loginUrl,
3017
+ ...connected.email ? {
3018
+ email: {
3019
+ provider: connected.email.provider,
3020
+ from: connected.email.from,
3021
+ ...connected.email.endpoint ? { endpoint: connected.email.endpoint } : {}
3022
+ }
3023
+ } : {},
3024
+ ...connected.imageBucket ? { images: true } : {}
3025
+ }
3026
+ } : {},
888
3027
  notion: {
889
3028
  databaseId: feedback.databaseId,
890
3029
  dataSourceId: feedback.dataSourceId,
@@ -908,21 +3047,21 @@ async function provisionGuidedInstallation({
908
3047
  ...turnstile.siteKey ? { siteKey: turnstile.siteKey } : {}
909
3048
  }
910
3049
  });
911
- await writeFile2(
912
- join2(destination, "src/feedbax.ts"),
3050
+ await writeFile3(
3051
+ join3(destination, "src/feedbax.ts"),
913
3052
  `import { defineFeedbax } from "@feedbax/config";
914
3053
 
915
3054
  export default defineFeedbax(${JSON.stringify(configuration, null, 2)});
916
3055
  `
917
3056
  );
918
3057
  const probePage = commentProbePageId ?? (feedbackSelection.kind === "create" ? feedbackSelection.parentPageId : feedback.databaseId);
919
- await writeFile2(
920
- join2(destination, ".dev.vars"),
3058
+ await writeFile3(
3059
+ join3(destination, ".dev.vars"),
921
3060
  `NOTION_COMMENT_CAPABILITY_PROBE_PAGE_ID=${JSON.stringify(probePage)}
922
3061
  `,
923
3062
  { flag: "a", mode: 384 }
924
3063
  );
925
- return { apiKey, feedback, changelog };
3064
+ return { apiKey, feedback, changelog, processingQueue };
926
3065
  }
927
3066
  function prefixProperties(prefix, properties) {
928
3067
  return Object.fromEntries(
@@ -1061,19 +3200,19 @@ try {
1061
3200
  const args = process.argv.slice(2);
1062
3201
  if (args.includes("--help") || args.includes("-h")) {
1063
3202
  console.log(
1064
- "Usage: create-feedbax [directory] [--template-only]\nGuided setup prompts for a destination, product, Notion connection, features, and deployment.\n--template-only copies source without external requests or installing dependencies."
3203
+ "Usage: create-feedbax [directory] [--template-only]\nGuided setup prompts for a destination, product, Notion or GitHub connection, features, and deployment.\n--template-only copies source without external requests or installing dependencies."
1065
3204
  );
1066
3205
  } else if (args.includes("--version")) {
1067
3206
  console.log(
1068
3207
  JSON.parse(
1069
- await readFile3(new URL("../package.json", import.meta.url), "utf8")
3208
+ await readFile4(new URL("../package.json", import.meta.url), "utf8")
1070
3209
  ).version
1071
3210
  );
1072
3211
  } else {
1073
3212
  if (args.some((arg) => arg.startsWith("-") && arg !== "--template-only"))
1074
3213
  throw new Error("Unknown option. Run create-feedbax --help.");
1075
3214
  const destination = resolve2(
1076
- args.find((arg) => !arg.startsWith("-")) ?? await text3("Project directory", "my-feedback")
3215
+ args.find((arg) => !arg.startsWith("-")) ?? await text4("Project directory", "my-feedback")
1077
3216
  );
1078
3217
  if (args.includes("--template-only")) {
1079
3218
  await copyPackagedTemplate(destination);
@@ -1081,109 +3220,195 @@ try {
1081
3220
  } else {
1082
3221
  prompt.intro("Create a Feedbax portal");
1083
3222
  const product = {
1084
- name: await text3("Product name", "My product"),
1085
- description: await text3(
3223
+ name: await text4("Product name", "My product"),
3224
+ description: await text4(
1086
3225
  "Public description",
1087
3226
  "Share feedback and follow product progress."
1088
3227
  ),
1089
- siteUrl: await text3(
3228
+ siteUrl: await text4(
1090
3229
  "Public portal URL (your workers.dev URL or custom domain)",
1091
3230
  "https://my-feedback.example.workers.dev"
1092
3231
  )
1093
3232
  };
1094
- const workerName = await text3(
3233
+ const workerName = await text4(
1095
3234
  "Cloudflare Worker name (choose a distinct name for this product)",
1096
3235
  basename2(destination).toLowerCase().replace(/[^a-z0-9-]/g, "-")
1097
3236
  );
1098
- const themePreset = await text3(
1099
- "shadcn preset code, or lyra-neutral for the included preset",
1100
- "lyra-neutral"
1101
- );
1102
- const notionToken = process.env.NOTION_TOKEN?.trim() || await secret("Notion internal integration token");
1103
- const existing = await confirm2(
1104
- "Use an existing compatible Feedback Data Source?",
1105
- false
3237
+ const provider = checked(
3238
+ await prompt.select({
3239
+ message: "Content Provider",
3240
+ options: [
3241
+ { value: "notion", label: "Notion" },
3242
+ { value: "github", label: "GitHub" }
3243
+ ]
3244
+ })
1106
3245
  );
1107
- const feedbackSelection = existing ? { kind: "existing", configuration: await existingFeedback() } : {
1108
- kind: "create",
1109
- parentPageId: await text3(
1110
- "Notion parent page ID (share it with the integration)"
1111
- )
1112
- };
1113
- const features = await collectFeatureSelection({
1114
- confirm: ({ message, initialValue }) => confirm2(message, initialValue)
1115
- });
1116
- const commentProbePageId = existing && features.comments ? await text3("Shared Notion page ID for checking Comment permissions") : void 0;
1117
- const changelogSelection = features.changelog && await confirm2(
1118
- "Use an existing compatible Changelog Data Source?",
1119
- false
1120
- ) ? {
1121
- kind: "existing",
1122
- dataSourceId: await text3("Changelog Data Source ID"),
1123
- propertyIds: await propertyIds([
1124
- "title",
1125
- "slug",
1126
- "date",
1127
- "summary",
1128
- "body",
1129
- "labels",
1130
- "image",
1131
- "published",
1132
- "createdAt",
1133
- "updatedAt"
1134
- ])
1135
- } : void 0;
1136
- const turnstile = await confirm2(
1137
- "Enable Cloudflare Turnstile? Recommended for production.",
1138
- true
1139
- ) ? {
1140
- siteKey: await text3("Turnstile site key"),
1141
- secretKey: process.env.TURNSTILE_SECRET_KEY || await secret("Turnstile secret key")
1142
- } : {};
1143
- const result = await provisionGuidedInstallation({
1144
- destination,
1145
- product,
1146
- themePreset,
1147
- workerName,
1148
- commentProbePageId,
1149
- features,
1150
- feedbackSelection,
1151
- changelogSelection,
1152
- notionToken,
1153
- turnstile,
1154
- preview: (message) => confirm2(message, false)
1155
- });
1156
- console.log(
1157
- `Created ${destination}.
3246
+ if (provider === "github") {
3247
+ await githubSetup(destination, product, workerName);
3248
+ } else {
3249
+ const connected = await confirm2(
3250
+ "Enable Connected Profile using your application's sign-in, dedicated D1, and a processing queue? Small processing supports Workers Free with Resend/custom email.",
3251
+ false
3252
+ ) ? {
3253
+ issuer: await text4("Application identity issuer (HTTPS URL)"),
3254
+ loginUrl: await text4(
3255
+ "Application signed-handoff login URL (HTTPS)"
3256
+ ),
3257
+ databaseId: await text4("Dedicated Cloudflare D1 database ID"),
3258
+ databaseName: await text4("Dedicated Cloudflare D1 database name"),
3259
+ queueName: await text4(
3260
+ "Dedicated Cloudflare Queue name (create it with wrangler queues create first)",
3261
+ defaultProcessingQueueName(workerName)
3262
+ ),
3263
+ handoffSecret: await secret(
3264
+ "Shared server-only identity handoff secret (at least 32 characters)"
3265
+ )
3266
+ } : void 0;
3267
+ let email;
3268
+ let imageBucket;
3269
+ if (connected) {
3270
+ const provider2 = await text4(
3271
+ "Email sender: cloudflare, resend, custom, or none",
3272
+ "none"
3273
+ );
3274
+ if (provider2 !== "none") {
3275
+ if (provider2 !== "cloudflare" && provider2 !== "resend" && provider2 !== "custom")
3276
+ throw new Error(
3277
+ "Email sender must be cloudflare, resend, custom, or none."
3278
+ );
3279
+ email = {
3280
+ provider: provider2,
3281
+ from: await text4("Verified sender email address"),
3282
+ ...provider2 === "custom" ? { endpoint: await text4("Custom HTTPS delivery endpoint") } : {},
3283
+ ...provider2 !== "cloudflare" ? { secret: await secret("Server-only email provider secret") } : {}
3284
+ };
3285
+ }
3286
+ if (await confirm2(
3287
+ "Enable Participant image attachments using an existing private R2 bucket?",
3288
+ false
3289
+ ))
3290
+ imageBucket = await text4(
3291
+ "Dedicated private R2 bucket name (create it in Cloudflare first)"
3292
+ );
3293
+ }
3294
+ const themePreset = await text4(
3295
+ "shadcn preset code, or lyra-neutral for the included preset",
3296
+ "lyra-neutral"
3297
+ );
3298
+ const notionToken = process.env.NOTION_TOKEN?.trim() || await secret("Notion internal integration token");
3299
+ const existing = await confirm2(
3300
+ "Use an existing compatible Feedback Data Source?",
3301
+ false
3302
+ );
3303
+ const feedbackSelection = existing ? {
3304
+ kind: "existing",
3305
+ configuration: await existingFeedback()
3306
+ } : {
3307
+ kind: "create",
3308
+ parentPageId: await text4(
3309
+ "Notion parent page ID (share it with the integration)"
3310
+ )
3311
+ };
3312
+ const features = await collectFeatureSelection({
3313
+ confirm: ({ message, initialValue }) => confirm2(message, initialValue)
3314
+ });
3315
+ const commentProbePageId = existing && features.comments ? await text4(
3316
+ "Shared Notion page ID for checking Comment permissions"
3317
+ ) : void 0;
3318
+ const changelogSelection = features.changelog && await confirm2(
3319
+ "Use an existing compatible Changelog Data Source?",
3320
+ false
3321
+ ) ? {
3322
+ kind: "existing",
3323
+ dataSourceId: await text4("Changelog Data Source ID"),
3324
+ propertyIds: await propertyIds([
3325
+ "title",
3326
+ "slug",
3327
+ "date",
3328
+ "summary",
3329
+ "body",
3330
+ "labels",
3331
+ "image",
3332
+ "published",
3333
+ "createdAt",
3334
+ "updatedAt"
3335
+ ])
3336
+ } : void 0;
3337
+ const turnstile = await confirm2(
3338
+ "Enable Cloudflare Turnstile? Recommended for production.",
3339
+ true
3340
+ ) ? {
3341
+ siteKey: await text4("Turnstile site key"),
3342
+ secretKey: process.env.TURNSTILE_SECRET_KEY || await secret("Turnstile secret key")
3343
+ } : {};
3344
+ const result = await provisionGuidedInstallation({
3345
+ destination,
3346
+ product,
3347
+ themePreset,
3348
+ workerName,
3349
+ commentProbePageId,
3350
+ connected: connected ? {
3351
+ ...connected,
3352
+ ...email ? { email } : {},
3353
+ ...imageBucket ? { imageBucket } : {}
3354
+ } : void 0,
3355
+ features,
3356
+ feedbackSelection,
3357
+ changelogSelection,
3358
+ notionToken,
3359
+ turnstile,
3360
+ preview: (message) => confirm2(message, false)
3361
+ });
3362
+ console.log(
3363
+ `Created ${destination}.
1158
3364
  Trusted API key \u2014 copy it now; plaintext is not stored:
1159
3365
  ${result.apiKey}`
1160
- );
1161
- await runIn(destination, "pnpm", ["install"]);
1162
- if (themePreset !== "lyra-neutral")
3366
+ );
3367
+ await runIn(destination, "pnpm", ["install"]);
3368
+ if (themePreset !== "lyra-neutral")
3369
+ await runIn(destination, "pnpm", [
3370
+ "dlx",
3371
+ "shadcn@latest",
3372
+ "init",
3373
+ "--preset",
3374
+ themePreset,
3375
+ "--yes",
3376
+ "--force"
3377
+ ]);
1163
3378
  await runIn(destination, "pnpm", [
1164
- "dlx",
1165
- "shadcn@latest",
1166
- "init",
1167
- "--preset",
1168
- themePreset,
1169
- "--yes",
1170
- "--force"
3379
+ "exec",
3380
+ "prettier",
3381
+ "--write",
3382
+ "src/feedbax.ts"
1171
3383
  ]);
1172
- await runIn(destination, "pnpm", [
1173
- "exec",
1174
- "prettier",
1175
- "--write",
1176
- "src/feedbax.ts"
1177
- ]);
1178
- await runIn(destination, "pnpm", ["exec", "feedbax", "doctor"]);
1179
- if (await confirm2(
1180
- "Authenticate with Cloudflare and deploy this portal?",
1181
- false
1182
- )) {
1183
- await runIn(destination, "pnpm", ["exec", "wrangler", "login"]);
1184
- await runIn(destination, "pnpm", ["exec", "feedbax", "deploy"]);
3384
+ if (connected) {
3385
+ if (!await confirm2(
3386
+ `Apply the included Connected schema migration to D1 ${connected.databaseName} (${connected.databaseId}) in your authenticated Cloudflare account?`,
3387
+ false
3388
+ ))
3389
+ throw new Error(
3390
+ "Connected source is preserved. Apply the D1 migration described in CONNECTED.md before running doctor or deploying."
3391
+ );
3392
+ await runIn(destination, "pnpm", [
3393
+ "exec",
3394
+ "wrangler",
3395
+ "d1",
3396
+ "migrations",
3397
+ "apply",
3398
+ "FEEDBAX_DB",
3399
+ "--remote"
3400
+ ]);
3401
+ }
3402
+ await runIn(destination, "pnpm", ["exec", "feedbax", "doctor"]);
3403
+ if (await confirm2(
3404
+ "Authenticate with Cloudflare and deploy this portal?",
3405
+ false
3406
+ )) {
3407
+ await runIn(destination, "pnpm", ["exec", "wrangler", "login"]);
3408
+ await runIn(destination, "pnpm", ["exec", "feedbax", "deploy"]);
3409
+ }
3410
+ prompt.outro(`Your portal source is ready in ${destination}.`);
1185
3411
  }
1186
- prompt.outro(`Your portal source is ready in ${destination}.`);
1187
3412
  }
1188
3413
  }
1189
3414
  } catch (error) {
@@ -1196,7 +3421,7 @@ function checked(value) {
1196
3421
  if (prompt.isCancel(value)) throw new Error("Setup cancelled.");
1197
3422
  return value;
1198
3423
  }
1199
- async function text3(message, defaultValue) {
3424
+ async function text4(message, defaultValue) {
1200
3425
  return checked(
1201
3426
  await prompt.text({
1202
3427
  message,
@@ -1220,13 +3445,13 @@ async function confirm2(message, initialValue) {
1220
3445
  async function propertyIds(keys) {
1221
3446
  const entries = [];
1222
3447
  for (const key of keys)
1223
- entries.push([key, await text3(`Stable property ID: ${key}`)]);
3448
+ entries.push([key, await text4(`Stable property ID: ${key}`)]);
1224
3449
  return Object.fromEntries(entries);
1225
3450
  }
1226
3451
  async function existingFeedback() {
1227
3452
  return {
1228
- databaseId: await text3("Feedback Database ID"),
1229
- dataSourceId: await text3("Feedback Data Source ID"),
3453
+ databaseId: await text4("Feedback Database ID"),
3454
+ dataSourceId: await text4("Feedback Data Source ID"),
1230
3455
  propertyIds: await propertyIds([
1231
3456
  "title",
1232
3457
  "slug",
@@ -1261,3 +3486,97 @@ function runIn(cwd, command, args) {
1261
3486
  );
1262
3487
  });
1263
3488
  }
3489
+ async function githubSetup(destination, product, workerName) {
3490
+ const serviceUrl = await text4(
3491
+ "GitHub App service HTTPS origin (official or your self-hosted service)"
3492
+ );
3493
+ const infoResponse = await fetch(`${serviceUrl}/v1/info`, {
3494
+ redirect: "manual"
3495
+ });
3496
+ if (!infoResponse.ok)
3497
+ throw new Error(
3498
+ "The App service is unavailable. Configure it before portal setup."
3499
+ );
3500
+ const info = await infoResponse.json();
3501
+ if (!Number.isSafeInteger(info.botUserId))
3502
+ throw new Error("The service did not return a valid App identity.");
3503
+ const database = {
3504
+ id: await text4(
3505
+ "Dedicated Cloudflare D1 database ID (required independently of sign-in)"
3506
+ ),
3507
+ name: await text4("Dedicated D1 database name")
3508
+ };
3509
+ const result = await provisionGitHubInstallation({
3510
+ destination,
3511
+ product,
3512
+ workerName,
3513
+ database,
3514
+ github: {
3515
+ serviceUrl,
3516
+ installationId: await text4("Stable Feedbax Installation ID", workerName),
3517
+ repository: {
3518
+ id: Number(
3519
+ await text4(
3520
+ "Feedback Repository numeric ID (gh api repos/OWNER/NAME --jq .id)"
3521
+ )
3522
+ ),
3523
+ owner: await text4("Repository owner"),
3524
+ name: await text4("Repository name")
3525
+ },
3526
+ botUserId: info.botUserId
3527
+ },
3528
+ features: {
3529
+ voting: await confirm2("Enable best-effort browser voting?", true),
3530
+ comments: await confirm2("Enable portal Comments?", true),
3531
+ changelog: false
3532
+ },
3533
+ turnstile: await confirm2("Enable Cloudflare Turnstile?", true) ? {
3534
+ siteKey: await text4("Turnstile site key"),
3535
+ secretKey: await secret("Turnstile secret key")
3536
+ } : {},
3537
+ preview: (message) => confirm2(message, false)
3538
+ });
3539
+ console.log(
3540
+ `Created ${destination}. Trusted API key \u2014 copy it now; plaintext is not stored:
3541
+ ${result.apiKey}`
3542
+ );
3543
+ await runIn(destination, "pnpm", ["install"]);
3544
+ await runIn(destination, "pnpm", [
3545
+ "exec",
3546
+ "prettier",
3547
+ "--write",
3548
+ "src/feedbax.ts"
3549
+ ]);
3550
+ if (await confirm2(
3551
+ `Apply included additive migrations to D1 ${database.name}?`,
3552
+ false
3553
+ ))
3554
+ await runIn(destination, "pnpm", [
3555
+ "exec",
3556
+ "wrangler",
3557
+ "d1",
3558
+ "migrations",
3559
+ "apply",
3560
+ "FEEDBAX_DB",
3561
+ "--remote"
3562
+ ]);
3563
+ if (await confirm2("Deploy the unpaired portal for GitHub authorization?", false))
3564
+ await runIn(destination, "pnpm", [
3565
+ "exec",
3566
+ "feedbax",
3567
+ "deploy",
3568
+ "--pairing-bootstrap"
3569
+ ]);
3570
+ const installationId = await text4(
3571
+ "GitHub App installation ID from the App installation page"
3572
+ );
3573
+ await runIn(destination, "pnpm", [
3574
+ "exec",
3575
+ "feedbax",
3576
+ "pair-github",
3577
+ installationId
3578
+ ]);
3579
+ prompt.outro(
3580
+ "Finish the GitHub authorization, then run feedbax doctor. Connected identity can be configured independently using CONNECTED.md."
3581
+ );
3582
+ }