@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.
Files changed (152) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -0
  3. package/dist/agents/agent-storage.d.ts +20 -0
  4. package/dist/agents/agent-storage.d.ts.map +1 -0
  5. package/dist/agents/index.d.ts +6 -0
  6. package/dist/agents/index.d.ts.map +1 -0
  7. package/dist/agents/messages.d.ts +13 -0
  8. package/dist/agents/messages.d.ts.map +1 -0
  9. package/dist/agents/rows.d.ts +77 -0
  10. package/dist/agents/rows.d.ts.map +1 -0
  11. package/dist/agents/runs.d.ts +25 -0
  12. package/dist/agents/runs.d.ts.map +1 -0
  13. package/dist/agents/threads.d.ts +13 -0
  14. package/dist/agents/threads.d.ts.map +1 -0
  15. package/dist/auth-storage/access-tokens.d.ts +33 -0
  16. package/dist/auth-storage/access-tokens.d.ts.map +1 -0
  17. package/dist/auth-storage/auth-storage.d.ts +41 -0
  18. package/dist/auth-storage/auth-storage.d.ts.map +1 -0
  19. package/dist/auth-storage/group-memberships.d.ts +21 -0
  20. package/dist/auth-storage/group-memberships.d.ts.map +1 -0
  21. package/dist/auth-storage/identities.d.ts +17 -0
  22. package/dist/auth-storage/identities.d.ts.map +1 -0
  23. package/dist/auth-storage/index.d.ts +3 -0
  24. package/dist/auth-storage/index.d.ts.map +1 -0
  25. package/dist/auth-storage/invitations.d.ts +29 -0
  26. package/dist/auth-storage/invitations.d.ts.map +1 -0
  27. package/dist/auth-storage/magic-links.d.ts +28 -0
  28. package/dist/auth-storage/magic-links.d.ts.map +1 -0
  29. package/dist/auth-storage/oidc-attempts.d.ts +18 -0
  30. package/dist/auth-storage/oidc-attempts.d.ts.map +1 -0
  31. package/dist/auth-storage/rows.d.ts +138 -0
  32. package/dist/auth-storage/rows.d.ts.map +1 -0
  33. package/dist/auth-storage/service-account-group-memberships.d.ts +17 -0
  34. package/dist/auth-storage/service-account-group-memberships.d.ts.map +1 -0
  35. package/dist/auth-storage/service-accounts.d.ts +14 -0
  36. package/dist/auth-storage/service-accounts.d.ts.map +1 -0
  37. package/dist/auth-storage/sessions.d.ts +55 -0
  38. package/dist/auth-storage/sessions.d.ts.map +1 -0
  39. package/dist/auth-storage/shared.d.ts +159 -0
  40. package/dist/auth-storage/shared.d.ts.map +1 -0
  41. package/dist/auth-storage/users.d.ts +19 -0
  42. package/dist/auth-storage/users.d.ts.map +1 -0
  43. package/dist/index.d.ts +127 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +11957 -0
  46. package/dist/index.js.map +55 -0
  47. package/dist/latest-run-query.d.ts +24 -0
  48. package/dist/latest-run-query.d.ts.map +1 -0
  49. package/dist/migrations.d.ts +20 -0
  50. package/dist/migrations.d.ts.map +1 -0
  51. package/dist/ontology-storage/commits.d.ts +11 -0
  52. package/dist/ontology-storage/commits.d.ts.map +1 -0
  53. package/dist/ontology-storage/index.d.ts +22 -0
  54. package/dist/ontology-storage/index.d.ts.map +1 -0
  55. package/dist/ontology-storage/materialization-session.d.ts +63 -0
  56. package/dist/ontology-storage/materialization-session.d.ts.map +1 -0
  57. package/dist/ontology-storage/materialization-state.d.ts +69 -0
  58. package/dist/ontology-storage/materialization-state.d.ts.map +1 -0
  59. package/dist/ontology-storage/materialization-writer.d.ts +15 -0
  60. package/dist/ontology-storage/materialization-writer.d.ts.map +1 -0
  61. package/dist/ontology-storage/materializations.d.ts +33 -0
  62. package/dist/ontology-storage/materializations.d.ts.map +1 -0
  63. package/dist/ontology-storage/outbox.d.ts +12 -0
  64. package/dist/ontology-storage/outbox.d.ts.map +1 -0
  65. package/dist/ontology-storage/shared.d.ts +95 -0
  66. package/dist/ontology-storage/shared.d.ts.map +1 -0
  67. package/dist/ontology-storage/sources.d.ts +25 -0
  68. package/dist/ontology-storage/sources.d.ts.map +1 -0
  69. package/dist/pg-action-run-storage.d.ts +21 -0
  70. package/dist/pg-action-run-storage.d.ts.map +1 -0
  71. package/dist/pg-client.d.ts +58 -0
  72. package/dist/pg-client.d.ts.map +1 -0
  73. package/dist/pg-object-query-compiler.d.ts +55 -0
  74. package/dist/pg-object-query-compiler.d.ts.map +1 -0
  75. package/dist/pg-object-storage.d.ts +90 -0
  76. package/dist/pg-object-storage.d.ts.map +1 -0
  77. package/dist/pg-pipeline-run-storage.d.ts +18 -0
  78. package/dist/pg-pipeline-run-storage.d.ts.map +1 -0
  79. package/dist/pg-projection-run-storage.d.ts +19 -0
  80. package/dist/pg-projection-run-storage.d.ts.map +1 -0
  81. package/dist/pg-rules-storage.d.ts +25 -0
  82. package/dist/pg-rules-storage.d.ts.map +1 -0
  83. package/dist/pg-sync-run-storage.d.ts +15 -0
  84. package/dist/pg-sync-run-storage.d.ts.map +1 -0
  85. package/dist/pg-timeseries-storage.d.ts +32 -0
  86. package/dist/pg-timeseries-storage.d.ts.map +1 -0
  87. package/dist/pg-webhook-delivery-storage.d.ts +17 -0
  88. package/dist/pg-webhook-delivery-storage.d.ts.map +1 -0
  89. package/dist/pg-webhook-run-storage.d.ts +14 -0
  90. package/dist/pg-webhook-run-storage.d.ts.map +1 -0
  91. package/dist/pg-workflow-intervention-storage.d.ts +16 -0
  92. package/dist/pg-workflow-intervention-storage.d.ts.map +1 -0
  93. package/dist/pg-workflow-run-storage.d.ts +49 -0
  94. package/dist/pg-workflow-run-storage.d.ts.map +1 -0
  95. package/dist/run-list-query.d.ts +30 -0
  96. package/dist/run-list-query.d.ts.map +1 -0
  97. package/dist/storage-errors.d.ts +16 -0
  98. package/dist/storage-errors.d.ts.map +1 -0
  99. package/dist/transactions.d.ts +10 -0
  100. package/dist/transactions.d.ts.map +1 -0
  101. package/dist/tsconfig.json +5 -0
  102. package/package.json +60 -0
  103. package/src/agents/agent-storage.ts +26 -0
  104. package/src/agents/index.ts +5 -0
  105. package/src/agents/messages.ts +144 -0
  106. package/src/agents/rows.ts +227 -0
  107. package/src/agents/runs.ts +330 -0
  108. package/src/agents/threads.ts +116 -0
  109. package/src/auth-storage/access-tokens.ts +254 -0
  110. package/src/auth-storage/auth-storage.ts +798 -0
  111. package/src/auth-storage/group-memberships.ts +47 -0
  112. package/src/auth-storage/identities.ts +85 -0
  113. package/src/auth-storage/index.ts +2 -0
  114. package/src/auth-storage/invitations.ts +280 -0
  115. package/src/auth-storage/magic-links.ts +140 -0
  116. package/src/auth-storage/oidc-attempts.ts +93 -0
  117. package/src/auth-storage/rows.ts +352 -0
  118. package/src/auth-storage/service-account-group-memberships.ts +150 -0
  119. package/src/auth-storage/service-accounts.ts +154 -0
  120. package/src/auth-storage/sessions.ts +193 -0
  121. package/src/auth-storage/shared.ts +799 -0
  122. package/src/auth-storage/users.ts +196 -0
  123. package/src/index.ts +367 -0
  124. package/src/latest-run-query.ts +42 -0
  125. package/src/migrations/001-initial-schema.sql +1195 -0
  126. package/src/migrations.ts +280 -0
  127. package/src/ontology-storage/commits.ts +115 -0
  128. package/src/ontology-storage/index.ts +35 -0
  129. package/src/ontology-storage/materialization-session.ts +426 -0
  130. package/src/ontology-storage/materialization-state.ts +1109 -0
  131. package/src/ontology-storage/materialization-writer.ts +509 -0
  132. package/src/ontology-storage/materializations.ts +939 -0
  133. package/src/ontology-storage/outbox.ts +216 -0
  134. package/src/ontology-storage/shared.ts +293 -0
  135. package/src/ontology-storage/sources.ts +658 -0
  136. package/src/pg-action-run-storage.ts +657 -0
  137. package/src/pg-client.ts +124 -0
  138. package/src/pg-object-query-compiler.ts +1545 -0
  139. package/src/pg-object-storage.ts +638 -0
  140. package/src/pg-pipeline-run-storage.ts +457 -0
  141. package/src/pg-projection-run-storage.ts +526 -0
  142. package/src/pg-rules-storage.ts +308 -0
  143. package/src/pg-sync-run-storage.ts +263 -0
  144. package/src/pg-timeseries-storage.ts +180 -0
  145. package/src/pg-webhook-delivery-storage.ts +180 -0
  146. package/src/pg-webhook-run-storage.ts +185 -0
  147. package/src/pg-workflow-intervention-storage.ts +339 -0
  148. package/src/pg-workflow-run-storage.ts +1065 -0
  149. package/src/run-list-query.ts +108 -0
  150. package/src/sql.d.ts +4 -0
  151. package/src/storage-errors.ts +72 -0
  152. 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,4 @@
1
+ declare module "*.sql" {
2
+ const sql: string
3
+ export default sql
4
+ }
@@ -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
+ }