@hot-updater/plugin-core 0.36.6 → 1.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/dist/assetStorageLayout.cjs +36 -18
  2. package/dist/assetStorageLayout.d.cts +21 -6
  3. package/dist/assetStorageLayout.d.mts +21 -6
  4. package/dist/assetStorageLayout.mjs +36 -18
  5. package/dist/bundleStorageLayout.cjs +2 -3
  6. package/dist/bundleStorageLayout.mjs +2 -3
  7. package/dist/contentAddressedAssets.cjs +5 -0
  8. package/dist/contentAddressedAssets.d.cts +2 -1
  9. package/dist/contentAddressedAssets.d.mts +2 -1
  10. package/dist/contentAddressedAssets.mjs +5 -1
  11. package/dist/createDatabasePlugin.cjs +775 -284
  12. package/dist/createDatabasePlugin.d.cts +20 -64
  13. package/dist/createDatabasePlugin.d.mts +20 -64
  14. package/dist/createDatabasePlugin.mjs +773 -284
  15. package/dist/createStorageKeyBuilder.cjs +9 -2
  16. package/dist/createStorageKeyBuilder.mjs +9 -2
  17. package/dist/createStoragePlugin.cjs +6 -145
  18. package/dist/createStoragePlugin.d.cts +7 -52
  19. package/dist/createStoragePlugin.d.mts +7 -52
  20. package/dist/createStoragePlugin.mjs +5 -143
  21. package/dist/databaseClient.cjs +112 -0
  22. package/dist/databaseClient.d.cts +33 -0
  23. package/dist/databaseClient.d.mts +33 -0
  24. package/dist/databaseClient.mjs +110 -0
  25. package/dist/databaseClientReads.cjs +113 -0
  26. package/dist/databaseClientReads.mjs +112 -0
  27. package/dist/databaseClientUpdates.cjs +39 -0
  28. package/dist/databaseClientUpdates.d.cts +11 -0
  29. package/dist/databaseClientUpdates.d.mts +11 -0
  30. package/dist/databaseClientUpdates.mjs +36 -0
  31. package/dist/databaseJsonValue.cjs +37 -0
  32. package/dist/databaseJsonValue.d.cts +5 -0
  33. package/dist/databaseJsonValue.d.mts +5 -0
  34. package/dist/databaseJsonValue.mjs +37 -0
  35. package/dist/databaseMetadata.cjs +13 -0
  36. package/dist/databaseMetadata.mjs +12 -0
  37. package/dist/databasePluginCrud.cjs +86 -0
  38. package/dist/databasePluginCrud.d.cts +1 -0
  39. package/dist/databasePluginCrud.d.mts +1 -0
  40. package/dist/databasePluginCrud.mjs +86 -0
  41. package/dist/databasePluginCrudValidation.d.cts +1 -0
  42. package/dist/databasePluginCrudValidation.d.mts +1 -0
  43. package/dist/databasePluginCrudValidationErrors.cjs +10 -0
  44. package/dist/databasePluginCrudValidationErrors.d.cts +9 -0
  45. package/dist/databasePluginCrudValidationErrors.d.mts +9 -0
  46. package/dist/databasePluginCrudValidationErrors.mjs +10 -0
  47. package/dist/databasePluginCrudValidationFields.cjs +291 -0
  48. package/dist/databasePluginCrudValidationFields.d.cts +1 -0
  49. package/dist/databasePluginCrudValidationFields.d.mts +1 -0
  50. package/dist/databasePluginCrudValidationFields.mjs +281 -0
  51. package/dist/databasePluginCrudValidationMutations.cjs +88 -0
  52. package/dist/databasePluginCrudValidationMutations.d.cts +1 -0
  53. package/dist/databasePluginCrudValidationMutations.d.mts +1 -0
  54. package/dist/databasePluginCrudValidationMutations.mjs +82 -0
  55. package/dist/databasePluginCrudValidationQueries.cjs +103 -0
  56. package/dist/databasePluginCrudValidationQueries.d.cts +1 -0
  57. package/dist/databasePluginCrudValidationQueries.d.mts +1 -0
  58. package/dist/databasePluginCrudValidationQueries.mjs +97 -0
  59. package/dist/databasePluginCrudValidationRows.cjs +55 -0
  60. package/dist/databasePluginCrudValidationRows.d.cts +1 -0
  61. package/dist/databasePluginCrudValidationRows.d.mts +1 -0
  62. package/dist/databasePluginCrudValidationRows.mjs +53 -0
  63. package/dist/databasePluginTransaction.cjs +7 -0
  64. package/dist/databasePluginTransaction.mjs +7 -0
  65. package/dist/databaseRows.cjs +149 -0
  66. package/dist/databaseRows.d.cts +34 -0
  67. package/dist/databaseRows.d.mts +34 -0
  68. package/dist/databaseRows.mjs +141 -0
  69. package/dist/index.cjs +76 -24
  70. package/dist/index.d.cts +22 -12
  71. package/dist/index.d.mts +22 -12
  72. package/dist/index.mjs +21 -14
  73. package/dist/internal.cjs +6 -0
  74. package/dist/internal.d.cts +9 -0
  75. package/dist/internal.d.mts +9 -0
  76. package/dist/internal.mjs +3 -0
  77. package/dist/node_modules/.pnpm/verkit@0.3.2/node_modules/verkit/dist/index.cjs +345 -0
  78. package/dist/node_modules/.pnpm/verkit@0.3.2/node_modules/verkit/dist/index.mjs +341 -0
  79. package/dist/paginateBundles.d.cts +6 -4
  80. package/dist/paginateBundles.d.mts +6 -4
  81. package/dist/parseStorageUri.cjs +46 -30
  82. package/dist/parseStorageUri.d.cts +20 -17
  83. package/dist/parseStorageUri.d.mts +20 -17
  84. package/dist/parseStorageUri.mjs +46 -30
  85. package/dist/queryBundles.cjs +1 -7
  86. package/dist/queryBundles.d.cts +3 -1
  87. package/dist/queryBundles.d.mts +3 -1
  88. package/dist/queryBundles.mjs +1 -7
  89. package/dist/releaseCatalogCompiler.cjs +391 -0
  90. package/dist/releaseCatalogCompiler.d.cts +55 -0
  91. package/dist/releaseCatalogCompiler.d.mts +55 -0
  92. package/dist/releaseCatalogCompiler.mjs +386 -0
  93. package/dist/releaseCatalogMutation.cjs +303 -0
  94. package/dist/releaseCatalogMutation.d.cts +86 -0
  95. package/dist/releaseCatalogMutation.d.mts +86 -0
  96. package/dist/releaseCatalogMutation.mjs +297 -0
  97. package/dist/releaseManagement.cjs +231 -0
  98. package/dist/releaseManagement.d.cts +48 -0
  99. package/dist/releaseManagement.d.mts +48 -0
  100. package/dist/releaseManagement.mjs +224 -0
  101. package/dist/remoteBundleSigning.cjs +296 -0
  102. package/dist/remoteBundleSigning.d.cts +43 -0
  103. package/dist/remoteBundleSigning.d.mts +43 -0
  104. package/dist/remoteBundleSigning.mjs +291 -0
  105. package/dist/requestBundleCache.cjs +22 -0
  106. package/dist/requestBundleCache.d.cts +9 -0
  107. package/dist/requestBundleCache.d.mts +9 -0
  108. package/dist/requestBundleCache.mjs +22 -0
  109. package/dist/semverSatisfies.cjs +3 -4
  110. package/dist/semverSatisfies.mjs +1 -1
  111. package/dist/storageDownloadPath.cjs +48 -0
  112. package/dist/storageDownloadPath.d.cts +15 -0
  113. package/dist/storageDownloadPath.d.mts +15 -0
  114. package/dist/storageDownloadPath.mjs +46 -0
  115. package/dist/types/database.d.cts +4 -0
  116. package/dist/types/database.d.mts +4 -0
  117. package/dist/types/databaseFields.cjs +92 -0
  118. package/dist/types/databaseFields.d.cts +12 -0
  119. package/dist/types/databaseFields.d.mts +12 -0
  120. package/dist/types/databaseFields.mjs +92 -0
  121. package/dist/types/databaseOperations.d.cts +173 -0
  122. package/dist/types/databaseOperations.d.mts +173 -0
  123. package/dist/types/databasePlugin.d.cts +240 -0
  124. package/dist/types/databasePlugin.d.mts +240 -0
  125. package/dist/types/databaseQuery.d.cts +47 -0
  126. package/dist/types/databaseQuery.d.mts +47 -0
  127. package/dist/types/databaseRows.d.cts +120 -0
  128. package/dist/types/databaseRows.d.mts +120 -0
  129. package/dist/types/index.d.cts +120 -162
  130. package/dist/types/index.d.mts +120 -162
  131. package/dist/types/internal.d.cts +7 -0
  132. package/dist/types/internal.d.mts +7 -0
  133. package/dist/types/public.d.cts +3 -0
  134. package/dist/types/public.d.mts +3 -0
  135. package/dist/uuidv7.cjs +9 -0
  136. package/dist/uuidv7.d.cts +4 -1
  137. package/dist/uuidv7.d.mts +4 -1
  138. package/dist/uuidv7.mjs +8 -1
  139. package/package.json +10 -5
  140. package/dist/bundleUnitOfWork.cjs +0 -158
  141. package/dist/bundleUnitOfWork.mjs +0 -158
  142. package/dist/bundleUnitOfWorkStore.cjs +0 -15
  143. package/dist/bundleUnitOfWorkStore.mjs +0 -15
  144. package/dist/createBlobDatabasePlugin.cjs +0 -382
  145. package/dist/createBlobDatabasePlugin.d.cts +0 -29
  146. package/dist/createBlobDatabasePlugin.d.mts +0 -29
  147. package/dist/createBlobDatabasePlugin.mjs +0 -381
  148. package/dist/legacyAssetStorageLayout.cjs +0 -12
  149. package/dist/legacyAssetStorageLayout.mjs +0 -12
  150. package/dist/requestUpdateBundleState.cjs +0 -26
  151. package/dist/requestUpdateBundleState.d.cts +0 -12
  152. package/dist/requestUpdateBundleState.d.mts +0 -12
  153. package/dist/requestUpdateBundleState.mjs +0 -24
  154. package/dist/resolveUpdateInfoFromBundles.cjs +0 -14
  155. package/dist/resolveUpdateInfoFromBundles.d.cts +0 -16
  156. package/dist/resolveUpdateInfoFromBundles.d.mts +0 -16
  157. package/dist/resolveUpdateInfoFromBundles.mjs +0 -13
  158. package/dist/storageProfile.cjs +0 -15
  159. package/dist/storageProfile.d.cts +0 -9
  160. package/dist/storageProfile.d.mts +0 -9
  161. package/dist/storageProfile.mjs +0 -12
@@ -1,307 +1,796 @@
1
- import { calculatePagination } from "./calculatePagination.mjs";
2
- import { BundleUnitOfWork } from "./bundleUnitOfWork.mjs";
3
- import { getRequestBundleUnitOfWork } from "./bundleUnitOfWorkStore.mjs";
4
- import { mergeWith } from "es-toolkit";
1
+ import { DatabasePluginInputError } from "./databasePluginCrudValidationErrors.mjs";
2
+ import { isChannelText, isRecord } from "./databasePluginCrudValidationFields.mjs";
3
+ import { validateCreateData, validateResult } from "./databasePluginCrudValidationRows.mjs";
4
+ import { validateApiKeyUpdateData, validateBundleUpdateData, validateReleaseUpdateData } from "./databasePluginCrudValidationMutations.mjs";
5
+ import { createDatabasePluginCrud } from "./databasePluginCrud.mjs";
6
+ import { createTransactionDatabasePlugin } from "./databasePluginTransaction.mjs";
5
7
  //#region src/createDatabasePlugin.ts
6
- const REPLACE_ON_UPDATE_KEYS = ["patches", "targetCohorts"];
7
- const DEFAULT_DESC_ORDER = {
8
- field: "id",
9
- direction: "desc"
8
+ const PAGE_SIZE = 100;
9
+ const compareChannelRows = (left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0;
10
+ var DatabaseAtomicCommitUnsupportedError = class extends Error {
11
+ name = "DatabaseAtomicCommitUnsupportedError";
12
+ constructor(pluginName) {
13
+ super(`Database plugin "${pluginName}" cannot atomically commit changes across models.`);
14
+ this.pluginName = pluginName;
15
+ }
10
16
  };
11
- function normalizePage(value) {
12
- if (!Number.isInteger(value) || value === void 0 || value < 1) return;
13
- return value;
14
- }
15
- function mergeBundleUpdate(baseBundle, patch) {
16
- return mergeWith({ ...baseBundle }, patch, (_targetValue, sourceValue, key) => {
17
- if (REPLACE_ON_UPDATE_KEYS.includes(key)) return sourceValue;
17
+ /**
18
+ * Internal provider signal for a delete rejected by a live reference.
19
+ *
20
+ * Provider implementations throw this after translating their native
21
+ * foreign-key or reference-condition error. The public database contract
22
+ * observes only the indexed `referenced` commit conflict.
23
+ */
24
+ var DatabaseRowReferencedError = class extends Error {
25
+ name = "DatabaseRowReferencedError";
26
+ constructor() {
27
+ super("The database row is still referenced.");
28
+ }
29
+ };
30
+ var DatabaseCommitConflictError = class extends Error {
31
+ name = "DatabaseCommitConflictError";
32
+ constructor(result) {
33
+ super("Database commit precondition failed.");
34
+ this.result = result;
35
+ }
36
+ };
37
+ const toBundleWhere = (where) => {
38
+ if (!where) return [];
39
+ const filters = [];
40
+ if (where.platform !== void 0) filters.push({
41
+ field: "platform",
42
+ value: where.platform
18
43
  });
19
- }
20
- function mergeIdFilter(base, patch) {
21
- return {
22
- ...base,
23
- ...patch
24
- };
25
- }
26
- function mergeWhereWithIdFilter(where, idFilter) {
27
- return {
28
- ...where,
29
- id: mergeIdFilter(where?.id, idFilter)
30
- };
31
- }
32
- function buildCursorPageQuery(where, cursor, orderBy) {
33
- const direction = orderBy.direction;
34
- if (cursor.after) return {
35
- reverseData: false,
36
- where: mergeWhereWithIdFilter(where, { [direction === "desc" ? "lt" : "gt"]: cursor.after }),
37
- orderBy
38
- };
39
- if (cursor.before) return {
40
- reverseData: true,
41
- where: mergeWhereWithIdFilter(where, { [direction === "desc" ? "gt" : "lt"]: cursor.before }),
42
- orderBy: {
43
- field: orderBy.field,
44
- direction: direction === "desc" ? "asc" : "desc"
44
+ if (where.id?.eq !== void 0) filters.push({
45
+ field: "id",
46
+ value: where.id.eq
47
+ });
48
+ if (where.id?.gt !== void 0) filters.push({
49
+ field: "id",
50
+ operator: "gt",
51
+ value: where.id.gt
52
+ });
53
+ if (where.id?.gte !== void 0) filters.push({
54
+ field: "id",
55
+ operator: "gte",
56
+ value: where.id.gte
57
+ });
58
+ if (where.id?.lt !== void 0) filters.push({
59
+ field: "id",
60
+ operator: "lt",
61
+ value: where.id.lt
62
+ });
63
+ if (where.id?.lte !== void 0) filters.push({
64
+ field: "id",
65
+ operator: "lte",
66
+ value: where.id.lte
67
+ });
68
+ if (where.id?.in !== void 0) filters.push({
69
+ field: "id",
70
+ operator: "in",
71
+ value: where.id.in
72
+ });
73
+ return filters;
74
+ };
75
+ const assertReleaseReferences = async (database, row) => {
76
+ if (await database.findOne({
77
+ model: "channels",
78
+ where: [{
79
+ field: "id",
80
+ value: row.channel_id
81
+ }]
82
+ }) === null) throw new DatabasePluginInputError("invalid-data");
83
+ if (row.bundle_id !== null) {
84
+ const bundle = await database.findOne({
85
+ model: "bundles",
86
+ where: [{
87
+ field: "id",
88
+ value: row.bundle_id
89
+ }]
90
+ });
91
+ if (bundle === null || bundle.platform !== row.platform) throw new DatabasePluginInputError("invalid-data");
92
+ }
93
+ };
94
+ const applyChange = async (database, change, changeIndex) => {
95
+ switch (change.model) {
96
+ case "bundles": switch (change.operation) {
97
+ case "insert":
98
+ await database.create({
99
+ model: "bundles",
100
+ data: change.row
101
+ });
102
+ return;
103
+ case "update":
104
+ if (await database.update({
105
+ model: "bundles",
106
+ where: [{
107
+ field: "id",
108
+ value: change.where.id
109
+ }],
110
+ update: change.update
111
+ }) === null) throw new DatabaseCommitConflictError({
112
+ committed: false,
113
+ conflict: {
114
+ changeIndex,
115
+ reason: "not_found"
116
+ }
117
+ });
118
+ return;
119
+ case "delete":
120
+ if (await database.count({
121
+ model: "releases",
122
+ where: [{
123
+ field: "bundle_id",
124
+ value: change.where.id
125
+ }]
126
+ }) > 0) throw new DatabaseCommitConflictError({
127
+ committed: false,
128
+ conflict: {
129
+ changeIndex,
130
+ reason: "referenced"
131
+ }
132
+ });
133
+ await database.delete({
134
+ model: "bundles",
135
+ where: [{
136
+ field: "id",
137
+ value: change.where.id
138
+ }]
139
+ });
140
+ return;
45
141
  }
46
- };
47
- return {
48
- reverseData: false,
49
- where: where ?? {},
50
- orderBy
51
- };
52
- }
53
- function buildCountBeforeWhere(where, firstBundleId, orderBy) {
54
- return mergeWhereWithIdFilter(where, { [orderBy.direction === "desc" ? "gt" : "lt"]: firstBundleId });
142
+ case "releases": switch (change.operation) {
143
+ case "insert":
144
+ await assertReleaseReferences(database, change.row);
145
+ await database.create({
146
+ model: "releases",
147
+ data: change.row
148
+ });
149
+ return;
150
+ case "update":
151
+ if (await database.update({
152
+ model: "releases",
153
+ where: [{
154
+ field: "id",
155
+ value: change.where.id
156
+ }],
157
+ update: change.update
158
+ }) === null) throw new DatabaseCommitConflictError({
159
+ committed: false,
160
+ conflict: {
161
+ changeIndex,
162
+ reason: "not_found"
163
+ }
164
+ });
165
+ return;
166
+ case "delete":
167
+ await database.delete({
168
+ model: "releases",
169
+ where: [{
170
+ field: "id",
171
+ value: change.where.id
172
+ }]
173
+ });
174
+ return;
175
+ }
176
+ case "releaseCatalogs":
177
+ if (await database.findOne({
178
+ model: "release_catalogs",
179
+ where: [{
180
+ field: "scope_key",
181
+ value: change.row.scope_key
182
+ }]
183
+ }) === null) await database.create({
184
+ model: "release_catalogs",
185
+ data: change.row
186
+ });
187
+ else {
188
+ const { scope_key: _scopeKey, ...update } = change.row;
189
+ await database.update({
190
+ model: "release_catalogs",
191
+ where: [{
192
+ field: "scope_key",
193
+ value: change.row.scope_key
194
+ }],
195
+ update
196
+ });
197
+ }
198
+ return;
199
+ case "bundlePatches": switch (change.operation) {
200
+ case "insert":
201
+ await database.create({
202
+ model: "bundle_patches",
203
+ data: change.row
204
+ });
205
+ return;
206
+ case "delete":
207
+ await database.delete({
208
+ model: "bundle_patches",
209
+ where: [{
210
+ field: "bundle_id",
211
+ value: change.where.bundleId
212
+ }]
213
+ });
214
+ return;
215
+ }
216
+ case "channels": switch (change.operation) {
217
+ case "insert":
218
+ await database.create({
219
+ model: "channels",
220
+ data: change.row,
221
+ onConflict: change.onConflict
222
+ });
223
+ return;
224
+ case "delete":
225
+ if (await database.count({
226
+ model: "releases",
227
+ where: [{
228
+ field: "channel_id",
229
+ value: change.where.id
230
+ }]
231
+ }) > 0) throw new DatabaseCommitConflictError({
232
+ committed: false,
233
+ conflict: {
234
+ changeIndex,
235
+ reason: "referenced"
236
+ }
237
+ });
238
+ try {
239
+ await database.delete({
240
+ model: "channels",
241
+ where: [{
242
+ field: "id",
243
+ value: change.where.id
244
+ }]
245
+ });
246
+ } catch (error) {
247
+ if (error instanceof DatabaseRowReferencedError) throw new DatabaseCommitConflictError({
248
+ committed: false,
249
+ conflict: {
250
+ changeIndex,
251
+ reason: "referenced"
252
+ }
253
+ });
254
+ throw error;
255
+ }
256
+ return;
257
+ }
258
+ case "analytics":
259
+ await database.create({
260
+ model: "bundle_events",
261
+ data: change.row
262
+ });
263
+ return;
264
+ case "apiKeys": switch (change.operation) {
265
+ case "insert":
266
+ await database.create({
267
+ model: "api_keys",
268
+ data: change.row,
269
+ onConflict: change.onConflict
270
+ });
271
+ return;
272
+ case "update":
273
+ if (await database.update({
274
+ model: "api_keys",
275
+ where: [{
276
+ field: "id",
277
+ value: change.where.id
278
+ }],
279
+ update: { revoked_at_ms: change.update.revokedAtMs }
280
+ }) === null) throw new DatabaseCommitConflictError({
281
+ committed: false,
282
+ conflict: {
283
+ changeIndex,
284
+ reason: "not_found"
285
+ }
286
+ });
287
+ return;
288
+ }
289
+ }
290
+ };
291
+ const expectationVersion = async (database, expectation) => {
292
+ if (expectation.model === "releases") return (await database.findOne({
293
+ model: "releases",
294
+ where: [{
295
+ field: "id",
296
+ value: expectation.id
297
+ }],
298
+ select: ["revision"]
299
+ }))?.revision ?? null;
300
+ return (await database.findOne({
301
+ model: "release_catalogs",
302
+ where: [{
303
+ field: "scope_key",
304
+ value: expectation.scopeKey
305
+ }],
306
+ select: ["generation"]
307
+ }))?.generation ?? null;
308
+ };
309
+ const applyExpectations = async (database, expectations) => {
310
+ for (const expectation of expectations) {
311
+ const expectedVersion = expectation.model === "releases" ? expectation.revision : expectation.generation;
312
+ const actualVersion = await expectationVersion(database, expectation);
313
+ if (actualVersion !== expectedVersion) throw new DatabaseCommitConflictError({
314
+ committed: false,
315
+ conflict: {
316
+ actualVersion,
317
+ changeIndex: -1,
318
+ expectedVersion,
319
+ key: expectation.model === "releases" ? expectation.id : expectation.scopeKey,
320
+ model: expectation.model,
321
+ reason: "version_conflict"
322
+ }
323
+ });
324
+ }
325
+ };
326
+ const applyChanges = async (database, input) => {
327
+ await applyExpectations(database, input.expectations ?? []);
328
+ for (const [changeIndex, change] of input.changes.entries()) await applyChange(database, change, changeIndex);
329
+ return { committed: true };
330
+ };
331
+ const hasOnlyKeys = (value, keys) => {
332
+ return Reflect.ownKeys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
333
+ };
334
+ const validateWhere = (where, field, validateValue = (value) => typeof value === "string") => {
335
+ if (!isRecord(where) || !hasOnlyKeys(where, [field]) || !validateValue(Reflect.get(where, field))) throw new DatabasePluginInputError("invalid-data");
336
+ };
337
+ const validateDatabaseChange = (change) => {
338
+ if (!isRecord(change)) throw new DatabasePluginInputError("invalid-data");
339
+ switch (change.model) {
340
+ case "bundles": switch (change.operation) {
341
+ case "insert":
342
+ if (!hasOnlyKeys(change, [
343
+ "model",
344
+ "operation",
345
+ "row"
346
+ ])) throw new DatabasePluginInputError("invalid-data");
347
+ validateCreateData("bundles", change.row);
348
+ return;
349
+ case "update":
350
+ if (!hasOnlyKeys(change, [
351
+ "model",
352
+ "operation",
353
+ "where",
354
+ "update"
355
+ ])) throw new DatabasePluginInputError("invalid-data");
356
+ validateWhere(change.where, "id");
357
+ validateBundleUpdateData(change.update);
358
+ return;
359
+ case "delete":
360
+ if (!hasOnlyKeys(change, [
361
+ "model",
362
+ "operation",
363
+ "where"
364
+ ])) throw new DatabasePluginInputError("invalid-data");
365
+ validateWhere(change.where, "id");
366
+ return;
367
+ default: throw new DatabasePluginInputError("invalid-operation");
368
+ }
369
+ case "bundlePatches": switch (change.operation) {
370
+ case "insert":
371
+ if (!hasOnlyKeys(change, [
372
+ "model",
373
+ "operation",
374
+ "row"
375
+ ])) throw new DatabasePluginInputError("invalid-data");
376
+ validateCreateData("bundle_patches", change.row);
377
+ return;
378
+ case "delete":
379
+ if (!hasOnlyKeys(change, [
380
+ "model",
381
+ "operation",
382
+ "where"
383
+ ])) throw new DatabasePluginInputError("invalid-data");
384
+ validateWhere(change.where, "bundleId");
385
+ return;
386
+ default: throw new DatabasePluginInputError("invalid-operation");
387
+ }
388
+ case "releases": switch (change.operation) {
389
+ case "insert":
390
+ if (!hasOnlyKeys(change, [
391
+ "model",
392
+ "operation",
393
+ "row"
394
+ ])) throw new DatabasePluginInputError("invalid-data");
395
+ validateCreateData("releases", change.row);
396
+ return;
397
+ case "update":
398
+ if (!hasOnlyKeys(change, [
399
+ "model",
400
+ "operation",
401
+ "where",
402
+ "update"
403
+ ])) throw new DatabasePluginInputError("invalid-data");
404
+ validateWhere(change.where, "id");
405
+ validateReleaseUpdateData(change.update);
406
+ return;
407
+ case "delete":
408
+ if (!hasOnlyKeys(change, [
409
+ "model",
410
+ "operation",
411
+ "where"
412
+ ])) throw new DatabasePluginInputError("invalid-data");
413
+ validateWhere(change.where, "id");
414
+ return;
415
+ default: throw new DatabasePluginInputError("invalid-operation");
416
+ }
417
+ case "releaseCatalogs":
418
+ if (change.operation !== "put" || !hasOnlyKeys(change, [
419
+ "model",
420
+ "operation",
421
+ "row"
422
+ ])) throw new DatabasePluginInputError("invalid-operation");
423
+ validateCreateData("release_catalogs", change.row);
424
+ return;
425
+ case "channels": switch (change.operation) {
426
+ case "insert":
427
+ if (!hasOnlyKeys(change, [
428
+ "model",
429
+ "operation",
430
+ "row",
431
+ "onConflict"
432
+ ]) || change.onConflict !== "ignore") throw new DatabasePluginInputError("invalid-operation");
433
+ validateCreateData("channels", change.row);
434
+ return;
435
+ case "delete":
436
+ if (!hasOnlyKeys(change, [
437
+ "model",
438
+ "operation",
439
+ "where"
440
+ ])) throw new DatabasePluginInputError("invalid-data");
441
+ validateWhere(change.where, "id", isChannelText);
442
+ return;
443
+ default: throw new DatabasePluginInputError("invalid-operation");
444
+ }
445
+ case "analytics":
446
+ if (change.operation !== "insert" || !hasOnlyKeys(change, [
447
+ "model",
448
+ "operation",
449
+ "row"
450
+ ])) throw new DatabasePluginInputError("invalid-operation");
451
+ validateCreateData("bundle_events", change.row);
452
+ return;
453
+ case "apiKeys": switch (change.operation) {
454
+ case "insert":
455
+ if (!hasOnlyKeys(change, [
456
+ "model",
457
+ "operation",
458
+ "row",
459
+ "onConflict"
460
+ ]) || change.onConflict !== "ignore") throw new DatabasePluginInputError("invalid-operation");
461
+ validateCreateData("api_keys", change.row);
462
+ return;
463
+ case "update":
464
+ if (!hasOnlyKeys(change, [
465
+ "model",
466
+ "operation",
467
+ "where",
468
+ "update"
469
+ ])) throw new DatabasePluginInputError("invalid-data");
470
+ validateWhere(change.where, "id");
471
+ if (!isRecord(change.update)) throw new DatabasePluginInputError("invalid-data");
472
+ if (!hasOnlyKeys(change.update, ["revokedAtMs"])) throw new DatabasePluginInputError("invalid-data");
473
+ validateApiKeyUpdateData({ revoked_at_ms: change.update.revokedAtMs });
474
+ return;
475
+ default: throw new DatabasePluginInputError("invalid-operation");
476
+ }
477
+ default: throw new DatabasePluginInputError("invalid-model");
478
+ }
479
+ };
480
+ const validateDatabaseCommitExpectation = (expectation) => {
481
+ if (!isRecord(expectation)) throw new DatabasePluginInputError("invalid-data");
482
+ if (expectation.model === "releases") {
483
+ if (!hasOnlyKeys(expectation, [
484
+ "model",
485
+ "id",
486
+ "revision"
487
+ ]) || typeof expectation.id !== "string" || !(expectation.revision === null || typeof expectation.revision === "number" && Number.isSafeInteger(expectation.revision) && expectation.revision >= 1)) throw new DatabasePluginInputError("invalid-data");
488
+ return;
489
+ }
490
+ if (expectation.model === "releaseCatalogs") {
491
+ if (!hasOnlyKeys(expectation, [
492
+ "model",
493
+ "scopeKey",
494
+ "generation"
495
+ ]) || typeof expectation.scopeKey !== "string" || !(expectation.generation === null || typeof expectation.generation === "number" && Number.isSafeInteger(expectation.generation) && expectation.generation >= 1)) throw new DatabasePluginInputError("invalid-data");
496
+ return;
497
+ }
498
+ throw new DatabasePluginInputError("invalid-model");
499
+ };
500
+ function validateDatabaseCommit(input) {
501
+ if (!isRecord(input) || !Array.isArray(input.changes) || (Object.hasOwn(input, "expectations") ? !hasOnlyKeys(input, ["changes", "expectations"]) || !Array.isArray(input.expectations) : !hasOnlyKeys(input, ["changes"]))) throw new DatabasePluginInputError("invalid-data");
502
+ input.changes.forEach(validateDatabaseChange);
503
+ if (Array.isArray(input.expectations)) input.expectations.forEach(validateDatabaseCommitExpectation);
55
504
  }
56
- function createPaginatedResult(total, limit, startIndex, data) {
57
- const pagination = calculatePagination(total, {
58
- limit,
59
- offset: startIndex
60
- });
61
- const nextCursor = data.length > 0 && startIndex + data.length < total ? data.at(-1)?.id : void 0;
62
- const previousCursor = data.length > 0 && startIndex > 0 ? data[0]?.id : void 0;
63
- return {
64
- data,
65
- pagination: {
66
- ...pagination,
67
- ...nextCursor ? { nextCursor } : {},
68
- ...previousCursor ? { previousCursor } : {}
505
+ const validateChannelInsertResult = (input, result) => {
506
+ validateResult("channels", result.row, void 0);
507
+ if (typeof result.inserted !== "boolean" || result.row.name !== input.row.name || result.inserted && result.row.id !== input.row.id) throw new DatabasePluginInputError("invalid-result");
508
+ };
509
+ const validateChannelDeleteResult = (result) => {
510
+ if (result.deleted) return;
511
+ if (result.reason !== "not_found" && result.reason !== "not_empty") throw new DatabasePluginInputError("invalid-result");
512
+ };
513
+ const createDatabasePluginAdapter = (name, implementation) => {
514
+ const crud = createDatabasePluginCrud(implementation);
515
+ const transaction = implementation.transaction;
516
+ const executeCommit = implementation.commit ? implementation.commit : async (input) => {
517
+ if (transaction) try {
518
+ return await transaction((database) => applyChanges(createTransactionDatabasePlugin(database), input));
519
+ } catch (error) {
520
+ if (error instanceof DatabaseCommitConflictError) return error.result;
521
+ throw error;
522
+ }
523
+ if ((input.expectations?.length ?? 0) > 0 || input.changes.length > 1 || input.changes.some((change) => change.model === "channels" && change.operation === "delete")) throw new DatabaseAtomicCommitUnsupportedError(name);
524
+ try {
525
+ return await applyChanges(crud, input);
526
+ } catch (error) {
527
+ if (error instanceof DatabaseCommitConflictError) return error.result;
528
+ throw error;
69
529
  }
70
530
  };
71
- }
72
- function expandLimitForUnitOfWork(options, unitOfWork) {
73
- const extraLimit = unitOfWork.listFetchExtraCount();
74
- if (extraLimit === 0) return options;
75
- return {
76
- ...options,
77
- limit: options.limit + extraLimit
531
+ const commit = async (input) => {
532
+ validateDatabaseCommit(input);
533
+ return executeCommit(input);
78
534
  };
79
- }
80
- function adjustPaginationTotal(pagination, options) {
81
- if (options.totalDelta === 0) return pagination;
82
- const total = Math.max(0, pagination.total + options.totalDelta);
83
- const hasPreviousPage = pagination.currentPage > 1;
84
- const hasNextPage = pagination.currentPage * options.limit < total;
535
+ const findApiKeyByHash = (database, hash) => database.findOne({
536
+ model: "api_keys",
537
+ where: [{
538
+ field: "hash",
539
+ value: hash
540
+ }]
541
+ });
85
542
  return {
86
- ...pagination,
87
- total,
88
- hasNextPage,
89
- hasPreviousPage,
90
- totalPages: total === 0 ? 0 : Math.ceil(total / options.limit)
91
- };
92
- }
93
- /**
94
- * Creates a database plugin with lazy initialization and automatic hook execution.
95
- *
96
- * This factory function abstracts the double currying pattern used by all database plugins,
97
- * ensuring consistent lazy initialization behavior across different database providers.
98
- * Hooks are automatically executed at appropriate times without requiring manual invocation.
99
- *
100
- * @param options - Configuration options for the database plugin
101
- * @returns A double-curried function that lazily initializes the database plugin
102
- *
103
- * @example
104
- * ```typescript
105
- * export const postgres = createDatabasePlugin<PostgresConfig>({
106
- * name: "postgres",
107
- * factory: (config) => {
108
- * const db = new Kysely(config);
109
- * return {
110
- * async getBundleById(bundleId) { ... },
111
- * async getBundles(options) { ... },
112
- * async getChannels() { ... },
113
- * async commitBundle({ changedSets }) { ... }
114
- * };
115
- * }
116
- * });
117
- * ```
118
- */
119
- function createDatabasePlugin(options) {
120
- return (config, hooks) => {
121
- let cachedMethods = null;
122
- const getMethods = () => {
123
- if (!cachedMethods) cachedMethods = options.factory(config);
124
- return cachedMethods;
125
- };
126
- return () => {
127
- const instanceMutationUnitOfWork = new BundleUnitOfWork();
128
- const getRequestUnitOfWork = (context) => {
129
- return getRequestBundleUnitOfWork(context);
130
- };
131
- const getMutationUnitOfWork = (context) => {
132
- return getRequestUnitOfWork(context) ?? instanceMutationUnitOfWork;
133
- };
134
- const runGetBundles = async (options, context) => {
135
- if (context === void 0) return getMethods().getBundles(options);
136
- return getMethods().getBundles(options, context);
137
- };
138
- const getBundlesWithLegacyCursorFallback = async (options, context) => {
139
- const orderBy = options.orderBy ?? DEFAULT_DESC_ORDER;
140
- const baseWhere = options.where;
141
- const total = (await runGetBundles({
142
- where: baseWhere,
143
- limit: 1,
144
- offset: 0,
145
- orderBy
146
- }, context)).pagination.total;
147
- if (!options.cursor?.after && !options.cursor?.before) {
148
- const firstPage = await runGetBundles({
149
- where: baseWhere,
150
- limit: options.limit,
543
+ models: {
544
+ bundles: {
545
+ findById: (id) => crud.findOne({
546
+ model: "bundles",
547
+ where: [{
548
+ field: "id",
549
+ value: id
550
+ }]
551
+ }),
552
+ findMany: (query) => crud.findMany({
553
+ model: "bundles",
554
+ where: toBundleWhere(query.where),
555
+ limit: query.limit,
556
+ offset: query.offset,
557
+ orderBy: [query.orderBy]
558
+ }),
559
+ count: (where) => crud.count({
560
+ model: "bundles",
561
+ where: toBundleWhere(where)
562
+ })
563
+ },
564
+ bundlePatches: { async findByBundleIds(bundleIds) {
565
+ if (bundleIds.length === 0) return [];
566
+ const rows = [];
567
+ for (let offset = 0;; offset += PAGE_SIZE) {
568
+ const page = await crud.findMany({
569
+ model: "bundle_patches",
570
+ where: [{
571
+ field: "bundle_id",
572
+ operator: "in",
573
+ value: bundleIds
574
+ }],
575
+ limit: PAGE_SIZE,
576
+ offset,
577
+ orderBy: [{
578
+ field: "id",
579
+ direction: "asc"
580
+ }]
581
+ });
582
+ rows.push(...page);
583
+ if (page.length < PAGE_SIZE) return rows;
584
+ }
585
+ } },
586
+ releases: {
587
+ findById: (id) => crud.findOne({
588
+ model: "releases",
589
+ where: [{
590
+ field: "id",
591
+ value: id
592
+ }]
593
+ }),
594
+ findMany(input) {
595
+ if (!Number.isSafeInteger(input.limit) || input.limit <= 0 || input.afterReleaseId !== void 0 && input.beforeReleaseId !== void 0) throw new DatabasePluginInputError("invalid-query");
596
+ const query = crud.findMany({
597
+ model: "releases",
598
+ where: [
599
+ ...input.afterReleaseId === void 0 ? [] : [{
600
+ field: "id",
601
+ operator: "gt",
602
+ value: input.afterReleaseId
603
+ }],
604
+ ...input.beforeReleaseId === void 0 ? [] : [{
605
+ field: "id",
606
+ operator: "lt",
607
+ value: input.beforeReleaseId
608
+ }],
609
+ ...input.bundleId === void 0 ? [] : [{
610
+ field: "bundle_id",
611
+ value: input.bundleId
612
+ }],
613
+ ...input.channelId === void 0 ? [] : [{
614
+ field: "channel_id",
615
+ value: input.channelId
616
+ }],
617
+ ...input.enabled === void 0 ? [] : [{
618
+ field: "enabled",
619
+ value: input.enabled
620
+ }],
621
+ ...input.platform === void 0 ? [] : [{
622
+ field: "platform",
623
+ value: input.platform
624
+ }],
625
+ ...input.targetAppVersion === void 0 ? [] : [{
626
+ field: "target_app_version",
627
+ value: input.targetAppVersion
628
+ }]
629
+ ],
630
+ limit: input.limit,
151
631
  offset: 0,
152
- orderBy
153
- }, context);
154
- return createPaginatedResult(total, options.limit, 0, firstPage.data);
632
+ orderBy: [{
633
+ field: "id",
634
+ direction: input.afterReleaseId === void 0 ? "desc" : "asc"
635
+ }]
636
+ });
637
+ return input.afterReleaseId === void 0 ? query : query.then((rows) => [...rows].reverse());
638
+ },
639
+ findManyByScope(input) {
640
+ if (input.consistency !== "strong" || !Number.isSafeInteger(input.limit) || input.limit <= 0) throw new DatabasePluginInputError("invalid-query");
641
+ return crud.findMany({
642
+ model: "releases",
643
+ where: [{
644
+ field: "scope_key",
645
+ value: input.scopeKey
646
+ }, ...input.afterReleaseId === void 0 ? [] : [{
647
+ field: "id",
648
+ operator: "gt",
649
+ value: input.afterReleaseId
650
+ }]],
651
+ limit: input.limit,
652
+ offset: 0,
653
+ orderBy: [{
654
+ field: "id",
655
+ direction: "asc"
656
+ }]
657
+ });
155
658
  }
156
- const { where, orderBy: queryOrderBy, reverseData } = buildCursorPageQuery(baseWhere, options.cursor, orderBy);
157
- const cursorPage = await runGetBundles({
158
- where,
159
- limit: options.limit,
160
- offset: 0,
161
- orderBy: queryOrderBy
162
- }, context);
163
- const data = reverseData ? cursorPage.data.slice().reverse() : cursorPage.data;
164
- if (data.length === 0) {
165
- const emptyStartIndex = options.cursor.after ? total : 0;
166
- return {
167
- data,
168
- pagination: {
169
- ...calculatePagination(total, {
170
- limit: options.limit,
171
- offset: emptyStartIndex
172
- }),
173
- ...options.cursor.after ? { previousCursor: options.cursor.after } : {},
174
- ...options.cursor.before ? { nextCursor: options.cursor.before } : {}
175
- }
176
- };
659
+ },
660
+ releaseCatalogs: {
661
+ findByScopeKey: (scopeKey) => crud.findOne({
662
+ model: "release_catalogs",
663
+ where: [{
664
+ field: "scope_key",
665
+ value: scopeKey
666
+ }]
667
+ }),
668
+ findMany(input) {
669
+ if (!Number.isSafeInteger(input.limit) || input.limit <= 0) throw new DatabasePluginInputError("invalid-query");
670
+ return crud.findMany({
671
+ model: "release_catalogs",
672
+ where: input.afterScopeKey === void 0 ? [] : [{
673
+ field: "scope_key",
674
+ operator: "gt",
675
+ value: input.afterScopeKey
676
+ }],
677
+ limit: input.limit,
678
+ offset: 0,
679
+ orderBy: [{
680
+ field: "scope_key",
681
+ direction: "asc"
682
+ }]
683
+ });
177
684
  }
178
- const firstBundleId = data[0].id;
179
- const countBeforeResult = await runGetBundles({
180
- where: buildCountBeforeWhere(baseWhere, firstBundleId, orderBy),
181
- limit: 1,
182
- offset: 0,
183
- orderBy
184
- }, context);
185
- return createPaginatedResult(total, options.limit, countBeforeResult.pagination.total, data);
186
- };
187
- const plugin = {
188
- name: options.name,
189
- async getBundleById(bundleId, context) {
190
- const requestUnitOfWork = getRequestUnitOfWork(context);
191
- if (requestUnitOfWork) return requestUnitOfWork.getById(bundleId, () => getMethods().getBundleById(bundleId, context));
192
- const pendingMutation = instanceMutationUnitOfWork.peekChanged(bundleId);
193
- if (pendingMutation.found) return pendingMutation.value;
194
- return getMethods().getBundleById(bundleId);
685
+ },
686
+ channels: {
687
+ async insert(input) {
688
+ if (input.onConflict !== "returnExisting") throw new DatabasePluginInputError("invalid-operation");
689
+ validateCreateData("channels", input.row);
690
+ const result = await implementation.insertChannel(input);
691
+ validateChannelInsertResult(input, result);
692
+ return result;
195
693
  },
196
- async getBundles(options, context) {
197
- if (typeof options === "object" && options !== null && "offset" in options && options.offset !== void 0) throw new Error("Bundle offset pagination has been removed. Use cursor.after or cursor.before instead.");
198
- const methods = getMethods();
199
- const requestUnitOfWork = getRequestUnitOfWork(context);
200
- const unitOfWork = requestUnitOfWork ?? instanceMutationUnitOfWork;
201
- const shouldOverlay = requestUnitOfWork !== null || instanceMutationUnitOfWork.hasChanges();
202
- const normalizedOptions = {
203
- ...options,
204
- page: normalizePage(options.page),
205
- orderBy: options.orderBy ?? DEFAULT_DESC_ORDER
206
- };
207
- const overlayResult = (result) => ({
208
- ...result,
209
- data: unitOfWork.overlayList(result.data, {
210
- limit: normalizedOptions.limit,
211
- orderBy: normalizedOptions.orderBy,
212
- where: normalizedOptions.where
213
- }),
214
- pagination: adjustPaginationTotal(result.pagination, {
215
- limit: normalizedOptions.limit,
216
- totalDelta: unitOfWork.totalDelta(normalizedOptions.where)
217
- })
218
- });
219
- if (normalizedOptions.page !== void 0) {
220
- const { page, ...pageOptions } = normalizedOptions;
221
- const requestedOffset = (page - 1) * normalizedOptions.limit;
222
- const fetchPageOptions = expandLimitForUnitOfWork(pageOptions, unitOfWork);
223
- let pageResult = await runGetBundles({
224
- ...fetchPageOptions,
225
- offset: requestedOffset
226
- }, context);
227
- const total = pageResult.pagination.total;
228
- const totalPages = total === 0 ? 0 : Math.ceil(total / normalizedOptions.limit);
229
- const maxOffset = totalPages === 0 ? 0 : (Math.max(1, totalPages) - 1) * normalizedOptions.limit;
230
- const resolvedOffset = Math.min(requestedOffset, maxOffset);
231
- if (resolvedOffset !== requestedOffset) pageResult = await runGetBundles({
232
- ...fetchPageOptions,
233
- offset: resolvedOffset
234
- }, context);
235
- const result = { ...createPaginatedResult(total, normalizedOptions.limit, resolvedOffset, pageResult.data) };
236
- return shouldOverlay ? overlayResult(result) : result;
237
- }
238
- if (methods.supportsCursorPagination) {
239
- const fetchOptions = expandLimitForUnitOfWork(normalizedOptions, unitOfWork);
240
- const result = context === void 0 ? await methods.getBundles(fetchOptions) : await methods.getBundles(fetchOptions, context);
241
- return shouldOverlay ? overlayResult(result) : result;
694
+ async list(_input) {
695
+ const channels = [];
696
+ for (let offset = 0;; offset += PAGE_SIZE) {
697
+ const page = await crud.findMany({
698
+ model: "channels",
699
+ orderBy: [{
700
+ field: "name",
701
+ direction: "asc"
702
+ }],
703
+ limit: PAGE_SIZE,
704
+ offset
705
+ });
706
+ channels.push(...page);
707
+ if (page.length < PAGE_SIZE) {
708
+ channels.sort(compareChannelRows);
709
+ return { channels };
710
+ }
242
711
  }
243
- const result = await getBundlesWithLegacyCursorFallback(shouldOverlay ? expandLimitForUnitOfWork(normalizedOptions, unitOfWork) : normalizedOptions, context);
244
- return shouldOverlay ? overlayResult(result) : result;
245
- },
246
- async getChannels(context) {
247
- if (context === void 0) return getMethods().getChannels();
248
- return getMethods().getChannels(context);
249
712
  },
250
- async onUnmount() {
251
- const methods = getMethods();
252
- if (methods.onUnmount) return methods.onUnmount();
253
- },
254
- async commitBundle(context) {
255
- const methods = getMethods();
256
- const unitOfWork = getMutationUnitOfWork(context);
257
- const params = { changedSets: unitOfWork.changedSets() };
258
- if (context === void 0) await methods.commitBundle(params);
259
- else await methods.commitBundle(params, context);
260
- unitOfWork.clear();
261
- await hooks?.onDatabaseUpdated?.();
262
- },
263
- async updateBundle(targetBundleId, newBundle, context) {
264
- const unitOfWork = getMutationUnitOfWork(context);
265
- const currentBundle = await unitOfWork.getById(targetBundleId, () => context === void 0 ? getMethods().getBundleById(targetBundleId) : getMethods().getBundleById(targetBundleId, context));
266
- if (!currentBundle) throw new Error("targetBundleId not found");
267
- const updatedBundle = mergeBundleUpdate(currentBundle, newBundle);
268
- unitOfWork.markUpdate(updatedBundle);
269
- },
270
- async appendBundle(inputBundle, context) {
271
- getMutationUnitOfWork(context).markInsert(inputBundle);
272
- },
273
- async deleteBundle(deleteBundle, context) {
274
- getMutationUnitOfWork(context).markDelete(deleteBundle);
713
+ async delete(input) {
714
+ if (!isChannelText(input.id)) throw new DatabasePluginInputError("invalid-data");
715
+ const result = await implementation.deleteChannel(input);
716
+ validateChannelDeleteResult(result);
717
+ return result;
275
718
  }
276
- };
277
- Object.defineProperty(plugin, "getUpdateInfo", {
278
- configurable: true,
279
- enumerable: true,
280
- get() {
281
- const directGetUpdateInfo = getMethods().getUpdateInfo;
282
- if (!directGetUpdateInfo) {
283
- Object.defineProperty(plugin, "getUpdateInfo", {
284
- configurable: true,
285
- enumerable: true,
286
- value: void 0
719
+ },
720
+ analytics: {
721
+ async append(row) {
722
+ await crud.create({
723
+ model: "bundle_events",
724
+ data: row
725
+ });
726
+ },
727
+ async scan(input) {
728
+ const rows = [];
729
+ for (let offset = 0; rows.length < input.limit; offset += PAGE_SIZE) {
730
+ const page = await crud.findMany({
731
+ model: "bundle_events",
732
+ where: [{
733
+ field: "received_at_ms",
734
+ operator: "lt",
735
+ value: input.beforeReceivedAtMs
736
+ }],
737
+ orderBy: [{
738
+ field: "received_at_ms",
739
+ direction: "asc"
740
+ }, {
741
+ field: "id",
742
+ direction: "asc"
743
+ }],
744
+ limit: PAGE_SIZE,
745
+ offset
287
746
  });
288
- return;
747
+ rows.push(...page.filter((row) => input.after === void 0 || row.received_at_ms > input.after.receivedAtMs || row.received_at_ms === input.after.receivedAtMs && row.id > input.after.id));
748
+ if (page.length < PAGE_SIZE) break;
289
749
  }
290
- const wrappedGetUpdateInfo = async (args, context) => {
291
- if (context === void 0) return directGetUpdateInfo(args);
292
- return directGetUpdateInfo(args, context);
750
+ return rows.slice(0, input.limit);
751
+ }
752
+ },
753
+ apiKeys: {
754
+ async create(row) {
755
+ const run = async (database) => {
756
+ if (await findApiKeyByHash(database, row.hash) !== null) return "existing";
757
+ await database.create({
758
+ model: "api_keys",
759
+ data: row
760
+ });
761
+ return "created";
293
762
  };
294
- Object.defineProperty(plugin, "getUpdateInfo", {
295
- configurable: true,
296
- enumerable: true,
297
- value: wrappedGetUpdateInfo
763
+ return transaction ? transaction((database) => run(createTransactionDatabasePlugin(database))) : run(crud);
764
+ },
765
+ findByHash: (hash) => findApiKeyByHash(crud, hash),
766
+ list: () => crud.findMany({
767
+ model: "api_keys",
768
+ orderBy: [{
769
+ field: "created_at_ms",
770
+ direction: "desc"
771
+ }, {
772
+ field: "id",
773
+ direction: "asc"
774
+ }],
775
+ limit: Number.MAX_SAFE_INTEGER,
776
+ offset: 0
777
+ }),
778
+ async revoke({ id, revokedAtMs }) {
779
+ return crud.update({
780
+ model: "api_keys",
781
+ where: [{
782
+ field: "id",
783
+ value: id
784
+ }],
785
+ update: { revoked_at_ms: revokedAtMs }
298
786
  });
299
- return wrappedGetUpdateInfo;
300
787
  }
301
- });
302
- return plugin;
303
- };
788
+ }
789
+ },
790
+ commit,
791
+ ...implementation.dispose ? { dispose: implementation.dispose } : {}
304
792
  };
305
- }
793
+ };
794
+ const createDatabasePlugin = (options) => ({ ...options });
306
795
  //#endregion
307
- export { createDatabasePlugin };
796
+ export { DatabaseAtomicCommitUnsupportedError, DatabaseRowReferencedError, createDatabasePlugin, createDatabasePluginAdapter };