@parlor/data 0.3.0 → 0.3.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/dist/cork.d.ts +14 -0
- package/dist/cork.d.ts.map +1 -0
- package/dist/cork.js +42 -0
- package/dist/cork.js.map +1 -0
- package/dist/database.d.ts +11 -0
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +39 -2
- package/dist/database.js.map +1 -1
- package/dist/platform.workerd.d.ts +2 -1
- package/dist/platform.workerd.d.ts.map +1 -1
- package/dist/platform.workerd.js +3 -1
- package/dist/platform.workerd.js.map +1 -1
- package/package.json +2 -2
package/dist/cork.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface Socket {
|
|
2
|
+
cork?: (() => void) | undefined;
|
|
3
|
+
uncork?: (() => void) | undefined;
|
|
4
|
+
write(data: Uint8Array | string, ...rest: unknown[]): boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Gives a socket without `cork` and `uncork` (workerd's, under node-postgres) both: writes
|
|
8
|
+
* made while corked go out as one write on the last `uncork`. node-postgres corks around a
|
|
9
|
+
* query's Parse, Bind, Describe, Execute and Sync; written apart, Nagle's algorithm holds
|
|
10
|
+
* them for the server's delayed ACK, about 40 ms per query.
|
|
11
|
+
*/
|
|
12
|
+
export declare function corkWrites(socket: Socket): void;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=cork.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cork.d.ts","sourceRoot":"","sources":["../src/cork.ts"],"names":[],"mappings":"AAEA,UAAU,MAAM;IACd,IAAI,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IAChC,MAAM,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IAClC,KAAK,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;CAC/D;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAiC/C"}
|
package/dist/cork.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gives a socket without `cork` and `uncork` (workerd's, under node-postgres) both: writes
|
|
3
|
+
* made while corked go out as one write on the last `uncork`. node-postgres corks around a
|
|
4
|
+
* query's Parse, Bind, Describe, Execute and Sync; written apart, Nagle's algorithm holds
|
|
5
|
+
* them for the server's delayed ACK, about 40 ms per query.
|
|
6
|
+
*/
|
|
7
|
+
export function corkWrites(socket) {
|
|
8
|
+
if (typeof socket.cork === "function")
|
|
9
|
+
return;
|
|
10
|
+
const write = socket.write.bind(socket);
|
|
11
|
+
let depth = 0;
|
|
12
|
+
let held = [];
|
|
13
|
+
socket.cork = () => {
|
|
14
|
+
depth += 1;
|
|
15
|
+
};
|
|
16
|
+
socket.uncork = () => {
|
|
17
|
+
if (depth === 0)
|
|
18
|
+
return;
|
|
19
|
+
depth -= 1;
|
|
20
|
+
if (depth > 0 || held.length === 0)
|
|
21
|
+
return;
|
|
22
|
+
const sending = held;
|
|
23
|
+
held = [];
|
|
24
|
+
const data = sending.length === 1 ? sending[0].data : Buffer.concat(sending.map((w) => w.data));
|
|
25
|
+
write(data, (error) => {
|
|
26
|
+
for (const { callback } of sending)
|
|
27
|
+
callback?.(error);
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
socket.write = (data, ...rest) => {
|
|
31
|
+
if (depth === 0)
|
|
32
|
+
return write(data, ...rest);
|
|
33
|
+
const callback = rest.find((arg) => typeof arg === "function");
|
|
34
|
+
const encoding = typeof rest[0] === "string" ? rest[0] : "utf8";
|
|
35
|
+
held.push({
|
|
36
|
+
data: typeof data === "string" ? Buffer.from(data, encoding) : data,
|
|
37
|
+
callback,
|
|
38
|
+
});
|
|
39
|
+
return true;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=cork.js.map
|
package/dist/cork.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cork.js","sourceRoot":"","sources":["../src/cork.ts"],"names":[],"mappings":"AAQA;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,UAAU;QAAE,OAAO;IAC9C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,IAAI,GAGF,EAAE,CAAC;IACT,MAAM,CAAC,IAAI,GAAG,GAAG,EAAE;QACjB,KAAK,IAAI,CAAC,CAAC;IACb,CAAC,CAAC;IACF,MAAM,CAAC,MAAM,GAAG,GAAG,EAAE;QACnB,IAAI,KAAK,KAAK,CAAC;YAAE,OAAO;QACxB,KAAK,IAAI,CAAC,CAAC;QACX,IAAI,KAAK,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC3C,MAAM,OAAO,GAAG,IAAI,CAAC;QACrB,IAAI,GAAG,EAAE,CAAC;QACV,MAAM,IAAI,GACR,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACtF,KAAK,CAAC,IAAI,EAAE,CAAC,KAAoB,EAAE,EAAE;YACnC,KAAK,MAAM,EAAE,QAAQ,EAAE,IAAI,OAAO;gBAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IACF,MAAM,CAAC,KAAK,GAAG,CAAC,IAAI,EAAE,GAAG,IAAI,EAAE,EAAE;QAC/B,IAAI,KAAK,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC;QAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAwB,EAAE,CAAC,OAAO,GAAG,KAAK,UAAU,CAAC,CAAC;QACrF,MAAM,QAAQ,GAAG,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,IAAI,CAAC,CAAC,CAAoB,CAAC,CAAC,CAAC,MAAM,CAAC;QACpF,IAAI,CAAC,IAAI,CAAC;YACR,IAAI,EAAE,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI;YACnE,QAAQ;SACT,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;AACJ,CAAC","sourcesContent":["type WriteCallback = (error?: Error | null) => void;\n\ninterface Socket {\n cork?: (() => void) | undefined;\n uncork?: (() => void) | undefined;\n write(data: Uint8Array | string, ...rest: unknown[]): boolean;\n}\n\n/**\n * Gives a socket without `cork` and `uncork` (workerd's, under node-postgres) both: writes\n * made while corked go out as one write on the last `uncork`. node-postgres corks around a\n * query's Parse, Bind, Describe, Execute and Sync; written apart, Nagle's algorithm holds\n * them for the server's delayed ACK, about 40 ms per query.\n */\nexport function corkWrites(socket: Socket): void {\n if (typeof socket.cork === \"function\") return;\n const write = socket.write.bind(socket);\n let depth = 0;\n let held: {\n readonly data: Uint8Array;\n readonly callback: WriteCallback | undefined;\n }[] = [];\n socket.cork = () => {\n depth += 1;\n };\n socket.uncork = () => {\n if (depth === 0) return;\n depth -= 1;\n if (depth > 0 || held.length === 0) return;\n const sending = held;\n held = [];\n const data =\n sending.length === 1 ? sending[0]!.data : Buffer.concat(sending.map((w) => w.data));\n write(data, (error?: Error | null) => {\n for (const { callback } of sending) callback?.(error);\n });\n };\n socket.write = (data, ...rest) => {\n if (depth === 0) return write(data, ...rest);\n const callback = rest.find((arg): arg is WriteCallback => typeof arg === \"function\");\n const encoding = typeof rest[0] === \"string\" ? (rest[0] as BufferEncoding) : \"utf8\";\n held.push({\n data: typeof data === \"string\" ? Buffer.from(data, encoding) : data,\n callback,\n });\n return true;\n };\n}\n"]}
|
package/dist/database.d.ts
CHANGED
|
@@ -40,5 +40,16 @@ export declare function resolveDatabaseUrl(env?: NodeJS.ProcessEnv): string | un
|
|
|
40
40
|
/** PGlite's directory when `dataDir` is not given: `pglite` in the run's generated directory. */
|
|
41
41
|
export declare function resolvePgliteDir(env?: NodeJS.ProcessEnv): string;
|
|
42
42
|
export declare function resolveDirectUrl(env?: NodeJS.ProcessEnv): string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* A deployment with no database yet: it loads, so a host that runs the app's modules before
|
|
45
|
+
* its settings exist (Cloudflare checks a Worker as it uploads) accepts it, and every use
|
|
46
|
+
* fails with `error`, which the first request reports.
|
|
47
|
+
*/
|
|
48
|
+
export declare function unconfiguredHandle(error: Error): DatabaseHandle;
|
|
49
|
+
/**
|
|
50
|
+
* `handle`, migrating `folder` on its first `prepare`. A failed run is not kept: the next
|
|
51
|
+
* request tries again, rather than every request failing until the server restarts.
|
|
52
|
+
*/
|
|
53
|
+
export declare function migrateOnPrepare(handle: DatabaseHandle, folder: string): DatabaseHandle;
|
|
43
54
|
export declare function createDatabase(options?: DatabaseOptions): Promise<DatabaseHandle>;
|
|
44
55
|
//# sourceMappingURL=database.d.ts.map
|
package/dist/database.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE1E,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE1E,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAcxE,MAAM,MAAM,QAAQ,GAAG,UAAU,CAAC,gBAAgB,CAAC,CAAC;AAEpD,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,YAAY;QACpB,QAAQ,EAAE,QAAQ,CAAC;KACpB;CACF;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChD;AAED,6FAA6F;AAC7F,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,OAAO,EAAE,CAAA;KAAE,CAAC,CAAC;CACvE;AAED,MAAM,WAAW,cAAe,SAAQ,YAAY,CAAC,QAAQ,CAAC;IAC5D,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,eAAe,CAAC;IAC5C,MAAM,CACJ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EAClC,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,QAAQ,CAAC,CAAC;IACrB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,eAAO,MAAM,kBAAkB,mBAAmB,CAAC;AAEnD,wBAAgB,kBAAkB,CAChC,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,GAAG,SAAS,CAEpB;AAED,iGAAiG;AACjG,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAG7E;AAED,wBAAgB,gBAAgB,CAC9B,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,GAAG,SAAS,CAEpB;AA+BD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,GAAG,cAAc,CAc/D;AAuFD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,CAQvF;AAED,wBAAsB,cAAc,CAClC,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,cAAc,CAAC,CAYzB"}
|
package/dist/database.js
CHANGED
|
@@ -2,6 +2,7 @@ import { drizzle } from "drizzle-orm/node-postgres";
|
|
|
2
2
|
import pg from "pg";
|
|
3
3
|
import { migratesWhileLoading, pgliteHandle } from "#platform";
|
|
4
4
|
import { ConnectionOwner, currentOwner, } from "./connection-scope.js";
|
|
5
|
+
import { corkWrites } from "./cork.js";
|
|
5
6
|
import { migrateDatabase } from "./migrations.js";
|
|
6
7
|
import { once } from "./once.js";
|
|
7
8
|
export const DEFAULT_PGLITE_DIR = ".parlor/pglite";
|
|
@@ -18,6 +19,11 @@ export function resolveDirectUrl(env = process.env) {
|
|
|
18
19
|
}
|
|
19
20
|
function openPgConnections(url, onClose) {
|
|
20
21
|
const pool = new pg.Pool({ connectionString: url });
|
|
22
|
+
pool.on("connect", (client) => {
|
|
23
|
+
const { connection } = client;
|
|
24
|
+
if (connection?.stream)
|
|
25
|
+
corkWrites(connection.stream);
|
|
26
|
+
});
|
|
21
27
|
pool.on("error", (error) => {
|
|
22
28
|
console.warn("[parlor] an idle database connection dropped:", error.message);
|
|
23
29
|
});
|
|
@@ -33,6 +39,26 @@ function openPgConnections(url, onClose) {
|
|
|
33
39
|
}),
|
|
34
40
|
};
|
|
35
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* A deployment with no database yet: it loads, so a host that runs the app's modules before
|
|
44
|
+
* its settings exist (Cloudflare checks a Worker as it uploads) accepts it, and every use
|
|
45
|
+
* fails with `error`, which the first request reports.
|
|
46
|
+
*/
|
|
47
|
+
export function unconfiguredHandle(error) {
|
|
48
|
+
const fail = () => {
|
|
49
|
+
throw error;
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
db: scopedDatabase(fail),
|
|
53
|
+
client: { query: () => Promise.reject(error) },
|
|
54
|
+
driver: "node-postgres",
|
|
55
|
+
read: fail,
|
|
56
|
+
transaction: () => Promise.reject(error),
|
|
57
|
+
listen: () => Promise.reject(error),
|
|
58
|
+
migrate: () => Promise.reject(error),
|
|
59
|
+
close: async () => undefined,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
36
62
|
/** Stays valid across scopes, so code that captured `handle.db` at startup still works. */
|
|
37
63
|
function scopedDatabase(current) {
|
|
38
64
|
return new Proxy({}, {
|
|
@@ -120,6 +146,18 @@ function nodePostgresHandle(url, directUrl) {
|
|
|
120
146
|
}),
|
|
121
147
|
};
|
|
122
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* `handle`, migrating `folder` on its first `prepare`. A failed run is not kept: the next
|
|
151
|
+
* request tries again, rather than every request failing until the server restarts.
|
|
152
|
+
*/
|
|
153
|
+
export function migrateOnPrepare(handle, folder) {
|
|
154
|
+
let migrated;
|
|
155
|
+
const migrate = () => handle.migrate(folder).catch((error) => {
|
|
156
|
+
migrated = undefined;
|
|
157
|
+
throw error;
|
|
158
|
+
});
|
|
159
|
+
return { ...handle, prepare: () => (migrated ??= migrate()) };
|
|
160
|
+
}
|
|
123
161
|
export async function createDatabase(options = {}) {
|
|
124
162
|
const url = options.url ?? resolveDatabaseUrl();
|
|
125
163
|
const handle = url
|
|
@@ -132,7 +170,6 @@ export async function createDatabase(options = {}) {
|
|
|
132
170
|
await handle.migrate(folder);
|
|
133
171
|
return handle;
|
|
134
172
|
}
|
|
135
|
-
|
|
136
|
-
return { ...handle, prepare: () => (migrated ??= handle.migrate(folder)) };
|
|
173
|
+
return migrateOnPrepare(handle, folder);
|
|
137
174
|
}
|
|
138
175
|
//# sourceMappingURL=database.js.map
|
package/dist/database.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.js","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DataProvider, ListenOptions, Unlisten } from \"@parlor/core\";\nimport { drizzle, type NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport type { PgDatabase, PgQueryResultHKT } from \"drizzle-orm/pg-core\";\nimport pg from \"pg\";\n\nimport { migratesWhileLoading, pgliteHandle } from \"#platform\";\n\nimport {\n ConnectionOwner,\n currentOwner,\n type ScopedConnections,\n} from \"./connection-scope.js\";\nimport { migrateDatabase } from \"./migrations.js\";\nimport { once } from \"./once.js\";\n\nexport type Database = PgDatabase<PgQueryResultHKT>;\n\ndeclare module \"@parlor/core\" {\n interface DataRegister {\n database: Database;\n }\n}\n\nexport interface DatabaseOptions {\n readonly url?: string | undefined;\n /**\n * A connection that bypasses any pooler, for LISTEN and migrations. Transaction poolers\n * such as PgBouncer, Neon's pooled endpoint or Hyperdrive cannot hold a LISTEN.\n * Defaults to `DATABASE_DIRECT_URL`, then to `url`.\n */\n readonly directUrl?: string | undefined;\n readonly dataDir?: string | undefined;\n readonly migrationsFolder?: string | undefined;\n}\n\n/** Runs one parameterized statement (`$1`, `$2`, ...) and answers its rows, as `pg` does. */\nexport interface DatabaseClient {\n query(text: string, params?: unknown[]): Promise<{ rows: unknown[] }>;\n}\n\nexport interface DatabaseHandle extends DataProvider<Database> {\n readonly db: Database;\n /**\n * The connection under `db`, for libraries that take a `pg`-style client. Each statement\n * runs on its own, outside any transaction, on the current connection scope's connections.\n */\n readonly client: DatabaseClient;\n readonly driver: \"pglite\" | \"node-postgres\";\n listen(\n channel: string,\n handler: (payload: string) => void,\n options?: ListenOptions,\n ): Promise<Unlisten>;\n migrate(folder: string): Promise<void>;\n close(): Promise<void>;\n}\n\nexport const DEFAULT_PGLITE_DIR = \".parlor/pglite\";\n\nexport function resolveDatabaseUrl(\n env: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n return env.DATABASE_URL || undefined;\n}\n\n/** PGlite's directory when `dataDir` is not given: `pglite` in the run's generated directory. */\nexport function resolvePgliteDir(env: NodeJS.ProcessEnv = process.env): string {\n const generated = env.PARLOR_GENERATED_DIR?.replace(/\\/+$/, \"\");\n return generated ? `${generated}/pglite` : DEFAULT_PGLITE_DIR;\n}\n\nexport function resolveDirectUrl(\n env: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n return env.DATABASE_DIRECT_URL || undefined;\n}\n\ninterface PgConnections extends ScopedConnections {\n readonly pool: pg.Pool;\n readonly db: NodePgDatabase;\n readonly listeners: Set<pg.Client>;\n}\n\nfunction openPgConnections(url: string, onClose?: () => void): PgConnections {\n const pool = new pg.Pool({ connectionString: url });\n pool.on(\"error\", (error) => {\n console.warn(\"[parlor] an idle database connection dropped:\", error.message);\n });\n const listeners = new Set<pg.Client>();\n return {\n pool,\n db: drizzle(pool),\n listeners,\n close: once(async () => {\n onClose?.();\n await Promise.all([...listeners].map((client) => client.end()));\n await pool.end();\n }),\n };\n}\n\n/** Stays valid across scopes, so code that captured `handle.db` at startup still works. */\nfunction scopedDatabase(current: () => Database): Database {\n return new Proxy({} as Database, {\n get(_target, property) {\n const db = current();\n const value: unknown = Reflect.get(db, property, db);\n return typeof value === \"function\" ? value.bind(db) : value;\n },\n has: (_target, property) => Reflect.has(current(), property),\n getPrototypeOf: () => Reflect.getPrototypeOf(current()),\n });\n}\n\nfunction nodePostgresHandle(url: string, directUrl: string): DatabaseHandle {\n const key = {};\n const outside = new ConnectionOwner();\n const opened = new Set<PgConnections>();\n const connections = () =>\n (currentOwner() ?? outside).connectionsFor(key, () => {\n const created = openPgConnections(url, () => opened.delete(created));\n opened.add(created);\n return created;\n });\n return {\n db: scopedDatabase(() => connections().db),\n client: { query: (text, params) => connections().pool.query(text, params) },\n driver: \"node-postgres\",\n read: () => connections().db,\n transaction: (run) => connections().db.transaction((tx) => run(tx)),\n listen: async (channel, handler, options = {}) => {\n const { listeners } = connections();\n const client = new pg.Client({ connectionString: directUrl });\n let state: \"listening\" | \"lost\" | \"closed\" = \"listening\";\n const current = () => state;\n const lose = () => {\n if (state !== \"listening\") return;\n state = \"lost\";\n listeners.delete(client);\n client.end().catch(() => {});\n options.onLost?.();\n };\n client.on(\"error\", lose);\n client.on(\"end\", lose);\n client.on(\"notification\", (message) => {\n if (message.channel === channel) handler(message.payload ?? \"\");\n });\n const identifier = pg.escapeIdentifier(channel);\n try {\n await client.connect();\n await client.query(`LISTEN ${identifier}`);\n } catch (error) {\n state = \"closed\";\n await client.end().catch(() => {});\n throw error;\n }\n if (current() === \"lost\")\n throw new Error(`LISTEN ${identifier} lost its connection`);\n listeners.add(client);\n return async () => {\n if (state !== \"listening\") return;\n state = \"closed\";\n listeners.delete(client);\n await client.query(`UNLISTEN ${identifier}`).catch(() => {});\n await client.end();\n };\n },\n migrate: async (folder) => {\n if (directUrl === url) {\n await migrateDatabase(connections().db, folder);\n return;\n }\n const direct = openPgConnections(directUrl);\n try {\n await migrateDatabase(direct.db, folder);\n } finally {\n await direct.close();\n }\n },\n close: once(async () => {\n outside.forget(key);\n await Promise.all([...opened].map((created) => created.close()));\n }),\n };\n}\n\nexport async function createDatabase(\n options: DatabaseOptions = {},\n): Promise<DatabaseHandle> {\n const url = options.url ?? resolveDatabaseUrl();\n const handle = url\n ? nodePostgresHandle(url, options.directUrl ?? resolveDirectUrl() ?? url)\n : await pgliteHandle(options.dataDir ?? resolvePgliteDir());\n const folder = options.migrationsFolder;\n if (!folder) return handle;\n if (migratesWhileLoading) {\n await handle.migrate(folder);\n return handle;\n }\n let migrated: Promise<void> | undefined;\n return { ...handle, prepare: () => (migrated ??= handle.migrate(folder)) };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DataProvider, ListenOptions, Unlisten } from \"@parlor/core\";\nimport { drizzle, type NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport type { PgDatabase, PgQueryResultHKT } from \"drizzle-orm/pg-core\";\nimport pg from \"pg\";\n\nimport { migratesWhileLoading, pgliteHandle } from \"#platform\";\n\nimport {\n ConnectionOwner,\n currentOwner,\n type ScopedConnections,\n} from \"./connection-scope.js\";\nimport { corkWrites } from \"./cork.js\";\nimport { migrateDatabase } from \"./migrations.js\";\nimport { once } from \"./once.js\";\n\nexport type Database = PgDatabase<PgQueryResultHKT>;\n\ndeclare module \"@parlor/core\" {\n interface DataRegister {\n database: Database;\n }\n}\n\nexport interface DatabaseOptions {\n readonly url?: string | undefined;\n /**\n * A connection that bypasses any pooler, for LISTEN and migrations. Transaction poolers\n * such as PgBouncer, Neon's pooled endpoint or Hyperdrive cannot hold a LISTEN.\n * Defaults to `DATABASE_DIRECT_URL`, then to `url`.\n */\n readonly directUrl?: string | undefined;\n readonly dataDir?: string | undefined;\n readonly migrationsFolder?: string | undefined;\n}\n\n/** Runs one parameterized statement (`$1`, `$2`, ...) and answers its rows, as `pg` does. */\nexport interface DatabaseClient {\n query(text: string, params?: unknown[]): Promise<{ rows: unknown[] }>;\n}\n\nexport interface DatabaseHandle extends DataProvider<Database> {\n readonly db: Database;\n /**\n * The connection under `db`, for libraries that take a `pg`-style client. Each statement\n * runs on its own, outside any transaction, on the current connection scope's connections.\n */\n readonly client: DatabaseClient;\n readonly driver: \"pglite\" | \"node-postgres\";\n listen(\n channel: string,\n handler: (payload: string) => void,\n options?: ListenOptions,\n ): Promise<Unlisten>;\n migrate(folder: string): Promise<void>;\n close(): Promise<void>;\n}\n\nexport const DEFAULT_PGLITE_DIR = \".parlor/pglite\";\n\nexport function resolveDatabaseUrl(\n env: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n return env.DATABASE_URL || undefined;\n}\n\n/** PGlite's directory when `dataDir` is not given: `pglite` in the run's generated directory. */\nexport function resolvePgliteDir(env: NodeJS.ProcessEnv = process.env): string {\n const generated = env.PARLOR_GENERATED_DIR?.replace(/\\/+$/, \"\");\n return generated ? `${generated}/pglite` : DEFAULT_PGLITE_DIR;\n}\n\nexport function resolveDirectUrl(\n env: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n return env.DATABASE_DIRECT_URL || undefined;\n}\n\ninterface PgConnections extends ScopedConnections {\n readonly pool: pg.Pool;\n readonly db: NodePgDatabase;\n readonly listeners: Set<pg.Client>;\n}\n\nfunction openPgConnections(url: string, onClose?: () => void): PgConnections {\n const pool = new pg.Pool({ connectionString: url });\n pool.on(\"connect\", (client) => {\n const { connection } = client as unknown as { connection?: { stream?: unknown } };\n if (connection?.stream)\n corkWrites(connection.stream as Parameters<typeof corkWrites>[0]);\n });\n pool.on(\"error\", (error) => {\n console.warn(\"[parlor] an idle database connection dropped:\", error.message);\n });\n const listeners = new Set<pg.Client>();\n return {\n pool,\n db: drizzle(pool),\n listeners,\n close: once(async () => {\n onClose?.();\n await Promise.all([...listeners].map((client) => client.end()));\n await pool.end();\n }),\n };\n}\n\n/**\n * A deployment with no database yet: it loads, so a host that runs the app's modules before\n * its settings exist (Cloudflare checks a Worker as it uploads) accepts it, and every use\n * fails with `error`, which the first request reports.\n */\nexport function unconfiguredHandle(error: Error): DatabaseHandle {\n const fail = (): never => {\n throw error;\n };\n return {\n db: scopedDatabase(fail),\n client: { query: () => Promise.reject(error) },\n driver: \"node-postgres\",\n read: fail,\n transaction: () => Promise.reject(error),\n listen: () => Promise.reject(error),\n migrate: () => Promise.reject(error),\n close: async () => undefined,\n };\n}\n\n/** Stays valid across scopes, so code that captured `handle.db` at startup still works. */\nfunction scopedDatabase(current: () => Database): Database {\n return new Proxy({} as Database, {\n get(_target, property) {\n const db = current();\n const value: unknown = Reflect.get(db, property, db);\n return typeof value === \"function\" ? value.bind(db) : value;\n },\n has: (_target, property) => Reflect.has(current(), property),\n getPrototypeOf: () => Reflect.getPrototypeOf(current()),\n });\n}\n\nfunction nodePostgresHandle(url: string, directUrl: string): DatabaseHandle {\n const key = {};\n const outside = new ConnectionOwner();\n const opened = new Set<PgConnections>();\n const connections = () =>\n (currentOwner() ?? outside).connectionsFor(key, () => {\n const created = openPgConnections(url, () => opened.delete(created));\n opened.add(created);\n return created;\n });\n return {\n db: scopedDatabase(() => connections().db),\n client: { query: (text, params) => connections().pool.query(text, params) },\n driver: \"node-postgres\",\n read: () => connections().db,\n transaction: (run) => connections().db.transaction((tx) => run(tx)),\n listen: async (channel, handler, options = {}) => {\n const { listeners } = connections();\n const client = new pg.Client({ connectionString: directUrl });\n let state: \"listening\" | \"lost\" | \"closed\" = \"listening\";\n const current = () => state;\n const lose = () => {\n if (state !== \"listening\") return;\n state = \"lost\";\n listeners.delete(client);\n client.end().catch(() => {});\n options.onLost?.();\n };\n client.on(\"error\", lose);\n client.on(\"end\", lose);\n client.on(\"notification\", (message) => {\n if (message.channel === channel) handler(message.payload ?? \"\");\n });\n const identifier = pg.escapeIdentifier(channel);\n try {\n await client.connect();\n await client.query(`LISTEN ${identifier}`);\n } catch (error) {\n state = \"closed\";\n await client.end().catch(() => {});\n throw error;\n }\n if (current() === \"lost\")\n throw new Error(`LISTEN ${identifier} lost its connection`);\n listeners.add(client);\n return async () => {\n if (state !== \"listening\") return;\n state = \"closed\";\n listeners.delete(client);\n await client.query(`UNLISTEN ${identifier}`).catch(() => {});\n await client.end();\n };\n },\n migrate: async (folder) => {\n if (directUrl === url) {\n await migrateDatabase(connections().db, folder);\n return;\n }\n const direct = openPgConnections(directUrl);\n try {\n await migrateDatabase(direct.db, folder);\n } finally {\n await direct.close();\n }\n },\n close: once(async () => {\n outside.forget(key);\n await Promise.all([...opened].map((created) => created.close()));\n }),\n };\n}\n\n/**\n * `handle`, migrating `folder` on its first `prepare`. A failed run is not kept: the next\n * request tries again, rather than every request failing until the server restarts.\n */\nexport function migrateOnPrepare(handle: DatabaseHandle, folder: string): DatabaseHandle {\n let migrated: Promise<void> | undefined;\n const migrate = () =>\n handle.migrate(folder).catch((error: unknown) => {\n migrated = undefined;\n throw error;\n });\n return { ...handle, prepare: () => (migrated ??= migrate()) };\n}\n\nexport async function createDatabase(\n options: DatabaseOptions = {},\n): Promise<DatabaseHandle> {\n const url = options.url ?? resolveDatabaseUrl();\n const handle = url\n ? nodePostgresHandle(url, options.directUrl ?? resolveDirectUrl() ?? url)\n : await pgliteHandle(options.dataDir ?? resolvePgliteDir());\n const folder = options.migrationsFolder;\n if (!folder) return handle;\n if (migratesWhileLoading) {\n await handle.migrate(folder);\n return handle;\n }\n return migrateOnPrepare(handle, folder);\n}\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type DatabaseHandle } from "./database.js";
|
|
2
2
|
/** workerd allows no I/O while the app's modules load, so migrations wait for `prepare`. */
|
|
3
3
|
export declare const migratesWhileLoading = false;
|
|
4
|
+
/** No PGlite on Cloudflare: without DATABASE_URL the app still loads, and says so on use. */
|
|
4
5
|
export declare function pgliteHandle(): Promise<DatabaseHandle>;
|
|
5
6
|
//# sourceMappingURL=platform.workerd.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.workerd.d.ts","sourceRoot":"","sources":["../src/platform.workerd.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"platform.workerd.d.ts","sourceRoot":"","sources":["../src/platform.workerd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAsB,MAAM,eAAe,CAAC;AAExE,4FAA4F;AAC5F,eAAO,MAAM,oBAAoB,QAAQ,CAAC;AAE1C,6FAA6F;AAC7F,wBAAgB,YAAY,IAAI,OAAO,CAAC,cAAc,CAAC,CAQtD"}
|
package/dist/platform.workerd.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { unconfiguredHandle } from "./database.js";
|
|
1
2
|
/** workerd allows no I/O while the app's modules load, so migrations wait for `prepare`. */
|
|
2
3
|
export const migratesWhileLoading = false;
|
|
4
|
+
/** No PGlite on Cloudflare: without DATABASE_URL the app still loads, and says so on use. */
|
|
3
5
|
export function pgliteHandle() {
|
|
4
|
-
return Promise.
|
|
6
|
+
return Promise.resolve(unconfiguredHandle(new Error("DATABASE_URL is not set. PGlite only runs in development; on Cloudflare, set DATABASE_URL to a Postgres connection string.")));
|
|
5
7
|
}
|
|
6
8
|
//# sourceMappingURL=platform.workerd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.workerd.js","sourceRoot":"","sources":["../src/platform.workerd.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"platform.workerd.js","sourceRoot":"","sources":["../src/platform.workerd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAExE,4FAA4F;AAC5F,MAAM,CAAC,MAAM,oBAAoB,GAAG,KAAK,CAAC;AAE1C,6FAA6F;AAC7F,MAAM,UAAU,YAAY;IAC1B,OAAO,OAAO,CAAC,OAAO,CACpB,kBAAkB,CAChB,IAAI,KAAK,CACP,4HAA4H,CAC7H,CACF,CACF,CAAC;AACJ,CAAC","sourcesContent":["import { type DatabaseHandle, unconfiguredHandle } from \"./database.js\";\n\n/** workerd allows no I/O while the app's modules load, so migrations wait for `prepare`. */\nexport const migratesWhileLoading = false;\n\n/** No PGlite on Cloudflare: without DATABASE_URL the app still loads, and says so on use. */\nexport function pgliteHandle(): Promise<DatabaseHandle> {\n return Promise.resolve(\n unconfiguredHandle(\n new Error(\n \"DATABASE_URL is not set. PGlite only runs in development; on Cloudflare, set DATABASE_URL to a Postgres connection string.\",\n ),\n ),\n );\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parlor/data",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"homepage": "https://cychien.github.io/parlor",
|
|
5
5
|
"bugs": "https://github.com/cychien/parlor/issues",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"@electric-sql/pglite": "^0.5.9",
|
|
30
30
|
"drizzle-orm": "^0.45.4",
|
|
31
31
|
"pg": "^8.23.1",
|
|
32
|
-
"@parlor/core": "0.3.
|
|
32
|
+
"@parlor/core": "0.3.1"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/node": "^24.14.0",
|