@intentius/chant-lexicon-sql 0.124.13 → 0.124.15
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/dist/change-set.d.ts +174 -0
- package/dist/change-set.d.ts.map +1 -0
- package/dist/composites/audit-log-table.d.ts +12 -4
- package/dist/composites/audit-log-table.d.ts.map +1 -1
- package/dist/composites/cdc-mirror.d.ts +11 -4
- package/dist/composites/cdc-mirror.d.ts.map +1 -1
- package/dist/composites/events-table.d.ts +10 -3
- package/dist/composites/events-table.d.ts.map +1 -1
- package/dist/composites/join-table.d.ts +11 -3
- package/dist/composites/join-table.d.ts.map +1 -1
- package/dist/composites/refreshed-view.d.ts +11 -3
- package/dist/composites/refreshed-view.d.ts.map +1 -1
- package/dist/composites/replacing-table.d.ts +10 -3
- package/dist/composites/replacing-table.d.ts.map +1 -1
- package/dist/composites/rollup-view.d.ts +11 -4
- package/dist/composites/rollup-view.d.ts.map +1 -1
- package/dist/composites/sharded-table.d.ts +11 -4
- package/dist/composites/sharded-table.d.ts.map +1 -1
- package/dist/composites/soft-delete-table.d.ts +12 -4
- package/dist/composites/soft-delete-table.d.ts.map +1 -1
- package/dist/composites/tenant-table.d.ts +11 -3
- package/dist/composites/tenant-table.d.ts.map +1 -1
- package/dist/composites/typed.d.ts +13 -0
- package/dist/composites/typed.d.ts.map +1 -0
- package/dist/core/typed.d.ts +22 -0
- package/dist/core/typed.d.ts.map +1 -1
- package/dist/integrity.json +3 -3
- package/dist/lint/rules/sqlpg005.d.ts +5 -0
- package/dist/lint/rules/sqlpg005.d.ts.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/op/activities/clickhouse-rebuild.d.ts +6 -0
- package/dist/op/activities/clickhouse-rebuild.d.ts.map +1 -1
- package/dist/op/activities/postgres-migration.d.ts +6 -0
- package/dist/op/activities/postgres-migration.d.ts.map +1 -1
- package/dist/op/activity-contracts.d.ts +30 -0
- package/dist/op/activity-contracts.d.ts.map +1 -1
- package/dist/postgres/entities.d.ts +12 -3
- package/dist/postgres/entities.d.ts.map +1 -1
- package/dist/rules/sqlpg005.ts +11 -3
- package/package.json +2 -2
- package/src/change-set.test.ts +246 -0
- package/src/change-set.ts +354 -0
- package/src/composites/audit-log-table.ts +11 -4
- package/src/composites/cdc-mirror.ts +11 -4
- package/src/composites/events-table.ts +8 -3
- package/src/composites/join-table.ts +10 -3
- package/src/composites/refreshed-view.ts +10 -3
- package/src/composites/replacing-table.ts +8 -3
- package/src/composites/rollup-view.ts +11 -4
- package/src/composites/sharded-table.ts +9 -4
- package/src/composites/soft-delete-table.ts +11 -4
- package/src/composites/tenant-table.ts +10 -3
- package/src/composites/typed.ts +15 -0
- package/src/core/typed.ts +40 -0
- package/src/lint/rules/sqlpg005.ts +11 -3
- package/src/lint/rules/typed-names.test.ts +12 -0
- package/src/op/activities/clickhouse-rebuild.ts +11 -1
- package/src/op/activities/postgres-migration.ts +11 -1
- package/src/op/activity-contracts.ts +6 -2
- package/src/postgres/entities.ts +13 -4
- package/src/typed-columns.e2e.test.ts +17 -0
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
|
|
12
12
|
import { Composite } from "@intentius/chant/composite";
|
|
13
13
|
import { index, table, type PostgresIndex, type PostgresSchema, type PostgresTable } from "../postgres/entities";
|
|
14
|
+
import type { PostgresMemberName } from "../core/typed";
|
|
15
|
+
import type { Instance, TypedComposite } from "./typed";
|
|
14
16
|
|
|
15
17
|
export interface TenantTableProps {
|
|
16
18
|
/** The table's name, unqualified. The index is `<name>_tenant_idx`. */
|
|
@@ -29,13 +31,18 @@ export interface TenantTableProps {
|
|
|
29
31
|
tenantType?: string;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
/** `Table` is the table's qualified name, when the call's props give it (chant#3787). */
|
|
35
|
+
export type TenantTableMembers<Table extends string = string> = {
|
|
36
|
+
table: PostgresTable<Table>;
|
|
34
37
|
index: PostgresIndex;
|
|
35
38
|
};
|
|
36
39
|
|
|
40
|
+
type TenantTableCall = <const N extends string, const S extends PostgresSchema | string = "public">(
|
|
41
|
+
props: TenantTableProps & { name: N; schema?: S },
|
|
42
|
+
) => Instance<TenantTableMembers<PostgresMemberName<S, N>>>;
|
|
43
|
+
|
|
37
44
|
/** A multi-tenant table with the tenant key first in its primary key and its index. */
|
|
38
|
-
export const TenantTable = Composite<TenantTableProps, TenantTableMembers
|
|
45
|
+
export const TenantTable = Composite<TenantTableProps, TenantTableMembers, TypedComposite<TenantTableProps, TenantTableMembers, TenantTableCall>>((props) => {
|
|
39
46
|
const schema = props.schema ?? "public";
|
|
40
47
|
const tenant = props.tenant ?? "tenant_id";
|
|
41
48
|
const rows = table`
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* chant#3787 — the call signature a composite is typed with, so a member's
|
|
3
|
+
* `.columns` are the generated `SqlSchema` entry for the name its props give
|
|
4
|
+
* it. `users.table.columns.emial` then fails tsc as `table<"app.users">`'s
|
|
5
|
+
* would. The definition is unchanged at run time: `Composite`'s third type
|
|
6
|
+
* argument carries this signature in front of the plain one.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { CompositeDefinition, CompositeFactoryMembers, CompositeInstance } from "@intentius/chant/composite";
|
|
10
|
+
|
|
11
|
+
/** What a typed composite call returns: the instance, with its members' types. */
|
|
12
|
+
export type Instance<M extends CompositeFactoryMembers> = CompositeInstance<M> & M;
|
|
13
|
+
|
|
14
|
+
/** A composite definition whose precise, generic `Call` signature is tried before the plain `(props: P) => M`. */
|
|
15
|
+
export type TypedComposite<P, M extends CompositeFactoryMembers, Call> = Call & CompositeDefinition<P, M>;
|
package/src/core/typed.ts
CHANGED
|
@@ -109,6 +109,46 @@ export type RowIn<Registry, T> = T extends string
|
|
|
109
109
|
? RowFromColumns<ColumnsIn<Registry, T>>
|
|
110
110
|
: T extends Named<infer N> ? RowFromColumns<ColumnsIn<Registry, N>> : never;
|
|
111
111
|
|
|
112
|
+
// ── Composite members ───────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
/** Whether `S` is a name the type system knows: a literal, not `string` and not {@link Unnamed}. */
|
|
115
|
+
type Known<S> = [S] extends [Unnamed] ? false : string extends S ? false : true;
|
|
116
|
+
|
|
117
|
+
/** A piece of a name a composite's props give: the literal, the name a {@link Named} entity carries, or {@link Unnamed}. */
|
|
118
|
+
type NamePiece<V> = [V] extends [string]
|
|
119
|
+
? Known<V> extends true ? V : Unnamed
|
|
120
|
+
: [V] extends [Named<infer N>] ? (Known<N> extends true ? N : Unnamed) : Unnamed;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* chant#3787 — the qualified name of a relation a Postgres composite declares
|
|
124
|
+
* as `${schema}.${name}${suffix}`: `schema` is the composite's `schema` prop (a
|
|
125
|
+
* string, or a schema whose tag was given its name, `schema<"app">`), `name`
|
|
126
|
+
* its `name` prop. Postgres folds both, as the build does. {@link Unnamed},
|
|
127
|
+
* so the columns stay unchecked, when either piece is not a known literal.
|
|
128
|
+
*/
|
|
129
|
+
export type PostgresMemberName<Schema, Name, Suffix extends string = ""> = [NamePiece<Schema>, NamePiece<Name>] extends [
|
|
130
|
+
infer S extends string,
|
|
131
|
+
infer N extends string,
|
|
132
|
+
]
|
|
133
|
+
? Known<S> extends true
|
|
134
|
+
? Known<N> extends true
|
|
135
|
+
? `${Lowercase<S>}.${Lowercase<N>}${Suffix}`
|
|
136
|
+
: Unnamed
|
|
137
|
+
: Unnamed
|
|
138
|
+
: Unnamed;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* chant#3787 — the name of a relation a ClickHouse composite declares,
|
|
142
|
+
* `${name}${suffix}`, where `name` is its `name` prop as written (`events` or
|
|
143
|
+
* `analytics.events`). ClickHouse does not fold names. {@link Unnamed} when
|
|
144
|
+
* the name is not a known literal.
|
|
145
|
+
*/
|
|
146
|
+
export type ClickHouseMemberName<Name, Suffix extends string = ""> = NamePiece<Name> extends infer N extends string
|
|
147
|
+
? Known<N> extends true
|
|
148
|
+
? `${N}${Suffix}`
|
|
149
|
+
: Unnamed
|
|
150
|
+
: Unnamed;
|
|
151
|
+
|
|
112
152
|
// ── The slot record ─────────────────────────────────────────────────────
|
|
113
153
|
|
|
114
154
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { LintContext, LintDiagnostic, LintRule } from "@intentius/chant/lint/rule";
|
|
2
2
|
import { parseStatements, SqlSyntaxError } from "../../postgres/parser";
|
|
3
3
|
import { findPostgresTemplates, postgresTokensOf } from "./postgres-templates";
|
|
4
|
-
import { declaredText, generatedNamesFor, nameAgrees, normalizedName, SQL_TYPES_FILE, typeArgumentName } from "./typed-names";
|
|
4
|
+
import { declaredText, generatedNamesFor, nameAgrees, normalizedName, splitQualified, SQL_TYPES_FILE, typeArgumentName } from "./typed-names";
|
|
5
5
|
|
|
6
6
|
const MODULE = "@intentius/chant-lexicon-sql/postgres";
|
|
7
7
|
|
|
@@ -19,6 +19,11 @@ const MODULE = "@intentius/chant-lexicon-sql/postgres";
|
|
|
19
19
|
* generated file, only the first check runs. A name missing from the file is
|
|
20
20
|
* a warning, since a table added since the last `chant sql types` is stale
|
|
21
21
|
* and `chant sql types --check` is what fails on it.
|
|
22
|
+
*
|
|
23
|
+
* chant#3787: `schema<"app">\`CREATE SCHEMA app\`` is read too, since a
|
|
24
|
+
* composite placed in the schema builds its members' qualified names from
|
|
25
|
+
* that argument. A schema is not in the generated file, so only the name
|
|
26
|
+
* comparison runs, and the argument is the schema's own name, one piece.
|
|
22
27
|
*/
|
|
23
28
|
export const sqlpg005: LintRule = {
|
|
24
29
|
id: "SQLPG005",
|
|
@@ -31,7 +36,7 @@ export const sqlpg005: LintRule = {
|
|
|
31
36
|
const out: LintDiagnostic[] = [];
|
|
32
37
|
let listed: Set<string> | undefined | null = null;
|
|
33
38
|
for (const found of findPostgresTemplates(source)) {
|
|
34
|
-
if (found.tag !== "table" && found.tag !== "view") continue;
|
|
39
|
+
if (found.tag !== "table" && found.tag !== "view" && found.tag !== "schema") continue;
|
|
35
40
|
const arg = typeArgumentName(found.node);
|
|
36
41
|
if (!arg) continue;
|
|
37
42
|
const at = (() => {
|
|
@@ -42,12 +47,14 @@ export const sqlpg005: LintRule = {
|
|
|
42
47
|
try {
|
|
43
48
|
const node = parseStatements(postgresTokensOf(found))[0];
|
|
44
49
|
if (node && (node.statement === "table" || node.statement === "view")) declared = node.name.pieces;
|
|
50
|
+
else if (node && node.statement === "schema" && found.tag === "schema") declared = node.name?.pieces;
|
|
45
51
|
} catch (err) {
|
|
46
52
|
if (err instanceof SqlSyntaxError) continue; // SQLPG001 reports it
|
|
47
53
|
throw err;
|
|
48
54
|
}
|
|
49
55
|
if (!declared) continue; // SQLPG001: the tag holds another statement
|
|
50
|
-
|
|
56
|
+
const agrees = found.tag === "schema" ? splitQualified(arg.name).length === 1 && nameAgrees(declared, arg.name) : nameAgrees(declared, arg.name);
|
|
57
|
+
if (!agrees) {
|
|
51
58
|
out.push({
|
|
52
59
|
ruleId: "SQLPG005",
|
|
53
60
|
severity: "error",
|
|
@@ -57,6 +64,7 @@ export const sqlpg005: LintRule = {
|
|
|
57
64
|
});
|
|
58
65
|
continue;
|
|
59
66
|
}
|
|
67
|
+
if (found.tag === "schema") continue;
|
|
60
68
|
if (listed === null) listed = generatedNamesFor(context.filePath, MODULE);
|
|
61
69
|
if (listed && !listed.has(normalizedName(arg.name))) {
|
|
62
70
|
out.push({
|
|
@@ -113,6 +113,18 @@ describe("SQLPG005: a typed Postgres tag's type argument", () => {
|
|
|
113
113
|
const code = `${PGX}export const o = table<"shop.orders">\`CREATE TABLE shop.orders (id bigint)\`;`;
|
|
114
114
|
expect(sqlpg005.check(ctx(code, file))).toEqual([]);
|
|
115
115
|
});
|
|
116
|
+
|
|
117
|
+
// chant#3787 — a composite builds its members' names from the schema's type argument.
|
|
118
|
+
test("a schema's type argument must be its name, and is not looked for in the generated file", () => {
|
|
119
|
+
const SCX = `import { schema } from "${PG}";\n`;
|
|
120
|
+
const file = project(PG, ["shop.orders"]);
|
|
121
|
+
expect(sqlpg005.check(ctx(`${SCX}export const s = schema<"shop">\`CREATE SCHEMA Shop\`;`, file))).toEqual([]);
|
|
122
|
+
const [d, ...rest] = sqlpg005.check(ctx(`${SCX}export const s = schema<"shp">\`CREATE SCHEMA shop\`;`, file));
|
|
123
|
+
expect(rest).toEqual([]);
|
|
124
|
+
expect(d).toMatchObject({ ruleId: "SQLPG005", severity: "error", line: 2 });
|
|
125
|
+
expect(sqlpg005.check(ctx(`${SCX}export const s = schema<"x.shop">\`CREATE SCHEMA shop\`;`))).toHaveLength(1);
|
|
126
|
+
expect(sqlpg005.check(ctx(`${SCX}export const s = schema\`CREATE SCHEMA shop\`;`))).toEqual([]);
|
|
127
|
+
});
|
|
116
128
|
});
|
|
117
129
|
|
|
118
130
|
describe("SQLCH004: a typed ClickHouse tag's type argument", () => {
|
|
@@ -34,6 +34,9 @@ import type { ClickHouseRebuildArgs } from "../../clickhouse/rebuild/op";
|
|
|
34
34
|
import { CLASSIFIER_RULES } from "../../clickhouse/plan/rules";
|
|
35
35
|
import { clusterSharding } from "../../clickhouse/rebuild/shards";
|
|
36
36
|
import { resolveMarker } from "./clickhouse-apply";
|
|
37
|
+
import { statementChange, type StatementChange } from "../../core/statements";
|
|
38
|
+
import { clickhouseRebuildPart } from "../../change-set";
|
|
39
|
+
import type { ChangeSetPart } from "@intentius/chant/change-set";
|
|
37
40
|
|
|
38
41
|
export type { ClickHouseRebuildArgs } from "../../clickhouse/rebuild/op";
|
|
39
42
|
|
|
@@ -104,6 +107,10 @@ export interface RebuildPlanResult {
|
|
|
104
107
|
planDigest?: string;
|
|
105
108
|
/** Each classified change, as `<rule> <field>: <before> -> <after>`. */
|
|
106
109
|
changes: string[];
|
|
110
|
+
/** The same changes, structured. */
|
|
111
|
+
classified: StatementChange[];
|
|
112
|
+
/** The plan as a change-set part (#3767), member the table: what `composeChangeSet` takes, and a gate's Cedar `context.plan`. */
|
|
113
|
+
part: ChangeSetPart;
|
|
107
114
|
summary: string;
|
|
108
115
|
}
|
|
109
116
|
|
|
@@ -125,7 +132,10 @@ async function plan(args: ClickHouseRebuildArgs, signal: AbortSignal | undefined
|
|
|
125
132
|
: `${o.names.key} holds its declaration; nothing to rebuild`;
|
|
126
133
|
run.log(`-- ${summary}`);
|
|
127
134
|
for (const c of changes) run.log(`-- ${c}`);
|
|
128
|
-
|
|
135
|
+
const classified = o.changes.map(statementChange);
|
|
136
|
+
const result = { state: o.state, table: o.names.key, ...(o.state === "rebuild" ? { planDigest: rebuildPlanDigest(o, run.dualWrite) } : {}), changes, classified };
|
|
137
|
+
const part = clickhouseRebuildPart({ member: o.names.key, plan: result, ...(args.environment !== undefined ? { environment: args.environment } : {}) });
|
|
138
|
+
return { ...result, part, summary };
|
|
129
139
|
}
|
|
130
140
|
|
|
131
141
|
export async function clickhouseRebuildCreate(args: ClickHouseRebuildArgs, signal?: AbortSignal, deps: ClickHouseRebuildDeps = {}): Promise<CreateResult> {
|
|
@@ -39,6 +39,9 @@ import {
|
|
|
39
39
|
import { DEFAULT_REPLICATION_LAG } from "../../postgres/migrate/replication";
|
|
40
40
|
import type { PostgresMigrationArgs } from "../../postgres/migrate/op";
|
|
41
41
|
import { buildMajor } from "./postgres-apply";
|
|
42
|
+
import { statementChange, type StatementChange } from "../../core/statements";
|
|
43
|
+
import { postgresMigrationPart } from "../../change-set";
|
|
44
|
+
import type { ChangeSetPart } from "@intentius/chant/change-set";
|
|
42
45
|
|
|
43
46
|
export type { PostgresMigrationArgs } from "../../postgres/migrate/op";
|
|
44
47
|
|
|
@@ -137,6 +140,10 @@ export interface MigrationPlanResult {
|
|
|
137
140
|
planDigest?: string;
|
|
138
141
|
/** Each classified change, as `<rule> <field>: <before> -> <after>`. */
|
|
139
142
|
changes: string[];
|
|
143
|
+
/** The same changes, structured. */
|
|
144
|
+
classified: StatementChange[];
|
|
145
|
+
/** The plan as a change-set part (#3767), member the migration (`schema.table.column`): what `composeChangeSet` takes, and a gate's Cedar `context.plan`. */
|
|
146
|
+
part: ChangeSetPart;
|
|
140
147
|
summary: string;
|
|
141
148
|
}
|
|
142
149
|
|
|
@@ -164,7 +171,10 @@ export async function postgresMigrationPlan(args: PostgresMigrationArgs, signal?
|
|
|
164
171
|
: `${n.key} holds its declaration; nothing to migrate`;
|
|
165
172
|
run.log(`-- ${summary}`);
|
|
166
173
|
for (const c of changes) run.log(`-- ${c}`);
|
|
167
|
-
|
|
174
|
+
const classified = o.changes.map(statementChange);
|
|
175
|
+
const result = { state: o.state, migration: n.key, change: n.change, ...(o.state === "migrate" ? { planDigest: migrationPlanDigest(o) } : {}), changes, classified };
|
|
176
|
+
const part = postgresMigrationPart({ member: n.key, plan: result, ...(args.environment !== undefined ? { environment: args.environment } : {}) });
|
|
177
|
+
return { ...result, part, summary };
|
|
168
178
|
});
|
|
169
179
|
}
|
|
170
180
|
|
|
@@ -39,12 +39,16 @@ const rebuildArgs = z.strictObject({
|
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
const state = z.enum(["rebuild", "swapped", "done"]);
|
|
42
|
+
|
|
43
|
+
/** A plan step's structured changes and its change-set part (#3767), as `../change-set.ts` makes it. */
|
|
44
|
+
const classified = z.array(z.object({ rule: z.string(), class: z.string(), field: z.string() }).passthrough());
|
|
45
|
+
const part = z.object({ member: z.object({ member: z.string(), planner: z.literal("sql"), planDigest: z.string().nullable() }).passthrough(), entries: z.array(z.unknown()) });
|
|
42
46
|
const tableEntity = { entities: ["table"] };
|
|
43
47
|
|
|
44
48
|
export const clickhouseRebuildPlanContract = activityContract(
|
|
45
49
|
"clickhouseRebuildPlan",
|
|
46
50
|
rebuildArgs,
|
|
47
|
-
z.object({ state, table: z.string(), planDigest: z.string().optional(), changes: z.array(z.string()), summary: z.string() }),
|
|
51
|
+
z.object({ state, table: z.string(), planDigest: z.string().optional(), changes: z.array(z.string()), classified, part, summary: z.string() }),
|
|
48
52
|
tableEntity,
|
|
49
53
|
);
|
|
50
54
|
|
|
@@ -136,7 +140,7 @@ const migrationState = z.enum(["migrate", "switched", "done"]);
|
|
|
136
140
|
export const postgresMigrationPlanContract = activityContract(
|
|
137
141
|
"postgresMigrationPlan",
|
|
138
142
|
migrationArgs,
|
|
139
|
-
z.object({ state: migrationState, migration: z.string(), change: z.enum(["rename", "type"]), planDigest: z.string().optional(), changes: z.array(z.string()), summary: z.string() }),
|
|
143
|
+
z.object({ state: migrationState, migration: z.string(), change: z.enum(["rename", "type"]), planDigest: z.string().optional(), changes: z.array(z.string()), classified, part, summary: z.string() }),
|
|
140
144
|
tableEntity,
|
|
141
145
|
);
|
|
142
146
|
|
package/src/postgres/entities.ts
CHANGED
|
@@ -434,7 +434,12 @@ export abstract class PostgresObject extends SqlObject {
|
|
|
434
434
|
declare readonly sqlName: string;
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
-
|
|
437
|
+
/**
|
|
438
|
+
* A schema. `N` is its name when its tag was given one
|
|
439
|
+
* (`` schema<"shop">`CREATE SCHEMA shop` ``), which is how a composite placed
|
|
440
|
+
* in it knows its members' qualified names (chant#3787).
|
|
441
|
+
*/
|
|
442
|
+
export interface PostgresSchema<N extends string = string> extends PostgresObject, Named<N> {
|
|
438
443
|
readonly entityType: "Postgres::Schema";
|
|
439
444
|
readonly props: SchemaProps;
|
|
440
445
|
}
|
|
@@ -1601,9 +1606,13 @@ function build(tag: PostgresTag, strings: TemplateStringsArray | readonly string
|
|
|
1601
1606
|
}
|
|
1602
1607
|
}
|
|
1603
1608
|
|
|
1604
|
-
/**
|
|
1605
|
-
|
|
1606
|
-
|
|
1609
|
+
/**
|
|
1610
|
+
* `` schema`CREATE SCHEMA ...` ``: one Postgres schema, parsed at build time.
|
|
1611
|
+
* Given its name as the type argument (`` schema<"shop">`...` ``), a composite
|
|
1612
|
+
* placed in it types its members' columns (chant#3787).
|
|
1613
|
+
*/
|
|
1614
|
+
export function schema<const N extends string = Unnamed>(strings: TemplateStringsArray, ...values: unknown[]): PostgresSchema<N> {
|
|
1615
|
+
return build("schema", strings, values) as PostgresSchema<N>;
|
|
1607
1616
|
}
|
|
1608
1617
|
|
|
1609
1618
|
/**
|
|
@@ -89,4 +89,21 @@ describe("chant sql types, through tsc", () => {
|
|
|
89
89
|
expect.stringMatching(/^src\/by-country\.ts\(7\): TS2345 Argument of type 'Column<"Nullable\(String\)", .*' is not assignable to parameter of type 'NotNullColumn'/),
|
|
90
90
|
]);
|
|
91
91
|
}, 120_000);
|
|
92
|
+
|
|
93
|
+
// chant#3787 — a composite's members are typed from the names its props give
|
|
94
|
+
// them: `schema<"app">` or a schema by name, and the `name` prop, Postgres
|
|
95
|
+
// folding both. A name or schema the type system cannot read stays unchecked.
|
|
96
|
+
test("Postgres composites: a misspelled column on a member fails; every member's row is its generated entry's", async () => {
|
|
97
|
+
const dir = join(fixtures, "postgres-composites");
|
|
98
|
+
expect(await rendered(dir)).toBe(readFileSync(join(dir, "src", SQL_TYPES_FILE), "utf8"));
|
|
99
|
+
expect(tscErrors(dir)).toEqual([
|
|
100
|
+
expect.stringMatching(/^checks\/misspelled\.ts\(4\): TS2551 Property 'emial' does not exist .* Did you mean 'email'\?$/),
|
|
101
|
+
]);
|
|
102
|
+
}, 120_000);
|
|
103
|
+
|
|
104
|
+
test("ClickHouse composites: a misspelled column on a member fails; every member's row is its generated entry's", async () => {
|
|
105
|
+
const dir = join(fixtures, "clickhouse-composites");
|
|
106
|
+
expect(await rendered(dir)).toBe(readFileSync(join(dir, "src", SQL_TYPES_FILE), "utf8"));
|
|
107
|
+
expect(tscErrors(dir)).toEqual([expect.stringMatching(/^checks\/rows\.ts\(12\): TS2339 Property 'knid' does not exist on type/)]);
|
|
108
|
+
}, 120_000);
|
|
92
109
|
});
|