@simtlix/simfinity-postgres 3.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/LICENSE +201 -0
- package/README.md +89 -0
- package/package.json +36 -0
- package/src/adapter.js +125 -0
- package/src/codecs.js +70 -0
- package/src/index.js +46 -0
- package/src/query/compiler.js +255 -0
- package/src/query/functions.js +47 -0
- package/src/records.js +219 -0
- package/src/schema/constraints.js +142 -0
- package/src/schema/ddl.js +29 -0
- package/src/schema/describe.js +138 -0
- package/src/schema/initialize.js +271 -0
- package/src/schema/sql.js +28 -0
- package/src/transactions.js +32 -0
- package/types/index.d.ts +173 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { SimfinityError } from '@simtlix/simfinity-core';
|
|
2
|
+
import { normalizeDatabaseError } from './codecs.js';
|
|
3
|
+
|
|
4
|
+
export const createTransactions = (getPool, assertReady) => {
|
|
5
|
+
const sessions = new WeakSet();
|
|
6
|
+
const requireSession = (session) => {
|
|
7
|
+
if (!sessions.has(session) || !session.inTransaction()) throw new SimfinityError('Session is not an active transaction of this PostgreSQL instance', 'INVALID_SESSION', 400);
|
|
8
|
+
return session.client;
|
|
9
|
+
};
|
|
10
|
+
const withTransaction = async (session, body) => {
|
|
11
|
+
assertReady();
|
|
12
|
+
if (session) { requireSession(session); return body(session); }
|
|
13
|
+
for (let attempt = 0; attempt <= 5; attempt++) {
|
|
14
|
+
const client = await getPool().connect();
|
|
15
|
+
let active = true;
|
|
16
|
+
const handle = { client, inTransaction: () => active, query: (...args) => requireSession(handle).query(...args) };
|
|
17
|
+
sessions.add(handle);
|
|
18
|
+
try {
|
|
19
|
+
await client.query('BEGIN ISOLATION LEVEL REPEATABLE READ');
|
|
20
|
+
const result = await body(handle);
|
|
21
|
+
await client.query('COMMIT');
|
|
22
|
+
return result;
|
|
23
|
+
} catch (error) {
|
|
24
|
+
await client.query('ROLLBACK').catch(() => {});
|
|
25
|
+
// Only confirmed aborted transactions are retried, never unknown commit outcomes.
|
|
26
|
+
if (['40001', '40P01'].includes(error.code) && attempt < 5) continue;
|
|
27
|
+
throw normalizeDatabaseError(error);
|
|
28
|
+
} finally { active = false; sessions.delete(handle); client.release(); }
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
return { withTransaction, requireSession };
|
|
32
|
+
};
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { ModelRegistration, Runtime } from '@simtlix/simfinity-core';
|
|
2
|
+
import { auth, plugins, scalars, validators } from '@simtlix/simfinity-core';
|
|
3
|
+
import type { GraphQLObjectType } from 'graphql';
|
|
4
|
+
export {
|
|
5
|
+
auth,
|
|
6
|
+
buildErrorFormatter,
|
|
7
|
+
createValidatedScalar,
|
|
8
|
+
InternalServerError,
|
|
9
|
+
plugins,
|
|
10
|
+
scalars,
|
|
11
|
+
SimfinityError,
|
|
12
|
+
validators,
|
|
13
|
+
} from '@simtlix/simfinity-core';
|
|
14
|
+
export type {
|
|
15
|
+
AuthPluginOptions,
|
|
16
|
+
AuthRule,
|
|
17
|
+
AuthRuleFunction,
|
|
18
|
+
EntityController,
|
|
19
|
+
EnvelopSchemaPlugin,
|
|
20
|
+
FieldValidations,
|
|
21
|
+
FieldValidator,
|
|
22
|
+
MiddlewareContext,
|
|
23
|
+
PermissionSchema,
|
|
24
|
+
PolicyExpression,
|
|
25
|
+
StateMachine,
|
|
26
|
+
TypePermissions,
|
|
27
|
+
} from '@simtlix/simfinity-core';
|
|
28
|
+
|
|
29
|
+
export interface DatabaseColumn {
|
|
30
|
+
name: string;
|
|
31
|
+
type: string;
|
|
32
|
+
collation?: 'C';
|
|
33
|
+
nullable: boolean;
|
|
34
|
+
default?: string;
|
|
35
|
+
/** Private boolean marker; SQL NULL without this marker represents an absent embedded field. */
|
|
36
|
+
presenceColumn?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface DatabaseForeignKey {
|
|
39
|
+
name: string;
|
|
40
|
+
columns: string[];
|
|
41
|
+
targetTable: string;
|
|
42
|
+
targetColumns: string[];
|
|
43
|
+
onDelete: 'NO ACTION' | 'CASCADE';
|
|
44
|
+
onUpdate: 'NO ACTION';
|
|
45
|
+
deferrable: boolean;
|
|
46
|
+
initiallyDeferred: boolean;
|
|
47
|
+
}
|
|
48
|
+
export interface DatabaseIndex {
|
|
49
|
+
name: string;
|
|
50
|
+
columns: string[];
|
|
51
|
+
unique: boolean;
|
|
52
|
+
nullsNotDistinct: boolean;
|
|
53
|
+
}
|
|
54
|
+
export interface DatabaseTable {
|
|
55
|
+
name: string;
|
|
56
|
+
columns: DatabaseColumn[];
|
|
57
|
+
primaryKey: { name: string; columns: string[] };
|
|
58
|
+
foreignKeys: DatabaseForeignKey[];
|
|
59
|
+
indexes: DatabaseIndex[];
|
|
60
|
+
checks: Array<{ name: string; expression: string }>;
|
|
61
|
+
auxiliary?: { rootTable: string; kind: 'guard' | 'uniqueKeys' };
|
|
62
|
+
uniqueKeys?: { rootTable: string; sourceTable: string; field: string };
|
|
63
|
+
ownership?: {
|
|
64
|
+
ownerTable: string;
|
|
65
|
+
ownerColumn: string;
|
|
66
|
+
field: string;
|
|
67
|
+
path: string[];
|
|
68
|
+
list: boolean;
|
|
69
|
+
required: boolean;
|
|
70
|
+
nullableItems: boolean;
|
|
71
|
+
stateColumn: string;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export interface DatabaseFunction {
|
|
75
|
+
name: string;
|
|
76
|
+
arguments: string[];
|
|
77
|
+
returns: 'boolean' | 'void' | 'trigger' | 'jsonb' | 'bytea';
|
|
78
|
+
language: 'plpgsql';
|
|
79
|
+
volatility: 'IMMUTABLE' | 'VOLATILE';
|
|
80
|
+
configuration: string[];
|
|
81
|
+
body: string;
|
|
82
|
+
}
|
|
83
|
+
export interface DatabaseTrigger {
|
|
84
|
+
name: string;
|
|
85
|
+
table: string;
|
|
86
|
+
function: string;
|
|
87
|
+
constraint: boolean;
|
|
88
|
+
deferrable: boolean;
|
|
89
|
+
initiallyDeferred: boolean;
|
|
90
|
+
}
|
|
91
|
+
export interface DatabaseMaintenance {
|
|
92
|
+
rootTable: string;
|
|
93
|
+
guardTable: string;
|
|
94
|
+
validateFunction: string;
|
|
95
|
+
refreshFunction: string;
|
|
96
|
+
}
|
|
97
|
+
export interface DatabaseDescription {
|
|
98
|
+
schema: string;
|
|
99
|
+
tables: DatabaseTable[];
|
|
100
|
+
functions: DatabaseFunction[];
|
|
101
|
+
triggers: DatabaseTrigger[];
|
|
102
|
+
maintenance: DatabaseMaintenance[];
|
|
103
|
+
}
|
|
104
|
+
export interface DatabasePool {
|
|
105
|
+
connect(): Promise<{
|
|
106
|
+
query(sql: string, values?: unknown[]): Promise<{ rows: any[]; rowCount: number | null }>;
|
|
107
|
+
release(): void;
|
|
108
|
+
}>;
|
|
109
|
+
}
|
|
110
|
+
export function describeDatabase(registrations: ModelRegistration[], options?: { schema?: string }): DatabaseDescription;
|
|
111
|
+
/** Accepts a trusted description produced by describeDatabase. */
|
|
112
|
+
export function compileDatabaseSchema(description: DatabaseDescription): string[];
|
|
113
|
+
/** Borrows one pool client; owns its transaction, not the pool lifecycle. PostgreSQL >=15. */
|
|
114
|
+
export function initializeDatabase(pool: DatabasePool, description: DatabaseDescription, options?: { mode?: 'create' | 'validate' }): Promise<{ mode: 'create' | 'validate'; created: string[] }>;
|
|
115
|
+
|
|
116
|
+
export interface DatabaseQueryable {
|
|
117
|
+
query(sql: string, values?: any[]): Promise<{ rows: any[]; rowCount: number | null }>;
|
|
118
|
+
}
|
|
119
|
+
export interface PostgresPool extends DatabasePool, DatabaseQueryable {}
|
|
120
|
+
export interface PostgresSession extends DatabaseQueryable {
|
|
121
|
+
/** Native pg client, valid only for the lifetime of this transaction. */
|
|
122
|
+
readonly client: DatabaseQueryable;
|
|
123
|
+
inTransaction(): boolean;
|
|
124
|
+
}
|
|
125
|
+
export interface PostgresRecord {
|
|
126
|
+
_id: string;
|
|
127
|
+
id: string;
|
|
128
|
+
[field: string]: any;
|
|
129
|
+
}
|
|
130
|
+
export interface PostgresModel {
|
|
131
|
+
readonly name: string;
|
|
132
|
+
readonly gqltype: GraphQLObjectType;
|
|
133
|
+
findById(id: string, options?: { session?: PostgresSession }): Promise<PostgresRecord | null>;
|
|
134
|
+
/** Uses the same filter/sort/pagination arguments as the GraphQL list query. */
|
|
135
|
+
find(args?: Record<string, any>, options?: { session?: PostgresSession }): Promise<PostgresRecord[]>;
|
|
136
|
+
/** Native helpers bypass GraphQL validators/controllers; references use storage column names. */
|
|
137
|
+
create(data: Record<string, any>, options?: { session?: PostgresSession }): Promise<PostgresRecord>;
|
|
138
|
+
update(id: string, changes: Record<string, any>, options?: { session?: PostgresSession }): Promise<PostgresRecord | null>;
|
|
139
|
+
delete(id: string, options?: { session?: PostgresSession }): Promise<PostgresRecord | null>;
|
|
140
|
+
}
|
|
141
|
+
export interface PostgresConfiguration { pool: PostgresPool; schema?: string }
|
|
142
|
+
export interface InitializationOptions { mode?: 'create' | 'validate' }
|
|
143
|
+
export interface InitializationResult { mode: 'create' | 'validate'; created: string[] }
|
|
144
|
+
export interface PostgresRuntime extends Runtime<PostgresModel, PostgresSession> {
|
|
145
|
+
readonly auth: typeof auth;
|
|
146
|
+
readonly plugins: typeof plugins;
|
|
147
|
+
readonly scalars: typeof scalars;
|
|
148
|
+
readonly validators: typeof validators;
|
|
149
|
+
/** Bind pool/schema once, before createSchema. The caller owns the pool lifetime. */
|
|
150
|
+
configure(options: PostgresConfiguration): void;
|
|
151
|
+
describeDatabase(): DatabaseDescription;
|
|
152
|
+
/** Must be awaited before operations execute. Does not synchronize incompatible schemas. */
|
|
153
|
+
initializeDatabase(options?: InitializationOptions): Promise<InitializationResult>;
|
|
154
|
+
withTransaction<T>(session: PostgresSession | null | undefined, callback: (session: PostgresSession) => Promise<T> | T): Promise<T>;
|
|
155
|
+
}
|
|
156
|
+
export function createPostgres(options?: PostgresConfiguration): PostgresRuntime;
|
|
157
|
+
export const configure: PostgresRuntime['configure'];
|
|
158
|
+
export const connect: PostgresRuntime['connect'];
|
|
159
|
+
export const addNoEndpointType: PostgresRuntime['addNoEndpointType'];
|
|
160
|
+
export const createSchema: PostgresRuntime['createSchema'];
|
|
161
|
+
export const getModel: PostgresRuntime['getModel'];
|
|
162
|
+
export const getType: PostgresRuntime['getType'];
|
|
163
|
+
export const getInputType: PostgresRuntime['getInputType'];
|
|
164
|
+
export const getRegistrations: PostgresRuntime['getRegistrations'];
|
|
165
|
+
export const use: PostgresRuntime['use'];
|
|
166
|
+
export const registerMutation: PostgresRuntime['registerMutation'];
|
|
167
|
+
export const saveObject: PostgresRuntime['saveObject'];
|
|
168
|
+
export const preventCreatingCollection: PostgresRuntime['preventCreatingCollection'];
|
|
169
|
+
export const withTransaction: PostgresRuntime['withTransaction'];
|
|
170
|
+
/** Initializes the default module instance, after configure/connect/createSchema. */
|
|
171
|
+
export function initializeDatabase(options?: InitializationOptions): Promise<InitializationResult>;
|
|
172
|
+
|
|
173
|
+
export const configureQueryLimits: Runtime['configureQueryLimits'];
|