schematic-pg 0.1.7 → 0.1.10
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 +237 -970
- package/dist/api/auth/jwt-crypto.d.ts +10 -0
- package/dist/api/auth/jwt-crypto.js +61 -0
- package/dist/api/auth/jwt-resolver.js +2 -27
- package/dist/api/auth/password/config.d.ts +32 -0
- package/dist/api/auth/password/config.js +35 -0
- package/dist/api/auth/password/errors.d.ts +8 -0
- package/dist/api/auth/password/errors.js +14 -0
- package/dist/api/auth/password/index.d.ts +3 -0
- package/dist/api/auth/password/index.js +3 -0
- package/dist/api/auth/password/password.d.ts +14 -0
- package/dist/api/auth/password/password.js +46 -0
- package/dist/api/auth/routes.d.ts +23 -0
- package/dist/api/auth/routes.js +118 -0
- package/dist/api/auth/token/config.d.ts +10 -0
- package/dist/api/auth/token/config.js +43 -0
- package/dist/api/auth/token/errors.d.ts +6 -0
- package/dist/api/auth/token/errors.js +12 -0
- package/dist/api/auth/token/index.d.ts +3 -0
- package/dist/api/auth/token/index.js +3 -0
- package/dist/api/auth/token/token.d.ts +11 -0
- package/dist/api/auth/token/token.js +31 -0
- package/dist/api/hooks/define.d.ts +10 -0
- package/dist/api/hooks/define.js +3 -0
- package/dist/api/hooks/index.d.ts +4 -0
- package/dist/api/hooks/index.js +2 -0
- package/dist/api/hooks/registry.d.ts +8 -0
- package/dist/api/hooks/registry.js +94 -0
- package/dist/api/hooks/types.d.ts +48 -0
- package/dist/api/hooks/types.js +1 -0
- package/dist/api/middleware/errors.js +11 -0
- package/dist/api-generator/app-generator.js +3 -0
- package/dist/api-generator/hook-scanner.d.ts +11 -0
- package/dist/api-generator/hook-scanner.js +36 -0
- package/dist/api-generator/hooks-generator.d.ts +2 -0
- package/dist/api-generator/hooks-generator.js +25 -0
- package/dist/api-generator/index.d.ts +2 -0
- package/dist/api-generator/index.js +7 -1
- package/dist/api-generator/route-generator.d.ts +3 -2
- package/dist/api-generator/route-generator.js +85 -64
- package/dist/cli/dev.js +5 -36
- package/dist/cli/generate.js +1 -0
- package/dist/cli/hooks.d.ts +6 -0
- package/dist/cli/hooks.js +85 -0
- package/dist/cli/init.js +9 -2
- package/dist/cli/paths.d.ts +1 -0
- package/dist/cli/paths.js +1 -0
- package/dist/cli/server.d.ts +5 -0
- package/dist/cli/server.js +60 -0
- package/dist/cli/start.d.ts +7 -0
- package/dist/cli/start.js +35 -0
- package/dist/cli/templates/agents.md +290 -0
- package/dist/cli/templates.d.ts +6 -3
- package/dist/cli/templates.js +58 -6
- package/dist/cli/wait-for-database.js +1 -1
- package/dist/cli.js +10 -0
- package/dist/db/db-client-generator.js +20 -2
- package/dist/db/include/executor.d.ts +2 -2
- package/dist/db/include/executor.js +7 -7
- package/dist/db/include/json-agg.d.ts +2 -2
- package/dist/db/include/json-agg.js +4 -4
- package/dist/db/include/load.d.ts +3 -3
- package/dist/db/include/load.js +8 -8
- package/dist/db/index.d.ts +4 -0
- package/dist/db/index.js +2 -0
- package/dist/db/model-client.d.ts +2 -2
- package/dist/db/model-client.js +3 -3
- package/dist/db/queryable.d.ts +5 -0
- package/dist/db/queryable.js +1 -0
- package/dist/db/raw.d.ts +22 -0
- package/dist/db/raw.js +35 -0
- package/dist/db/transaction.d.ts +2 -0
- package/dist/db/transaction.js +24 -0
- package/dist/routes/auth.d.ts +3 -0
- package/dist/routes/auth.js +5 -0
- package/dist/types/generated-db.stub.d.ts +5 -1
- package/dist/types/generated-db.stub.js +8 -1
- package/package.json +11 -4
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { runDbBootstrap, runDbDiff, runDbMigrate, runDbPing } from './cli/db.js';
|
|
3
3
|
import { runDev } from './cli/dev.js';
|
|
4
|
+
import { runStart } from './cli/start.js';
|
|
4
5
|
import { generateAll, generateApi, generateClient, generateSql } from './cli/generate.js';
|
|
6
|
+
import { runHooksAdd } from './cli/hooks.js';
|
|
5
7
|
import { runInit } from './cli/init.js';
|
|
6
8
|
import { PACKAGE_NAME } from './constants.js';
|
|
7
9
|
const USAGE = `Usage: ${PACKAGE_NAME} <command> [options]
|
|
@@ -12,7 +14,9 @@ Commands:
|
|
|
12
14
|
generate:sql [schema] Generate SQL DDL to stdout
|
|
13
15
|
generate:client [schema] Generate db client files
|
|
14
16
|
generate:api [schema] Generate API files
|
|
17
|
+
hooks:add [schema] [--model ModelName] Scaffold lifecycle hooks for a model
|
|
15
18
|
dev [schema] [--no-watch] Generate, bootstrap DB, start server, watch schema
|
|
19
|
+
start [schema] [--no-migrate] Run production server (migrate DB, no generate/watch)
|
|
16
20
|
db:ping Test database connection
|
|
17
21
|
db:bootstrap [schema] Apply DDL and snapshot schema state
|
|
18
22
|
db:diff [schema] Show schema diff (--name <name> to write migration)
|
|
@@ -49,9 +53,15 @@ async function main() {
|
|
|
49
53
|
case 'generate:api':
|
|
50
54
|
await generateApi(schemaPath);
|
|
51
55
|
break;
|
|
56
|
+
case 'hooks:add':
|
|
57
|
+
await runHooksAdd(args);
|
|
58
|
+
break;
|
|
52
59
|
case 'dev':
|
|
53
60
|
await runDev(args);
|
|
54
61
|
break;
|
|
62
|
+
case 'start':
|
|
63
|
+
await runStart(args);
|
|
64
|
+
break;
|
|
55
65
|
case 'db:ping':
|
|
56
66
|
await runDbPing();
|
|
57
67
|
break;
|
|
@@ -31,10 +31,13 @@ export class DbClientGenerator {
|
|
|
31
31
|
"import type { Pool } from 'pg';",
|
|
32
32
|
`import { createModelClient } from '${PACKAGE_NAME}/db/model-client';`,
|
|
33
33
|
`import { hydrateModelMeta } from '${PACKAGE_NAME}/db/model-meta';`,
|
|
34
|
+
`import type { Queryable } from '${PACKAGE_NAME}/db/queryable';`,
|
|
35
|
+
`import { createRawClient } from '${PACKAGE_NAME}/db/raw';`,
|
|
36
|
+
`import { runInTransaction } from '${PACKAGE_NAME}/db/transaction';`,
|
|
34
37
|
`import type {\n ${imports},\n} from './db-types.js';`,
|
|
35
38
|
`import {\n ${this.schema.models.map((model) => `${getClientExportName(model.name)}ModelMeta`).join(',\n ')},\n} from './db-model-meta.js';`,
|
|
36
39
|
'',
|
|
37
|
-
'
|
|
40
|
+
'function buildModels(executor: Queryable) {',
|
|
38
41
|
...metaHydrations,
|
|
39
42
|
` const modelRegistry = new Map([`,
|
|
40
43
|
...registryEntries.map((entry) => ` ${entry},`),
|
|
@@ -42,6 +45,21 @@ export class DbClientGenerator {
|
|
|
42
45
|
'',
|
|
43
46
|
' return {',
|
|
44
47
|
clientEntries.map((entry) => ` ${entry},`).join('\n'),
|
|
48
|
+
' ...createRawClient(executor),',
|
|
49
|
+
' };',
|
|
50
|
+
'}',
|
|
51
|
+
'',
|
|
52
|
+
'/** All model clients scoped to a single transaction. Nested `$transaction` is unsupported. */',
|
|
53
|
+
'export type TxClient = ReturnType<typeof buildModels>;',
|
|
54
|
+
'',
|
|
55
|
+
'export function createDbClient(pool: Pool) {',
|
|
56
|
+
' async function $transaction<T>(fn: (tx: TxClient) => Promise<T>): Promise<T> {',
|
|
57
|
+
' return runInTransaction(pool, (client) => fn(buildModels(client)));',
|
|
58
|
+
' }',
|
|
59
|
+
'',
|
|
60
|
+
' return {',
|
|
61
|
+
' ...buildModels(pool),',
|
|
62
|
+
' $transaction,',
|
|
45
63
|
' };',
|
|
46
64
|
'}',
|
|
47
65
|
'',
|
|
@@ -65,7 +83,7 @@ export class DbClientGenerator {
|
|
|
65
83
|
}
|
|
66
84
|
generateClientEntry(modelName) {
|
|
67
85
|
const clientKey = getClientExportName(modelName);
|
|
68
|
-
return `${clientKey}: createModelClient<${modelName}, ${modelName}CreateInput, ${modelName}UpdateInput, ${modelName}WhereInput, ${modelName}OrderByInput>(${clientKey}Meta,
|
|
86
|
+
return `${clientKey}: createModelClient<${modelName}, ${modelName}CreateInput, ${modelName}UpdateInput, ${modelName}WhereInput, ${modelName}OrderByInput>(${clientKey}Meta, executor, modelRegistry)`;
|
|
69
87
|
}
|
|
70
88
|
}
|
|
71
89
|
export function generateDbClientFiles(schema) {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Queryable } from '../queryable.js';
|
|
2
2
|
import type { LoadNode } from './planner.js';
|
|
3
|
-
export declare function loadIncludes(parentRows: Record<string, unknown>[], plan: LoadNode,
|
|
3
|
+
export declare function loadIncludes(parentRows: Record<string, unknown>[], plan: LoadNode, executor: Queryable): Promise<void>;
|
|
@@ -2,7 +2,7 @@ import { mapPgError } from '../errors.js';
|
|
|
2
2
|
import { mapRows } from '../row-mapper.js';
|
|
3
3
|
import { WhereTranslator } from '../where-translator.js';
|
|
4
4
|
import { dedupeKeys, extractParentKeys, stitch } from './hydrator.js';
|
|
5
|
-
export async function loadIncludes(parentRows, plan,
|
|
5
|
+
export async function loadIncludes(parentRows, plan, executor) {
|
|
6
6
|
for (const childPlan of plan.children) {
|
|
7
7
|
const relation = childPlan.relation;
|
|
8
8
|
if (!relation) {
|
|
@@ -13,8 +13,8 @@ export async function loadIncludes(parentRows, plan, pool) {
|
|
|
13
13
|
assignEmptyRelation(parentRows, relation);
|
|
14
14
|
continue;
|
|
15
15
|
}
|
|
16
|
-
const childRows = await fetchRelationRows(childPlan, parentKeys,
|
|
17
|
-
await loadIncludes(childRows, childPlan,
|
|
16
|
+
const childRows = await fetchRelationRows(childPlan, parentKeys, executor);
|
|
17
|
+
await loadIncludes(childRows, childPlan, executor);
|
|
18
18
|
stitch(parentRows, childRows, relation);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
@@ -23,13 +23,13 @@ function assignEmptyRelation(parentRows, relation) {
|
|
|
23
23
|
parent[relation.name] = relation.unique ? null : [];
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
async function fetchRelationRows(node, parentKeys,
|
|
26
|
+
async function fetchRelationRows(node, parentKeys, executor) {
|
|
27
27
|
const relation = node.relation;
|
|
28
28
|
if (!relation) {
|
|
29
29
|
return [];
|
|
30
30
|
}
|
|
31
31
|
const query = buildRelationSelect(node, parentKeys);
|
|
32
|
-
const rows = await executeQuery(
|
|
32
|
+
const rows = await executeQuery(executor, query.sql, query.params, node.model);
|
|
33
33
|
return mapRows(rows, node.model);
|
|
34
34
|
}
|
|
35
35
|
function buildRelationSelect(node, parentKeys) {
|
|
@@ -79,9 +79,9 @@ function buildOrderByClause(plan) {
|
|
|
79
79
|
}
|
|
80
80
|
return parts.length > 0 ? `ORDER BY ${parts.join(', ')}` : '';
|
|
81
81
|
}
|
|
82
|
-
async function executeQuery(
|
|
82
|
+
async function executeQuery(executor, sql, params, model) {
|
|
83
83
|
try {
|
|
84
|
-
const result = await
|
|
84
|
+
const result = await executor.query(sql, params);
|
|
85
85
|
return result.rows;
|
|
86
86
|
}
|
|
87
87
|
catch (error) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Pool } from 'pg';
|
|
2
1
|
import type { ModelMeta } from '../model-meta.js';
|
|
3
2
|
import type { FindArgs } from '../query-builder.js';
|
|
3
|
+
import type { Queryable } from '../queryable.js';
|
|
4
4
|
import type { LoadNode } from './planner.js';
|
|
5
|
-
export declare function fetchRootWithJsonAgg<T extends Record<string, unknown>>(model: ModelMeta, plan: LoadNode, args: FindArgs,
|
|
5
|
+
export declare function fetchRootWithJsonAgg<T extends Record<string, unknown>>(model: ModelMeta, plan: LoadNode, args: FindArgs, executor: Queryable): Promise<T[]>;
|
|
@@ -2,9 +2,9 @@ import { mapPgError } from '../errors.js';
|
|
|
2
2
|
import { QueryBuilder } from '../query-builder.js';
|
|
3
3
|
import { mapRow } from '../row-mapper.js';
|
|
4
4
|
const ROOT_ALIAS = 'root';
|
|
5
|
-
export async function fetchRootWithJsonAgg(model, plan, args,
|
|
5
|
+
export async function fetchRootWithJsonAgg(model, plan, args, executor) {
|
|
6
6
|
const query = buildJsonAggRootQuery(model, plan, args);
|
|
7
|
-
const rows = await executeQuery(
|
|
7
|
+
const rows = await executeQuery(executor, query.sql, query.params, model);
|
|
8
8
|
return rows.map((row) => hydrateJsonAggRow(row, model, plan));
|
|
9
9
|
}
|
|
10
10
|
function buildJsonAggRootQuery(model, plan, args) {
|
|
@@ -90,9 +90,9 @@ function hydrateJsonObject(value, plan) {
|
|
|
90
90
|
}
|
|
91
91
|
return mapped;
|
|
92
92
|
}
|
|
93
|
-
async function executeQuery(
|
|
93
|
+
async function executeQuery(executor, sql, params, model) {
|
|
94
94
|
try {
|
|
95
|
-
const result = await
|
|
95
|
+
const result = await executor.query(sql, params);
|
|
96
96
|
return result.rows;
|
|
97
97
|
}
|
|
98
98
|
catch (error) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { Pool } from 'pg';
|
|
2
1
|
import type { ModelMeta } from '../model-meta.js';
|
|
3
2
|
import type { FindArgs } from '../query-builder.js';
|
|
3
|
+
import type { Queryable } from '../queryable.js';
|
|
4
4
|
import type { IncludeInput, IncludeOptions } from './types.js';
|
|
5
|
-
export declare function fetchWithIncludes<T extends Record<string, unknown>>(model: ModelMeta, registry: Map<string, ModelMeta>,
|
|
5
|
+
export declare function fetchWithIncludes<T extends Record<string, unknown>>(model: ModelMeta, registry: Map<string, ModelMeta>, executor: Queryable, rootArgs: FindArgs & {
|
|
6
6
|
include?: IncludeInput;
|
|
7
7
|
}, options?: IncludeOptions): Promise<T[]>;
|
|
8
|
-
export declare function attachIncludes<T extends Record<string, unknown>>(model: ModelMeta, registry: Map<string, ModelMeta>,
|
|
8
|
+
export declare function attachIncludes<T extends Record<string, unknown>>(model: ModelMeta, registry: Map<string, ModelMeta>, executor: Queryable, rootRows: T[], include: IncludeInput, rootArgs: FindArgs, options?: IncludeOptions): Promise<T[]>;
|
package/dist/db/include/load.js
CHANGED
|
@@ -4,7 +4,7 @@ import { mapRows } from '../row-mapper.js';
|
|
|
4
4
|
import { loadIncludes } from './executor.js';
|
|
5
5
|
import { fetchRootWithJsonAgg } from './json-agg.js';
|
|
6
6
|
import { buildLoadPlan } from './planner.js';
|
|
7
|
-
export async function fetchWithIncludes(model, registry,
|
|
7
|
+
export async function fetchWithIncludes(model, registry, executor, rootArgs, options = {}) {
|
|
8
8
|
if (!rootArgs.include) {
|
|
9
9
|
throw new Error('fetchWithIncludes requires include');
|
|
10
10
|
}
|
|
@@ -14,14 +14,14 @@ export async function fetchWithIncludes(model, registry, pool, rootArgs, options
|
|
|
14
14
|
plan.take = rootArgs.take;
|
|
15
15
|
plan.skip = rootArgs.skip;
|
|
16
16
|
if (plan.strategy === 'join') {
|
|
17
|
-
return fetchRootWithJsonAgg(model, plan, rootArgs,
|
|
17
|
+
return fetchRootWithJsonAgg(model, plan, rootArgs, executor);
|
|
18
18
|
}
|
|
19
|
-
const rows = await executeRootSelect(model,
|
|
19
|
+
const rows = await executeRootSelect(model, executor, rootArgs);
|
|
20
20
|
const mapped = mapRows(rows, model);
|
|
21
|
-
await loadIncludes(mapped, plan,
|
|
21
|
+
await loadIncludes(mapped, plan, executor);
|
|
22
22
|
return mapped;
|
|
23
23
|
}
|
|
24
|
-
export async function attachIncludes(model, registry,
|
|
24
|
+
export async function attachIncludes(model, registry, executor, rootRows, include, rootArgs, options = {}) {
|
|
25
25
|
if (rootRows.length === 0) {
|
|
26
26
|
return rootRows;
|
|
27
27
|
}
|
|
@@ -30,14 +30,14 @@ export async function attachIncludes(model, registry, pool, rootRows, include, r
|
|
|
30
30
|
plan.orderBy = rootArgs.orderBy;
|
|
31
31
|
plan.take = rootArgs.take;
|
|
32
32
|
plan.skip = rootArgs.skip;
|
|
33
|
-
await loadIncludes(rootRows, plan,
|
|
33
|
+
await loadIncludes(rootRows, plan, executor);
|
|
34
34
|
return rootRows;
|
|
35
35
|
}
|
|
36
|
-
async function executeRootSelect(model,
|
|
36
|
+
async function executeRootSelect(model, executor, args) {
|
|
37
37
|
const builder = new QueryBuilder(model);
|
|
38
38
|
const query = builder.select(args);
|
|
39
39
|
try {
|
|
40
|
-
const result = await
|
|
40
|
+
const result = await executor.query(query.sql, query.params);
|
|
41
41
|
return result.rows;
|
|
42
42
|
}
|
|
43
43
|
catch (error) {
|
package/dist/db/index.d.ts
CHANGED
|
@@ -4,6 +4,10 @@ export { getDatabaseUrl } from './config.js';
|
|
|
4
4
|
export { QueryBuilder } from './query-builder.js';
|
|
5
5
|
export { WhereTranslator } from './where-translator.js';
|
|
6
6
|
export { createModelClient } from './model-client.js';
|
|
7
|
+
export type { Queryable } from './queryable.js';
|
|
8
|
+
export { createRawClient } from './raw.js';
|
|
9
|
+
export type { RawClient } from './raw.js';
|
|
10
|
+
export { runInTransaction } from './transaction.js';
|
|
7
11
|
export { TypeGenerator, generateDbTypes } from './type-generator.js';
|
|
8
12
|
export { DbClientGenerator, generateDbClientFiles } from './db-client-generator.js';
|
|
9
13
|
export { buildModelMeta, buildModelMetaSnapshot, hydrateModelMeta } from './model-meta.js';
|
package/dist/db/index.js
CHANGED
|
@@ -4,6 +4,8 @@ export { getDatabaseUrl } from './config.js';
|
|
|
4
4
|
export { QueryBuilder } from './query-builder.js';
|
|
5
5
|
export { WhereTranslator } from './where-translator.js';
|
|
6
6
|
export { createModelClient } from './model-client.js';
|
|
7
|
+
export { createRawClient } from './raw.js';
|
|
8
|
+
export { runInTransaction } from './transaction.js';
|
|
7
9
|
export { TypeGenerator, generateDbTypes } from './type-generator.js';
|
|
8
10
|
export { DbClientGenerator, generateDbClientFiles } from './db-client-generator.js';
|
|
9
11
|
export { buildModelMeta, buildModelMetaSnapshot, hydrateModelMeta } from './model-meta.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { Pool } from 'pg';
|
|
2
1
|
import type { IncludeInput, IncludeOptions } from './include/types.js';
|
|
3
2
|
import type { ModelMeta } from './model-meta.js';
|
|
3
|
+
import type { Queryable } from './queryable.js';
|
|
4
4
|
export type ModelRegistry = Map<string, ModelMeta>;
|
|
5
5
|
export interface SelectArgs<TWhere, TOrderBy> {
|
|
6
6
|
where?: TWhere;
|
|
@@ -35,4 +35,4 @@ export interface ModelClient<T, TCreate, TUpdate, TWhere, TOrderBy> {
|
|
|
35
35
|
count: number;
|
|
36
36
|
}>;
|
|
37
37
|
}
|
|
38
|
-
export declare function createModelClient<T, TCreate, TUpdate, TWhere, TOrderBy>(model: ModelMeta,
|
|
38
|
+
export declare function createModelClient<T, TCreate, TUpdate, TWhere, TOrderBy>(model: ModelMeta, executor: Queryable, registry?: ModelRegistry): ModelClient<T, TCreate, TUpdate, TWhere, TOrderBy>;
|
package/dist/db/model-client.js
CHANGED
|
@@ -2,11 +2,11 @@ import { mapPgError } from './errors.js';
|
|
|
2
2
|
import { fetchWithIncludes } from './include/load.js';
|
|
3
3
|
import { QueryBuilder } from './query-builder.js';
|
|
4
4
|
import { mapRow, mapRows } from './row-mapper.js';
|
|
5
|
-
export function createModelClient(model,
|
|
5
|
+
export function createModelClient(model, executor, registry) {
|
|
6
6
|
const builder = new QueryBuilder(model);
|
|
7
7
|
async function execute(sql, params) {
|
|
8
8
|
try {
|
|
9
|
-
const result = await
|
|
9
|
+
const result = await executor.query(sql, params);
|
|
10
10
|
return result.rows;
|
|
11
11
|
}
|
|
12
12
|
catch (error) {
|
|
@@ -16,7 +16,7 @@ export function createModelClient(model, pool, registry) {
|
|
|
16
16
|
async function selectRows(args = {}) {
|
|
17
17
|
const findArgs = toFindArgs(args);
|
|
18
18
|
if (args.include && registry) {
|
|
19
|
-
return fetchWithIncludes(model, registry,
|
|
19
|
+
return fetchWithIncludes(model, registry, executor, {
|
|
20
20
|
...findArgs,
|
|
21
21
|
include: args.include,
|
|
22
22
|
}, {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/db/raw.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { QueryResultRow } from 'pg';
|
|
2
|
+
import type { Queryable } from './queryable.js';
|
|
3
|
+
export interface RawClient {
|
|
4
|
+
/**
|
|
5
|
+
* Runs an arbitrary SQL query and returns the raw result rows.
|
|
6
|
+
*
|
|
7
|
+
* Rows are returned EXACTLY as `pg` produces them: `snake_case` column names
|
|
8
|
+
* and driver type coercion, with no `camelCase` mapping. Values MUST be passed
|
|
9
|
+
* through the positional `params` array (`$1`, `$2`, …) — never interpolate
|
|
10
|
+
* user input into the `sql` string.
|
|
11
|
+
*/
|
|
12
|
+
$queryRaw<T extends QueryResultRow = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>;
|
|
13
|
+
/**
|
|
14
|
+
* Runs an arbitrary SQL statement and returns the number of affected rows.
|
|
15
|
+
*
|
|
16
|
+
* Values MUST be passed through the positional `params` array (`$1`, `$2`, …)
|
|
17
|
+
* — never interpolate user input into the `sql` string.
|
|
18
|
+
*/
|
|
19
|
+
$executeRaw(sql: string, params?: unknown[]): Promise<number>;
|
|
20
|
+
}
|
|
21
|
+
/** Builds the parameterized raw-query escape hatch bound to a single executor. */
|
|
22
|
+
export declare function createRawClient(executor: Queryable): RawClient;
|
package/dist/db/raw.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { mapPgError } from './errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Label used when surfacing raw-query errors. A raw query has no single owning
|
|
4
|
+
* model, so error mapping runs without a `column -> field` translation map.
|
|
5
|
+
*/
|
|
6
|
+
const RAW_QUERY_LABEL = '$queryRaw';
|
|
7
|
+
const EMPTY_COLUMN_TO_FIELD = new Map();
|
|
8
|
+
const NO_PARAMS = [];
|
|
9
|
+
/** Maps a driver error to the project's typed `DatabaseError` subclasses. */
|
|
10
|
+
function wrapRawError(error) {
|
|
11
|
+
return mapPgError(error, RAW_QUERY_LABEL, EMPTY_COLUMN_TO_FIELD);
|
|
12
|
+
}
|
|
13
|
+
/** Builds the parameterized raw-query escape hatch bound to a single executor. */
|
|
14
|
+
export function createRawClient(executor) {
|
|
15
|
+
return {
|
|
16
|
+
async $queryRaw(sql, params = NO_PARAMS) {
|
|
17
|
+
try {
|
|
18
|
+
const result = await executor.query(sql, params);
|
|
19
|
+
return result.rows;
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
throw wrapRawError(error);
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
async $executeRaw(sql, params = NO_PARAMS) {
|
|
26
|
+
try {
|
|
27
|
+
const result = await executor.query(sql, params);
|
|
28
|
+
return result.rowCount ?? 0;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
throw wrapRawError(error);
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const BEGIN = 'BEGIN';
|
|
2
|
+
const COMMIT = 'COMMIT';
|
|
3
|
+
const ROLLBACK = 'ROLLBACK';
|
|
4
|
+
export async function runInTransaction(pool, fn) {
|
|
5
|
+
const client = await pool.connect();
|
|
6
|
+
try {
|
|
7
|
+
await client.query(BEGIN);
|
|
8
|
+
const result = await fn(client);
|
|
9
|
+
await client.query(COMMIT);
|
|
10
|
+
return result;
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
try {
|
|
14
|
+
await client.query(ROLLBACK);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// ignore rollback failure; surface the original error
|
|
18
|
+
}
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
client.release();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import type { QueryResultRow } from 'pg';
|
|
2
|
+
export interface DbClient extends Record<string, unknown> {
|
|
3
|
+
$queryRaw<T extends QueryResultRow = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>;
|
|
4
|
+
$executeRaw(sql: string, params?: unknown[]): Promise<number>;
|
|
5
|
+
}
|
|
2
6
|
export declare function createDbClient(_pool: unknown): DbClient;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "schematic-pg",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Single-file backend framework for PostgreSQL and Node.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "git+https://github.com/tawsbob/
|
|
12
|
+
"url": "git+https://github.com/tawsbob/schematic-pg.git"
|
|
13
13
|
},
|
|
14
14
|
"main": "./dist/index.js",
|
|
15
15
|
"types": "./dist/index.d.ts",
|
|
@@ -34,6 +34,10 @@
|
|
|
34
34
|
"types": "./dist/schema-dsl/index.d.ts",
|
|
35
35
|
"import": "./dist/schema-dsl/index.js"
|
|
36
36
|
},
|
|
37
|
+
"./api/hooks": {
|
|
38
|
+
"types": "./dist/api/hooks/index.d.ts",
|
|
39
|
+
"import": "./dist/api/hooks/index.js"
|
|
40
|
+
},
|
|
37
41
|
"./api/*": {
|
|
38
42
|
"types": "./dist/api/*.d.ts",
|
|
39
43
|
"import": "./dist/api/*.js"
|
|
@@ -44,13 +48,14 @@
|
|
|
44
48
|
}
|
|
45
49
|
},
|
|
46
50
|
"scripts": {
|
|
47
|
-
"build": "tsc -p tsconfig.build.json",
|
|
51
|
+
"build": "tsc -p tsconfig.build.json && mkdir -p dist/cli/templates && cp src/cli/templates/agents.md dist/cli/templates/",
|
|
48
52
|
"parse": "tsx src/schema-dsl/cli.ts",
|
|
49
53
|
"tokenize": "tsx src/schema-dsl/cli.ts --tokens",
|
|
50
54
|
"generate": "tsx src/cli.ts generate",
|
|
51
55
|
"generate:client": "tsx src/cli.ts generate:client",
|
|
52
56
|
"generate:api": "tsx src/cli.ts generate:api",
|
|
53
57
|
"dev:api": "tsx src/cli.ts dev",
|
|
58
|
+
"start": "tsx src/cli.ts start",
|
|
54
59
|
"setup:env": "test -f .env || cp .env.example .env",
|
|
55
60
|
"db:ping": "tsx src/cli.ts db:ping",
|
|
56
61
|
"db:bootstrap": "tsx src/cli.ts db:bootstrap",
|
|
@@ -60,7 +65,7 @@
|
|
|
60
65
|
"build:lsp": "npm run build --workspace=@schematic-pg/schema-dsl-language-server",
|
|
61
66
|
"build:extension": "npm run vscode:prepublish --workspace=schematic-pg-schema-dsl-vscode && npm run package --workspace=schematic-pg-schema-dsl-vscode",
|
|
62
67
|
"test": "node --import tsx --test $(find src editors/language-server -path '*/__tests__/*.test.ts' ! -name '*.integration.test.ts')",
|
|
63
|
-
"test:integration": "npm run setup:env && docker compose up -d --wait && npm run generate:client && npm run generate:api && JWT_SECRET=integration-test-secret node --import tsx --test --test-concurrency=1 'src/**/__tests__/**/*.integration.test.ts'",
|
|
68
|
+
"test:integration": "npm run setup:env && docker compose up -d --wait && npm run generate:client && npm run generate:api && JWT_SECRET=integration-test-secret AUTH_PEPPER=integration-test-pepper node --import tsx --test --test-concurrency=1 'src/**/__tests__/**/*.integration.test.ts'",
|
|
64
69
|
"docker:up": "docker compose up -d",
|
|
65
70
|
"docker:down": "docker compose down",
|
|
66
71
|
"docker:logs": "docker compose logs -f postgres",
|
|
@@ -69,6 +74,8 @@
|
|
|
69
74
|
"dependencies": {
|
|
70
75
|
"@hono/node-server": "^2.0.6",
|
|
71
76
|
"@hono/zod-validator": "^0.8.0",
|
|
77
|
+
"@inquirer/prompts": "^8.5.2",
|
|
78
|
+
"argon2": "^0.44.0",
|
|
72
79
|
"hono": "^4.12.27",
|
|
73
80
|
"pg": "^8.22.0",
|
|
74
81
|
"tsx": "^4.19.4",
|