orez 0.2.27 → 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.
- package/package.json +3 -4
- package/src/admin/admin-data.test.ts +0 -348
- package/src/admin/http-proxy.ts +0 -252
- package/src/admin/log-store.ts +0 -192
- package/src/admin/server.ts +0 -471
- package/src/admin/ui.ts +0 -1322
- package/src/bench/proxy-throughput.bench.ts +0 -343
- package/src/bench/serial-mutations.bench.ts +0 -270
- package/src/browser.ts +0 -203
- package/src/cf-do/.wrangler/cache/cf.json +0 -1
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm +0 -0
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/0ffaabee41a60e04dd0eb7db3073f0a40139e6a97ccd26823967acb652b89a7b.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-shm +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-wal +0 -0
- package/src/cf-do/.wrangler/tmp/bundle-0z4CpE/middleware-insertion-facade.js +0 -11
- package/src/cf-do/.wrangler/tmp/bundle-0z4CpE/middleware-loader.entry.ts +0 -134
- package/src/cf-do/.wrangler/tmp/bundle-vYmw0E/middleware-insertion-facade.js +0 -11
- package/src/cf-do/.wrangler/tmp/bundle-vYmw0E/middleware-loader.entry.ts +0 -134
- package/src/cf-do/.wrangler/tmp/dev-cbILNo/worker.js +0 -1059
- package/src/cf-do/.wrangler/tmp/dev-cbILNo/worker.js.map +0 -8
- package/src/cf-do/.wrangler/tmp/dev-qbho19/worker.js +0 -1059
- package/src/cf-do/.wrangler/tmp/dev-qbho19/worker.js.map +0 -8
- package/src/cf-do/ARCHITECTURE.md +0 -93
- package/src/cf-do/CHAT_E2E.md +0 -213
- package/src/cf-do/watermark.test.ts +0 -103
- package/src/cf-do/watermark.ts +0 -118
- package/src/cf-do/worker.ts +0 -1041
- package/src/cf-do/wrangler.toml +0 -11
- package/src/cf-pglite/README.md +0 -19
- package/src/change-tracking.ts +0 -25
- package/src/child-process.test.ts +0 -147
- package/src/child-process.ts +0 -90
- package/src/cli-entry.ts +0 -72
- package/src/cli.test.ts +0 -40
- package/src/cli.ts +0 -1214
- package/src/config.ts +0 -150
- package/src/do-sql-tracking.test.ts +0 -19
- package/src/do-sql-tracking.ts +0 -19
- package/src/index.ts +0 -1215
- package/src/integration/integration.test.ts +0 -517
- package/src/integration/native-binary.guard.test.ts +0 -13
- package/src/integration/native-startup.test.ts +0 -44
- package/src/integration/replication-latency.test.ts +0 -428
- package/src/integration/restore-live-stress.test.ts +0 -433
- package/src/integration/restore-reset.test.ts +0 -400
- package/src/integration/restore.test.ts +0 -274
- package/src/integration/test-permissions.ts +0 -147
- package/src/load-config.ts +0 -46
- package/src/log.ts +0 -96
- package/src/mutex.ts +0 -47
- package/src/pg-proxy-browser.singledb.test.ts +0 -233
- package/src/pg-proxy-browser.ts +0 -2022
- package/src/pg-proxy-do-backend.test.ts +0 -3890
- package/src/pg-proxy-do-backend.ts +0 -7191
- package/src/pg-proxy.ts +0 -1087
- package/src/pg-sqlite-compiler/README.md +0 -53
- package/src/pg-sqlite-compiler/catalog/seed.ts +0 -524
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/arithmetic.json +0 -307
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/array.json +0 -377
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/cast.json +0 -12
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/catalog.json +0 -447
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/create-table.json +0 -32
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/datetime.json +0 -397
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/enum.json +0 -337
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/insert.json +0 -337
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/json.json +0 -537
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/misc.json +0 -1837
- package/src/pg-sqlite-compiler/index.ts +0 -73
- package/src/pg-sqlite-compiler/integration.test.ts +0 -136
- package/src/pg-sqlite-compiler/passes/ast-utils.ts +0 -113
- package/src/pg-sqlite-compiler/passes/catalog.ts +0 -65
- package/src/pg-sqlite-compiler/passes/datetime.ts +0 -74
- package/src/pg-sqlite-compiler/passes/index.ts +0 -49
- package/src/pg-sqlite-compiler/passes/types.ts +0 -156
- package/src/pg-sqlite-compiler/smoke.test.ts +0 -69
- package/src/pg-sqlite-compiler/test/catalog.test.ts +0 -171
- package/src/pg-sqlite-compiler/test/corpus.test.ts +0 -161
- package/src/pg-sqlite-compiler/test/datetime.oracle.test.ts +0 -102
- package/src/pg-sqlite-compiler/test/oracle.ts +0 -237
- package/src/pg-sqlite-compiler/test/types.test.ts +0 -109
- package/src/pg-sqlite-compiler/types.ts +0 -63
- package/src/pglite-ipc.test.ts +0 -116
- package/src/pglite-ipc.ts +0 -266
- package/src/pglite-manager.ts +0 -557
- package/src/pglite-web-proxy.test.ts +0 -57
- package/src/pglite-web-proxy.ts +0 -221
- package/src/pglite-web-worker.ts +0 -152
- package/src/pglite-worker-thread.ts +0 -253
- package/src/port.ts +0 -25
- package/src/process-title.ts +0 -9
- package/src/recovery.ts +0 -155
- package/src/replication/change-tracker.test.ts +0 -357
- package/src/replication/change-tracker.ts +0 -279
- package/src/replication/handler.test.ts +0 -511
- package/src/replication/handler.ts +0 -1190
- package/src/replication/pgoutput-encoder.test.ts +0 -697
- package/src/replication/pgoutput-encoder.ts +0 -373
- package/src/replication/tcp-replication.test.ts +0 -876
- package/src/replication/zero-compat.test.ts +0 -1150
- package/src/restore-stress.test.ts +0 -188
- package/src/s3-local.ts +0 -203
- package/src/shim/hooks.mjs +0 -120
- package/src/shim/register.mjs +0 -4
- package/src/sqlite-mode/apply-mode.ts +0 -224
- package/src/sqlite-mode/index.ts +0 -15
- package/src/sqlite-mode/native-binary.ts +0 -89
- package/src/sqlite-mode/package-resolve.ts +0 -17
- package/src/sqlite-mode/resolve-mode.ts +0 -80
- package/src/sqlite-mode/shim-template.ts +0 -159
- package/src/sqlite-mode/sqlite-mode.test.ts +0 -427
- package/src/sqlite-mode/types.ts +0 -30
- package/src/vite-plugin.ts +0 -67
- package/src/wasm-sqlite.test.ts +0 -537
- package/src/worker/browser-admin.ts +0 -52
- package/src/worker/browser-build-config.test.ts +0 -71
- package/src/worker/browser-build-config.ts +0 -109
- package/src/worker/browser-embed-admin.test.ts +0 -75
- package/src/worker/browser-embed.ts +0 -345
- package/src/worker/cf-patches.ts +0 -384
- package/src/worker/embed-integration.test.ts +0 -321
- package/src/worker/index.ts +0 -138
- package/src/worker/shims/fastify.test.ts +0 -255
- package/src/worker/shims/fastify.ts +0 -306
- package/src/worker/shims/http-service.test.ts +0 -355
- package/src/worker/shims/http-service.ts +0 -293
- package/src/worker/shims/node-stub.ts +0 -290
- package/src/worker/shims/oxfmt.ts +0 -3
- package/src/worker/shims/postgres-browser.ts +0 -59
- package/src/worker/shims/postgres-socket.test.ts +0 -576
- package/src/worker/shims/postgres-socket.ts +0 -310
- package/src/worker/shims/postgres.test.ts +0 -364
- package/src/worker/shims/postgres.ts +0 -1454
- package/src/worker/shims/sqlite-browser.test.ts +0 -233
- package/src/worker/shims/sqlite-browser.ts +0 -175
- package/src/worker/shims/sqlite.test.ts +0 -786
- package/src/worker/shims/sqlite.ts +0 -978
- package/src/worker/shims/stream-browser.ts +0 -15
- package/src/worker/shims/ws-browser.test.ts +0 -205
- package/src/worker/shims/ws-browser.ts +0 -248
- package/src/worker/shims/ws.test.ts +0 -288
- package/src/worker/shims/ws.ts +0 -467
- package/src/worker/shims/zero-process-env.ts +0 -11
- package/src/worker/types.ts +0 -75
- package/src/worker/worker-integration.test.ts +0 -223
- package/src/worker/worker.test.ts +0 -136
- package/src/worker/zero-cache-embed-cf.ts +0 -463
- 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
|
-
}
|
package/src/load-config.ts
DELETED
|
@@ -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
|
-
})
|