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

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
@@ -22,324 +22,28 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
22
  }) : target, mod));
23
23
  //#endregion
24
24
  let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
25
- let _hot_updater_plugin_core_internal = require("@hot-updater/plugin-core/internal");
25
+ let _hot_updater_server_adapters_kysely = require("@hot-updater/server/adapters/kysely");
26
26
  let kysely = require("kysely");
27
27
  let pg = require("pg");
28
28
  pg = __toESM(pg, 1);
29
- //#region src/postgresQuery.ts
30
- const applyOrder = (order, clause) => {
31
- const directed = clause.direction === "asc" ? order.asc() : order.desc();
32
- return clause.nulls === void 0 ? directed : clause.nulls === "first" ? directed.nullsFirst() : directed.nullsLast();
33
- };
34
- const countPostgresRows = async (db, input, where) => {
35
- switch (input.model) {
36
- case "bundles": {
37
- let rows = db.selectFrom("bundles");
38
- if (where !== void 0) rows = rows.where(where);
39
- const query = input.distinct === void 0 ? rows.select(({ fn }) => fn.countAll().as("count")) : db.selectFrom(rows.select(input.distinct).distinct().as("distinct_rows")).select(({ fn }) => fn.countAll().as("count"));
40
- return Number((await query.executeTakeFirstOrThrow()).count);
41
- }
42
- case "bundle_patches": {
43
- let rows = db.selectFrom("bundle_patches");
44
- if (where !== void 0) rows = rows.where(where);
45
- const query = input.distinct === void 0 ? rows.select(({ fn }) => fn.countAll().as("count")) : db.selectFrom(rows.select(input.distinct).distinct().as("distinct_rows")).select(({ fn }) => fn.countAll().as("count"));
46
- return Number((await query.executeTakeFirstOrThrow()).count);
47
- }
48
- case "releases": {
49
- let rows = db.selectFrom("releases");
50
- if (where !== void 0) rows = rows.where(where);
51
- const query = rows.select(({ fn }) => fn.countAll().as("count"));
52
- return Number((await query.executeTakeFirstOrThrow()).count);
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
- }
61
- }
62
- };
63
- const findManyPostgresRows = async (db, input, where) => {
64
- switch (input.model) {
65
- case "bundles": {
66
- let query = db.selectFrom("bundles").selectAll();
67
- if (where !== void 0) query = query.where(where);
68
- if (input.distinctOn !== void 0) query = query.distinctOn(input.distinctOn.fields);
69
- for (const clause of input.orderBy ?? []) query = query.orderBy(clause.field, (order) => applyOrder(order, clause));
70
- return query.limit(input.limit).offset(input.offset).execute();
71
- }
72
- case "release_catalogs": {
73
- let query = db.selectFrom("release_catalogs").selectAll();
74
- if (where !== void 0) query = query.where(where);
75
- for (const clause of input.orderBy ?? []) query = query.orderBy(clause.field, (order) => applyOrder(order, clause));
76
- return query.limit(input.limit).offset(input.offset).execute();
77
- }
78
- case "bundle_events": {
79
- let query = db.selectFrom("bundle_events").selectAll();
80
- if (where !== void 0) query = query.where(where);
81
- if (input.distinctOn !== void 0) query = query.distinctOn(input.distinctOn.fields);
82
- for (const clause of input.orderBy ?? []) query = query.orderBy(clause.field, (order) => applyOrder(order, clause));
83
- return query.limit(input.limit).offset(input.offset).execute();
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
- }
91
- case "channels": {
92
- let query = db.selectFrom("channels").selectAll();
93
- if (where !== void 0) query = query.where(where);
94
- for (const clause of input.orderBy ?? []) query = query.orderBy(clause.field, (order) => applyOrder(order, clause));
95
- return query.limit(input.limit).offset(input.offset).execute();
96
- }
97
- case "api_keys": {
98
- let query = db.selectFrom("api_keys").selectAll();
99
- if (where !== void 0) query = query.where(where);
100
- if (input.distinctOn !== void 0) query = query.distinctOn(input.distinctOn.fields);
101
- for (const clause of input.orderBy ?? []) query = query.orderBy(clause.field, (order) => applyOrder(order, clause));
102
- return query.limit(input.limit).offset(input.offset).execute();
103
- }
104
- case "bundle_patches": {
105
- let query = db.selectFrom("bundle_patches").selectAll();
106
- if (where !== void 0) query = query.where(where);
107
- if (input.distinctOn !== void 0) query = query.distinctOn(input.distinctOn.fields);
108
- for (const clause of input.orderBy ?? []) query = query.orderBy(clause.field, (order) => applyOrder(order, clause));
109
- return query.limit(input.limit).offset(input.offset).execute();
110
- }
111
- case "releases": {
112
- let query = db.selectFrom("releases").selectAll();
113
- if (where !== void 0) query = query.where(where);
114
- for (const clause of input.orderBy ?? []) query = query.orderBy(clause.field, (order) => applyOrder(order, clause));
115
- return query.limit(input.limit).offset(input.offset).execute();
116
- }
117
- }
118
- };
119
- //#endregion
120
29
  //#region src/postgres.ts
121
30
  const { Pool } = pg.default;
122
- var InvalidPostgresPredicateError = class extends Error {
123
- name = "InvalidPostgresPredicateError";
124
- };
125
- const isForeignKeyViolation = (error) => typeof error === "object" && error !== null && Reflect.get(error, "code") === "23503";
126
- const escapeLikePattern = (value) => value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
127
- const stringPredicate = (condition, operator) => {
128
- if (typeof condition.value !== "string") throw new InvalidPostgresPredicateError();
129
- const column = kysely.sql.ref(condition.field);
130
- const literal = escapeLikePattern(condition.value);
131
- const pattern = operator === "contains" ? `%${literal}%` : operator === "starts_with" ? `${literal}%` : `%${literal}`;
132
- return "mode" in condition && condition.mode === "insensitive" ? kysely.sql`lower(${column}) like lower(${pattern}) escape '\\'` : kysely.sql`${column} like ${pattern} escape '\\'`;
133
- };
134
- const predicate = (condition) => {
135
- const column = kysely.sql.ref(condition.field);
136
- const operator = condition.operator ?? "eq";
137
- switch (operator) {
138
- case "eq":
139
- case "ne":
140
- if (condition.value === null) return operator === "eq" ? kysely.sql`${column} is null` : kysely.sql`${column} is not null`;
141
- if ("mode" in condition && condition.mode === "insensitive") return operator === "eq" ? kysely.sql`lower(${column}) = lower(${condition.value})` : kysely.sql`lower(${column}) <> lower(${condition.value})`;
142
- return operator === "eq" ? kysely.sql`${column} = ${condition.value}` : kysely.sql`${column} <> ${condition.value}`;
143
- case "gt": return kysely.sql`${column} > ${condition.value}`;
144
- case "gte": return kysely.sql`${column} >= ${condition.value}`;
145
- case "lt": return kysely.sql`${column} < ${condition.value}`;
146
- case "lte": return kysely.sql`${column} <= ${condition.value}`;
147
- case "in":
148
- case "not_in":
149
- if (!Array.isArray(condition.value)) throw new InvalidPostgresPredicateError();
150
- if (condition.value.length === 0) return kysely.sql`${operator === "not_in"}`;
151
- return operator === "in" ? kysely.sql`${column} in (${kysely.sql.join(condition.value)})` : kysely.sql`${column} not in (${kysely.sql.join(condition.value)})`;
152
- case "contains":
153
- case "starts_with":
154
- case "ends_with": return stringPredicate(condition, operator);
155
- }
156
- };
157
- const buildWhere = (where) => {
158
- const [first, ...rest] = where ?? [];
159
- if (first === void 0) return;
160
- let expression = predicate(first);
161
- for (const condition of rest) {
162
- const next = predicate(condition);
163
- expression = condition.connector === "OR" ? kysely.sql`(${expression} or ${next})` : kysely.sql`(${expression} and ${next})`;
164
- }
165
- return expression;
166
- };
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
- },
184
- async create(input) {
185
- switch (input.model) {
186
- case "bundles": return db.insertInto("bundles").values(input.data).returningAll().executeTakeFirstOrThrow();
187
- case "bundle_patches": return db.insertInto("bundle_patches").values(input.data).returningAll().executeTakeFirstOrThrow();
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
- }
193
- case "releases": return db.insertInto("releases").values(input.data).returningAll().executeTakeFirstOrThrow();
194
- case "release_catalogs": return db.insertInto("release_catalogs").values(input.data).returningAll().executeTakeFirstOrThrow();
195
- case "channels": {
196
- const query = db.insertInto("channels").values(input.data);
197
- return await (input.onConflict === "ignore" ? query.onConflict((conflict) => conflict.column("name").doNothing()) : query).returningAll().executeTakeFirst() ?? await db.selectFrom("channels").selectAll().where("name", "=", input.data.name).executeTakeFirstOrThrow();
198
- }
199
- case "api_keys": return await (input.onConflict === "ignore" ? db.insertInto("api_keys").values(input.data).onConflict((conflict) => conflict.column("hash").doNothing()) : db.insertInto("api_keys").values(input.data)).returningAll().executeTakeFirst() ?? await db.selectFrom("api_keys").selectAll().where("hash", "=", input.data.hash).executeTakeFirstOrThrow();
200
- }
201
- },
202
- async update(input) {
203
- const where = buildWhere(input.where);
204
- if (input.model === "api_keys") {
205
- let query = db.updateTable("api_keys").set(input.update);
206
- if (where !== void 0) query = query.where(where);
207
- return await query.returningAll().executeTakeFirst() ?? null;
208
- }
209
- if (input.model === "releases") {
210
- let query = db.updateTable("releases").set(input.update);
211
- if (where !== void 0) query = query.where(where);
212
- return await query.returningAll().executeTakeFirst() ?? null;
213
- }
214
- if (input.model === "release_catalogs") {
215
- let query = db.updateTable("release_catalogs").set(input.update);
216
- if (where !== void 0) query = query.where(where);
217
- return await query.returningAll().executeTakeFirst() ?? null;
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
- }
224
- let query = db.updateTable("bundles").set(input.update);
225
- if (where !== void 0) query = query.where(where);
226
- return await query.returningAll().executeTakeFirst() ?? null;
227
- },
228
- async delete(input) {
229
- const where = buildWhere(input.where);
230
- switch (input.model) {
231
- case "bundles": {
232
- let query = db.deleteFrom("bundles");
233
- if (where !== void 0) query = query.where(where);
234
- await query.execute();
235
- return;
236
- }
237
- case "bundle_patches": {
238
- let query = db.deleteFrom("bundle_patches");
239
- if (where !== void 0) query = query.where(where);
240
- await query.execute();
241
- return;
242
- }
243
- case "channels": {
244
- let query = db.deleteFrom("channels");
245
- if (where !== void 0) query = query.where(where);
246
- try {
247
- await query.execute();
248
- } catch (error) {
249
- if (isForeignKeyViolation(error)) throw new _hot_updater_plugin_core_internal.DatabaseRowReferencedError();
250
- throw error;
251
- }
252
- }
253
- case "releases": {
254
- let query = db.deleteFrom("releases");
255
- if (where !== void 0) query = query.where(where);
256
- await query.execute();
257
- }
258
- }
259
- },
260
- count: (input) => countPostgresRows(db, input, buildWhere(input.where)),
261
- async findOne(input) {
262
- const where = buildWhere(input.where);
263
- switch (input.model) {
264
- case "bundles": {
265
- let query = db.selectFrom("bundles").selectAll();
266
- if (where !== void 0) query = query.where(where);
267
- return await query.executeTakeFirst() ?? null;
268
- }
269
- case "api_keys": {
270
- let query = db.selectFrom("api_keys").selectAll();
271
- if (where !== void 0) query = query.where(where);
272
- return await query.executeTakeFirst() ?? null;
273
- }
274
- case "bundle_patches": {
275
- let query = db.selectFrom("bundle_patches").selectAll();
276
- if (where !== void 0) query = query.where(where);
277
- return await query.executeTakeFirst() ?? null;
278
- }
279
- case "channels": {
280
- let query = db.selectFrom("channels").selectAll();
281
- if (where !== void 0) query = query.where(where);
282
- return await query.executeTakeFirst() ?? null;
283
- }
284
- case "releases": {
285
- let query = db.selectFrom("releases").selectAll();
286
- if (where !== void 0) query = query.where(where);
287
- return await query.executeTakeFirst() ?? null;
288
- }
289
- case "release_catalogs": {
290
- let query = db.selectFrom("release_catalogs").selectAll();
291
- if (where !== void 0) query = query.where(where);
292
- return await query.executeTakeFirst() ?? null;
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
- }
299
- }
300
- },
301
- findMany: (input) => findManyPostgresRows(db, input, buildWhere(input.where)),
302
- async insertChannel({ row }) {
303
- const inserted = await db.insertInto("channels").values(row).onConflict((conflict) => conflict.column("name").doNothing()).returningAll().executeTakeFirst();
304
- if (inserted !== void 0) return {
305
- row: inserted,
306
- inserted: true
307
- };
308
- return {
309
- row: await db.selectFrom("channels").selectAll().where("name", "=", row.name).executeTakeFirstOrThrow(),
310
- inserted: false
311
- };
312
- },
313
- async deleteChannel({ id }) {
314
- try {
315
- return await db.deleteFrom("channels").where("id", "=", id).returning("id").executeTakeFirst() === void 0 ? {
316
- deleted: false,
317
- reason: "not_found"
318
- } : { deleted: true };
319
- } catch (error) {
320
- if (isForeignKeyViolation(error)) return {
321
- deleted: false,
322
- reason: "not_empty"
323
- };
324
- throw error;
325
- }
326
- },
327
- transaction: (callback) => db.transaction().execute((transaction) => callback(createPostgresImplementation(transaction))),
328
- dispose: () => db.destroy()
329
- });
31
+ /**
32
+ * Hot Updater's database on PostgreSQL, through a `pg` pool or the given
33
+ * Kysely dialect. Before first use, apply `sql/bundles.sql`, core's tables,
34
+ * and add the tables of the server's plugins with `hot-updater db migrate`;
35
+ * the schema fence refuses a database without their settings rows.
36
+ */
330
37
  const postgres = (config) => {
331
38
  const { dialect, ...poolConfig } = config;
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);
337
- return (0, _hot_updater_plugin_core.createDatabasePlugin)({
338
- name: "postgres",
339
- models: adapter.models,
340
- commit: adapter.commit,
341
- dispose: adapter.dispose
342
- });
39
+ const db = new kysely.Kysely({ dialect: dialect ?? new kysely.PostgresDialect({ pool: new Pool(poolConfig) }) });
40
+ return {
41
+ ...(0, _hot_updater_plugin_core.createEngineDatabase)({
42
+ name: "postgres",
43
+ adapter: (0, _hot_updater_plugin_core.createSqlAdapter)({ executor: (0, _hot_updater_server_adapters_kysely.kyselyExecutor)(db, "postgresql") })
44
+ }),
45
+ dispose: () => db.destroy()
46
+ };
343
47
  };
344
48
  //#endregion
345
49
  exports.postgres = postgres;
package/dist/index.d.cts CHANGED
@@ -1,9 +1,16 @@
1
+ import { EngineDatabase } from "@hot-updater/plugin-core";
1
2
  import { Dialect } from "kysely";
2
3
  import { PoolConfig } from "pg";
3
4
  //#region src/postgres.d.ts
4
5
  type PostgresConfig = PoolConfig & {
5
6
  readonly dialect?: Dialect;
6
7
  };
7
- declare const postgres: (config: PostgresConfig) => import("@hot-updater/plugin-core").DatabasePlugin;
8
+ /**
9
+ * Hot Updater's database on PostgreSQL, through a `pg` pool or the given
10
+ * Kysely dialect. Before first use, apply `sql/bundles.sql`, core's tables,
11
+ * and add the tables of the server's plugins with `hot-updater db migrate`;
12
+ * the schema fence refuses a database without their settings rows.
13
+ */
14
+ declare const postgres: (config: PostgresConfig) => EngineDatabase;
8
15
  //#endregion
9
16
  export { PostgresConfig, postgres };
package/dist/index.d.mts CHANGED
@@ -1,9 +1,16 @@
1
+ import { EngineDatabase } from "@hot-updater/plugin-core";
1
2
  import { Dialect } from "kysely";
2
3
  import { PoolConfig } from "pg";
3
4
  //#region src/postgres.d.ts
4
5
  type PostgresConfig = PoolConfig & {
5
6
  readonly dialect?: Dialect;
6
7
  };
7
- declare const postgres: (config: PostgresConfig) => import("@hot-updater/plugin-core").DatabasePlugin;
8
+ /**
9
+ * Hot Updater's database on PostgreSQL, through a `pg` pool or the given
10
+ * Kysely dialect. Before first use, apply `sql/bundles.sql`, core's tables,
11
+ * and add the tables of the server's plugins with `hot-updater db migrate`;
12
+ * the schema fence refuses a database without their settings rows.
13
+ */
14
+ declare const postgres: (config: PostgresConfig) => EngineDatabase;
8
15
  //#endregion
9
16
  export { PostgresConfig, postgres };
package/dist/index.mjs CHANGED
@@ -1,321 +1,25 @@
1
- import { createDatabasePlugin } from "@hot-updater/plugin-core";
2
- import { DatabaseRowReferencedError, createDatabasePluginAdapter } from "@hot-updater/plugin-core/internal";
3
- import { Kysely, PostgresDialect, sql } from "kysely";
1
+ import { createEngineDatabase, createSqlAdapter } from "@hot-updater/plugin-core";
2
+ import { kyselyExecutor } from "@hot-updater/server/adapters/kysely";
3
+ import { Kysely, PostgresDialect } from "kysely";
4
4
  import pg from "pg";
5
- //#region src/postgresQuery.ts
6
- const applyOrder = (order, clause) => {
7
- const directed = clause.direction === "asc" ? order.asc() : order.desc();
8
- return clause.nulls === void 0 ? directed : clause.nulls === "first" ? directed.nullsFirst() : directed.nullsLast();
9
- };
10
- const countPostgresRows = async (db, input, where) => {
11
- switch (input.model) {
12
- case "bundles": {
13
- let rows = db.selectFrom("bundles");
14
- if (where !== void 0) rows = rows.where(where);
15
- const query = input.distinct === void 0 ? rows.select(({ fn }) => fn.countAll().as("count")) : db.selectFrom(rows.select(input.distinct).distinct().as("distinct_rows")).select(({ fn }) => fn.countAll().as("count"));
16
- return Number((await query.executeTakeFirstOrThrow()).count);
17
- }
18
- case "bundle_patches": {
19
- let rows = db.selectFrom("bundle_patches");
20
- if (where !== void 0) rows = rows.where(where);
21
- const query = input.distinct === void 0 ? rows.select(({ fn }) => fn.countAll().as("count")) : db.selectFrom(rows.select(input.distinct).distinct().as("distinct_rows")).select(({ fn }) => fn.countAll().as("count"));
22
- return Number((await query.executeTakeFirstOrThrow()).count);
23
- }
24
- case "releases": {
25
- let rows = db.selectFrom("releases");
26
- if (where !== void 0) rows = rows.where(where);
27
- const query = rows.select(({ fn }) => fn.countAll().as("count"));
28
- return Number((await query.executeTakeFirstOrThrow()).count);
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
- }
37
- }
38
- };
39
- const findManyPostgresRows = async (db, input, where) => {
40
- switch (input.model) {
41
- case "bundles": {
42
- let query = db.selectFrom("bundles").selectAll();
43
- if (where !== void 0) query = query.where(where);
44
- if (input.distinctOn !== void 0) query = query.distinctOn(input.distinctOn.fields);
45
- for (const clause of input.orderBy ?? []) query = query.orderBy(clause.field, (order) => applyOrder(order, clause));
46
- return query.limit(input.limit).offset(input.offset).execute();
47
- }
48
- case "release_catalogs": {
49
- let query = db.selectFrom("release_catalogs").selectAll();
50
- if (where !== void 0) query = query.where(where);
51
- for (const clause of input.orderBy ?? []) query = query.orderBy(clause.field, (order) => applyOrder(order, clause));
52
- return query.limit(input.limit).offset(input.offset).execute();
53
- }
54
- case "bundle_events": {
55
- let query = db.selectFrom("bundle_events").selectAll();
56
- if (where !== void 0) query = query.where(where);
57
- if (input.distinctOn !== void 0) query = query.distinctOn(input.distinctOn.fields);
58
- for (const clause of input.orderBy ?? []) query = query.orderBy(clause.field, (order) => applyOrder(order, clause));
59
- return query.limit(input.limit).offset(input.offset).execute();
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
- }
67
- case "channels": {
68
- let query = db.selectFrom("channels").selectAll();
69
- if (where !== void 0) query = query.where(where);
70
- for (const clause of input.orderBy ?? []) query = query.orderBy(clause.field, (order) => applyOrder(order, clause));
71
- return query.limit(input.limit).offset(input.offset).execute();
72
- }
73
- case "api_keys": {
74
- let query = db.selectFrom("api_keys").selectAll();
75
- if (where !== void 0) query = query.where(where);
76
- if (input.distinctOn !== void 0) query = query.distinctOn(input.distinctOn.fields);
77
- for (const clause of input.orderBy ?? []) query = query.orderBy(clause.field, (order) => applyOrder(order, clause));
78
- return query.limit(input.limit).offset(input.offset).execute();
79
- }
80
- case "bundle_patches": {
81
- let query = db.selectFrom("bundle_patches").selectAll();
82
- if (where !== void 0) query = query.where(where);
83
- if (input.distinctOn !== void 0) query = query.distinctOn(input.distinctOn.fields);
84
- for (const clause of input.orderBy ?? []) query = query.orderBy(clause.field, (order) => applyOrder(order, clause));
85
- return query.limit(input.limit).offset(input.offset).execute();
86
- }
87
- case "releases": {
88
- let query = db.selectFrom("releases").selectAll();
89
- if (where !== void 0) query = query.where(where);
90
- for (const clause of input.orderBy ?? []) query = query.orderBy(clause.field, (order) => applyOrder(order, clause));
91
- return query.limit(input.limit).offset(input.offset).execute();
92
- }
93
- }
94
- };
95
- //#endregion
96
5
  //#region src/postgres.ts
97
6
  const { Pool } = pg;
98
- var InvalidPostgresPredicateError = class extends Error {
99
- name = "InvalidPostgresPredicateError";
100
- };
101
- const isForeignKeyViolation = (error) => typeof error === "object" && error !== null && Reflect.get(error, "code") === "23503";
102
- const escapeLikePattern = (value) => value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
103
- const stringPredicate = (condition, operator) => {
104
- if (typeof condition.value !== "string") throw new InvalidPostgresPredicateError();
105
- const column = sql.ref(condition.field);
106
- const literal = escapeLikePattern(condition.value);
107
- const pattern = operator === "contains" ? `%${literal}%` : operator === "starts_with" ? `${literal}%` : `%${literal}`;
108
- return "mode" in condition && condition.mode === "insensitive" ? sql`lower(${column}) like lower(${pattern}) escape '\\'` : sql`${column} like ${pattern} escape '\\'`;
109
- };
110
- const predicate = (condition) => {
111
- const column = sql.ref(condition.field);
112
- const operator = condition.operator ?? "eq";
113
- switch (operator) {
114
- case "eq":
115
- case "ne":
116
- if (condition.value === null) return operator === "eq" ? sql`${column} is null` : sql`${column} is not null`;
117
- if ("mode" in condition && condition.mode === "insensitive") return operator === "eq" ? sql`lower(${column}) = lower(${condition.value})` : sql`lower(${column}) <> lower(${condition.value})`;
118
- return operator === "eq" ? sql`${column} = ${condition.value}` : sql`${column} <> ${condition.value}`;
119
- case "gt": return sql`${column} > ${condition.value}`;
120
- case "gte": return sql`${column} >= ${condition.value}`;
121
- case "lt": return sql`${column} < ${condition.value}`;
122
- case "lte": return sql`${column} <= ${condition.value}`;
123
- case "in":
124
- case "not_in":
125
- if (!Array.isArray(condition.value)) throw new InvalidPostgresPredicateError();
126
- if (condition.value.length === 0) return sql`${operator === "not_in"}`;
127
- return operator === "in" ? sql`${column} in (${sql.join(condition.value)})` : sql`${column} not in (${sql.join(condition.value)})`;
128
- case "contains":
129
- case "starts_with":
130
- case "ends_with": return stringPredicate(condition, operator);
131
- }
132
- };
133
- const buildWhere = (where) => {
134
- const [first, ...rest] = where ?? [];
135
- if (first === void 0) return;
136
- let expression = predicate(first);
137
- for (const condition of rest) {
138
- const next = predicate(condition);
139
- expression = condition.connector === "OR" ? sql`(${expression} or ${next})` : sql`(${expression} and ${next})`;
140
- }
141
- return expression;
142
- };
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
- },
160
- async create(input) {
161
- switch (input.model) {
162
- case "bundles": return db.insertInto("bundles").values(input.data).returningAll().executeTakeFirstOrThrow();
163
- case "bundle_patches": return db.insertInto("bundle_patches").values(input.data).returningAll().executeTakeFirstOrThrow();
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
- }
169
- case "releases": return db.insertInto("releases").values(input.data).returningAll().executeTakeFirstOrThrow();
170
- case "release_catalogs": return db.insertInto("release_catalogs").values(input.data).returningAll().executeTakeFirstOrThrow();
171
- case "channels": {
172
- const query = db.insertInto("channels").values(input.data);
173
- return await (input.onConflict === "ignore" ? query.onConflict((conflict) => conflict.column("name").doNothing()) : query).returningAll().executeTakeFirst() ?? await db.selectFrom("channels").selectAll().where("name", "=", input.data.name).executeTakeFirstOrThrow();
174
- }
175
- case "api_keys": return await (input.onConflict === "ignore" ? db.insertInto("api_keys").values(input.data).onConflict((conflict) => conflict.column("hash").doNothing()) : db.insertInto("api_keys").values(input.data)).returningAll().executeTakeFirst() ?? await db.selectFrom("api_keys").selectAll().where("hash", "=", input.data.hash).executeTakeFirstOrThrow();
176
- }
177
- },
178
- async update(input) {
179
- const where = buildWhere(input.where);
180
- if (input.model === "api_keys") {
181
- let query = db.updateTable("api_keys").set(input.update);
182
- if (where !== void 0) query = query.where(where);
183
- return await query.returningAll().executeTakeFirst() ?? null;
184
- }
185
- if (input.model === "releases") {
186
- let query = db.updateTable("releases").set(input.update);
187
- if (where !== void 0) query = query.where(where);
188
- return await query.returningAll().executeTakeFirst() ?? null;
189
- }
190
- if (input.model === "release_catalogs") {
191
- let query = db.updateTable("release_catalogs").set(input.update);
192
- if (where !== void 0) query = query.where(where);
193
- return await query.returningAll().executeTakeFirst() ?? null;
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
- }
200
- let query = db.updateTable("bundles").set(input.update);
201
- if (where !== void 0) query = query.where(where);
202
- return await query.returningAll().executeTakeFirst() ?? null;
203
- },
204
- async delete(input) {
205
- const where = buildWhere(input.where);
206
- switch (input.model) {
207
- case "bundles": {
208
- let query = db.deleteFrom("bundles");
209
- if (where !== void 0) query = query.where(where);
210
- await query.execute();
211
- return;
212
- }
213
- case "bundle_patches": {
214
- let query = db.deleteFrom("bundle_patches");
215
- if (where !== void 0) query = query.where(where);
216
- await query.execute();
217
- return;
218
- }
219
- case "channels": {
220
- let query = db.deleteFrom("channels");
221
- if (where !== void 0) query = query.where(where);
222
- try {
223
- await query.execute();
224
- } catch (error) {
225
- if (isForeignKeyViolation(error)) throw new DatabaseRowReferencedError();
226
- throw error;
227
- }
228
- }
229
- case "releases": {
230
- let query = db.deleteFrom("releases");
231
- if (where !== void 0) query = query.where(where);
232
- await query.execute();
233
- }
234
- }
235
- },
236
- count: (input) => countPostgresRows(db, input, buildWhere(input.where)),
237
- async findOne(input) {
238
- const where = buildWhere(input.where);
239
- switch (input.model) {
240
- case "bundles": {
241
- let query = db.selectFrom("bundles").selectAll();
242
- if (where !== void 0) query = query.where(where);
243
- return await query.executeTakeFirst() ?? null;
244
- }
245
- case "api_keys": {
246
- let query = db.selectFrom("api_keys").selectAll();
247
- if (where !== void 0) query = query.where(where);
248
- return await query.executeTakeFirst() ?? null;
249
- }
250
- case "bundle_patches": {
251
- let query = db.selectFrom("bundle_patches").selectAll();
252
- if (where !== void 0) query = query.where(where);
253
- return await query.executeTakeFirst() ?? null;
254
- }
255
- case "channels": {
256
- let query = db.selectFrom("channels").selectAll();
257
- if (where !== void 0) query = query.where(where);
258
- return await query.executeTakeFirst() ?? null;
259
- }
260
- case "releases": {
261
- let query = db.selectFrom("releases").selectAll();
262
- if (where !== void 0) query = query.where(where);
263
- return await query.executeTakeFirst() ?? null;
264
- }
265
- case "release_catalogs": {
266
- let query = db.selectFrom("release_catalogs").selectAll();
267
- if (where !== void 0) query = query.where(where);
268
- return await query.executeTakeFirst() ?? null;
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
- }
275
- }
276
- },
277
- findMany: (input) => findManyPostgresRows(db, input, buildWhere(input.where)),
278
- async insertChannel({ row }) {
279
- const inserted = await db.insertInto("channels").values(row).onConflict((conflict) => conflict.column("name").doNothing()).returningAll().executeTakeFirst();
280
- if (inserted !== void 0) return {
281
- row: inserted,
282
- inserted: true
283
- };
284
- return {
285
- row: await db.selectFrom("channels").selectAll().where("name", "=", row.name).executeTakeFirstOrThrow(),
286
- inserted: false
287
- };
288
- },
289
- async deleteChannel({ id }) {
290
- try {
291
- return await db.deleteFrom("channels").where("id", "=", id).returning("id").executeTakeFirst() === void 0 ? {
292
- deleted: false,
293
- reason: "not_found"
294
- } : { deleted: true };
295
- } catch (error) {
296
- if (isForeignKeyViolation(error)) return {
297
- deleted: false,
298
- reason: "not_empty"
299
- };
300
- throw error;
301
- }
302
- },
303
- transaction: (callback) => db.transaction().execute((transaction) => callback(createPostgresImplementation(transaction))),
304
- dispose: () => db.destroy()
305
- });
7
+ /**
8
+ * Hot Updater's database on PostgreSQL, through a `pg` pool or the given
9
+ * Kysely dialect. Before first use, apply `sql/bundles.sql`, core's tables,
10
+ * and add the tables of the server's plugins with `hot-updater db migrate`;
11
+ * the schema fence refuses a database without their settings rows.
12
+ */
306
13
  const postgres = (config) => {
307
14
  const { dialect, ...poolConfig } = config;
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);
313
- return createDatabasePlugin({
314
- name: "postgres",
315
- models: adapter.models,
316
- commit: adapter.commit,
317
- dispose: adapter.dispose
318
- });
15
+ const db = new Kysely({ dialect: dialect ?? new PostgresDialect({ pool: new Pool(poolConfig) }) });
16
+ return {
17
+ ...createEngineDatabase({
18
+ name: "postgres",
19
+ adapter: createSqlAdapter({ executor: kyselyExecutor(db, "postgresql") })
20
+ }),
21
+ dispose: () => db.destroy()
22
+ };
319
23
  };
320
24
  //#endregion
321
25
  export { postgres };
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.2",
4
+ "version": "1.0.0-rc.21",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.mjs",
@@ -34,8 +34,9 @@
34
34
  "dependencies": {
35
35
  "kysely": "0.28.17",
36
36
  "pg": "8.13.1",
37
- "@hot-updater/core": "1.0.0-rc.0",
38
- "@hot-updater/plugin-core": "1.0.0-rc.2"
37
+ "@hot-updater/protocol": "1.0.0-rc.21",
38
+ "@hot-updater/plugin-core": "1.0.0-rc.21",
39
+ "@hot-updater/server": "1.0.0-rc.21"
39
40
  },
40
41
  "devDependencies": {
41
42
  "@electric-sql/pglite": "0.4.1",
@@ -44,7 +45,7 @@
44
45
  "camelcase-keys": "^9.1.3",
45
46
  "kysely-pglite-dialect": "1.2.0",
46
47
  "pg-minify": "^1.6.5",
47
- "@hot-updater/test-utils": "1.0.0-rc.2"
48
+ "@hot-updater/test-utils": "1.0.0-rc.21"
48
49
  },
49
50
  "scripts": {
50
51
  "build": "tsdown",
package/sql/bundles.sql CHANGED
@@ -1,199 +1,41 @@
1
1
  -- HotUpdater.schema
2
2
 
3
- create table channels (
4
- id varchar(255) primary key not null,
5
- name varchar(255) not null
6
- );
7
-
8
- create table bundles (
9
- id uuid primary key not null,
10
- platform text not null,
11
- file_hash text not null,
12
- git_commit_hash text,
13
- storage_uri text not null,
14
- archive_byte_size double precision not null,
15
- metadata json not null default '{}'::json,
16
- manifest_storage_uri text,
17
- manifest_file_hash text,
18
- asset_base_storage_uri text
19
- );
20
-
21
- create table bundle_patches (
22
- id varchar(255) primary key not null,
23
- bundle_id uuid not null,
24
- base_bundle_id uuid not null,
25
- base_file_hash text not null,
26
- patch_file_hash text not null,
27
- patch_storage_uri text not null,
28
- byte_size double precision not null,
29
- order_index integer not null default 0
30
- );
31
-
32
- create table releases (
33
- id uuid primary key not null,
34
- revision integer not null,
35
- scope_key varchar(2048) not null,
36
- channel_id varchar(255) not null,
37
- platform text not null,
38
- kind text not null,
39
- bundle_id uuid,
40
- strategy text not null,
41
- target_app_version text,
42
- fingerprint_hash text,
43
- enabled boolean not null,
44
- should_force_update boolean not null,
45
- message text,
46
- rollout_cohort_count integer not null default 1000,
47
- target_cohorts json not null default '[]'::json,
48
- operation text not null,
49
- source_release_id uuid,
50
- created_at_ms double precision not null,
51
- updated_at_ms double precision not null
52
- );
53
-
54
- create table release_catalogs (
55
- scope_key varchar(2048) primary key not null,
56
- catalog_id varchar(255) not null,
57
- strategy text not null,
58
- channel_id varchar(255) not null,
59
- channel_key varchar(1400) not null,
60
- platform text not null,
61
- fingerprint_hash text,
62
- generation double precision not null,
63
- payload text not null,
64
- catalog_hash varchar(71) not null,
65
- byte_size integer not null,
66
- is_tombstone boolean not null,
67
- updated_at_ms double precision not null
68
- );
69
-
70
- create table bundle_events (
71
- id uuid primary key not null,
72
- type text not null,
73
- install_id text collate "C" not null,
74
- user_id text collate "C",
75
- username text,
76
- from_release_id uuid,
77
- from_bundle_id uuid,
78
- to_release_id uuid,
79
- to_bundle_id uuid not null,
80
- platform text collate "C" not null,
81
- app_version text not null,
82
- channel text collate "C" not null,
83
- cohort text not null,
84
- update_strategy text,
85
- fingerprint_hash text,
86
- sdk_version text,
87
- received_at_ms double precision not null
88
- );
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
-
104
- create table api_keys (
105
- id varchar(255) primary key not null,
106
- hash text not null,
107
- name text not null,
108
- prefix text not null,
109
- role text not null,
110
- created_at_ms double precision not null,
111
- revoked_at_ms double precision
112
- );
113
-
114
- create table private_hot_updater_settings (
115
- key varchar(255) primary key not null,
116
- value text not null default '1.0.0'
117
- );
118
-
119
- create unique index channels_name_key on channels(name);
120
- create index bundle_patches_bundle_id_idx on bundle_patches(bundle_id);
121
- create index bundle_patches_base_bundle_id_idx on bundle_patches(base_bundle_id);
122
- create index releases_scope_order_idx on releases(scope_key, id);
123
- create index releases_channel_platform_order_idx on releases(channel_id, platform, id);
124
- create index releases_bundle_id_idx on releases(bundle_id);
125
- create index releases_fingerprint_hash_idx on releases(fingerprint_hash);
126
- create index releases_enabled_idx on releases(enabled);
127
- create index release_catalogs_channel_idx on release_catalogs(channel_id);
128
- create index bundle_events_received_at_idx on bundle_events(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);
136
- create unique index api_keys_hash_key on api_keys(hash);
137
- create index api_keys_created_at_idx on api_keys(created_at_ms, id);
138
-
139
- alter table channels add constraint channels_id_length_check
140
- check (char_length(id) between 1 and 255);
141
- alter table channels add constraint channels_name_length_check
142
- check (char_length(name) between 1 and 255);
143
- alter table bundles add constraint bundles_archive_byte_size_check
144
- check (archive_byte_size >= 0 and archive_byte_size <= 9007199254740991);
145
- alter table bundle_patches add constraint bundle_patches_byte_size_check
146
- check (byte_size >= 0 and byte_size <= 9007199254740991);
147
- alter table releases add constraint releases_revision_check
148
- check (revision >= 1);
149
- alter table releases add constraint releases_kind_bundle_check
150
- check ((kind = 'BUNDLE' and bundle_id is not null) or (kind = 'EMBEDDED' and bundle_id is null));
151
- alter table releases add constraint releases_strategy_target_check
152
- check ((strategy = 'APP_VERSION' and target_app_version is not null and fingerprint_hash is null) or (strategy = 'FINGERPRINT' and target_app_version is null and fingerprint_hash is not null));
153
- alter table releases add constraint releases_rollout_cohort_count_check
154
- check (rollout_cohort_count >= 0 and rollout_cohort_count <= 1000);
155
- alter table releases add constraint releases_operation_check
156
- check (operation in ('DEPLOY', 'PROMOTE', 'ROLLBACK'));
157
- alter table release_catalogs add constraint release_catalogs_strategy_target_check
158
- check ((strategy = 'APP_VERSION' and fingerprint_hash is null) or (strategy = 'FINGERPRINT' and fingerprint_hash is not null));
159
- alter table release_catalogs add constraint release_catalogs_generation_check
160
- check (generation >= 1 and generation <= 9007199254740991);
161
- alter table release_catalogs add constraint release_catalogs_byte_size_check
162
- check (byte_size >= 0 and byte_size <= 262144);
163
- alter table bundle_events add constraint bundle_events_type_check
164
- check (type in ('UPDATE_APPLIED', 'RECOVERED', 'RELEASE_ADOPTED', 'UNCHANGED'));
165
- alter table bundle_events add constraint bundle_events_platform_check
166
- check (platform in ('ios', 'android'));
167
- alter table bundle_events add constraint bundle_events_shape_check
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));
169
- alter table bundle_events add constraint bundle_events_received_at_check
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);
177
- alter table api_keys add constraint api_keys_role_check
178
- check (role = 'client');
179
- alter table api_keys add constraint api_keys_created_at_check
180
- check (created_at_ms >= 0);
181
- alter table api_keys add constraint api_keys_revoked_at_check
182
- check (revoked_at_ms is null or revoked_at_ms >= 0);
183
-
184
- alter table bundle_patches add constraint bundle_patches_bundle_id_fk
185
- foreign key (bundle_id) references bundles(id) on update restrict on delete cascade;
186
- alter table bundle_patches add constraint bundle_patches_base_bundle_id_fk
187
- foreign key (base_bundle_id) references bundles(id) on update restrict on delete cascade;
188
- alter table releases add constraint releases_channel_id_fk
189
- foreign key (channel_id) references channels(id) on update restrict on delete restrict;
190
- alter table releases add constraint releases_bundle_id_fk
191
- foreign key (bundle_id) references bundles(id) on update restrict on delete restrict;
192
- alter table releases add constraint releases_source_release_id_fk
193
- foreign key (source_release_id) references releases(id) on update restrict on delete set null;
194
- alter table release_catalogs add constraint release_catalogs_channel_id_fk
195
- foreign key (channel_id) references channels(id) on update restrict on delete restrict;
196
-
197
- insert into private_hot_updater_settings (key, value)
198
- values ('schema.core', '1.0.0')
199
- on conflict (key) do update set value = excluded.value;
3
+ CREATE TABLE IF NOT EXISTS "bundles" ("id" varchar(36) COLLATE "C" NOT NULL, "platform" varchar(16) COLLATE "C" NOT NULL, "git_commit_hash" varchar(255) COLLATE "C", "metadata" jsonb NOT NULL, "manifest_storage_uri" text COLLATE "C" NOT NULL, "manifest_file_hash" text COLLATE "C" NOT NULL, "asset_base_storage_uri" text COLLATE "C" NOT NULL, "_refs_bundle_patches_bundle_id" bigint NOT NULL DEFAULT 0, "_refs_bundle_patches_base_bundle_id" bigint NOT NULL DEFAULT 0, "_refs_releases_bundle_id" bigint NOT NULL DEFAULT 0, "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("id"));
4
+
5
+ CREATE INDEX IF NOT EXISTS "bundles_byPlatform" ON "bundles" ("platform", "id");
6
+
7
+ CREATE TABLE IF NOT EXISTS "bundle_patches" ("id" varchar(255) COLLATE "C" NOT NULL, "bundle_id" varchar(36) COLLATE "C" NOT NULL, "base_bundle_id" varchar(36) COLLATE "C" NOT NULL, "base_file_hash" text COLLATE "C" NOT NULL, "patch_file_hash" text COLLATE "C" NOT NULL, "patch_storage_uri" text COLLATE "C" NOT NULL, "byte_size" bigint NOT NULL, "order_index" bigint NOT NULL, "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("id"));
8
+
9
+ CREATE UNIQUE INDEX IF NOT EXISTS "bundle_patches_pair" ON "bundle_patches" ("bundle_id", "base_bundle_id");
10
+
11
+ CREATE INDEX IF NOT EXISTS "bundle_patches_byBundle" ON "bundle_patches" ("bundle_id", "order_index", "id");
12
+
13
+ CREATE INDEX IF NOT EXISTS "bundle_patches_byBase" ON "bundle_patches" ("base_bundle_id", "bundle_id", "id");
14
+
15
+ CREATE TABLE IF NOT EXISTS "releases" ("id" varchar(36) COLLATE "C" NOT NULL, "revision" bigint NOT NULL, "scope_key" varchar(2048) COLLATE "C" NOT NULL, "channel_id" varchar(1408) COLLATE "C" NOT NULL, "platform" varchar(16) COLLATE "C" NOT NULL, "kind" varchar(16) COLLATE "C" NOT NULL, "bundle_id" varchar(36) COLLATE "C", "strategy" varchar(16) COLLATE "C" NOT NULL, "target_app_version" text COLLATE "C", "fingerprint_hash" varchar(255) COLLATE "C", "enabled" boolean NOT NULL, "should_force_update" boolean NOT NULL, "message" text COLLATE "C", "rollout_cohort_count" bigint NOT NULL, "target_cohorts" jsonb NOT NULL, "operation" varchar(16) COLLATE "C" NOT NULL, "source_release_id" varchar(36) COLLATE "C", "created_at_ms" bigint NOT NULL, "updated_at_ms" bigint NOT NULL, "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("id"));
16
+
17
+ CREATE INDEX IF NOT EXISTS "releases_byScope" ON "releases" ("scope_key", "id");
18
+
19
+ CREATE INDEX IF NOT EXISTS "releases_byScopeEnabled" ON "releases" ("scope_key", "enabled", "id");
20
+
21
+ CREATE INDEX IF NOT EXISTS "releases_byChannelPlatform" ON "releases" ("channel_id", "platform", "id");
22
+
23
+ CREATE INDEX IF NOT EXISTS "releases_byChannelPlatformEnabled" ON "releases" ("channel_id", "platform", "enabled", "id");
24
+
25
+ CREATE INDEX IF NOT EXISTS "releases_byBundle" ON "releases" ("bundle_id", "id");
26
+
27
+ CREATE TABLE IF NOT EXISTS "release_catalogs" ("scope_key" varchar(2048) COLLATE "C" NOT NULL, "catalog_id" varchar(255) COLLATE "C" NOT NULL, "strategy" varchar(16) COLLATE "C" NOT NULL, "channel_id" varchar(1408) COLLATE "C" NOT NULL, "channel_key" varchar(1400) COLLATE "C" NOT NULL, "platform" varchar(16) COLLATE "C" NOT NULL, "fingerprint_hash" varchar(255) COLLATE "C", "generation" bigint NOT NULL, "payload" text COLLATE "C" NOT NULL, "catalog_hash" varchar(71) COLLATE "C" NOT NULL, "byte_size" bigint NOT NULL, "is_tombstone" boolean NOT NULL, "updated_at_ms" bigint NOT NULL, "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("scope_key"));
28
+
29
+ CREATE TABLE IF NOT EXISTS "channels" ("id" varchar(1408) COLLATE "C" NOT NULL, "name" varchar(255) COLLATE "C" NOT NULL, "_refs_releases_channel_id" bigint NOT NULL DEFAULT 0, "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("id"));
30
+
31
+ CREATE INDEX IF NOT EXISTS "channels_all" ON "channels" ("name", "id");
32
+
33
+ CREATE UNIQUE INDEX IF NOT EXISTS "channels_name" ON "channels" ("name");
34
+
35
+ CREATE TABLE IF NOT EXISTS "bundle_totals" ("platform_key" varchar(16) COLLATE "C" NOT NULL, "_shard" bigint NOT NULL, "bundles" bigint NOT NULL, "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("platform_key", "_shard"));
36
+
37
+ CREATE TABLE IF NOT EXISTS "private_hot_updater_settings" ("key" varchar(255) COLLATE "C" NOT NULL, "value" varchar(255) COLLATE "C" NOT NULL, "_v" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("key"));
38
+
39
+ INSERT INTO "private_hot_updater_settings" ("key", "value", "_v") VALUES ('schema.engine', '1', 0) ON CONFLICT ("key") DO UPDATE SET "value" = excluded."value";
40
+
41
+ INSERT INTO "private_hot_updater_settings" ("key", "value", "_v") VALUES ('schema.core', '1.0.0', 0) ON CONFLICT ("key") DO UPDATE SET "value" = excluded."value";