@mettlecast/domain-cli 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +133 -0
  3. package/dist/builder/build-registry.d.ts +20 -0
  4. package/dist/builder/build-registry.js +178 -0
  5. package/dist/builder/build-types.d.ts +10 -0
  6. package/dist/builder/build-types.js +32 -0
  7. package/dist/builder/load-domain-module-config.d.ts +12 -0
  8. package/dist/builder/load-domain-module-config.js +34 -0
  9. package/dist/builder/load-module.d.ts +26 -0
  10. package/dist/builder/load-module.js +114 -0
  11. package/dist/builder/zod-to-json.d.ts +10 -0
  12. package/dist/builder/zod-to-json.js +15 -0
  13. package/dist/cli.d.ts +2 -0
  14. package/dist/cli.js +338 -0
  15. package/dist/commands/add-api.d.ts +32 -0
  16. package/dist/commands/add-api.js +106 -0
  17. package/dist/commands/add-component.d.ts +15 -0
  18. package/dist/commands/add-component.js +78 -0
  19. package/dist/commands/add-domain.d.ts +21 -0
  20. package/dist/commands/add-domain.js +71 -0
  21. package/dist/commands/add-flow.d.ts +14 -0
  22. package/dist/commands/add-flow.js +108 -0
  23. package/dist/commands/add-hook.d.ts +15 -0
  24. package/dist/commands/add-hook.js +65 -0
  25. package/dist/commands/add-migration.d.ts +26 -0
  26. package/dist/commands/add-migration.js +93 -0
  27. package/dist/commands/add-module.d.ts +7 -0
  28. package/dist/commands/add-module.js +261 -0
  29. package/dist/commands/add-page.d.ts +13 -0
  30. package/dist/commands/add-page.js +170 -0
  31. package/dist/commands/add-seed-page.d.ts +5 -0
  32. package/dist/commands/add-seed-page.js +251 -0
  33. package/dist/commands/add-subscriber.d.ts +30 -0
  34. package/dist/commands/add-subscriber.js +87 -0
  35. package/dist/commands/add-test.d.ts +22 -0
  36. package/dist/commands/add-test.js +285 -0
  37. package/dist/commands/audit.d.ts +39 -0
  38. package/dist/commands/audit.js +171 -0
  39. package/dist/commands/build-catalog.d.ts +68 -0
  40. package/dist/commands/build-catalog.js +105 -0
  41. package/dist/commands/build-flows.d.ts +26 -0
  42. package/dist/commands/build-flows.js +174 -0
  43. package/dist/commands/build.d.ts +17 -0
  44. package/dist/commands/build.js +39 -0
  45. package/dist/commands/check-hashes.d.ts +38 -0
  46. package/dist/commands/check-hashes.js +97 -0
  47. package/dist/commands/create-project.d.ts +5 -0
  48. package/dist/commands/create-project.js +272 -0
  49. package/dist/commands/dev.d.ts +15 -0
  50. package/dist/commands/dev.js +27 -0
  51. package/dist/commands/doctor.d.ts +48 -0
  52. package/dist/commands/doctor.js +1301 -0
  53. package/dist/commands/explain.d.ts +34 -0
  54. package/dist/commands/explain.js +167 -0
  55. package/dist/commands/power-tune.d.ts +1 -0
  56. package/dist/commands/power-tune.js +87 -0
  57. package/dist/commands/reseed-page.d.ts +6 -0
  58. package/dist/commands/reseed-page.js +297 -0
  59. package/dist/commands/show-dns.d.ts +15 -0
  60. package/dist/commands/show-dns.js +101 -0
  61. package/dist/commands/show.d.ts +46 -0
  62. package/dist/commands/show.js +135 -0
  63. package/dist/commands/test.d.ts +18 -0
  64. package/dist/commands/test.js +56 -0
  65. package/dist/commands/upgrade-backend.d.ts +9 -0
  66. package/dist/commands/upgrade-backend.js +46 -0
  67. package/dist/commands/upgrade-event.d.ts +22 -0
  68. package/dist/commands/upgrade-event.js +111 -0
  69. package/dist/commands/upgrade-frontend.d.ts +5 -0
  70. package/dist/commands/upgrade-frontend.js +99 -0
  71. package/dist/commands/upgrade.d.ts +7 -0
  72. package/dist/commands/upgrade.js +495 -0
  73. package/dist/commands/validate.d.ts +30 -0
  74. package/dist/commands/validate.js +148 -0
  75. package/dist/runtime-stubs/hydrate-local-ctx.d.ts +49 -0
  76. package/dist/runtime-stubs/hydrate-local-ctx.js +45 -0
  77. package/dist/runtime-stubs/load-secrets.d.ts +11 -0
  78. package/dist/runtime-stubs/load-secrets.js +37 -0
  79. package/dist/server/api-server.d.ts +21 -0
  80. package/dist/server/api-server.js +14 -0
  81. package/dist/server/mount-routes.d.ts +19 -0
  82. package/dist/server/mount-routes.js +42 -0
  83. package/dist/templates/api-skeleton.d.ts +18 -0
  84. package/dist/templates/api-skeleton.js +69 -0
  85. package/dist/templates/claude-md.d.ts +4 -0
  86. package/dist/templates/claude-md.js +7 -0
  87. package/dist/templates/dashboard-pages/index.d.ts +101 -0
  88. package/dist/templates/dashboard-pages/index.js +22 -0
  89. package/dist/templates/domain-config.d.ts +10 -0
  90. package/dist/templates/domain-config.js +19 -0
  91. package/dist/templates/events-skeleton.d.ts +9 -0
  92. package/dist/templates/events-skeleton.js +23 -0
  93. package/dist/templates/patterns/api/create-with-event.d.ts +5 -0
  94. package/dist/templates/patterns/api/create-with-event.js +69 -0
  95. package/dist/templates/patterns/api/idempotent-mutation.d.ts +5 -0
  96. package/dist/templates/patterns/api/idempotent-mutation.js +99 -0
  97. package/dist/templates/patterns/api/paginated-list.d.ts +5 -0
  98. package/dist/templates/patterns/api/paginated-list.js +71 -0
  99. package/dist/templates/patterns/api/simple-crud.d.ts +6 -0
  100. package/dist/templates/patterns/api/simple-crud.js +95 -0
  101. package/dist/templates/patterns/api/system-admin.d.ts +5 -0
  102. package/dist/templates/patterns/api/system-admin.js +98 -0
  103. package/dist/templates/patterns/api/webhook-receiver-style.d.ts +5 -0
  104. package/dist/templates/patterns/api/webhook-receiver-style.js +125 -0
  105. package/dist/templates/patterns/component/index.d.ts +25 -0
  106. package/dist/templates/patterns/component/index.js +814 -0
  107. package/dist/templates/patterns/flow/autonomous-remediation.d.ts +5 -0
  108. package/dist/templates/patterns/flow/autonomous-remediation.js +98 -0
  109. package/dist/templates/patterns/flow/fan-out-reaction.d.ts +5 -0
  110. package/dist/templates/patterns/flow/fan-out-reaction.js +53 -0
  111. package/dist/templates/patterns/flow/human-in-loop.d.ts +5 -0
  112. package/dist/templates/patterns/flow/human-in-loop.js +90 -0
  113. package/dist/templates/patterns/flow/multi-step-orchestration.d.ts +5 -0
  114. package/dist/templates/patterns/flow/multi-step-orchestration.js +67 -0
  115. package/dist/templates/patterns/hook/index.d.ts +7 -0
  116. package/dist/templates/patterns/hook/index.js +378 -0
  117. package/dist/templates/patterns/migration/column-add.d.ts +5 -0
  118. package/dist/templates/patterns/migration/column-add.js +52 -0
  119. package/dist/templates/patterns/migration/event-store.d.ts +6 -0
  120. package/dist/templates/patterns/migration/event-store.js +97 -0
  121. package/dist/templates/patterns/migration/index.d.ts +5 -0
  122. package/dist/templates/patterns/migration/index.js +52 -0
  123. package/dist/templates/patterns/migration/rls-policy.d.ts +5 -0
  124. package/dist/templates/patterns/migration/rls-policy.js +78 -0
  125. package/dist/templates/patterns/migration/tenant-scoped-table.d.ts +6 -0
  126. package/dist/templates/patterns/migration/tenant-scoped-table.js +81 -0
  127. package/dist/templates/patterns/page/dashboard.d.ts +4 -0
  128. package/dist/templates/patterns/page/dashboard.js +164 -0
  129. package/dist/templates/patterns/page/detail.d.ts +4 -0
  130. package/dist/templates/patterns/page/detail.js +162 -0
  131. package/dist/templates/patterns/page/form.d.ts +4 -0
  132. package/dist/templates/patterns/page/form.js +192 -0
  133. package/dist/templates/patterns/page/index.d.ts +8 -0
  134. package/dist/templates/patterns/page/index.js +14 -0
  135. package/dist/templates/patterns/page/list.d.ts +4 -0
  136. package/dist/templates/patterns/page/list.js +138 -0
  137. package/dist/templates/patterns/page/settings.d.ts +4 -0
  138. package/dist/templates/patterns/page/settings.js +318 -0
  139. package/dist/templates/patterns/page/wizard.d.ts +4 -0
  140. package/dist/templates/patterns/page/wizard.js +293 -0
  141. package/dist/templates/patterns/subscriber/audit-relay.d.ts +5 -0
  142. package/dist/templates/patterns/subscriber/audit-relay.js +51 -0
  143. package/dist/templates/patterns/subscriber/cascade-deletion.d.ts +5 -0
  144. package/dist/templates/patterns/subscriber/cascade-deletion.js +60 -0
  145. package/dist/templates/patterns/subscriber/single-step-projection.d.ts +5 -0
  146. package/dist/templates/patterns/subscriber/single-step-projection.js +36 -0
  147. package/dist/templates/readme.d.ts +4 -0
  148. package/dist/templates/readme.js +7 -0
  149. package/dist/templates/subscriber-skeleton.d.ts +11 -0
  150. package/dist/templates/subscriber-skeleton.js +23 -0
  151. package/dist/utils/checksum.d.ts +18 -0
  152. package/dist/utils/checksum.js +39 -0
  153. package/dist/utils/file-helpers.d.ts +35 -0
  154. package/dist/utils/file-helpers.js +59 -0
  155. package/dist/utils/header-inject.d.ts +15 -0
  156. package/dist/utils/header-inject.js +93 -0
  157. package/dist/utils/install-file.d.ts +17 -0
  158. package/dist/utils/install-file.js +78 -0
  159. package/dist/utils/logger.d.ts +12 -0
  160. package/dist/utils/logger.js +24 -0
  161. package/dist/utils/manifest.d.ts +28 -0
  162. package/dist/utils/manifest.js +93 -0
  163. package/dist/utils/s3-fetch.d.ts +25 -0
  164. package/dist/utils/s3-fetch.js +52 -0
  165. package/dist/utils/scaffold-config.d.ts +95 -0
  166. package/dist/utils/scaffold-config.js +53 -0
  167. package/dist/utils/token-substitution.d.ts +35 -0
  168. package/dist/utils/token-substitution.js +52 -0
  169. package/dist/utils/zod-extractor.d.ts +10 -0
  170. package/dist/utils/zod-extractor.js +28 -0
  171. package/package.json +43 -0
  172. package/src/__tests__/build-registry.test.ts +115 -0
  173. package/src/__tests__/build-types.test.ts +100 -0
  174. package/src/__tests__/builder/walkDomainDir.test.ts +140 -0
  175. package/src/__tests__/commands/add-domain.test.ts +174 -0
  176. package/src/__tests__/commands/add-flow.test.ts +69 -0
  177. package/src/__tests__/commands/add-module.test.ts +207 -0
  178. package/src/__tests__/commands/build-flows.test.ts +154 -0
  179. package/src/__tests__/commands/check-hashes.test.ts +142 -0
  180. package/src/__tests__/commands/create-project.test.ts +712 -0
  181. package/src/__tests__/commands/upgrade.test.ts +762 -0
  182. package/src/__tests__/doctor.test.ts +291 -0
  183. package/src/__tests__/hydrate-local-ctx.test.ts +71 -0
  184. package/src/__tests__/load-domain-module-config.test.ts +10 -0
  185. package/src/__tests__/mount-routes.test.ts +100 -0
  186. package/src/__tests__/scaffold-src/part-a-layout.test.ts +46 -0
  187. package/src/__tests__/scripts/package-scaffold.test.ts +45 -0
  188. package/src/__tests__/utils/checksum.test.ts +90 -0
  189. package/src/__tests__/utils/header-inject.test.ts +89 -0
  190. package/src/__tests__/utils/install-file.test.ts +207 -0
  191. package/src/__tests__/utils/manifest.test.ts +229 -0
  192. package/src/__tests__/utils/token-substitution.test.ts +134 -0
  193. package/src/__tests__/validate.test.ts +18 -0
  194. package/src/builder/build-registry.ts +237 -0
  195. package/src/builder/build-types.ts +36 -0
  196. package/src/builder/load-domain-module-config.ts +43 -0
  197. package/src/builder/load-module.ts +133 -0
  198. package/src/builder/zod-to-json.ts +16 -0
  199. package/src/cli.ts +387 -0
  200. package/src/commands/add-api.ts +145 -0
  201. package/src/commands/add-component.ts +116 -0
  202. package/src/commands/add-domain.ts +100 -0
  203. package/src/commands/add-flow.ts +136 -0
  204. package/src/commands/add-hook.ts +100 -0
  205. package/src/commands/add-migration.ts +129 -0
  206. package/src/commands/add-module.ts +347 -0
  207. package/src/commands/add-page.ts +242 -0
  208. package/src/commands/add-seed-page.ts +338 -0
  209. package/src/commands/add-subscriber.ts +123 -0
  210. package/src/commands/add-test.ts +328 -0
  211. package/src/commands/audit.ts +217 -0
  212. package/src/commands/build-catalog.ts +181 -0
  213. package/src/commands/build-flows.ts +198 -0
  214. package/src/commands/build.ts +59 -0
  215. package/src/commands/check-hashes.ts +150 -0
  216. package/src/commands/create-project.ts +367 -0
  217. package/src/commands/dev.ts +47 -0
  218. package/src/commands/doctor.ts +1362 -0
  219. package/src/commands/explain.ts +209 -0
  220. package/src/commands/power-tune.ts +112 -0
  221. package/src/commands/reseed-page.ts +410 -0
  222. package/src/commands/show-dns.ts +139 -0
  223. package/src/commands/show.ts +179 -0
  224. package/src/commands/test.ts +91 -0
  225. package/src/commands/upgrade-backend.ts +72 -0
  226. package/src/commands/upgrade-event.ts +175 -0
  227. package/src/commands/upgrade-frontend.ts +135 -0
  228. package/src/commands/upgrade.ts +668 -0
  229. package/src/commands/validate.ts +208 -0
  230. package/src/runtime-stubs/hydrate-local-ctx.ts +102 -0
  231. package/src/runtime-stubs/load-secrets.ts +40 -0
  232. package/src/server/api-server.ts +34 -0
  233. package/src/server/mount-routes.ts +68 -0
  234. package/src/templates/api-skeleton.ts +72 -0
  235. package/src/templates/claude-md.ts +7 -0
  236. package/src/templates/dashboard-pages/account/api-keys.tsx +107 -0
  237. package/src/templates/dashboard-pages/account/audit-log.tsx +60 -0
  238. package/src/templates/dashboard-pages/account/index.ts +5 -0
  239. package/src/templates/dashboard-pages/account/members.tsx +107 -0
  240. package/src/templates/dashboard-pages/account/profile.tsx +53 -0
  241. package/src/templates/dashboard-pages/account/workspace-settings.tsx +64 -0
  242. package/src/templates/dashboard-pages/auth/accept-invitation.tsx +62 -0
  243. package/src/templates/dashboard-pages/auth/choose-org.tsx +66 -0
  244. package/src/templates/dashboard-pages/auth/forgot-password.tsx +69 -0
  245. package/src/templates/dashboard-pages/auth/index.ts +10 -0
  246. package/src/templates/dashboard-pages/auth/login.tsx +75 -0
  247. package/src/templates/dashboard-pages/auth/mfa-setup.tsx +56 -0
  248. package/src/templates/dashboard-pages/auth/mfa-verify.tsx +49 -0
  249. package/src/templates/dashboard-pages/auth/reset-password.tsx +64 -0
  250. package/src/templates/dashboard-pages/auth/sign-out.tsx +20 -0
  251. package/src/templates/dashboard-pages/auth/signup.tsx +71 -0
  252. package/src/templates/dashboard-pages/auth/verify-email.tsx +53 -0
  253. package/src/templates/dashboard-pages/index.ts +22 -0
  254. package/src/templates/dashboard-pages/shell/dashboard-home.tsx +41 -0
  255. package/src/templates/dashboard-pages/shell/forbidden.tsx +46 -0
  256. package/src/templates/dashboard-pages/shell/index.ts +5 -0
  257. package/src/templates/dashboard-pages/shell/maintenance.tsx +27 -0
  258. package/src/templates/dashboard-pages/shell/not-found.tsx +34 -0
  259. package/src/templates/dashboard-pages/shell/server-error.tsx +44 -0
  260. package/src/templates/domain-config.ts +20 -0
  261. package/src/templates/events-skeleton.ts +24 -0
  262. package/src/templates/patterns/api/create-with-event.ts +72 -0
  263. package/src/templates/patterns/api/idempotent-mutation.ts +102 -0
  264. package/src/templates/patterns/api/paginated-list.ts +74 -0
  265. package/src/templates/patterns/api/simple-crud.ts +100 -0
  266. package/src/templates/patterns/api/system-admin.ts +101 -0
  267. package/src/templates/patterns/api/webhook-receiver-style.ts +128 -0
  268. package/src/templates/patterns/component/index.ts +833 -0
  269. package/src/templates/patterns/flow/autonomous-remediation.ts +101 -0
  270. package/src/templates/patterns/flow/fan-out-reaction.ts +56 -0
  271. package/src/templates/patterns/flow/human-in-loop.ts +93 -0
  272. package/src/templates/patterns/flow/multi-step-orchestration.ts +70 -0
  273. package/src/templates/patterns/hook/index.ts +382 -0
  274. package/src/templates/patterns/migration/column-add.ts +54 -0
  275. package/src/templates/patterns/migration/event-store.ts +99 -0
  276. package/src/templates/patterns/migration/index.ts +54 -0
  277. package/src/templates/patterns/migration/rls-policy.ts +80 -0
  278. package/src/templates/patterns/migration/tenant-scoped-table.ts +83 -0
  279. package/src/templates/patterns/page/dashboard.ts +164 -0
  280. package/src/templates/patterns/page/detail.ts +162 -0
  281. package/src/templates/patterns/page/form.ts +192 -0
  282. package/src/templates/patterns/page/index.ts +17 -0
  283. package/src/templates/patterns/page/list.ts +138 -0
  284. package/src/templates/patterns/page/settings.ts +318 -0
  285. package/src/templates/patterns/page/wizard.ts +293 -0
  286. package/src/templates/patterns/subscriber/audit-relay.ts +54 -0
  287. package/src/templates/patterns/subscriber/cascade-deletion.ts +63 -0
  288. package/src/templates/patterns/subscriber/single-step-projection.ts +39 -0
  289. package/src/templates/readme.ts +7 -0
  290. package/src/templates/subscriber-skeleton.ts +28 -0
  291. package/src/utils/checksum.ts +43 -0
  292. package/src/utils/file-helpers.ts +91 -0
  293. package/src/utils/header-inject.ts +126 -0
  294. package/src/utils/install-file.ts +99 -0
  295. package/src/utils/logger.ts +26 -0
  296. package/src/utils/manifest.ts +137 -0
  297. package/src/utils/s3-fetch.ts +91 -0
  298. package/src/utils/scaffold-config.ts +172 -0
  299. package/src/utils/token-substitution.ts +80 -0
  300. package/src/utils/zod-extractor.ts +31 -0
  301. package/tsconfig.json +13 -0
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Column-add migration pattern template.
3
+ * Produces: ALTER TABLE to add a new column with optional default and NOT NULL constraint.
4
+ */
5
+
6
+ export function columnAddTemplate(domain: string, name: string): string {
7
+ const timestamp = new Date().toISOString().replace(/[-:T.]/g, '').slice(0, 14);
8
+
9
+ return `-- Migration: ${timestamp}_${name}
10
+ -- Domain: ${domain}
11
+ -- Pattern: column-add
12
+ -- Adds one or more columns to an existing table with safe defaults.
13
+
14
+ -- ── Example: add a new column ────────────────────────────────
15
+ -- Replace table_name, column_name, type, and default_value as needed.
16
+
17
+ -- ALTER TABLE table_name
18
+ -- ADD COLUMN IF NOT EXISTS column_name VARCHAR(255) NOT NULL DEFAULT 'default_value';
19
+
20
+ -- ── Backfill existing rows (if default is not sufficient) ────
21
+ -- UPDATE table_name
22
+ -- SET column_name = 'default_value'
23
+ -- WHERE column_name IS NULL;
24
+
25
+ -- ── Add index on new column (if frequently queried) ──────────
26
+ -- CREATE INDEX IF NOT EXISTS idx_table_name_column_name
27
+ -- ON table_name(tenant_id, column_name);
28
+
29
+ -- ── Template for adding multiple columns ─────────────────────
30
+
31
+ DO $$
32
+ BEGIN
33
+ -- Column 1
34
+ IF NOT EXISTS (
35
+ SELECT 1 FROM information_schema.columns
36
+ WHERE table_name = 'REPLACE_WITH_TABLE_NAME'
37
+ AND column_name = 'REPLACE_WITH_COLUMN_1'
38
+ ) THEN
39
+ ALTER TABLE REPLACE_WITH_TABLE_NAME
40
+ ADD COLUMN REPLACE_WITH_COLUMN_1 TEXT;
41
+ END IF;
42
+
43
+ -- Column 2
44
+ IF NOT EXISTS (
45
+ SELECT 1 FROM information_schema.columns
46
+ WHERE table_name = 'REPLACE_WITH_TABLE_NAME'
47
+ AND column_name = 'REPLACE_WITH_COLUMN_2'
48
+ ) THEN
49
+ ALTER TABLE REPLACE_WITH_TABLE_NAME
50
+ ADD COLUMN REPLACE_WITH_COLUMN_2 JSONB DEFAULT '{}'::jsonb;
51
+ END IF;
52
+ END $$;
53
+ `;
54
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Event-store migration pattern template.
3
+ * Produces: CREATE TABLE for an event-store with append-only semantics,
4
+ * event_id sequence, aggregate root, and version tracking.
5
+ */
6
+
7
+ export function eventStoreTemplate(domain: string, name: string): string {
8
+ const tableName = name.replace(/-/g, '_');
9
+ const timestamp = new Date().toISOString().replace(/[-:T.]/g, '').slice(0, 14);
10
+
11
+ return `-- Migration: ${timestamp}_${name}
12
+ -- Domain: ${domain}
13
+ -- Pattern: event-store
14
+ -- Creates an append-only event-store table with aggregate-root tracking.
15
+
16
+ CREATE TABLE IF NOT EXISTS ${tableName} (
17
+ -- Event identity
18
+ id BIGSERIAL PRIMARY KEY,
19
+ event_id UUID NOT NULL UNIQUE DEFAULT gen_random_uuid(),
20
+
21
+ -- Aggregate root
22
+ aggregate_type VARCHAR(100) NOT NULL,
23
+ aggregate_id UUID NOT NULL,
24
+ aggregate_version INT NOT NULL DEFAULT 1,
25
+
26
+ -- Event metadata
27
+ event_type VARCHAR(200) NOT NULL,
28
+ event_version INT NOT NULL DEFAULT 1,
29
+ payload JSONB NOT NULL,
30
+ metadata JSONB DEFAULT '{}'::jsonb,
31
+
32
+ -- Tenancy
33
+ tenant_id UUID NOT NULL,
34
+ workspace_id UUID,
35
+
36
+ -- Audit
37
+ occurred_at TIMESTAMPTZ NOT NULL,
38
+ recorded_at TIMESTAMPTZ NOT NULL DEFAULT now(),
39
+ correlation_id UUID,
40
+ causation_id UUID,
41
+
42
+ -- Concurrency guard
43
+ UNIQUE(aggregate_type, aggregate_id, aggregate_version)
44
+ );
45
+
46
+ -- ── Indexes ──────────────────────────────────────────────────
47
+
48
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_aggregate
49
+ ON ${tableName}(aggregate_type, aggregate_id, aggregate_version);
50
+
51
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_tenant_occurred
52
+ ON ${tableName}(tenant_id, occurred_at DESC);
53
+
54
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_event_type
55
+ ON ${tableName}(event_type, occurred_at DESC);
56
+
57
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_correlation
58
+ ON ${tableName}(correlation_id)
59
+ WHERE correlation_id IS NOT NULL;
60
+
61
+ -- ── Row-Level Security ───────────────────────────────────────
62
+
63
+ ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;
64
+
65
+ CREATE POLICY ${tableName}_tenant_isolation
66
+ ON ${tableName}
67
+ FOR SELECT
68
+ TO authenticated
69
+ USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
70
+
71
+ -- ── Append-only guard ────────────────────────────────────────
72
+ -- No UPDATE or DELETE allowed; only INSERT and SELECT.
73
+
74
+ CREATE POLICY ${tableName}_insert_only
75
+ ON ${tableName}
76
+ FOR INSERT
77
+ TO authenticated
78
+ WITH CHECK (true);
79
+
80
+ -- ── Helper: next aggregate version ───────────────────────────
81
+
82
+ CREATE OR REPLACE FUNCTION ${tableName}_next_version(
83
+ p_aggregate_type VARCHAR,
84
+ p_aggregate_id UUID
85
+ )
86
+ RETURNS INT AS $$
87
+ DECLARE
88
+ v_next INT;
89
+ BEGIN
90
+ SELECT COALESCE(MAX(aggregate_version), 0) + 1
91
+ INTO v_next
92
+ FROM ${tableName}
93
+ WHERE aggregate_type = p_aggregate_type
94
+ AND aggregate_id = p_aggregate_id;
95
+ RETURN v_next;
96
+ END;
97
+ $$ LANGUAGE plpgsql;
98
+ `;
99
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Index migration pattern template.
3
+ * Produces: CREATE INDEX statements with CONCURRENTLY option and performance notes.
4
+ */
5
+
6
+ export function indexTemplate(domain: string, name: string): string {
7
+ const timestamp = new Date().toISOString().replace(/[-:T.]/g, '').slice(0, 14);
8
+
9
+ return `-- Migration: ${timestamp}_${name}
10
+ -- Domain: ${domain}
11
+ -- Pattern: index
12
+ -- Creates indexes to optimise query performance.
13
+ --
14
+ -- IMPORTANT: CREATE INDEX CONCURRENTLY must run outside a transaction.
15
+ -- Run this migration with --no-transaction if the tooling supports it.
16
+
17
+ -- ── Single-column index ──────────────────────────────────────
18
+ -- CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_table_name_column
19
+ -- ON table_name(column_name);
20
+
21
+ -- ── Composite index (tenant-scoped queries) ──────────────────
22
+ -- CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_table_name_tenant_column
23
+ -- ON table_name(tenant_id, column_name);
24
+
25
+ -- ── Partial index (filtered) ─────────────────────────────────
26
+ -- CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_table_name_active
27
+ -- ON table_name(tenant_id, created_at DESC)
28
+ -- WHERE status = 'active';
29
+
30
+ -- ── GIN index for JSONB columns ──────────────────────────────
31
+ -- CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_table_name_metadata_gin
32
+ -- ON table_name USING GIN(metadata jsonb_path_ops);
33
+
34
+ -- ── Unique index (enforce business constraint) ───────────────
35
+ -- CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS uq_table_name_tenant_name
36
+ -- ON table_name(tenant_id, name)
37
+ -- WHERE deleted_at IS NULL;
38
+
39
+ -- ── Template: safe index creation with existence check ───────
40
+
41
+ DO $$
42
+ BEGIN
43
+ IF NOT EXISTS (
44
+ SELECT 1 FROM pg_indexes
45
+ WHERE schemaname = 'public'
46
+ AND tablename = 'REPLACE_WITH_TABLE_NAME'
47
+ AND indexname = 'REPLACE_WITH_INDEX_NAME'
48
+ ) THEN
49
+ CREATE INDEX CONCURRENTLY REPLACE_WITH_INDEX_NAME
50
+ ON REPLACE_WITH_TABLE_NAME(REPLACE_WITH_COLUMN_NAME);
51
+ END IF;
52
+ END $$;
53
+ `;
54
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * RLS-policy migration pattern template.
3
+ * Produces: ALTER TABLE to add/enable RLS policies on an existing table.
4
+ */
5
+
6
+ export function rlsPolicyTemplate(domain: string, name: string): string {
7
+ const tableName = name.replace(/-/g, '_');
8
+ const timestamp = new Date().toISOString().replace(/[-:T.]/g, '').slice(0, 14);
9
+
10
+ return `-- Migration: ${timestamp}_${name}
11
+ -- Domain: ${domain}
12
+ -- Pattern: rls-policy
13
+ -- Adds Row-Level Security policies to an existing table.
14
+
15
+ -- ── Enable RLS if not already enabled ────────────────────────
16
+
17
+ ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;
18
+
19
+ -- ── Tenant isolation policy ──────────────────────────────────
20
+ -- Ensures rows are only visible within the same tenant context.
21
+
22
+ DO $$
23
+ BEGIN
24
+ IF NOT EXISTS (
25
+ SELECT 1 FROM pg_policies
26
+ WHERE schemaname = 'public'
27
+ AND tablename = '${tableName}'
28
+ AND policyname = '${tableName}_tenant_isolation'
29
+ ) THEN
30
+ CREATE POLICY ${tableName}_tenant_isolation
31
+ ON ${tableName}
32
+ FOR ALL
33
+ TO authenticated
34
+ USING (tenant_id = current_setting('app.current_tenant_id')::UUID)
35
+ WITH CHECK (tenant_id = current_setting('app.current_tenant_id')::UUID);
36
+ END IF;
37
+ END $$;
38
+
39
+ -- ── Soft-delete filter policy ────────────────────────────────
40
+ -- Automatically excludes soft-deleted rows from SELECT queries.
41
+
42
+ DO $$
43
+ BEGIN
44
+ IF NOT EXISTS (
45
+ SELECT 1 FROM pg_policies
46
+ WHERE schemaname = 'public'
47
+ AND tablename = '${tableName}'
48
+ AND policyname = '${tableName}_soft_delete_filter'
49
+ ) THEN
50
+ CREATE POLICY ${tableName}_soft_delete_filter
51
+ ON ${tableName}
52
+ FOR SELECT
53
+ TO authenticated
54
+ USING (deleted_at IS NULL);
55
+ END IF;
56
+ END $$;
57
+
58
+ -- ── Workspace-scoped access policy ───────────────────────────
59
+ -- Restricts access to rows belonging to the current workspace.
60
+
61
+ DO $$
62
+ BEGIN
63
+ IF NOT EXISTS (
64
+ SELECT 1 FROM pg_policies
65
+ WHERE schemaname = 'public'
66
+ AND tablename = '${tableName}'
67
+ AND policyname = '${tableName}_workspace_scope'
68
+ ) THEN
69
+ CREATE POLICY ${tableName}_workspace_scope
70
+ ON ${tableName}
71
+ FOR SELECT
72
+ TO authenticated
73
+ USING (
74
+ workspace_id IS NULL
75
+ OR workspace_id = current_setting('app.current_workspace_id', true)::UUID
76
+ );
77
+ END IF;
78
+ END $$;
79
+ `;
80
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Tenant-scoped-table migration pattern template.
3
+ * Produces: CREATE TABLE with tenancy trio (tenant_id, workspace_id, org_id),
4
+ * RLS policy, updated_at trigger, and standard indexes.
5
+ */
6
+
7
+ export function tenantScopedTableTemplate(domain: string, name: string): string {
8
+ const tableName = name.replace(/-/g, '_');
9
+ const timestamp = new Date().toISOString().replace(/[-:T.]/g, '').slice(0, 14);
10
+
11
+ return `-- Migration: ${timestamp}_${name}
12
+ -- Domain: ${domain}
13
+ -- Pattern: tenant-scoped-table
14
+ -- Creates a tenant-scoped table with RLS, audit columns, and updated_at trigger.
15
+
16
+ CREATE TABLE IF NOT EXISTS ${tableName} (
17
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
18
+ tenant_id UUID NOT NULL,
19
+ workspace_id UUID,
20
+ org_id UUID,
21
+
22
+ -- Domain-specific columns
23
+ name VARCHAR(255) NOT NULL,
24
+ status VARCHAR(50) NOT NULL DEFAULT 'active',
25
+ metadata JSONB DEFAULT '{}'::jsonb,
26
+
27
+ -- Audit columns
28
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
29
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
30
+ created_by UUID,
31
+ updated_by UUID,
32
+ deleted_at TIMESTAMPTZ
33
+ );
34
+
35
+ -- ── Indexes ──────────────────────────────────────────────────
36
+
37
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_tenant_id
38
+ ON ${tableName}(tenant_id);
39
+
40
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_workspace_id
41
+ ON ${tableName}(workspace_id)
42
+ WHERE workspace_id IS NOT NULL;
43
+
44
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_status
45
+ ON ${tableName}(tenant_id, status);
46
+
47
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_created_at
48
+ ON ${tableName}(created_at DESC);
49
+
50
+ -- ── Row-Level Security ───────────────────────────────────────
51
+
52
+ ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;
53
+
54
+ CREATE POLICY ${tableName}_tenant_isolation
55
+ ON ${tableName}
56
+ FOR ALL
57
+ TO authenticated
58
+ USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
59
+
60
+ -- ── Updated-at trigger ───────────────────────────────────────
61
+
62
+ CREATE OR REPLACE FUNCTION update_${tableName}_updated_at()
63
+ RETURNS TRIGGER AS $$
64
+ BEGIN
65
+ NEW.updated_at = now();
66
+ RETURN NEW;
67
+ END;
68
+ $$ LANGUAGE plpgsql;
69
+
70
+ CREATE TRIGGER trg_${tableName}_updated_at
71
+ BEFORE UPDATE ON ${tableName}
72
+ FOR EACH ROW
73
+ EXECUTE FUNCTION update_${tableName}_updated_at();
74
+
75
+ -- ── Soft-delete policy ───────────────────────────────────────
76
+
77
+ CREATE POLICY ${tableName}_soft_delete
78
+ ON ${tableName}
79
+ FOR SELECT
80
+ TO authenticated
81
+ USING (deleted_at IS NULL);
82
+ `;
83
+ }
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Dashboard page pattern template — dashboard page with widget grid layout.
3
+ */
4
+ export function dashboardPageTemplate(pageName: string): string {
5
+ const componentName = `${pageName}Page`;
6
+ return `import { useQuery } from '@tanstack/react-query';
7
+ import { useTib } from '@/hooks/useTib';
8
+ import { Page } from '@/components/layout/Page';
9
+ import { PageHeader } from '@/components/layout/PageHeader';
10
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
11
+ import { ErrorState } from '@/components/state/ErrorState';
12
+ import { LoadingState } from '@/components/state/LoadingState';
13
+ import { EmptyState } from '@/components/state/EmptyState';
14
+ import { BarChart3, Activity, Users, Clock } from 'lucide-react';
15
+
16
+ interface ${componentName}Props {
17
+ workspaceId: string;
18
+ }
19
+
20
+ interface DashboardStats {
21
+ totalItems: number;
22
+ activeItems: number;
23
+ recentActivity: number;
24
+ avgCompletion: string;
25
+ }
26
+
27
+ interface RecentItem {
28
+ id: string;
29
+ name: string;
30
+ status: string;
31
+ updatedAt: string;
32
+ }
33
+
34
+ export function ${componentName}({ workspaceId }: ${componentName}Props) {
35
+ const tib = useTib();
36
+
37
+ const { data: stats, isLoading: statsLoading, isError: statsError, refetch: refetchStats } = useQuery({
38
+ queryKey: ['dashboard-stats', workspaceId],
39
+ queryFn: () => tib.dashboard.get() as Promise<DashboardStats>,
40
+ staleTime: 30_000,
41
+ });
42
+
43
+ const { data: recent, isLoading: recentLoading } = useQuery({
44
+ queryKey: ['dashboard-recent', workspaceId],
45
+ queryFn: () => tib.data.list({ workspaceId, limit: 5 }) as Promise<{ items: RecentItem[] }>,
46
+ staleTime: 30_000,
47
+ });
48
+
49
+ const isLoading = statsLoading || recentLoading;
50
+
51
+ return (
52
+ <Page title="Dashboard" description="Overview of your workspace">
53
+ <Page.Header
54
+ breadcrumb={[{ label: '${pageName}' }]}
55
+ />
56
+ <Page.Body>
57
+ {statsError ? (
58
+ <ErrorState
59
+ message="Failed to load dashboard data"
60
+ onRetry={() => refetchStats()}
61
+ />
62
+ ) : isLoading ? (
63
+ <LoadingState variant="page" />
64
+ ) : !stats ? (
65
+ <EmptyState
66
+ icon={BarChart3}
67
+ title="No data yet"
68
+ description="Start creating items to see your dashboard."
69
+ />
70
+ ) : (
71
+ <div className="space-y-6">
72
+ {/* Stat cards grid */}
73
+ <div className="layout-cards">
74
+ <Card>
75
+ <CardHeader className="flex flex-row items-center justify-between pb-2">
76
+ <CardTitle className="text-sm font-medium text-muted-foreground">
77
+ Total Items
78
+ </CardTitle>
79
+ <BarChart3 className="h-4 w-4 text-muted-foreground" />
80
+ </CardHeader>
81
+ <CardContent>
82
+ <p className="text-2xl font-semibold">{stats.totalItems}</p>
83
+ </CardContent>
84
+ </Card>
85
+
86
+ <Card>
87
+ <CardHeader className="flex flex-row items-center justify-between pb-2">
88
+ <CardTitle className="text-sm font-medium text-muted-foreground">
89
+ Active
90
+ </CardTitle>
91
+ <Activity className="h-4 w-4 text-muted-foreground" />
92
+ </CardHeader>
93
+ <CardContent>
94
+ <p className="text-2xl font-semibold">{stats.activeItems}</p>
95
+ </CardContent>
96
+ </Card>
97
+
98
+ <Card>
99
+ <CardHeader className="flex flex-row items-center justify-between pb-2">
100
+ <CardTitle className="text-sm font-medium text-muted-foreground">
101
+ Recent Activity
102
+ </CardTitle>
103
+ <Users className="h-4 w-4 text-muted-foreground" />
104
+ </CardHeader>
105
+ <CardContent>
106
+ <p className="text-2xl font-semibold">{stats.recentActivity}</p>
107
+ </CardContent>
108
+ </Card>
109
+
110
+ <Card>
111
+ <CardHeader className="flex flex-row items-center justify-between pb-2">
112
+ <CardTitle className="text-sm font-medium text-muted-foreground">
113
+ Avg Completion
114
+ </CardTitle>
115
+ <Clock className="h-4 w-4 text-muted-foreground" />
116
+ </CardHeader>
117
+ <CardContent>
118
+ <p className="text-2xl font-semibold">{stats.avgCompletion}</p>
119
+ </CardContent>
120
+ </Card>
121
+ </div>
122
+
123
+ {/* Recent items */}
124
+ <Card>
125
+ <CardHeader>
126
+ <CardTitle className="text-lg font-semibold">Recent Items</CardTitle>
127
+ </CardHeader>
128
+ <CardContent>
129
+ {!recent?.items?.length ? (
130
+ <EmptyState
131
+ icon={BarChart3}
132
+ title="No recent items"
133
+ description="Items you create will appear here."
134
+ />
135
+ ) : (
136
+ <div className="space-y-3">
137
+ {recent.items.map((item) => (
138
+ <div
139
+ key={item.id}
140
+ className="flex items-center justify-between rounded-lg border border-border p-3"
141
+ >
142
+ <div>
143
+ <p className="text-sm font-medium">{item.name}</p>
144
+ <p className="text-xs text-muted-foreground">
145
+ Updated {new Date(item.updatedAt).toLocaleDateString()}
146
+ </p>
147
+ </div>
148
+ <span className="text-xs text-muted-foreground">{item.status}</span>
149
+ </div>
150
+ ))}
151
+ </div>
152
+ )}
153
+ </CardContent>
154
+ </Card>
155
+ </div>
156
+ )}
157
+ </Page.Body>
158
+ </Page>
159
+ );
160
+ }
161
+
162
+ export default ${componentName};
163
+ `;
164
+ }