@tangen/db 0.0.0-stage → 0.1.55

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
@@ -0,0 +1,36 @@
1
+ /**
2
+ * File Name: ai-chats.ts
3
+ * Purpose: Drizzle schema for the `ai_chats` table — Gen AI conversation
4
+ * threads. A member can keep several threads per content object
5
+ * (workspace + env + type + id), listed by recency in the panel.
6
+ *
7
+ * Author: Raghunath J
8
+ * License: MIT
9
+ *
10
+ * Dependencies/Notes: messages is a capped jsonb array; threads per scope are
11
+ * capped and member-private (see routes/ai/chats.ts).
12
+ */
13
+ import { pgTable, text, timestamp, jsonb, index } from 'drizzle-orm/pg-core'
14
+ import { sql } from 'drizzle-orm'
15
+
16
+ /**
17
+ * Gen AI conversation threads — several rows per content object per member.
18
+ */
19
+ export const aiChats = pgTable(
20
+ 'ai_chats',
21
+ {
22
+ id: text('id').primaryKey(),
23
+ workspaceId: text('workspace_id').notNull(),
24
+ objectType: text('object_type').notNull(),
25
+ objectId: text('object_id').notNull(),
26
+ /** Conversations are private — every query is member-scoped. */
27
+ memberId: text('member_id').notNull(),
28
+ /** Thread label shown in the picker; derived from the first user message. */
29
+ title: text('title'),
30
+ messages: jsonb('messages').notNull().default(sql`'[]'`),
31
+ updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
32
+ },
33
+ (t) => ({
34
+ aiChatsScopeIdx: index('ai_chats_scope_idx').on(t.workspaceId, t.objectType, t.objectId, t.memberId),
35
+ }),
36
+ )
@@ -0,0 +1,24 @@
1
+ /**
2
+ * File Name: ai-settings.ts
3
+ * Purpose: Drizzle schema for the `ai_settings` table — one row per workspace
4
+ * holding the admin-editable Content AI configuration (chosen provider
5
+ * and model, title-generation toggle, and content guidelines).
6
+ *
7
+ * Author: Raghunath J
8
+ * License: MIT
9
+ *
10
+ * Dependencies/Notes: Workspace-scoped by design (guidelines are brand-level and
11
+ * shared across environments). Provider API keys never live
12
+ * here — they are environment/process configuration.
13
+ */
14
+ import { pgTable, text, timestamp, jsonb } from 'drizzle-orm/pg-core'
15
+ import { sql } from 'drizzle-orm'
16
+
17
+ /**
18
+ * Admin-editable Content AI settings, one row per workspace.
19
+ */
20
+ export const aiSettings = pgTable('ai_settings', {
21
+ workspaceId: text('workspace_id').primaryKey(),
22
+ data: jsonb('data').notNull().default(sql`'{}'`),
23
+ updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
24
+ })
@@ -0,0 +1,37 @@
1
+ /**
2
+ * File Name: api-keys.ts
3
+ * Purpose: Drizzle schema for the `api_keys` table, which stores hashed API credentials
4
+ * used by external clients to authenticate against the Tangen REST/GraphQL APIs.
5
+ *
6
+ * Author: Raghunath J
7
+ * License: MIT
8
+ *
9
+ * Dependencies/Notes: Keys are stored as SHA-256 hashes (keyHash); only the prefix is
10
+ * stored in plaintext for display. An index on keyHash enables fast
11
+ * lookup during request authentication.
12
+ */
13
+ import { pgTable, text, timestamp, jsonb, index } from 'drizzle-orm/pg-core'
14
+ import { sql } from 'drizzle-orm'
15
+
16
+ /**
17
+ * Stores hashed API keys scoped to a workspace and environment, with permission
18
+ * grants, optional expiry, and revocation tracking.
19
+ */
20
+ export const apiKeys = pgTable('api_keys', {
21
+ id: text('id').primaryKey(),
22
+ workspaceId: text('workspace_id').notNull(),
23
+ label: text('label').notNull(),
24
+ keyPrefix: text('key_prefix').notNull(),
25
+ keyHash: text('key_hash').notNull(),
26
+ permissions: jsonb('permissions').notNull().default(sql`'{}'`),
27
+ expiresAt: timestamp('expires_at', { withTimezone: true }),
28
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
29
+ createdBy: text('created_by'),
30
+ /** The identity this key is a credential of (K8); null for a key that acts as itself. */
31
+ identityId: text('identity_id'),
32
+ lastUsedAt: timestamp('last_used_at', { withTimezone: true }),
33
+ revokedAt: timestamp('revoked_at', { withTimezone: true }),
34
+ }, (t) => ({
35
+ keyHashIdx: index('api_keys_key_hash_idx').on(t.keyHash),
36
+ identityIdx: index('api_keys_identity_idx').on(t.identityId),
37
+ }))
@@ -0,0 +1,54 @@
1
+ /**
2
+ * File Name: audit-log.ts
3
+ * Purpose: Drizzle schema for the `audit_log` table, which records an immutable
4
+ * chronological trail of significant events across the workspace.
5
+ *
6
+ * Author: Raghunath J
7
+ * License: MIT
8
+ *
9
+ * Dependencies/Notes: A GIN index
10
+ * on payload->>'object_id' should be applied via gin-indexes.sql.
11
+ */
12
+ import { pgTable, text, timestamp, jsonb, index, boolean } from 'drizzle-orm/pg-core'
13
+ import { sql } from 'drizzle-orm'
14
+
15
+ /**
16
+ * Append-only log of workspace events (content changes, schema pushes, member actions, etc.)
17
+ * indexed by workspace and creation time for chronological queries.
18
+ */
19
+ export const auditLog = pgTable('audit_log', {
20
+ id: text('id').primaryKey(),
21
+ workspaceId: text('workspace_id').notNull(),
22
+ eventType: text('event_type').notNull(),
23
+ eventId: text('event_id').notNull(), // cuid2 of the event itself
24
+ actorId: text('actor_id'),
25
+ actorType: text('actor_type'), // 'member' | 'api_key' (the bus envelope's word)
26
+ payload: jsonb('payload').notNull(),
27
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
28
+ // ── The kernel's event record (K5): the row the domain operations write in their transaction ──
29
+ /** 'member' | 'api_key' | 'system' | 'site_user' */
30
+ actorKind: text('actor_kind'),
31
+ initiatorId: text('initiator_id'),
32
+ initiatorKind: text('initiator_kind'),
33
+ /** request | job | flow | scheduler | release | porter | cli | agent */
34
+ executionKind: text('execution_kind'),
35
+ executionId: text('execution_id'),
36
+ correlationId: text('correlation_id'),
37
+ causationId: text('causation_id'),
38
+ /** 'object' | 'media' | 'global' | ... */
39
+ resourceKind: text('resource_kind'),
40
+ resourceType: text('resource_type'),
41
+ resourceId: text('resource_id'),
42
+ locale: text('locale'),
43
+ /** The transport: admin | rest | sdk | graphql | mcp | extension | scheduler | ... */
44
+ source: text('source'),
45
+ /** True when a domain operation wrote this row inside its transaction (false: the bus subscriber's best-effort copy). */
46
+ recordedInTransaction: boolean('recorded_in_transaction').notNull().default(false),
47
+ }, (t) => ({
48
+ idxWorkspace: index('audit_log_workspace').on(t.workspaceId, t.createdAt),
49
+ idxResource: index('audit_log_resource').on(t.workspaceId, t.resourceId, t.createdAt),
50
+ idxCorrelation: index('audit_log_correlation').on(t.correlationId),
51
+ // Expression index on payload->>'object_id' — expressed as raw SQL because Drizzle's
52
+ // IndexBuilderOn.on() expects column refs; a drizzle-kit migration is the canonical
53
+ // way to add this index in production.
54
+ }))
@@ -0,0 +1,35 @@
1
+ /**
2
+ * File Name: connections.ts
3
+ * Purpose: The `connections` table (Extension Kernel K6.1): an operator's
4
+ * credentials for a connector, per workspace, sealed at rest. The
5
+ * API reads and writes it through services/connections.ts only.
6
+ *
7
+ * Author: Raghunath J
8
+ * License: MIT
9
+ */
10
+ import { pgTable, text, jsonb, timestamp, index } from 'drizzle-orm/pg-core'
11
+ import { sql } from 'drizzle-orm'
12
+
13
+ export const connections = pgTable(
14
+ 'connections',
15
+ {
16
+ id: text('id').primaryKey(),
17
+ workspaceId: text('workspace_id').notNull(),
18
+ /** `ext:<extension>/<connector>`. */
19
+ connectorId: text('connector_id').notNull(),
20
+ label: text('label').notNull(),
21
+ /** `oauth2` | `apiKey` | `basic`. */
22
+ kind: text('kind').notNull(),
23
+ /** The sealed envelope (`v1.<iv>.<tag>.<data>`), never plaintext. */
24
+ secret: text('secret').notNull(),
25
+ meta: jsonb('meta').notNull().default(sql`'{}'::jsonb`),
26
+ /** `active` | `revoked` | `unreadable`. */
27
+ status: text('status').notNull().default('active'),
28
+ health: jsonb('health'),
29
+ createdBy: text('created_by'),
30
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
31
+ updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
32
+ lastCheckedAt: timestamp('last_checked_at', { withTimezone: true }),
33
+ },
34
+ (t) => ({ workspaceConnector: index('connections_workspace_connector_idx').on(t.workspaceId, t.connectorId) }),
35
+ )
@@ -0,0 +1,32 @@
1
+ /**
2
+ * File Name: custom-table-owners.ts
3
+ * Purpose: Who declared each custom table, so ownership survives the extension
4
+ * that asked for it.
5
+ *
6
+ * Author: Raghunath J
7
+ * License: MIT
8
+ *
9
+ * Dependencies/Notes: Keyed by table name alone, because a physical table is
10
+ * instance-global: a second workspace enabling the same extension finds the
11
+ * table that is already there rather than creating another. Row data stays
12
+ * workspace-scoped; `workspaceId` here records only
13
+ * which push first claimed the table, for diagnostics.
14
+ *
15
+ * No row means UNCLAIMED, not unowned. Every custom table older than this
16
+ * registry has no row, and an extension declaring such a name is refused
17
+ * rather than handed a table full of somebody else's data.
18
+ */
19
+ import { pgTable, text, timestamp } from 'drizzle-orm/pg-core'
20
+
21
+ export const customTableOwners = pgTable('custom_table_owners', {
22
+ tableName: text('table_name').primaryKey(),
23
+ /** 'site' | 'extension' */
24
+ ownerKind: text('owner_kind').notNull(),
25
+ /** The extension id, when ownerKind is 'extension'. */
26
+ ownerId: text('owner_id'),
27
+ /** The manifest version the table was last claimed at. */
28
+ ownerVersion: text('owner_version'),
29
+ workspaceId: text('workspace_id'),
30
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
31
+ updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
32
+ })
@@ -0,0 +1,33 @@
1
+ /**
2
+ * File Name: delivery-artifacts.ts
3
+ * Purpose: The delivery_artifacts table - where published Studio artifacts
4
+ * (page and stylesheet builds, route pointers) live.
5
+ *
6
+ * Author: Raghunath J
7
+ * License: MIT
8
+ *
9
+ * Dependencies/Notes: Platform-owned on purpose: the public delivery route
10
+ * reads this table whether or not Studio is installed, which is the
11
+ * guarantee that a published page never goes down with the
12
+ * extension. Artifacts used to live in extension_data, the shared
13
+ * extension KV table, and were the wrong shape for it - immutable,
14
+ * never deleted, unbounded. One row per (workspace, env, path).
15
+ * Build rows (tangen/builds/<version>/...) are content-addressed
16
+ * and immutable; pointer rows (tangen/routes/<route>.json) are the
17
+ * one mutable thing and are upserted on publish. `body` is text:
18
+ * every artifact is CSS or JSON, and the content type is recorded
19
+ * beside it so a bucket or a CDN can serve it as-is. Migration
20
+ * 0030 and the ensure-system-schema guard create it.
21
+ */
22
+ import { pgTable, text, timestamp, primaryKey } from 'drizzle-orm/pg-core'
23
+
24
+ export const deliveryArtifacts = pgTable('delivery_artifacts', {
25
+ workspaceId: text('workspace_id').notNull(),
26
+ path: text('path').notNull(),
27
+ body: text('body').notNull(),
28
+ contentType: text('content_type').notNull().default('application/octet-stream'),
29
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
30
+ updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
31
+ }, (t) => [
32
+ primaryKey({ columns: [t.workspaceId, t.path], name: 'delivery_artifacts_pk' }),
33
+ ])
@@ -0,0 +1,33 @@
1
+ /**
2
+ * File Name: entity-tags.ts
3
+ * Purpose: System tags — a single polymorphic table holding free-form tags for ANY
4
+ * entity (content records, form submissions, members, media). One queryable place
5
+ * so admin users, extensions, the API, and MCP can all tag + filter uniformly,
6
+ * without a per-entity column/migration. Env-scoped (instances are env-scoped).
7
+ * `entity_type` is namespaced: 'content:<typeName>' | 'submission' | 'member' |
8
+ * 'media'. `source` records who added it ('user' | 'system' | 'ext:<id>' | ...).
9
+ *
10
+ * Author: Raghunath J
11
+ * License: MIT
12
+ */
13
+ import { pgTable, text, timestamp, primaryKey, index } from 'drizzle-orm/pg-core'
14
+
15
+ export const entityTags = pgTable('entity_tags', {
16
+ workspaceId: text('workspace_id').notNull(),
17
+ /** 'content:<typeName>' | 'submission' | 'member' | 'media' */
18
+ entityType: text('entity_type').notNull(),
19
+ entityId: text('entity_id').notNull(),
20
+ /** Normalized tag (trim + collapsed whitespace). */
21
+ tag: text('tag').notNull(),
22
+ /** Who added it: 'user' | 'system' | 'ext:<id>' | 'mcp' | 'api'. */
23
+ source: text('source').notNull().default('user'),
24
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
25
+ /** Member id / key id / null (audit). */
26
+ createdBy: text('created_by'),
27
+ }, (t) => ({
28
+ pk: primaryKey({ columns: [t.workspaceId, t.entityType, t.entityId, t.tag] }),
29
+ // filter a type by tag + distinct-tag autocomplete
30
+ filterIdx: index('entity_tags_filter_idx').on(t.workspaceId, t.entityType, t.tag),
31
+ // list one entity's tags / batch-list for a list view
32
+ entityIdx: index('entity_tags_entity_idx').on(t.workspaceId, t.entityType, t.entityId),
33
+ }))
@@ -0,0 +1,38 @@
1
+ /**
2
+ * File Name: extension-data.ts
3
+ * Purpose: Drizzle schema for the `extension_data` table, which stores arbitrary
4
+ * per-object data owned by installed extensions, separate from core content.
5
+ *
6
+ * Author: Raghunath J
7
+ * License: MIT
8
+ *
9
+ * Dependencies/Notes: One row per (extensionId, objectId, workspaceId) tuple.
10
+ * publishedData mirrors data at publish time, allowing extensions
11
+ * to distinguish draft vs published state.
12
+ */
13
+ import { pgTable, text, timestamp, jsonb, uniqueIndex } from 'drizzle-orm/pg-core'
14
+ import { sql } from 'drizzle-orm'
15
+
16
+ /**
17
+ * Stores extension-owned key-value data attached to a content object, with separate
18
+ * draft and published data blobs and a unique constraint per extension-object pair.
19
+ */
20
+ export const extensionData = pgTable(
21
+ 'extension_data',
22
+ {
23
+ id: text('id').primaryKey(),
24
+ extensionId: text('extension_id').notNull(),
25
+ objectId: text('object_id').notNull(),
26
+ workspaceId: text('workspace_id').notNull(),
27
+ data: jsonb('data').notNull().default(sql`'{}'`),
28
+ publishedData: jsonb('published_data'),
29
+ updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
30
+ },
31
+ (t) => ({
32
+ uniqueExtensionObject: uniqueIndex('extension_data_ext_object_unique').on(
33
+ t.extensionId,
34
+ t.objectId,
35
+ t.workspaceId,
36
+ ),
37
+ }),
38
+ )
@@ -0,0 +1,42 @@
1
+ /**
2
+ * File Name: extension-migrations.ts
3
+ * Purpose: The extension migration ledger (closure plan Phase 4, Unit 5):
4
+ * one row per migration an extension applied in one workspace x
5
+ * environment, with the checksum of the SQL that ran and the
6
+ * extension version that carried it. INSTALL and UPGRADE read it
7
+ * to compute what is pending; the gate reads it to refuse an
8
+ * extension whose schema is not there; doctor reads it to explain.
9
+ *
10
+ * Author: Raghunath J
11
+ * License: MIT
12
+ *
13
+ * Dependencies/Notes: Migration 0046 creates it. A `failed` row is the
14
+ * diagnosable trace of a migration whose transaction rolled back;
15
+ * the next run retries it. A row whose checksum no longer matches
16
+ * the declared SQL, or whose migration the extension no longer
17
+ * declares, refuses every further migration of that extension in
18
+ * that scope: history is not rewritten.
19
+ */
20
+ import { pgTable, text, timestamp, uniqueIndex } from 'drizzle-orm/pg-core'
21
+
22
+ export const extensionMigrations = pgTable(
23
+ 'extension_migrations',
24
+ {
25
+ id: text('id').primaryKey(),
26
+ workspaceId: text('workspace_id').notNull(),
27
+ extensionId: text('extension_id').notNull(),
28
+ /** The migration's declared id, e.g. `0001_create_reviews`. */
29
+ migrationId: text('migration_id').notNull(),
30
+ /** The extension version that carried the migration when it ran. */
31
+ extensionVersion: text('extension_version'),
32
+ /** sha256 over the migration id and its SQL. */
33
+ checksum: text('checksum').notNull(),
34
+ /** `applied` | `failed` */
35
+ status: text('status').notNull().default('applied'),
36
+ error: text('error'),
37
+ appliedAt: timestamp('applied_at', { withTimezone: true }).notNull().defaultNow(),
38
+ },
39
+ (t) => ({
40
+ scopeUnique: uniqueIndex('extension_migrations_scope_unique').on(t.workspaceId, t.extensionId, t.migrationId),
41
+ }),
42
+ )
@@ -0,0 +1,37 @@
1
+ /**
2
+ * File Name: fields.ts
3
+ * Purpose: Drizzle schema for the `fields` table, which stores the field-level
4
+ * schema definitions for each content type (objectType) within an environment.
5
+ *
6
+ * Author: Raghunath J
7
+ * License: MIT
8
+ *
9
+ * Dependencies/Notes: References object_types. previousNames tracks rename history
10
+ * to support non-destructive field renames in migrations.
11
+ * config is a JSON blob whose shape depends on fieldType.
12
+ */
13
+ import { pgTable, text, timestamp, boolean, integer, jsonb, unique } from 'drizzle-orm/pg-core'
14
+ import { sql } from 'drizzle-orm'
15
+ import { objectTypes } from './object-types'
16
+
17
+ /**
18
+ * Defines individual field schemas belonging to a content object type, including
19
+ * field type, display config, ordering, required flag, and rename history.
20
+ */
21
+ export const fields = pgTable('fields', {
22
+ id: text('id').primaryKey(),
23
+ typeId: text('type_id').notNull().references(() => objectTypes.id),
24
+ name: text('name').notNull(),
25
+ label: text('label').notNull(),
26
+ fieldType: text('field_type').notNull(),
27
+ config: jsonb('config').notNull().default(sql`'{}'`),
28
+ previousNames: text('previous_names').array().notNull().default(sql`'{}'`),
29
+ position: integer('position').notNull().default(0),
30
+ required: boolean('required').notNull().default(false),
31
+ /** Soft-disable: hidden from every runtime surface; column + data preserved. */
32
+ disabled: boolean('disabled').notNull().default(false),
33
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
34
+ updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
35
+ }, (t) => ({
36
+ uniqTypeField: unique().on(t.typeId, t.name),
37
+ }))
@@ -0,0 +1,35 @@
1
+ /**
2
+ * File Name: globals.ts
3
+ * Purpose: Drizzle schema for the `globals` table, which stores workspace-level
4
+ * singleton data entries (Options / Globals) defined in tangen.config.ts.
5
+ *
6
+ * Author: Raghunath J
7
+ * License: MIT
8
+ *
9
+ * Dependencies/Notes: Keyed by (workspaceId, name) with a unique index,
10
+ * making each named global a single JSON document per environment.
11
+ */
12
+ import { pgTable, text, timestamp, jsonb, uniqueIndex } from 'drizzle-orm/pg-core'
13
+ import { sql } from 'drizzle-orm'
14
+
15
+ /**
16
+ * Workspace-level key-value store (Globals / Options).
17
+ * Developers define named global entries in tangen.config.ts; this table
18
+ * stores the actual data. Keyed by (workspace_id, name).
19
+ */
20
+ export const globals = pgTable(
21
+ 'globals',
22
+ {
23
+ id: text('id').primaryKey(),
24
+ name: text('name').notNull(),
25
+ workspaceId: text('workspace_id').notNull(),
26
+ data: jsonb('data').notNull().default(sql`'{}'`),
27
+ updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
28
+ },
29
+ (t) => ({
30
+ uniqueGlobal: uniqueIndex('globals_workspace_name_unique').on(
31
+ t.workspaceId,
32
+ t.name,
33
+ ),
34
+ }),
35
+ )
@@ -0,0 +1,27 @@
1
+ /**
2
+ * File Name: identities.ts
3
+ * Purpose: The `identities` table (framework kernel K8): service and agent
4
+ * identities that hold authority and own API keys as credentials.
5
+ * Migration 0055.
6
+ *
7
+ * Author: Raghunath J
8
+ * License: MIT
9
+ */
10
+ import { pgTable, text, timestamp, jsonb, index } from 'drizzle-orm/pg-core'
11
+ import { sql } from 'drizzle-orm'
12
+
13
+ export const identities = pgTable('identities', {
14
+ id: text('id').primaryKey(),
15
+ workspaceId: text('workspace_id').notNull(),
16
+ kind: text('kind').notNull(), // 'service' | 'agent'
17
+ name: text('name').notNull(),
18
+ description: text('description'),
19
+ status: text('status').notNull().default('active'), // 'active' | 'suspended'
20
+ permissions: jsonb('permissions').notNull().default(sql`'{}'`),
21
+ capabilities: jsonb('capabilities').notNull().default(sql`'[]'`),
22
+ ownerId: text('owner_id'),
23
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
24
+ updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
25
+ }, (t) => ({
26
+ workspaceIdx: index('identities_workspace_idx').on(t.workspaceId),
27
+ }))
@@ -0,0 +1,51 @@
1
+ /**
2
+ * File Name: index.ts
3
+ * Purpose: Barrel file that re-exports all Drizzle table definitions for the system schema,
4
+ * making every table available through a single import path.
5
+ *
6
+ * Author: Raghunath J
7
+ * License: MIT
8
+ *
9
+ * Dependencies/Notes: This file is the schema entry point referenced by drizzle.config.ts.
10
+ * Content tables (generated by tangen push) are exported separately
11
+ * from src/generated/content-tables.ts.
12
+ */
13
+ export * from './workspaces'
14
+ export * from './object-types'
15
+ export * from './fields'
16
+ export * from './objects' // exports `revisions` (_revisions table)
17
+ export * from './members'
18
+ export * from './member-grants'
19
+ export * from './api-keys'
20
+ export * from './identities'
21
+ export * from './media'
22
+ export * from './webhooks'
23
+ export * from './webhook-deliveries'
24
+ export * from './audit-log'
25
+ export * from './saved-views'
26
+ export * from './roles'
27
+ export * from './jobs'
28
+ export * from './workspace-domains'
29
+ export * from './releases'
30
+ export * from './workspace-installed-apps'
31
+ export * from './connections'
32
+ export * from './extension-migrations'
33
+ export * from './extension-data'
34
+ export * from './ai-settings'
35
+ export * from './ai-chats'
36
+ export * from './globals'
37
+ export * from './realtime'
38
+ export * from './delivery-artifacts'
39
+ export * from './workspace-activity'
40
+ export * from './search-query-log'
41
+ export * from './search-synonyms'
42
+ export * from './search-recommendations'
43
+ export * from './playground'
44
+ export * from './paused-jobs'
45
+ export * from './entity-tags'
46
+ export * from './migration-runs'
47
+ export * from './porter-transfer'
48
+ export * from './schema-pushes'
49
+ export * from './custom-table-owners'
50
+ export * from './search-documents'
51
+ export * from './knowledge'
@@ -0,0 +1,104 @@
1
+ /**
2
+ * File Name: jobs.ts
3
+ * Purpose: Drizzle schema for the jobs-v2 tables — the durable Postgres
4
+ * queue (job_queue), execution history (job_runs), redis-less
5
+ * scheduler state, admin-managed cron entries, and the Postgres
6
+ * cache fallback store.
7
+ *
8
+ * Author: Raghunath J
9
+ * License: MIT
10
+ *
11
+ * Dependencies/Notes: Queue polling uses raw SKIP LOCKED SQL (task-queue
12
+ * service); these definitions serve typed reads/writes and admin
13
+ * routes. Migration: drizzle/0011_jobs_v2.sql.
14
+ */
15
+ import { pgTable, text, timestamp, jsonb, integer, boolean, bigserial, index } from 'drizzle-orm/pg-core'
16
+
17
+ export const jobQueue = pgTable('job_queue', {
18
+ id: bigserial('id', { mode: 'number' }).primaryKey(),
19
+ taskId: text('task_id').notNull(),
20
+ workspaceId: text('workspace_id').notNull(),
21
+ payload: jsonb('payload'),
22
+ /**
23
+ * Collapses redundant PENDING work: at most one queued row per key, enforced
24
+ * by a partial unique index (drizzle/0037). NULL means "never deduplicate",
25
+ * which is every task except search indexing. See task-queue.pgEnqueue.
26
+ */
27
+ dedupeKey: text('dedupe_key'),
28
+ /** Who enqueued and whose authority the run uses (kernel K6): { initiator, runAs, correlationId, causationId, locale }. */
29
+ execution: jsonb('execution'),
30
+ status: text('status').notNull().default('queued'),
31
+ attempts: integer('attempts').notNull().default(0),
32
+ maxAttempts: integer('max_attempts').notNull().default(3),
33
+ runAt: timestamp('run_at', { withTimezone: true }).notNull().defaultNow(),
34
+ lockedAt: timestamp('locked_at', { withTimezone: true }),
35
+ lockedBy: text('locked_by'),
36
+ lastError: text('last_error'),
37
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
38
+ finishedAt: timestamp('finished_at', { withTimezone: true }),
39
+ })
40
+
41
+ export const jobRuns = pgTable('job_runs', {
42
+ id: bigserial('id', { mode: 'number' }).primaryKey(),
43
+ kind: text('kind').notNull(),
44
+ name: text('name').notNull(),
45
+ workspaceId: text('workspace_id').notNull(),
46
+ status: text('status').notNull(),
47
+ startedAt: timestamp('started_at', { withTimezone: true }).notNull().defaultNow(),
48
+ finishedAt: timestamp('finished_at', { withTimezone: true }),
49
+ durationMs: integer('duration_ms'),
50
+ error: text('error'),
51
+ meta: jsonb('meta'),
52
+ // ── The run's identity (kernel K6) ──
53
+ actorKind: text('actor_kind'),
54
+ actorId: text('actor_id'),
55
+ initiatorKind: text('initiator_kind'),
56
+ initiatorId: text('initiator_id'),
57
+ executionId: text('execution_id'),
58
+ correlationId: text('correlation_id'),
59
+ attempt: integer('attempt'),
60
+ }, (t) => ({
61
+ /** Retention: the purge scans by workspace and age (migration 0044). */
62
+ retentionIdx: index('job_runs_retention_idx').on(t.workspaceId, t.startedAt),
63
+ correlationIdx: index('job_runs_correlation').on(t.correlationId),
64
+ }))
65
+
66
+ export const schedulerState = pgTable('scheduler_state', {
67
+ workspaceId: text('workspace_id').notNull(),
68
+ jobId: text('job_id').notNull(),
69
+ lastRun: timestamp('last_run', { withTimezone: true }).notNull(),
70
+ })
71
+
72
+ export const cronJobs = pgTable('cron_jobs', {
73
+ id: text('id').primaryKey(),
74
+ workspaceId: text('workspace_id').notNull(),
75
+ name: text('name').notNull(),
76
+ schedule: text('schedule').notNull(),
77
+ /** IANA zone the cron fields evaluate in; NULL = server-local time. */
78
+ timezone: text('timezone'),
79
+ action: jsonb('action').notNull(),
80
+ enabled: boolean('enabled').notNull().default(true),
81
+ createdBy: text('created_by'),
82
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
83
+ updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
84
+ })
85
+
86
+ export const cacheEntries = pgTable('cache_entries', {
87
+ key: text('key').primaryKey(),
88
+ value: text('value').notNull(),
89
+ tags: text('tags').array().notNull().default([]),
90
+ storedAt: timestamp('stored_at', { withTimezone: true }).notNull().defaultNow(),
91
+ expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
92
+ })
93
+
94
+ /**
95
+ * Fixed-window rate-limit counters for deployments without Redis (Gateway
96
+ * M5, plan section 28). One row per counter key; a hit past `reset_at`
97
+ * starts a new window. Pruned by the queue worker like cache_entries.
98
+ */
99
+ export const rateLimitCounters = pgTable('rate_limit_counters', {
100
+ key: text('key').primaryKey(),
101
+ count: integer('count').notNull().default(0),
102
+ resetAt: timestamp('reset_at', { withTimezone: true }).notNull(),
103
+ })
104
+