orez 0.2.26 → 0.2.29

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 (172) hide show
  1. package/dist/cf-do/worker.d.ts.map +1 -1
  2. package/dist/cf-do/worker.js +9 -1
  3. package/dist/cf-do/worker.js.map +1 -1
  4. package/dist/pg-proxy-do-backend.d.ts +2 -0
  5. package/dist/pg-proxy-do-backend.d.ts.map +1 -1
  6. package/dist/pg-proxy-do-backend.js +49 -7
  7. package/dist/pg-proxy-do-backend.js.map +1 -1
  8. package/dist/pg-sqlite-compiler/catalog/seed.d.ts +67 -0
  9. package/dist/pg-sqlite-compiler/catalog/seed.d.ts.map +1 -0
  10. package/dist/pg-sqlite-compiler/catalog/seed.js +436 -0
  11. package/dist/pg-sqlite-compiler/catalog/seed.js.map +1 -0
  12. package/dist/pg-sqlite-compiler/index.d.ts +12 -0
  13. package/dist/pg-sqlite-compiler/index.d.ts.map +1 -0
  14. package/dist/pg-sqlite-compiler/index.js +59 -0
  15. package/dist/pg-sqlite-compiler/index.js.map +1 -0
  16. package/dist/pg-sqlite-compiler/passes/ast-utils.d.ts +48 -0
  17. package/dist/pg-sqlite-compiler/passes/ast-utils.d.ts.map +1 -0
  18. package/dist/pg-sqlite-compiler/passes/ast-utils.js +93 -0
  19. package/dist/pg-sqlite-compiler/passes/ast-utils.js.map +1 -0
  20. package/dist/pg-sqlite-compiler/passes/catalog.d.ts +34 -0
  21. package/dist/pg-sqlite-compiler/passes/catalog.d.ts.map +1 -0
  22. package/dist/pg-sqlite-compiler/passes/catalog.js +30 -0
  23. package/dist/pg-sqlite-compiler/passes/catalog.js.map +1 -0
  24. package/dist/pg-sqlite-compiler/passes/datetime.d.ts +21 -0
  25. package/dist/pg-sqlite-compiler/passes/datetime.d.ts.map +1 -0
  26. package/dist/pg-sqlite-compiler/passes/datetime.js +53 -0
  27. package/dist/pg-sqlite-compiler/passes/datetime.js.map +1 -0
  28. package/dist/pg-sqlite-compiler/passes/index.d.ts +21 -0
  29. package/dist/pg-sqlite-compiler/passes/index.d.ts.map +1 -0
  30. package/dist/pg-sqlite-compiler/passes/index.js +39 -0
  31. package/dist/pg-sqlite-compiler/passes/index.js.map +1 -0
  32. package/dist/pg-sqlite-compiler/passes/types.d.ts +41 -0
  33. package/dist/pg-sqlite-compiler/passes/types.d.ts.map +1 -0
  34. package/dist/pg-sqlite-compiler/passes/types.js +103 -0
  35. package/dist/pg-sqlite-compiler/passes/types.js.map +1 -0
  36. package/dist/pg-sqlite-compiler/test/oracle.d.ts +34 -0
  37. package/dist/pg-sqlite-compiler/test/oracle.d.ts.map +1 -0
  38. package/dist/pg-sqlite-compiler/test/oracle.js +204 -0
  39. package/dist/pg-sqlite-compiler/test/oracle.js.map +1 -0
  40. package/dist/pg-sqlite-compiler/types.d.ts +55 -0
  41. package/dist/pg-sqlite-compiler/types.d.ts.map +1 -0
  42. package/dist/pg-sqlite-compiler/types.js +2 -0
  43. package/dist/pg-sqlite-compiler/types.js.map +1 -0
  44. package/package.json +8 -4
  45. package/src/admin/admin-data.test.ts +0 -348
  46. package/src/admin/http-proxy.ts +0 -252
  47. package/src/admin/log-store.ts +0 -192
  48. package/src/admin/server.ts +0 -471
  49. package/src/admin/ui.ts +0 -1322
  50. package/src/bench/proxy-throughput.bench.ts +0 -343
  51. package/src/bench/serial-mutations.bench.ts +0 -270
  52. package/src/browser.ts +0 -203
  53. package/src/cf-do/.wrangler/cache/cf.json +0 -1
  54. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite +0 -0
  55. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm +0 -0
  56. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal +0 -0
  57. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/0f0f3bdf0abda097eb6f1246db4657d9fc622081362d894d82c1a1ce067b05b6.sqlite +0 -0
  58. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/1ddd3a4a48a11b51658444f5458a1fb175194b1d5b6a5bda20ef3fe3205b900c.sqlite +0 -0
  59. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/204a39120310d37e972c5914cfd71ad55c151bdb9e8ed289a5f8c5b052dd60e4.sqlite +0 -0
  60. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/3835f242df9728adba3d127a238793fd054ed3e51df3f60749ee744c469bf2a2.sqlite +0 -0
  61. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/4aa9c80eb716cf55b8995ccf7afab0b36c683e6da07d7c37a3f9c570136036df.sqlite +0 -0
  62. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/533e2fd1d6ea46e7a9a0017916ef341802d438d72583462755f2c1f8225e9bf2.sqlite +0 -0
  63. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/5ffa1aced1225ecaeac6366f7586aa3de92761cdff8711d81fbd81f248076abd.sqlite +0 -0
  64. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/686c3a9f0d7e59ed2ab607efd4b76d779c97cafeb3818380033bf7c7eb86c819.sqlite +0 -0
  65. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/6e8214e8dcfadd0deb52d64e5e9ca85c6b329ace11193909845995396914c473.sqlite +0 -0
  66. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/78d9ec9ff873d3fe3507ff53c2a6f6dfc408b4268eb0db3f2a146c0678965366.sqlite +0 -0
  67. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/7eff9f0ed7e27ad0d3f9d923de0682fab1928591172c1ba336c5f79a134a5d85.sqlite +0 -0
  68. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/836cda5b995b25867d722ed4f4c2292167e80351a3c6038db626648eb247dd8b.sqlite +0 -0
  69. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/91ef63b112209ab30172763acd8a0935106c248f7f1bcae5545ce37a9f201551.sqlite +0 -0
  70. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/a66ea4293a5f5938bc6d116edfa2522bb85bc37aea3541fbc09c3b613b9b32c0.sqlite +0 -0
  71. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/ceb2ab26b80590840b65651deb6e948d3bf81565c6751f3a58752cf4bf4aecae.sqlite +0 -0
  72. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite +0 -0
  73. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-shm +0 -0
  74. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-wal +0 -0
  75. package/src/cf-do/ARCHITECTURE.md +0 -83
  76. package/src/cf-do/watermark.test.ts +0 -103
  77. package/src/cf-do/watermark.ts +0 -118
  78. package/src/cf-do/worker.ts +0 -1033
  79. package/src/cf-do/wrangler.toml +0 -11
  80. package/src/cf-pglite/README.md +0 -19
  81. package/src/change-tracking.ts +0 -25
  82. package/src/child-process.test.ts +0 -147
  83. package/src/child-process.ts +0 -90
  84. package/src/cli-entry.ts +0 -72
  85. package/src/cli.test.ts +0 -38
  86. package/src/cli.ts +0 -1214
  87. package/src/config.ts +0 -150
  88. package/src/do-sql-tracking.test.ts +0 -19
  89. package/src/do-sql-tracking.ts +0 -19
  90. package/src/index.ts +0 -1215
  91. package/src/integration/integration.test.ts +0 -517
  92. package/src/integration/native-binary.guard.test.ts +0 -13
  93. package/src/integration/native-startup.test.ts +0 -44
  94. package/src/integration/replication-latency.test.ts +0 -428
  95. package/src/integration/restore-live-stress.test.ts +0 -433
  96. package/src/integration/restore-reset.test.ts +0 -400
  97. package/src/integration/restore.test.ts +0 -274
  98. package/src/integration/test-permissions.ts +0 -147
  99. package/src/load-config.ts +0 -46
  100. package/src/log.ts +0 -96
  101. package/src/mutex.ts +0 -47
  102. package/src/pg-proxy-browser.singledb.test.ts +0 -233
  103. package/src/pg-proxy-browser.ts +0 -2022
  104. package/src/pg-proxy-do-backend.test.ts +0 -3890
  105. package/src/pg-proxy-do-backend.ts +0 -7157
  106. package/src/pg-proxy.ts +0 -1087
  107. package/src/pglite-ipc.test.ts +0 -116
  108. package/src/pglite-ipc.ts +0 -266
  109. package/src/pglite-manager.ts +0 -557
  110. package/src/pglite-web-proxy.test.ts +0 -57
  111. package/src/pglite-web-proxy.ts +0 -221
  112. package/src/pglite-web-worker.ts +0 -152
  113. package/src/pglite-worker-thread.ts +0 -253
  114. package/src/port.ts +0 -25
  115. package/src/process-title.ts +0 -9
  116. package/src/recovery.ts +0 -155
  117. package/src/replication/change-tracker.test.ts +0 -357
  118. package/src/replication/change-tracker.ts +0 -279
  119. package/src/replication/handler.test.ts +0 -511
  120. package/src/replication/handler.ts +0 -1190
  121. package/src/replication/pgoutput-encoder.test.ts +0 -697
  122. package/src/replication/pgoutput-encoder.ts +0 -373
  123. package/src/replication/tcp-replication.test.ts +0 -876
  124. package/src/replication/zero-compat.test.ts +0 -1150
  125. package/src/restore-stress.test.ts +0 -188
  126. package/src/s3-local.ts +0 -203
  127. package/src/shim/hooks.mjs +0 -120
  128. package/src/shim/register.mjs +0 -4
  129. package/src/sqlite-mode/apply-mode.ts +0 -224
  130. package/src/sqlite-mode/index.ts +0 -15
  131. package/src/sqlite-mode/native-binary.ts +0 -89
  132. package/src/sqlite-mode/package-resolve.ts +0 -17
  133. package/src/sqlite-mode/resolve-mode.ts +0 -80
  134. package/src/sqlite-mode/shim-template.ts +0 -159
  135. package/src/sqlite-mode/sqlite-mode.test.ts +0 -427
  136. package/src/sqlite-mode/types.ts +0 -30
  137. package/src/vite-plugin.ts +0 -67
  138. package/src/wasm-sqlite.test.ts +0 -537
  139. package/src/worker/browser-admin.ts +0 -52
  140. package/src/worker/browser-build-config.test.ts +0 -71
  141. package/src/worker/browser-build-config.ts +0 -109
  142. package/src/worker/browser-embed-admin.test.ts +0 -75
  143. package/src/worker/browser-embed.ts +0 -345
  144. package/src/worker/cf-patches.ts +0 -384
  145. package/src/worker/embed-integration.test.ts +0 -321
  146. package/src/worker/index.ts +0 -138
  147. package/src/worker/shims/fastify.test.ts +0 -255
  148. package/src/worker/shims/fastify.ts +0 -306
  149. package/src/worker/shims/http-service.test.ts +0 -355
  150. package/src/worker/shims/http-service.ts +0 -293
  151. package/src/worker/shims/node-stub.ts +0 -290
  152. package/src/worker/shims/oxfmt.ts +0 -3
  153. package/src/worker/shims/postgres-browser.ts +0 -59
  154. package/src/worker/shims/postgres-socket.test.ts +0 -576
  155. package/src/worker/shims/postgres-socket.ts +0 -310
  156. package/src/worker/shims/postgres.test.ts +0 -364
  157. package/src/worker/shims/postgres.ts +0 -1454
  158. package/src/worker/shims/sqlite-browser.test.ts +0 -233
  159. package/src/worker/shims/sqlite-browser.ts +0 -175
  160. package/src/worker/shims/sqlite.test.ts +0 -786
  161. package/src/worker/shims/sqlite.ts +0 -978
  162. package/src/worker/shims/stream-browser.ts +0 -15
  163. package/src/worker/shims/ws-browser.test.ts +0 -205
  164. package/src/worker/shims/ws-browser.ts +0 -248
  165. package/src/worker/shims/ws.test.ts +0 -288
  166. package/src/worker/shims/ws.ts +0 -467
  167. package/src/worker/shims/zero-process-env.ts +0 -11
  168. package/src/worker/types.ts +0 -75
  169. package/src/worker/worker-integration.test.ts +0 -223
  170. package/src/worker/worker.test.ts +0 -136
  171. package/src/worker/zero-cache-embed-cf.ts +0 -463
  172. package/src/worker/zero-cache-embed.ts +0 -277
@@ -1,147 +0,0 @@
1
- import type { PGlite } from '@electric-sql/pglite'
2
-
3
- type DbLike = Pick<PGlite, 'query' | 'exec'>
4
-
5
- const ALLOW_ALL_CONDITION = { type: 'and', conditions: [] as unknown[] }
6
- const ALLOW_ALL_POLICY = [['allow', ALLOW_ALL_CONDITION]]
7
- const DEFAULT_APP_ID = process.env.ZERO_APP_ID?.trim() || 'zero'
8
-
9
- export async function installAllowAllPermissions(
10
- db: DbLike,
11
- tables: string[]
12
- ): Promise<void> {
13
- const schemas = await findPermissionsSchemas(db)
14
- if (schemas.length === 0) {
15
- schemas.push(DEFAULT_APP_ID)
16
- }
17
-
18
- for (const schema of schemas) {
19
- const quotedSchema = '"' + schema.replace(/"/g, '""') + '"'
20
-
21
- // Bootstrap the same global permissions table shape zero-cache expects.
22
- await db.exec(`
23
- CREATE SCHEMA IF NOT EXISTS ${quotedSchema};
24
-
25
- CREATE TABLE IF NOT EXISTS ${quotedSchema}.permissions (
26
- "permissions" JSONB,
27
- "hash" TEXT,
28
- "lock" BOOL PRIMARY KEY DEFAULT true CHECK (lock)
29
- );
30
-
31
- CREATE OR REPLACE FUNCTION ${quotedSchema}.set_permissions_hash()
32
- RETURNS TRIGGER AS $$
33
- BEGIN
34
- NEW.hash = md5(NEW.permissions::text);
35
- RETURN NEW;
36
- END;
37
- $$ LANGUAGE plpgsql;
38
-
39
- DROP TRIGGER IF EXISTS on_set_permissions ON ${quotedSchema}.permissions;
40
- CREATE TRIGGER on_set_permissions
41
- BEFORE INSERT OR UPDATE ON ${quotedSchema}.permissions
42
- FOR EACH ROW
43
- EXECUTE FUNCTION ${quotedSchema}.set_permissions_hash();
44
-
45
- INSERT INTO ${quotedSchema}.permissions ("permissions")
46
- VALUES (NULL)
47
- ON CONFLICT DO NOTHING;
48
- `)
49
-
50
- const existing = await db.query<{ permissions: unknown }>(
51
- `SELECT permissions FROM ${quotedSchema}.permissions WHERE lock = true LIMIT 1`
52
- )
53
- const existingPermissions = parsePermissions(existing.rows[0]?.permissions)
54
-
55
- const tablesToAdd = Object.fromEntries(
56
- tables.map((table) => [
57
- table,
58
- {
59
- row: {
60
- select: ALLOW_ALL_POLICY,
61
- insert: ALLOW_ALL_POLICY,
62
- update: {
63
- preMutation: ALLOW_ALL_POLICY,
64
- postMutation: ALLOW_ALL_POLICY,
65
- },
66
- delete: ALLOW_ALL_POLICY,
67
- },
68
- },
69
- ])
70
- )
71
-
72
- const permissions = {
73
- ...existingPermissions,
74
- tables: {
75
- ...(existingPermissions.tables || {}),
76
- ...tablesToAdd,
77
- },
78
- }
79
-
80
- await db.query(
81
- `UPDATE ${quotedSchema}.permissions SET permissions = $1 WHERE lock = true`,
82
- [JSON.stringify(permissions)]
83
- )
84
- }
85
- }
86
-
87
- export async function hasNonNullPermissions(db: DbLike): Promise<boolean> {
88
- const schemas = await findPermissionsSchemas(db)
89
- for (const schema of schemas) {
90
- const quotedSchema = '"' + schema.replace(/"/g, '""') + '"'
91
- const result = await db.query<{ has_permissions: boolean }>(
92
- `SELECT (permissions IS NOT NULL) AS has_permissions
93
- FROM ${quotedSchema}.permissions
94
- WHERE lock = true
95
- LIMIT 1`
96
- )
97
- if (result.rows[0]?.has_permissions) return true
98
- }
99
- return false
100
- }
101
-
102
- export async function ensureTablesInPublications(
103
- db: DbLike,
104
- tables: string[]
105
- ): Promise<void> {
106
- const pubs = await db.query<{ pubname: string }>(
107
- `SELECT pubname
108
- FROM pg_publication
109
- WHERE pubname NOT LIKE '%metadata%'
110
- ORDER BY pubname`
111
- )
112
- for (const { pubname } of pubs.rows) {
113
- const quotedPub = '"' + pubname.replace(/"/g, '""') + '"'
114
- for (const table of tables) {
115
- const quotedTable = '"' + table.replace(/"/g, '""') + '"'
116
- await db
117
- .exec(`ALTER PUBLICATION ${quotedPub} ADD TABLE "public".${quotedTable}`)
118
- .catch(() => {})
119
- }
120
- }
121
- }
122
-
123
- function parsePermissions(value: unknown): { tables?: Record<string, unknown> } {
124
- if (!value) return {}
125
- if (typeof value === 'string') {
126
- try {
127
- return JSON.parse(value)
128
- } catch {
129
- return {}
130
- }
131
- }
132
- if (typeof value === 'object') return value as { tables?: Record<string, unknown> }
133
- return {}
134
- }
135
-
136
- async function findPermissionsSchemas(db: DbLike): Promise<string[]> {
137
- const result = await db.query<{ schemaname: string }>(
138
- `SELECT schemaname
139
- FROM pg_tables
140
- WHERE tablename = 'permissions'
141
- AND schemaname NOT IN ('pg_catalog', 'information_schema')
142
- AND schemaname NOT LIKE 'pg_%'
143
- ORDER BY CASE WHEN schemaname = $1 THEN 0 ELSE 1 END, schemaname`,
144
- [DEFAULT_APP_ID]
145
- )
146
- return result.rows.map((r) => r.schemaname)
147
- }
@@ -1,46 +0,0 @@
1
- import { existsSync } from 'node:fs'
2
- import { resolve } from 'node:path'
3
- import { pathToFileURL } from 'node:url'
4
-
5
- import type { OrezConfig } from './config.js'
6
-
7
- const CONFIG_FILES = ['orez.config.ts', 'orez.config.js', 'orez.config.mjs']
8
-
9
- export async function loadConfigFile(cwd = process.cwd()): Promise<OrezConfig> {
10
- for (const name of CONFIG_FILES) {
11
- const filePath = resolve(cwd, name)
12
- if (!existsSync(filePath)) continue
13
-
14
- try {
15
- const mod = await import(pathToFileURL(filePath).href)
16
- const config: OrezConfig = mod.default ?? mod
17
- return config
18
- } catch (err) {
19
- throw new Error(
20
- `failed to load ${name}: ${err instanceof Error ? err.message : err}`
21
- )
22
- }
23
- }
24
-
25
- return {}
26
- }
27
-
28
- /**
29
- * resolve OrezConfig aliases and convert to the shape expected by
30
- * startZeroLite() + CLI extras (s3, s3Port, disableAdmin).
31
- *
32
- * CLI args are passed as overrides — they take precedence over config file values.
33
- */
34
- export function resolveOrezConfig(
35
- fileConfig: OrezConfig,
36
- cliOverrides: Partial<OrezConfig> = {}
37
- ): OrezConfig {
38
- // merge: file < cli (undefined cli values don't override)
39
- const merged: OrezConfig = { ...fileConfig }
40
- for (const [k, v] of Object.entries(cliOverrides)) {
41
- if (v !== undefined) {
42
- ;(merged as Record<string, unknown>)[k] = v
43
- }
44
- }
45
- return merged
46
- }
package/src/log.ts DELETED
@@ -1,96 +0,0 @@
1
- import type { LogStore } from './admin/log-store.js'
2
- import type { LogLevel } from './config.js'
3
-
4
- const RESET = '\x1b[0m'
5
- const BOLD = '\x1b[1m'
6
- const DIM = '\x1b[2m'
7
-
8
- const COLORS = {
9
- cyan: '\x1b[36m',
10
- green: '\x1b[32m',
11
- yellow: '\x1b[33m',
12
- magenta: '\x1b[35m',
13
- blue: '\x1b[34m',
14
- } as const
15
-
16
- const LEVEL_PRIORITY: Record<LogLevel, number> = {
17
- error: 0,
18
- warn: 1,
19
- info: 2,
20
- debug: 3,
21
- }
22
-
23
- let currentLevel: LogLevel = 'warn'
24
- let logStore: LogStore | undefined
25
-
26
- export function setLogLevel(level: LogLevel) {
27
- currentLevel = level
28
- }
29
-
30
- /** hook up logStore for admin dashboard observability */
31
- export function setLogStore(store: LogStore | undefined) {
32
- logStore = store
33
- }
34
-
35
- function prefix(label: string, color: string): string {
36
- return `${BOLD}${color}[${label}]${RESET}`
37
- }
38
-
39
- /** format a port number with matching dim color */
40
- export function port(n: number, color: keyof typeof COLORS): string {
41
- return `${DIM}${COLORS[color]}:${n}${RESET}`
42
- }
43
-
44
- /** format a url with green color */
45
- export function url(u: string): string {
46
- return `${COLORS.green}${u}${RESET}`
47
- }
48
-
49
- // map logger labels to logStore source names
50
- const LABEL_TO_SOURCE: Record<string, string> = {
51
- orez: 'orez',
52
- 'orez:pg': 'orez',
53
- pglite: 'pglite',
54
- 'pg-proxy': 'proxy',
55
- 'orez:zero': 'zero',
56
- 'orez:s3': 's3',
57
- 'orez:repl': 'proxy',
58
- }
59
-
60
- function makeLogger(label: string, color: string, level: LogLevel = 'info') {
61
- const p = prefix(label, color)
62
- const source = LABEL_TO_SOURCE[label] || 'orez'
63
- // zero logs are handled specially in startZeroCache with better level detection
64
- const skipLogStore = source === 'zero'
65
- return (...args: unknown[]) => {
66
- const shouldLog = LEVEL_PRIORITY[level] <= LEVEL_PRIORITY[currentLevel]
67
- if (shouldLog) {
68
- console.info(p, ...args)
69
- }
70
- // push to logStore for non-debug messages, or debug only if console level allows it.
71
- // debug-level logs from the poll loop are very high volume and bloat the store.
72
- if (logStore && !skipLogStore && (level !== 'debug' || shouldLog)) {
73
- const msg = args.map((a) => (typeof a === 'string' ? a : String(a))).join(' ')
74
- logStore.push(source, level, msg)
75
- }
76
- }
77
- }
78
-
79
- export const log = {
80
- orez: makeLogger('orez', COLORS.cyan, 'warn'),
81
- pg: makeLogger('orez:pg', COLORS.green, 'warn'),
82
- pglite: makeLogger('pglite', COLORS.green, 'warn'),
83
- proxy: makeLogger('pg-proxy', COLORS.yellow, 'warn'),
84
- zero: makeLogger('orez:zero', COLORS.magenta, 'warn'),
85
- s3: makeLogger('orez:s3', COLORS.blue, 'warn'),
86
- repl: makeLogger('orez:repl', COLORS.yellow, 'warn'),
87
- debug: {
88
- orez: makeLogger('orez', COLORS.cyan, 'debug'),
89
- pg: makeLogger('orez:pg', COLORS.green, 'debug'),
90
- pglite: makeLogger('pglite', COLORS.green, 'debug'),
91
- proxy: makeLogger('pg-proxy', COLORS.yellow, 'debug'),
92
- zero: makeLogger('orez:zero', COLORS.magenta, 'debug'),
93
- s3: makeLogger('orez:s3', COLORS.blue, 'debug'),
94
- repl: makeLogger('orez:repl', COLORS.yellow, 'debug'),
95
- },
96
- }
package/src/mutex.ts DELETED
@@ -1,47 +0,0 @@
1
- // simple mutex for serializing pglite access
2
- // uses head-index instead of Array.shift() for O(1) release
3
- export class Mutex {
4
- private locked = false
5
- private queue: Array<() => void> = []
6
- private head = 0
7
-
8
- /** check if the mutex is currently held (non-blocking, no side effects) */
9
- get isLocked(): boolean {
10
- return this.locked
11
- }
12
-
13
- async acquire(): Promise<void> {
14
- if (!this.locked) {
15
- this.locked = true
16
- return
17
- }
18
- return new Promise<void>((resolve) => {
19
- this.queue.push(resolve)
20
- })
21
- }
22
-
23
- // non-blocking acquire: returns true if lock was obtained, false otherwise
24
- tryAcquire(): boolean {
25
- if (!this.locked) {
26
- this.locked = true
27
- return true
28
- }
29
- return false
30
- }
31
-
32
- release(): void {
33
- if (this.head < this.queue.length) {
34
- const next = this.queue[this.head++]!
35
- // compact periodically to prevent unbounded array growth
36
- if (this.head > 64) {
37
- this.queue = this.queue.slice(this.head)
38
- this.head = 0
39
- }
40
- next()
41
- } else {
42
- this.queue = []
43
- this.head = 0
44
- this.locked = false
45
- }
46
- }
47
- }
@@ -1,233 +0,0 @@
1
- /**
2
- * regression test for the explicit singleDb option in createBrowserProxy.
3
- *
4
- * background: orez-web (in soot) wraps a single PGlite worker in three
5
- * distinct port-proxy façades (one per database role) and hands them to
6
- * createBrowserProxy. before this fix, mutex coalescing relied on
7
- * `instances.postgres === instances.cvr` reference equality — which fails
8
- * for distinct façades, leaving 3 separate mutexes guarding a single
9
- * underlying PGlite. that allows concurrent extended-protocol sequences on
10
- * one shared session, racing named-statement slots and replication state.
11
- *
12
- * the explicit `config.singleDb` option forces mutex coalescing regardless
13
- * of object identity. this test pins that contract: with singleDb=true and
14
- * three distinct façades over one PGlite, concurrent calls must serialize.
15
- */
16
-
17
- import { PGlite } from '@electric-sql/pglite'
18
- import postgres from 'postgres'
19
- import { afterAll, beforeAll, describe, expect, test } from 'vitest'
20
-
21
- import { createBrowserProxy } from './pg-proxy-browser.js'
22
- import { createSocketFactory } from './worker/shims/postgres-socket.js'
23
-
24
- import type { PGliteInstances } from './pglite-manager.js'
25
-
26
- /**
27
- * thin façade that re-exposes a PGlite's surface as a *distinct* object
28
- * reference. simulates what orez-web does (it wraps the same underlying
29
- * PGlite worker in three port-proxy objects, one per database role).
30
- */
31
- function makeFacade(real: PGlite, label: string) {
32
- const facade: any = {
33
- _label: label,
34
- closed: false,
35
- ready: true,
36
- get waitReady() {
37
- return real.waitReady
38
- },
39
- query: (sql: string, params?: any[]) => real.query(sql, params as any),
40
- exec: (sql: string) => real.exec(sql),
41
- execProtocolRaw: (data: Uint8Array, options?: any) =>
42
- real.execProtocolRaw(data, options),
43
- listen: () => Promise.resolve(async () => {}),
44
- close: () => Promise.resolve(),
45
- }
46
- return facade as PGlite
47
- }
48
-
49
- function createSql(
50
- proxy: ReturnType<typeof createBrowserProxy> extends Promise<infer T> ? T : never
51
- ) {
52
- return postgres({
53
- socket: createSocketFactory((port) => proxy.handleConnection(port)),
54
- database: 'postgres',
55
- username: 'u',
56
- password: '',
57
- host: '127.0.0.1',
58
- port: 0,
59
- ssl: false,
60
- max: 1,
61
- no_subscribe: true,
62
- } as any)
63
- }
64
-
65
- function deferred<T>() {
66
- let resolve!: (value: T | PromiseLike<T>) => void
67
- let reject!: (reason?: unknown) => void
68
- const promise = new Promise<T>((res, rej) => {
69
- resolve = res
70
- reject = rej
71
- })
72
- return { promise, resolve, reject }
73
- }
74
-
75
- describe('createBrowserProxy singleDb mutex coalescing', () => {
76
- let pg: PGlite
77
-
78
- beforeAll(async () => {
79
- pg = new PGlite()
80
- await pg.waitReady
81
- }, 30_000)
82
-
83
- afterAll(async () => {
84
- await pg.close().catch(() => {})
85
- })
86
-
87
- test('reference equality on the same PGlite still coalesces (legacy path)', async () => {
88
- const instances: PGliteInstances = {
89
- postgres: pg,
90
- cvr: pg,
91
- cdb: pg,
92
- postgresReplicas: [],
93
- }
94
- const proxy = await createBrowserProxy(instances, { pgPassword: '', pgUser: 'u' })
95
- // smoke: it constructs without error. proper concurrency proof requires
96
- // pg-wire client; covered by integration tests. this guards the legacy path.
97
- expect(proxy).toBeTruthy()
98
- proxy.close()
99
- })
100
-
101
- test('distinct façades + singleDb=true coalesces; without flag they would split', async () => {
102
- const facadePg = makeFacade(pg, 'postgres')
103
- const facadeCvr = makeFacade(pg, 'cvr')
104
- const facadeCdb = makeFacade(pg, 'cdb')
105
-
106
- // sanity: the three façades are distinct refs (would defeat reference equality)
107
- expect(facadePg).not.toBe(facadeCvr)
108
- expect(facadePg).not.toBe(facadeCdb)
109
- expect(facadeCvr).not.toBe(facadeCdb)
110
-
111
- const instances: PGliteInstances = {
112
- postgres: facadePg,
113
- cvr: facadeCvr,
114
- cdb: facadeCdb,
115
- postgresReplicas: [],
116
- }
117
-
118
- // explicit singleDb=true should still build a working proxy.
119
- const proxy = await createBrowserProxy(instances, {
120
- pgPassword: '',
121
- pgUser: 'u',
122
- singleDb: true,
123
- })
124
- expect(proxy).toBeTruthy()
125
- proxy.close()
126
- })
127
-
128
- test('coordinated query/exec runs through the same per-db mutex', async () => {
129
- // proxy.query / proxy.exec exist so out-of-band JSON callers (soot's
130
- // project-server / main-thread SAB JSON channels) go through the same
131
- // mutex + txState the wire-protocol path uses. this test pins that the
132
- // API works end-to-end against a shared-PGlite façade setup; the actual
133
- // 'E'-rescue behaviour is exercised by the soot integration suite where
134
- // a wire-protocol abort populates txState first.
135
- const facadePg = makeFacade(pg, 'postgres')
136
- const facadeCvr = makeFacade(pg, 'cvr')
137
- const facadeCdb = makeFacade(pg, 'cdb')
138
- const proxy = await createBrowserProxy(
139
- {
140
- postgres: facadePg,
141
- cvr: facadeCvr,
142
- cdb: facadeCdb,
143
- postgresReplicas: [],
144
- },
145
- { pgPassword: '', pgUser: 'u', singleDb: true }
146
- )
147
-
148
- const r1 = await proxy.query('postgres', 'SELECT 1 AS ok')
149
- expect(r1.rows).toEqual([{ ok: 1 }])
150
-
151
- const r2 = await proxy.exec(
152
- 'postgres',
153
- 'CREATE TABLE IF NOT EXISTS rescue_test (id int)'
154
- )
155
- expect(Array.isArray(r2)).toBe(true)
156
-
157
- proxy.close()
158
- })
159
-
160
- test('singleDb waits for the owning transaction before serving another client', async () => {
161
- await pg.exec(`
162
- DROP TABLE IF EXISTS singledb_tx_owner;
163
- CREATE TABLE singledb_tx_owner (id int);
164
- `)
165
- const facadePg = makeFacade(pg, 'postgres')
166
- const facadeCvr = makeFacade(pg, 'cvr')
167
- const facadeCdb = makeFacade(pg, 'cdb')
168
- const proxy = await createBrowserProxy(
169
- {
170
- postgres: facadePg,
171
- cvr: facadeCvr,
172
- cdb: facadeCdb,
173
- postgresReplicas: [],
174
- },
175
- { pgPassword: '', pgUser: 'u', singleDb: true }
176
- )
177
- const sql1 = createSql(proxy)
178
- const sql2 = createSql(proxy)
179
- const releaseTx = deferred<void>()
180
- const txStarted = deferred<void>()
181
-
182
- const tx = sql1.begin(async (sql) => {
183
- await sql`INSERT INTO singledb_tx_owner VALUES (1)`
184
- txStarted.resolve()
185
- await releaseTx.promise
186
- })
187
- await txStarted.promise
188
-
189
- let readCompleted = false
190
- const read = sql2`SELECT count(*)::int AS count FROM singledb_tx_owner`.then(
191
- (rows) => {
192
- readCompleted = true
193
- return rows[0]?.count
194
- }
195
- )
196
- await new Promise((resolve) => setTimeout(resolve, 25))
197
- expect(readCompleted).toBe(false)
198
-
199
- releaseTx.resolve()
200
- await tx
201
- await expect(read).resolves.toBe(1)
202
-
203
- await sql1.end({ timeout: 1 }).catch(() => {})
204
- await sql2.end({ timeout: 1 }).catch(() => {})
205
- proxy.close()
206
- }, 10_000)
207
-
208
- test('explicit singleDb=false on distinct façades preserves split mutexes', async () => {
209
- // negative case: when caller doesn't opt in and refs are distinct, the
210
- // legacy reference-equality heuristic gives separate mutexes (the bug we
211
- // shipped around). this test pins the contract that singleDb is opt-in,
212
- // so adding it later cannot quietly break consumers that rely on split
213
- // mutexes for their three real PGlite instances.
214
- const facadePg = makeFacade(pg, 'postgres')
215
- const facadeCvr = makeFacade(pg, 'cvr')
216
- const facadeCdb = makeFacade(pg, 'cdb')
217
-
218
- const instances: PGliteInstances = {
219
- postgres: facadePg,
220
- cvr: facadeCvr,
221
- cdb: facadeCdb,
222
- postgresReplicas: [],
223
- }
224
-
225
- const proxy = await createBrowserProxy(instances, {
226
- pgPassword: '',
227
- pgUser: 'u',
228
- // singleDb omitted — defaults to false
229
- })
230
- expect(proxy).toBeTruthy()
231
- proxy.close()
232
- })
233
- })