@vertz/db 0.2.0 → 0.2.1

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.
@@ -7,8 +7,9 @@ interface ColumnMetadata {
7
7
  readonly unique: boolean;
8
8
  readonly nullable: boolean;
9
9
  readonly hasDefault: boolean;
10
- readonly sensitive: boolean;
11
- readonly hidden: boolean;
10
+ readonly _annotations: Record<string, true>;
11
+ readonly isReadOnly: boolean;
12
+ readonly isAutoUpdate: boolean;
12
13
  readonly isTenant: boolean;
13
14
  readonly references: {
14
15
  readonly table: string;
@@ -23,6 +24,7 @@ interface ColumnMetadata {
23
24
  readonly enumName?: string;
24
25
  readonly enumValues?: readonly string[];
25
26
  readonly validator?: JsonbValidator<unknown>;
27
+ readonly generate?: "cuid" | "uuid" | "nanoid";
26
28
  }
27
29
  /** Phantom symbol to carry the TypeScript type without a runtime value. */
28
30
  declare const PhantomType: unique symbol;
@@ -33,9 +35,12 @@ interface ColumnBuilder<
33
35
  /** Phantom field -- only exists at the type level for inference. Do not access at runtime. */
34
36
  readonly [PhantomType]: TType;
35
37
  readonly _meta: TMeta;
36
- primary(): ColumnBuilder<TType, Omit<TMeta, "primary" | "hasDefault"> & {
38
+ primary(options?: {
39
+ generate?: "cuid" | "uuid" | "nanoid";
40
+ }): ColumnBuilder<TType, Omit<TMeta, "primary" | "hasDefault" | "generate"> & {
37
41
  readonly primary: true;
38
42
  readonly hasDefault: true;
43
+ readonly generate?: "cuid" | "uuid" | "nanoid";
39
44
  }>;
40
45
  unique(): ColumnBuilder<TType, Omit<TMeta, "unique"> & {
41
46
  readonly unique: true;
@@ -47,11 +52,15 @@ interface ColumnBuilder<
47
52
  readonly hasDefault: true;
48
53
  readonly defaultValue: TType | "now";
49
54
  }>;
50
- sensitive(): ColumnBuilder<TType, Omit<TMeta, "sensitive"> & {
51
- readonly sensitive: true;
55
+ is<TFlag extends string>(flag: TFlag): ColumnBuilder<TType, Omit<TMeta, "_annotations"> & {
56
+ readonly _annotations: TMeta["_annotations"] & { readonly [K in TFlag] : true };
52
57
  }>;
53
- hidden(): ColumnBuilder<TType, Omit<TMeta, "hidden"> & {
54
- readonly hidden: true;
58
+ readOnly(): ColumnBuilder<TType, Omit<TMeta, "isReadOnly"> & {
59
+ readonly isReadOnly: true;
60
+ }>;
61
+ autoUpdate(): ColumnBuilder<TType, Omit<TMeta, "isAutoUpdate" | "isReadOnly"> & {
62
+ readonly isAutoUpdate: true;
63
+ readonly isReadOnly: true;
55
64
  }>;
56
65
  check(sql: string): ColumnBuilder<TType, Omit<TMeta, "check"> & {
57
66
  readonly check: string;
@@ -66,26 +75,33 @@ interface ColumnBuilder<
66
75
  type InferColumnType<C> = C extends ColumnBuilder<infer T, ColumnMetadata> ? T : never;
67
76
  interface IndexDef {
68
77
  readonly columns: readonly string[];
78
+ readonly name?: string;
79
+ readonly unique?: boolean;
69
80
  }
70
81
  /** A record of column builders -- the shape passed to d.table(). */
71
82
  type ColumnRecord = Record<string, ColumnBuilder<unknown, ColumnMetadata>>;
72
83
  /** Extract the TypeScript type from every column in a record. */
73
84
  type InferColumns<T extends ColumnRecord> = { [K in keyof T] : InferColumnType<T[K]> };
74
- /** Keys of columns where a given metadata flag is `true`. */
85
+ /** Keys of columns where a given metadata property is `true`. */
75
86
  type ColumnKeysWhere<
76
87
  T extends ColumnRecord,
77
88
  Flag extends keyof ColumnMetadata
78
89
  > = { [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). */
90
+ /** Keys of columns where a given metadata property is NOT `true` (i.e., false). */
80
91
  type ColumnKeysWhereNot<
81
92
  T extends ColumnRecord,
82
93
  Flag extends keyof ColumnMetadata
83
94
  > = { [K in keyof T] : T[K] extends ColumnBuilder<unknown, infer M> ? M extends Record<Flag, true> ? never : K : never }[keyof T];
95
+ /** Keys of columns that do NOT have ANY of the specified annotations in `_annotations`. */
96
+ type ColumnKeysWithoutAnyAnnotation<
97
+ T extends ColumnRecord,
98
+ Annotations extends string
99
+ > = { [K in keyof T] : T[K] extends ColumnBuilder<unknown, infer M> ? M["_annotations"] extends Record<Annotations, true> ? never : K : never }[keyof T];
84
100
  /**
85
101
  * $infer -- default SELECT type.
86
- * Excludes hidden columns. Includes everything else (including sensitive).
102
+ * Excludes columns annotated 'hidden'. Includes everything else.
87
103
  */
88
- type Infer<T extends ColumnRecord> = { [K in ColumnKeysWhereNot<T, "hidden">] : InferColumnType<T[K]> };
104
+ type Infer<T extends ColumnRecord> = { [K in ColumnKeysWithoutAnyAnnotation<T, "hidden">] : InferColumnType<T[K]> };
89
105
  /**
90
106
  * $infer_all -- all columns including hidden.
91
107
  */
@@ -101,21 +117,26 @@ type Insert<T extends ColumnRecord> = { [K in ColumnKeysWhereNot<T, "hasDefault"
101
117
  */
102
118
  type Update<T extends ColumnRecord> = { [K in ColumnKeysWhereNot<T, "primary">]? : InferColumnType<T[K]> };
103
119
  /**
104
- * $not_sensitive -- excludes columns marked .sensitive() OR .hidden().
105
- * (hidden implies sensitive for read purposes)
120
+ * $response -- API response shape. Excludes columns annotated 'hidden'.
106
121
  */
107
- type NotSensitive<T extends ColumnRecord> = { [K in ColumnKeysWhereNot<T, "sensitive"> & ColumnKeysWhereNot<T, "hidden"> & keyof T] : InferColumnType<T[K]> };
122
+ type Response<T extends ColumnRecord> = { [K in ColumnKeysWithoutAnyAnnotation<T, "hidden">] : InferColumnType<T[K]> };
108
123
  /**
109
- * $not_hidden -- excludes columns marked .hidden().
110
- * Same as $infer (excludes hidden, keeps sensitive).
124
+ * $create_input -- API create input shape.
125
+ * Excludes readOnly and primary key columns.
126
+ * Columns with defaults are optional.
111
127
  */
112
- type NotHidden<T extends ColumnRecord> = { [K in ColumnKeysWhereNot<T, "hidden">] : InferColumnType<T[K]> };
128
+ type ApiCreateInput<T extends ColumnRecord> = { [K in ColumnKeysWhereNot<T, "isReadOnly"> & ColumnKeysWhereNot<T, "primary"> & ColumnKeysWhereNot<T, "hasDefault"> & string] : InferColumnType<T[K]> } & { [K in ColumnKeysWhereNot<T, "isReadOnly"> & ColumnKeysWhereNot<T, "primary"> & ColumnKeysWhere<T, "hasDefault"> & string]? : InferColumnType<T[K]> };
129
+ /**
130
+ * $update_input -- API update input shape.
131
+ * Excludes readOnly and primary key columns. All fields optional (partial update).
132
+ */
133
+ type ApiUpdateInput<T extends ColumnRecord> = { [K in ColumnKeysWhereNot<T, "isReadOnly"> & ColumnKeysWhereNot<T, "primary"> & string]? : InferColumnType<T[K]> };
113
134
  interface TableDef<TColumns extends ColumnRecord = ColumnRecord> {
114
135
  readonly _name: string;
115
136
  readonly _columns: TColumns;
116
137
  readonly _indexes: readonly IndexDef[];
117
138
  readonly _shared: boolean;
118
- /** Default SELECT type -- excludes hidden columns. */
139
+ /** Default SELECT type -- excludes columns annotated 'hidden'. */
119
140
  readonly $infer: Infer<TColumns>;
120
141
  /** All columns including hidden. */
121
142
  readonly $infer_all: InferAll<TColumns>;
@@ -123,13 +144,60 @@ interface TableDef<TColumns extends ColumnRecord = ColumnRecord> {
123
144
  readonly $insert: Insert<TColumns>;
124
145
  /** Update type -- all non-PK columns optional. ALL columns included. */
125
146
  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>;
147
+ /** API response shape excludes columns annotated 'hidden'. */
148
+ readonly $response: Response<TColumns>;
149
+ /** API create input — excludes readOnly + PK; defaulted columns optional. */
150
+ readonly $create_input: ApiCreateInput<TColumns>;
151
+ /** API update input — excludes readOnly + PK; all fields optional. */
152
+ readonly $update_input: ApiUpdateInput<TColumns>;
130
153
  /** Mark this table as shared / cross-tenant. */
131
154
  shared(): TableDef<TColumns>;
132
155
  }
156
+ interface ColumnSnapshot {
157
+ type: string;
158
+ nullable: boolean;
159
+ primary: boolean;
160
+ unique: boolean;
161
+ default?: string;
162
+ annotations?: string[];
163
+ }
164
+ interface IndexSnapshot {
165
+ columns: string[];
166
+ name?: string;
167
+ unique?: boolean;
168
+ }
169
+ interface ForeignKeySnapshot {
170
+ column: string;
171
+ targetTable: string;
172
+ targetColumn: string;
173
+ }
174
+ interface TableSnapshot {
175
+ columns: Record<string, ColumnSnapshot>;
176
+ indexes: IndexSnapshot[];
177
+ foreignKeys: ForeignKeySnapshot[];
178
+ _metadata: Record<string, unknown>;
179
+ }
180
+ interface SchemaSnapshot {
181
+ version: 1;
182
+ tables: Record<string, TableSnapshot>;
183
+ enums: Record<string, string[]>;
184
+ }
185
+ /**
186
+ * Abstraction over snapshot persistence.
187
+ * Decouples the migration system from Node.js filesystem APIs.
188
+ */
189
+ interface SnapshotStorage {
190
+ load(key: string): Promise<SchemaSnapshot | null>;
191
+ save(key: string, snapshot: SchemaSnapshot): Promise<void>;
192
+ }
193
+ /**
194
+ * Node.js filesystem implementation of SnapshotStorage.
195
+ * Uses node:fs/promises for file I/O and node:path for directory creation.
196
+ */
197
+ declare class NodeSnapshotStorage implements SnapshotStorage {
198
+ load(path: string): Promise<SchemaSnapshot | null>;
199
+ save(path: string, snapshot: SchemaSnapshot): Promise<void>;
200
+ }
133
201
  /**
134
202
  * Query executor — wraps raw SQL execution with error mapping.
135
203
  *
@@ -175,23 +243,20 @@ interface GroupByArgs {
175
243
  */
176
244
  declare function getColumnNames(table: TableDef<ColumnRecord>): string[];
177
245
  /**
178
- * Get column names excluding hidden columns (default SELECT behavior).
246
+ * Get column names excluding 'hidden'-annotated columns (default SELECT behavior).
179
247
  */
180
248
  declare function getDefaultColumns(table: TableDef<ColumnRecord>): string[];
181
249
  /**
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.
250
+ * Get column names excluding columns with ANY of the specified annotations.
251
+ * Always excludes 'hidden'-annotated columns in addition to the specified annotations.
187
252
  */
188
- declare function getNotHiddenColumns(table: TableDef<ColumnRecord>): string[];
253
+ declare function getColumnsWithoutAnnotations(table: TableDef<ColumnRecord>, annotations: string[]): string[];
189
254
  /**
190
255
  * Resolve a select option to a list of column names.
191
256
  *
192
257
  * - undefined -> default columns (exclude hidden)
193
258
  * - { not: 'sensitive' } -> exclude sensitive + hidden
194
- * - { not: 'hidden' } -> exclude hidden
259
+ * - { not: ['sensitive', 'patchable'] } -> exclude columns with any listed annotation + hidden
195
260
  * - { id: true, name: true } -> explicit pick
196
261
  */
197
262
  declare function resolveSelectColumns(table: TableDef<ColumnRecord>, select?: Record<string, unknown>): string[];
@@ -220,4 +285,4 @@ declare function mapRow<T>(row: Record<string, unknown>): T;
220
285
  * Convert an array of rows from snake_case to camelCase.
221
286
  */
222
287
  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 };
288
+ export { resolveSelectColumns, mapRows, mapRow, getTimestampColumns, getPrimaryKeyColumns, getDefaultColumns, getColumnsWithoutAnnotations, getColumnNames, executeQuery, QueryFn, NodeSnapshotStorage, GroupByArgs, ExecutorResult, CountArgs, AggregateArgs };
package/dist/internals.js CHANGED
@@ -1,25 +1,26 @@
1
1
  import {
2
2
  executeQuery,
3
3
  getColumnNames,
4
+ getColumnsWithoutAnnotations,
4
5
  getDefaultColumns,
5
- getNotHiddenColumns,
6
- getNotSensitiveColumns,
7
6
  getPrimaryKeyColumns,
8
7
  getTimestampColumns,
9
8
  mapRow,
10
9
  mapRows,
11
10
  resolveSelectColumns
12
- } from "./shared/chunk-xp022dyp.js";
13
- import"./shared/chunk-hrfdj0rr.js";
11
+ } from "./shared/chunk-agyds4jw.js";
12
+ import {
13
+ NodeSnapshotStorage
14
+ } from "./shared/chunk-kb4tnn2k.js";
14
15
  export {
15
16
  resolveSelectColumns,
16
17
  mapRows,
17
18
  mapRow,
18
19
  getTimestampColumns,
19
20
  getPrimaryKeyColumns,
20
- getNotSensitiveColumns,
21
- getNotHiddenColumns,
22
21
  getDefaultColumns,
22
+ getColumnsWithoutAnnotations,
23
23
  getColumnNames,
24
- executeQuery
24
+ executeQuery,
25
+ NodeSnapshotStorage
25
26
  };
@@ -33,7 +33,7 @@ interface QueryShape {
33
33
  * Same shape (table + operation + where keys + select keys + include keys)
34
34
  * always yields the same fingerprint, regardless of parameter values.
35
35
  */
36
- declare function fingerprint(query: QueryShape): string;
36
+ declare function fingerprint(query: QueryShape): Promise<string>;
37
37
  /**
38
38
  * Context passed to plugin hooks.
39
39
  */
@@ -1,3 +1,7 @@
1
+ import {
2
+ sha256Hex
3
+ } from "../shared/chunk-ssga2xea.js";
4
+
1
5
  // src/plugin/event-bus.ts
2
6
  function createEventBus() {
3
7
  const handlers = new Set;
@@ -16,8 +20,7 @@ function createEventBus() {
16
20
  };
17
21
  }
18
22
  // src/plugin/fingerprint.ts
19
- import { createHash } from "node:crypto";
20
- function fingerprint(query) {
23
+ async function fingerprint(query) {
21
24
  const parts = [
22
25
  query.table,
23
26
  query.operation,
@@ -26,7 +29,8 @@ function fingerprint(query) {
26
29
  `i:${sortedKeys(query.include)}`
27
30
  ];
28
31
  const input = parts.join("|");
29
- return createHash("sha256").update(input).digest("hex").slice(0, 16);
32
+ const hex = await sha256Hex(input);
33
+ return hex.slice(0, 16);
30
34
  }
31
35
  function sortedKeys(obj) {
32
36
  if (!obj)
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Query executor — wraps raw SQL execution with error mapping.
3
+ *
4
+ * Takes a query function (from the database driver) and wraps it to:
5
+ * 1. Execute parameterized SQL
6
+ * 2. Map PG errors to typed DbError subclasses
7
+ * 3. Return typed QueryResult
8
+ */
9
+ interface ExecutorResult<T> {
10
+ readonly rows: readonly T[];
11
+ readonly rowCount: number;
12
+ }
13
+ type QueryFn = <T>(sql: string, params: readonly unknown[]) => Promise<ExecutorResult<T>>;
14
+ /**
15
+ * Database driver interface.
16
+ *
17
+ * Provides a unified interface for different database backends
18
+ * (PostgreSQL, SQLite/D1, etc.) with query and execute methods.
19
+ */
20
+ interface DbDriver {
21
+ /**
22
+ * Execute a read query and return results.
23
+ */
24
+ query<T = unknown>(sql: string, params?: unknown[]): Promise<T[]>;
25
+ /**
26
+ * Execute a write query and return affected row count.
27
+ */
28
+ execute(sql: string, params?: unknown[]): Promise<{
29
+ rowsAffected: number;
30
+ }>;
31
+ /**
32
+ * Close the database connection.
33
+ */
34
+ close(): Promise<void>;
35
+ }
36
+ interface PoolConfig {
37
+ /** Maximum number of connections in the pool. */
38
+ readonly max?: number;
39
+ /**
40
+ * Idle timeout in milliseconds before a connection is closed.
41
+ * Defaults to 30000 (30 seconds) if not specified, preventing
42
+ * idle connections from staying open indefinitely.
43
+ */
44
+ readonly idleTimeout?: number;
45
+ /** Connection timeout in milliseconds. */
46
+ readonly connectionTimeout?: number;
47
+ /**
48
+ * Health check timeout in milliseconds.
49
+ * Used by isHealthy() to prevent hanging on degraded databases.
50
+ * Defaults to 5000 (5 seconds) if not specified.
51
+ */
52
+ readonly healthCheckTimeout?: number;
53
+ /**
54
+ * Read replica URLs for query routing.
55
+ * Read-only queries (SELECT) can be routed to replicas for load balancing.
56
+ * The primary URL is always used for writes (INSERT, UPDATE, DELETE).
57
+ */
58
+ readonly replicas?: readonly string[];
59
+ }
60
+ interface PostgresDriver extends DbDriver {
61
+ /** The QueryFn adapter for use with createDb. */
62
+ readonly queryFn: QueryFn;
63
+ /** Check connection health with SELECT 1. */
64
+ isHealthy(): Promise<boolean>;
65
+ }
66
+ /**
67
+ * Create a PostgreSQL driver from a connection URL and optional pool config.
68
+ *
69
+ * Note: Query routing (to replicas) is handled at the database.ts layer (createDb).
70
+ * This driver provides a simple connection to a single PostgreSQL instance.
71
+ *
72
+ * @param url - PostgreSQL connection URL (e.g., postgres://user:pass@host:5432/db)
73
+ * @param pool - Optional pool configuration
74
+ * @returns A PostgresDriver with queryFn, close(), and isHealthy()
75
+ */
76
+ declare function createPostgresDriver(url: string, pool?: PoolConfig): PostgresDriver;
77
+ export { createPostgresDriver, PostgresDriver };
@@ -0,0 +1,7 @@
1
+ import {
2
+ createPostgresDriver
3
+ } from "../shared/chunk-rqe0prft.js";
4
+ import"../shared/chunk-j4kwq1gh.js";
5
+ export {
6
+ createPostgresDriver
7
+ };