@pluv/persistence-cloudflare-transactional-storage 3.2.2 → 4.0.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.
@@ -1,18 +1,16 @@
1
1
 
2
- > @pluv/persistence-cloudflare-transactional-storage@3.2.2 build /home/runner/work/pluv/pluv/packages/persistence-cloudflare-transactional-storage
3
- > tsup src/index.ts --format esm,cjs --dts
2
+ > @pluv/persistence-cloudflare-transactional-storage@4.0.0 build /home/runner/work/pluv/pluv/packages/persistence-cloudflare-transactional-storage
3
+ > tsdown
4
4
 
5
- CLI Building entry: src/index.ts
6
- CLI Using tsconfig: tsconfig.json
7
- CLI tsup v8.5.0
8
- CLI Target: es6
9
- ESM Build start
10
- CJS Build start
11
- ESM dist/index.mjs 11.15 KB
12
- ESM ⚡️ Build success in 96ms
13
- CJS dist/index.js 12.12 KB
14
- CJS ⚡️ Build success in 96ms
15
- DTS Build start
16
- DTS ⚡️ Build success in 3389ms
17
- DTS dist/index.d.mts 1.22 KB
18
- DTS dist/index.d.ts 1.22 KB
5
+ ℹ tsdown v0.20.0-beta.4 powered by rolldown v1.0.0-beta.60
6
+ ℹ config file: /home/runner/work/pluv/pluv/packages/persistence-cloudflare-transactional-storage/tsdown.config.ts
7
+ ℹ entry: src/index.ts
8
+ ℹ target: esnext
9
+ ℹ tsconfig: tsconfig.json
10
+ ℹ Build start
11
+ ℹ dist/index.mjs  8.23 kB │ gzip: 1.93 kB
12
+ ℹ dist/index.mjs.map 17.13 kB │ gzip: 3.93 kB
13
+ ℹ dist/index.d.mts.map  0.73 kB │ gzip: 0.36 kB
14
+ ℹ dist/index.d.mts  1.32 kB │ gzip: 0.46 kB
15
+ ℹ 4 files, total: 27.40 kB
16
+ ✔ Build complete in 2700ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @pluv/persistence-cloudflare-transactional-storage
2
2
 
3
+ ## 4.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - df5c39c: Update all packages to be ESM only.
8
+
9
+ ### Minor Changes
10
+
11
+ - 037074e: Migrated build process from tsup to tsdown.
12
+
13
+ ### Patch Changes
14
+
15
+ - 2eb5def: Bumped dependencies.
16
+ - Updated dependencies [2eb5def]
17
+ - Updated dependencies [df5c39c]
18
+ - Updated dependencies [c142910]
19
+ - Updated dependencies [037074e]
20
+ - Updated dependencies [07e2a1e]
21
+ - @pluv/types@4.0.0
22
+ - @pluv/io@4.0.0
23
+
3
24
  ## 3.2.2
4
25
 
5
26
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -1,28 +1,30 @@
1
- import { AbstractPersistence } from '@pluv/io';
2
- import { JsonObject } from '@pluv/types';
1
+ import { AbstractPersistence } from "@pluv/io";
2
+ import { JsonObject } from "@pluv/types";
3
3
 
4
+ //#region src/PersistenceCloudflareTransactionalStorage.d.ts
4
5
  interface PersistenceCloudflareTransactionalStorageConfig {
5
- mode: "kv" | "sqlite";
6
+ mode: "kv" | "sqlite";
6
7
  }
7
8
  declare class PersistenceCloudflareTransactionalStorage extends AbstractPersistence {
8
- private _mode;
9
- private _state;
10
- private _initialized;
11
- constructor(config: PersistenceCloudflareTransactionalStorageConfig);
12
- addUser(room: string, connectionId: string, user: JsonObject | null): Promise<void>;
13
- deleteStorageState(room: string): Promise<void>;
14
- deleteUser(room: string, connectionId: string): Promise<void>;
15
- deleteUsers(room: string): Promise<void>;
16
- getStorageState(room: string): Promise<string | null>;
17
- getUser(room: string, connectionId: string): Promise<JsonObject | null>;
18
- getUsers(room: string): Promise<Map<[connectionId: string][0], JsonObject | null>>;
19
- getUsersSize(room: string): Promise<number>;
20
- initialize(roomContext: {
21
- state: DurableObjectState;
22
- }): typeof this;
23
- setStorageState(room: string, state: string): Promise<void>;
24
- private _getStorageKey;
25
- private _getUserKey;
9
+ private _mode;
10
+ private _state;
11
+ private _initialized;
12
+ constructor(config: PersistenceCloudflareTransactionalStorageConfig);
13
+ addUser(room: string, connectionId: string, user: JsonObject | null): Promise<void>;
14
+ deleteStorageState(room: string): Promise<void>;
15
+ deleteUser(room: string, connectionId: string): Promise<void>;
16
+ deleteUsers(room: string): Promise<void>;
17
+ getStorageState(room: string): Promise<string | null>;
18
+ getUser(room: string, connectionId: string): Promise<JsonObject | null>;
19
+ getUsers(room: string): Promise<Map<[connectionId: string][0], JsonObject | null>>;
20
+ getUsersSize(room: string): Promise<number>;
21
+ initialize(roomContext: {
22
+ state: DurableObjectState;
23
+ }): typeof this;
24
+ setStorageState(room: string, state: string): Promise<void>;
25
+ private _getStorageKey;
26
+ private _getUserKey;
26
27
  }
27
-
28
+ //#endregion
28
29
  export { PersistenceCloudflareTransactionalStorage, type PersistenceCloudflareTransactionalStorageConfig };
30
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/PersistenceCloudflareTransactionalStorage.ts"],"mappings":";;;;UAiBiB,+CAAA;EACb,IAAA;AAAA;AAAA,cAES,yCAAA,SAAkD,mBAAA;EAAA,QACnD,KAAA;EAAA,QACA,MAAA;EAAA,QACA,YAAA;cAEI,MAAA,EAAQ,+CAAA;EASP,OAAA,CACT,IAAA,UACA,YAAA,UACA,IAAA,EAAM,UAAA,UACP,OAAA;EAkCU,kBAAA,CAAmB,IAAA,WAAe,OAAA;EAoBlC,UAAA,CAAW,IAAA,UAAc,YAAA,WAAuB,OAAA;EAuBhD,WAAA,CAAY,IAAA,WAAe,OAAA;EAqC3B,eAAA,CAAgB,IAAA,WAAe,OAAA;EA6B/B,OAAA,CAAQ,IAAA,UAAc,YAAA,WAAuB,OAAA,CAAQ,UAAA;EAmCrD,QAAA,CACT,IAAA,WACD,OAAA,CAAQ,GAAA,EAAK,YAAA,cAA0B,UAAA;EAwC7B,YAAA,CAAa,IAAA,WAAe,OAAA;EAgClC,UAAA,CAAW,WAAA;IAAe,KAAA,EAAO,kBAAA;EAAA;EA8B3B,eAAA,CAAgB,IAAA,UAAc,KAAA,WAAgB,OAAA;EAAA,QA+CnD,cAAA;EAAA,QAQA,WAAA;AAAA"}
package/dist/index.mjs CHANGED
@@ -1,299 +1,192 @@
1
- var __defProp = Object.defineProperty;
2
- var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
- var __hasOwnProp = Object.prototype.hasOwnProperty;
4
- var __propIsEnum = Object.prototype.propertyIsEnumerable;
5
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
- var __spreadValues = (a, b) => {
7
- for (var prop in b || (b = {}))
8
- if (__hasOwnProp.call(b, prop))
9
- __defNormalProp(a, prop, b[prop]);
10
- if (__getOwnPropSymbols)
11
- for (var prop of __getOwnPropSymbols(b)) {
12
- if (__propIsEnum.call(b, prop))
13
- __defNormalProp(a, prop, b[prop]);
14
- }
15
- return a;
16
- };
17
- var __async = (__this, __arguments, generator) => {
18
- return new Promise((resolve, reject) => {
19
- var fulfilled = (value) => {
20
- try {
21
- step(generator.next(value));
22
- } catch (e) {
23
- reject(e);
24
- }
25
- };
26
- var rejected = (value) => {
27
- try {
28
- step(generator.throw(value));
29
- } catch (e) {
30
- reject(e);
31
- }
32
- };
33
- var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
34
- step((generator = generator.apply(__this, __arguments)).next());
35
- });
36
- };
37
-
38
- // src/PersistenceCloudflareTransactionalStorage.ts
39
1
  import { AbstractPersistence } from "@pluv/io";
40
2
 
41
- // src/utils/castNumber.ts
42
- var castNumber = (value) => {
43
- return typeof value === "number" ? value : typeof value === "string" ? Number.parseInt(value, 10) : Number(value);
3
+ //#region src/utils/castNumber.ts
4
+ const castNumber = (value) => {
5
+ return typeof value === "number" ? value : typeof value === "string" ? Number.parseInt(value, 10) : Number(value);
44
6
  };
45
7
 
46
- // src/utils/partitionByLength.ts
47
- var partitionByLength = (arr, length) => {
48
- if (!arr.length) return [];
49
- const head = arr.slice(0, length);
50
- const tail = arr.slice(length);
51
- return [head, ...partitionByLength(tail, length)];
8
+ //#endregion
9
+ //#region src/utils/partitionByLength.ts
10
+ const partitionByLength = (arr, length) => {
11
+ if (!arr.length) return [];
12
+ return [arr.slice(0, length), ...partitionByLength(arr.slice(length), length)];
52
13
  };
53
14
 
54
- // src/utils/sql.ts
55
- var sql = (strings, ...values) => {
56
- return strings.reduce((result, str, i) => {
57
- const cleaned = str.replace(/(\n[ \t]*)+/g, " ");
58
- return result + cleaned + (i < values.length ? values[i] : "");
59
- }, "").trim();
15
+ //#endregion
16
+ //#region src/utils/sql.ts
17
+ const sql = (strings, ...values) => {
18
+ return strings.reduce((result, str, i) => {
19
+ return result + str.replace(/(\n[ \t]*)+/g, " ") + (i < values.length ? values[i] : "");
20
+ }, "").trim();
60
21
  };
61
22
 
62
- // src/PersistenceCloudflareTransactionalStorage.ts
63
- var KV_CLOUDFLARE_DELETE_BATCH_LIMIT = 128;
64
- var KV_STORAGE_PREFIX = "$PLUV_STORAGE;";
65
- var KV_USER_PREFIX = "$PLUV_USER";
66
- var SQLITE_STORAGE_TABLE = "__pluv_storage";
67
- var SQLITE_USER_TABLE = "__pluv_user";
68
- var PersistenceCloudflareTransactionalStorage = class _PersistenceCloudflareTransactionalStorage extends AbstractPersistence {
69
- constructor(config) {
70
- super();
71
- this._state = null;
72
- this._initialized = null;
73
- const { mode } = config;
74
- this._initialized = config._initialized;
75
- this._mode = mode;
76
- }
77
- addUser(room, connectionId, user) {
78
- return __async(this, null, function* () {
79
- var _a, _b;
80
- if (!this._initialized) return;
81
- if (!this._state) return;
82
- yield this._initialized;
83
- if (this._mode === "kv") {
84
- yield this._state.storage.put(this._getUserKey(room, connectionId), user);
85
- return;
86
- }
87
- const result = this._state.storage.sql.exec(
88
- sql`
23
+ //#endregion
24
+ //#region src/PersistenceCloudflareTransactionalStorage.ts
25
+ /**
26
+ * !HACK
27
+ * @description The max amount of keys a Cloudflare transactional storage may delete at once
28
+ * @see https://developers.cloudflare.com/durable-objects/api/transactional-storage-api/#delete
29
+ * @date July 8, 2024
30
+ */
31
+ const KV_CLOUDFLARE_DELETE_BATCH_LIMIT = 128;
32
+ const KV_STORAGE_PREFIX = "$PLUV_STORAGE;";
33
+ const KV_USER_PREFIX = "$PLUV_USER";
34
+ const SQLITE_STORAGE_TABLE = "__pluv_storage";
35
+ const SQLITE_USER_TABLE = "__pluv_user";
36
+ var PersistenceCloudflareTransactionalStorage = class PersistenceCloudflareTransactionalStorage extends AbstractPersistence {
37
+ _mode;
38
+ _state = null;
39
+ _initialized = null;
40
+ constructor(config) {
41
+ super();
42
+ const { mode } = config;
43
+ this._initialized = config._initialized;
44
+ this._mode = mode;
45
+ }
46
+ async addUser(room, connectionId, user) {
47
+ if (!this._initialized) return;
48
+ if (!this._state) return;
49
+ await this._initialized;
50
+ if (this._mode === "kv") {
51
+ await this._state.storage.put(this._getUserKey(room, connectionId), user);
52
+ return;
53
+ }
54
+ if (!!(this._state.storage.sql.exec(sql`
89
55
  SELECT id
90
56
  FROM ${SQLITE_USER_TABLE}
91
57
  WHERE id = ?;
92
- `,
93
- connectionId
94
- );
95
- const existing = !!((_b = (_a = result.toArray()[0]) == null ? void 0 : _a.count) != null ? _b : 0);
96
- if (existing) return;
97
- this._state.storage.sql.exec(
98
- sql`
58
+ `, connectionId).toArray()[0]?.count ?? 0)) return;
59
+ this._state.storage.sql.exec(sql`
99
60
  INSERT INTO ${SQLITE_USER_TABLE} VALUES (?, ?, ?);
100
- `,
101
- connectionId,
102
- JSON.stringify(user),
103
- room
104
- );
105
- });
106
- }
107
- deleteStorageState(room) {
108
- return __async(this, null, function* () {
109
- if (!this._initialized) return;
110
- if (!this._state) return;
111
- yield this._initialized;
112
- if (this._mode === "kv") {
113
- yield this._state.storage.delete(this._getStorageKey(room));
114
- return;
115
- }
116
- this._state.storage.sql.exec(
117
- sql`
61
+ `, connectionId, JSON.stringify(user), room);
62
+ }
63
+ async deleteStorageState(room) {
64
+ if (!this._initialized) return;
65
+ if (!this._state) return;
66
+ await this._initialized;
67
+ if (this._mode === "kv") {
68
+ await this._state.storage.delete(this._getStorageKey(room));
69
+ return;
70
+ }
71
+ this._state.storage.sql.exec(sql`
118
72
  DELETE FROM ${SQLITE_STORAGE_TABLE}
119
73
  WHERE room = ?;
120
- `,
121
- room
122
- );
123
- });
124
- }
125
- deleteUser(room, connectionId) {
126
- return __async(this, null, function* () {
127
- if (!this._initialized) return;
128
- if (!this._state) return;
129
- yield this._initialized;
130
- if (this._mode === "kv") {
131
- yield this._state.storage.delete(this._getUserKey(room, connectionId));
132
- return;
133
- }
134
- this._state.storage.sql.exec(
135
- sql`
74
+ `, room);
75
+ }
76
+ async deleteUser(room, connectionId) {
77
+ if (!this._initialized) return;
78
+ if (!this._state) return;
79
+ await this._initialized;
80
+ if (this._mode === "kv") {
81
+ await this._state.storage.delete(this._getUserKey(room, connectionId));
82
+ return;
83
+ }
84
+ this._state.storage.sql.exec(sql`
136
85
  DELETE FROM ${SQLITE_USER_TABLE}
137
86
  WHERE room = ?
138
87
  AND id = ?;
139
- `,
140
- room,
141
- connectionId
142
- );
143
- });
144
- }
145
- deleteUsers(room) {
146
- return __async(this, null, function* () {
147
- if (!this._initialized) return;
148
- if (!this._state) return;
149
- yield this._initialized;
150
- if (this._mode === "kv") {
151
- const map = yield this._state.storage.list({
152
- allowConcurrency: true,
153
- noCache: true,
154
- prefix: `${KV_USER_PREFIX}::${room}`
155
- });
156
- const partitions = partitionByLength(
157
- Array.from(map.keys()),
158
- KV_CLOUDFLARE_DELETE_BATCH_LIMIT
159
- );
160
- yield this._state.storage.transaction((tx) => __async(this, null, function* () {
161
- yield partitions.reduce((promise, partition) => __async(this, null, function* () {
162
- yield promise;
163
- yield tx.delete(partition);
164
- }), Promise.resolve());
165
- }));
166
- return;
167
- }
168
- this._state.storage.sql.exec(
169
- sql`
88
+ `, room, connectionId);
89
+ }
90
+ async deleteUsers(room) {
91
+ if (!this._initialized) return;
92
+ if (!this._state) return;
93
+ await this._initialized;
94
+ if (this._mode === "kv") {
95
+ const map = await this._state.storage.list({
96
+ allowConcurrency: true,
97
+ noCache: true,
98
+ prefix: `${KV_USER_PREFIX}::${room}`
99
+ });
100
+ const partitions = partitionByLength(Array.from(map.keys()), KV_CLOUDFLARE_DELETE_BATCH_LIMIT);
101
+ await this._state.storage.transaction(async (tx) => {
102
+ await partitions.reduce(async (promise, partition) => {
103
+ await promise;
104
+ await tx.delete(partition);
105
+ }, Promise.resolve());
106
+ });
107
+ return;
108
+ }
109
+ this._state.storage.sql.exec(sql`
170
110
  DELETE FROM ${SQLITE_USER_TABLE}
171
111
  WHERE room = ?;
172
- `,
173
- room
174
- );
175
- });
176
- }
177
- getStorageState(room) {
178
- return __async(this, null, function* () {
179
- if (!this._initialized) return null;
180
- if (!this._state) return null;
181
- yield this._initialized;
182
- if (this._mode === "kv") {
183
- const storage = yield this._state.storage.get(this._getStorageKey(room));
184
- return storage != null ? storage : null;
185
- }
186
- const cursor = this._state.storage.sql.exec(
187
- sql`
112
+ `, room);
113
+ }
114
+ async getStorageState(room) {
115
+ if (!this._initialized) return null;
116
+ if (!this._state) return null;
117
+ await this._initialized;
118
+ if (this._mode === "kv") return await this._state.storage.get(this._getStorageKey(room)) ?? null;
119
+ const cursor = this._state.storage.sql.exec(sql`
188
120
  SELECT room, data
189
121
  FROM ${SQLITE_STORAGE_TABLE}
190
122
  WHERE room = ?;
191
- `,
192
- room
193
- );
194
- try {
195
- const result = cursor.one();
196
- return result.data;
197
- } catch (e) {
198
- return null;
199
- }
200
- });
201
- }
202
- getUser(room, connectionId) {
203
- return __async(this, null, function* () {
204
- if (!this._initialized) return null;
205
- if (!this._state) return null;
206
- yield this._initialized;
207
- if (this._mode === "kv") {
208
- const user = yield this._state.storage.get(
209
- this._getUserKey(room, connectionId)
210
- );
211
- return user != null ? user : null;
212
- }
213
- const cursor = this._state.storage.sql.exec(
214
- sql`
123
+ `, room);
124
+ try {
125
+ return cursor.one().data;
126
+ } catch {
127
+ return null;
128
+ }
129
+ }
130
+ async getUser(room, connectionId) {
131
+ if (!this._initialized) return null;
132
+ if (!this._state) return null;
133
+ await this._initialized;
134
+ if (this._mode === "kv") return await this._state.storage.get(this._getUserKey(room, connectionId)) ?? null;
135
+ const cursor = this._state.storage.sql.exec(sql`
215
136
  SELECT id, data, room
216
137
  FROM ${SQLITE_USER_TABLE}
217
138
  WHERE room = ?
218
139
  AND id = ?;
219
- `,
220
- room,
221
- connectionId
222
- );
223
- try {
224
- const result = cursor.one();
225
- const user = JSON.parse(result.data);
226
- return user;
227
- } catch (e) {
228
- return null;
229
- }
230
- });
231
- }
232
- getUsers(room) {
233
- return __async(this, null, function* () {
234
- if (!this._initialized) return /* @__PURE__ */ new Map();
235
- if (!this._state) return /* @__PURE__ */ new Map();
236
- yield this._initialized;
237
- if (this._mode === "kv") {
238
- const entries = yield this._state.storage.list({
239
- prefix: `${KV_USER_PREFIX}::${room}`
240
- }).then((map) => Array.from(map.entries()));
241
- return entries.reduce((map, [key, user]) => {
242
- const connectionId = key.split("::").slice(-1)[0];
243
- return !!connectionId ? map.set(connectionId, user) : map;
244
- }, /* @__PURE__ */ new Map());
245
- }
246
- const cursor = this._state.storage.sql.exec(
247
- sql`
140
+ `, room, connectionId);
141
+ try {
142
+ const result = cursor.one();
143
+ return JSON.parse(result.data);
144
+ } catch {
145
+ return null;
146
+ }
147
+ }
148
+ async getUsers(room) {
149
+ if (!this._initialized) return /* @__PURE__ */ new Map();
150
+ if (!this._state) return /* @__PURE__ */ new Map();
151
+ await this._initialized;
152
+ if (this._mode === "kv") return (await this._state.storage.list({ prefix: `${KV_USER_PREFIX}::${room}` }).then((map) => Array.from(map.entries()))).reduce((map, [key, user]) => {
153
+ const connectionId = key.split("::").slice(-1)[0];
154
+ return !!connectionId ? map.set(connectionId, user) : map;
155
+ }, /* @__PURE__ */ new Map());
156
+ return this._state.storage.sql.exec(sql`
248
157
  SELECT id, room, data
249
158
  FROM ${SQLITE_USER_TABLE}
250
159
  WHERE room = ?;
251
- `,
252
- room
253
- );
254
- return cursor.toArray().reduce((map, { id, data }) => {
255
- try {
256
- const user = JSON.parse(data);
257
- return map.set(id, user);
258
- } catch (e) {
259
- return map;
260
- }
261
- }, /* @__PURE__ */ new Map());
262
- });
263
- }
264
- getUsersSize(room) {
265
- return __async(this, null, function* () {
266
- if (!this._initialized) return 0;
267
- if (!this._state) return 0;
268
- yield this._initialized;
269
- if (this._mode) {
270
- const storage = yield this._state.storage.list({
271
- prefix: `${KV_USER_PREFIX}::${room}`
272
- });
273
- return storage.size;
274
- }
275
- const cursor = this._state.storage.sql.exec(
276
- sql`
160
+ `, room).toArray().reduce((map, { id, data }) => {
161
+ try {
162
+ const user = JSON.parse(data);
163
+ return map.set(id, user);
164
+ } catch {
165
+ return map;
166
+ }
167
+ }, /* @__PURE__ */ new Map());
168
+ }
169
+ async getUsersSize(room) {
170
+ if (!this._initialized) return 0;
171
+ if (!this._state) return 0;
172
+ await this._initialized;
173
+ if (this._mode) return (await this._state.storage.list({ prefix: `${KV_USER_PREFIX}::${room}` })).size;
174
+ const cursor = this._state.storage.sql.exec(sql`
277
175
  SELECT COUNT(*) count
278
176
  FROM ${SQLITE_USER_TABLE}
279
177
  WHERE room = ?;
280
- `,
281
- room
282
- );
283
- try {
284
- const result = cursor.one();
285
- return castNumber(result.count);
286
- } catch (e) {
287
- return 0;
288
- }
289
- });
290
- }
291
- initialize(roomContext) {
292
- const initialized = (() => __async(this, null, function* () {
293
- const { state } = roomContext;
294
- this._state = state;
295
- if (this._mode === "sqlite") {
296
- this._state.storage.sql.exec(sql`
178
+ `, room);
179
+ try {
180
+ return castNumber(cursor.one().count);
181
+ } catch {
182
+ return 0;
183
+ }
184
+ }
185
+ initialize(roomContext) {
186
+ const initialized = (async () => {
187
+ const { state } = roomContext;
188
+ this._state = state;
189
+ if (this._mode === "sqlite") this._state.storage.sql.exec(sql`
297
190
  CREATE TABLE IF NOT EXISTS ${SQLITE_STORAGE_TABLE}(
298
191
  room TEXT PRIMARY KEY,
299
192
  data TEXT NOT NULL
@@ -305,68 +198,47 @@ var PersistenceCloudflareTransactionalStorage = class _PersistenceCloudflareTran
305
198
  );
306
199
  CREATE INDEX IF NOT EXISTS ${SQLITE_USER_TABLE}_room ON ${SQLITE_USER_TABLE}(room);
307
200
  `);
308
- }
309
- return true;
310
- }))();
311
- return new _PersistenceCloudflareTransactionalStorage(__spreadValues({
312
- mode: this._mode
313
- }, { _initialized: initialized }));
314
- }
315
- setStorageState(room, state) {
316
- return __async(this, null, function* () {
317
- var _a, _b;
318
- if (!this._initialized) return;
319
- if (!this._state) return;
320
- yield this._initialized;
321
- if (this._mode === "kv") {
322
- yield this._state.storage.put(this._getStorageKey(room), state, {
323
- allowConcurrency: true
324
- });
325
- return;
326
- }
327
- const result = this._state.storage.sql.exec(
328
- sql`
201
+ return true;
202
+ })();
203
+ return new PersistenceCloudflareTransactionalStorage({
204
+ mode: this._mode,
205
+ _initialized: initialized
206
+ });
207
+ }
208
+ async setStorageState(room, state) {
209
+ if (!this._initialized) return;
210
+ if (!this._state) return;
211
+ await this._initialized;
212
+ if (this._mode === "kv") {
213
+ await this._state.storage.put(this._getStorageKey(room), state, { allowConcurrency: true });
214
+ return;
215
+ }
216
+ if (!!(this._state.storage.sql.exec(sql`
329
217
  SELECT COUNT(*) as count
330
218
  FROM ${SQLITE_STORAGE_TABLE}
331
219
  WHERE room = ?;
332
- `,
333
- room
334
- );
335
- const existing = !!((_b = (_a = result.toArray()[0]) == null ? void 0 : _a.count) != null ? _b : 0);
336
- if (existing) {
337
- this._state.storage.sql.exec(
338
- sql`
220
+ `, room).toArray()[0]?.count ?? 0)) {
221
+ this._state.storage.sql.exec(sql`
339
222
  UPDATE ${SQLITE_STORAGE_TABLE}
340
223
  SET data = ?
341
224
  WHERE room = ?;
342
- `,
343
- state,
344
- room
345
- );
346
- return;
347
- }
348
- this._state.storage.sql.exec(
349
- sql`
225
+ `, state, room);
226
+ return;
227
+ }
228
+ this._state.storage.sql.exec(sql`
350
229
  INSERT INTO ${SQLITE_STORAGE_TABLE} VALUES (?, ?);
351
- `,
352
- room,
353
- state
354
- );
355
- });
356
- }
357
- _getStorageKey(room) {
358
- if (this._mode === "sqlite") {
359
- throw new Error("Attempted to get kv key for sqlite storage");
360
- }
361
- return `${KV_STORAGE_PREFIX}::${room}`;
362
- }
363
- _getUserKey(room, connectionId) {
364
- if (this._mode === "sqlite") {
365
- throw new Error("Attempted to get kv key for sqlite storage");
366
- }
367
- return `${KV_USER_PREFIX}::${room}::${connectionId}`;
368
- }
369
- };
370
- export {
371
- PersistenceCloudflareTransactionalStorage
230
+ `, room, state);
231
+ }
232
+ _getStorageKey(room) {
233
+ if (this._mode === "sqlite") throw new Error("Attempted to get kv key for sqlite storage");
234
+ return `${KV_STORAGE_PREFIX}::${room}`;
235
+ }
236
+ _getUserKey(room, connectionId) {
237
+ if (this._mode === "sqlite") throw new Error("Attempted to get kv key for sqlite storage");
238
+ return `${KV_USER_PREFIX}::${room}::${connectionId}`;
239
+ }
372
240
  };
241
+
242
+ //#endregion
243
+ export { PersistenceCloudflareTransactionalStorage };
244
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/utils/castNumber.ts","../src/utils/partitionByLength.ts","../src/utils/sql.ts","../src/PersistenceCloudflareTransactionalStorage.ts"],"sourcesContent":["export const castNumber = (value: any): number => {\n return typeof value === \"number\"\n ? value\n : typeof value === \"string\"\n ? Number.parseInt(value, 10)\n : Number(value);\n};\n","export const partitionByLength = <T>(\n arr: readonly T[] | T[],\n length: number,\n): readonly (readonly T[])[] => {\n if (!arr.length) return [];\n\n const head = arr.slice(0, length);\n const tail = arr.slice(length);\n\n return [head, ...partitionByLength(tail, length)];\n};\n","export const sql = (strings: TemplateStringsArray, ...values: any[]): string => {\n return strings\n .reduce((result, str, i) => {\n // Compresses the string to a single line (not including the interpolated values)\n const cleaned = str.replace(/(\\n[ \\t]*)+/g, \" \");\n\n return result + cleaned + (i < values.length ? values[i] : \"\");\n }, \"\")\n .trim();\n};\n","import { AbstractPersistence } from \"@pluv/io\";\nimport type { JsonObject } from \"@pluv/types\";\nimport { castNumber, partitionByLength, sql } from \"./utils\";\n\n/**\n * !HACK\n * @description The max amount of keys a Cloudflare transactional storage may delete at once\n * @see https://developers.cloudflare.com/durable-objects/api/transactional-storage-api/#delete\n * @date July 8, 2024\n */\nconst KV_CLOUDFLARE_DELETE_BATCH_LIMIT = 128;\nconst KV_STORAGE_PREFIX = \"$PLUV_STORAGE;\";\nconst KV_USER_PREFIX = \"$PLUV_USER\";\n\nconst SQLITE_STORAGE_TABLE = \"__pluv_storage\";\nconst SQLITE_USER_TABLE = \"__pluv_user\";\n\nexport interface PersistenceCloudflareTransactionalStorageConfig {\n mode: \"kv\" | \"sqlite\";\n}\nexport class PersistenceCloudflareTransactionalStorage extends AbstractPersistence {\n private _mode: \"kv\" | \"sqlite\";\n private _state: DurableObjectState | null = null;\n private _initialized: Promise<true> | null = null;\n\n constructor(config: PersistenceCloudflareTransactionalStorageConfig) {\n super();\n\n const { mode } = config;\n\n this._initialized = (config as any)._initialized;\n this._mode = mode;\n }\n\n public async addUser(\n room: string,\n connectionId: string,\n user: JsonObject | null,\n ): Promise<void> {\n if (!this._initialized) return;\n if (!this._state) return;\n\n await this._initialized;\n\n if (this._mode === \"kv\") {\n await this._state.storage.put(this._getUserKey(room, connectionId), user);\n return;\n }\n\n const result = this._state.storage.sql.exec<{ count: 0 }>(\n sql`\n SELECT id\n FROM ${SQLITE_USER_TABLE}\n WHERE id = ?;\n `,\n connectionId,\n );\n\n const existing = !!(result.toArray()[0]?.count ?? 0);\n\n if (existing) return;\n\n this._state.storage.sql.exec(\n sql`\n INSERT INTO ${SQLITE_USER_TABLE} VALUES (?, ?, ?);\n `,\n connectionId,\n JSON.stringify(user),\n room,\n );\n }\n\n public async deleteStorageState(room: string): Promise<void> {\n if (!this._initialized) return;\n if (!this._state) return;\n\n await this._initialized;\n\n if (this._mode === \"kv\") {\n await this._state.storage.delete(this._getStorageKey(room));\n return;\n }\n\n this._state.storage.sql.exec(\n sql`\n DELETE FROM ${SQLITE_STORAGE_TABLE}\n WHERE room = ?;\n `,\n room,\n );\n }\n\n public async deleteUser(room: string, connectionId: string): Promise<void> {\n if (!this._initialized) return;\n if (!this._state) return;\n\n await this._initialized;\n\n if (this._mode === \"kv\") {\n await this._state.storage.delete(this._getUserKey(room, connectionId));\n\n return;\n }\n\n this._state.storage.sql.exec(\n sql`\n DELETE FROM ${SQLITE_USER_TABLE}\n WHERE room = ?\n AND id = ?;\n `,\n room,\n connectionId,\n );\n }\n\n public async deleteUsers(room: string): Promise<void> {\n if (!this._initialized) return;\n if (!this._state) return;\n\n await this._initialized;\n\n if (this._mode === \"kv\") {\n const map = await this._state.storage.list({\n allowConcurrency: true,\n noCache: true,\n prefix: `${KV_USER_PREFIX}::${room}`,\n });\n\n const partitions = partitionByLength(\n Array.from(map.keys()),\n KV_CLOUDFLARE_DELETE_BATCH_LIMIT,\n );\n\n await this._state.storage.transaction(async (tx) => {\n await partitions.reduce(async (promise, partition) => {\n await promise;\n await tx.delete(partition as string[]);\n }, Promise.resolve());\n });\n\n return;\n }\n\n this._state.storage.sql.exec(\n sql`\n DELETE FROM ${SQLITE_USER_TABLE}\n WHERE room = ?;\n `,\n room,\n );\n }\n\n public async getStorageState(room: string): Promise<string | null> {\n if (!this._initialized) return null;\n if (!this._state) return null;\n\n await this._initialized;\n\n if (this._mode === \"kv\") {\n const storage = await this._state.storage.get<string>(this._getStorageKey(room));\n return storage ?? null;\n }\n\n const cursor = this._state.storage.sql.exec<{ room: string; data: string }>(\n sql`\n SELECT room, data\n FROM ${SQLITE_STORAGE_TABLE}\n WHERE room = ?;\n `,\n room,\n );\n\n try {\n const result = cursor.one();\n\n return result.data;\n } catch {\n return null;\n }\n }\n\n public async getUser(room: string, connectionId: string): Promise<JsonObject | null> {\n if (!this._initialized) return null;\n if (!this._state) return null;\n\n await this._initialized;\n\n if (this._mode === \"kv\") {\n const user = await this._state.storage.get<JsonObject | null>(\n this._getUserKey(room, connectionId),\n );\n\n return user ?? null;\n }\n\n const cursor = this._state.storage.sql.exec<{ id: string; data: string; room: string }>(\n sql`\n SELECT id, data, room\n FROM ${SQLITE_USER_TABLE}\n WHERE room = ?\n AND id = ?;\n `,\n room,\n connectionId,\n );\n\n try {\n const result = cursor.one();\n const user = JSON.parse(result.data);\n\n return user;\n } catch {\n return null;\n }\n }\n\n public async getUsers(\n room: string,\n ): Promise<Map<[connectionId: string][0], JsonObject | null>> {\n if (!this._initialized) return new Map();\n if (!this._state) return new Map();\n\n await this._initialized;\n\n if (this._mode === \"kv\") {\n const entries = await this._state.storage\n .list<JsonObject | null>({\n prefix: `${KV_USER_PREFIX}::${room}`,\n })\n .then((map) => Array.from(map.entries()));\n\n return entries.reduce((map, [key, user]) => {\n const connectionId = key.split(\"::\").slice(-1)[0];\n\n return !!connectionId ? map.set(connectionId, user) : map;\n }, new Map<string, JsonObject | null>());\n }\n\n const cursor = this._state.storage.sql.exec<{ id: string; data: string; room: string }>(\n sql`\n SELECT id, room, data\n FROM ${SQLITE_USER_TABLE}\n WHERE room = ?;\n `,\n room,\n );\n\n return cursor.toArray().reduce((map, { id, data }) => {\n try {\n const user = JSON.parse(data) as JsonObject;\n\n return map.set(id, user);\n } catch {\n return map;\n }\n }, new Map<string, JsonObject | null>());\n }\n\n public async getUsersSize(room: string): Promise<number> {\n if (!this._initialized) return 0;\n if (!this._state) return 0;\n\n await this._initialized;\n\n if (this._mode) {\n const storage = await this._state.storage.list({\n prefix: `${KV_USER_PREFIX}::${room}`,\n });\n\n return storage.size;\n }\n\n const cursor = this._state.storage.sql.exec<{ count: number }>(\n sql`\n SELECT COUNT(*) count\n FROM ${SQLITE_USER_TABLE}\n WHERE room = ?;\n `,\n room,\n );\n\n try {\n const result = cursor.one();\n\n return castNumber(result.count);\n } catch {\n return 0;\n }\n }\n\n public initialize(roomContext: { state: DurableObjectState }): typeof this {\n const initialized = (async () => {\n const { state } = roomContext;\n\n this._state = state;\n\n if (this._mode === \"sqlite\") {\n this._state.storage.sql.exec(sql`\n CREATE TABLE IF NOT EXISTS ${SQLITE_STORAGE_TABLE}(\n room TEXT PRIMARY KEY,\n data TEXT NOT NULL\n );\n CREATE TABLE IF NOT EXISTS ${SQLITE_USER_TABLE}(\n id TEXT PRIMARY KEY,\n data TEXT NOT NULL,\n room TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS ${SQLITE_USER_TABLE}_room ON ${SQLITE_USER_TABLE}(room);\n `);\n }\n\n return true as const;\n })();\n\n return new PersistenceCloudflareTransactionalStorage({\n mode: this._mode,\n ...{ _initialized: initialized },\n }) as typeof this;\n }\n\n public async setStorageState(room: string, state: string): Promise<void> {\n if (!this._initialized) return;\n if (!this._state) return;\n\n await this._initialized;\n\n if (this._mode === \"kv\") {\n await this._state.storage.put(this._getStorageKey(room), state, {\n allowConcurrency: true,\n });\n return;\n }\n\n const result = this._state.storage.sql.exec<{ count: number }>(\n sql`\n SELECT COUNT(*) as count\n FROM ${SQLITE_STORAGE_TABLE}\n WHERE room = ?;\n `,\n room,\n );\n\n const existing = !!(result.toArray()[0]?.count ?? 0);\n\n if (existing) {\n this._state.storage.sql.exec(\n sql`\n UPDATE ${SQLITE_STORAGE_TABLE}\n SET data = ?\n WHERE room = ?;\n `,\n state,\n room,\n );\n\n return;\n }\n\n this._state.storage.sql.exec(\n sql`\n INSERT INTO ${SQLITE_STORAGE_TABLE} VALUES (?, ?);\n `,\n room,\n state,\n );\n }\n\n private _getStorageKey(room: string): string {\n if (this._mode === \"sqlite\") {\n throw new Error(\"Attempted to get kv key for sqlite storage\");\n }\n\n return `${KV_STORAGE_PREFIX}::${room}`;\n }\n\n private _getUserKey(room: string, connectionId: string): string {\n if (this._mode === \"sqlite\") {\n throw new Error(\"Attempted to get kv key for sqlite storage\");\n }\n\n return `${KV_USER_PREFIX}::${room}::${connectionId}`;\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pluv/persistence-cloudflare-transactional-storage",
3
- "version": "3.2.2",
3
+ "version": "4.0.0",
4
4
  "description": "@pluv/io crdt storage persistence for cloudflare via transactional storage",
5
5
  "author": {
6
6
  "email": "david@pluv.io",
@@ -14,28 +14,32 @@
14
14
  "url": "git+https://github.com/pluv-io/pluv.git",
15
15
  "directory": "packages/persistence-cloudflare-transactional-storage"
16
16
  },
17
- "main": "./dist/index.js",
18
- "module": "./dist/index.mjs",
19
- "types": "./dist/index.d.ts",
17
+ "type": "module",
18
+ "types": "./dist/index.d.mts",
20
19
  "publishConfig": {
21
20
  "access": "public"
22
21
  },
23
22
  "dependencies": {
24
- "@pluv/io": "^3.2.2",
25
- "@pluv/types": "^3.2.2"
23
+ "@pluv/io": "^4.0.0",
24
+ "@pluv/types": "^4.0.0"
26
25
  },
27
26
  "devDependencies": {
28
- "@cloudflare/workers-types": "^4.20250715.0",
29
- "eslint": "^9.31.0",
30
- "tsup": "^8.5.0",
31
- "typescript": "^5.8.3",
32
- "@pluv/tsconfig": "^3.2.2",
33
- "eslint-config-pluv": "^3.2.2"
27
+ "@cloudflare/workers-types": "^4.20260120.0",
28
+ "eslint": "^9.39.2",
29
+ "tsdown": "0.20.0-beta.4",
30
+ "typescript": "^5.9.3",
31
+ "@pluv/tsconfig": "^4.0.0",
32
+ "eslint-config-pluv": "^4.0.0"
33
+ },
34
+ "exports": {
35
+ ".": "./dist/index.mjs",
36
+ "./*": "./*"
34
37
  },
35
38
  "scripts": {
36
- "build": "tsup src/index.ts --format esm,cjs --dts",
37
- "dev": "tsup src/index.ts --format esm,cjs --watch --dts",
39
+ "build": "tsdown",
40
+ "dev": "tsdown --watch",
38
41
  "lint": "eslint \"src/**/*.ts*\" --fix --max-warnings 0",
42
+ "typecheck": "tsc --noEmit",
39
43
  "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
40
44
  }
41
45
  }
@@ -0,0 +1,15 @@
1
+ import { defineConfig } from "tsdown";
2
+
3
+ export default defineConfig({
4
+ format: ["esm"],
5
+ dts: true,
6
+ outDir: "dist",
7
+ entry: "src/index.ts",
8
+ target: "esnext",
9
+ sourcemap: true,
10
+ clean: true,
11
+ external: ["cloudflare:workers"],
12
+ exports: {
13
+ all: true,
14
+ },
15
+ });
package/dist/index.d.ts DELETED
@@ -1,28 +0,0 @@
1
- import { AbstractPersistence } from '@pluv/io';
2
- import { JsonObject } from '@pluv/types';
3
-
4
- interface PersistenceCloudflareTransactionalStorageConfig {
5
- mode: "kv" | "sqlite";
6
- }
7
- declare class PersistenceCloudflareTransactionalStorage extends AbstractPersistence {
8
- private _mode;
9
- private _state;
10
- private _initialized;
11
- constructor(config: PersistenceCloudflareTransactionalStorageConfig);
12
- addUser(room: string, connectionId: string, user: JsonObject | null): Promise<void>;
13
- deleteStorageState(room: string): Promise<void>;
14
- deleteUser(room: string, connectionId: string): Promise<void>;
15
- deleteUsers(room: string): Promise<void>;
16
- getStorageState(room: string): Promise<string | null>;
17
- getUser(room: string, connectionId: string): Promise<JsonObject | null>;
18
- getUsers(room: string): Promise<Map<[connectionId: string][0], JsonObject | null>>;
19
- getUsersSize(room: string): Promise<number>;
20
- initialize(roomContext: {
21
- state: DurableObjectState;
22
- }): typeof this;
23
- setStorageState(room: string, state: string): Promise<void>;
24
- private _getStorageKey;
25
- private _getUserKey;
26
- }
27
-
28
- export { PersistenceCloudflareTransactionalStorage, type PersistenceCloudflareTransactionalStorageConfig };
package/dist/index.js DELETED
@@ -1,396 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getOwnPropSymbols = Object.getOwnPropertySymbols;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __propIsEnum = Object.prototype.propertyIsEnumerable;
8
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
- var __spreadValues = (a, b) => {
10
- for (var prop in b || (b = {}))
11
- if (__hasOwnProp.call(b, prop))
12
- __defNormalProp(a, prop, b[prop]);
13
- if (__getOwnPropSymbols)
14
- for (var prop of __getOwnPropSymbols(b)) {
15
- if (__propIsEnum.call(b, prop))
16
- __defNormalProp(a, prop, b[prop]);
17
- }
18
- return a;
19
- };
20
- var __export = (target, all) => {
21
- for (var name in all)
22
- __defProp(target, name, { get: all[name], enumerable: true });
23
- };
24
- var __copyProps = (to, from, except, desc) => {
25
- if (from && typeof from === "object" || typeof from === "function") {
26
- for (let key of __getOwnPropNames(from))
27
- if (!__hasOwnProp.call(to, key) && key !== except)
28
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
29
- }
30
- return to;
31
- };
32
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
- var __async = (__this, __arguments, generator) => {
34
- return new Promise((resolve, reject) => {
35
- var fulfilled = (value) => {
36
- try {
37
- step(generator.next(value));
38
- } catch (e) {
39
- reject(e);
40
- }
41
- };
42
- var rejected = (value) => {
43
- try {
44
- step(generator.throw(value));
45
- } catch (e) {
46
- reject(e);
47
- }
48
- };
49
- var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
50
- step((generator = generator.apply(__this, __arguments)).next());
51
- });
52
- };
53
-
54
- // src/index.ts
55
- var index_exports = {};
56
- __export(index_exports, {
57
- PersistenceCloudflareTransactionalStorage: () => PersistenceCloudflareTransactionalStorage
58
- });
59
- module.exports = __toCommonJS(index_exports);
60
-
61
- // src/PersistenceCloudflareTransactionalStorage.ts
62
- var import_io = require("@pluv/io");
63
-
64
- // src/utils/castNumber.ts
65
- var castNumber = (value) => {
66
- return typeof value === "number" ? value : typeof value === "string" ? Number.parseInt(value, 10) : Number(value);
67
- };
68
-
69
- // src/utils/partitionByLength.ts
70
- var partitionByLength = (arr, length) => {
71
- if (!arr.length) return [];
72
- const head = arr.slice(0, length);
73
- const tail = arr.slice(length);
74
- return [head, ...partitionByLength(tail, length)];
75
- };
76
-
77
- // src/utils/sql.ts
78
- var sql = (strings, ...values) => {
79
- return strings.reduce((result, str, i) => {
80
- const cleaned = str.replace(/(\n[ \t]*)+/g, " ");
81
- return result + cleaned + (i < values.length ? values[i] : "");
82
- }, "").trim();
83
- };
84
-
85
- // src/PersistenceCloudflareTransactionalStorage.ts
86
- var KV_CLOUDFLARE_DELETE_BATCH_LIMIT = 128;
87
- var KV_STORAGE_PREFIX = "$PLUV_STORAGE;";
88
- var KV_USER_PREFIX = "$PLUV_USER";
89
- var SQLITE_STORAGE_TABLE = "__pluv_storage";
90
- var SQLITE_USER_TABLE = "__pluv_user";
91
- var PersistenceCloudflareTransactionalStorage = class _PersistenceCloudflareTransactionalStorage extends import_io.AbstractPersistence {
92
- constructor(config) {
93
- super();
94
- this._state = null;
95
- this._initialized = null;
96
- const { mode } = config;
97
- this._initialized = config._initialized;
98
- this._mode = mode;
99
- }
100
- addUser(room, connectionId, user) {
101
- return __async(this, null, function* () {
102
- var _a, _b;
103
- if (!this._initialized) return;
104
- if (!this._state) return;
105
- yield this._initialized;
106
- if (this._mode === "kv") {
107
- yield this._state.storage.put(this._getUserKey(room, connectionId), user);
108
- return;
109
- }
110
- const result = this._state.storage.sql.exec(
111
- sql`
112
- SELECT id
113
- FROM ${SQLITE_USER_TABLE}
114
- WHERE id = ?;
115
- `,
116
- connectionId
117
- );
118
- const existing = !!((_b = (_a = result.toArray()[0]) == null ? void 0 : _a.count) != null ? _b : 0);
119
- if (existing) return;
120
- this._state.storage.sql.exec(
121
- sql`
122
- INSERT INTO ${SQLITE_USER_TABLE} VALUES (?, ?, ?);
123
- `,
124
- connectionId,
125
- JSON.stringify(user),
126
- room
127
- );
128
- });
129
- }
130
- deleteStorageState(room) {
131
- return __async(this, null, function* () {
132
- if (!this._initialized) return;
133
- if (!this._state) return;
134
- yield this._initialized;
135
- if (this._mode === "kv") {
136
- yield this._state.storage.delete(this._getStorageKey(room));
137
- return;
138
- }
139
- this._state.storage.sql.exec(
140
- sql`
141
- DELETE FROM ${SQLITE_STORAGE_TABLE}
142
- WHERE room = ?;
143
- `,
144
- room
145
- );
146
- });
147
- }
148
- deleteUser(room, connectionId) {
149
- return __async(this, null, function* () {
150
- if (!this._initialized) return;
151
- if (!this._state) return;
152
- yield this._initialized;
153
- if (this._mode === "kv") {
154
- yield this._state.storage.delete(this._getUserKey(room, connectionId));
155
- return;
156
- }
157
- this._state.storage.sql.exec(
158
- sql`
159
- DELETE FROM ${SQLITE_USER_TABLE}
160
- WHERE room = ?
161
- AND id = ?;
162
- `,
163
- room,
164
- connectionId
165
- );
166
- });
167
- }
168
- deleteUsers(room) {
169
- return __async(this, null, function* () {
170
- if (!this._initialized) return;
171
- if (!this._state) return;
172
- yield this._initialized;
173
- if (this._mode === "kv") {
174
- const map = yield this._state.storage.list({
175
- allowConcurrency: true,
176
- noCache: true,
177
- prefix: `${KV_USER_PREFIX}::${room}`
178
- });
179
- const partitions = partitionByLength(
180
- Array.from(map.keys()),
181
- KV_CLOUDFLARE_DELETE_BATCH_LIMIT
182
- );
183
- yield this._state.storage.transaction((tx) => __async(this, null, function* () {
184
- yield partitions.reduce((promise, partition) => __async(this, null, function* () {
185
- yield promise;
186
- yield tx.delete(partition);
187
- }), Promise.resolve());
188
- }));
189
- return;
190
- }
191
- this._state.storage.sql.exec(
192
- sql`
193
- DELETE FROM ${SQLITE_USER_TABLE}
194
- WHERE room = ?;
195
- `,
196
- room
197
- );
198
- });
199
- }
200
- getStorageState(room) {
201
- return __async(this, null, function* () {
202
- if (!this._initialized) return null;
203
- if (!this._state) return null;
204
- yield this._initialized;
205
- if (this._mode === "kv") {
206
- const storage = yield this._state.storage.get(this._getStorageKey(room));
207
- return storage != null ? storage : null;
208
- }
209
- const cursor = this._state.storage.sql.exec(
210
- sql`
211
- SELECT room, data
212
- FROM ${SQLITE_STORAGE_TABLE}
213
- WHERE room = ?;
214
- `,
215
- room
216
- );
217
- try {
218
- const result = cursor.one();
219
- return result.data;
220
- } catch (e) {
221
- return null;
222
- }
223
- });
224
- }
225
- getUser(room, connectionId) {
226
- return __async(this, null, function* () {
227
- if (!this._initialized) return null;
228
- if (!this._state) return null;
229
- yield this._initialized;
230
- if (this._mode === "kv") {
231
- const user = yield this._state.storage.get(
232
- this._getUserKey(room, connectionId)
233
- );
234
- return user != null ? user : null;
235
- }
236
- const cursor = this._state.storage.sql.exec(
237
- sql`
238
- SELECT id, data, room
239
- FROM ${SQLITE_USER_TABLE}
240
- WHERE room = ?
241
- AND id = ?;
242
- `,
243
- room,
244
- connectionId
245
- );
246
- try {
247
- const result = cursor.one();
248
- const user = JSON.parse(result.data);
249
- return user;
250
- } catch (e) {
251
- return null;
252
- }
253
- });
254
- }
255
- getUsers(room) {
256
- return __async(this, null, function* () {
257
- if (!this._initialized) return /* @__PURE__ */ new Map();
258
- if (!this._state) return /* @__PURE__ */ new Map();
259
- yield this._initialized;
260
- if (this._mode === "kv") {
261
- const entries = yield this._state.storage.list({
262
- prefix: `${KV_USER_PREFIX}::${room}`
263
- }).then((map) => Array.from(map.entries()));
264
- return entries.reduce((map, [key, user]) => {
265
- const connectionId = key.split("::").slice(-1)[0];
266
- return !!connectionId ? map.set(connectionId, user) : map;
267
- }, /* @__PURE__ */ new Map());
268
- }
269
- const cursor = this._state.storage.sql.exec(
270
- sql`
271
- SELECT id, room, data
272
- FROM ${SQLITE_USER_TABLE}
273
- WHERE room = ?;
274
- `,
275
- room
276
- );
277
- return cursor.toArray().reduce((map, { id, data }) => {
278
- try {
279
- const user = JSON.parse(data);
280
- return map.set(id, user);
281
- } catch (e) {
282
- return map;
283
- }
284
- }, /* @__PURE__ */ new Map());
285
- });
286
- }
287
- getUsersSize(room) {
288
- return __async(this, null, function* () {
289
- if (!this._initialized) return 0;
290
- if (!this._state) return 0;
291
- yield this._initialized;
292
- if (this._mode) {
293
- const storage = yield this._state.storage.list({
294
- prefix: `${KV_USER_PREFIX}::${room}`
295
- });
296
- return storage.size;
297
- }
298
- const cursor = this._state.storage.sql.exec(
299
- sql`
300
- SELECT COUNT(*) count
301
- FROM ${SQLITE_USER_TABLE}
302
- WHERE room = ?;
303
- `,
304
- room
305
- );
306
- try {
307
- const result = cursor.one();
308
- return castNumber(result.count);
309
- } catch (e) {
310
- return 0;
311
- }
312
- });
313
- }
314
- initialize(roomContext) {
315
- const initialized = (() => __async(this, null, function* () {
316
- const { state } = roomContext;
317
- this._state = state;
318
- if (this._mode === "sqlite") {
319
- this._state.storage.sql.exec(sql`
320
- CREATE TABLE IF NOT EXISTS ${SQLITE_STORAGE_TABLE}(
321
- room TEXT PRIMARY KEY,
322
- data TEXT NOT NULL
323
- );
324
- CREATE TABLE IF NOT EXISTS ${SQLITE_USER_TABLE}(
325
- id TEXT PRIMARY KEY,
326
- data TEXT NOT NULL,
327
- room TEXT NOT NULL
328
- );
329
- CREATE INDEX IF NOT EXISTS ${SQLITE_USER_TABLE}_room ON ${SQLITE_USER_TABLE}(room);
330
- `);
331
- }
332
- return true;
333
- }))();
334
- return new _PersistenceCloudflareTransactionalStorage(__spreadValues({
335
- mode: this._mode
336
- }, { _initialized: initialized }));
337
- }
338
- setStorageState(room, state) {
339
- return __async(this, null, function* () {
340
- var _a, _b;
341
- if (!this._initialized) return;
342
- if (!this._state) return;
343
- yield this._initialized;
344
- if (this._mode === "kv") {
345
- yield this._state.storage.put(this._getStorageKey(room), state, {
346
- allowConcurrency: true
347
- });
348
- return;
349
- }
350
- const result = this._state.storage.sql.exec(
351
- sql`
352
- SELECT COUNT(*) as count
353
- FROM ${SQLITE_STORAGE_TABLE}
354
- WHERE room = ?;
355
- `,
356
- room
357
- );
358
- const existing = !!((_b = (_a = result.toArray()[0]) == null ? void 0 : _a.count) != null ? _b : 0);
359
- if (existing) {
360
- this._state.storage.sql.exec(
361
- sql`
362
- UPDATE ${SQLITE_STORAGE_TABLE}
363
- SET data = ?
364
- WHERE room = ?;
365
- `,
366
- state,
367
- room
368
- );
369
- return;
370
- }
371
- this._state.storage.sql.exec(
372
- sql`
373
- INSERT INTO ${SQLITE_STORAGE_TABLE} VALUES (?, ?);
374
- `,
375
- room,
376
- state
377
- );
378
- });
379
- }
380
- _getStorageKey(room) {
381
- if (this._mode === "sqlite") {
382
- throw new Error("Attempted to get kv key for sqlite storage");
383
- }
384
- return `${KV_STORAGE_PREFIX}::${room}`;
385
- }
386
- _getUserKey(room, connectionId) {
387
- if (this._mode === "sqlite") {
388
- throw new Error("Attempted to get kv key for sqlite storage");
389
- }
390
- return `${KV_USER_PREFIX}::${room}::${connectionId}`;
391
- }
392
- };
393
- // Annotate the CommonJS export names for ESM import in node:
394
- 0 && (module.exports = {
395
- PersistenceCloudflareTransactionalStorage
396
- });