@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.
- package/CHANGELOG.md +18 -0
- package/README.md +133 -0
- package/dist/builder/build-registry.d.ts +20 -0
- package/dist/builder/build-registry.js +178 -0
- package/dist/builder/build-types.d.ts +10 -0
- package/dist/builder/build-types.js +32 -0
- package/dist/builder/load-domain-module-config.d.ts +12 -0
- package/dist/builder/load-domain-module-config.js +34 -0
- package/dist/builder/load-module.d.ts +26 -0
- package/dist/builder/load-module.js +114 -0
- package/dist/builder/zod-to-json.d.ts +10 -0
- package/dist/builder/zod-to-json.js +15 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +338 -0
- package/dist/commands/add-api.d.ts +32 -0
- package/dist/commands/add-api.js +106 -0
- package/dist/commands/add-component.d.ts +15 -0
- package/dist/commands/add-component.js +78 -0
- package/dist/commands/add-domain.d.ts +21 -0
- package/dist/commands/add-domain.js +71 -0
- package/dist/commands/add-flow.d.ts +14 -0
- package/dist/commands/add-flow.js +108 -0
- package/dist/commands/add-hook.d.ts +15 -0
- package/dist/commands/add-hook.js +65 -0
- package/dist/commands/add-migration.d.ts +26 -0
- package/dist/commands/add-migration.js +93 -0
- package/dist/commands/add-module.d.ts +7 -0
- package/dist/commands/add-module.js +261 -0
- package/dist/commands/add-page.d.ts +13 -0
- package/dist/commands/add-page.js +170 -0
- package/dist/commands/add-seed-page.d.ts +5 -0
- package/dist/commands/add-seed-page.js +251 -0
- package/dist/commands/add-subscriber.d.ts +30 -0
- package/dist/commands/add-subscriber.js +87 -0
- package/dist/commands/add-test.d.ts +22 -0
- package/dist/commands/add-test.js +285 -0
- package/dist/commands/audit.d.ts +39 -0
- package/dist/commands/audit.js +171 -0
- package/dist/commands/build-catalog.d.ts +68 -0
- package/dist/commands/build-catalog.js +105 -0
- package/dist/commands/build-flows.d.ts +26 -0
- package/dist/commands/build-flows.js +174 -0
- package/dist/commands/build.d.ts +17 -0
- package/dist/commands/build.js +39 -0
- package/dist/commands/check-hashes.d.ts +38 -0
- package/dist/commands/check-hashes.js +97 -0
- package/dist/commands/create-project.d.ts +5 -0
- package/dist/commands/create-project.js +272 -0
- package/dist/commands/dev.d.ts +15 -0
- package/dist/commands/dev.js +27 -0
- package/dist/commands/doctor.d.ts +48 -0
- package/dist/commands/doctor.js +1301 -0
- package/dist/commands/explain.d.ts +34 -0
- package/dist/commands/explain.js +167 -0
- package/dist/commands/power-tune.d.ts +1 -0
- package/dist/commands/power-tune.js +87 -0
- package/dist/commands/reseed-page.d.ts +6 -0
- package/dist/commands/reseed-page.js +297 -0
- package/dist/commands/show-dns.d.ts +15 -0
- package/dist/commands/show-dns.js +101 -0
- package/dist/commands/show.d.ts +46 -0
- package/dist/commands/show.js +135 -0
- package/dist/commands/test.d.ts +18 -0
- package/dist/commands/test.js +56 -0
- package/dist/commands/upgrade-backend.d.ts +9 -0
- package/dist/commands/upgrade-backend.js +46 -0
- package/dist/commands/upgrade-event.d.ts +22 -0
- package/dist/commands/upgrade-event.js +111 -0
- package/dist/commands/upgrade-frontend.d.ts +5 -0
- package/dist/commands/upgrade-frontend.js +99 -0
- package/dist/commands/upgrade.d.ts +7 -0
- package/dist/commands/upgrade.js +495 -0
- package/dist/commands/validate.d.ts +30 -0
- package/dist/commands/validate.js +148 -0
- package/dist/runtime-stubs/hydrate-local-ctx.d.ts +49 -0
- package/dist/runtime-stubs/hydrate-local-ctx.js +45 -0
- package/dist/runtime-stubs/load-secrets.d.ts +11 -0
- package/dist/runtime-stubs/load-secrets.js +37 -0
- package/dist/server/api-server.d.ts +21 -0
- package/dist/server/api-server.js +14 -0
- package/dist/server/mount-routes.d.ts +19 -0
- package/dist/server/mount-routes.js +42 -0
- package/dist/templates/api-skeleton.d.ts +18 -0
- package/dist/templates/api-skeleton.js +69 -0
- package/dist/templates/claude-md.d.ts +4 -0
- package/dist/templates/claude-md.js +7 -0
- package/dist/templates/dashboard-pages/index.d.ts +101 -0
- package/dist/templates/dashboard-pages/index.js +22 -0
- package/dist/templates/domain-config.d.ts +10 -0
- package/dist/templates/domain-config.js +19 -0
- package/dist/templates/events-skeleton.d.ts +9 -0
- package/dist/templates/events-skeleton.js +23 -0
- package/dist/templates/patterns/api/create-with-event.d.ts +5 -0
- package/dist/templates/patterns/api/create-with-event.js +69 -0
- package/dist/templates/patterns/api/idempotent-mutation.d.ts +5 -0
- package/dist/templates/patterns/api/idempotent-mutation.js +99 -0
- package/dist/templates/patterns/api/paginated-list.d.ts +5 -0
- package/dist/templates/patterns/api/paginated-list.js +71 -0
- package/dist/templates/patterns/api/simple-crud.d.ts +6 -0
- package/dist/templates/patterns/api/simple-crud.js +95 -0
- package/dist/templates/patterns/api/system-admin.d.ts +5 -0
- package/dist/templates/patterns/api/system-admin.js +98 -0
- package/dist/templates/patterns/api/webhook-receiver-style.d.ts +5 -0
- package/dist/templates/patterns/api/webhook-receiver-style.js +125 -0
- package/dist/templates/patterns/component/index.d.ts +25 -0
- package/dist/templates/patterns/component/index.js +814 -0
- package/dist/templates/patterns/flow/autonomous-remediation.d.ts +5 -0
- package/dist/templates/patterns/flow/autonomous-remediation.js +98 -0
- package/dist/templates/patterns/flow/fan-out-reaction.d.ts +5 -0
- package/dist/templates/patterns/flow/fan-out-reaction.js +53 -0
- package/dist/templates/patterns/flow/human-in-loop.d.ts +5 -0
- package/dist/templates/patterns/flow/human-in-loop.js +90 -0
- package/dist/templates/patterns/flow/multi-step-orchestration.d.ts +5 -0
- package/dist/templates/patterns/flow/multi-step-orchestration.js +67 -0
- package/dist/templates/patterns/hook/index.d.ts +7 -0
- package/dist/templates/patterns/hook/index.js +378 -0
- package/dist/templates/patterns/migration/column-add.d.ts +5 -0
- package/dist/templates/patterns/migration/column-add.js +52 -0
- package/dist/templates/patterns/migration/event-store.d.ts +6 -0
- package/dist/templates/patterns/migration/event-store.js +97 -0
- package/dist/templates/patterns/migration/index.d.ts +5 -0
- package/dist/templates/patterns/migration/index.js +52 -0
- package/dist/templates/patterns/migration/rls-policy.d.ts +5 -0
- package/dist/templates/patterns/migration/rls-policy.js +78 -0
- package/dist/templates/patterns/migration/tenant-scoped-table.d.ts +6 -0
- package/dist/templates/patterns/migration/tenant-scoped-table.js +81 -0
- package/dist/templates/patterns/page/dashboard.d.ts +4 -0
- package/dist/templates/patterns/page/dashboard.js +164 -0
- package/dist/templates/patterns/page/detail.d.ts +4 -0
- package/dist/templates/patterns/page/detail.js +162 -0
- package/dist/templates/patterns/page/form.d.ts +4 -0
- package/dist/templates/patterns/page/form.js +192 -0
- package/dist/templates/patterns/page/index.d.ts +8 -0
- package/dist/templates/patterns/page/index.js +14 -0
- package/dist/templates/patterns/page/list.d.ts +4 -0
- package/dist/templates/patterns/page/list.js +138 -0
- package/dist/templates/patterns/page/settings.d.ts +4 -0
- package/dist/templates/patterns/page/settings.js +318 -0
- package/dist/templates/patterns/page/wizard.d.ts +4 -0
- package/dist/templates/patterns/page/wizard.js +293 -0
- package/dist/templates/patterns/subscriber/audit-relay.d.ts +5 -0
- package/dist/templates/patterns/subscriber/audit-relay.js +51 -0
- package/dist/templates/patterns/subscriber/cascade-deletion.d.ts +5 -0
- package/dist/templates/patterns/subscriber/cascade-deletion.js +60 -0
- package/dist/templates/patterns/subscriber/single-step-projection.d.ts +5 -0
- package/dist/templates/patterns/subscriber/single-step-projection.js +36 -0
- package/dist/templates/readme.d.ts +4 -0
- package/dist/templates/readme.js +7 -0
- package/dist/templates/subscriber-skeleton.d.ts +11 -0
- package/dist/templates/subscriber-skeleton.js +23 -0
- package/dist/utils/checksum.d.ts +18 -0
- package/dist/utils/checksum.js +39 -0
- package/dist/utils/file-helpers.d.ts +35 -0
- package/dist/utils/file-helpers.js +59 -0
- package/dist/utils/header-inject.d.ts +15 -0
- package/dist/utils/header-inject.js +93 -0
- package/dist/utils/install-file.d.ts +17 -0
- package/dist/utils/install-file.js +78 -0
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.js +24 -0
- package/dist/utils/manifest.d.ts +28 -0
- package/dist/utils/manifest.js +93 -0
- package/dist/utils/s3-fetch.d.ts +25 -0
- package/dist/utils/s3-fetch.js +52 -0
- package/dist/utils/scaffold-config.d.ts +95 -0
- package/dist/utils/scaffold-config.js +53 -0
- package/dist/utils/token-substitution.d.ts +35 -0
- package/dist/utils/token-substitution.js +52 -0
- package/dist/utils/zod-extractor.d.ts +10 -0
- package/dist/utils/zod-extractor.js +28 -0
- package/package.json +43 -0
- package/src/__tests__/build-registry.test.ts +115 -0
- package/src/__tests__/build-types.test.ts +100 -0
- package/src/__tests__/builder/walkDomainDir.test.ts +140 -0
- package/src/__tests__/commands/add-domain.test.ts +174 -0
- package/src/__tests__/commands/add-flow.test.ts +69 -0
- package/src/__tests__/commands/add-module.test.ts +207 -0
- package/src/__tests__/commands/build-flows.test.ts +154 -0
- package/src/__tests__/commands/check-hashes.test.ts +142 -0
- package/src/__tests__/commands/create-project.test.ts +712 -0
- package/src/__tests__/commands/upgrade.test.ts +762 -0
- package/src/__tests__/doctor.test.ts +291 -0
- package/src/__tests__/hydrate-local-ctx.test.ts +71 -0
- package/src/__tests__/load-domain-module-config.test.ts +10 -0
- package/src/__tests__/mount-routes.test.ts +100 -0
- package/src/__tests__/scaffold-src/part-a-layout.test.ts +46 -0
- package/src/__tests__/scripts/package-scaffold.test.ts +45 -0
- package/src/__tests__/utils/checksum.test.ts +90 -0
- package/src/__tests__/utils/header-inject.test.ts +89 -0
- package/src/__tests__/utils/install-file.test.ts +207 -0
- package/src/__tests__/utils/manifest.test.ts +229 -0
- package/src/__tests__/utils/token-substitution.test.ts +134 -0
- package/src/__tests__/validate.test.ts +18 -0
- package/src/builder/build-registry.ts +237 -0
- package/src/builder/build-types.ts +36 -0
- package/src/builder/load-domain-module-config.ts +43 -0
- package/src/builder/load-module.ts +133 -0
- package/src/builder/zod-to-json.ts +16 -0
- package/src/cli.ts +387 -0
- package/src/commands/add-api.ts +145 -0
- package/src/commands/add-component.ts +116 -0
- package/src/commands/add-domain.ts +100 -0
- package/src/commands/add-flow.ts +136 -0
- package/src/commands/add-hook.ts +100 -0
- package/src/commands/add-migration.ts +129 -0
- package/src/commands/add-module.ts +347 -0
- package/src/commands/add-page.ts +242 -0
- package/src/commands/add-seed-page.ts +338 -0
- package/src/commands/add-subscriber.ts +123 -0
- package/src/commands/add-test.ts +328 -0
- package/src/commands/audit.ts +217 -0
- package/src/commands/build-catalog.ts +181 -0
- package/src/commands/build-flows.ts +198 -0
- package/src/commands/build.ts +59 -0
- package/src/commands/check-hashes.ts +150 -0
- package/src/commands/create-project.ts +367 -0
- package/src/commands/dev.ts +47 -0
- package/src/commands/doctor.ts +1362 -0
- package/src/commands/explain.ts +209 -0
- package/src/commands/power-tune.ts +112 -0
- package/src/commands/reseed-page.ts +410 -0
- package/src/commands/show-dns.ts +139 -0
- package/src/commands/show.ts +179 -0
- package/src/commands/test.ts +91 -0
- package/src/commands/upgrade-backend.ts +72 -0
- package/src/commands/upgrade-event.ts +175 -0
- package/src/commands/upgrade-frontend.ts +135 -0
- package/src/commands/upgrade.ts +668 -0
- package/src/commands/validate.ts +208 -0
- package/src/runtime-stubs/hydrate-local-ctx.ts +102 -0
- package/src/runtime-stubs/load-secrets.ts +40 -0
- package/src/server/api-server.ts +34 -0
- package/src/server/mount-routes.ts +68 -0
- package/src/templates/api-skeleton.ts +72 -0
- package/src/templates/claude-md.ts +7 -0
- package/src/templates/dashboard-pages/account/api-keys.tsx +107 -0
- package/src/templates/dashboard-pages/account/audit-log.tsx +60 -0
- package/src/templates/dashboard-pages/account/index.ts +5 -0
- package/src/templates/dashboard-pages/account/members.tsx +107 -0
- package/src/templates/dashboard-pages/account/profile.tsx +53 -0
- package/src/templates/dashboard-pages/account/workspace-settings.tsx +64 -0
- package/src/templates/dashboard-pages/auth/accept-invitation.tsx +62 -0
- package/src/templates/dashboard-pages/auth/choose-org.tsx +66 -0
- package/src/templates/dashboard-pages/auth/forgot-password.tsx +69 -0
- package/src/templates/dashboard-pages/auth/index.ts +10 -0
- package/src/templates/dashboard-pages/auth/login.tsx +75 -0
- package/src/templates/dashboard-pages/auth/mfa-setup.tsx +56 -0
- package/src/templates/dashboard-pages/auth/mfa-verify.tsx +49 -0
- package/src/templates/dashboard-pages/auth/reset-password.tsx +64 -0
- package/src/templates/dashboard-pages/auth/sign-out.tsx +20 -0
- package/src/templates/dashboard-pages/auth/signup.tsx +71 -0
- package/src/templates/dashboard-pages/auth/verify-email.tsx +53 -0
- package/src/templates/dashboard-pages/index.ts +22 -0
- package/src/templates/dashboard-pages/shell/dashboard-home.tsx +41 -0
- package/src/templates/dashboard-pages/shell/forbidden.tsx +46 -0
- package/src/templates/dashboard-pages/shell/index.ts +5 -0
- package/src/templates/dashboard-pages/shell/maintenance.tsx +27 -0
- package/src/templates/dashboard-pages/shell/not-found.tsx +34 -0
- package/src/templates/dashboard-pages/shell/server-error.tsx +44 -0
- package/src/templates/domain-config.ts +20 -0
- package/src/templates/events-skeleton.ts +24 -0
- package/src/templates/patterns/api/create-with-event.ts +72 -0
- package/src/templates/patterns/api/idempotent-mutation.ts +102 -0
- package/src/templates/patterns/api/paginated-list.ts +74 -0
- package/src/templates/patterns/api/simple-crud.ts +100 -0
- package/src/templates/patterns/api/system-admin.ts +101 -0
- package/src/templates/patterns/api/webhook-receiver-style.ts +128 -0
- package/src/templates/patterns/component/index.ts +833 -0
- package/src/templates/patterns/flow/autonomous-remediation.ts +101 -0
- package/src/templates/patterns/flow/fan-out-reaction.ts +56 -0
- package/src/templates/patterns/flow/human-in-loop.ts +93 -0
- package/src/templates/patterns/flow/multi-step-orchestration.ts +70 -0
- package/src/templates/patterns/hook/index.ts +382 -0
- package/src/templates/patterns/migration/column-add.ts +54 -0
- package/src/templates/patterns/migration/event-store.ts +99 -0
- package/src/templates/patterns/migration/index.ts +54 -0
- package/src/templates/patterns/migration/rls-policy.ts +80 -0
- package/src/templates/patterns/migration/tenant-scoped-table.ts +83 -0
- package/src/templates/patterns/page/dashboard.ts +164 -0
- package/src/templates/patterns/page/detail.ts +162 -0
- package/src/templates/patterns/page/form.ts +192 -0
- package/src/templates/patterns/page/index.ts +17 -0
- package/src/templates/patterns/page/list.ts +138 -0
- package/src/templates/patterns/page/settings.ts +318 -0
- package/src/templates/patterns/page/wizard.ts +293 -0
- package/src/templates/patterns/subscriber/audit-relay.ts +54 -0
- package/src/templates/patterns/subscriber/cascade-deletion.ts +63 -0
- package/src/templates/patterns/subscriber/single-step-projection.ts +39 -0
- package/src/templates/readme.ts +7 -0
- package/src/templates/subscriber-skeleton.ts +28 -0
- package/src/utils/checksum.ts +43 -0
- package/src/utils/file-helpers.ts +91 -0
- package/src/utils/header-inject.ts +126 -0
- package/src/utils/install-file.ts +99 -0
- package/src/utils/logger.ts +26 -0
- package/src/utils/manifest.ts +137 -0
- package/src/utils/s3-fetch.ts +91 -0
- package/src/utils/scaffold-config.ts +172 -0
- package/src/utils/token-substitution.ts +80 -0
- package/src/utils/zod-extractor.ts +31 -0
- 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
|
+
}
|