@di-framework/pglite-component 6.0.9 → 6.0.12

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.
@@ -0,0 +1,514 @@
1
+ /**
2
+ * `SqlDatabase` adapter for the `di-framework:pglite/database@0.2.0` WIT
3
+ * import: embedded PostgreSQL 17 bundled into a provider component. The guest
4
+ * exchanges `value` variants over one exclusive connection, so every call is
5
+ * serialized through an async mutex.
6
+ *
7
+ * Repositories and migrations write `?` placeholders; they are rewritten to
8
+ * PostgreSQL's `$1`, `$2`, ... (see {@link toPgPlaceholders}). Transactions use
9
+ * the provider's `begin`/`commit`/`rollback`, and nested `transaction()` calls
10
+ * open savepoints. Result values match the `wasmcloud:postgres` codec in
11
+ * `@di-framework/repo` (`pgScalar`): JSON is parsed, `timestamptz` becomes an ISO string with
12
+ * milliseconds, and an `int8` outside the safe integer range becomes a decimal
13
+ * string. A Postgres error keeps its SQLSTATE in `error.code`.
14
+ *
15
+ * Component bindings differ in how they surface `result<T, error>`: some
16
+ * return `{ tag: 'ok' | 'err', val }` records, others throw an error object
17
+ * carrying the variant as `payload`. Both shapes are normalized here.
18
+ */
19
+ import { AsyncLocalStorage } from 'node:async_hooks';
20
+ import { AsyncMutex, guardedStatements, isWasmResult, leaseGuard, SQL_DATABASE_BRAND, wasmBlobBytes, } from '@di-framework/repo';
21
+ export const WASM_PGLITE_MODULE_ID = 'di-framework:pglite/database@0.2.0';
22
+ /** Database root the provider selects when `open` receives an empty path. */
23
+ export const WASM_PGLITE_DEFAULT_ROOT = '/data/pglite';
24
+ /**
25
+ * A provider error. `code` is the SQLSTATE for `execution-failed` (for example
26
+ * `23505` for a unique violation). The message then starts with
27
+ * `PostgreSQL <code>`, the format `isUniqueViolation` from `@di-framework/repo` reads.
28
+ */
29
+ export class WasmPgliteError extends Error {
30
+ tag;
31
+ detail;
32
+ code;
33
+ constructor(payload, cause) {
34
+ let detail;
35
+ let code;
36
+ if (typeof payload.val === 'string') {
37
+ detail = payload.val;
38
+ }
39
+ else if (typeof payload.val === 'object' && payload.val !== null) {
40
+ detail = payload.val.message;
41
+ code = payload.val.code || undefined;
42
+ }
43
+ super(wasmPgliteErrorMessage(payload.tag, code, detail), { cause });
44
+ this.name = 'WasmPgliteError';
45
+ this.tag = payload.tag;
46
+ this.detail = detail;
47
+ this.code = code;
48
+ }
49
+ }
50
+ function wasmPgliteErrorMessage(tag, code, detail) {
51
+ if (code)
52
+ return detail ? `PostgreSQL ${code} ${detail}` : `PostgreSQL ${code}`;
53
+ return detail ? `PGlite ${tag}: ${detail}` : `PGlite ${tag}`;
54
+ }
55
+ function isErrorPayload(value) {
56
+ return (typeof value === 'object' &&
57
+ value !== null &&
58
+ typeof value.tag === 'string' &&
59
+ value.tag !== 'ok');
60
+ }
61
+ /** Normalizes a thrown binding error or an `err` payload into an `Error`. */
62
+ export function normalizeWasmPgliteError(error) {
63
+ if (error instanceof WasmPgliteError)
64
+ return error;
65
+ if (isErrorPayload(error))
66
+ return new WasmPgliteError(error);
67
+ if (typeof error === 'object' && error !== null) {
68
+ const payload = error.payload;
69
+ if (isErrorPayload(payload))
70
+ return new WasmPgliteError(payload, error);
71
+ }
72
+ if (typeof error === 'string')
73
+ return new WasmPgliteError({ tag: 'other', val: error });
74
+ return error instanceof Error ? error : new Error(String(error));
75
+ }
76
+ /** Unwraps `{ tag: 'ok' | 'err' }` records; passes bare values through. */
77
+ export function unwrapWasmPgliteResult(value) {
78
+ if (isWasmResult(value)) {
79
+ if (value.tag === 'err')
80
+ throw normalizeWasmPgliteError(value.val);
81
+ return value.val;
82
+ }
83
+ return value;
84
+ }
85
+ const MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
86
+ const MIN_SAFE = BigInt(Number.MIN_SAFE_INTEGER);
87
+ /**
88
+ * Converts a bound JavaScript parameter into a `value` variant.
89
+ *
90
+ * - `bigint` in the safe integer range becomes `integer`; a larger one is sent
91
+ * as `text` (OID 0), so PostgreSQL parses it for an `int8`/`numeric` column
92
+ * without rounding through a double.
93
+ * - A `number` that is an integer outside the safe range throws a `TypeError`
94
+ * (pass a `bigint` or string for `int8`); other non-safe numbers, including
95
+ * NaN and Infinity, are `real` (`float8`).
96
+ * - `Date` becomes `timestamp` (RFC 3339 UTC); plain objects and arrays become
97
+ * `json`. Arrays are not PostgreSQL arrays: pass a literal such as `'{a,b}'`.
98
+ */
99
+ export function toWasmPgValue(value) {
100
+ if (value === null || value === undefined)
101
+ return { tag: 'null' };
102
+ switch (typeof value) {
103
+ case 'bigint':
104
+ // componentize-qjs lowers WIT s64 from JS numbers (f64), not BigInt (#153).
105
+ if (value >= MIN_SAFE && value <= MAX_SAFE) {
106
+ return { tag: 'integer', val: Number(value) };
107
+ }
108
+ return { tag: 'text', val: value.toString() };
109
+ case 'number':
110
+ if (Number.isSafeInteger(value)) {
111
+ return { tag: 'integer', val: value };
112
+ }
113
+ if (Number.isInteger(value)) {
114
+ // An integral double past 2^53 has already lost its low bits, and the
115
+ // column is most likely int8; sending float8 would fail or round silently.
116
+ throw new TypeError(`Cannot bind ${value} as a PostgreSQL parameter: it is an integer outside the safe range. Pass a bigint or a decimal string for int8 (or a string such as '1e300' for float8)`);
117
+ }
118
+ // Fractions, NaN and +/-Infinity are valid float8 values in PostgreSQL.
119
+ return { tag: 'real', val: value };
120
+ case 'boolean':
121
+ return { tag: 'boolean', val: value };
122
+ case 'string':
123
+ return { tag: 'text', val: value };
124
+ case 'object':
125
+ return toWasmPgObject(value);
126
+ }
127
+ throw new TypeError(`Cannot bind ${typeof value} as a PostgreSQL parameter; use null, number, bigint, boolean, string, Date, Uint8Array, or a JSON object`);
128
+ }
129
+ function toWasmPgObject(value) {
130
+ const bytes = wasmBlobBytes(value);
131
+ if (bytes)
132
+ return { tag: 'blob', val: bytes };
133
+ if (!(value instanceof Date))
134
+ return { tag: 'json', val: JSON.stringify(value) };
135
+ if (Number.isNaN(value.getTime())) {
136
+ throw new TypeError('Cannot bind an invalid Date as a PostgreSQL parameter');
137
+ }
138
+ return { tag: 'timestamp', val: value.toISOString() };
139
+ }
140
+ const RFC3339_UTC = /^(\d{4,}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(?:\.(\d+))?Z$/;
141
+ /**
142
+ * Formats a `timestamp` variant the way the `wasmcloud:postgres` codec does:
143
+ * `YYYY-MM-DDTHH:MM:SS.mmmZ`. Microseconds are truncated to milliseconds.
144
+ * `infinity`, `-infinity`, and BC dates are returned unchanged.
145
+ */
146
+ export function pgliteTimestampIso(value) {
147
+ const match = RFC3339_UTC.exec(value);
148
+ if (!match)
149
+ return value;
150
+ const millis = (match[2] ?? '').padEnd(3, '0').slice(0, 3);
151
+ return `${match[1]}.${millis}Z`;
152
+ }
153
+ /** Converts a `value` variant into what the `wasmcloud:postgres` codec in `@di-framework/repo` returns. */
154
+ export function fromWasmPgValue(value) {
155
+ switch (value.tag) {
156
+ case 'null':
157
+ return null;
158
+ case 'integer': {
159
+ if (typeof value.val === 'number')
160
+ return value.val;
161
+ return value.val >= MIN_SAFE && value.val <= MAX_SAFE
162
+ ? Number(value.val)
163
+ : value.val.toString();
164
+ }
165
+ case 'real':
166
+ case 'text':
167
+ case 'boolean':
168
+ return value.val;
169
+ case 'blob':
170
+ return value.val instanceof Uint8Array ? value.val : Uint8Array.from(value.val);
171
+ case 'json':
172
+ try {
173
+ return JSON.parse(value.val);
174
+ }
175
+ catch {
176
+ return value.val;
177
+ }
178
+ case 'timestamp':
179
+ return pgliteTimestampIso(value.val);
180
+ default:
181
+ throw new WasmPgliteError({
182
+ tag: 'value-conversion-failed',
183
+ val: `Unknown value tag ${String(value.tag)}`,
184
+ });
185
+ }
186
+ }
187
+ export function toWasmPgParams(params = []) {
188
+ return params.map((param) => toWasmPgValue(param));
189
+ }
190
+ export function fromWasmPgRow(row) {
191
+ const result = {};
192
+ for (const [column, value] of row) {
193
+ result[column] = fromWasmPgValue(value);
194
+ }
195
+ return result;
196
+ }
197
+ const IDENTIFIER_CHAR = /[\w$]/;
198
+ const DOLLAR_TAG = /^\$(?:[A-Za-z_]\w*)?\$/;
199
+ const POSITIONAL = /^\$\d+/;
200
+ function skipQuoted(sql, start, quote, backslashes) {
201
+ let index = start + 1;
202
+ while (index < sql.length) {
203
+ const char = sql[index];
204
+ if (backslashes && char === '\\') {
205
+ index += 2;
206
+ }
207
+ else if (char === quote) {
208
+ if (sql[index + 1] !== quote)
209
+ return index + 1;
210
+ index += 2;
211
+ }
212
+ else {
213
+ index++;
214
+ }
215
+ }
216
+ return sql.length;
217
+ }
218
+ function skipBlockComment(sql, start) {
219
+ let depth = 0;
220
+ let index = start;
221
+ while (index < sql.length) {
222
+ if (sql.startsWith('/*', index)) {
223
+ depth++;
224
+ index += 2;
225
+ }
226
+ else if (sql.startsWith('*/', index)) {
227
+ depth--;
228
+ index += 2;
229
+ if (depth === 0)
230
+ return index;
231
+ }
232
+ else {
233
+ index++;
234
+ }
235
+ }
236
+ return sql.length;
237
+ }
238
+ function isEscapeString(sql, quote) {
239
+ const prefix = sql[quote - 1];
240
+ return (prefix === 'E' || prefix === 'e') && !IDENTIFIER_CHAR.test(sql[quote - 2] ?? '');
241
+ }
242
+ /**
243
+ * Rewrites `?` placeholders to `$1`, `$2`, ... in order.
244
+ *
245
+ * Skipped: `'...'` and `E'...'` strings, `"..."` identifiers, `$tag$...$tag$`
246
+ * dollar quotes, `--` and nested `/* *\/` comments, and the `?|` / `?&` jsonb
247
+ * operators. The bare jsonb `?` (key exists) operator cannot be told apart
248
+ * from a placeholder: write `jsonb_exists(col, ?)` instead. The adapter does
249
+ * not rewrite statements bound without parameters, so a literal `?` operator
250
+ * still works there. Mixing `?` with explicit `$n` placeholders throws.
251
+ */
252
+ export function toPgPlaceholders(sql) {
253
+ let out = '';
254
+ let index = 0;
255
+ let count = 0;
256
+ let positional = false;
257
+ while (index < sql.length) {
258
+ let end = skippedTokenEnd(sql, index);
259
+ if (end === undefined && sql[index] === '$') {
260
+ const dollar = dollarTokenEnd(sql, index);
261
+ end = dollar.end;
262
+ positional ||= dollar.positional;
263
+ }
264
+ if (end === undefined && sql[index] === '?') {
265
+ out += `$${++count}`;
266
+ index++;
267
+ continue;
268
+ }
269
+ end ??= index + 1;
270
+ out += sql.slice(index, end);
271
+ index = end;
272
+ }
273
+ if (count > 0 && positional) {
274
+ throw new TypeError('Cannot mix ? placeholders with $n placeholders in one statement');
275
+ }
276
+ return out;
277
+ }
278
+ /** End of the string, identifier, comment, or `?|`/`?&` operator at `index`. */
279
+ function skippedTokenEnd(sql, index) {
280
+ const char = sql[index];
281
+ const next = sql[index + 1];
282
+ if (char === "'")
283
+ return skipQuoted(sql, index, "'", isEscapeString(sql, index));
284
+ if (char === '"')
285
+ return skipQuoted(sql, index, '"', false);
286
+ if (char === '-' && next === '-') {
287
+ const newline = sql.indexOf('\n', index);
288
+ return newline < 0 ? sql.length : newline;
289
+ }
290
+ if (char === '/' && next === '*')
291
+ return skipBlockComment(sql, index);
292
+ if (char === '?' && (next === '|' || next === '&'))
293
+ return index + 2;
294
+ return undefined;
295
+ }
296
+ /** End of the `$tag$...$tag$` body or `$n` placeholder that starts at `index`. */
297
+ function dollarTokenEnd(sql, index) {
298
+ if (IDENTIFIER_CHAR.test(sql[index - 1] ?? ''))
299
+ return { end: index + 1, positional: false };
300
+ const rest = sql.slice(index);
301
+ const tag = DOLLAR_TAG.exec(rest)?.[0];
302
+ if (tag) {
303
+ const close = sql.indexOf(tag, index + tag.length);
304
+ return { end: close < 0 ? sql.length : close + tag.length, positional: false };
305
+ }
306
+ const number = POSITIONAL.exec(rest)?.[0];
307
+ if (number)
308
+ return { end: index + number.length, positional: true };
309
+ return { end: index + 1, positional: false };
310
+ }
311
+ async function call(fn) {
312
+ try {
313
+ return unwrapWasmPgliteResult(await fn());
314
+ }
315
+ catch (error) {
316
+ throw normalizeWasmPgliteError(error);
317
+ }
318
+ }
319
+ function descendsFrom(scope, ancestor) {
320
+ for (let current = scope.parent; current; current = current.parent) {
321
+ if (current === ancestor)
322
+ return true;
323
+ }
324
+ return false;
325
+ }
326
+ /**
327
+ * Wraps an open WIT `connection` resource as a `SqlDatabase`.
328
+ *
329
+ * Every statement holds one mutex. `transaction(fn)` runs `begin` … `commit`
330
+ * and calls `rollback` when `fn` throws. Statements issued from inside `fn`,
331
+ * through the callback view or the outer handle, join the transaction; other
332
+ * tasks wait. A nested `transaction()` opens a savepoint: its failure rolls
333
+ * back to the savepoint, and its work commits only with the outer transaction.
334
+ * Sibling savepoints started concurrently run one at a time; a plain statement
335
+ * issued while a sibling savepoint is open joins that savepoint.
336
+ *
337
+ * Inside `fn`, prefer the callback view: while its transaction or savepoint is
338
+ * open it joins from any async context. The outer handle joins only from
339
+ * `fn`'s own async context (tracked with AsyncLocalStorage); from anywhere else
340
+ * it waits for the mutex, which deadlocks if `fn` awaits it.
341
+ *
342
+ * Sibling savepoints are queued on their parent. `transaction()` on a view,
343
+ * called from an async context outside an open sibling savepoint, waits for
344
+ * that sibling to finish; if the sibling awaits that call, it waits forever.
345
+ *
346
+ * Joining lasts only while a transaction is open. A savepoint view kept after
347
+ * its savepoint ends joins the nearest open ancestor, so its work commits or
348
+ * rolls back with that ancestor. Once the root transaction has ended, a
349
+ * retained view, like a timer scheduled inside `fn`, takes the mutex like any
350
+ * other caller.
351
+ */
352
+ export function createSqlDatabaseFromWasmPgliteConnection(connection) {
353
+ const mutex = new AsyncMutex();
354
+ // The outer handle joins a running root transaction from its own async
355
+ // context for as long as that transaction's lease is active. The lease ends
356
+ // before COMMIT/ROLLBACK is issued (see `enter`), unlike `contextLock`, which
357
+ // would keep it until the holder returns.
358
+ const held = new AsyncLocalStorage();
359
+ const locked = (fn) => (held.getStore()?.active ? fn() : mutex.run(fn));
360
+ // Statements a transaction's callers issue concurrently reach the connection
361
+ // one at a time, even when the binding returns promises (see `leaseGuard`).
362
+ const wire = new AsyncMutex();
363
+ const invoke = (fn) => wire.run(() => call(fn));
364
+ const activeScope = new AsyncLocalStorage();
365
+ let savepoints = 0;
366
+ const bind = (sql, params) => {
367
+ const values = toWasmPgParams(params);
368
+ return { sql: values.length > 0 ? toPgPlaceholders(sql) : sql, values };
369
+ };
370
+ const calls = {
371
+ run: async (sql, params) => {
372
+ const bound = bind(sql, params);
373
+ const changes = await invoke(() => connection.run(bound.sql, bound.values));
374
+ return { changes: Number(changes ?? 0) };
375
+ },
376
+ query: async (sql, params) => {
377
+ const bound = bind(sql, params);
378
+ const rows = await invoke(() => connection.query(bound.sql, bound.values));
379
+ return rows.map((row) => fromWasmPgRow(row));
380
+ },
381
+ first: async (sql, params) => {
382
+ const bound = bind(sql, params);
383
+ const row = await invoke(() => connection.first(bound.sql, bound.values));
384
+ return row ? fromWasmPgRow(row) : null;
385
+ },
386
+ exec: async (sql) => {
387
+ await invoke(() => connection.exec(sql));
388
+ },
389
+ };
390
+ const statementsVia = (guard) => ({
391
+ [SQL_DATABASE_BRAND]: true,
392
+ ...guardedStatements(guard, calls),
393
+ });
394
+ /** The innermost transaction or savepoint of this context that is still open. */
395
+ const liveScope = () => {
396
+ let scope = activeScope.getStore();
397
+ while (scope && !scope.lease.active)
398
+ scope = scope.parent;
399
+ return scope;
400
+ };
401
+ const enter = async (scope, fn, open, close, undo) => {
402
+ try {
403
+ await invoke(open);
404
+ try {
405
+ const result = await activeScope.run(scope, () => fn(scope.view));
406
+ // End the lease before COMMIT / RELEASE is issued: a statement that
407
+ // arrives late waits for the mutex and runs after the transaction,
408
+ // instead of reaching the wire between the close and the end of the lease.
409
+ scope.lease.active = false;
410
+ await invoke(close);
411
+ return result;
412
+ }
413
+ catch (error) {
414
+ scope.lease.active = false;
415
+ try {
416
+ await invoke(undo);
417
+ }
418
+ catch { }
419
+ throw error;
420
+ }
421
+ }
422
+ finally {
423
+ scope.lease.active = false;
424
+ }
425
+ };
426
+ const nest = (parent, fn) => {
427
+ const current = liveScope();
428
+ if (current && descendsFrom(current, parent))
429
+ return nest(current, fn);
430
+ return parent.children.run(() => {
431
+ const name = `di_sp_${++savepoints}`;
432
+ return enter(createScope(parent), fn, () => connection.savepoint(name), () => connection.releaseSavepoint(name), () => connection.rollbackToSavepoint(name));
433
+ });
434
+ };
435
+ // A view runs directly while its scope is open, from any async context: the
436
+ // scope's lease is cleared before the mutex is released. Once a savepoint has
437
+ // ended, its view falls back to the parent scope, which is still open and
438
+ // may be awaiting the caller; taking the root mutex there would deadlock. A
439
+ // root view, whose transaction holds that mutex, takes it like the outer handle.
440
+ const createScope = (parent, lease = { active: true }) => {
441
+ const guard = leaseGuard(lease, parent ? parent.guard : locked);
442
+ const scope = {
443
+ parent,
444
+ children: new AsyncMutex(),
445
+ lease,
446
+ guard,
447
+ view: {
448
+ ...statementsVia(guard),
449
+ // A view kept past its scope opens its transaction on the parent scope
450
+ // (or, for a root view, a fresh transaction).
451
+ transaction: (fn) => lease.active
452
+ ? nest(scope, fn)
453
+ : parent
454
+ ? parent.view.transaction(fn)
455
+ : database.transaction(fn),
456
+ },
457
+ };
458
+ return scope;
459
+ };
460
+ const database = {
461
+ ...statementsVia(locked),
462
+ transaction: async (fn) => {
463
+ const current = liveScope();
464
+ if (current)
465
+ return nest(current, fn);
466
+ return mutex.run(() => {
467
+ // The root scope's lease doubles as the outer handle's join lease.
468
+ const lease = { active: true };
469
+ return held.run(lease, () => enter(createScope(undefined, lease), fn, () => connection.begin(), () => connection.commit(), () => connection.rollback()));
470
+ });
471
+ },
472
+ close: () => locked(() => invoke(() => connection.close())),
473
+ };
474
+ return database;
475
+ }
476
+ let cachedModule;
477
+ /**
478
+ * Imports `di-framework:pglite/database@0.2.0`, provided by the composed provider component.
479
+ *
480
+ * The module's types come from `wit.d.ts`, generated from `wit/world.wit`. The
481
+ * conversion below has no cast, so a WIT change that breaks the adapter's view
482
+ * of the interface fails the typecheck. The check covers method names, arity
483
+ * and result types, not the parameter variants (see the TODO on
484
+ * `WasmPgliteConnection`).
485
+ */
486
+ export function loadWasmPgliteModule() {
487
+ cachedModule ??= import('di-framework:pglite/database@0.2.0')
488
+ .then((module) => module)
489
+ .catch((error) => {
490
+ cachedModule = undefined;
491
+ throw new Error(`The ${WASM_PGLITE_MODULE_ID} import is unavailable in this runtime; compose the component with the di-framework PGlite provider`, { cause: error });
492
+ });
493
+ return cachedModule;
494
+ }
495
+ /**
496
+ * Opens a persistent PGlite database root through the provider component and
497
+ * adapts it to `SqlDatabase`. `path` is the root directory inside a writable
498
+ * WASI preopen (for example `/data/identity`); an empty path selects
499
+ * {@link WASM_PGLITE_DEFAULT_ROOT}. The provider allows one open connection per
500
+ * component instance, so keep one handle and share it.
501
+ */
502
+ export async function createWasmPgliteDatabase(path = '', options = {}) {
503
+ const { module: providedModule, loadModule, ...openOptions } = options;
504
+ const module = providedModule ?? (await (loadModule ?? loadWasmPgliteModule)());
505
+ const resolvedOpenOptions = {
506
+ ...(openOptions.database !== undefined ? { database: openOptions.database } : {}),
507
+ ...(openOptions.statementTimeoutMs !== undefined
508
+ ? { statementTimeoutMs: openOptions.statementTimeoutMs }
509
+ : {}),
510
+ };
511
+ const hasOptions = Object.keys(resolvedOpenOptions).length > 0;
512
+ const connection = await call(() => module.open(path, hasOptions ? resolvedOpenOptions : undefined));
513
+ return createSqlDatabaseFromWasmPgliteConnection(connection);
514
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * `SqlDatabase` adapter for the `di-framework:pglite/database` WIT interface
3
+ * this package's component exports. Depends on `@di-framework/repo` as a peer.
4
+ */
5
+ export * from './adapter.js';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * `SqlDatabase` adapter for the `di-framework:pglite/database` WIT interface
3
+ * this package's component exports. Depends on `@di-framework/repo` as a peer.
4
+ */
5
+ export * from './adapter.js';
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@di-framework/pglite-component",
3
- "version": "6.0.9",
4
- "private": false,
5
- "description": "Embedded PostgreSQL component with persistent WASI storage and no TCP.",
3
+ "version": "6.0.12",
4
+ "type": "module",
5
+ "description": "Embedded PostgreSQL component with persistent WASI storage and no TCP, plus its SqlDatabase adapter for @di-framework/repo.",
6
6
  "license": "(MIT OR Apache-2.0)",
7
7
  "author": "@di-framework Contributors",
8
8
  "repository": {
@@ -14,15 +14,54 @@
14
14
  "url": "https://github.com/di-framework/platform/issues"
15
15
  },
16
16
  "homepage": "https://github.com/di-framework/platform",
17
+ "keywords": [
18
+ "postgresql",
19
+ "pglite",
20
+ "wasi",
21
+ "wit",
22
+ "webassembly",
23
+ "component-model",
24
+ "di-framework"
25
+ ],
26
+ "main": "./dist/js/index.js",
27
+ "types": "./dist/js/index.d.ts",
28
+ "module": "./dist/js/index.js",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/js/index.d.ts",
32
+ "bun": "./dist/js/index.js",
33
+ "import": "./dist/js/index.js",
34
+ "default": "./dist/js/index.js"
35
+ },
36
+ "./wasm": "./dist/di-framework-pglite.wasm",
37
+ "./wit": "./dist/di-framework-pglite.wit",
38
+ "./wit/*": "./wit/*",
39
+ "./package.json": "./package.json"
40
+ },
17
41
  "files": [
18
- "dist",
42
+ "dist/js",
43
+ "dist/di-framework-pglite.wasm",
44
+ "dist/di-framework-pglite.wit",
45
+ "dist/BUILD-INFO.txt",
46
+ "dist/SHA256SUMS",
47
+ "dist/THIRD-PARTY-NOTICES.txt",
19
48
  "README.md",
20
- "wit"
49
+ "wit",
50
+ "LICENSE-MIT",
51
+ "LICENSE-APACHE"
21
52
  ],
22
53
  "scripts": {
23
- "build": "make build",
54
+ "build": "bun run build:adapter && make build",
55
+ "build:adapter": "bun scripts/wit-types.ts --check && rm -rf dist/js && bun x tsc -p tsconfig.build.json && bun ../../scripts/fix-esm-imports.ts dist/js",
56
+ "wit-types": "bun scripts/wit-types.ts",
24
57
  "test": "bun test",
25
58
  "smoke": "make smoke",
26
59
  "publish-asset": "make publish-asset"
60
+ },
61
+ "peerDependencies": {
62
+ "@di-framework/repo": "^6.1.0"
63
+ },
64
+ "devDependencies": {
65
+ "@di-framework/repo": "^6.1.0"
27
66
  }
28
67
  }
package/wit/world.wit CHANGED
@@ -1,4 +1,4 @@
1
- package di-framework:pglite@0.1.0;
1
+ package di-framework:pglite@0.2.0;
2
2
 
3
3
  /// Types shared by the `database` interface.
4
4
  ///
@@ -25,9 +25,11 @@ interface types {
25
25
  open-failed(string),
26
26
  /// The connection has been closed with `close` and cannot be used again.
27
27
  closed,
28
- /// The statement could not be prepared or executed: syntax error, unknown
29
- /// table or column. Only single statements are accepted by `run`/`query`/`first`
30
- /// (unlike `exec`, which allows `;`-separated batches like the SQLite provider).
28
+ /// The SQL was rejected before execution. Causes: a syntax error (42601),
29
+ /// an undeterminable parameter type (42P18), a bad savepoint name, a NUL
30
+ /// byte in the SQL, or more than one statement passed to `run`/`query`/
31
+ /// `first` (the server reports that as a syntax error; unlike `exec`,
32
+ /// which allows `;`-separated batches like the SQLite provider).
31
33
  invalid-sql(string),
32
34
  /// Wrong number of parameters or a value that could not be bound.
33
35
  /// Parameters bind positionally to `$1`, `$2`, ... (not `?`/`?NNN`).
@@ -50,6 +52,23 @@ interface types {
50
52
  /// A dynamically-typed value, used for parameters and results.
51
53
  /// Identical to the SQLite provider plus `boolean`: Postgres has a native
52
54
  /// boolean type and the text encoding (`t`/`f`) is lossy without it.
55
+ /// `json` and `timestamp` are new in 0.2.0 for PostgreSQL-specific types.
56
+ ///
57
+ /// Results (session is `DateStyle=ISO`, `TimeZone=UTC`):
58
+ /// - `bool` -> `boolean`; `int2`/`int4`/`int8` -> `integer` (`int8` too);
59
+ /// `float4`/`float8` -> `real`; `bytea` -> `blob`.
60
+ /// - `json` and `jsonb` -> `json` (JSON text).
61
+ /// - `timestamptz` -> `timestamp`, RFC 3339 in UTC (`2024-01-15T10:30:00Z`,
62
+ /// fractional seconds kept). `infinity` and `-infinity` are returned as
63
+ /// those literals.
64
+ /// - `timestamp` (no zone) and `date` -> `text` (`2024-01-15 10:30:00`,
65
+ /// `2024-01-15`).
66
+ /// - `uuid` and `numeric` -> `text`; arrays -> `text` as PostgreSQL array
67
+ /// literals (`{a,b}`); all other types -> `text`.
68
+ ///
69
+ /// Parameters: `text`, `json` and `timestamp` are sent with unknown type
70
+ /// (OID 0) so PostgreSQL infers the type from context; a parameter with no
71
+ /// context (`SELECT $1 IS NULL`) needs a cast.
53
72
  variant value {
54
73
  null,
55
74
  integer(s64),
@@ -57,6 +76,8 @@ interface types {
57
76
  text(string),
58
77
  blob(list<u8>),
59
78
  boolean(bool),
79
+ json(string),
80
+ timestamp(string),
60
81
  }
61
82
 
62
83
  /// A result row: `(column-name, value)` pairs in column order.