@keyv/postgres 2.2.1 → 2.2.3

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/index.cjs CHANGED
@@ -56,6 +56,9 @@ var endPool = async () => {
56
56
  };
57
57
 
58
58
  // src/index.ts
59
+ function escapeIdentifier(identifier) {
60
+ return `"${identifier.replace(/"/g, '""')}"`;
61
+ }
59
62
  var KeyvPostgres = class extends import_node_events.default {
60
63
  ttlSupport;
61
64
  opts;
@@ -120,6 +123,19 @@ var KeyvPostgres = class extends import_node_events.default {
120
123
  DO UPDATE SET value=excluded.value;`;
121
124
  await this.query(upsert, [key, value]);
122
125
  }
126
+ async setMany(entries) {
127
+ const keys = [];
128
+ const values = [];
129
+ for (const { key, value } of entries) {
130
+ keys.push(key);
131
+ values.push(value);
132
+ }
133
+ const upsert = `INSERT INTO ${this.opts.schema}.${this.opts.table} (key, value)
134
+ SELECT * FROM UNNEST($1::text[], $2::text[])
135
+ ON CONFLICT(key)
136
+ DO UPDATE SET value=excluded.value;`;
137
+ await this.query(upsert, [keys, values]);
138
+ }
123
139
  async delete(key) {
124
140
  const select = `SELECT * FROM ${this.opts.schema}.${this.opts.table} WHERE key = $1`;
125
141
  const del = `DELETE FROM ${this.opts.schema}.${this.opts.table} WHERE key = $1`;
@@ -146,24 +162,44 @@ var KeyvPostgres = class extends import_node_events.default {
146
162
  }
147
163
  async *iterator(namespace) {
148
164
  const limit = Number.parseInt(String(this.opts.iterationLimit), 10) || 10;
149
- async function* iterate(offset, options, query) {
150
- const select = `SELECT * FROM ${options.schema}.${options.table} WHERE key LIKE $1 LIMIT $2 OFFSET $3`;
151
- const entries = await query(select, [
152
- // biome-ignore lint/style/useTemplate: need to fix
153
- `${namespace ? namespace + ":" : ""}%`,
154
- limit,
155
- offset
156
- ]);
165
+ const escapedNamespace = namespace ? `${namespace.replace(/[%_\\]/g, "\\$&")}:` : "";
166
+ const pattern = `${escapedNamespace}%`;
167
+ let lastKey = null;
168
+ while (true) {
169
+ let entries;
170
+ try {
171
+ let select;
172
+ let params;
173
+ if (lastKey === null) {
174
+ select = `SELECT * FROM ${escapeIdentifier(this.opts.schema)}.${escapeIdentifier(this.opts.table)} WHERE key LIKE $1 ORDER BY key LIMIT $2`;
175
+ params = [pattern, limit];
176
+ } else {
177
+ select = `SELECT * FROM ${escapeIdentifier(this.opts.schema)}.${escapeIdentifier(this.opts.table)} WHERE key LIKE $1 AND key > $2 ORDER BY key LIMIT $3`;
178
+ params = [pattern, lastKey, limit];
179
+ }
180
+ entries = await this.query(select, params);
181
+ } catch (error) {
182
+ this.emit(
183
+ "error",
184
+ new Error(
185
+ `Iterator failed at cursor ${lastKey ?? "start"}: ${error.message}`
186
+ )
187
+ );
188
+ return;
189
+ }
157
190
  if (entries.length === 0) {
158
191
  return;
159
192
  }
160
193
  for (const entry of entries) {
161
- offset += 1;
162
- yield [entry.key, entry.value];
194
+ if (entry.key !== void 0 && entry.key !== null) {
195
+ yield [entry.key, entry.value];
196
+ }
197
+ }
198
+ lastKey = entries[entries.length - 1].key;
199
+ if (entries.length < limit) {
200
+ return;
163
201
  }
164
- yield* iterate(offset, options, query);
165
202
  }
166
- yield* iterate(0, this.opts, this.query);
167
203
  }
168
204
  async has(key) {
169
205
  const exists = `SELECT EXISTS ( SELECT * FROM ${this.opts.schema}.${this.opts.table} WHERE key = $1 )`;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import EventEmitter from 'node:events';
2
- import Keyv, { KeyvStoreAdapter } from 'keyv';
2
+ import Keyv, { KeyvStoreAdapter, KeyvEntry } from 'keyv';
3
3
  import { PoolConfig } from 'pg';
4
4
 
5
5
  type KeyvPostgresOptions = {
@@ -23,10 +23,11 @@ declare class KeyvPostgres extends EventEmitter implements KeyvStoreAdapter {
23
23
  get(key: string): Promise<any>;
24
24
  getMany(keys: string[]): Promise<any[]>;
25
25
  set(key: string, value: any): Promise<void>;
26
+ setMany(entries: KeyvEntry[]): Promise<void>;
26
27
  delete(key: string): Promise<boolean>;
27
28
  deleteMany(keys: string[]): Promise<boolean>;
28
29
  clear(): Promise<void>;
29
- iterator(namespace?: string): AsyncGenerator<any, void, any>;
30
+ iterator(namespace?: string): AsyncGenerator<string[], void, unknown>;
30
31
  has(key: string): Promise<any>;
31
32
  connect(): Promise<(sql: string, values?: any) => Promise<any[]>>;
32
33
  disconnect(): Promise<void>;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import EventEmitter from 'node:events';
2
- import Keyv, { KeyvStoreAdapter } from 'keyv';
2
+ import Keyv, { KeyvStoreAdapter, KeyvEntry } from 'keyv';
3
3
  import { PoolConfig } from 'pg';
4
4
 
5
5
  type KeyvPostgresOptions = {
@@ -23,10 +23,11 @@ declare class KeyvPostgres extends EventEmitter implements KeyvStoreAdapter {
23
23
  get(key: string): Promise<any>;
24
24
  getMany(keys: string[]): Promise<any[]>;
25
25
  set(key: string, value: any): Promise<void>;
26
+ setMany(entries: KeyvEntry[]): Promise<void>;
26
27
  delete(key: string): Promise<boolean>;
27
28
  deleteMany(keys: string[]): Promise<boolean>;
28
29
  clear(): Promise<void>;
29
- iterator(namespace?: string): AsyncGenerator<any, void, any>;
30
+ iterator(namespace?: string): AsyncGenerator<string[], void, unknown>;
30
31
  has(key: string): Promise<any>;
31
32
  connect(): Promise<(sql: string, values?: any) => Promise<any[]>>;
32
33
  disconnect(): Promise<void>;
package/dist/index.js CHANGED
@@ -20,6 +20,9 @@ var endPool = async () => {
20
20
  };
21
21
 
22
22
  // src/index.ts
23
+ function escapeIdentifier(identifier) {
24
+ return `"${identifier.replace(/"/g, '""')}"`;
25
+ }
23
26
  var KeyvPostgres = class extends EventEmitter {
24
27
  ttlSupport;
25
28
  opts;
@@ -84,6 +87,19 @@ var KeyvPostgres = class extends EventEmitter {
84
87
  DO UPDATE SET value=excluded.value;`;
85
88
  await this.query(upsert, [key, value]);
86
89
  }
90
+ async setMany(entries) {
91
+ const keys = [];
92
+ const values = [];
93
+ for (const { key, value } of entries) {
94
+ keys.push(key);
95
+ values.push(value);
96
+ }
97
+ const upsert = `INSERT INTO ${this.opts.schema}.${this.opts.table} (key, value)
98
+ SELECT * FROM UNNEST($1::text[], $2::text[])
99
+ ON CONFLICT(key)
100
+ DO UPDATE SET value=excluded.value;`;
101
+ await this.query(upsert, [keys, values]);
102
+ }
87
103
  async delete(key) {
88
104
  const select = `SELECT * FROM ${this.opts.schema}.${this.opts.table} WHERE key = $1`;
89
105
  const del = `DELETE FROM ${this.opts.schema}.${this.opts.table} WHERE key = $1`;
@@ -110,24 +126,44 @@ var KeyvPostgres = class extends EventEmitter {
110
126
  }
111
127
  async *iterator(namespace) {
112
128
  const limit = Number.parseInt(String(this.opts.iterationLimit), 10) || 10;
113
- async function* iterate(offset, options, query) {
114
- const select = `SELECT * FROM ${options.schema}.${options.table} WHERE key LIKE $1 LIMIT $2 OFFSET $3`;
115
- const entries = await query(select, [
116
- // biome-ignore lint/style/useTemplate: need to fix
117
- `${namespace ? namespace + ":" : ""}%`,
118
- limit,
119
- offset
120
- ]);
129
+ const escapedNamespace = namespace ? `${namespace.replace(/[%_\\]/g, "\\$&")}:` : "";
130
+ const pattern = `${escapedNamespace}%`;
131
+ let lastKey = null;
132
+ while (true) {
133
+ let entries;
134
+ try {
135
+ let select;
136
+ let params;
137
+ if (lastKey === null) {
138
+ select = `SELECT * FROM ${escapeIdentifier(this.opts.schema)}.${escapeIdentifier(this.opts.table)} WHERE key LIKE $1 ORDER BY key LIMIT $2`;
139
+ params = [pattern, limit];
140
+ } else {
141
+ select = `SELECT * FROM ${escapeIdentifier(this.opts.schema)}.${escapeIdentifier(this.opts.table)} WHERE key LIKE $1 AND key > $2 ORDER BY key LIMIT $3`;
142
+ params = [pattern, lastKey, limit];
143
+ }
144
+ entries = await this.query(select, params);
145
+ } catch (error) {
146
+ this.emit(
147
+ "error",
148
+ new Error(
149
+ `Iterator failed at cursor ${lastKey ?? "start"}: ${error.message}`
150
+ )
151
+ );
152
+ return;
153
+ }
121
154
  if (entries.length === 0) {
122
155
  return;
123
156
  }
124
157
  for (const entry of entries) {
125
- offset += 1;
126
- yield [entry.key, entry.value];
158
+ if (entry.key !== void 0 && entry.key !== null) {
159
+ yield [entry.key, entry.value];
160
+ }
161
+ }
162
+ lastKey = entries[entries.length - 1].key;
163
+ if (entries.length < limit) {
164
+ return;
127
165
  }
128
- yield* iterate(offset, options, query);
129
166
  }
130
- yield* iterate(0, this.opts, this.query);
131
167
  }
132
168
  async has(key) {
133
169
  const exists = `SELECT EXISTS ( SELECT * FROM ${this.opts.schema}.${this.opts.table} WHERE key = $1 )`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keyv/postgres",
3
- "version": "2.2.1",
3
+ "version": "2.2.3",
4
4
  "description": "PostgreSQL storage adapter for Keyv",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -42,18 +42,19 @@
42
42
  },
43
43
  "homepage": "https://github.com/jaredwray/keyv",
44
44
  "dependencies": {
45
- "pg": "^8.16.3"
45
+ "pg": "^8.17.1"
46
46
  },
47
47
  "peerDependencies": {
48
- "keyv": "^5.5.4"
48
+ "keyv": "^5.6.0"
49
49
  },
50
50
  "devDependencies": {
51
- "@biomejs/biome": "^2.3.6",
52
- "@types/pg": "^8.15.6",
53
- "@vitest/coverage-v8": "^4.0.10",
54
- "rimraf": "^6.1.0",
51
+ "@biomejs/biome": "^2.3.11",
52
+ "@faker-js/faker": "^10.2.0",
53
+ "@types/pg": "^8.16.0",
54
+ "@vitest/coverage-v8": "^4.0.17",
55
+ "rimraf": "^6.1.2",
55
56
  "tsd": "^0.33.0",
56
- "vitest": "^4.0.10",
57
+ "vitest": "^4.0.17",
57
58
  "@keyv/test-suite": "^2.1.2"
58
59
  },
59
60
  "tsd": {