@fabrika/platform-node 0.3.3 → 0.4.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.
- package/package.json +7 -2
- package/src/application-postgres-grants.ts +117 -0
- package/src/application-postgres-migrations.ts +152 -0
- package/src/application-postgres-roles.ts +239 -0
- package/src/application-schema.ts +21 -0
- package/src/blob-s3.ts +88 -2
- package/src/database-code.ts +164 -0
- package/src/file-table-ddl.ts +96 -0
- package/src/index.ts +21 -0
- package/src/migration-classify.ts +1044 -0
- package/src/migration-sql.ts +204 -0
- package/src/pg-catalog-names.ts +359 -0
- package/src/postgres-migration-error.ts +4 -0
- package/src/postgres-migrations.ts +270 -25
- package/src/postgres-test-provisioner.ts +96 -0
- package/src/sql-names.ts +740 -0
- package/src/sql-postgres.ts +56 -22
- package/src/sql.ts +52 -0
package/src/sql-postgres.ts
CHANGED
|
@@ -56,31 +56,35 @@ export class PostgresStatement implements SqlStatement {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
async first<T = Record<string, unknown>>(): Promise<T | null> {
|
|
59
|
-
const
|
|
59
|
+
const statement = this.bound()
|
|
60
|
+
const rows = await onReservedConnection(this.sql, (connection) => statement.rows<T>(connection))
|
|
60
61
|
return rows[0] ?? null
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
async all<T = Record<string, unknown>>(): Promise<SqlQueryResult<T>> {
|
|
64
|
-
|
|
65
|
+
const statement = this.bound()
|
|
66
|
+
return { results: await onReservedConnection(this.sql, (connection) => statement.rows<T>(connection)) }
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
async run(): Promise<SqlRunResult> {
|
|
68
|
-
const
|
|
70
|
+
const statement = this.bound()
|
|
71
|
+
const result: unknown = await onReservedConnection(this.sql, (connection) => statement.rows<Record<string, unknown>>(connection))
|
|
69
72
|
return { meta: { changes: changesOf(result) } }
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
/**
|
|
73
|
-
|
|
74
|
-
* opens. Internal to the package; the port only ever sees `first`/`all`/`run`.
|
|
75
|
-
*/
|
|
76
|
-
async rows<T>(handle: Bun.SQL): Promise<T[]> {
|
|
77
|
-
return this.execute<T>(handle)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
private execute<T>(handle: Bun.SQL): Promise<T[]> {
|
|
75
|
+
/** This statement, once every placeholder has its value: a wrong count fails before any connection is taken. */
|
|
76
|
+
bound(): PostgresStatement {
|
|
81
77
|
if (this.values.length !== this.placeholders) {
|
|
82
78
|
throw new Error(`statement expects ${this.placeholders} bound value(s), got ${this.values.length}`)
|
|
83
79
|
}
|
|
80
|
+
return this
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Run this statement on a specific handle — a reserved connection of the pool, or the one `batch()` holds its
|
|
85
|
+
* transaction on. Internal to the package; the port only ever sees `first`/`all`/`run`.
|
|
86
|
+
*/
|
|
87
|
+
rows<T>(handle: Bun.SQL): Promise<T[]> {
|
|
84
88
|
return handle.unsafe<T[]>(this.text, [...this.values])
|
|
85
89
|
}
|
|
86
90
|
}
|
|
@@ -111,19 +115,33 @@ export class PostgresDatabase implements SqlDatabase {
|
|
|
111
115
|
return new PostgresStatement(this.sql, rewritten.text, rewritten.count, [])
|
|
112
116
|
}
|
|
113
117
|
|
|
114
|
-
/**
|
|
118
|
+
/**
|
|
119
|
+
* Run every statement in ONE transaction, in order. A throw rolls the whole batch back. The transaction is sent by
|
|
120
|
+
* hand on the reserved connection: Bun 1.3.14's `begin()` on a reserved connection rejects a failed statement twice,
|
|
121
|
+
* once unhandled.
|
|
122
|
+
*/
|
|
115
123
|
async batch<T = Record<string, unknown>>(statements: SqlStatement[]): Promise<SqlQueryResult<T>[]> {
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
124
|
+
const own = statements.map((statement) => {
|
|
125
|
+
if (!(statement instanceof PostgresStatement)) {
|
|
126
|
+
throw new Error('batch() accepts only statements created by this database')
|
|
127
|
+
}
|
|
128
|
+
return statement.bound()
|
|
129
|
+
})
|
|
130
|
+
return onReservedConnection(this.sql, async (connection) => {
|
|
131
|
+
await connection.unsafe('BEGIN')
|
|
132
|
+
try {
|
|
133
|
+
const out: SqlQueryResult<T>[] = []
|
|
134
|
+
for (const statement of own) {
|
|
135
|
+
out.push({ results: await statement.rows<T>(connection) })
|
|
121
136
|
}
|
|
122
|
-
|
|
137
|
+
await connection.unsafe('COMMIT')
|
|
138
|
+
return out
|
|
139
|
+
} catch (error) {
|
|
140
|
+
// ROLLBACK fails only on a connection that is gone; the statement's own error is the one to report.
|
|
141
|
+
await connection.unsafe('ROLLBACK').catch(() => undefined)
|
|
142
|
+
throw error
|
|
123
143
|
}
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
return this.sql.begin(run)
|
|
144
|
+
})
|
|
127
145
|
}
|
|
128
146
|
|
|
129
147
|
/** Close the pool. Waits for in-flight queries unless a timeout is given. */
|
|
@@ -132,6 +150,22 @@ export class PostgresDatabase implements SqlDatabase {
|
|
|
132
150
|
}
|
|
133
151
|
}
|
|
134
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Runs `work` on a connection reserved from `pool` and releases it afterwards; every statement and every batch goes
|
|
155
|
+
* through here. Bun 1.3.14's pool sends an unreserved query onto a connection that holds another caller's
|
|
156
|
+
* transaction, where it waits for that transaction or runs inside it, and a burst of batches larger than the pool
|
|
157
|
+
* hangs. Once every use is reserved, the pool has no unreserved query to send.
|
|
158
|
+
*/
|
|
159
|
+
async function onReservedConnection<T>(pool: Bun.SQL, work: (connection: Bun.SQL) => Promise<T>): Promise<T> {
|
|
160
|
+
const connection = await pool.reserve()
|
|
161
|
+
try {
|
|
162
|
+
return await work(connection)
|
|
163
|
+
} finally {
|
|
164
|
+
// Typed `void`, `release()` answers a promise that rejects once the pool has closed the connection.
|
|
165
|
+
Promise.resolve(connection.release()).catch(() => undefined)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
135
169
|
/** The rows-affected count D1 would report, or 0 for a statement that changes nothing. */
|
|
136
170
|
function changesOf(result: unknown): number {
|
|
137
171
|
const command: unknown = readProperty(result, 'command')
|
package/src/sql.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// @fabrika/platform-node/sql — the SQL lexer and the migration classifier, RUNTIME-NEUTRAL: no Bun, no Node, no
|
|
2
|
+
// driver, so a Cloudflare Worker (Control reading a commit) loads it as the runner does. `sql-subpath.test.ts` holds
|
|
3
|
+
// the import graph to that.
|
|
4
|
+
|
|
5
|
+
export { applicationDatabaseSchema } from './application-schema'
|
|
6
|
+
export {
|
|
7
|
+
FILE_SCAN_COLUMNS,
|
|
8
|
+
FILE_STATUSES,
|
|
9
|
+
FILE_TABLE_PREFIX,
|
|
10
|
+
fileScanAlter,
|
|
11
|
+
fileScanAlterTable,
|
|
12
|
+
fileScanIndex,
|
|
13
|
+
fileStatusCheck,
|
|
14
|
+
fileTableScanReady,
|
|
15
|
+
legacyStatusCheckName,
|
|
16
|
+
} from './file-table-ddl'
|
|
17
|
+
export {
|
|
18
|
+
classifyMigration,
|
|
19
|
+
type ClassifyMigrationOptions,
|
|
20
|
+
classifyMigrations,
|
|
21
|
+
type DatabaseObject,
|
|
22
|
+
type DatabaseObjectKind,
|
|
23
|
+
declaredFunctions,
|
|
24
|
+
type DeviationKind,
|
|
25
|
+
type ManifestChange,
|
|
26
|
+
type MigrationClassification,
|
|
27
|
+
type MigrationDeviation,
|
|
28
|
+
type MigrationFile,
|
|
29
|
+
migrationManifest,
|
|
30
|
+
type MigrationRefusal,
|
|
31
|
+
type MigrationSetClassification,
|
|
32
|
+
} from './migration-classify'
|
|
33
|
+
export { lexSql, type SqlToken, sqlTokens, type Statement, topLevelStatements, upperWord } from './migration-sql'
|
|
34
|
+
export { PG_CATALOG_FUNCTIONS, SQL_KEYWORDS } from './pg-catalog-names'
|
|
35
|
+
export {
|
|
36
|
+
ddlForeignSchema,
|
|
37
|
+
dmlForeignSchema,
|
|
38
|
+
dmlRelations,
|
|
39
|
+
frameworkReference,
|
|
40
|
+
type FunctionUse,
|
|
41
|
+
functionUse,
|
|
42
|
+
isFrameworkName,
|
|
43
|
+
type NameReference,
|
|
44
|
+
nameReferences,
|
|
45
|
+
type NameRole,
|
|
46
|
+
nameText,
|
|
47
|
+
nestedStatement,
|
|
48
|
+
parenthesizedTarget,
|
|
49
|
+
refusedFunctionCall,
|
|
50
|
+
statementKind,
|
|
51
|
+
unicodeIdentifier,
|
|
52
|
+
} from './sql-names'
|