@hot-updater/cloudflare 1.0.0-rc.2 → 1.0.0-rc.4

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
@@ -16,7 +16,7 @@ var __copyProps = (to, from, except, desc) => {
16
16
  }
17
17
  return to;
18
18
  };
19
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
20
20
  value: mod,
21
21
  enumerable: true
22
22
  }) : target, mod));
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
25
25
  let _hot_updater_plugin_core_internal = require("@hot-updater/plugin-core/internal");
26
26
  let cloudflare = require("cloudflare");
27
- cloudflare = __toESM(cloudflare);
27
+ cloudflare = __toESM(cloudflare, 1);
28
28
  let _aws_sdk_client_s3 = require("@aws-sdk/client-s3");
29
29
  let _aws_sdk_lib_storage = require("@aws-sdk/lib-storage");
30
30
  //#region src/d1Rows.ts
@@ -134,6 +134,29 @@ const eventRow = (row) => {
134
134
  received_at_ms: numberValue(row, "received_at_ms", "bundle_events")
135
135
  };
136
136
  };
137
+ const installationRow = (row) => {
138
+ const type = stringValue(row, "type", "bundle_installations");
139
+ const platform = stringValue(row, "platform", "bundle_installations");
140
+ if (![
141
+ "UPDATE_APPLIED",
142
+ "RECOVERED",
143
+ "RELEASE_ADOPTED",
144
+ "UNCHANGED"
145
+ ].includes(type) || platform !== "ios" && platform !== "android") throw new InvalidD1RowError("bundle_installations");
146
+ return {
147
+ id: stringValue(row, "id", "bundle_installations"),
148
+ install_id: stringValue(row, "install_id", "bundle_installations"),
149
+ user_id: nullableString(row, "user_id", "bundle_installations"),
150
+ username: nullableString(row, "username", "bundle_installations"),
151
+ to_bundle_id: stringValue(row, "to_bundle_id", "bundle_installations"),
152
+ type,
153
+ platform,
154
+ app_version: stringValue(row, "app_version", "bundle_installations"),
155
+ channel: stringValue(row, "channel", "bundle_installations"),
156
+ cohort: stringValue(row, "cohort", "bundle_installations"),
157
+ received_at_ms: numberValue(row, "received_at_ms", "bundle_installations")
158
+ };
159
+ };
137
160
  const apiKeyRow = (row) => {
138
161
  const role = stringValue(row, "role", "api_keys");
139
162
  if (role !== "client") throw new InvalidD1RowError("api_keys");
@@ -208,6 +231,7 @@ function parseD1Row(model, value) {
208
231
  case "bundle_patches": return patchRow(value);
209
232
  case "channels": return channelRow(value);
210
233
  case "bundle_events": return eventRow(value);
234
+ case "bundle_installations": return installationRow(value);
211
235
  case "api_keys": return apiKeyRow(value);
212
236
  case "releases": return releaseRow(value);
213
237
  case "release_catalogs": return releaseCatalogRow(value);
@@ -244,7 +268,8 @@ const stringPredicate = (condition, operator) => {
244
268
  };
245
269
  }
246
270
  const literal = escapeLikePattern(condition.value);
247
- const parameter = bind(operator === "contains" ? `%${literal}%` : operator === "starts_with" ? `${literal}%` : `%${literal}`);
271
+ const pattern = operator === "contains" ? `%${literal}%` : operator === "starts_with" ? `${literal}%` : `%${literal}`;
272
+ const parameter = bind(pattern);
248
273
  return {
249
274
  sql: `lower(${condition.field}) LIKE lower(${parameter.sql}) ESCAPE '\\'`,
250
275
  params: parameter.params
@@ -329,11 +354,13 @@ const d1TableNames = {
329
354
  bundle_patches: "bundle_patches",
330
355
  channels: "channels",
331
356
  bundle_events: "bundle_events",
357
+ bundle_installations: "bundle_installations",
332
358
  api_keys: "api_keys",
333
359
  release_catalogs: "release_catalogs",
334
360
  releases: "releases"
335
361
  };
336
362
  var D1QueryResultError = class extends Error {
363
+ result;
337
364
  name = "D1QueryResultError";
338
365
  operation = "count";
339
366
  constructor(result) {
@@ -364,6 +391,7 @@ const findManyD1Rows = async (executor, input) => {
364
391
  case "bundle_patches": return rows.map((row) => parseD1Row("bundle_patches", row));
365
392
  case "channels": return rows.map((row) => parseD1Row("channels", row));
366
393
  case "bundle_events": return rows.map((row) => parseD1Row("bundle_events", row));
394
+ case "bundle_installations": return rows.map((row) => parseD1Row("bundle_installations", row));
367
395
  case "api_keys": return rows.map((row) => parseD1Row("api_keys", row));
368
396
  case "releases": return rows.map((row) => parseD1Row("releases", row));
369
397
  case "release_catalogs": return rows.map((row) => parseD1Row("release_catalogs", row));
@@ -371,6 +399,12 @@ const findManyD1Rows = async (executor, input) => {
371
399
  };
372
400
  //#endregion
373
401
  //#region src/d1Implementation.ts
402
+ var D1ExecutionError = class extends Error {
403
+ name = "D1ExecutionError";
404
+ constructor() {
405
+ super("D1 did not successfully execute every requested statement");
406
+ }
407
+ };
374
408
  const EXPECTATION_CONFLICT_MARKER = "HOT_UPDATER_COMMIT_EXPECTATION_CONFLICT";
375
409
  const expectationGuard = (expectations) => {
376
410
  const clauses = [];
@@ -408,7 +442,8 @@ const expectationConflict = async (executor, expectations) => {
408
442
  const versionField = isRelease ? "revision" : "generation";
409
443
  const key = isRelease ? expectation.id : expectation.scopeKey;
410
444
  const expectedVersion = isRelease ? expectation.revision : expectation.generation;
411
- const actualVersion = readVersion((await executor.query(`SELECT ${versionField} FROM ${table} WHERE ${keyField} = json_extract(?, '$') LIMIT 1`, encodeD1Values([key])))[0], versionField);
445
+ const rows = await executor.query(`SELECT ${versionField} FROM ${table} WHERE ${keyField} = json_extract(?, '$') LIMIT 1`, encodeD1Values([key]));
446
+ const actualVersion = readVersion(rows[0], versionField);
412
447
  if (actualVersion !== expectedVersion) return {
413
448
  committed: false,
414
449
  conflict: {
@@ -487,14 +522,14 @@ const insertQuery = (input, guard, conflictMode = "returnExisting") => {
487
522
  values = channelValues(input.data);
488
523
  break;
489
524
  case "bundle_events":
525
+ case "bundle_installations":
490
526
  case "api_keys":
491
527
  case "release_catalogs":
492
528
  case "releases":
493
529
  columns = Object.keys(input.data);
494
530
  values = Object.values(input.data);
495
- break;
496
531
  }
497
- const conflict = input.onConflict !== "ignore" ? "" : input.model === "channels" ? conflictMode === "ignore" ? " ON CONFLICT(name) DO NOTHING" : " ON CONFLICT(name) DO UPDATE SET name = excluded.name" : input.model === "api_keys" ? conflictMode === "ignore" ? " ON CONFLICT(hash) DO NOTHING" : " ON CONFLICT(hash) DO UPDATE SET hash = excluded.hash" : "";
532
+ const conflict = input.onConflict !== "ignore" ? "" : input.model === "channels" ? conflictMode === "ignore" ? " ON CONFLICT(name) DO NOTHING" : " ON CONFLICT(name) DO UPDATE SET name = excluded.name" : input.model === "api_keys" ? conflictMode === "ignore" ? " ON CONFLICT(hash) DO NOTHING" : " ON CONFLICT(hash) DO UPDATE SET hash = excluded.hash" : " ON CONFLICT(install_id) DO UPDATE SET install_id = excluded.install_id";
498
533
  return {
499
534
  sql: `INSERT INTO ${d1TableNames[input.model]} (${columns.join(", ")}) ${guard === void 0 ? `VALUES (${d1Placeholders(values.length)})` : `SELECT ${d1Placeholders(values.length)} WHERE ${guard.sql}`}${conflict} RETURNING *`,
500
535
  params: [...encodeD1Values(values), ...guard?.params ?? []]
@@ -558,9 +593,7 @@ const channelDeletePrecondition = (changes, changeIndex, channelId) => {
558
593
  releaseEffects.set(change.row.id, change.row.channel_id);
559
594
  break;
560
595
  case "update": break;
561
- case "delete":
562
- releaseEffects.set(change.where.id, null);
563
- break;
596
+ case "delete": releaseEffects.set(change.where.id, null);
564
597
  }
565
598
  }
566
599
  return {
@@ -635,10 +668,6 @@ const changeQuery = (change, guard) => {
635
668
  sql: `DELETE FROM channels WHERE id = json_extract(?, '$') AND NOT EXISTS (SELECT 1 FROM releases WHERE channel_id = json_extract(?, '$')) AND ${guard.sql}`,
636
669
  params: [...encodeD1Values([change.where.id, change.where.id]), ...guard.params]
637
670
  };
638
- case "insights": return insertQuery({
639
- model: "bundle_events",
640
- data: change.row
641
- }, guard);
642
671
  case "apiKeys": return change.operation === "insert" ? insertQuery({
643
672
  model: "api_keys",
644
673
  data: change.row,
@@ -807,6 +836,25 @@ const deleteChannel = async (executor, input) => {
807
836
  };
808
837
  };
809
838
  const createD1Implementation = (executor) => ({
839
+ async recordInsights({ event, installation }) {
840
+ const columns = Object.keys(installation);
841
+ const eventInsert = insertQuery({
842
+ model: "bundle_events",
843
+ data: event
844
+ });
845
+ await executor.batch([{
846
+ sql: `INSERT INTO bundle_installations (${columns.join(", ")})
847
+ SELECT ${d1Placeholders(columns.length)}
848
+ WHERE NOT EXISTS (SELECT 1 FROM bundle_events WHERE id = json_extract(?, '$'))
849
+ ON CONFLICT(install_id) DO UPDATE SET ${columns.filter((column) => column !== "install_id").map((column) => `${column} = excluded.${column}`).join(", ")}
850
+ WHERE (excluded.received_at_ms, excluded.id) >
851
+ (bundle_installations.received_at_ms, bundle_installations.id)`,
852
+ params: encodeD1Values([...Object.values(installation), event.id])
853
+ }, {
854
+ ...eventInsert,
855
+ sql: eventInsert.sql.replace(" RETURNING *", " ON CONFLICT(id) DO NOTHING")
856
+ }]);
857
+ },
810
858
  async create(input) {
811
859
  const query = insertQuery(input);
812
860
  const rows = await executor.query(query.sql, query.params);
@@ -815,6 +863,7 @@ const createD1Implementation = (executor) => ({
815
863
  case "bundle_patches": return parseD1Row("bundle_patches", rows[0]);
816
864
  case "channels": return parseD1Row("channels", rows[0]);
817
865
  case "bundle_events": return parseD1Row("bundle_events", rows[0]);
866
+ case "bundle_installations": return parseD1Row("bundle_installations", rows[0]);
818
867
  case "api_keys": return parseD1Row("api_keys", rows[0]);
819
868
  case "releases": return parseD1Row("releases", rows[0]);
820
869
  case "release_catalogs": return parseD1Row("release_catalogs", rows[0]);
@@ -829,6 +878,7 @@ const createD1Implementation = (executor) => ({
829
878
  case "api_keys": return parseD1Row("api_keys", rows[0]);
830
879
  case "releases": return parseD1Row("releases", rows[0]);
831
880
  case "release_catalogs": return parseD1Row("release_catalogs", rows[0]);
881
+ case "bundle_installations": return parseD1Row("bundle_installations", rows[0]);
832
882
  }
833
883
  },
834
884
  async delete(input) {
@@ -847,6 +897,7 @@ const createD1Implementation = (executor) => ({
847
897
  case "api_keys": return parseD1Row("api_keys", rows[0]);
848
898
  case "releases": return parseD1Row("releases", rows[0]);
849
899
  case "release_catalogs": return parseD1Row("release_catalogs", rows[0]);
900
+ case "bundle_installations": return parseD1Row("bundle_installations", rows[0]);
850
901
  }
851
902
  },
852
903
  findMany: (input) => findManyD1Rows(executor, input),
@@ -896,7 +947,11 @@ const d1Database = (config) => {
896
947
  };
897
948
  const page = await cloudflare$1.d1.database.query(config.databaseId, { ...body });
898
949
  const results = [];
899
- for await (const resultPage of page.iterPages()) for (const result of resultPage.result) results.push(result.results ?? []);
950
+ for await (const resultPage of page.iterPages()) for (const result of resultPage.result) {
951
+ if (result.success === false) throw new D1ExecutionError();
952
+ results.push(result.results ?? []);
953
+ }
954
+ if (results.length !== statements.length) throw new D1ExecutionError();
900
955
  return results;
901
956
  };
902
957
  const adapter = (0, _hot_updater_plugin_core_internal.createDatabasePluginAdapter)("d1Database", createD1Implementation({
package/dist/index.d.cts CHANGED
@@ -1,14 +1,12 @@
1
- import * as _$_hot_updater_plugin_core0 from "@hot-updater/plugin-core";
2
1
  import { StoragePluginWith } from "@hot-updater/plugin-core";
3
2
  import { S3ClientConfig } from "@aws-sdk/client-s3";
4
-
5
3
  //#region src/d1Database.d.ts
6
4
  interface D1DatabaseConfig {
7
5
  readonly databaseId: string;
8
6
  readonly accountId: string;
9
7
  readonly cloudflareApiToken: string;
10
8
  }
11
- declare const d1Database: (config: D1DatabaseConfig) => _$_hot_updater_plugin_core0.DatabasePlugin;
9
+ declare const d1Database: (config: D1DatabaseConfig) => import("@hot-updater/plugin-core").DatabasePlugin;
12
10
  //#endregion
13
11
  //#region src/r2S3Storage.d.ts
14
12
  interface R2S3StorageConfig extends S3ClientConfig {
package/dist/index.d.mts CHANGED
@@ -1,14 +1,12 @@
1
- import * as _$_hot_updater_plugin_core0 from "@hot-updater/plugin-core";
2
1
  import { StoragePluginWith } from "@hot-updater/plugin-core";
3
2
  import { S3ClientConfig } from "@aws-sdk/client-s3";
4
-
5
3
  //#region src/d1Database.d.ts
6
4
  interface D1DatabaseConfig {
7
5
  readonly databaseId: string;
8
6
  readonly accountId: string;
9
7
  readonly cloudflareApiToken: string;
10
8
  }
11
- declare const d1Database: (config: D1DatabaseConfig) => _$_hot_updater_plugin_core0.DatabasePlugin;
9
+ declare const d1Database: (config: D1DatabaseConfig) => import("@hot-updater/plugin-core").DatabasePlugin;
12
10
  //#endregion
13
11
  //#region src/r2S3Storage.d.ts
14
12
  interface R2S3StorageConfig extends S3ClientConfig {
package/dist/index.mjs CHANGED
@@ -110,6 +110,29 @@ const eventRow = (row) => {
110
110
  received_at_ms: numberValue(row, "received_at_ms", "bundle_events")
111
111
  };
112
112
  };
113
+ const installationRow = (row) => {
114
+ const type = stringValue(row, "type", "bundle_installations");
115
+ const platform = stringValue(row, "platform", "bundle_installations");
116
+ if (![
117
+ "UPDATE_APPLIED",
118
+ "RECOVERED",
119
+ "RELEASE_ADOPTED",
120
+ "UNCHANGED"
121
+ ].includes(type) || platform !== "ios" && platform !== "android") throw new InvalidD1RowError("bundle_installations");
122
+ return {
123
+ id: stringValue(row, "id", "bundle_installations"),
124
+ install_id: stringValue(row, "install_id", "bundle_installations"),
125
+ user_id: nullableString(row, "user_id", "bundle_installations"),
126
+ username: nullableString(row, "username", "bundle_installations"),
127
+ to_bundle_id: stringValue(row, "to_bundle_id", "bundle_installations"),
128
+ type,
129
+ platform,
130
+ app_version: stringValue(row, "app_version", "bundle_installations"),
131
+ channel: stringValue(row, "channel", "bundle_installations"),
132
+ cohort: stringValue(row, "cohort", "bundle_installations"),
133
+ received_at_ms: numberValue(row, "received_at_ms", "bundle_installations")
134
+ };
135
+ };
113
136
  const apiKeyRow = (row) => {
114
137
  const role = stringValue(row, "role", "api_keys");
115
138
  if (role !== "client") throw new InvalidD1RowError("api_keys");
@@ -184,6 +207,7 @@ function parseD1Row(model, value) {
184
207
  case "bundle_patches": return patchRow(value);
185
208
  case "channels": return channelRow(value);
186
209
  case "bundle_events": return eventRow(value);
210
+ case "bundle_installations": return installationRow(value);
187
211
  case "api_keys": return apiKeyRow(value);
188
212
  case "releases": return releaseRow(value);
189
213
  case "release_catalogs": return releaseCatalogRow(value);
@@ -220,7 +244,8 @@ const stringPredicate = (condition, operator) => {
220
244
  };
221
245
  }
222
246
  const literal = escapeLikePattern(condition.value);
223
- const parameter = bind(operator === "contains" ? `%${literal}%` : operator === "starts_with" ? `${literal}%` : `%${literal}`);
247
+ const pattern = operator === "contains" ? `%${literal}%` : operator === "starts_with" ? `${literal}%` : `%${literal}`;
248
+ const parameter = bind(pattern);
224
249
  return {
225
250
  sql: `lower(${condition.field}) LIKE lower(${parameter.sql}) ESCAPE '\\'`,
226
251
  params: parameter.params
@@ -305,11 +330,13 @@ const d1TableNames = {
305
330
  bundle_patches: "bundle_patches",
306
331
  channels: "channels",
307
332
  bundle_events: "bundle_events",
333
+ bundle_installations: "bundle_installations",
308
334
  api_keys: "api_keys",
309
335
  release_catalogs: "release_catalogs",
310
336
  releases: "releases"
311
337
  };
312
338
  var D1QueryResultError = class extends Error {
339
+ result;
313
340
  name = "D1QueryResultError";
314
341
  operation = "count";
315
342
  constructor(result) {
@@ -340,6 +367,7 @@ const findManyD1Rows = async (executor, input) => {
340
367
  case "bundle_patches": return rows.map((row) => parseD1Row("bundle_patches", row));
341
368
  case "channels": return rows.map((row) => parseD1Row("channels", row));
342
369
  case "bundle_events": return rows.map((row) => parseD1Row("bundle_events", row));
370
+ case "bundle_installations": return rows.map((row) => parseD1Row("bundle_installations", row));
343
371
  case "api_keys": return rows.map((row) => parseD1Row("api_keys", row));
344
372
  case "releases": return rows.map((row) => parseD1Row("releases", row));
345
373
  case "release_catalogs": return rows.map((row) => parseD1Row("release_catalogs", row));
@@ -347,6 +375,12 @@ const findManyD1Rows = async (executor, input) => {
347
375
  };
348
376
  //#endregion
349
377
  //#region src/d1Implementation.ts
378
+ var D1ExecutionError = class extends Error {
379
+ name = "D1ExecutionError";
380
+ constructor() {
381
+ super("D1 did not successfully execute every requested statement");
382
+ }
383
+ };
350
384
  const EXPECTATION_CONFLICT_MARKER = "HOT_UPDATER_COMMIT_EXPECTATION_CONFLICT";
351
385
  const expectationGuard = (expectations) => {
352
386
  const clauses = [];
@@ -384,7 +418,8 @@ const expectationConflict = async (executor, expectations) => {
384
418
  const versionField = isRelease ? "revision" : "generation";
385
419
  const key = isRelease ? expectation.id : expectation.scopeKey;
386
420
  const expectedVersion = isRelease ? expectation.revision : expectation.generation;
387
- const actualVersion = readVersion((await executor.query(`SELECT ${versionField} FROM ${table} WHERE ${keyField} = json_extract(?, '$') LIMIT 1`, encodeD1Values([key])))[0], versionField);
421
+ const rows = await executor.query(`SELECT ${versionField} FROM ${table} WHERE ${keyField} = json_extract(?, '$') LIMIT 1`, encodeD1Values([key]));
422
+ const actualVersion = readVersion(rows[0], versionField);
388
423
  if (actualVersion !== expectedVersion) return {
389
424
  committed: false,
390
425
  conflict: {
@@ -463,14 +498,14 @@ const insertQuery = (input, guard, conflictMode = "returnExisting") => {
463
498
  values = channelValues(input.data);
464
499
  break;
465
500
  case "bundle_events":
501
+ case "bundle_installations":
466
502
  case "api_keys":
467
503
  case "release_catalogs":
468
504
  case "releases":
469
505
  columns = Object.keys(input.data);
470
506
  values = Object.values(input.data);
471
- break;
472
507
  }
473
- const conflict = input.onConflict !== "ignore" ? "" : input.model === "channels" ? conflictMode === "ignore" ? " ON CONFLICT(name) DO NOTHING" : " ON CONFLICT(name) DO UPDATE SET name = excluded.name" : input.model === "api_keys" ? conflictMode === "ignore" ? " ON CONFLICT(hash) DO NOTHING" : " ON CONFLICT(hash) DO UPDATE SET hash = excluded.hash" : "";
508
+ const conflict = input.onConflict !== "ignore" ? "" : input.model === "channels" ? conflictMode === "ignore" ? " ON CONFLICT(name) DO NOTHING" : " ON CONFLICT(name) DO UPDATE SET name = excluded.name" : input.model === "api_keys" ? conflictMode === "ignore" ? " ON CONFLICT(hash) DO NOTHING" : " ON CONFLICT(hash) DO UPDATE SET hash = excluded.hash" : " ON CONFLICT(install_id) DO UPDATE SET install_id = excluded.install_id";
474
509
  return {
475
510
  sql: `INSERT INTO ${d1TableNames[input.model]} (${columns.join(", ")}) ${guard === void 0 ? `VALUES (${d1Placeholders(values.length)})` : `SELECT ${d1Placeholders(values.length)} WHERE ${guard.sql}`}${conflict} RETURNING *`,
476
511
  params: [...encodeD1Values(values), ...guard?.params ?? []]
@@ -534,9 +569,7 @@ const channelDeletePrecondition = (changes, changeIndex, channelId) => {
534
569
  releaseEffects.set(change.row.id, change.row.channel_id);
535
570
  break;
536
571
  case "update": break;
537
- case "delete":
538
- releaseEffects.set(change.where.id, null);
539
- break;
572
+ case "delete": releaseEffects.set(change.where.id, null);
540
573
  }
541
574
  }
542
575
  return {
@@ -611,10 +644,6 @@ const changeQuery = (change, guard) => {
611
644
  sql: `DELETE FROM channels WHERE id = json_extract(?, '$') AND NOT EXISTS (SELECT 1 FROM releases WHERE channel_id = json_extract(?, '$')) AND ${guard.sql}`,
612
645
  params: [...encodeD1Values([change.where.id, change.where.id]), ...guard.params]
613
646
  };
614
- case "insights": return insertQuery({
615
- model: "bundle_events",
616
- data: change.row
617
- }, guard);
618
647
  case "apiKeys": return change.operation === "insert" ? insertQuery({
619
648
  model: "api_keys",
620
649
  data: change.row,
@@ -783,6 +812,25 @@ const deleteChannel = async (executor, input) => {
783
812
  };
784
813
  };
785
814
  const createD1Implementation = (executor) => ({
815
+ async recordInsights({ event, installation }) {
816
+ const columns = Object.keys(installation);
817
+ const eventInsert = insertQuery({
818
+ model: "bundle_events",
819
+ data: event
820
+ });
821
+ await executor.batch([{
822
+ sql: `INSERT INTO bundle_installations (${columns.join(", ")})
823
+ SELECT ${d1Placeholders(columns.length)}
824
+ WHERE NOT EXISTS (SELECT 1 FROM bundle_events WHERE id = json_extract(?, '$'))
825
+ ON CONFLICT(install_id) DO UPDATE SET ${columns.filter((column) => column !== "install_id").map((column) => `${column} = excluded.${column}`).join(", ")}
826
+ WHERE (excluded.received_at_ms, excluded.id) >
827
+ (bundle_installations.received_at_ms, bundle_installations.id)`,
828
+ params: encodeD1Values([...Object.values(installation), event.id])
829
+ }, {
830
+ ...eventInsert,
831
+ sql: eventInsert.sql.replace(" RETURNING *", " ON CONFLICT(id) DO NOTHING")
832
+ }]);
833
+ },
786
834
  async create(input) {
787
835
  const query = insertQuery(input);
788
836
  const rows = await executor.query(query.sql, query.params);
@@ -791,6 +839,7 @@ const createD1Implementation = (executor) => ({
791
839
  case "bundle_patches": return parseD1Row("bundle_patches", rows[0]);
792
840
  case "channels": return parseD1Row("channels", rows[0]);
793
841
  case "bundle_events": return parseD1Row("bundle_events", rows[0]);
842
+ case "bundle_installations": return parseD1Row("bundle_installations", rows[0]);
794
843
  case "api_keys": return parseD1Row("api_keys", rows[0]);
795
844
  case "releases": return parseD1Row("releases", rows[0]);
796
845
  case "release_catalogs": return parseD1Row("release_catalogs", rows[0]);
@@ -805,6 +854,7 @@ const createD1Implementation = (executor) => ({
805
854
  case "api_keys": return parseD1Row("api_keys", rows[0]);
806
855
  case "releases": return parseD1Row("releases", rows[0]);
807
856
  case "release_catalogs": return parseD1Row("release_catalogs", rows[0]);
857
+ case "bundle_installations": return parseD1Row("bundle_installations", rows[0]);
808
858
  }
809
859
  },
810
860
  async delete(input) {
@@ -823,6 +873,7 @@ const createD1Implementation = (executor) => ({
823
873
  case "api_keys": return parseD1Row("api_keys", rows[0]);
824
874
  case "releases": return parseD1Row("releases", rows[0]);
825
875
  case "release_catalogs": return parseD1Row("release_catalogs", rows[0]);
876
+ case "bundle_installations": return parseD1Row("bundle_installations", rows[0]);
826
877
  }
827
878
  },
828
879
  findMany: (input) => findManyD1Rows(executor, input),
@@ -872,7 +923,11 @@ const d1Database = (config) => {
872
923
  };
873
924
  const page = await cloudflare.d1.database.query(config.databaseId, { ...body });
874
925
  const results = [];
875
- for await (const resultPage of page.iterPages()) for (const result of resultPage.result) results.push(result.results ?? []);
926
+ for await (const resultPage of page.iterPages()) for (const result of resultPage.result) {
927
+ if (result.success === false) throw new D1ExecutionError();
928
+ results.push(result.results ?? []);
929
+ }
930
+ if (results.length !== statements.length) throw new D1ExecutionError();
876
931
  return results;
877
932
  };
878
933
  const adapter = createDatabasePluginAdapter("d1Database", createD1Implementation({
@@ -108,6 +108,29 @@ const eventRow = (row) => {
108
108
  received_at_ms: numberValue(row, "received_at_ms", "bundle_events")
109
109
  };
110
110
  };
111
+ const installationRow = (row) => {
112
+ const type = stringValue(row, "type", "bundle_installations");
113
+ const platform = stringValue(row, "platform", "bundle_installations");
114
+ if (![
115
+ "UPDATE_APPLIED",
116
+ "RECOVERED",
117
+ "RELEASE_ADOPTED",
118
+ "UNCHANGED"
119
+ ].includes(type) || platform !== "ios" && platform !== "android") throw new InvalidD1RowError("bundle_installations");
120
+ return {
121
+ id: stringValue(row, "id", "bundle_installations"),
122
+ install_id: stringValue(row, "install_id", "bundle_installations"),
123
+ user_id: nullableString(row, "user_id", "bundle_installations"),
124
+ username: nullableString(row, "username", "bundle_installations"),
125
+ to_bundle_id: stringValue(row, "to_bundle_id", "bundle_installations"),
126
+ type,
127
+ platform,
128
+ app_version: stringValue(row, "app_version", "bundle_installations"),
129
+ channel: stringValue(row, "channel", "bundle_installations"),
130
+ cohort: stringValue(row, "cohort", "bundle_installations"),
131
+ received_at_ms: numberValue(row, "received_at_ms", "bundle_installations")
132
+ };
133
+ };
111
134
  const apiKeyRow = (row) => {
112
135
  const role = stringValue(row, "role", "api_keys");
113
136
  if (role !== "client") throw new InvalidD1RowError("api_keys");
@@ -182,6 +205,7 @@ function parseD1Row(model, value) {
182
205
  case "bundle_patches": return patchRow(value);
183
206
  case "channels": return channelRow(value);
184
207
  case "bundle_events": return eventRow(value);
208
+ case "bundle_installations": return installationRow(value);
185
209
  case "api_keys": return apiKeyRow(value);
186
210
  case "releases": return releaseRow(value);
187
211
  case "release_catalogs": return releaseCatalogRow(value);
@@ -218,7 +242,8 @@ const stringPredicate = (condition, operator) => {
218
242
  };
219
243
  }
220
244
  const literal = escapeLikePattern(condition.value);
221
- const parameter = bind(operator === "contains" ? `%${literal}%` : operator === "starts_with" ? `${literal}%` : `%${literal}`);
245
+ const pattern = operator === "contains" ? `%${literal}%` : operator === "starts_with" ? `${literal}%` : `%${literal}`;
246
+ const parameter = bind(pattern);
222
247
  return {
223
248
  sql: `lower(${condition.field}) LIKE lower(${parameter.sql}) ESCAPE '\\'`,
224
249
  params: parameter.params
@@ -303,11 +328,13 @@ const d1TableNames = {
303
328
  bundle_patches: "bundle_patches",
304
329
  channels: "channels",
305
330
  bundle_events: "bundle_events",
331
+ bundle_installations: "bundle_installations",
306
332
  api_keys: "api_keys",
307
333
  release_catalogs: "release_catalogs",
308
334
  releases: "releases"
309
335
  };
310
336
  var D1QueryResultError = class extends Error {
337
+ result;
311
338
  name = "D1QueryResultError";
312
339
  operation = "count";
313
340
  constructor(result) {
@@ -338,6 +365,7 @@ const findManyD1Rows = async (executor, input) => {
338
365
  case "bundle_patches": return rows.map((row) => parseD1Row("bundle_patches", row));
339
366
  case "channels": return rows.map((row) => parseD1Row("channels", row));
340
367
  case "bundle_events": return rows.map((row) => parseD1Row("bundle_events", row));
368
+ case "bundle_installations": return rows.map((row) => parseD1Row("bundle_installations", row));
341
369
  case "api_keys": return rows.map((row) => parseD1Row("api_keys", row));
342
370
  case "releases": return rows.map((row) => parseD1Row("releases", row));
343
371
  case "release_catalogs": return rows.map((row) => parseD1Row("release_catalogs", row));
@@ -345,6 +373,12 @@ const findManyD1Rows = async (executor, input) => {
345
373
  };
346
374
  //#endregion
347
375
  //#region src/d1Implementation.ts
376
+ var D1ExecutionError = class extends Error {
377
+ name = "D1ExecutionError";
378
+ constructor() {
379
+ super("D1 did not successfully execute every requested statement");
380
+ }
381
+ };
348
382
  const EXPECTATION_CONFLICT_MARKER = "HOT_UPDATER_COMMIT_EXPECTATION_CONFLICT";
349
383
  const expectationGuard = (expectations) => {
350
384
  const clauses = [];
@@ -382,7 +416,8 @@ const expectationConflict = async (executor, expectations) => {
382
416
  const versionField = isRelease ? "revision" : "generation";
383
417
  const key = isRelease ? expectation.id : expectation.scopeKey;
384
418
  const expectedVersion = isRelease ? expectation.revision : expectation.generation;
385
- const actualVersion = readVersion((await executor.query(`SELECT ${versionField} FROM ${table} WHERE ${keyField} = json_extract(?, '$') LIMIT 1`, encodeD1Values([key])))[0], versionField);
419
+ const rows = await executor.query(`SELECT ${versionField} FROM ${table} WHERE ${keyField} = json_extract(?, '$') LIMIT 1`, encodeD1Values([key]));
420
+ const actualVersion = readVersion(rows[0], versionField);
386
421
  if (actualVersion !== expectedVersion) return {
387
422
  committed: false,
388
423
  conflict: {
@@ -461,14 +496,14 @@ const insertQuery = (input, guard, conflictMode = "returnExisting") => {
461
496
  values = channelValues(input.data);
462
497
  break;
463
498
  case "bundle_events":
499
+ case "bundle_installations":
464
500
  case "api_keys":
465
501
  case "release_catalogs":
466
502
  case "releases":
467
503
  columns = Object.keys(input.data);
468
504
  values = Object.values(input.data);
469
- break;
470
505
  }
471
- const conflict = input.onConflict !== "ignore" ? "" : input.model === "channels" ? conflictMode === "ignore" ? " ON CONFLICT(name) DO NOTHING" : " ON CONFLICT(name) DO UPDATE SET name = excluded.name" : input.model === "api_keys" ? conflictMode === "ignore" ? " ON CONFLICT(hash) DO NOTHING" : " ON CONFLICT(hash) DO UPDATE SET hash = excluded.hash" : "";
506
+ const conflict = input.onConflict !== "ignore" ? "" : input.model === "channels" ? conflictMode === "ignore" ? " ON CONFLICT(name) DO NOTHING" : " ON CONFLICT(name) DO UPDATE SET name = excluded.name" : input.model === "api_keys" ? conflictMode === "ignore" ? " ON CONFLICT(hash) DO NOTHING" : " ON CONFLICT(hash) DO UPDATE SET hash = excluded.hash" : " ON CONFLICT(install_id) DO UPDATE SET install_id = excluded.install_id";
472
507
  return {
473
508
  sql: `INSERT INTO ${d1TableNames[input.model]} (${columns.join(", ")}) ${guard === void 0 ? `VALUES (${d1Placeholders(values.length)})` : `SELECT ${d1Placeholders(values.length)} WHERE ${guard.sql}`}${conflict} RETURNING *`,
474
509
  params: [...encodeD1Values(values), ...guard?.params ?? []]
@@ -532,9 +567,7 @@ const channelDeletePrecondition = (changes, changeIndex, channelId) => {
532
567
  releaseEffects.set(change.row.id, change.row.channel_id);
533
568
  break;
534
569
  case "update": break;
535
- case "delete":
536
- releaseEffects.set(change.where.id, null);
537
- break;
570
+ case "delete": releaseEffects.set(change.where.id, null);
538
571
  }
539
572
  }
540
573
  return {
@@ -609,10 +642,6 @@ const changeQuery = (change, guard) => {
609
642
  sql: `DELETE FROM channels WHERE id = json_extract(?, '$') AND NOT EXISTS (SELECT 1 FROM releases WHERE channel_id = json_extract(?, '$')) AND ${guard.sql}`,
610
643
  params: [...encodeD1Values([change.where.id, change.where.id]), ...guard.params]
611
644
  };
612
- case "insights": return insertQuery({
613
- model: "bundle_events",
614
- data: change.row
615
- }, guard);
616
645
  case "apiKeys": return change.operation === "insert" ? insertQuery({
617
646
  model: "api_keys",
618
647
  data: change.row,
@@ -781,6 +810,25 @@ const deleteChannel = async (executor, input) => {
781
810
  };
782
811
  };
783
812
  const createD1Implementation = (executor) => ({
813
+ async recordInsights({ event, installation }) {
814
+ const columns = Object.keys(installation);
815
+ const eventInsert = insertQuery({
816
+ model: "bundle_events",
817
+ data: event
818
+ });
819
+ await executor.batch([{
820
+ sql: `INSERT INTO bundle_installations (${columns.join(", ")})
821
+ SELECT ${d1Placeholders(columns.length)}
822
+ WHERE NOT EXISTS (SELECT 1 FROM bundle_events WHERE id = json_extract(?, '$'))
823
+ ON CONFLICT(install_id) DO UPDATE SET ${columns.filter((column) => column !== "install_id").map((column) => `${column} = excluded.${column}`).join(", ")}
824
+ WHERE (excluded.received_at_ms, excluded.id) >
825
+ (bundle_installations.received_at_ms, bundle_installations.id)`,
826
+ params: encodeD1Values([...Object.values(installation), event.id])
827
+ }, {
828
+ ...eventInsert,
829
+ sql: eventInsert.sql.replace(" RETURNING *", " ON CONFLICT(id) DO NOTHING")
830
+ }]);
831
+ },
784
832
  async create(input) {
785
833
  const query = insertQuery(input);
786
834
  const rows = await executor.query(query.sql, query.params);
@@ -789,6 +837,7 @@ const createD1Implementation = (executor) => ({
789
837
  case "bundle_patches": return parseD1Row("bundle_patches", rows[0]);
790
838
  case "channels": return parseD1Row("channels", rows[0]);
791
839
  case "bundle_events": return parseD1Row("bundle_events", rows[0]);
840
+ case "bundle_installations": return parseD1Row("bundle_installations", rows[0]);
792
841
  case "api_keys": return parseD1Row("api_keys", rows[0]);
793
842
  case "releases": return parseD1Row("releases", rows[0]);
794
843
  case "release_catalogs": return parseD1Row("release_catalogs", rows[0]);
@@ -803,6 +852,7 @@ const createD1Implementation = (executor) => ({
803
852
  case "api_keys": return parseD1Row("api_keys", rows[0]);
804
853
  case "releases": return parseD1Row("releases", rows[0]);
805
854
  case "release_catalogs": return parseD1Row("release_catalogs", rows[0]);
855
+ case "bundle_installations": return parseD1Row("bundle_installations", rows[0]);
806
856
  }
807
857
  },
808
858
  async delete(input) {
@@ -821,6 +871,7 @@ const createD1Implementation = (executor) => ({
821
871
  case "api_keys": return parseD1Row("api_keys", rows[0]);
822
872
  case "releases": return parseD1Row("releases", rows[0]);
823
873
  case "release_catalogs": return parseD1Row("release_catalogs", rows[0]);
874
+ case "bundle_installations": return parseD1Row("bundle_installations", rows[0]);
824
875
  }
825
876
  },
826
877
  findMany: (input) => findManyD1Rows(executor, input),
@@ -853,14 +904,19 @@ const createD1Implementation = (executor) => ({
853
904
  //#endregion
854
905
  //#region src/worker/d1Database.ts
855
906
  const d1Database = (database) => {
856
- const adapter = (0, _hot_updater_plugin_core_internal.createDatabasePluginAdapter)("d1Database", createD1Implementation({
907
+ const implementation = createD1Implementation({
857
908
  async query(sql, params) {
858
- return (await database.prepare(sql).bind(...params).all()).results ?? [];
909
+ const result = await database.prepare(sql).bind(...params).all();
910
+ if (result.success === false) throw new D1ExecutionError();
911
+ return result.results ?? [];
859
912
  },
860
913
  async batch(statements) {
861
- return (await database.batch(statements.map(({ sql, params }) => database.prepare(sql).bind(...params)))).map(({ results }) => results ?? []);
914
+ const results = await database.batch(statements.map(({ sql, params }) => database.prepare(sql).bind(...params)));
915
+ if (results.length !== statements.length || results.some(({ success }) => success === false)) throw new D1ExecutionError();
916
+ return results.map(({ results }) => results ?? []);
862
917
  }
863
- }));
918
+ });
919
+ const adapter = (0, _hot_updater_plugin_core_internal.createDatabasePluginAdapter)("d1Database", implementation);
864
920
  return (0, _hot_updater_plugin_core.createDatabasePlugin)({
865
921
  name: "d1Database",
866
922
  models: adapter.models,
@@ -1,9 +1,8 @@
1
- import * as _$_hot_updater_plugin_core0 from "@hot-updater/plugin-core";
2
1
  import { StoragePluginWith } from "@hot-updater/plugin-core";
3
-
4
2
  //#region src/worker/d1Database.d.ts
5
3
  type D1Result = {
6
4
  readonly results?: readonly unknown[];
5
+ readonly success?: boolean;
7
6
  };
8
7
  type D1BoundStatement = {
9
8
  all: () => Promise<D1Result>;
@@ -15,7 +14,7 @@ type D1Like = {
15
14
  prepare(sql: string): D1PreparedStatement;
16
15
  batch(statements: D1BoundStatement[]): Promise<readonly D1Result[]>;
17
16
  };
18
- declare const d1Database: (database: D1Like) => _$_hot_updater_plugin_core0.DatabasePlugin;
17
+ declare const d1Database: (database: D1Like) => import("@hot-updater/plugin-core").DatabasePlugin;
19
18
  //#endregion
20
19
  //#region src/r2WorkerStorage.d.ts
21
20
  interface CloudflareWorkerStorageConfig {