@tangen/db 0.0.0-stage → 0.1.54

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 (127) hide show
  1. package/README.md +73 -2
  2. package/drizzle/0000_flimsy_shotgun.sql +339 -0
  3. package/drizzle/0001_app_data.sql +11 -0
  4. package/drizzle/0002_add_indexes.sql +36 -0
  5. package/drizzle/0003_extension_data_rename.sql +3 -0
  6. package/drizzle/0004_extension_data_published_globals.sql +12 -0
  7. package/drizzle/0005_auth.sql +51 -0
  8. package/drizzle/0006_auth_identities.sql +13 -0
  9. package/drizzle/0007_ai.sql +18 -0
  10. package/drizzle/0008_ai_chat_threads.sql +5 -0
  11. package/drizzle/0009_forms_source_slug.sql +11 -0
  12. package/drizzle/0010_rbac_roles_status.sql +14 -0
  13. package/drizzle/0011_jobs_v2.sql +67 -0
  14. package/drizzle/0012_search_platform.sql +36 -0
  15. package/drizzle/0013_search_synonym_label.sql +2 -0
  16. package/drizzle/0014_object_types_disabled.sql +2 -0
  17. package/drizzle/0015_playground_collections.sql +25 -0
  18. package/drizzle/0016_catchup_activity_promotion.sql +36 -0
  19. package/drizzle/0017_fields_disabled.sql +3 -0
  20. package/drizzle/0018_object_types_plural.sql +1 -0
  21. package/drizzle/0019_member_profile.sql +2 -0
  22. package/drizzle/0020_paused_jobs.sql +7 -0
  23. package/drizzle/0021_form_submission_notes.sql +2 -0
  24. package/drizzle/0022_paused_jobs_env_reason.sql +4 -0
  25. package/drizzle/0023_scale_indexes.sql +3 -0
  26. package/drizzle/0024_migration_runs.sql +24 -0
  27. package/drizzle/0025_porter_transfer.sql +30 -0
  28. package/drizzle/0026_cron_timezone.sql +1 -0
  29. package/drizzle/0027_rate_limit_counters.sql +6 -0
  30. package/drizzle/0028_realtime_events.sql +10 -0
  31. package/drizzle/0030_delivery_artifacts.sql +10 -0
  32. package/drizzle/0031_realtime_presence.sql +8 -0
  33. package/drizzle/0032_schema_pushes.sql +15 -0
  34. package/drizzle/0033_studio_design_capability.sql +37 -0
  35. package/drizzle/0034_object_types_provided_by.sql +12 -0
  36. package/drizzle/0035_custom_table_owners.sql +24 -0
  37. package/drizzle/0036_revision_version_unique.sql +42 -0
  38. package/drizzle/0037_job_queue_dedupe.sql +20 -0
  39. package/drizzle/0038_search_documents.sql +87 -0
  40. package/drizzle/0039_job_prune_indexes.sql +25 -0
  41. package/drizzle/0040_extension_data_object_idx.sql +10 -0
  42. package/drizzle/0041_knowledge.sql +79 -0
  43. package/drizzle/0042_schema_pushes_checksum.sql +7 -0
  44. package/drizzle/0043_guard_adoption.sql +12 -0
  45. package/drizzle/0044_retention_indexes.sql +7 -0
  46. package/drizzle/0045_extension_install_per_env.sql +40 -0
  47. package/drizzle/0046_extension_migrations.sql +18 -0
  48. package/drizzle/0047_environment_role.sql +25 -0
  49. package/drizzle/0048_environment_role_adoption.sql +30 -0
  50. package/drizzle/0049_release_lifecycle.sql +55 -0
  51. package/drizzle/0050_member_env_membership.sql +21 -0
  52. package/drizzle/0051_remove_environments.sql +350 -0
  53. package/drizzle/0052_release_actor_columns.sql +9 -0
  54. package/drizzle/0053_kernel_event_record.sql +26 -0
  55. package/drizzle/0054_kernel_run_identity.sql +13 -0
  56. package/drizzle/0055_kernel_identities.sql +19 -0
  57. package/drizzle/0056_forms_leave_core.sql +7 -0
  58. package/drizzle/0057_auth_leave_core.sql +8 -0
  59. package/drizzle/0058_member_grants.sql +11 -0
  60. package/drizzle/0059_extension_granted_grants.sql +7 -0
  61. package/drizzle/0060_connections.sql +21 -0
  62. package/drizzle/meta/0000_snapshot.json +1692 -0
  63. package/drizzle/meta/0002_snapshot.json +1935 -0
  64. package/drizzle/meta/0003_snapshot.json +1935 -0
  65. package/drizzle/meta/_journal.json +426 -0
  66. package/package.json +85 -4
  67. package/src/cache-keys.ts +43 -0
  68. package/src/client.ts +136 -0
  69. package/src/content-ddl.ts +70 -0
  70. package/src/content-tables.ts +110 -0
  71. package/src/custom-tables-loader.ts +65 -0
  72. package/src/ensure-system-schema.ts +195 -0
  73. package/src/extension-install-state.ts +220 -0
  74. package/src/extension-migrations.ts +270 -0
  75. package/src/generated/content-tables.ts +271 -0
  76. package/src/generated/custom-tables.ts +18 -0
  77. package/src/index.ts +24 -0
  78. package/src/instrument.ts +139 -0
  79. package/src/knowledge-schema.ts +232 -0
  80. package/src/migrate.ts +67 -0
  81. package/src/prepare-policy.ts +118 -0
  82. package/src/reconcile.ts +115 -0
  83. package/src/schema/ai-chats.ts +36 -0
  84. package/src/schema/ai-settings.ts +24 -0
  85. package/src/schema/api-keys.ts +37 -0
  86. package/src/schema/audit-log.ts +54 -0
  87. package/src/schema/connections.ts +35 -0
  88. package/src/schema/custom-table-owners.ts +32 -0
  89. package/src/schema/delivery-artifacts.ts +33 -0
  90. package/src/schema/entity-tags.ts +33 -0
  91. package/src/schema/extension-data.ts +38 -0
  92. package/src/schema/extension-migrations.ts +42 -0
  93. package/src/schema/fields.ts +37 -0
  94. package/src/schema/globals.ts +35 -0
  95. package/src/schema/identities.ts +27 -0
  96. package/src/schema/index.ts +51 -0
  97. package/src/schema/jobs.ts +104 -0
  98. package/src/schema/knowledge.ts +85 -0
  99. package/src/schema/media.ts +52 -0
  100. package/src/schema/member-grants.ts +34 -0
  101. package/src/schema/members.ts +49 -0
  102. package/src/schema/migration-runs.ts +46 -0
  103. package/src/schema/object-types.ts +41 -0
  104. package/src/schema/objects.ts +38 -0
  105. package/src/schema/paused-jobs.ts +25 -0
  106. package/src/schema/playground.ts +43 -0
  107. package/src/schema/porter-transfer.ts +47 -0
  108. package/src/schema/realtime.ts +45 -0
  109. package/src/schema/releases.ts +95 -0
  110. package/src/schema/roles.ts +27 -0
  111. package/src/schema/saved-views.ts +33 -0
  112. package/src/schema/schema-pushes.ts +43 -0
  113. package/src/schema/search-documents.ts +47 -0
  114. package/src/schema/search-query-log.ts +24 -0
  115. package/src/schema/search-recommendations.ts +27 -0
  116. package/src/schema/search-synonyms.ts +23 -0
  117. package/src/schema/webhook-deliveries.ts +37 -0
  118. package/src/schema/webhooks.ts +36 -0
  119. package/src/schema/workspace-activity.ts +35 -0
  120. package/src/schema/workspace-domains.ts +24 -0
  121. package/src/schema/workspace-installed-apps.ts +48 -0
  122. package/src/schema/workspaces.ts +24 -0
  123. package/src/schema-kit.ts +64 -0
  124. package/src/sealed.ts +84 -0
  125. package/src/seed/skeleton.ts +77 -0
  126. package/src/sql/apply-gin-indexes.ts +31 -0
  127. package/src/sql/gin-indexes.sql +11 -0
package/README.md CHANGED
@@ -1,3 +1,74 @@
1
- # Temporary Holding Version
1
+ # @tangen/db
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Database client and system schema for the Tangen headless CMS — a typed
4
+ Drizzle ORM client over postgres.js plus the platform's system table
5
+ definitions (workspaces, environments, object types, fields, members, …).
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ bun add @tangen/db
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import { createDb, objectTypes, environments } from '@tangen/db'
17
+
18
+ const db = createDb(process.env.DATABASE_URL!, {
19
+ max: 10, // pool size (optional)
20
+ prepare: false, // opt out of prepared execution (optional; default true)
21
+ })
22
+
23
+ const types = await db.select().from(objectTypes)
24
+ await db.end()
25
+ ```
26
+
27
+ Pool options may also come from environment variables:
28
+ `TANGEN_DB_POOL_MAX`, `TANGEN_DB_IDLE_TIMEOUT`, `TANGEN_DB_CONNECT_TIMEOUT`,
29
+ `TANGEN_DB_PREPARE`.
30
+
31
+ ### `TANGEN_DB_PREPARE`
32
+
33
+ **Defaults to `true`.** Tangen owns this contract rather than inheriting the
34
+ driver's, so an unset variable has one documented meaning. Precedence is
35
+ `createDb`'s `prepare` option, then `TANGEN_DB_PREPARE`, then the default; only
36
+ the exact strings `true` and `false` are recognised, and `tangen doctor` prints
37
+ the effective value with its source so a typo is visible.
38
+
39
+ Prepared execution reuses a named statement per SQL shape per connection, which
40
+ removes one PostgreSQL protocol round trip from every repeated parameterized
41
+ query. On a remote database that is roughly half of each statement.
42
+
43
+ `resolvePrepare(opts, env)` returns `{ prepare, source }` for anything that
44
+ needs to report the decision.
45
+
46
+ postgres.js does not honour a pool-level `prepare` for the queries Drizzle
47
+ issues, so `prepare-policy.ts` threads the option through by hand. Read that
48
+ file before changing anything here; it also carries the removal condition for
49
+ when Drizzle fixes this upstream.
50
+
51
+ Set `TANGEN_DB_PREPARE=false` for a pooler or proxy that cannot carry named
52
+ statements - PgBouncer in transaction mode with `max_prepared_statements = 0`,
53
+ or anything whose support you have not verified. Direct PostgreSQL, session
54
+ pooling, and PgBouncer 1.21+ transaction pooling with prepared statements
55
+ enabled all want the default. `false` is permanent: nothing overrides it.
56
+
57
+ One thing to know if you write SQL against this client: a named statement is
58
+ cached per distinct SQL string, per connection, until that connection closes.
59
+ Code that inlines a value into the SQL text instead of binding it as a
60
+ parameter mints a new prepared statement on every call. Tangen's own write
61
+ paths bind their values; extension or application code that concatenates would
62
+ accumulate.
63
+
64
+ ## What's included
65
+
66
+ - `createDb(url, poolOptions?)` — Drizzle client with an `end()` helper
67
+ - All Tangen system table definitions (drizzle `pgTable` objects)
68
+
69
+ Project-specific generated content tables are **not** part of this package —
70
+ they are generated into your project by `tangen push`.
71
+
72
+ ## License
73
+
74
+ MIT
@@ -0,0 +1,339 @@
1
+ CREATE TABLE IF NOT EXISTS "workspaces" (
2
+ "id" text PRIMARY KEY NOT NULL,
3
+ "name" text NOT NULL,
4
+ "slug" text NOT NULL,
5
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
6
+ "deleted_at" timestamp with time zone,
7
+ CONSTRAINT "workspaces_slug_unique" UNIQUE("slug")
8
+ );
9
+ --> statement-breakpoint
10
+ CREATE TABLE IF NOT EXISTS "environments" (
11
+ "id" text PRIMARY KEY NOT NULL,
12
+ "workspace_id" text NOT NULL,
13
+ "slug" text NOT NULL,
14
+ "label" text NOT NULL,
15
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
16
+ CONSTRAINT "environments_workspace_id_slug_unique" UNIQUE("workspace_id","slug")
17
+ );
18
+ --> statement-breakpoint
19
+ CREATE TABLE IF NOT EXISTS "object_types" (
20
+ "id" text PRIMARY KEY NOT NULL,
21
+ "env_id" text NOT NULL,
22
+ "name" text NOT NULL,
23
+ "label" text NOT NULL,
24
+ "singleton" boolean DEFAULT false NOT NULL,
25
+ "previous_names" text[] DEFAULT '{}' NOT NULL,
26
+ "admin_config" jsonb DEFAULT '{}' NOT NULL,
27
+ "schema_version" integer DEFAULT 1 NOT NULL,
28
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
29
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
30
+ CONSTRAINT "object_types_env_id_name_unique" UNIQUE("env_id","name")
31
+ );
32
+ --> statement-breakpoint
33
+ CREATE TABLE IF NOT EXISTS "fields" (
34
+ "id" text PRIMARY KEY NOT NULL,
35
+ "env_id" text NOT NULL,
36
+ "type_id" text NOT NULL,
37
+ "name" text NOT NULL,
38
+ "label" text NOT NULL,
39
+ "field_type" text NOT NULL,
40
+ "config" jsonb DEFAULT '{}' NOT NULL,
41
+ "previous_names" text[] DEFAULT '{}' NOT NULL,
42
+ "position" integer DEFAULT 0 NOT NULL,
43
+ "required" boolean DEFAULT false NOT NULL,
44
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
45
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
46
+ CONSTRAINT "fields_type_id_name_unique" UNIQUE("type_id","name")
47
+ );
48
+ --> statement-breakpoint
49
+ CREATE TABLE IF NOT EXISTS "_revisions" (
50
+ "id" text PRIMARY KEY NOT NULL,
51
+ "workspace_id" text NOT NULL,
52
+ "env_id" text NOT NULL,
53
+ "type_id" text NOT NULL,
54
+ "type_name" text NOT NULL,
55
+ "object_id" text NOT NULL,
56
+ "version" integer DEFAULT 1 NOT NULL,
57
+ "version_type" text,
58
+ "snapshot" jsonb NOT NULL,
59
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
60
+ "created_by" text
61
+ );
62
+ --> statement-breakpoint
63
+ CREATE TABLE IF NOT EXISTS "members" (
64
+ "id" text PRIMARY KEY NOT NULL,
65
+ "workspace_id" text NOT NULL,
66
+ "email" text NOT NULL,
67
+ "name" text,
68
+ "password_hash" text NOT NULL,
69
+ "role" text DEFAULT 'editor' NOT NULL,
70
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
71
+ "last_login_at" timestamp with time zone,
72
+ CONSTRAINT "members_workspace_id_email_unique" UNIQUE("workspace_id","email")
73
+ );
74
+ --> statement-breakpoint
75
+ CREATE TABLE IF NOT EXISTS "refresh_tokens" (
76
+ "id" text PRIMARY KEY NOT NULL,
77
+ "member_id" text NOT NULL,
78
+ "token_hash" text NOT NULL,
79
+ "expires_at" timestamp with time zone NOT NULL,
80
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
81
+ "revoked_at" timestamp with time zone
82
+ );
83
+ --> statement-breakpoint
84
+ CREATE TABLE IF NOT EXISTS "api_keys" (
85
+ "id" text PRIMARY KEY NOT NULL,
86
+ "workspace_id" text NOT NULL,
87
+ "env_id" text NOT NULL,
88
+ "label" text NOT NULL,
89
+ "key_prefix" text NOT NULL,
90
+ "key_hash" text NOT NULL,
91
+ "permissions" jsonb DEFAULT '{}' NOT NULL,
92
+ "expires_at" timestamp with time zone,
93
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
94
+ "created_by" text,
95
+ "last_used_at" timestamp with time zone,
96
+ "revoked_at" timestamp with time zone
97
+ );
98
+ --> statement-breakpoint
99
+ CREATE TABLE IF NOT EXISTS "media_assets" (
100
+ "id" text PRIMARY KEY NOT NULL,
101
+ "workspace_id" text NOT NULL,
102
+ "env_id" text NOT NULL,
103
+ "folder_id" text,
104
+ "filename" text NOT NULL,
105
+ "original_name" text NOT NULL,
106
+ "mime_type" text NOT NULL,
107
+ "size_bytes" bigint NOT NULL,
108
+ "width" integer,
109
+ "height" integer,
110
+ "alt_text" text,
111
+ "tags" text[] DEFAULT '{}' NOT NULL,
112
+ "metadata" jsonb DEFAULT '{}' NOT NULL,
113
+ "storage_path" text NOT NULL,
114
+ "thumbnail_path" text,
115
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
116
+ "created_by" text
117
+ );
118
+ --> statement-breakpoint
119
+ CREATE TABLE IF NOT EXISTS "media_folders" (
120
+ "id" text PRIMARY KEY NOT NULL,
121
+ "workspace_id" text NOT NULL,
122
+ "env_id" text NOT NULL,
123
+ "name" text NOT NULL,
124
+ "parent_id" text,
125
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
126
+ );
127
+ --> statement-breakpoint
128
+ CREATE TABLE IF NOT EXISTS "webhooks" (
129
+ "id" text PRIMARY KEY NOT NULL,
130
+ "workspace_id" text NOT NULL,
131
+ "env_id" text NOT NULL,
132
+ "label" text NOT NULL,
133
+ "url" text NOT NULL,
134
+ "secret" text,
135
+ "events" text[] DEFAULT '{}' NOT NULL,
136
+ "type_names" text[] DEFAULT '{}' NOT NULL,
137
+ "enabled" boolean DEFAULT true NOT NULL,
138
+ "headers" jsonb DEFAULT '{}' NOT NULL,
139
+ "consecutive_failures" integer DEFAULT 0 NOT NULL,
140
+ "paused_reason" text,
141
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
142
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
143
+ );
144
+ --> statement-breakpoint
145
+ CREATE TABLE IF NOT EXISTS "webhook_deliveries" (
146
+ "id" text PRIMARY KEY NOT NULL,
147
+ "webhook_id" text NOT NULL,
148
+ "workspace_id" text NOT NULL,
149
+ "env_id" text NOT NULL,
150
+ "event" text NOT NULL,
151
+ "type_name" text NOT NULL,
152
+ "object_id" text NOT NULL,
153
+ "payload" jsonb NOT NULL,
154
+ "status_code" integer,
155
+ "response_body" text,
156
+ "duration_ms" integer,
157
+ "success" boolean NOT NULL,
158
+ "error" text,
159
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
160
+ );
161
+ --> statement-breakpoint
162
+ CREATE TABLE IF NOT EXISTS "audit_log" (
163
+ "id" text PRIMARY KEY NOT NULL,
164
+ "workspace_id" text NOT NULL,
165
+ "env_id" text,
166
+ "event_type" text NOT NULL,
167
+ "event_id" text NOT NULL,
168
+ "actor_id" text,
169
+ "actor_type" text,
170
+ "payload" jsonb NOT NULL,
171
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
172
+ );
173
+ --> statement-breakpoint
174
+ CREATE TABLE IF NOT EXISTS "saved_views" (
175
+ "id" text PRIMARY KEY NOT NULL,
176
+ "workspace_id" text NOT NULL,
177
+ "env_id" text NOT NULL,
178
+ "type_name" text NOT NULL,
179
+ "name" text NOT NULL,
180
+ "filters" jsonb DEFAULT '{}' NOT NULL,
181
+ "visibility" text DEFAULT 'private' NOT NULL,
182
+ "created_by" text NOT NULL,
183
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
184
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
185
+ );
186
+ --> statement-breakpoint
187
+ CREATE TABLE IF NOT EXISTS "member_env_roles" (
188
+ "id" text PRIMARY KEY NOT NULL,
189
+ "member_id" text NOT NULL,
190
+ "env_id" text NOT NULL,
191
+ "workspace_id" text NOT NULL,
192
+ "role" text NOT NULL,
193
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
194
+ CONSTRAINT "member_env_roles_member_id_env_id_unique" UNIQUE("member_id","env_id")
195
+ );
196
+ --> statement-breakpoint
197
+ CREATE TABLE IF NOT EXISTS "workspace_domains" (
198
+ "id" text PRIMARY KEY NOT NULL,
199
+ "workspace_id" text NOT NULL,
200
+ "domain" text NOT NULL,
201
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
202
+ CONSTRAINT "workspace_domains_domain_unique" UNIQUE("domain")
203
+ );
204
+ --> statement-breakpoint
205
+ CREATE TABLE IF NOT EXISTS "release_items" (
206
+ "id" text PRIMARY KEY NOT NULL,
207
+ "release_id" text NOT NULL,
208
+ "object_id" text NOT NULL,
209
+ "type_name" text NOT NULL,
210
+ "field_snapshot" jsonb NOT NULL
211
+ );
212
+ --> statement-breakpoint
213
+ CREATE TABLE IF NOT EXISTS "releases" (
214
+ "id" text PRIMARY KEY NOT NULL,
215
+ "name" text NOT NULL,
216
+ "workspace_id" text NOT NULL,
217
+ "source_env_id" text NOT NULL,
218
+ "target_env_id" text NOT NULL,
219
+ "status" text DEFAULT 'pending' NOT NULL,
220
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
221
+ "applied_at" timestamp with time zone
222
+ );
223
+ --> statement-breakpoint
224
+ CREATE TABLE IF NOT EXISTS "form_submissions" (
225
+ "id" text PRIMARY KEY NOT NULL,
226
+ "form_id" text NOT NULL,
227
+ "workspace_id" text NOT NULL,
228
+ "env_id" text NOT NULL,
229
+ "data" jsonb NOT NULL,
230
+ "ip_address" text,
231
+ "metadata" jsonb,
232
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
233
+ );
234
+ --> statement-breakpoint
235
+ CREATE TABLE IF NOT EXISTS "form_app_settings" (
236
+ "workspace_id" text NOT NULL,
237
+ "env_id" text NOT NULL,
238
+ "settings" jsonb DEFAULT '{}'::jsonb NOT NULL,
239
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
240
+ CONSTRAINT "form_app_settings_workspace_id_env_id_pk" PRIMARY KEY("workspace_id","env_id")
241
+ );
242
+ --> statement-breakpoint
243
+ CREATE TABLE IF NOT EXISTS "forms" (
244
+ "id" text PRIMARY KEY NOT NULL,
245
+ "workspace_id" text NOT NULL,
246
+ "env_id" text NOT NULL,
247
+ "name" text NOT NULL,
248
+ "slug" text NOT NULL,
249
+ "is_live" boolean DEFAULT false NOT NULL,
250
+ "fields" jsonb DEFAULT '[]'::jsonb NOT NULL,
251
+ "settings" jsonb DEFAULT '{}'::jsonb NOT NULL,
252
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
253
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
254
+ );
255
+ --> statement-breakpoint
256
+ CREATE TABLE IF NOT EXISTS "workspace_installed_apps" (
257
+ "id" text PRIMARY KEY NOT NULL,
258
+ "workspace_id" text NOT NULL,
259
+ "app_id" text NOT NULL,
260
+ "options" jsonb DEFAULT '{}' NOT NULL,
261
+ "installed_at" timestamp with time zone DEFAULT now() NOT NULL,
262
+ "installed_by" text
263
+ );
264
+ --> statement-breakpoint
265
+ DO $$ BEGIN
266
+ ALTER TABLE "environments" ADD CONSTRAINT "environments_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE no action ON UPDATE no action;
267
+ EXCEPTION
268
+ WHEN duplicate_object THEN null;
269
+ END $$;
270
+ --> statement-breakpoint
271
+ DO $$ BEGIN
272
+ ALTER TABLE "fields" ADD CONSTRAINT "fields_type_id_object_types_id_fk" FOREIGN KEY ("type_id") REFERENCES "public"."object_types"("id") ON DELETE no action ON UPDATE no action;
273
+ EXCEPTION
274
+ WHEN duplicate_object THEN null;
275
+ END $$;
276
+ --> statement-breakpoint
277
+ DO $$ BEGIN
278
+ ALTER TABLE "refresh_tokens" ADD CONSTRAINT "refresh_tokens_member_id_members_id_fk" FOREIGN KEY ("member_id") REFERENCES "public"."members"("id") ON DELETE no action ON UPDATE no action;
279
+ EXCEPTION
280
+ WHEN duplicate_object THEN null;
281
+ END $$;
282
+ --> statement-breakpoint
283
+ DO $$ BEGIN
284
+ ALTER TABLE "media_assets" ADD CONSTRAINT "media_assets_folder_id_media_folders_id_fk" FOREIGN KEY ("folder_id") REFERENCES "public"."media_folders"("id") ON DELETE no action ON UPDATE no action;
285
+ EXCEPTION
286
+ WHEN duplicate_object THEN null;
287
+ END $$;
288
+ --> statement-breakpoint
289
+ DO $$ BEGIN
290
+ ALTER TABLE "member_env_roles" ADD CONSTRAINT "member_env_roles_member_id_members_id_fk" FOREIGN KEY ("member_id") REFERENCES "public"."members"("id") ON DELETE cascade ON UPDATE no action;
291
+ EXCEPTION
292
+ WHEN duplicate_object THEN null;
293
+ END $$;
294
+ --> statement-breakpoint
295
+ DO $$ BEGIN
296
+ ALTER TABLE "member_env_roles" ADD CONSTRAINT "member_env_roles_env_id_environments_id_fk" FOREIGN KEY ("env_id") REFERENCES "public"."environments"("id") ON DELETE cascade ON UPDATE no action;
297
+ EXCEPTION
298
+ WHEN duplicate_object THEN null;
299
+ END $$;
300
+ --> statement-breakpoint
301
+ DO $$ BEGIN
302
+ ALTER TABLE "workspace_domains" ADD CONSTRAINT "workspace_domains_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;
303
+ EXCEPTION
304
+ WHEN duplicate_object THEN null;
305
+ END $$;
306
+ --> statement-breakpoint
307
+ DO $$ BEGIN
308
+ ALTER TABLE "release_items" ADD CONSTRAINT "release_items_release_id_releases_id_fk" FOREIGN KEY ("release_id") REFERENCES "public"."releases"("id") ON DELETE cascade ON UPDATE no action;
309
+ EXCEPTION
310
+ WHEN duplicate_object THEN null;
311
+ END $$;
312
+ --> statement-breakpoint
313
+ DO $$ BEGIN
314
+ ALTER TABLE "releases" ADD CONSTRAINT "releases_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;
315
+ EXCEPTION
316
+ WHEN duplicate_object THEN null;
317
+ END $$;
318
+ --> statement-breakpoint
319
+ DO $$ BEGIN
320
+ ALTER TABLE "form_submissions" ADD CONSTRAINT "form_submissions_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;
321
+ EXCEPTION
322
+ WHEN duplicate_object THEN null;
323
+ END $$;
324
+ --> statement-breakpoint
325
+ DO $$ BEGIN
326
+ ALTER TABLE "form_app_settings" ADD CONSTRAINT "form_app_settings_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;
327
+ EXCEPTION
328
+ WHEN duplicate_object THEN null;
329
+ END $$;
330
+ --> statement-breakpoint
331
+ DO $$ BEGIN
332
+ ALTER TABLE "forms" ADD CONSTRAINT "forms_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;
333
+ EXCEPTION
334
+ WHEN duplicate_object THEN null;
335
+ END $$;
336
+ --> statement-breakpoint
337
+ CREATE INDEX IF NOT EXISTS "revisions_object" ON "_revisions" ("workspace_id","env_id","object_id","created_at");--> statement-breakpoint
338
+ CREATE INDEX IF NOT EXISTS "audit_log_workspace" ON "audit_log" ("workspace_id","created_at");--> statement-breakpoint
339
+ CREATE UNIQUE INDEX IF NOT EXISTS "workspace_installed_apps_ws_app_unique" ON "workspace_installed_apps" ("workspace_id","app_id");
@@ -0,0 +1,11 @@
1
+ CREATE TABLE IF NOT EXISTS "app_data" (
2
+ "id" text PRIMARY KEY NOT NULL,
3
+ "app_id" text NOT NULL,
4
+ "object_id" text NOT NULL,
5
+ "workspace_id" text NOT NULL,
6
+ "env_id" text NOT NULL,
7
+ "data" jsonb DEFAULT '{}' NOT NULL,
8
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
9
+ );
10
+ --> statement-breakpoint
11
+ CREATE UNIQUE INDEX IF NOT EXISTS "app_data_app_object_unique" ON "app_data" ("app_id","object_id","workspace_id","env_id");
@@ -0,0 +1,36 @@
1
+ CREATE TABLE IF NOT EXISTS "app_data" (
2
+ "id" text PRIMARY KEY NOT NULL,
3
+ "app_id" text NOT NULL,
4
+ "object_id" text NOT NULL,
5
+ "workspace_id" text NOT NULL,
6
+ "env_id" text NOT NULL,
7
+ "data" jsonb DEFAULT '{}' NOT NULL,
8
+ "published_data" jsonb,
9
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
10
+ );
11
+ --> statement-breakpoint
12
+ CREATE TABLE IF NOT EXISTS "globals" (
13
+ "id" text PRIMARY KEY NOT NULL,
14
+ "name" text NOT NULL,
15
+ "workspace_id" text NOT NULL,
16
+ "env_id" text NOT NULL,
17
+ "data" jsonb DEFAULT '{}' NOT NULL,
18
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
19
+ );
20
+ --> statement-breakpoint
21
+ CREATE UNIQUE INDEX IF NOT EXISTS "app_data_app_object_unique" ON "app_data" ("app_id","object_id","workspace_id","env_id");--> statement-breakpoint
22
+ CREATE UNIQUE INDEX IF NOT EXISTS "globals_workspace_env_name_unique" ON "globals" ("workspace_id","env_id","name");--> statement-breakpoint
23
+ CREATE INDEX IF NOT EXISTS "refresh_tokens_member_idx" ON "refresh_tokens" ("member_id");--> statement-breakpoint
24
+ CREATE INDEX IF NOT EXISTS "api_keys_key_hash_idx" ON "api_keys" ("key_hash");--> statement-breakpoint
25
+ CREATE INDEX IF NOT EXISTS "media_assets_workspace_env_idx" ON "media_assets" ("workspace_id","env_id");--> statement-breakpoint
26
+ CREATE INDEX IF NOT EXISTS "media_assets_folder_idx" ON "media_assets" ("folder_id");--> statement-breakpoint
27
+ CREATE INDEX IF NOT EXISTS "webhooks_workspace_env_idx" ON "webhooks" ("workspace_id","env_id");--> statement-breakpoint
28
+ CREATE INDEX IF NOT EXISTS "webhook_deliveries_workspace_env_idx" ON "webhook_deliveries" ("workspace_id","env_id");--> statement-breakpoint
29
+ CREATE INDEX IF NOT EXISTS "webhook_deliveries_webhook_idx" ON "webhook_deliveries" ("webhook_id");--> statement-breakpoint
30
+ CREATE INDEX IF NOT EXISTS "saved_views_workspace_env_idx" ON "saved_views" ("workspace_id","env_id");--> statement-breakpoint
31
+ CREATE INDEX IF NOT EXISTS "release_items_release_idx" ON "release_items" ("release_id");--> statement-breakpoint
32
+ CREATE INDEX IF NOT EXISTS "release_items_object_idx" ON "release_items" ("object_id");--> statement-breakpoint
33
+ CREATE INDEX IF NOT EXISTS "releases_workspace_idx" ON "releases" ("workspace_id");--> statement-breakpoint
34
+ CREATE INDEX IF NOT EXISTS "form_submissions_workspace_env_idx" ON "form_submissions" ("workspace_id","env_id");--> statement-breakpoint
35
+ CREATE INDEX IF NOT EXISTS "form_submissions_form_idx" ON "form_submissions" ("form_id");--> statement-breakpoint
36
+ CREATE INDEX IF NOT EXISTS "forms_workspace_env_idx" ON "forms" ("workspace_id","env_id");
@@ -0,0 +1,3 @@
1
+ ALTER TABLE "app_data" RENAME TO "extension_data";--> statement-breakpoint
2
+ ALTER TABLE "extension_data" RENAME COLUMN "app_id" TO "extension_id";--> statement-breakpoint
3
+ ALTER INDEX IF EXISTS "app_data_app_object_unique" RENAME TO "extension_data_ext_object_unique";
@@ -0,0 +1,12 @@
1
+ -- Adds published_data column to extension_data (idempotent: previously in 0002_app_data_published.sql but not journalled)
2
+ ALTER TABLE "extension_data" ADD COLUMN IF NOT EXISTS "published_data" jsonb;--> statement-breakpoint
3
+ -- Creates globals table (idempotent: previously in 0003_globals.sql but not journalled)
4
+ CREATE TABLE IF NOT EXISTS "globals" (
5
+ "id" text PRIMARY KEY NOT NULL,
6
+ "name" text NOT NULL,
7
+ "workspace_id" text NOT NULL,
8
+ "env_id" text NOT NULL,
9
+ "data" jsonb DEFAULT '{}' NOT NULL,
10
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
11
+ );--> statement-breakpoint
12
+ CREATE UNIQUE INDEX IF NOT EXISTS "globals_workspace_env_name_unique" ON "globals" ("workspace_id","env_id","name");
@@ -0,0 +1,51 @@
1
+ CREATE TABLE IF NOT EXISTS "auth_users" (
2
+ "id" text PRIMARY KEY NOT NULL,
3
+ "workspace_id" text NOT NULL,
4
+ "env_id" text NOT NULL,
5
+ "email" text,
6
+ "name" text,
7
+ "password_hash" text,
8
+ "role" text DEFAULT 'user' NOT NULL,
9
+ "status" text DEFAULT 'active' NOT NULL,
10
+ "is_anonymous" boolean DEFAULT false NOT NULL,
11
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
12
+ "last_login_at" timestamp with time zone
13
+ );
14
+ --> statement-breakpoint
15
+ CREATE TABLE IF NOT EXISTS "auth_sessions" (
16
+ "id" text PRIMARY KEY NOT NULL,
17
+ "user_id" text NOT NULL,
18
+ "workspace_id" text NOT NULL,
19
+ "env_id" text NOT NULL,
20
+ "refresh_token_hash" text NOT NULL,
21
+ "expires_at" timestamp with time zone NOT NULL,
22
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
23
+ "revoked_at" timestamp with time zone,
24
+ CONSTRAINT "auth_sessions_user_id_auth_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "auth_users"("id") ON DELETE CASCADE ON UPDATE NO ACTION
25
+ );
26
+ --> statement-breakpoint
27
+ CREATE TABLE IF NOT EXISTS "auth_sso_accounts" (
28
+ "id" text PRIMARY KEY NOT NULL,
29
+ "user_id" text NOT NULL,
30
+ "workspace_id" text NOT NULL,
31
+ "env_id" text NOT NULL,
32
+ "provider" text NOT NULL,
33
+ "provider_user_id" text NOT NULL,
34
+ "access_token" text,
35
+ "refresh_token" text,
36
+ "expires_at" timestamp with time zone,
37
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
38
+ CONSTRAINT "auth_sso_accounts_user_id_auth_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "auth_users"("id") ON DELETE CASCADE ON UPDATE NO ACTION
39
+ );
40
+ --> statement-breakpoint
41
+ CREATE INDEX IF NOT EXISTS "auth_users_workspace_env_idx" ON "auth_users" ("workspace_id","env_id");
42
+ --> statement-breakpoint
43
+ CREATE INDEX IF NOT EXISTS "auth_users_anon_idx" ON "auth_users" ("workspace_id","env_id","is_anonymous");
44
+ --> statement-breakpoint
45
+ CREATE UNIQUE INDEX IF NOT EXISTS "auth_users_workspace_env_email_idx" ON "auth_users" ("workspace_id","env_id","email") WHERE email IS NOT NULL;
46
+ --> statement-breakpoint
47
+ CREATE INDEX IF NOT EXISTS "auth_sessions_user_idx" ON "auth_sessions" ("user_id");
48
+ --> statement-breakpoint
49
+ CREATE INDEX IF NOT EXISTS "auth_sessions_workspace_env_idx" ON "auth_sessions" ("workspace_id","env_id");
50
+ --> statement-breakpoint
51
+ CREATE UNIQUE INDEX IF NOT EXISTS "auth_sso_accounts_provider_user_idx" ON "auth_sso_accounts" ("workspace_id","env_id","provider","provider_user_id");
@@ -0,0 +1,13 @@
1
+ CREATE TABLE IF NOT EXISTS "auth_identities" (
2
+ "id" text PRIMARY KEY NOT NULL,
3
+ "user_id" text NOT NULL REFERENCES "auth_users"("id") ON DELETE CASCADE,
4
+ "workspace_id" text NOT NULL,
5
+ "env_id" text NOT NULL,
6
+ "provider" text NOT NULL,
7
+ "provider_user_id" text NOT NULL,
8
+ "created_at" timestamptz NOT NULL DEFAULT now()
9
+ );
10
+
11
+ CREATE UNIQUE INDEX IF NOT EXISTS "auth_identities_user_provider_idx" ON "auth_identities"("user_id","provider");
12
+ CREATE UNIQUE INDEX IF NOT EXISTS "auth_identities_provider_uid_idx" ON "auth_identities"("workspace_id","env_id","provider","provider_user_id");
13
+ CREATE INDEX IF NOT EXISTS "auth_identities_user_idx" ON "auth_identities"("user_id");
@@ -0,0 +1,18 @@
1
+ CREATE TABLE IF NOT EXISTS "ai_settings" (
2
+ "workspace_id" text PRIMARY KEY NOT NULL,
3
+ "data" jsonb DEFAULT '{}' NOT NULL,
4
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
5
+ );
6
+ --> statement-breakpoint
7
+ CREATE TABLE IF NOT EXISTS "ai_chats" (
8
+ "id" text PRIMARY KEY NOT NULL,
9
+ "workspace_id" text NOT NULL,
10
+ "env_id" text NOT NULL,
11
+ "object_type" text NOT NULL,
12
+ "object_id" text NOT NULL,
13
+ "member_id" text NOT NULL,
14
+ "messages" jsonb DEFAULT '[]' NOT NULL,
15
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
16
+ );
17
+ --> statement-breakpoint
18
+ CREATE UNIQUE INDEX IF NOT EXISTS "ai_chats_scope_unique" ON "ai_chats" ("workspace_id","env_id","object_type","object_id","member_id");
@@ -0,0 +1,5 @@
1
+ ALTER TABLE "ai_chats" ADD COLUMN IF NOT EXISTS "title" text;
2
+ --> statement-breakpoint
3
+ DROP INDEX IF EXISTS "ai_chats_scope_unique";
4
+ --> statement-breakpoint
5
+ CREATE INDEX IF NOT EXISTS "ai_chats_scope_idx" ON "ai_chats" ("workspace_id","env_id","object_type","object_id","member_id");
@@ -0,0 +1,11 @@
1
+ -- Forms v2: code-first forms need an origin marker and a slug identity.
2
+ -- Pre-dedupe: suffix any duplicate slugs before the unique index lands.
3
+ WITH d AS (
4
+ SELECT id, ROW_NUMBER() OVER (PARTITION BY workspace_id, env_id, slug ORDER BY created_at) AS rn
5
+ FROM forms
6
+ )
7
+ UPDATE forms f SET slug = f.slug || '-' || d.rn
8
+ FROM d WHERE f.id = d.id AND d.rn > 1;
9
+
10
+ ALTER TABLE forms ADD COLUMN IF NOT EXISTS source text NOT NULL DEFAULT 'admin';
11
+ CREATE UNIQUE INDEX IF NOT EXISTS forms_workspace_env_slug_unique ON forms (workspace_id, env_id, slug);
@@ -0,0 +1,14 @@
1
+ -- RBAC: workspace-defined custom roles + member suspension status.
2
+ CREATE TABLE IF NOT EXISTS roles (
3
+ id text PRIMARY KEY,
4
+ workspace_id text NOT NULL,
5
+ slug text NOT NULL,
6
+ name text NOT NULL,
7
+ description text,
8
+ permissions jsonb NOT NULL DEFAULT '{}'::jsonb,
9
+ created_at timestamptz NOT NULL DEFAULT now(),
10
+ updated_at timestamptz NOT NULL DEFAULT now(),
11
+ CONSTRAINT roles_workspace_slug_unique UNIQUE (workspace_id, slug)
12
+ );
13
+
14
+ ALTER TABLE members ADD COLUMN IF NOT EXISTS status text NOT NULL DEFAULT 'active';