@vertz/db 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/README.md +923 -0
- package/dist/diagnostic/index.d.ts +41 -0
- package/dist/diagnostic/index.js +10 -0
- package/dist/index.d.ts +1346 -0
- package/dist/index.js +2010 -0
- package/dist/internals.d.ts +223 -0
- package/dist/internals.js +25 -0
- package/dist/plugin/index.d.ts +66 -0
- package/dist/plugin/index.js +66 -0
- package/dist/shared/chunk-3f2grpak.js +428 -0
- package/dist/shared/chunk-hrfdj0rr.js +13 -0
- package/dist/shared/chunk-wj026daz.js +86 -0
- package/dist/shared/chunk-xp022dyp.js +296 -0
- package/dist/sql/index.d.ts +213 -0
- package/dist/sql/index.js +64 -0
- package/package.json +72 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
interface JsonbValidator<T> {
|
|
2
|
+
parse(value: unknown): T;
|
|
3
|
+
}
|
|
4
|
+
interface ColumnMetadata {
|
|
5
|
+
readonly sqlType: string;
|
|
6
|
+
readonly primary: boolean;
|
|
7
|
+
readonly unique: boolean;
|
|
8
|
+
readonly nullable: boolean;
|
|
9
|
+
readonly hasDefault: boolean;
|
|
10
|
+
readonly sensitive: boolean;
|
|
11
|
+
readonly hidden: boolean;
|
|
12
|
+
readonly isTenant: boolean;
|
|
13
|
+
readonly references: {
|
|
14
|
+
readonly table: string;
|
|
15
|
+
readonly column: string;
|
|
16
|
+
} | null;
|
|
17
|
+
readonly check: string | null;
|
|
18
|
+
readonly defaultValue?: unknown;
|
|
19
|
+
readonly format?: string;
|
|
20
|
+
readonly length?: number;
|
|
21
|
+
readonly precision?: number;
|
|
22
|
+
readonly scale?: number;
|
|
23
|
+
readonly enumName?: string;
|
|
24
|
+
readonly enumValues?: readonly string[];
|
|
25
|
+
readonly validator?: JsonbValidator<unknown>;
|
|
26
|
+
}
|
|
27
|
+
/** Phantom symbol to carry the TypeScript type without a runtime value. */
|
|
28
|
+
declare const PhantomType: unique symbol;
|
|
29
|
+
interface ColumnBuilder<
|
|
30
|
+
TType,
|
|
31
|
+
TMeta extends ColumnMetadata = ColumnMetadata
|
|
32
|
+
> {
|
|
33
|
+
/** Phantom field -- only exists at the type level for inference. Do not access at runtime. */
|
|
34
|
+
readonly [PhantomType]: TType;
|
|
35
|
+
readonly _meta: TMeta;
|
|
36
|
+
primary(): ColumnBuilder<TType, Omit<TMeta, "primary" | "hasDefault"> & {
|
|
37
|
+
readonly primary: true;
|
|
38
|
+
readonly hasDefault: true;
|
|
39
|
+
}>;
|
|
40
|
+
unique(): ColumnBuilder<TType, Omit<TMeta, "unique"> & {
|
|
41
|
+
readonly unique: true;
|
|
42
|
+
}>;
|
|
43
|
+
nullable(): ColumnBuilder<TType | null, Omit<TMeta, "nullable"> & {
|
|
44
|
+
readonly nullable: true;
|
|
45
|
+
}>;
|
|
46
|
+
default(value: TType | "now"): ColumnBuilder<TType, Omit<TMeta, "hasDefault"> & {
|
|
47
|
+
readonly hasDefault: true;
|
|
48
|
+
readonly defaultValue: TType | "now";
|
|
49
|
+
}>;
|
|
50
|
+
sensitive(): ColumnBuilder<TType, Omit<TMeta, "sensitive"> & {
|
|
51
|
+
readonly sensitive: true;
|
|
52
|
+
}>;
|
|
53
|
+
hidden(): ColumnBuilder<TType, Omit<TMeta, "hidden"> & {
|
|
54
|
+
readonly hidden: true;
|
|
55
|
+
}>;
|
|
56
|
+
check(sql: string): ColumnBuilder<TType, Omit<TMeta, "check"> & {
|
|
57
|
+
readonly check: string;
|
|
58
|
+
}>;
|
|
59
|
+
references(table: string, column?: string): ColumnBuilder<TType, Omit<TMeta, "references"> & {
|
|
60
|
+
readonly references: {
|
|
61
|
+
readonly table: string;
|
|
62
|
+
readonly column: string;
|
|
63
|
+
};
|
|
64
|
+
}>;
|
|
65
|
+
}
|
|
66
|
+
type InferColumnType<C> = C extends ColumnBuilder<infer T, ColumnMetadata> ? T : never;
|
|
67
|
+
interface IndexDef {
|
|
68
|
+
readonly columns: readonly string[];
|
|
69
|
+
}
|
|
70
|
+
/** A record of column builders -- the shape passed to d.table(). */
|
|
71
|
+
type ColumnRecord = Record<string, ColumnBuilder<unknown, ColumnMetadata>>;
|
|
72
|
+
/** Extract the TypeScript type from every column in a record. */
|
|
73
|
+
type InferColumns<T extends ColumnRecord> = { [K in keyof T] : InferColumnType<T[K]> };
|
|
74
|
+
/** Keys of columns where a given metadata flag is `true`. */
|
|
75
|
+
type ColumnKeysWhere<
|
|
76
|
+
T extends ColumnRecord,
|
|
77
|
+
Flag extends keyof ColumnMetadata
|
|
78
|
+
> = { [K in keyof T] : T[K] extends ColumnBuilder<unknown, infer M> ? M extends Record<Flag, true> ? K : never : never }[keyof T];
|
|
79
|
+
/** Keys of columns where a given metadata flag is NOT `true` (i.e., false). */
|
|
80
|
+
type ColumnKeysWhereNot<
|
|
81
|
+
T extends ColumnRecord,
|
|
82
|
+
Flag extends keyof ColumnMetadata
|
|
83
|
+
> = { [K in keyof T] : T[K] extends ColumnBuilder<unknown, infer M> ? M extends Record<Flag, true> ? never : K : never }[keyof T];
|
|
84
|
+
/**
|
|
85
|
+
* $infer -- default SELECT type.
|
|
86
|
+
* Excludes hidden columns. Includes everything else (including sensitive).
|
|
87
|
+
*/
|
|
88
|
+
type Infer<T extends ColumnRecord> = { [K in ColumnKeysWhereNot<T, "hidden">] : InferColumnType<T[K]> };
|
|
89
|
+
/**
|
|
90
|
+
* $infer_all -- all columns including hidden.
|
|
91
|
+
*/
|
|
92
|
+
type InferAll<T extends ColumnRecord> = InferColumns<T>;
|
|
93
|
+
/**
|
|
94
|
+
* $insert -- write type. ALL columns included (visibility is read-side only).
|
|
95
|
+
* Columns with hasDefault: true become optional.
|
|
96
|
+
*/
|
|
97
|
+
type Insert<T extends ColumnRecord> = { [K in ColumnKeysWhereNot<T, "hasDefault">] : InferColumnType<T[K]> } & { [K in ColumnKeysWhere<T, "hasDefault">]? : InferColumnType<T[K]> };
|
|
98
|
+
/**
|
|
99
|
+
* $update -- write type. ALL non-primary-key columns, all optional.
|
|
100
|
+
* Primary key excluded (you don't update a PK).
|
|
101
|
+
*/
|
|
102
|
+
type Update<T extends ColumnRecord> = { [K in ColumnKeysWhereNot<T, "primary">]? : InferColumnType<T[K]> };
|
|
103
|
+
/**
|
|
104
|
+
* $not_sensitive -- excludes columns marked .sensitive() OR .hidden().
|
|
105
|
+
* (hidden implies sensitive for read purposes)
|
|
106
|
+
*/
|
|
107
|
+
type NotSensitive<T extends ColumnRecord> = { [K in ColumnKeysWhereNot<T, "sensitive"> & ColumnKeysWhereNot<T, "hidden"> & keyof T] : InferColumnType<T[K]> };
|
|
108
|
+
/**
|
|
109
|
+
* $not_hidden -- excludes columns marked .hidden().
|
|
110
|
+
* Same as $infer (excludes hidden, keeps sensitive).
|
|
111
|
+
*/
|
|
112
|
+
type NotHidden<T extends ColumnRecord> = { [K in ColumnKeysWhereNot<T, "hidden">] : InferColumnType<T[K]> };
|
|
113
|
+
interface TableDef<TColumns extends ColumnRecord = ColumnRecord> {
|
|
114
|
+
readonly _name: string;
|
|
115
|
+
readonly _columns: TColumns;
|
|
116
|
+
readonly _indexes: readonly IndexDef[];
|
|
117
|
+
readonly _shared: boolean;
|
|
118
|
+
/** Default SELECT type -- excludes hidden columns. */
|
|
119
|
+
readonly $infer: Infer<TColumns>;
|
|
120
|
+
/** All columns including hidden. */
|
|
121
|
+
readonly $infer_all: InferAll<TColumns>;
|
|
122
|
+
/** Insert type -- defaulted columns optional. ALL columns included. */
|
|
123
|
+
readonly $insert: Insert<TColumns>;
|
|
124
|
+
/** Update type -- all non-PK columns optional. ALL columns included. */
|
|
125
|
+
readonly $update: Update<TColumns>;
|
|
126
|
+
/** Excludes sensitive and hidden columns. */
|
|
127
|
+
readonly $not_sensitive: NotSensitive<TColumns>;
|
|
128
|
+
/** Excludes hidden columns. */
|
|
129
|
+
readonly $not_hidden: NotHidden<TColumns>;
|
|
130
|
+
/** Mark this table as shared / cross-tenant. */
|
|
131
|
+
shared(): TableDef<TColumns>;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Query executor — wraps raw SQL execution with error mapping.
|
|
135
|
+
*
|
|
136
|
+
* Takes a query function (from the database driver) and wraps it to:
|
|
137
|
+
* 1. Execute parameterized SQL
|
|
138
|
+
* 2. Map PG errors to typed DbError subclasses
|
|
139
|
+
* 3. Return typed QueryResult
|
|
140
|
+
*/
|
|
141
|
+
interface ExecutorResult<T> {
|
|
142
|
+
readonly rows: readonly T[];
|
|
143
|
+
readonly rowCount: number;
|
|
144
|
+
}
|
|
145
|
+
type QueryFn = <T>(sql: string, params: readonly unknown[]) => Promise<ExecutorResult<T>>;
|
|
146
|
+
/**
|
|
147
|
+
* Execute a SQL query, mapping PG errors to typed DbError.
|
|
148
|
+
*/
|
|
149
|
+
declare function executeQuery<T>(queryFn: QueryFn, sql: string, params: readonly unknown[]): Promise<ExecutorResult<T>>;
|
|
150
|
+
interface CountArgs {
|
|
151
|
+
readonly where?: Record<string, unknown>;
|
|
152
|
+
}
|
|
153
|
+
interface AggregateArgs {
|
|
154
|
+
readonly where?: Record<string, unknown>;
|
|
155
|
+
readonly _avg?: Record<string, true>;
|
|
156
|
+
readonly _sum?: Record<string, true>;
|
|
157
|
+
readonly _min?: Record<string, true>;
|
|
158
|
+
readonly _max?: Record<string, true>;
|
|
159
|
+
readonly _count?: true | Record<string, true>;
|
|
160
|
+
}
|
|
161
|
+
interface GroupByArgs {
|
|
162
|
+
readonly by: readonly string[];
|
|
163
|
+
readonly where?: Record<string, unknown>;
|
|
164
|
+
readonly _count?: true | Record<string, true>;
|
|
165
|
+
readonly _avg?: Record<string, true>;
|
|
166
|
+
readonly _sum?: Record<string, true>;
|
|
167
|
+
readonly _min?: Record<string, true>;
|
|
168
|
+
readonly _max?: Record<string, true>;
|
|
169
|
+
readonly orderBy?: Record<string, "asc" | "desc">;
|
|
170
|
+
readonly limit?: number;
|
|
171
|
+
readonly offset?: number;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Get all column names from a table definition.
|
|
175
|
+
*/
|
|
176
|
+
declare function getColumnNames(table: TableDef<ColumnRecord>): string[];
|
|
177
|
+
/**
|
|
178
|
+
* Get column names excluding hidden columns (default SELECT behavior).
|
|
179
|
+
*/
|
|
180
|
+
declare function getDefaultColumns(table: TableDef<ColumnRecord>): string[];
|
|
181
|
+
/**
|
|
182
|
+
* Get column names excluding sensitive AND hidden columns.
|
|
183
|
+
*/
|
|
184
|
+
declare function getNotSensitiveColumns(table: TableDef<ColumnRecord>): string[];
|
|
185
|
+
/**
|
|
186
|
+
* Get column names excluding hidden columns.
|
|
187
|
+
*/
|
|
188
|
+
declare function getNotHiddenColumns(table: TableDef<ColumnRecord>): string[];
|
|
189
|
+
/**
|
|
190
|
+
* Resolve a select option to a list of column names.
|
|
191
|
+
*
|
|
192
|
+
* - undefined -> default columns (exclude hidden)
|
|
193
|
+
* - { not: 'sensitive' } -> exclude sensitive + hidden
|
|
194
|
+
* - { not: 'hidden' } -> exclude hidden
|
|
195
|
+
* - { id: true, name: true } -> explicit pick
|
|
196
|
+
*/
|
|
197
|
+
declare function resolveSelectColumns(table: TableDef<ColumnRecord>, select?: Record<string, unknown>): string[];
|
|
198
|
+
/**
|
|
199
|
+
* Get timestamp column names that support the 'now' sentinel.
|
|
200
|
+
*/
|
|
201
|
+
declare function getTimestampColumns(table: TableDef<ColumnRecord>): string[];
|
|
202
|
+
/**
|
|
203
|
+
* Get the primary key column name(s) for a table.
|
|
204
|
+
*/
|
|
205
|
+
declare function getPrimaryKeyColumns(table: TableDef<ColumnRecord>): string[];
|
|
206
|
+
/**
|
|
207
|
+
* Row mapper — converts snake_case PostgreSQL row keys to camelCase.
|
|
208
|
+
*
|
|
209
|
+
* Used by query builder methods to normalize result rows from PG.
|
|
210
|
+
* Handles nested objects and arrays (for JSONB columns, these are
|
|
211
|
+
* preserved as-is since JSONB key casing is user-defined).
|
|
212
|
+
*/
|
|
213
|
+
/**
|
|
214
|
+
* Convert all top-level keys of a row from snake_case to camelCase.
|
|
215
|
+
* JSONB columns (objects/arrays) are not deeply transformed — only
|
|
216
|
+
* top-level column keys are mapped.
|
|
217
|
+
*/
|
|
218
|
+
declare function mapRow<T>(row: Record<string, unknown>): T;
|
|
219
|
+
/**
|
|
220
|
+
* Convert an array of rows from snake_case to camelCase.
|
|
221
|
+
*/
|
|
222
|
+
declare function mapRows<T>(rows: readonly Record<string, unknown>[]): T[];
|
|
223
|
+
export { resolveSelectColumns, mapRows, mapRow, getTimestampColumns, getPrimaryKeyColumns, getNotSensitiveColumns, getNotHiddenColumns, getDefaultColumns, getColumnNames, executeQuery, QueryFn, GroupByArgs, ExecutorResult, CountArgs, AggregateArgs };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
executeQuery,
|
|
3
|
+
getColumnNames,
|
|
4
|
+
getDefaultColumns,
|
|
5
|
+
getNotHiddenColumns,
|
|
6
|
+
getNotSensitiveColumns,
|
|
7
|
+
getPrimaryKeyColumns,
|
|
8
|
+
getTimestampColumns,
|
|
9
|
+
mapRow,
|
|
10
|
+
mapRows,
|
|
11
|
+
resolveSelectColumns
|
|
12
|
+
} from "./shared/chunk-xp022dyp.js";
|
|
13
|
+
import"./shared/chunk-hrfdj0rr.js";
|
|
14
|
+
export {
|
|
15
|
+
resolveSelectColumns,
|
|
16
|
+
mapRows,
|
|
17
|
+
mapRow,
|
|
18
|
+
getTimestampColumns,
|
|
19
|
+
getPrimaryKeyColumns,
|
|
20
|
+
getNotSensitiveColumns,
|
|
21
|
+
getNotHiddenColumns,
|
|
22
|
+
getDefaultColumns,
|
|
23
|
+
getColumnNames,
|
|
24
|
+
executeQuery
|
|
25
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mutation event emitted by the event bus.
|
|
3
|
+
*/
|
|
4
|
+
interface MutationEvent {
|
|
5
|
+
type: "create" | "update" | "delete";
|
|
6
|
+
table: string;
|
|
7
|
+
data: unknown;
|
|
8
|
+
}
|
|
9
|
+
type EventHandler = (event: MutationEvent) => void;
|
|
10
|
+
interface EventBus {
|
|
11
|
+
emit(event: MutationEvent): void;
|
|
12
|
+
on(handler: EventHandler): void;
|
|
13
|
+
off(handler: EventHandler): void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Create a synchronous event bus for mutation events.
|
|
17
|
+
*/
|
|
18
|
+
declare function createEventBus(): EventBus;
|
|
19
|
+
/**
|
|
20
|
+
* The shape of a query for fingerprinting.
|
|
21
|
+
* Only the structural keys matter — not the parameter values.
|
|
22
|
+
*/
|
|
23
|
+
interface QueryShape {
|
|
24
|
+
table: string;
|
|
25
|
+
operation: string;
|
|
26
|
+
where?: Record<string, unknown>;
|
|
27
|
+
select?: Record<string, unknown>;
|
|
28
|
+
include?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Produce a deterministic fingerprint for a query shape.
|
|
32
|
+
*
|
|
33
|
+
* Same shape (table + operation + where keys + select keys + include keys)
|
|
34
|
+
* always yields the same fingerprint, regardless of parameter values.
|
|
35
|
+
*/
|
|
36
|
+
declare function fingerprint(query: QueryShape): string;
|
|
37
|
+
/**
|
|
38
|
+
* Context passed to plugin hooks.
|
|
39
|
+
*/
|
|
40
|
+
interface QueryContext {
|
|
41
|
+
table: string;
|
|
42
|
+
operation: string;
|
|
43
|
+
args: Record<string, unknown>;
|
|
44
|
+
fingerprint: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Plugin interface for the database.
|
|
48
|
+
*
|
|
49
|
+
* @experimental
|
|
50
|
+
*/
|
|
51
|
+
interface DbPlugin {
|
|
52
|
+
name: string;
|
|
53
|
+
beforeQuery?(context: QueryContext): QueryContext | undefined;
|
|
54
|
+
afterQuery?(context: QueryContext, result: unknown): unknown;
|
|
55
|
+
}
|
|
56
|
+
interface PluginRunner {
|
|
57
|
+
runBeforeQuery(context: QueryContext): QueryContext | undefined;
|
|
58
|
+
runAfterQuery(context: QueryContext, result: unknown): unknown;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Create a plugin runner that executes hooks across a set of plugins.
|
|
62
|
+
*
|
|
63
|
+
* @experimental
|
|
64
|
+
*/
|
|
65
|
+
declare function createPluginRunner(plugins: DbPlugin[]): PluginRunner;
|
|
66
|
+
export { fingerprint, createPluginRunner, createEventBus, QueryShape, QueryContext, PluginRunner, MutationEvent, EventHandler, EventBus, DbPlugin };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// src/plugin/event-bus.ts
|
|
2
|
+
function createEventBus() {
|
|
3
|
+
const handlers = new Set;
|
|
4
|
+
return {
|
|
5
|
+
emit(event) {
|
|
6
|
+
for (const handler of handlers) {
|
|
7
|
+
handler(event);
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
on(handler) {
|
|
11
|
+
handlers.add(handler);
|
|
12
|
+
},
|
|
13
|
+
off(handler) {
|
|
14
|
+
handlers.delete(handler);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
// src/plugin/fingerprint.ts
|
|
19
|
+
import { createHash } from "node:crypto";
|
|
20
|
+
function fingerprint(query) {
|
|
21
|
+
const parts = [
|
|
22
|
+
query.table,
|
|
23
|
+
query.operation,
|
|
24
|
+
`w:${sortedKeys(query.where)}`,
|
|
25
|
+
`s:${sortedKeys(query.select)}`,
|
|
26
|
+
`i:${sortedKeys(query.include)}`
|
|
27
|
+
];
|
|
28
|
+
const input = parts.join("|");
|
|
29
|
+
return createHash("sha256").update(input).digest("hex").slice(0, 16);
|
|
30
|
+
}
|
|
31
|
+
function sortedKeys(obj) {
|
|
32
|
+
if (!obj)
|
|
33
|
+
return "";
|
|
34
|
+
return Object.keys(obj).sort().join(",");
|
|
35
|
+
}
|
|
36
|
+
// src/plugin/plugin-runner.ts
|
|
37
|
+
function createPluginRunner(plugins) {
|
|
38
|
+
return {
|
|
39
|
+
runBeforeQuery(context) {
|
|
40
|
+
for (const plugin of plugins) {
|
|
41
|
+
if (plugin.beforeQuery) {
|
|
42
|
+
const result = plugin.beforeQuery(context);
|
|
43
|
+
if (result !== undefined) {
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
},
|
|
50
|
+
runAfterQuery(context, result) {
|
|
51
|
+
let current = result;
|
|
52
|
+
for (const plugin of plugins) {
|
|
53
|
+
if (plugin.afterQuery) {
|
|
54
|
+
const pluginResult = plugin.afterQuery(context, current);
|
|
55
|
+
current = pluginResult !== undefined ? pluginResult : current;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return current;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export {
|
|
63
|
+
fingerprint,
|
|
64
|
+
createPluginRunner,
|
|
65
|
+
createEventBus
|
|
66
|
+
};
|