@hot-updater/postgres 1.0.0-rc.1 → 1.0.0-rc.2

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));
@@ -25,7 +25,7 @@ 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 kysely = require("kysely");
27
27
  let pg = require("pg");
28
- pg = __toESM(pg);
28
+ pg = __toESM(pg, 1);
29
29
  //#region src/postgresQuery.ts
30
30
  const applyOrder = (order, clause) => {
31
31
  const directed = clause.direction === "asc" ? order.asc() : order.desc();
@@ -51,6 +51,13 @@ const countPostgresRows = async (db, input, where) => {
51
51
  const query = rows.select(({ fn }) => fn.countAll().as("count"));
52
52
  return Number((await query.executeTakeFirstOrThrow()).count);
53
53
  }
54
+ case "bundle_events":
55
+ case "bundle_installations": {
56
+ let rows = db.selectFrom(input.model);
57
+ if (where !== void 0) rows = rows.where(where);
58
+ const query = rows.select(({ fn }) => fn.countAll().as("count"));
59
+ return Number((await query.executeTakeFirstOrThrow()).count);
60
+ }
54
61
  }
55
62
  };
56
63
  const findManyPostgresRows = async (db, input, where) => {
@@ -75,6 +82,12 @@ const findManyPostgresRows = async (db, input, where) => {
75
82
  for (const clause of input.orderBy ?? []) query = query.orderBy(clause.field, (order) => applyOrder(order, clause));
76
83
  return query.limit(input.limit).offset(input.offset).execute();
77
84
  }
85
+ case "bundle_installations": {
86
+ let query = db.selectFrom("bundle_installations").selectAll();
87
+ if (where !== void 0) query = query.where(where);
88
+ for (const clause of input.orderBy ?? []) query = query.orderBy(clause.field, (order) => applyOrder(order, clause));
89
+ return query.limit(input.limit).offset(input.offset).execute();
90
+ }
78
91
  case "channels": {
79
92
  let query = db.selectFrom("channels").selectAll();
80
93
  if (where !== void 0) query = query.where(where);
@@ -152,11 +165,31 @@ const buildWhere = (where) => {
152
165
  return expression;
153
166
  };
154
167
  const createPostgresImplementation = (db) => ({
168
+ async recordInsights({ event, installation }) {
169
+ const eventColumns = Object.keys(event);
170
+ const installationColumns = Object.keys(installation);
171
+ await kysely.sql`
172
+ WITH accepted_event AS (
173
+ INSERT INTO bundle_events (${kysely.sql.join(eventColumns.map(kysely.sql.ref))})
174
+ VALUES (${kysely.sql.join(Object.values(event))})
175
+ ON CONFLICT (id) DO NOTHING RETURNING id
176
+ )
177
+ INSERT INTO bundle_installations (${kysely.sql.join(installationColumns.map(kysely.sql.ref))})
178
+ SELECT ${kysely.sql.join(Object.values(installation))} FROM accepted_event
179
+ ON CONFLICT (install_id) DO UPDATE SET ${kysely.sql.join(installationColumns.filter((column) => column !== "install_id").map((column) => kysely.sql`${kysely.sql.ref(column)} = ${kysely.sql.ref(`excluded.${column}`)}`))}
180
+ WHERE (excluded.received_at_ms, excluded.id) >
181
+ (bundle_installations.received_at_ms, bundle_installations.id)
182
+ `.execute(db);
183
+ },
155
184
  async create(input) {
156
185
  switch (input.model) {
157
186
  case "bundles": return db.insertInto("bundles").values(input.data).returningAll().executeTakeFirstOrThrow();
158
187
  case "bundle_patches": return db.insertInto("bundle_patches").values(input.data).returningAll().executeTakeFirstOrThrow();
159
188
  case "bundle_events": return db.insertInto("bundle_events").values(input.data).returningAll().executeTakeFirstOrThrow();
189
+ case "bundle_installations": {
190
+ const query = db.insertInto("bundle_installations").values(input.data);
191
+ return await (input.onConflict === "ignore" ? query.onConflict((conflict) => conflict.column("install_id").doNothing()) : query).returningAll().executeTakeFirst() ?? await db.selectFrom("bundle_installations").selectAll().where("install_id", "=", input.data.install_id).executeTakeFirstOrThrow();
192
+ }
160
193
  case "releases": return db.insertInto("releases").values(input.data).returningAll().executeTakeFirstOrThrow();
161
194
  case "release_catalogs": return db.insertInto("release_catalogs").values(input.data).returningAll().executeTakeFirstOrThrow();
162
195
  case "channels": {
@@ -183,6 +216,11 @@ const createPostgresImplementation = (db) => ({
183
216
  if (where !== void 0) query = query.where(where);
184
217
  return await query.returningAll().executeTakeFirst() ?? null;
185
218
  }
219
+ if (input.model === "bundle_installations") {
220
+ let query = db.updateTable("bundle_installations").set(input.update);
221
+ if (where !== void 0) query = query.where(where);
222
+ return await query.returningAll().executeTakeFirst() ?? null;
223
+ }
186
224
  let query = db.updateTable("bundles").set(input.update);
187
225
  if (where !== void 0) query = query.where(where);
188
226
  return await query.returningAll().executeTakeFirst() ?? null;
@@ -253,6 +291,11 @@ const createPostgresImplementation = (db) => ({
253
291
  if (where !== void 0) query = query.where(where);
254
292
  return await query.executeTakeFirst() ?? null;
255
293
  }
294
+ case "bundle_installations": {
295
+ let query = db.selectFrom("bundle_installations").selectAll();
296
+ if (where !== void 0) query = query.where(where);
297
+ return await query.executeTakeFirst() ?? null;
298
+ }
256
299
  }
257
300
  },
258
301
  findMany: (input) => findManyPostgresRows(db, input, buildWhere(input.where)),
@@ -286,7 +329,11 @@ const createPostgresImplementation = (db) => ({
286
329
  });
287
330
  const postgres = (config) => {
288
331
  const { dialect, ...poolConfig } = config;
289
- const adapter = (0, _hot_updater_plugin_core_internal.createDatabasePluginAdapter)("postgres", dialect !== void 0 ? createPostgresImplementation(new kysely.Kysely({ dialect })) : createPostgresImplementation(new kysely.Kysely({ dialect: new kysely.PostgresDialect({ pool: new Pool(poolConfig) }) })));
332
+ const implementation = dialect !== void 0 ? createPostgresImplementation(new kysely.Kysely({ dialect })) : (() => {
333
+ const pool = new Pool(poolConfig);
334
+ return createPostgresImplementation(new kysely.Kysely({ dialect: new kysely.PostgresDialect({ pool }) }));
335
+ })();
336
+ const adapter = (0, _hot_updater_plugin_core_internal.createDatabasePluginAdapter)("postgres", implementation);
290
337
  return (0, _hot_updater_plugin_core.createDatabasePlugin)({
291
338
  name: "postgres",
292
339
  models: adapter.models,
package/dist/index.d.cts CHANGED
@@ -1,11 +1,9 @@
1
- import * as _$_hot_updater_plugin_core0 from "@hot-updater/plugin-core";
2
1
  import { Dialect } from "kysely";
3
2
  import { PoolConfig } from "pg";
4
-
5
3
  //#region src/postgres.d.ts
6
4
  type PostgresConfig = PoolConfig & {
7
5
  readonly dialect?: Dialect;
8
6
  };
9
- declare const postgres: (config: PostgresConfig) => _$_hot_updater_plugin_core0.DatabasePlugin;
7
+ declare const postgres: (config: PostgresConfig) => import("@hot-updater/plugin-core").DatabasePlugin;
10
8
  //#endregion
11
9
  export { PostgresConfig, postgres };
package/dist/index.d.mts CHANGED
@@ -1,11 +1,9 @@
1
- import * as _$_hot_updater_plugin_core0 from "@hot-updater/plugin-core";
2
1
  import { Dialect } from "kysely";
3
2
  import { PoolConfig } from "pg";
4
-
5
3
  //#region src/postgres.d.ts
6
4
  type PostgresConfig = PoolConfig & {
7
5
  readonly dialect?: Dialect;
8
6
  };
9
- declare const postgres: (config: PostgresConfig) => _$_hot_updater_plugin_core0.DatabasePlugin;
7
+ declare const postgres: (config: PostgresConfig) => import("@hot-updater/plugin-core").DatabasePlugin;
10
8
  //#endregion
11
9
  export { PostgresConfig, postgres };
package/dist/index.mjs CHANGED
@@ -27,6 +27,13 @@ const countPostgresRows = async (db, input, where) => {
27
27
  const query = rows.select(({ fn }) => fn.countAll().as("count"));
28
28
  return Number((await query.executeTakeFirstOrThrow()).count);
29
29
  }
30
+ case "bundle_events":
31
+ case "bundle_installations": {
32
+ let rows = db.selectFrom(input.model);
33
+ if (where !== void 0) rows = rows.where(where);
34
+ const query = rows.select(({ fn }) => fn.countAll().as("count"));
35
+ return Number((await query.executeTakeFirstOrThrow()).count);
36
+ }
30
37
  }
31
38
  };
32
39
  const findManyPostgresRows = async (db, input, where) => {
@@ -51,6 +58,12 @@ const findManyPostgresRows = async (db, input, where) => {
51
58
  for (const clause of input.orderBy ?? []) query = query.orderBy(clause.field, (order) => applyOrder(order, clause));
52
59
  return query.limit(input.limit).offset(input.offset).execute();
53
60
  }
61
+ case "bundle_installations": {
62
+ let query = db.selectFrom("bundle_installations").selectAll();
63
+ if (where !== void 0) query = query.where(where);
64
+ for (const clause of input.orderBy ?? []) query = query.orderBy(clause.field, (order) => applyOrder(order, clause));
65
+ return query.limit(input.limit).offset(input.offset).execute();
66
+ }
54
67
  case "channels": {
55
68
  let query = db.selectFrom("channels").selectAll();
56
69
  if (where !== void 0) query = query.where(where);
@@ -128,11 +141,31 @@ const buildWhere = (where) => {
128
141
  return expression;
129
142
  };
130
143
  const createPostgresImplementation = (db) => ({
144
+ async recordInsights({ event, installation }) {
145
+ const eventColumns = Object.keys(event);
146
+ const installationColumns = Object.keys(installation);
147
+ await sql`
148
+ WITH accepted_event AS (
149
+ INSERT INTO bundle_events (${sql.join(eventColumns.map(sql.ref))})
150
+ VALUES (${sql.join(Object.values(event))})
151
+ ON CONFLICT (id) DO NOTHING RETURNING id
152
+ )
153
+ INSERT INTO bundle_installations (${sql.join(installationColumns.map(sql.ref))})
154
+ SELECT ${sql.join(Object.values(installation))} FROM accepted_event
155
+ ON CONFLICT (install_id) DO UPDATE SET ${sql.join(installationColumns.filter((column) => column !== "install_id").map((column) => sql`${sql.ref(column)} = ${sql.ref(`excluded.${column}`)}`))}
156
+ WHERE (excluded.received_at_ms, excluded.id) >
157
+ (bundle_installations.received_at_ms, bundle_installations.id)
158
+ `.execute(db);
159
+ },
131
160
  async create(input) {
132
161
  switch (input.model) {
133
162
  case "bundles": return db.insertInto("bundles").values(input.data).returningAll().executeTakeFirstOrThrow();
134
163
  case "bundle_patches": return db.insertInto("bundle_patches").values(input.data).returningAll().executeTakeFirstOrThrow();
135
164
  case "bundle_events": return db.insertInto("bundle_events").values(input.data).returningAll().executeTakeFirstOrThrow();
165
+ case "bundle_installations": {
166
+ const query = db.insertInto("bundle_installations").values(input.data);
167
+ return await (input.onConflict === "ignore" ? query.onConflict((conflict) => conflict.column("install_id").doNothing()) : query).returningAll().executeTakeFirst() ?? await db.selectFrom("bundle_installations").selectAll().where("install_id", "=", input.data.install_id).executeTakeFirstOrThrow();
168
+ }
136
169
  case "releases": return db.insertInto("releases").values(input.data).returningAll().executeTakeFirstOrThrow();
137
170
  case "release_catalogs": return db.insertInto("release_catalogs").values(input.data).returningAll().executeTakeFirstOrThrow();
138
171
  case "channels": {
@@ -159,6 +192,11 @@ const createPostgresImplementation = (db) => ({
159
192
  if (where !== void 0) query = query.where(where);
160
193
  return await query.returningAll().executeTakeFirst() ?? null;
161
194
  }
195
+ if (input.model === "bundle_installations") {
196
+ let query = db.updateTable("bundle_installations").set(input.update);
197
+ if (where !== void 0) query = query.where(where);
198
+ return await query.returningAll().executeTakeFirst() ?? null;
199
+ }
162
200
  let query = db.updateTable("bundles").set(input.update);
163
201
  if (where !== void 0) query = query.where(where);
164
202
  return await query.returningAll().executeTakeFirst() ?? null;
@@ -229,6 +267,11 @@ const createPostgresImplementation = (db) => ({
229
267
  if (where !== void 0) query = query.where(where);
230
268
  return await query.executeTakeFirst() ?? null;
231
269
  }
270
+ case "bundle_installations": {
271
+ let query = db.selectFrom("bundle_installations").selectAll();
272
+ if (where !== void 0) query = query.where(where);
273
+ return await query.executeTakeFirst() ?? null;
274
+ }
232
275
  }
233
276
  },
234
277
  findMany: (input) => findManyPostgresRows(db, input, buildWhere(input.where)),
@@ -262,7 +305,11 @@ const createPostgresImplementation = (db) => ({
262
305
  });
263
306
  const postgres = (config) => {
264
307
  const { dialect, ...poolConfig } = config;
265
- const adapter = createDatabasePluginAdapter("postgres", dialect !== void 0 ? createPostgresImplementation(new Kysely({ dialect })) : createPostgresImplementation(new Kysely({ dialect: new PostgresDialect({ pool: new Pool(poolConfig) }) })));
308
+ const implementation = dialect !== void 0 ? createPostgresImplementation(new Kysely({ dialect })) : (() => {
309
+ const pool = new Pool(poolConfig);
310
+ return createPostgresImplementation(new Kysely({ dialect: new PostgresDialect({ pool }) }));
311
+ })();
312
+ const adapter = createDatabasePluginAdapter("postgres", implementation);
266
313
  return createDatabasePlugin({
267
314
  name: "postgres",
268
315
  models: adapter.models,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hot-updater/postgres",
3
3
  "type": "module",
4
- "version": "1.0.0-rc.1",
4
+ "version": "1.0.0-rc.2",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.mjs",
@@ -35,7 +35,7 @@
35
35
  "kysely": "0.28.17",
36
36
  "pg": "8.13.1",
37
37
  "@hot-updater/core": "1.0.0-rc.0",
38
- "@hot-updater/plugin-core": "1.0.0-rc.1"
38
+ "@hot-updater/plugin-core": "1.0.0-rc.2"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@electric-sql/pglite": "0.4.1",
@@ -44,7 +44,7 @@
44
44
  "camelcase-keys": "^9.1.3",
45
45
  "kysely-pglite-dialect": "1.2.0",
46
46
  "pg-minify": "^1.6.5",
47
- "@hot-updater/test-utils": "1.0.0-rc.1"
47
+ "@hot-updater/test-utils": "1.0.0-rc.2"
48
48
  },
49
49
  "scripts": {
50
50
  "build": "tsdown",
package/sql/bundles.sql CHANGED
@@ -70,16 +70,16 @@ create table release_catalogs (
70
70
  create table bundle_events (
71
71
  id uuid primary key not null,
72
72
  type text not null,
73
- install_id text not null,
74
- user_id text,
73
+ install_id text collate "C" not null,
74
+ user_id text collate "C",
75
75
  username text,
76
76
  from_release_id uuid,
77
77
  from_bundle_id uuid,
78
78
  to_release_id uuid,
79
79
  to_bundle_id uuid not null,
80
- platform text not null,
80
+ platform text collate "C" not null,
81
81
  app_version text not null,
82
- channel text not null,
82
+ channel text collate "C" not null,
83
83
  cohort text not null,
84
84
  update_strategy text,
85
85
  fingerprint_hash text,
@@ -87,6 +87,20 @@ create table bundle_events (
87
87
  received_at_ms double precision not null
88
88
  );
89
89
 
90
+ create table bundle_installations (
91
+ install_id text collate "C" primary key not null,
92
+ id uuid not null,
93
+ user_id text collate "C",
94
+ username text,
95
+ to_bundle_id uuid not null,
96
+ type text not null,
97
+ platform text collate "C" not null,
98
+ app_version text not null,
99
+ channel text collate "C" not null,
100
+ cohort text not null,
101
+ received_at_ms double precision not null
102
+ );
103
+
90
104
  create table api_keys (
91
105
  id varchar(255) primary key not null,
92
106
  hash text not null,
@@ -112,13 +126,13 @@ create index releases_fingerprint_hash_idx on releases(fingerprint_hash);
112
126
  create index releases_enabled_idx on releases(enabled);
113
127
  create index release_catalogs_channel_idx on release_catalogs(channel_id);
114
128
  create index bundle_events_received_at_idx on bundle_events(received_at_ms, id);
115
- create index bundle_events_install_idx on bundle_events(install_id, received_at_ms, id);
116
- create index bundle_events_user_id_idx on bundle_events(user_id, received_at_ms, id);
117
- create index bundle_events_username_idx on bundle_events(username, received_at_ms, id);
118
- create index bundle_events_to_bundle_idx on bundle_events(type, to_bundle_id, received_at_ms, id);
119
- create index bundle_events_from_bundle_idx on bundle_events(type, from_bundle_id, received_at_ms, id);
120
- create index bundle_events_to_release_idx on bundle_events(type, to_release_id, received_at_ms, id);
121
- create index bundle_events_from_release_idx on bundle_events(type, from_release_id, received_at_ms, id);
129
+ create index bundle_events_install_idx on bundle_events(install_id, type, received_at_ms, id);
130
+ create index bundle_installations_user_id_idx on bundle_installations(user_id, install_id);
131
+ create index bundle_installations_received_at_idx on bundle_installations(received_at_ms);
132
+ create index bundle_events_from_bundle_idx on bundle_events(type, platform, channel, from_bundle_id, received_at_ms, id);
133
+ create index bundle_events_to_bundle_idx on bundle_events(type, platform, channel, to_bundle_id, received_at_ms, id);
134
+ create index bundle_installations_scope_idx on bundle_installations(platform, channel, received_at_ms);
135
+ create index bundle_installations_bundle_idx on bundle_installations(platform, channel, to_bundle_id, received_at_ms);
122
136
  create unique index api_keys_hash_key on api_keys(hash);
123
137
  create index api_keys_created_at_idx on api_keys(created_at_ms, id);
124
138
 
@@ -154,6 +168,12 @@ alter table bundle_events add constraint bundle_events_shape_check
154
168
  check (((type in ('UPDATE_APPLIED', 'RECOVERED', 'RELEASE_ADOPTED')) and from_bundle_id is not null and update_strategy is not null and update_strategy in ('fingerprint', 'appVersion')) or (type = 'UNCHANGED' and from_bundle_id is null and update_strategy is null));
155
169
  alter table bundle_events add constraint bundle_events_received_at_check
156
170
  check (received_at_ms >= 0);
171
+ alter table bundle_installations add constraint bundle_installations_type_check
172
+ check (type in ('UPDATE_APPLIED', 'RECOVERED', 'RELEASE_ADOPTED', 'UNCHANGED'));
173
+ alter table bundle_installations add constraint bundle_installations_platform_check
174
+ check (platform in ('ios', 'android'));
175
+ alter table bundle_installations add constraint bundle_installations_received_at_check
176
+ check (received_at_ms >= 0);
157
177
  alter table api_keys add constraint api_keys_role_check
158
178
  check (role = 'client');
159
179
  alter table api_keys add constraint api_keys_created_at_check