@rawsql-ts/ztd-cli 0.16.0 → 0.17.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 (50) hide show
  1. package/README.md +33 -20
  2. package/dist/commands/init.d.ts +13 -0
  3. package/dist/commands/init.js +372 -127
  4. package/dist/commands/init.js.map +1 -1
  5. package/dist/commands/lint.d.ts +4 -4
  6. package/dist/commands/lint.js +60 -40
  7. package/dist/commands/lint.js.map +1 -1
  8. package/dist/commands/ztdConfig.d.ts +2 -2
  9. package/dist/commands/ztdConfig.js +26 -12
  10. package/dist/commands/ztdConfig.js.map +1 -1
  11. package/dist/utils/optionalDependencies.d.ts +35 -0
  12. package/dist/utils/optionalDependencies.js +96 -0
  13. package/dist/utils/optionalDependencies.js.map +1 -0
  14. package/package.json +16 -9
  15. package/templates/AGENTS.md +36 -309
  16. package/templates/README.md +12 -215
  17. package/templates/dist/ztd-cli/templates/src/db/sql-client.ts +24 -0
  18. package/templates/src/AGENTS.md +26 -0
  19. package/templates/src/catalog/AGENTS.md +37 -0
  20. package/templates/src/catalog/runtime/AGENTS.md +75 -0
  21. package/templates/src/catalog/runtime/_coercions.ts +1 -0
  22. package/templates/src/catalog/runtime/_smoke.runtime.ts +21 -0
  23. package/templates/src/catalog/specs/AGENTS.md +48 -0
  24. package/templates/src/catalog/specs/_smoke.spec.arktype.ts +21 -0
  25. package/templates/src/catalog/specs/_smoke.spec.zod.ts +20 -0
  26. package/templates/src/db/sql-client.ts +5 -5
  27. package/templates/src/jobs/AGENTS.md +26 -0
  28. package/templates/src/jobs/README.md +3 -0
  29. package/templates/src/repositories/AGENTS.md +118 -0
  30. package/templates/src/repositories/tables/AGENTS.md +94 -0
  31. package/templates/src/repositories/tables/README.md +3 -0
  32. package/templates/src/repositories/views/AGENTS.md +25 -0
  33. package/templates/src/repositories/views/README.md +3 -0
  34. package/templates/src/sql/AGENTS.md +77 -0
  35. package/templates/src/sql/README.md +6 -0
  36. package/templates/tests/AGENTS.md +43 -150
  37. package/templates/tests/generated/AGENTS.md +16 -0
  38. package/templates/tests/smoke.test.ts +5 -0
  39. package/templates/tests/smoke.validation.test.ts +34 -0
  40. package/templates/tests/support/AGENTS.md +26 -0
  41. package/templates/tests/support/global-setup.ts +8 -23
  42. package/templates/tests/support/testkit-client.ts +13 -741
  43. package/templates/tsconfig.json +8 -1
  44. package/templates/ztd/AGENTS.md +11 -67
  45. package/templates/ztd/README.md +4 -13
  46. package/templates/ztd/ddl/AGENTS.md +34 -0
  47. package/templates/ztd/ddl/demo.sql +74 -0
  48. package/templates/src/repositories/user-accounts.ts +0 -179
  49. package/templates/tests/user-profiles.test.ts +0 -161
  50. package/templates/tests/writer-constraints.test.ts +0 -32
@@ -1,6 +1,13 @@
1
1
  {
2
- "extends": "../../../tsconfig.json",
3
2
  "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022"],
5
+ "module": "NodeNext",
6
+ "moduleResolution": "NodeNext",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "skipLibCheck": true,
4
11
  "outDir": "dist",
5
12
  "tsBuildInfoFile": "dist/.tsbuildinfo"
6
13
  },
@@ -1,74 +1,18 @@
1
- # AGENTS: Zero Table Dependency Definitions
1
+ # ztd AGENTS
2
2
 
3
- This file defines **protected, human-led domains** under the `ztd/` directory.
4
- AI must treat these directories as authoritative sources of truth and must not modify them without explicit instruction.
5
- These rules govern the `ztd/` contents after project initialization and apply regardless of mapper, writer, or runtime architecture decisions.
3
+ This directory contains ZTD inputs and related documentation.
6
4
 
7
- ---
5
+ ## Core rule
8
6
 
9
- ## Generated files (important)
7
+ - "ztd/ddl/" is the only human-owned source of truth inside "ztd/".
8
+ - Do not create new subdirectories under "ztd/" unless explicitly instructed.
10
9
 
11
- - `tests/generated/` is auto-generated and must never be committed.
12
- - After cloning the repository (or in a clean environment), run `npx ztd ztd-config`.
13
- - If TypeScript reports missing modules or type errors because `tests/generated/` is missing, run `npx ztd ztd-config`.
14
- - Generated artifacts exist solely to support validation and testing and MUST NEVER influence definitions under `ztd/`.
10
+ ## Boundaries
15
11
 
16
- ---
12
+ - Runtime code must not depend on "ztd/".
13
+ - Tests may reference DDL and generated outputs via ZTD tooling.
17
14
 
18
- ## DDL Specifications (`ztd/ddl/`)
15
+ ## Editing policy
19
16
 
20
- You are an AI assistant responsible for **reading and respecting** the contents of this directory.
21
-
22
- ### Purpose
23
-
24
- This directory contains all canonical definitions of database structure, including:
25
-
26
- - CREATE TABLE
27
- - ALTER TABLE
28
- - Constraints
29
- - Indexes
30
-
31
- It is the **single source of truth** for the physical database schema as interpreted by ztd-cli.
32
-
33
- ### Behavior Rules (strict)
34
-
35
- - **Never modify files in this directory unless explicitly instructed by a human.**
36
- - Do not apply “helpful” refactors, cleanups, or formatting changes on your own.
37
- - You may propose edits or review changes when asked, but you must not apply them without approval.
38
- - All DDL statements must be:
39
- - Valid PostgreSQL syntax
40
- - Explicitly semicolon-terminated
41
- - Do not reorder statements; dependency and execution order matters.
42
- - Preserve all human-authored:
43
- - Naming
44
- - Formatting
45
- - Comments
46
- - Structural intent
47
- - When asked to extend existing definitions:
48
- - Do not remove or rewrite existing columns or comments unless explicitly told.
49
- - Maintain column order and constraint style.
50
- - Do not introduce schema changes that conflict with existing constraints or indexes.
51
- - The `public.user_account` and `public.user_profile` tables exist to support the mapper/writer sample; any modification to those tables is a maintenance obligation that requires concurrent updates to `src/repositories/user-accounts.ts` and `tests/writer-constraints.test.ts` so the workflow keeps functioning.
52
- - DDL defines physical truth only and MUST NEVER be reshaped to accommodate mapper, writer, or test tooling.
53
- - Runtime convenience is never a valid reason to alter DDL.
54
-
55
- If there is uncertainty, stop and request clarification instead of guessing.
56
-
57
- ---
58
-
59
- - Only `ztd/ddl` is part of the canonical `ztd` contract; do not create or assume additional subdirectories without explicit human direction.
60
-
61
- ---
62
-
63
- ## Absolute Restrictions (important)
64
-
65
- - AI must not modify anything under `ztd/` by default.
66
- - DDL is the only **human-led artifact** in this directory.
67
- - AI may assist by:
68
- - Reading
69
- - Explaining
70
- - Proposing diffs
71
- - AI may apply changes **only** with explicit instruction.
72
- - No tooling limitation, test strategy, or runtime design justifies modifying `ztd/` artifacts.
73
-
74
- Violation of these rules leads to silent corruption of domain meaning and is unacceptable.
17
+ - Avoid modifying "ztd/README.md" unless explicitly asked.
18
+ - Prefer adding rules to "ztd/ddl/AGENTS.md" for DDL-related guidance.
@@ -1,15 +1,6 @@
1
- # ZTD Definitions
1
+ # ztd/
2
2
 
3
- This directory hosts the schema definitions under `ztd/ddl/`. Every DDL file in that folder is the single source of truth for database objects; parser tools and tests read these files to learn table structures, columns, indexes, and constraints.
3
+ This directory stores ZTD schema artifacts.
4
4
 
5
- ## DDL Guidelines
6
-
7
- - Place every schema definition under `ztd/ddl/` with valid PostgreSQL and semicolon-terminated statements.
8
- - Keep the files deterministic: avoid generated output, enforce column ordering, and document any non-obvious constraints with comments.
9
- - When you rename or drop a column, update the corresponding DDL file rather than trying to patch test artifacts manually.
10
- - Treat `ztd/ddl/` as a human-maintained catalog; AI may assist but must not invent or diverge from the files stored there.
11
-
12
- ## Workflow expectations
13
-
14
- - Regenerate `tests/generated/ztd-row-map.generated.ts` via `npx ztd ztd-config` whenever the DDL changes.
15
- - Do not assume any other subdirectories under `/ztd` exist unless a human has explicitly created them for a specific purpose.
5
+ - `ztd/ddl/<schema>.sql` is the source of truth for schema.
6
+ - Run `npx ztd ztd-config` to regenerate `tests/generated` outputs.
@@ -0,0 +1,34 @@
1
+ # ztd/ddl AGENTS
2
+
3
+ This folder contains physical schema definitions (DDL). Human-led.
4
+
5
+ ## Ownership
6
+
7
+ - Humans own DDL semantics.
8
+ - AI may assist with mechanical edits, but must not invent domain rules.
9
+
10
+ ## Conventions (Postgres-oriented)
11
+
12
+ - Table names: singular (example: "user", "order", "invoice")
13
+ - Primary key: "serial8" (bigserial) by default
14
+ - Timestamps: default "current_timestamp" where applicable
15
+
16
+ ## Comments (required)
17
+
18
+ Add comments for tables and important columns.
19
+
20
+ Postgres syntax:
21
+ - "comment on table <schema>.<table> is '...';"
22
+ - "comment on column <schema>.<table>.<column> is '...';"
23
+
24
+ ## Constraints
25
+
26
+ - Prefer explicit NOT NULL where appropriate.
27
+ - Prefer explicit unique constraints for business keys.
28
+ - Foreign keys are allowed, but keep them intentional and explain rationale in comments.
29
+
30
+ ## File strategy
31
+
32
+ - Keep schema DDL in a clear and reviewable form.
33
+ - If using one file per schema (example: "public.sql"), keep it consistent.
34
+ - Avoid random splitting unless there is a strong reason.
@@ -0,0 +1,74 @@
1
+ create table "user" (
2
+ user_id serial8 primary key,
3
+ user_name text not null,
4
+ email text not null,
5
+ created_at timestamptz not null default current_timestamp
6
+ );
7
+
8
+ comment on table "user" is
9
+ 'User master. Referenced by task_assignment.';
10
+
11
+ comment on column "user".user_id is
12
+ 'Primary key of user.';
13
+ comment on column "user".user_name is
14
+ 'Display name of the user.';
15
+ comment on column "user".email is
16
+ 'Email address of the user.';
17
+ comment on column "user".created_at is
18
+ 'Timestamp when the user was created.';
19
+
20
+ create table task (
21
+ task_id serial8 primary key,
22
+ title text not null,
23
+ status text not null,
24
+ priority integer not null,
25
+ due_date date,
26
+ created_at timestamptz not null default current_timestamp
27
+ );
28
+
29
+ comment on table task is
30
+ 'Task entity. Current assignee is derived from task_assignment history.';
31
+
32
+ comment on column task.task_id is
33
+ 'Primary key of task.';
34
+ comment on column task.title is
35
+ 'Short description of the task.';
36
+ comment on column task.status is
37
+ 'Task status. Example: open, in_progress, done.';
38
+ comment on column task.priority is
39
+ 'Priority of the task. Higher value means higher priority.';
40
+ comment on column task.due_date is
41
+ 'Optional due date of the task.';
42
+ comment on column task.created_at is
43
+ 'Timestamp when the task was created.';
44
+
45
+ create table task_assignment (
46
+ task_assignment_id serial8 primary key,
47
+ task_id bigint not null,
48
+ user_id bigint not null,
49
+ assigned_at timestamptz not null default current_timestamp
50
+ );
51
+
52
+ comment on table task_assignment is
53
+ 'Assignment history of tasks. Latest assigned_at defines current assignee.';
54
+
55
+ comment on column task_assignment.task_assignment_id is
56
+ 'Primary key of task_assignment.';
57
+ comment on column task_assignment.task_id is
58
+ 'Referenced task identifier.';
59
+ comment on column task_assignment.user_id is
60
+ 'Referenced user identifier.';
61
+ comment on column task_assignment.assigned_at is
62
+ 'Timestamp when the task was assigned to the user.';
63
+
64
+ create index idx_task_assignment_task
65
+ on task_assignment(task_id);
66
+
67
+ create index idx_task_assignment_task_time
68
+ on task_assignment(task_id, assigned_at desc);
69
+
70
+ comment on index idx_task_assignment_task is
71
+ 'Index for joining task_assignment by task_id.';
72
+
73
+ comment on index idx_task_assignment_task_time is
74
+ 'Index for resolving latest assignment per task.';
@@ -1,179 +0,0 @@
1
- import type { SqlClient } from '../db/sql-client';
2
- import { createMapper, entity, toRowsExecutor } from '@rawsql-ts/mapper-core';
3
- import { insert, Key, remove, update } from '@rawsql-ts/writer-core';
4
-
5
- const userAccountTable = 'public.user_account';
6
-
7
- type UserProfileRow = {
8
- profileId: number;
9
- userAccountId: number;
10
- bio: string | null;
11
- website: string | null;
12
- verified: boolean;
13
- };
14
-
15
- /**
16
- * DTO that represents a user account with its optional profile information.
17
- * @property {number} userAccountId The primary key for the user account.
18
- * @property {string} username The canonical username.
19
- * @property {string} email The account email address.
20
- * @property {string} displayName The account display name.
21
- * @property {Date} createdAt When the account was created.
22
- * @property {Date} updatedAt When the account was last updated.
23
- * @property {UserProfileRow} [profile] Optional profile payload joined from user_profile.
24
- */
25
- export type UserAccountWithProfile = {
26
- userAccountId: number;
27
- username: string;
28
- email: string;
29
- displayName: string;
30
- createdAt: Date;
31
- updatedAt: Date;
32
- profile?: UserProfileRow;
33
- };
34
-
35
- // Map the joined profile columns so we can hydrate nested objects later.
36
- const profileMapping = entity<UserProfileRow>({
37
- name: 'userProfile',
38
- key: 'profileId',
39
- columnMap: {
40
- profileId: 'profile_id',
41
- userAccountId: 'profile_user_account_id',
42
- bio: 'bio',
43
- website: 'website',
44
- verified: 'verified',
45
- },
46
- });
47
-
48
- const userAccountMapping = entity<UserAccountWithProfile>({
49
- name: 'userAccount',
50
- key: 'userAccountId',
51
- columnMap: {
52
- userAccountId: 'user_account_id',
53
- username: 'username',
54
- email: 'email',
55
- displayName: 'display_name',
56
- createdAt: 'created_at',
57
- updatedAt: 'updated_at',
58
- },
59
- }).belongsTo('profile', profileMapping, 'userAccountId', { optional: true });
60
-
61
- const userProfilesSql = `
62
- SELECT
63
- u.user_account_id,
64
- u.username,
65
- u.email,
66
- u.display_name,
67
- u.created_at,
68
- u.updated_at,
69
- p.profile_id,
70
- p.user_account_id AS profile_user_account_id,
71
- p.bio,
72
- p.website,
73
- p.verified
74
- FROM public.user_account u
75
- LEFT JOIN public.user_profile p ON p.user_account_id = u.user_account_id
76
- ORDER BY u.user_account_id, p.profile_id;
77
- `;
78
-
79
- // Build a mapper that can translate snake_case columns into camelCase DTOs.
80
- const createMapperForClient = (client: SqlClient) =>
81
- createMapper(
82
- toRowsExecutor((sql, params: unknown[] = []) =>
83
- client.query<Record<string, unknown>>(sql, params),
84
- ),
85
- {
86
- // The explicit column maps enumerate the nested entity columns while keyTransform handles generic snake_to_camel conversions.
87
- keyTransform: 'snake_to_camel',
88
- coerceDates: true,
89
- },
90
- );
91
-
92
- /**
93
- * Queries all user accounts together with their associated profiles.
94
- * @param {SqlClient} client Client proxy that executes the mapper SQL.
95
- * @returns {Promise<UserAccountWithProfile[]>} The joined account-with-profile rows.
96
- */
97
- export async function listUserProfiles(
98
- client: SqlClient,
99
- ): Promise<UserAccountWithProfile[]> {
100
- const mapper = createMapperForClient(client);
101
- return mapper.query(userProfilesSql, [], userAccountMapping);
102
- }
103
-
104
- /**
105
- * Parameters required to insert a new user account.
106
- * @property {string} username The requested username.
107
- * @property {string} email The requested email address.
108
- * @property {string} displayName The requested display name.
109
- */
110
- export type NewUserAccount = {
111
- username: string;
112
- email: string;
113
- displayName: string;
114
- };
115
-
116
- /**
117
- * Payload describing the display name change for an existing account.
118
- * @property {string} displayName The new display name to persist.
119
- */
120
- export type DisplayNameUpdatePayload = {
121
- displayName: string;
122
- };
123
-
124
- /**
125
- * Builds an insert statement for the user_account writer.
126
- * @param {NewUserAccount} input The normalized fields for the new account.
127
- * @returns {ReturnType<typeof insert>} A well-formed insert statement for the user_account writer.
128
- */
129
- export function buildInsertUserAccount(
130
- input: NewUserAccount,
131
- ): ReturnType<typeof insert> {
132
- return insert(userAccountTable, {
133
- username: input.username,
134
- email: input.email,
135
- display_name: input.displayName,
136
- });
137
- }
138
-
139
- /**
140
- * Builds an update statement that refreshes the display name and timestamp.
141
- * @param {Key} key The unique key identifying the row to update.
142
- * @param {DisplayNameUpdatePayload} payload The new display name payload.
143
- * @returns {ReturnType<typeof update>} A writer update statement that refreshes the display name and updated_at timestamp.
144
- */
145
- export function buildUpdateDisplayName(
146
- key: Key,
147
- payload: DisplayNameUpdatePayload,
148
- ): ReturnType<typeof update> {
149
- return update(
150
- userAccountTable,
151
- {
152
- // Persist the new display name and bump the timestamp along with it.
153
- display_name: payload.displayName,
154
- updated_at: new Date(),
155
- },
156
- key,
157
- );
158
- }
159
-
160
- /**
161
- * Builds a delete statement for the specified user account key.
162
- * @param {Key} key Identifies the row to remove.
163
- * @returns {ReturnType<typeof remove>} A writer delete statement for the matching user account.
164
- */
165
- export function buildRemoveUserAccount(key: Key): ReturnType<typeof remove> {
166
- return remove(userAccountTable, key);
167
- }
168
-
169
- /**
170
- * Column sets that writer tests use to ensure only approved columns are touched.
171
- * @property {readonly string[]} insertColumns Columns allowed for new account inserts.
172
- * @property {readonly string[]} updateColumns Columns permitted during updates.
173
- * @property {readonly string[]} immutableColumns Columns that must remain unchanged.
174
- */
175
- export const userAccountWriterColumnSets = {
176
- insertColumns: ['username', 'email', 'display_name'] as const,
177
- updateColumns: ['display_name', 'updated_at'] as const,
178
- immutableColumns: ['user_account_id', 'created_at'] as const,
179
- };
@@ -1,161 +0,0 @@
1
- import { describe, expect, test, afterAll } from 'vitest';
2
- import type { TableFixture, TestkitProvider } from '@rawsql-ts/testkit-core';
3
- import { createTestkitProvider } from '@rawsql-ts/testkit-core';
4
- import { createPgTestkitClient } from '@rawsql-ts/pg-testkit';
5
- import { Pool } from 'pg';
6
- import path from 'node:path';
7
- import {
8
- tableFixture,
9
- tableSchemas,
10
- TestRowMap,
11
- } from './generated/ztd-row-map.generated';
12
- import { listUserProfiles } from '../src/repositories/user-accounts';
13
-
14
- const ddlDirectories = [path.resolve(__dirname, '../ztd/ddl')];
15
- const skipReason = 'DATABASE_URL is not configured';
16
- const configuredDatabaseUrl = process.env.DATABASE_URL?.trim();
17
- const suiteTitle = configuredDatabaseUrl
18
- ? 'user profile mapper'
19
- : `user profile mapper (skipped: ${skipReason})`;
20
- const describeUserProfile = configuredDatabaseUrl
21
- ? describe
22
- : (describe.skip as typeof describe);
23
- let pool: Pool | undefined;
24
- let providerPromise: Promise<TestkitProvider> | undefined;
25
-
26
- // Lazily initialize the test provider so missing DATABASE_URL values do not trigger side effects.
27
- function getProvider(): Promise<TestkitProvider> {
28
- if (providerPromise) {
29
- return providerPromise;
30
- }
31
-
32
- const databaseUrl = process.env.DATABASE_URL?.trim();
33
- if (!databaseUrl) {
34
- throw new Error(
35
- 'Cannot initialize the repository testkit provider without DATABASE_URL.',
36
- );
37
- }
38
-
39
- pool = new Pool({ connectionString: databaseUrl });
40
- const activePool = pool;
41
- providerPromise = createTestkitProvider({
42
- connectionFactory: () => activePool.connect(),
43
- resourceFactory: async (connection, fixtures) =>
44
- createPgTestkitClient({
45
- connectionFactory: () => connection,
46
- tableRows: fixtures,
47
- ddl: { directories: ddlDirectories },
48
- }),
49
- releaseResource: async (client) => {
50
- await client.close();
51
- },
52
- disposeConnection: async (connection) => {
53
- if (typeof connection.release === 'function') {
54
- connection.release();
55
- return;
56
- }
57
- if (typeof connection.end === 'function') {
58
- await connection.end();
59
- }
60
- },
61
- });
62
-
63
- return providerPromise;
64
- }
65
-
66
- afterAll(async () => {
67
- // Close resources only when initialization actually happened.
68
- if (!providerPromise) {
69
- return;
70
- }
71
-
72
- const provider = await providerPromise;
73
- await provider.close();
74
- if (pool) {
75
- await pool.end();
76
- }
77
- });
78
-
79
- function buildUserAccounts(): TestRowMap['public.user_account'][] {
80
- return [
81
- {
82
- user_account_id: 1,
83
- username: 'alpha',
84
- email: 'alpha@example.com',
85
- display_name: 'Alpha Tester',
86
- created_at: '2025-12-01T08:00:00Z',
87
- updated_at: '2025-12-01T09:00:00Z',
88
- },
89
- {
90
- user_account_id: 2,
91
- username: 'bravo',
92
- email: 'bravo@example.com',
93
- display_name: 'Bravo Builder',
94
- created_at: '2025-12-02T10:00:00Z',
95
- updated_at: '2025-12-02T11:00:00Z',
96
- },
97
- ];
98
- }
99
-
100
- function buildUserProfiles(): TestRowMap['public.user_profile'][] {
101
- return [
102
- {
103
- profile_id: 101,
104
- user_account_id: 1,
105
- bio: 'Lead engineer and mapper advocate.',
106
- website: 'https://example.com',
107
- verified: true,
108
- },
109
- ];
110
- }
111
-
112
- function buildFixtures(): TableFixture[] {
113
- return [
114
- tableFixture(
115
- 'public.user_account',
116
- buildUserAccounts(),
117
- tableSchemas['public.user_account'],
118
- ),
119
- tableFixture(
120
- 'public.user_profile',
121
- buildUserProfiles(),
122
- tableSchemas['public.user_profile'],
123
- ),
124
- ];
125
- }
126
-
127
- describeUserProfile(suiteTitle, () => {
128
- test('listUserProfiles hydrates optional profiles', async () => {
129
- const fixtures = buildFixtures();
130
- const provider = await getProvider();
131
- await provider.withRepositoryFixture(fixtures, async (client) => {
132
- const result = await listUserProfiles(client);
133
- expect(result).toEqual([
134
- {
135
- userAccountId: 1,
136
- username: 'alpha',
137
- email: 'alpha@example.com',
138
- displayName: 'Alpha Tester',
139
- createdAt: new Date('2025-12-01T08:00:00Z'),
140
- updatedAt: new Date('2025-12-01T09:00:00Z'),
141
- profile: {
142
- profileId: 101,
143
- userAccountId: 1,
144
- bio: 'Lead engineer and mapper advocate.',
145
- website: 'https://example.com',
146
- verified: true,
147
- },
148
- },
149
- {
150
- userAccountId: 2,
151
- username: 'bravo',
152
- email: 'bravo@example.com',
153
- displayName: 'Bravo Builder',
154
- createdAt: new Date('2025-12-02T10:00:00Z'),
155
- updatedAt: new Date('2025-12-02T11:00:00Z'),
156
- profile: undefined,
157
- },
158
- ]);
159
- });
160
- });
161
- });
@@ -1,32 +0,0 @@
1
- import { describe, expect, test } from 'vitest';
2
- import { tableSchemas } from './generated/ztd-row-map.generated';
3
- import { userAccountWriterColumnSets } from '../src/repositories/user-accounts';
4
-
5
- const userColumns = new Set(
6
- Object.keys(tableSchemas['public.user_account'].columns),
7
- );
8
-
9
- describe('user_account writer columns', () => {
10
- test('insert columns must exist on the canonical table', () => {
11
- const { insertColumns } = userAccountWriterColumnSets;
12
- const missing = insertColumns.filter((column) => !userColumns.has(column));
13
- expect(missing, `Missing columns: ${missing.join(', ')}`).toEqual([]);
14
- expect(insertColumns).toEqual(
15
- expect.arrayContaining(['username', 'email', 'display_name']),
16
- );
17
- });
18
-
19
- test('writer column sets align with the canonical table', () => {
20
- const { updateColumns, immutableColumns } = userAccountWriterColumnSets;
21
- const missingUpdates = updateColumns.filter((column) => !userColumns.has(column));
22
- expect(missingUpdates, `Missing update columns: ${missingUpdates.join(', ')}`).toEqual([]);
23
- const missingImmutables = immutableColumns.filter((column) => !userColumns.has(column));
24
- expect(missingImmutables, `Missing immutable columns: ${missingImmutables.join(', ')}`).toEqual([]);
25
- immutableColumns.forEach((column) => {
26
- expect(
27
- updateColumns,
28
- `Immutable column "${column}" should never appear in updateColumns`,
29
- ).not.toContain(column);
30
- });
31
- });
32
- });