@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,378 @@
1
+ /**
2
+ * Paginated list hook pattern — uses `useInfiniteQuery` from TanStack Query for paginated lists.
3
+ */
4
+ export function paginatedListHookTemplate(name) {
5
+ const hookName = `use${capitalize(name)}`;
6
+ return `import { useInfiniteQuery } from '@tanstack/react-query';
7
+ import { useTib } from '@/hooks/useTib';
8
+
9
+ /** Request shape for paginated list endpoints. */
10
+ export interface ${capitalize(name)}ListRequest {
11
+ workspaceId: string;
12
+ status?: string;
13
+ search?: string;
14
+ cursor?: string;
15
+ limit?: number;
16
+ }
17
+
18
+ /** Single item in the paginated response. */
19
+ export interface ${capitalize(name)}Item {
20
+ id: string;
21
+ name: string;
22
+ status: string;
23
+ createdAt: string;
24
+ updatedAt: string;
25
+ }
26
+
27
+ /** Paginated response from the API. */
28
+ export interface ${capitalize(name)}ListResponse {
29
+ items: ${capitalize(name)}Item[];
30
+ nextCursor: string | null;
31
+ totalCount: number;
32
+ }
33
+
34
+ /**
35
+ * Infinite-query hook for paginated ${name} lists.
36
+ *
37
+ * Uses TanStack Query \`useInfiniteQuery\` with cursor-based pagination.
38
+ * Each page is fetched as the user scrolls, keeping memory bounded.
39
+ */
40
+ export function ${hookName}(args: ${capitalize(name)}ListRequest) {
41
+ const tib = useTib();
42
+
43
+ return useInfiniteQuery<${capitalize(name)}ListResponse, Error>({
44
+ queryKey: ['${name}-list', args.workspaceId, args.status, args.search],
45
+ queryFn: ({ pageParam }) =>
46
+ tib.data.list({
47
+ workspaceId: args.workspaceId,
48
+ status: args.status,
49
+ search: args.search,
50
+ cursor: pageParam as string | undefined,
51
+ limit: args.limit ?? 20,
52
+ }),
53
+ initialPageParam: undefined as string | undefined,
54
+ getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
55
+ staleTime: 10_000,
56
+ });
57
+ }
58
+ `;
59
+ }
60
+ export function mutationHookTemplate(name) {
61
+ const hookName = `use${capitalize(name)}`;
62
+ return `import { useMutation, useQueryClient } from '@tanstack/react-query';
63
+ import { useTib } from '@/hooks/useTib';
64
+ import { toast } from 'sonner';
65
+
66
+ /** Input shape for the mutation. */
67
+ export interface ${capitalize(name)}Input {
68
+ workspaceId: string;
69
+ /** Fields to create or update. */
70
+ values: Record<string, unknown>;
71
+ }
72
+
73
+ /** Response from the API after a successful mutation. */
74
+ export interface ${capitalize(name)}Response {
75
+ id: string;
76
+ ok: boolean;
77
+ }
78
+
79
+ /**
80
+ * Mutation hook for creating or updating ${name} entities.
81
+ *
82
+ * Uses TanStack Query \`useMutation\` with optimistic update support,
83
+ * automatic error rollback, and toast notifications.
84
+ */
85
+ export function ${hookName}() {
86
+ const tib = useTib();
87
+ const queryClient = useQueryClient();
88
+
89
+ return useMutation<${capitalize(name)}Response, Error, ${capitalize(name)}Input>({
90
+ mutationFn: (input: ${capitalize(name)}Input) =>
91
+ tib.data.upsert({
92
+ workspaceId: input.workspaceId,
93
+ ...input.values,
94
+ }),
95
+
96
+ onMutate: async (input) => {
97
+ // Cancel outgoing refetches so they don't overwrite the optimistic update
98
+ const key = ['${name}-list', input.workspaceId];
99
+ await queryClient.cancelQueries({ queryKey: key });
100
+
101
+ // Snapshot the previous value for rollback
102
+ const previous = queryClient.getQueryData(key);
103
+ return { previous, key };
104
+ },
105
+
106
+ onError: (err, input, context) => {
107
+ // Roll back to the previous value on error
108
+ if (context?.previous) {
109
+ queryClient.setQueryData(context.key, context.previous);
110
+ }
111
+ toast.error('Operation failed', {
112
+ description: err.message,
113
+ });
114
+ },
115
+
116
+ onSuccess: (_data, input) => {
117
+ toast.success('Operation successful');
118
+ },
119
+
120
+ onSettled: (_data, _error, input) => {
121
+ // Always refetch to ensure cache is fresh
122
+ queryClient.invalidateQueries({
123
+ queryKey: ['${name}', input.workspaceId],
124
+ });
125
+ },
126
+ });
127
+ }
128
+
129
+ /**
130
+ * Mutation hook for deleting ${name} entities.
131
+ */
132
+ export function useDelete${capitalize(name)}() {
133
+ const tib = useTib();
134
+ const queryClient = useQueryClient();
135
+
136
+ return useMutation<void, Error, { workspaceId: string; id: string }>({
137
+ mutationFn: ({ workspaceId, id }) =>
138
+ tib.data.delete({ workspaceId, id }),
139
+
140
+ onSuccess: (_data, { workspaceId }) => {
141
+ toast.success('Deleted');
142
+ queryClient.invalidateQueries({
143
+ queryKey: ['${name}', workspaceId],
144
+ });
145
+ },
146
+
147
+ onError: (err) => {
148
+ toast.error('Delete failed', {
149
+ description: err.message,
150
+ });
151
+ },
152
+ });
153
+ }
154
+ `;
155
+ }
156
+ export function featureFlagHookTemplate(name) {
157
+ const hookName = `use${capitalize(name)}`;
158
+ return `import { useQuery } from '@tanstack/react-query';
159
+ import { useTib } from '@/hooks/useTib';
160
+
161
+ /** W6-style error shape returned by the TIB SDK. */
162
+ interface TibErrorShape {
163
+ code?: string;
164
+ traceId?: string;
165
+ message: string;
166
+ }
167
+
168
+ interface FeatureFlagResult {
169
+ enabled: boolean;
170
+ config?: Record<string, unknown>;
171
+ }
172
+
173
+ /**
174
+ * Hook that checks a feature flag via the TIB SDK.
175
+ *
176
+ * Uses TanStack Query with a 60-second stale time (matching the
177
+ * backend flag cache TTL). Returns \`{ enabled: false }\` on error
178
+ * so the UI degrades safely when the flags service is unreachable.
179
+ */
180
+ export function ${hookName}(flagKey: string): {
181
+ enabled: boolean;
182
+ config?: Record<string, unknown>;
183
+ isLoading: boolean;
184
+ isError: boolean;
185
+ error: Error | null;
186
+ } {
187
+ const tib = useTib();
188
+
189
+ const { data, isLoading, isError, error } = useQuery<FeatureFlagResult, Error>({
190
+ queryKey: ['feature-flag', flagKey],
191
+ queryFn: async () => {
192
+ try {
193
+ const result = await tib.flags.get(flagKey) as { value: boolean; config?: Record<string, unknown> };
194
+ return { enabled: result.value, config: result.config };
195
+ } catch (err) {
196
+ // Degrade gracefully: return disabled on error
197
+ return { enabled: false };
198
+ }
199
+ },
200
+ staleTime: 60_000, // matches backend flag cache TTL
201
+ retry: 1,
202
+ });
203
+
204
+ return {
205
+ enabled: data?.enabled ?? false,
206
+ config: data?.config,
207
+ isLoading,
208
+ isError,
209
+ error,
210
+ };
211
+ }
212
+ `;
213
+ }
214
+ export function subscriptionHookTemplate(name) {
215
+ const hookName = `use${capitalize(name)}`;
216
+ return `import { useEffect, useRef, useCallback } from 'react';
217
+ import { useQueryClient } from '@tanstack/react-query';
218
+ import { useTib } from '@/hooks/useTib';
219
+
220
+ /** Message envelope received over the subscription channel. */
221
+ interface ${capitalize(name)}Message {
222
+ eventId: string;
223
+ type: string;
224
+ payload: Record<string, unknown>;
225
+ timestamp: string;
226
+ }
227
+
228
+ /** Callback invoked for each received message. */
229
+ type ${capitalize(name)}Handler = (message: ${capitalize(name)}Message) => void;
230
+
231
+ /** Connection states for the subscription. */
232
+ type ConnectionStatus = 'connecting' | 'connected' | 'reconnecting' | 'disconnected';
233
+
234
+ /**
235
+ * Hook for subscribing to a real-time ${name} event stream.
236
+ *
237
+ * Uses EventSource (SSE) with automatic reconnection (exponential
238
+ * backoff, capped at 16 s). Incoming events are dispatched to the
239
+ * provided handler and optionally update TanStack Query caches.
240
+ *
241
+ * Heartbeat: server emits \`ping\` every 25 s. Missing two consecutive
242
+ * pings triggers reconnection.
243
+ */
244
+ export function ${hookName}(
245
+ workspaceId: string,
246
+ onMessage?: ${capitalize(name)}Handler,
247
+ ): {
248
+ status: ConnectionStatus;
249
+ disconnect: () => void;
250
+ reconnect: () => void;
251
+ } {
252
+ const tib = useTib();
253
+ const queryClient = useQueryClient();
254
+ const eventSourceRef = useRef<EventSource | null>(null);
255
+ const reconnectAttemptRef = useRef<number>(0);
256
+ const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
257
+ const lastPingRef = useRef<number>(Date.now());
258
+ const pingCheckTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
259
+ const statusRef = useRef<ConnectionStatus>('disconnected');
260
+
261
+ const connect = useCallback(() => {
262
+ // Clean up any existing connection
263
+ if (eventSourceRef.current) {
264
+ eventSourceRef.current.close();
265
+ }
266
+
267
+ statusRef.current = reconnectAttemptRef.current > 0 ? 'reconnecting' : 'connecting';
268
+
269
+ const url = new URL('\\\${import.meta.env.VITE_API_URL}/v1/stream');
270
+ url.searchParams.set('workspaceId', workspaceId);
271
+
272
+ const es = new EventSource(url.toString());
273
+ eventSourceRef.current = es;
274
+ lastPingRef.current = Date.now();
275
+
276
+ es.addEventListener('ping', () => {
277
+ lastPingRef.current = Date.now();
278
+ });
279
+
280
+ es.addEventListener('message', (event) => {
281
+ try {
282
+ const data = JSON.parse(event.data) as ${capitalize(name)}Message;
283
+ statusRef.current = 'connected';
284
+ reconnectAttemptRef.current = 0;
285
+
286
+ if (onMessage) {
287
+ onMessage(data);
288
+ }
289
+
290
+ // Optionally update a query cache key with the latest event
291
+ queryClient.setQueryData(['${name}-stream', workspaceId], data);
292
+ } catch {
293
+ // Ignore malformed messages
294
+ }
295
+ });
296
+
297
+ es.onerror = () => {
298
+ es.close();
299
+ scheduleReconnect();
300
+ };
301
+
302
+ es.onopen = () => {
303
+ statusRef.current = 'connected';
304
+ reconnectAttemptRef.current = 0;
305
+ };
306
+
307
+ // Start ping heartbeat checker
308
+ if (pingCheckTimerRef.current) {
309
+ clearInterval(pingCheckTimerRef.current);
310
+ }
311
+ pingCheckTimerRef.current = setInterval(() => {
312
+ const elapsed = Date.now() - lastPingRef.current;
313
+ if (elapsed > 50_000) {
314
+ // Missed two consecutive pings (> 50 s)
315
+ es.close();
316
+ scheduleReconnect();
317
+ }
318
+ }, 25_000);
319
+ }, [workspaceId, onMessage, queryClient]);
320
+
321
+ const scheduleReconnect = useCallback(() => {
322
+ if (reconnectTimerRef.current) {
323
+ clearTimeout(reconnectTimerRef.current);
324
+ }
325
+
326
+ const attempt = reconnectAttemptRef.current;
327
+ const delay = Math.min(1000 * Math.pow(2, attempt), 16_000) +
328
+ Math.random() * 1000; // ± jitter
329
+
330
+ reconnectAttemptRef.current = attempt + 1;
331
+ statusRef.current = 'reconnecting';
332
+
333
+ reconnectTimerRef.current = setTimeout(() => {
334
+ connect();
335
+ }, delay);
336
+ }, [connect]);
337
+
338
+ const disconnect = useCallback(() => {
339
+ if (eventSourceRef.current) {
340
+ eventSourceRef.current.close();
341
+ eventSourceRef.current = null;
342
+ }
343
+ if (reconnectTimerRef.current) {
344
+ clearTimeout(reconnectTimerRef.current);
345
+ reconnectTimerRef.current = null;
346
+ }
347
+ if (pingCheckTimerRef.current) {
348
+ clearInterval(pingCheckTimerRef.current);
349
+ pingCheckTimerRef.current = null;
350
+ }
351
+ statusRef.current = 'disconnected';
352
+ }, []);
353
+
354
+ const reconnect = useCallback(() => {
355
+ reconnectAttemptRef.current = 0;
356
+ disconnect();
357
+ connect();
358
+ }, [connect, disconnect]);
359
+
360
+ useEffect(() => {
361
+ connect();
362
+ return () => disconnect();
363
+ }, [connect, disconnect]);
364
+
365
+ return {
366
+ get status() {
367
+ return statusRef.current;
368
+ },
369
+ disconnect,
370
+ reconnect,
371
+ };
372
+ }
373
+ `;
374
+ }
375
+ /** Capitalizes the first character of a string. */
376
+ function capitalize(s) {
377
+ return s.charAt(0).toUpperCase() + s.slice(1);
378
+ }
@@ -0,0 +1,5 @@
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
+ export declare function columnAddTemplate(domain: string, name: string): string;
@@ -0,0 +1,52 @@
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
+ export function columnAddTemplate(domain, name) {
6
+ const timestamp = new Date().toISOString().replace(/[-:T.]/g, '').slice(0, 14);
7
+ return `-- Migration: ${timestamp}_${name}
8
+ -- Domain: ${domain}
9
+ -- Pattern: column-add
10
+ -- Adds one or more columns to an existing table with safe defaults.
11
+
12
+ -- ── Example: add a new column ────────────────────────────────
13
+ -- Replace table_name, column_name, type, and default_value as needed.
14
+
15
+ -- ALTER TABLE table_name
16
+ -- ADD COLUMN IF NOT EXISTS column_name VARCHAR(255) NOT NULL DEFAULT 'default_value';
17
+
18
+ -- ── Backfill existing rows (if default is not sufficient) ────
19
+ -- UPDATE table_name
20
+ -- SET column_name = 'default_value'
21
+ -- WHERE column_name IS NULL;
22
+
23
+ -- ── Add index on new column (if frequently queried) ──────────
24
+ -- CREATE INDEX IF NOT EXISTS idx_table_name_column_name
25
+ -- ON table_name(tenant_id, column_name);
26
+
27
+ -- ── Template for adding multiple columns ─────────────────────
28
+
29
+ DO $$
30
+ BEGIN
31
+ -- Column 1
32
+ IF NOT EXISTS (
33
+ SELECT 1 FROM information_schema.columns
34
+ WHERE table_name = 'REPLACE_WITH_TABLE_NAME'
35
+ AND column_name = 'REPLACE_WITH_COLUMN_1'
36
+ ) THEN
37
+ ALTER TABLE REPLACE_WITH_TABLE_NAME
38
+ ADD COLUMN REPLACE_WITH_COLUMN_1 TEXT;
39
+ END IF;
40
+
41
+ -- Column 2
42
+ IF NOT EXISTS (
43
+ SELECT 1 FROM information_schema.columns
44
+ WHERE table_name = 'REPLACE_WITH_TABLE_NAME'
45
+ AND column_name = 'REPLACE_WITH_COLUMN_2'
46
+ ) THEN
47
+ ALTER TABLE REPLACE_WITH_TABLE_NAME
48
+ ADD COLUMN REPLACE_WITH_COLUMN_2 JSONB DEFAULT '{}'::jsonb;
49
+ END IF;
50
+ END $$;
51
+ `;
52
+ }
@@ -0,0 +1,6 @@
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
+ export declare function eventStoreTemplate(domain: string, name: string): string;
@@ -0,0 +1,97 @@
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
+ export function eventStoreTemplate(domain, name) {
7
+ const tableName = name.replace(/-/g, '_');
8
+ const timestamp = new Date().toISOString().replace(/[-:T.]/g, '').slice(0, 14);
9
+ return `-- Migration: ${timestamp}_${name}
10
+ -- Domain: ${domain}
11
+ -- Pattern: event-store
12
+ -- Creates an append-only event-store table with aggregate-root tracking.
13
+
14
+ CREATE TABLE IF NOT EXISTS ${tableName} (
15
+ -- Event identity
16
+ id BIGSERIAL PRIMARY KEY,
17
+ event_id UUID NOT NULL UNIQUE DEFAULT gen_random_uuid(),
18
+
19
+ -- Aggregate root
20
+ aggregate_type VARCHAR(100) NOT NULL,
21
+ aggregate_id UUID NOT NULL,
22
+ aggregate_version INT NOT NULL DEFAULT 1,
23
+
24
+ -- Event metadata
25
+ event_type VARCHAR(200) NOT NULL,
26
+ event_version INT NOT NULL DEFAULT 1,
27
+ payload JSONB NOT NULL,
28
+ metadata JSONB DEFAULT '{}'::jsonb,
29
+
30
+ -- Tenancy
31
+ tenant_id UUID NOT NULL,
32
+ workspace_id UUID,
33
+
34
+ -- Audit
35
+ occurred_at TIMESTAMPTZ NOT NULL,
36
+ recorded_at TIMESTAMPTZ NOT NULL DEFAULT now(),
37
+ correlation_id UUID,
38
+ causation_id UUID,
39
+
40
+ -- Concurrency guard
41
+ UNIQUE(aggregate_type, aggregate_id, aggregate_version)
42
+ );
43
+
44
+ -- ── Indexes ──────────────────────────────────────────────────
45
+
46
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_aggregate
47
+ ON ${tableName}(aggregate_type, aggregate_id, aggregate_version);
48
+
49
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_tenant_occurred
50
+ ON ${tableName}(tenant_id, occurred_at DESC);
51
+
52
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_event_type
53
+ ON ${tableName}(event_type, occurred_at DESC);
54
+
55
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_correlation
56
+ ON ${tableName}(correlation_id)
57
+ WHERE correlation_id IS NOT NULL;
58
+
59
+ -- ── Row-Level Security ───────────────────────────────────────
60
+
61
+ ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;
62
+
63
+ CREATE POLICY ${tableName}_tenant_isolation
64
+ ON ${tableName}
65
+ FOR SELECT
66
+ TO authenticated
67
+ USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
68
+
69
+ -- ── Append-only guard ────────────────────────────────────────
70
+ -- No UPDATE or DELETE allowed; only INSERT and SELECT.
71
+
72
+ CREATE POLICY ${tableName}_insert_only
73
+ ON ${tableName}
74
+ FOR INSERT
75
+ TO authenticated
76
+ WITH CHECK (true);
77
+
78
+ -- ── Helper: next aggregate version ───────────────────────────
79
+
80
+ CREATE OR REPLACE FUNCTION ${tableName}_next_version(
81
+ p_aggregate_type VARCHAR,
82
+ p_aggregate_id UUID
83
+ )
84
+ RETURNS INT AS $$
85
+ DECLARE
86
+ v_next INT;
87
+ BEGIN
88
+ SELECT COALESCE(MAX(aggregate_version), 0) + 1
89
+ INTO v_next
90
+ FROM ${tableName}
91
+ WHERE aggregate_type = p_aggregate_type
92
+ AND aggregate_id = p_aggregate_id;
93
+ RETURN v_next;
94
+ END;
95
+ $$ LANGUAGE plpgsql;
96
+ `;
97
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Index migration pattern template.
3
+ * Produces: CREATE INDEX statements with CONCURRENTLY option and performance notes.
4
+ */
5
+ export declare function indexTemplate(domain: string, name: string): string;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Index migration pattern template.
3
+ * Produces: CREATE INDEX statements with CONCURRENTLY option and performance notes.
4
+ */
5
+ export function indexTemplate(domain, name) {
6
+ const timestamp = new Date().toISOString().replace(/[-:T.]/g, '').slice(0, 14);
7
+ return `-- Migration: ${timestamp}_${name}
8
+ -- Domain: ${domain}
9
+ -- Pattern: index
10
+ -- Creates indexes to optimise query performance.
11
+ --
12
+ -- IMPORTANT: CREATE INDEX CONCURRENTLY must run outside a transaction.
13
+ -- Run this migration with --no-transaction if the tooling supports it.
14
+
15
+ -- ── Single-column index ──────────────────────────────────────
16
+ -- CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_table_name_column
17
+ -- ON table_name(column_name);
18
+
19
+ -- ── Composite index (tenant-scoped queries) ──────────────────
20
+ -- CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_table_name_tenant_column
21
+ -- ON table_name(tenant_id, column_name);
22
+
23
+ -- ── Partial index (filtered) ─────────────────────────────────
24
+ -- CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_table_name_active
25
+ -- ON table_name(tenant_id, created_at DESC)
26
+ -- WHERE status = 'active';
27
+
28
+ -- ── GIN index for JSONB columns ──────────────────────────────
29
+ -- CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_table_name_metadata_gin
30
+ -- ON table_name USING GIN(metadata jsonb_path_ops);
31
+
32
+ -- ── Unique index (enforce business constraint) ───────────────
33
+ -- CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS uq_table_name_tenant_name
34
+ -- ON table_name(tenant_id, name)
35
+ -- WHERE deleted_at IS NULL;
36
+
37
+ -- ── Template: safe index creation with existence check ───────
38
+
39
+ DO $$
40
+ BEGIN
41
+ IF NOT EXISTS (
42
+ SELECT 1 FROM pg_indexes
43
+ WHERE schemaname = 'public'
44
+ AND tablename = 'REPLACE_WITH_TABLE_NAME'
45
+ AND indexname = 'REPLACE_WITH_INDEX_NAME'
46
+ ) THEN
47
+ CREATE INDEX CONCURRENTLY REPLACE_WITH_INDEX_NAME
48
+ ON REPLACE_WITH_TABLE_NAME(REPLACE_WITH_COLUMN_NAME);
49
+ END IF;
50
+ END $$;
51
+ `;
52
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * RLS-policy migration pattern template.
3
+ * Produces: ALTER TABLE to add/enable RLS policies on an existing table.
4
+ */
5
+ export declare function rlsPolicyTemplate(domain: string, name: string): string;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * RLS-policy migration pattern template.
3
+ * Produces: ALTER TABLE to add/enable RLS policies on an existing table.
4
+ */
5
+ export function rlsPolicyTemplate(domain, name) {
6
+ const tableName = name.replace(/-/g, '_');
7
+ const timestamp = new Date().toISOString().replace(/[-:T.]/g, '').slice(0, 14);
8
+ return `-- Migration: ${timestamp}_${name}
9
+ -- Domain: ${domain}
10
+ -- Pattern: rls-policy
11
+ -- Adds Row-Level Security policies to an existing table.
12
+
13
+ -- ── Enable RLS if not already enabled ────────────────────────
14
+
15
+ ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;
16
+
17
+ -- ── Tenant isolation policy ──────────────────────────────────
18
+ -- Ensures rows are only visible within the same tenant context.
19
+
20
+ DO $$
21
+ BEGIN
22
+ IF NOT EXISTS (
23
+ SELECT 1 FROM pg_policies
24
+ WHERE schemaname = 'public'
25
+ AND tablename = '${tableName}'
26
+ AND policyname = '${tableName}_tenant_isolation'
27
+ ) THEN
28
+ CREATE POLICY ${tableName}_tenant_isolation
29
+ ON ${tableName}
30
+ FOR ALL
31
+ TO authenticated
32
+ USING (tenant_id = current_setting('app.current_tenant_id')::UUID)
33
+ WITH CHECK (tenant_id = current_setting('app.current_tenant_id')::UUID);
34
+ END IF;
35
+ END $$;
36
+
37
+ -- ── Soft-delete filter policy ────────────────────────────────
38
+ -- Automatically excludes soft-deleted rows from SELECT queries.
39
+
40
+ DO $$
41
+ BEGIN
42
+ IF NOT EXISTS (
43
+ SELECT 1 FROM pg_policies
44
+ WHERE schemaname = 'public'
45
+ AND tablename = '${tableName}'
46
+ AND policyname = '${tableName}_soft_delete_filter'
47
+ ) THEN
48
+ CREATE POLICY ${tableName}_soft_delete_filter
49
+ ON ${tableName}
50
+ FOR SELECT
51
+ TO authenticated
52
+ USING (deleted_at IS NULL);
53
+ END IF;
54
+ END $$;
55
+
56
+ -- ── Workspace-scoped access policy ───────────────────────────
57
+ -- Restricts access to rows belonging to the current workspace.
58
+
59
+ DO $$
60
+ BEGIN
61
+ IF NOT EXISTS (
62
+ SELECT 1 FROM pg_policies
63
+ WHERE schemaname = 'public'
64
+ AND tablename = '${tableName}'
65
+ AND policyname = '${tableName}_workspace_scope'
66
+ ) THEN
67
+ CREATE POLICY ${tableName}_workspace_scope
68
+ ON ${tableName}
69
+ FOR SELECT
70
+ TO authenticated
71
+ USING (
72
+ workspace_id IS NULL
73
+ OR workspace_id = current_setting('app.current_workspace_id', true)::UUID
74
+ );
75
+ END IF;
76
+ END $$;
77
+ `;
78
+ }
@@ -0,0 +1,6 @@
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
+ export declare function tenantScopedTableTemplate(domain: string, name: string): string;