@fedify/pglite 2.4.0-dev.0

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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright 2024–2026 Hong Minhee
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ <!-- deno-fmt-ignore-file -->
2
+
3
+ @fedify/pglite: PGlite driver for Fedify
4
+ ========================================
5
+
6
+ [![JSR][JSR badge]][JSR]
7
+ [![npm][npm badge]][npm]
8
+
9
+ This package provides [`PgliteKvStore`], a PGlite-backed implementation of
10
+ Fedify's [`KvStore`]:
11
+
12
+ ~~~~ typescript
13
+ import { PGlite } from "@electric-sql/pglite";
14
+ import { createFederation } from "@fedify/fedify";
15
+ import { PgliteKvStore } from "@fedify/pglite";
16
+
17
+ const pg = new PGlite("./data/fedify");
18
+ const federation = createFederation({
19
+ kv: new PgliteKvStore(pg),
20
+ });
21
+ ~~~~
22
+
23
+ The caller owns the `PGlite` instance. Close it during application shutdown,
24
+ after every pending store operation has completed. `PgliteKvStore` neither
25
+ creates nor closes the instance.
26
+
27
+ Use one PGlite instance per data directory and runtime isolate. PGlite does
28
+ not make writes visible to other processes, so the same data directory cannot
29
+ back horizontally scaled workers. This package does not provide a message
30
+ queue because those constraints cannot preserve ordering or delivery during a
31
+ rolling restart. See [PGlite issue #489] and [PGlite pull request #892].
32
+
33
+ [JSR badge]: https://jsr.io/badges/@fedify/pglite
34
+ [JSR]: https://jsr.io/@fedify/pglite
35
+ [npm badge]: https://img.shields.io/npm/v/@fedify/pglite?logo=npm
36
+ [npm]: https://www.npmjs.com/package/@fedify/pglite
37
+ [`PgliteKvStore`]: https://jsr.io/@fedify/pglite/doc/~/PgliteKvStore
38
+ [`KvStore`]: https://jsr.io/@fedify/fedify/doc/federation/~/KvStore
39
+ [PGlite issue #489]: https://github.com/electric-sql/pglite/issues/489#issuecomment-2587892190
40
+ [PGlite pull request #892]: https://github.com/electric-sql/pglite/pull/892
41
+
42
+
43
+ Installation
44
+ ------------
45
+
46
+ ~~~~ sh
47
+ deno add jsr:@fedify/pglite npm:@electric-sql/pglite # Deno
48
+ npm add @fedify/pglite @electric-sql/pglite # npm
49
+ pnpm add @fedify/pglite @electric-sql/pglite # pnpm
50
+ yarn add @fedify/pglite @electric-sql/pglite # Yarn
51
+ bun add @fedify/pglite @electric-sql/pglite # Bun
52
+ ~~~~
package/dist/kv.cjs ADDED
@@ -0,0 +1,217 @@
1
+
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const require_index = require("./node_modules/.pnpm/temporal-polyfill@1.0.1/node_modules/temporal-polyfill/index.cjs");
4
+ let _logtape_logtape = require("@logtape/logtape");
5
+ //#region src/kv.ts
6
+ const Temporal = require_index.Temporal;
7
+ const logger = (0, _logtape_logtape.getLogger)([
8
+ "fedify",
9
+ "pglite",
10
+ "kv"
11
+ ]);
12
+ function quoteIdentifier(identifier) {
13
+ const parts = identifier.split(".");
14
+ if (parts.includes("")) throw new TypeError(`Invalid table name for the key–value store: ${JSON.stringify(identifier)}`);
15
+ return parts.map((part) => `"${part.replaceAll("\"", "\"\"")}"`).join(".");
16
+ }
17
+ function serializeJson(value) {
18
+ const serialized = JSON.stringify(value);
19
+ if (serialized == null) throw new TypeError("PGlite cannot store a value that is not valid JSON");
20
+ return serialized;
21
+ }
22
+ /**
23
+ * A key–value store backed by an embedded PGlite database.
24
+ *
25
+ * The caller owns the PGlite instance and is responsible for closing it after
26
+ * every operation using the store has completed. Use only one PGlite instance
27
+ * for a data directory within a runtime isolate.
28
+ *
29
+ * @example
30
+ * ```ts ignore
31
+ * import { PGlite } from "@electric-sql/pglite";
32
+ * import { createFederation } from "@fedify/fedify";
33
+ * import { PgliteKvStore } from "@fedify/pglite";
34
+ *
35
+ * const pg = new PGlite("./data/fedify");
36
+ * const federation = createFederation({
37
+ * // ...
38
+ * kv: new PgliteKvStore(pg),
39
+ * });
40
+ *
41
+ * // Close pg during application shutdown, after pending operations finish.
42
+ * await pg.close();
43
+ * ```
44
+ * @since 2.4.0
45
+ */
46
+ var PgliteKvStore = class {
47
+ #pg;
48
+ #tableName;
49
+ #quotedTableName;
50
+ #initialized;
51
+ #initializing;
52
+ /**
53
+ * Creates a new PGlite key–value store.
54
+ * @param pg The caller-owned PGlite instance to use.
55
+ * @param options The options for the key–value store.
56
+ */
57
+ constructor(pg, options = {}) {
58
+ this.#pg = pg;
59
+ this.#tableName = options.tableName ?? "fedify_kv_v2";
60
+ this.#quotedTableName = quoteIdentifier(this.#tableName);
61
+ this.#initialized = options.initialized ?? false;
62
+ }
63
+ async #expire() {
64
+ await this.#pg.query(`
65
+ DELETE FROM ${this.#quotedTableName}
66
+ WHERE ttl IS NOT NULL AND created + ttl < CURRENT_TIMESTAMP;
67
+ `);
68
+ }
69
+ async get(key) {
70
+ await this.initialize();
71
+ return (await this.#pg.query(`
72
+ SELECT value
73
+ FROM ${this.#quotedTableName}
74
+ WHERE key = $1::text[]
75
+ AND (ttl IS NULL OR created + ttl > CURRENT_TIMESTAMP);
76
+ `, [key])).rows[0]?.value;
77
+ }
78
+ async set(key, value, options) {
79
+ await this.initialize();
80
+ const ttl = options?.ttl == null ? null : Temporal.Duration.from(options.ttl).toString();
81
+ await this.#pg.query(`
82
+ INSERT INTO ${this.#quotedTableName} (key, value, ttl)
83
+ VALUES ($1::text[], $2::text::jsonb, $3::text::interval)
84
+ ON CONFLICT (key)
85
+ DO UPDATE SET
86
+ value = EXCLUDED.value,
87
+ created = CURRENT_TIMESTAMP,
88
+ ttl = EXCLUDED.ttl;
89
+ `, [
90
+ key,
91
+ serializeJson(value),
92
+ ttl
93
+ ]);
94
+ await this.#expire();
95
+ }
96
+ async delete(key) {
97
+ await this.initialize();
98
+ await this.#pg.query(`
99
+ DELETE FROM ${this.#quotedTableName}
100
+ WHERE key = $1::text[];
101
+ `, [key]);
102
+ await this.#expire();
103
+ }
104
+ /** {@inheritDoc KvStore.cas} */
105
+ async cas(key, expectedValue, newValue, options) {
106
+ await this.initialize();
107
+ const ttl = options?.ttl == null ? null : Temporal.Duration.from(options.ttl).toString();
108
+ if (expectedValue === void 0 && newValue === void 0) {
109
+ const matched = await this.get(key) === void 0;
110
+ await this.#expire();
111
+ return matched;
112
+ }
113
+ let rowCount;
114
+ if (expectedValue === void 0) rowCount = (await this.#pg.query(`
115
+ INSERT INTO ${this.#quotedTableName} AS existing
116
+ (key, value, created, ttl)
117
+ VALUES (
118
+ $1::text[],
119
+ $2::text::jsonb,
120
+ CURRENT_TIMESTAMP,
121
+ $3::text::interval
122
+ )
123
+ ON CONFLICT (key)
124
+ DO UPDATE SET
125
+ value = EXCLUDED.value,
126
+ created = EXCLUDED.created,
127
+ ttl = EXCLUDED.ttl
128
+ WHERE existing.ttl IS NOT NULL
129
+ AND existing.created + existing.ttl <= CURRENT_TIMESTAMP
130
+ RETURNING key;
131
+ `, [
132
+ key,
133
+ serializeJson(newValue),
134
+ ttl
135
+ ])).rows.length;
136
+ else if (newValue === void 0) rowCount = (await this.#pg.query(`
137
+ DELETE FROM ${this.#quotedTableName}
138
+ WHERE key = $1::text[]
139
+ AND (ttl IS NULL OR created + ttl > CURRENT_TIMESTAMP)
140
+ AND value = $2::text::jsonb
141
+ RETURNING key;
142
+ `, [key, serializeJson(expectedValue)])).rows.length;
143
+ else rowCount = (await this.#pg.query(`
144
+ UPDATE ${this.#quotedTableName}
145
+ SET
146
+ value = $3::text::jsonb,
147
+ created = CURRENT_TIMESTAMP,
148
+ ttl = $4::text::interval
149
+ WHERE key = $1::text[]
150
+ AND (ttl IS NULL OR created + ttl > CURRENT_TIMESTAMP)
151
+ AND value = $2::text::jsonb
152
+ RETURNING key;
153
+ `, [
154
+ key,
155
+ serializeJson(expectedValue),
156
+ serializeJson(newValue),
157
+ ttl
158
+ ])).rows.length;
159
+ await this.#expire();
160
+ return rowCount > 0;
161
+ }
162
+ /** {@inheritDoc KvStore.list} */
163
+ async *list(prefix) {
164
+ await this.initialize();
165
+ const result = prefix == null || prefix.length === 0 ? await this.#pg.query(`
166
+ SELECT key, value
167
+ FROM ${this.#quotedTableName}
168
+ WHERE ttl IS NULL OR created + ttl > CURRENT_TIMESTAMP
169
+ ORDER BY key;
170
+ `) : await this.#pg.query(`
171
+ SELECT key, value
172
+ FROM ${this.#quotedTableName}
173
+ WHERE array_length(key, 1) >= $1
174
+ AND key[1:$1] = $2::text[]
175
+ AND (ttl IS NULL OR created + ttl > CURRENT_TIMESTAMP)
176
+ ORDER BY key;
177
+ `, [prefix.length, prefix]);
178
+ for (const row of result.rows) yield row;
179
+ }
180
+ /**
181
+ * Creates the table used by the store if it does not already exist.
182
+ */
183
+ async initialize() {
184
+ await this.#pg.waitReady;
185
+ if (this.#initialized) return;
186
+ this.#initializing ??= (async () => {
187
+ logger.debug("Initializing the key–value store table {tableName}...", { tableName: this.#tableName });
188
+ await this.#pg.query(`
189
+ CREATE TABLE IF NOT EXISTS ${this.#quotedTableName} (
190
+ key text[] PRIMARY KEY,
191
+ value jsonb NOT NULL,
192
+ created timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
193
+ ttl interval
194
+ );
195
+ `);
196
+ this.#initialized = true;
197
+ logger.debug("Initialized the key–value store table {tableName}.", { tableName: this.#tableName });
198
+ })();
199
+ try {
200
+ await this.#initializing;
201
+ } catch (error) {
202
+ this.#initializing = void 0;
203
+ throw error;
204
+ }
205
+ }
206
+ /**
207
+ * Drops the table used by the store. Does nothing if it does not exist.
208
+ */
209
+ async drop() {
210
+ await this.#pg.waitReady;
211
+ await this.#pg.query(`DROP TABLE IF EXISTS ${this.#quotedTableName};`);
212
+ this.#initialized = false;
213
+ this.#initializing = void 0;
214
+ }
215
+ };
216
+ //#endregion
217
+ exports.PgliteKvStore = PgliteKvStore;
package/dist/kv.d.cts ADDED
@@ -0,0 +1,74 @@
1
+ /// <reference types="emscripten" />
2
+ /// <reference lib="esnext.temporal" />
3
+ import { PGliteInterface } from "@electric-sql/pglite";
4
+ import { KvKey, KvStore, KvStoreListEntry, KvStoreSetOptions } from "@fedify/fedify";
5
+
6
+ //#region src/kv.d.ts
7
+ /**
8
+ * Options for the PGlite key–value store.
9
+ */
10
+ interface PgliteKvStoreOptions {
11
+ /**
12
+ * The table name to use for the key–value store.
13
+ * A `.` separates the schema from the table name, so neither the whole
14
+ * name nor any segment between dots can be empty.
15
+ * `"fedify_kv_v2"` by default.
16
+ * @default `"fedify_kv_v2"`
17
+ */
18
+ readonly tableName?: string;
19
+ /**
20
+ * Whether the table has already been initialized. `false` by default.
21
+ * @default `false`
22
+ */
23
+ readonly initialized?: boolean;
24
+ }
25
+ /**
26
+ * A key–value store backed by an embedded PGlite database.
27
+ *
28
+ * The caller owns the PGlite instance and is responsible for closing it after
29
+ * every operation using the store has completed. Use only one PGlite instance
30
+ * for a data directory within a runtime isolate.
31
+ *
32
+ * @example
33
+ * ```ts ignore
34
+ * import { PGlite } from "@electric-sql/pglite";
35
+ * import { createFederation } from "@fedify/fedify";
36
+ * import { PgliteKvStore } from "@fedify/pglite";
37
+ *
38
+ * const pg = new PGlite("./data/fedify");
39
+ * const federation = createFederation({
40
+ * // ...
41
+ * kv: new PgliteKvStore(pg),
42
+ * });
43
+ *
44
+ * // Close pg during application shutdown, after pending operations finish.
45
+ * await pg.close();
46
+ * ```
47
+ * @since 2.4.0
48
+ */
49
+ declare class PgliteKvStore implements KvStore {
50
+ #private;
51
+ /**
52
+ * Creates a new PGlite key–value store.
53
+ * @param pg The caller-owned PGlite instance to use.
54
+ * @param options The options for the key–value store.
55
+ */
56
+ constructor(pg: PGliteInterface, options?: PgliteKvStoreOptions);
57
+ get<T = unknown>(key: KvKey): Promise<T | undefined>;
58
+ set(key: KvKey, value: unknown, options?: KvStoreSetOptions): Promise<void>;
59
+ delete(key: KvKey): Promise<void>;
60
+ /** {@inheritDoc KvStore.cas} */
61
+ cas(key: KvKey, expectedValue: unknown, newValue: unknown, options?: KvStoreSetOptions): Promise<boolean>;
62
+ /** {@inheritDoc KvStore.list} */
63
+ list(prefix?: KvKey): AsyncIterable<KvStoreListEntry>;
64
+ /**
65
+ * Creates the table used by the store if it does not already exist.
66
+ */
67
+ initialize(): Promise<void>;
68
+ /**
69
+ * Drops the table used by the store. Does nothing if it does not exist.
70
+ */
71
+ drop(): Promise<void>;
72
+ }
73
+ //#endregion
74
+ export { PgliteKvStore, PgliteKvStoreOptions };
package/dist/kv.d.ts ADDED
@@ -0,0 +1,74 @@
1
+ /// <reference types="emscripten" />
2
+ /// <reference lib="esnext.temporal" />
3
+ import { PGliteInterface } from "@electric-sql/pglite";
4
+ import { KvKey, KvStore, KvStoreListEntry, KvStoreSetOptions } from "@fedify/fedify";
5
+
6
+ //#region src/kv.d.ts
7
+ /**
8
+ * Options for the PGlite key–value store.
9
+ */
10
+ interface PgliteKvStoreOptions {
11
+ /**
12
+ * The table name to use for the key–value store.
13
+ * A `.` separates the schema from the table name, so neither the whole
14
+ * name nor any segment between dots can be empty.
15
+ * `"fedify_kv_v2"` by default.
16
+ * @default `"fedify_kv_v2"`
17
+ */
18
+ readonly tableName?: string;
19
+ /**
20
+ * Whether the table has already been initialized. `false` by default.
21
+ * @default `false`
22
+ */
23
+ readonly initialized?: boolean;
24
+ }
25
+ /**
26
+ * A key–value store backed by an embedded PGlite database.
27
+ *
28
+ * The caller owns the PGlite instance and is responsible for closing it after
29
+ * every operation using the store has completed. Use only one PGlite instance
30
+ * for a data directory within a runtime isolate.
31
+ *
32
+ * @example
33
+ * ```ts ignore
34
+ * import { PGlite } from "@electric-sql/pglite";
35
+ * import { createFederation } from "@fedify/fedify";
36
+ * import { PgliteKvStore } from "@fedify/pglite";
37
+ *
38
+ * const pg = new PGlite("./data/fedify");
39
+ * const federation = createFederation({
40
+ * // ...
41
+ * kv: new PgliteKvStore(pg),
42
+ * });
43
+ *
44
+ * // Close pg during application shutdown, after pending operations finish.
45
+ * await pg.close();
46
+ * ```
47
+ * @since 2.4.0
48
+ */
49
+ declare class PgliteKvStore implements KvStore {
50
+ #private;
51
+ /**
52
+ * Creates a new PGlite key–value store.
53
+ * @param pg The caller-owned PGlite instance to use.
54
+ * @param options The options for the key–value store.
55
+ */
56
+ constructor(pg: PGliteInterface, options?: PgliteKvStoreOptions);
57
+ get<T = unknown>(key: KvKey): Promise<T | undefined>;
58
+ set(key: KvKey, value: unknown, options?: KvStoreSetOptions): Promise<void>;
59
+ delete(key: KvKey): Promise<void>;
60
+ /** {@inheritDoc KvStore.cas} */
61
+ cas(key: KvKey, expectedValue: unknown, newValue: unknown, options?: KvStoreSetOptions): Promise<boolean>;
62
+ /** {@inheritDoc KvStore.list} */
63
+ list(prefix?: KvKey): AsyncIterable<KvStoreListEntry>;
64
+ /**
65
+ * Creates the table used by the store if it does not already exist.
66
+ */
67
+ initialize(): Promise<void>;
68
+ /**
69
+ * Drops the table used by the store. Does nothing if it does not exist.
70
+ */
71
+ drop(): Promise<void>;
72
+ }
73
+ //#endregion
74
+ export { PgliteKvStore, PgliteKvStoreOptions };
package/dist/kv.js ADDED
@@ -0,0 +1,214 @@
1
+ import { Temporal } from "temporal-polyfill";
2
+ import { getLogger } from "@logtape/logtape";
3
+ //#region src/kv.ts
4
+ const logger = getLogger([
5
+ "fedify",
6
+ "pglite",
7
+ "kv"
8
+ ]);
9
+ function quoteIdentifier(identifier) {
10
+ const parts = identifier.split(".");
11
+ if (parts.includes("")) throw new TypeError(`Invalid table name for the key–value store: ${JSON.stringify(identifier)}`);
12
+ return parts.map((part) => `"${part.replaceAll("\"", "\"\"")}"`).join(".");
13
+ }
14
+ function serializeJson(value) {
15
+ const serialized = JSON.stringify(value);
16
+ if (serialized == null) throw new TypeError("PGlite cannot store a value that is not valid JSON");
17
+ return serialized;
18
+ }
19
+ /**
20
+ * A key–value store backed by an embedded PGlite database.
21
+ *
22
+ * The caller owns the PGlite instance and is responsible for closing it after
23
+ * every operation using the store has completed. Use only one PGlite instance
24
+ * for a data directory within a runtime isolate.
25
+ *
26
+ * @example
27
+ * ```ts ignore
28
+ * import { PGlite } from "@electric-sql/pglite";
29
+ * import { createFederation } from "@fedify/fedify";
30
+ * import { PgliteKvStore } from "@fedify/pglite";
31
+ *
32
+ * const pg = new PGlite("./data/fedify");
33
+ * const federation = createFederation({
34
+ * // ...
35
+ * kv: new PgliteKvStore(pg),
36
+ * });
37
+ *
38
+ * // Close pg during application shutdown, after pending operations finish.
39
+ * await pg.close();
40
+ * ```
41
+ * @since 2.4.0
42
+ */
43
+ var PgliteKvStore = class {
44
+ #pg;
45
+ #tableName;
46
+ #quotedTableName;
47
+ #initialized;
48
+ #initializing;
49
+ /**
50
+ * Creates a new PGlite key–value store.
51
+ * @param pg The caller-owned PGlite instance to use.
52
+ * @param options The options for the key–value store.
53
+ */
54
+ constructor(pg, options = {}) {
55
+ this.#pg = pg;
56
+ this.#tableName = options.tableName ?? "fedify_kv_v2";
57
+ this.#quotedTableName = quoteIdentifier(this.#tableName);
58
+ this.#initialized = options.initialized ?? false;
59
+ }
60
+ async #expire() {
61
+ await this.#pg.query(`
62
+ DELETE FROM ${this.#quotedTableName}
63
+ WHERE ttl IS NOT NULL AND created + ttl < CURRENT_TIMESTAMP;
64
+ `);
65
+ }
66
+ async get(key) {
67
+ await this.initialize();
68
+ return (await this.#pg.query(`
69
+ SELECT value
70
+ FROM ${this.#quotedTableName}
71
+ WHERE key = $1::text[]
72
+ AND (ttl IS NULL OR created + ttl > CURRENT_TIMESTAMP);
73
+ `, [key])).rows[0]?.value;
74
+ }
75
+ async set(key, value, options) {
76
+ await this.initialize();
77
+ const ttl = options?.ttl == null ? null : Temporal.Duration.from(options.ttl).toString();
78
+ await this.#pg.query(`
79
+ INSERT INTO ${this.#quotedTableName} (key, value, ttl)
80
+ VALUES ($1::text[], $2::text::jsonb, $3::text::interval)
81
+ ON CONFLICT (key)
82
+ DO UPDATE SET
83
+ value = EXCLUDED.value,
84
+ created = CURRENT_TIMESTAMP,
85
+ ttl = EXCLUDED.ttl;
86
+ `, [
87
+ key,
88
+ serializeJson(value),
89
+ ttl
90
+ ]);
91
+ await this.#expire();
92
+ }
93
+ async delete(key) {
94
+ await this.initialize();
95
+ await this.#pg.query(`
96
+ DELETE FROM ${this.#quotedTableName}
97
+ WHERE key = $1::text[];
98
+ `, [key]);
99
+ await this.#expire();
100
+ }
101
+ /** {@inheritDoc KvStore.cas} */
102
+ async cas(key, expectedValue, newValue, options) {
103
+ await this.initialize();
104
+ const ttl = options?.ttl == null ? null : Temporal.Duration.from(options.ttl).toString();
105
+ if (expectedValue === void 0 && newValue === void 0) {
106
+ const matched = await this.get(key) === void 0;
107
+ await this.#expire();
108
+ return matched;
109
+ }
110
+ let rowCount;
111
+ if (expectedValue === void 0) rowCount = (await this.#pg.query(`
112
+ INSERT INTO ${this.#quotedTableName} AS existing
113
+ (key, value, created, ttl)
114
+ VALUES (
115
+ $1::text[],
116
+ $2::text::jsonb,
117
+ CURRENT_TIMESTAMP,
118
+ $3::text::interval
119
+ )
120
+ ON CONFLICT (key)
121
+ DO UPDATE SET
122
+ value = EXCLUDED.value,
123
+ created = EXCLUDED.created,
124
+ ttl = EXCLUDED.ttl
125
+ WHERE existing.ttl IS NOT NULL
126
+ AND existing.created + existing.ttl <= CURRENT_TIMESTAMP
127
+ RETURNING key;
128
+ `, [
129
+ key,
130
+ serializeJson(newValue),
131
+ ttl
132
+ ])).rows.length;
133
+ else if (newValue === void 0) rowCount = (await this.#pg.query(`
134
+ DELETE FROM ${this.#quotedTableName}
135
+ WHERE key = $1::text[]
136
+ AND (ttl IS NULL OR created + ttl > CURRENT_TIMESTAMP)
137
+ AND value = $2::text::jsonb
138
+ RETURNING key;
139
+ `, [key, serializeJson(expectedValue)])).rows.length;
140
+ else rowCount = (await this.#pg.query(`
141
+ UPDATE ${this.#quotedTableName}
142
+ SET
143
+ value = $3::text::jsonb,
144
+ created = CURRENT_TIMESTAMP,
145
+ ttl = $4::text::interval
146
+ WHERE key = $1::text[]
147
+ AND (ttl IS NULL OR created + ttl > CURRENT_TIMESTAMP)
148
+ AND value = $2::text::jsonb
149
+ RETURNING key;
150
+ `, [
151
+ key,
152
+ serializeJson(expectedValue),
153
+ serializeJson(newValue),
154
+ ttl
155
+ ])).rows.length;
156
+ await this.#expire();
157
+ return rowCount > 0;
158
+ }
159
+ /** {@inheritDoc KvStore.list} */
160
+ async *list(prefix) {
161
+ await this.initialize();
162
+ const result = prefix == null || prefix.length === 0 ? await this.#pg.query(`
163
+ SELECT key, value
164
+ FROM ${this.#quotedTableName}
165
+ WHERE ttl IS NULL OR created + ttl > CURRENT_TIMESTAMP
166
+ ORDER BY key;
167
+ `) : await this.#pg.query(`
168
+ SELECT key, value
169
+ FROM ${this.#quotedTableName}
170
+ WHERE array_length(key, 1) >= $1
171
+ AND key[1:$1] = $2::text[]
172
+ AND (ttl IS NULL OR created + ttl > CURRENT_TIMESTAMP)
173
+ ORDER BY key;
174
+ `, [prefix.length, prefix]);
175
+ for (const row of result.rows) yield row;
176
+ }
177
+ /**
178
+ * Creates the table used by the store if it does not already exist.
179
+ */
180
+ async initialize() {
181
+ await this.#pg.waitReady;
182
+ if (this.#initialized) return;
183
+ this.#initializing ??= (async () => {
184
+ logger.debug("Initializing the key–value store table {tableName}...", { tableName: this.#tableName });
185
+ await this.#pg.query(`
186
+ CREATE TABLE IF NOT EXISTS ${this.#quotedTableName} (
187
+ key text[] PRIMARY KEY,
188
+ value jsonb NOT NULL,
189
+ created timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
190
+ ttl interval
191
+ );
192
+ `);
193
+ this.#initialized = true;
194
+ logger.debug("Initialized the key–value store table {tableName}.", { tableName: this.#tableName });
195
+ })();
196
+ try {
197
+ await this.#initializing;
198
+ } catch (error) {
199
+ this.#initializing = void 0;
200
+ throw error;
201
+ }
202
+ }
203
+ /**
204
+ * Drops the table used by the store. Does nothing if it does not exist.
205
+ */
206
+ async drop() {
207
+ await this.#pg.waitReady;
208
+ await this.#pg.query(`DROP TABLE IF EXISTS ${this.#quotedTableName};`);
209
+ this.#initialized = false;
210
+ this.#initializing = void 0;
211
+ }
212
+ };
213
+ //#endregion
214
+ export { PgliteKvStore };
package/dist/mod.cjs ADDED
@@ -0,0 +1,4 @@
1
+
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const require_kv = require("./kv.cjs");
4
+ exports.PgliteKvStore = require_kv.PgliteKvStore;
package/dist/mod.d.cts ADDED
@@ -0,0 +1,4 @@
1
+ /// <reference types="emscripten" />
2
+ /// <reference lib="esnext.temporal" />
3
+ import { PgliteKvStore, PgliteKvStoreOptions } from "./kv.cjs";
4
+ export { PgliteKvStore, PgliteKvStoreOptions };
package/dist/mod.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ /// <reference types="emscripten" />
2
+ /// <reference lib="esnext.temporal" />
3
+ import { PgliteKvStore, PgliteKvStoreOptions } from "./kv.js";
4
+ export { PgliteKvStore, PgliteKvStoreOptions };
package/dist/mod.js ADDED
@@ -0,0 +1,3 @@
1
+ import { Temporal } from "temporal-polyfill";
2
+ import { PgliteKvStore } from "./kv.js";
3
+ export { PgliteKvStore };