@sixb/pg 0.0.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/LICENSE +21 -0
- package/README.md +73 -0
- package/dist/agents/agent-storage.d.ts +20 -0
- package/dist/agents/agent-storage.d.ts.map +1 -0
- package/dist/agents/index.d.ts +6 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/messages.d.ts +13 -0
- package/dist/agents/messages.d.ts.map +1 -0
- package/dist/agents/rows.d.ts +77 -0
- package/dist/agents/rows.d.ts.map +1 -0
- package/dist/agents/runs.d.ts +25 -0
- package/dist/agents/runs.d.ts.map +1 -0
- package/dist/agents/threads.d.ts +13 -0
- package/dist/agents/threads.d.ts.map +1 -0
- package/dist/auth-storage/access-tokens.d.ts +33 -0
- package/dist/auth-storage/access-tokens.d.ts.map +1 -0
- package/dist/auth-storage/auth-storage.d.ts +41 -0
- package/dist/auth-storage/auth-storage.d.ts.map +1 -0
- package/dist/auth-storage/group-memberships.d.ts +21 -0
- package/dist/auth-storage/group-memberships.d.ts.map +1 -0
- package/dist/auth-storage/identities.d.ts +17 -0
- package/dist/auth-storage/identities.d.ts.map +1 -0
- package/dist/auth-storage/index.d.ts +3 -0
- package/dist/auth-storage/index.d.ts.map +1 -0
- package/dist/auth-storage/invitations.d.ts +29 -0
- package/dist/auth-storage/invitations.d.ts.map +1 -0
- package/dist/auth-storage/magic-links.d.ts +28 -0
- package/dist/auth-storage/magic-links.d.ts.map +1 -0
- package/dist/auth-storage/oidc-attempts.d.ts +18 -0
- package/dist/auth-storage/oidc-attempts.d.ts.map +1 -0
- package/dist/auth-storage/rows.d.ts +138 -0
- package/dist/auth-storage/rows.d.ts.map +1 -0
- package/dist/auth-storage/service-account-group-memberships.d.ts +17 -0
- package/dist/auth-storage/service-account-group-memberships.d.ts.map +1 -0
- package/dist/auth-storage/service-accounts.d.ts +14 -0
- package/dist/auth-storage/service-accounts.d.ts.map +1 -0
- package/dist/auth-storage/sessions.d.ts +55 -0
- package/dist/auth-storage/sessions.d.ts.map +1 -0
- package/dist/auth-storage/shared.d.ts +159 -0
- package/dist/auth-storage/shared.d.ts.map +1 -0
- package/dist/auth-storage/users.d.ts +19 -0
- package/dist/auth-storage/users.d.ts.map +1 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11957 -0
- package/dist/index.js.map +55 -0
- package/dist/latest-run-query.d.ts +24 -0
- package/dist/latest-run-query.d.ts.map +1 -0
- package/dist/migrations.d.ts +20 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/ontology-storage/commits.d.ts +11 -0
- package/dist/ontology-storage/commits.d.ts.map +1 -0
- package/dist/ontology-storage/index.d.ts +22 -0
- package/dist/ontology-storage/index.d.ts.map +1 -0
- package/dist/ontology-storage/materialization-session.d.ts +63 -0
- package/dist/ontology-storage/materialization-session.d.ts.map +1 -0
- package/dist/ontology-storage/materialization-state.d.ts +69 -0
- package/dist/ontology-storage/materialization-state.d.ts.map +1 -0
- package/dist/ontology-storage/materialization-writer.d.ts +15 -0
- package/dist/ontology-storage/materialization-writer.d.ts.map +1 -0
- package/dist/ontology-storage/materializations.d.ts +33 -0
- package/dist/ontology-storage/materializations.d.ts.map +1 -0
- package/dist/ontology-storage/outbox.d.ts +12 -0
- package/dist/ontology-storage/outbox.d.ts.map +1 -0
- package/dist/ontology-storage/shared.d.ts +95 -0
- package/dist/ontology-storage/shared.d.ts.map +1 -0
- package/dist/ontology-storage/sources.d.ts +25 -0
- package/dist/ontology-storage/sources.d.ts.map +1 -0
- package/dist/pg-action-run-storage.d.ts +21 -0
- package/dist/pg-action-run-storage.d.ts.map +1 -0
- package/dist/pg-client.d.ts +58 -0
- package/dist/pg-client.d.ts.map +1 -0
- package/dist/pg-object-query-compiler.d.ts +55 -0
- package/dist/pg-object-query-compiler.d.ts.map +1 -0
- package/dist/pg-object-storage.d.ts +90 -0
- package/dist/pg-object-storage.d.ts.map +1 -0
- package/dist/pg-pipeline-run-storage.d.ts +18 -0
- package/dist/pg-pipeline-run-storage.d.ts.map +1 -0
- package/dist/pg-projection-run-storage.d.ts +19 -0
- package/dist/pg-projection-run-storage.d.ts.map +1 -0
- package/dist/pg-rules-storage.d.ts +25 -0
- package/dist/pg-rules-storage.d.ts.map +1 -0
- package/dist/pg-sync-run-storage.d.ts +15 -0
- package/dist/pg-sync-run-storage.d.ts.map +1 -0
- package/dist/pg-timeseries-storage.d.ts +32 -0
- package/dist/pg-timeseries-storage.d.ts.map +1 -0
- package/dist/pg-webhook-delivery-storage.d.ts +17 -0
- package/dist/pg-webhook-delivery-storage.d.ts.map +1 -0
- package/dist/pg-webhook-run-storage.d.ts +14 -0
- package/dist/pg-webhook-run-storage.d.ts.map +1 -0
- package/dist/pg-workflow-intervention-storage.d.ts +16 -0
- package/dist/pg-workflow-intervention-storage.d.ts.map +1 -0
- package/dist/pg-workflow-run-storage.d.ts +49 -0
- package/dist/pg-workflow-run-storage.d.ts.map +1 -0
- package/dist/run-list-query.d.ts +30 -0
- package/dist/run-list-query.d.ts.map +1 -0
- package/dist/storage-errors.d.ts +16 -0
- package/dist/storage-errors.d.ts.map +1 -0
- package/dist/transactions.d.ts +10 -0
- package/dist/transactions.d.ts.map +1 -0
- package/dist/tsconfig.json +5 -0
- package/package.json +60 -0
- package/src/agents/agent-storage.ts +26 -0
- package/src/agents/index.ts +5 -0
- package/src/agents/messages.ts +144 -0
- package/src/agents/rows.ts +227 -0
- package/src/agents/runs.ts +330 -0
- package/src/agents/threads.ts +116 -0
- package/src/auth-storage/access-tokens.ts +254 -0
- package/src/auth-storage/auth-storage.ts +798 -0
- package/src/auth-storage/group-memberships.ts +47 -0
- package/src/auth-storage/identities.ts +85 -0
- package/src/auth-storage/index.ts +2 -0
- package/src/auth-storage/invitations.ts +280 -0
- package/src/auth-storage/magic-links.ts +140 -0
- package/src/auth-storage/oidc-attempts.ts +93 -0
- package/src/auth-storage/rows.ts +352 -0
- package/src/auth-storage/service-account-group-memberships.ts +150 -0
- package/src/auth-storage/service-accounts.ts +154 -0
- package/src/auth-storage/sessions.ts +193 -0
- package/src/auth-storage/shared.ts +799 -0
- package/src/auth-storage/users.ts +196 -0
- package/src/index.ts +367 -0
- package/src/latest-run-query.ts +42 -0
- package/src/migrations/001-initial-schema.sql +1195 -0
- package/src/migrations.ts +280 -0
- package/src/ontology-storage/commits.ts +115 -0
- package/src/ontology-storage/index.ts +35 -0
- package/src/ontology-storage/materialization-session.ts +426 -0
- package/src/ontology-storage/materialization-state.ts +1109 -0
- package/src/ontology-storage/materialization-writer.ts +509 -0
- package/src/ontology-storage/materializations.ts +939 -0
- package/src/ontology-storage/outbox.ts +216 -0
- package/src/ontology-storage/shared.ts +293 -0
- package/src/ontology-storage/sources.ts +658 -0
- package/src/pg-action-run-storage.ts +657 -0
- package/src/pg-client.ts +124 -0
- package/src/pg-object-query-compiler.ts +1545 -0
- package/src/pg-object-storage.ts +638 -0
- package/src/pg-pipeline-run-storage.ts +457 -0
- package/src/pg-projection-run-storage.ts +526 -0
- package/src/pg-rules-storage.ts +308 -0
- package/src/pg-sync-run-storage.ts +263 -0
- package/src/pg-timeseries-storage.ts +180 -0
- package/src/pg-webhook-delivery-storage.ts +180 -0
- package/src/pg-webhook-run-storage.ts +185 -0
- package/src/pg-workflow-intervention-storage.ts +339 -0
- package/src/pg-workflow-run-storage.ts +1065 -0
- package/src/run-list-query.ts +108 -0
- package/src/sql.d.ts +4 -0
- package/src/storage-errors.ts +72 -0
- package/src/transactions.ts +58 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { SQLClient, SqlParameter } from "./pg-client"
|
|
2
|
+
|
|
3
|
+
type PgRunListTable =
|
|
4
|
+
| "agent_runs"
|
|
5
|
+
| "pipeline_runs"
|
|
6
|
+
| "pipeline_step_runs"
|
|
7
|
+
| "sync_runs"
|
|
8
|
+
| "webhook_runs"
|
|
9
|
+
| "workflow_runs"
|
|
10
|
+
| "workflow_node_runs"
|
|
11
|
+
| "workflow_agent_node_runs"
|
|
12
|
+
|
|
13
|
+
/** Column projection — `sync_runs` needs the derived `checkpoint_present` flag. */
|
|
14
|
+
type PgRunListSelectList = "*" | "*, checkpoint IS NOT NULL AS checkpoint_present"
|
|
15
|
+
type StartedAtExpression = "started_at" | "COALESCE(started_at, created_at)"
|
|
16
|
+
|
|
17
|
+
export function hasEmptyStatuses(input: { readonly statuses?: readonly unknown[] }): boolean {
|
|
18
|
+
return input.statuses !== undefined && input.statuses.length === 0
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function appendRunListFilters(
|
|
22
|
+
whereClauses: string[],
|
|
23
|
+
params: unknown[],
|
|
24
|
+
index: number,
|
|
25
|
+
input: {
|
|
26
|
+
readonly statuses?: readonly string[]
|
|
27
|
+
readonly startedAfter?: Date
|
|
28
|
+
readonly startedBefore?: Date
|
|
29
|
+
},
|
|
30
|
+
startedAtExpression: StartedAtExpression = "started_at"
|
|
31
|
+
): number {
|
|
32
|
+
let nextIndex = index
|
|
33
|
+
|
|
34
|
+
if (input.statuses) {
|
|
35
|
+
if (input.statuses.length === 0) {
|
|
36
|
+
whereClauses.push("1 = 0")
|
|
37
|
+
} else {
|
|
38
|
+
const placeholders = input.statuses.map(() => `$${nextIndex++}`)
|
|
39
|
+
whereClauses.push(`status IN (${placeholders.join(", ")})`)
|
|
40
|
+
params.push(...input.statuses)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (input.startedAfter) {
|
|
45
|
+
whereClauses.push(`${startedAtExpression} >= $${nextIndex++}`)
|
|
46
|
+
params.push(input.startedAfter)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (input.startedBefore) {
|
|
50
|
+
whereClauses.push(`${startedAtExpression} <= $${nextIndex++}`)
|
|
51
|
+
params.push(input.startedBefore)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return nextIndex
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function queryRunList<TRow>(input: {
|
|
58
|
+
readonly sql: SQLClient
|
|
59
|
+
readonly tableName: PgRunListTable
|
|
60
|
+
readonly whereClauses: readonly string[]
|
|
61
|
+
readonly params: readonly unknown[]
|
|
62
|
+
readonly nextIndex: number
|
|
63
|
+
readonly order?: "asc" | "desc"
|
|
64
|
+
readonly limit?: number
|
|
65
|
+
readonly offset?: number
|
|
66
|
+
readonly selectList?: PgRunListSelectList
|
|
67
|
+
}): Promise<{ readonly rows: readonly TRow[]; readonly total: number; readonly hasMore: boolean }> {
|
|
68
|
+
const where = `WHERE ${input.whereClauses.join(" AND ")}`
|
|
69
|
+
const order = input.order === "asc" ? "ASC" : "DESC"
|
|
70
|
+
const offset = input.offset ?? 0
|
|
71
|
+
|
|
72
|
+
const [totalRow] = await input.sql.unsafe<{ count: string | number }[]>(
|
|
73
|
+
`SELECT COUNT(*)::bigint AS count FROM ${input.tableName} ${where}`,
|
|
74
|
+
[...input.params] as SqlParameter[]
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
const queryParams = [...input.params] as SqlParameter[]
|
|
78
|
+
const orderColumn =
|
|
79
|
+
input.tableName === "agent_runs"
|
|
80
|
+
? "COALESCE(started_at, created_at)"
|
|
81
|
+
: input.tableName === "workflow_agent_node_runs"
|
|
82
|
+
? "created_at"
|
|
83
|
+
: "started_at"
|
|
84
|
+
const idColumn = input.tableName === "workflow_agent_node_runs" ? "node_run_id" : "id"
|
|
85
|
+
let query = `
|
|
86
|
+
SELECT ${input.selectList ?? "*"} FROM ${input.tableName}
|
|
87
|
+
${where}
|
|
88
|
+
ORDER BY ${orderColumn} ${order}, ${idColumn} ${order}
|
|
89
|
+
`
|
|
90
|
+
let nextIndex = input.nextIndex
|
|
91
|
+
|
|
92
|
+
if (input.limit !== undefined) {
|
|
93
|
+
query += ` LIMIT $${nextIndex++} OFFSET $${nextIndex++}`
|
|
94
|
+
queryParams.push(input.limit, offset)
|
|
95
|
+
} else if (offset > 0) {
|
|
96
|
+
query += ` OFFSET $${nextIndex++}`
|
|
97
|
+
queryParams.push(offset)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const rows = await input.sql.unsafe<TRow[]>(query, queryParams)
|
|
101
|
+
const total = Number(totalRow?.count ?? 0)
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
rows,
|
|
105
|
+
total,
|
|
106
|
+
hasMore: offset + rows.length < total,
|
|
107
|
+
}
|
|
108
|
+
}
|
package/src/sql.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Map thrown errors to their PostgreSQL/porsager meaning by code rather than by matching
|
|
2
|
+
// message text. Database errors arrive in the native Postgres format with a SQLSTATE on
|
|
3
|
+
// `.code`; connection-layer failures arrive with porsager's own codes or, for Node socket
|
|
4
|
+
// errors, as an `AggregateError` of `ECONNREFUSED`/`ETIMEDOUT`/… entries.
|
|
5
|
+
|
|
6
|
+
/** PostgreSQL SQLSTATE for a unique-constraint violation. */
|
|
7
|
+
const UNIQUE_VIOLATION = "23505"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* PostgreSQL SQLSTATEs in class 40 (transaction rollback) that are transient and safe to retry by
|
|
11
|
+
* replaying the whole transaction: `40001` (serialization_failure) and `40P01` (deadlock_detected).
|
|
12
|
+
* Under `SERIALIZABLE` the server raises *either* — a serialization anomaly surfaces as `40001`,
|
|
13
|
+
* while two transactions taking row locks in crossing order surface as `40P01` — and both clear on
|
|
14
|
+
* a fresh attempt. The other class-40 codes (`40002` integrity-constraint violation, `40003`
|
|
15
|
+
* statement-completion unknown) are deliberately excluded: they are not safe to blindly replay.
|
|
16
|
+
*/
|
|
17
|
+
const RETRYABLE_TRANSACTION_CONFLICT_CODES = new Set(["40001", "40P01"])
|
|
18
|
+
|
|
19
|
+
const CONNECTION_ERROR_CODES = new Set([
|
|
20
|
+
// porsager-specific
|
|
21
|
+
"CONNECT_TIMEOUT",
|
|
22
|
+
"CONNECTION_CLOSED",
|
|
23
|
+
"CONNECTION_DESTROYED",
|
|
24
|
+
"CONNECTION_ENDED",
|
|
25
|
+
// Node socket errors
|
|
26
|
+
"ECONNREFUSED",
|
|
27
|
+
"ECONNRESET",
|
|
28
|
+
"ETIMEDOUT",
|
|
29
|
+
"ENOTFOUND",
|
|
30
|
+
"EAI_AGAIN",
|
|
31
|
+
])
|
|
32
|
+
|
|
33
|
+
/** The SQLSTATE / system error code carried by a thrown database or connection error. */
|
|
34
|
+
export function pgErrorCode(error: unknown): string | undefined {
|
|
35
|
+
if (error instanceof Error) {
|
|
36
|
+
const code = (error as { readonly code?: unknown }).code
|
|
37
|
+
if (typeof code === "string") {
|
|
38
|
+
return code
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return undefined
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** True when the error is a PostgreSQL unique-constraint violation (SQLSTATE 23505). */
|
|
45
|
+
export function isUniqueViolation(error: unknown): boolean {
|
|
46
|
+
return pgErrorCode(error) === UNIQUE_VIOLATION
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* True when the error is a transient transaction-rollback conflict that is safe to retry by
|
|
51
|
+
* replaying the transaction — a serialization failure (SQLSTATE 40001) or a deadlock (40P01).
|
|
52
|
+
* See {@link RETRYABLE_TRANSACTION_CONFLICT_CODES}.
|
|
53
|
+
*/
|
|
54
|
+
export function isRetryableTransactionConflict(error: unknown): boolean {
|
|
55
|
+
const code = pgErrorCode(error)
|
|
56
|
+
return code !== undefined && RETRYABLE_TRANSACTION_CONFLICT_CODES.has(code)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* True when the error originates from the connection layer (porsager connection codes, or a
|
|
61
|
+
* Node socket failure surfaced as an `AggregateError`) rather than from a SQL statement.
|
|
62
|
+
*/
|
|
63
|
+
export function isConnectionError(error: unknown): boolean {
|
|
64
|
+
const code = pgErrorCode(error)
|
|
65
|
+
if (code !== undefined && CONNECTION_ERROR_CODES.has(code)) {
|
|
66
|
+
return true
|
|
67
|
+
}
|
|
68
|
+
if (error instanceof AggregateError) {
|
|
69
|
+
return error.errors.some(isConnectionError)
|
|
70
|
+
}
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createHash } from "node:crypto"
|
|
2
|
+
import type { SQL, SQLClient } from "./pg-client"
|
|
3
|
+
|
|
4
|
+
export type PgStoreClient = SQL | SQLClient
|
|
5
|
+
|
|
6
|
+
export interface RunPgTransactionOptions {
|
|
7
|
+
/** Open the transaction at `SERIALIZABLE`; omit for the server default (`READ COMMITTED`). */
|
|
8
|
+
readonly isolation?: "serializable"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function runPgTransaction<T>(
|
|
12
|
+
sql: PgStoreClient,
|
|
13
|
+
run: (tx: SQLClient) => Promise<T>,
|
|
14
|
+
options: RunPgTransactionOptions = {}
|
|
15
|
+
): Promise<T> {
|
|
16
|
+
if (!canStartPgTransaction(sql)) {
|
|
17
|
+
return run(sql)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// porsager passes the callback a transaction-scoped client (a TransactionSql, which is an
|
|
21
|
+
// ISql == SQLClient). The `as Promise<T>` only unwraps porsager's UnwrapPromiseArray return
|
|
22
|
+
// type (our callbacks never return the pipelined-array form), not a structural cast.
|
|
23
|
+
//
|
|
24
|
+
// The isolation level is folded into `BEGIN` (`BEGIN ISOLATION LEVEL SERIALIZABLE`) rather than
|
|
25
|
+
// a separate `SET TRANSACTION` statement: one round-trip instead of two, and the level is
|
|
26
|
+
// guaranteed to take effect before any data statement of the transaction runs.
|
|
27
|
+
if (options.isolation === "serializable") {
|
|
28
|
+
return sql.begin("isolation level serializable", run) as Promise<T>
|
|
29
|
+
}
|
|
30
|
+
return sql.begin(run) as Promise<T>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function authLockKey(kind: string, ...parts: readonly string[]): string {
|
|
34
|
+
return ["auth", kind, ...parts].join(":")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function lockAdvisoryKeys(sql: SQLClient, keys: readonly string[]): Promise<void> {
|
|
38
|
+
const locks = [...new Set(keys)].sort().map(advisoryLockParts)
|
|
39
|
+
if (locks.length === 0) return
|
|
40
|
+
|
|
41
|
+
await sql`
|
|
42
|
+
SELECT pg_advisory_xact_lock(locks.first_key, locks.second_key)
|
|
43
|
+
FROM unnest(
|
|
44
|
+
${sql.array(locks.map(([first]) => first))}::integer[],
|
|
45
|
+
${sql.array(locks.map(([, second]) => second))}::integer[]
|
|
46
|
+
) WITH ORDINALITY AS locks(first_key, second_key, lock_order)
|
|
47
|
+
ORDER BY locks.lock_order
|
|
48
|
+
`
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function advisoryLockParts(key: string): readonly [number, number] {
|
|
52
|
+
const hash = createHash("sha256").update(key).digest()
|
|
53
|
+
return [hash.readInt32BE(0), hash.readInt32BE(4)]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function canStartPgTransaction(sql: PgStoreClient): sql is SQL {
|
|
57
|
+
return typeof (sql as { readonly begin?: unknown }).begin === "function"
|
|
58
|
+
}
|