@hot-updater/aws 1.1.4 → 1.2.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.
- package/dist/index.cjs +181 -0
- package/dist/index.d.cts +49 -2
- package/dist/index.d.mts +49 -2
- package/dist/index.mjs +181 -1
- package/dist/init/index.cjs +12 -1
- package/dist/init/index.mjs +12 -1
- package/dist/lambda/index.cjs +998 -487
- package/package.json +8 -7
package/dist/lambda/index.cjs
CHANGED
|
@@ -247,7 +247,7 @@ const bundleMetadataToRow = (metadata) => {
|
|
|
247
247
|
return metadata;
|
|
248
248
|
};
|
|
249
249
|
//#endregion
|
|
250
|
-
//#region ../../packages/protocol/dist/
|
|
250
|
+
//#region ../../packages/protocol/dist/uuid-kZvLA0Ly.mjs
|
|
251
251
|
const LETTER_DASH_NUMBER$2 = "[a-zA-Z0-9-]";
|
|
252
252
|
const NUMERIC_IDENTIFIER$2 = String.raw`0|[1-9]\d*`;
|
|
253
253
|
const NUMERIC_IDENTIFIER_LOOSE$2 = String.raw`\d+`;
|
|
@@ -360,128 +360,6 @@ function canonicalizeAppVersion(appVersion) {
|
|
|
360
360
|
const version = coerce$1(appVersion);
|
|
361
361
|
return version ? normalizeFull(version) : null;
|
|
362
362
|
}
|
|
363
|
-
const getManifestStorageUri = (bundle) => bundle.manifestStorageUri;
|
|
364
|
-
const getManifestFileHash = (bundle) => bundle.manifestFileHash;
|
|
365
|
-
const getAssetBaseStorageUri = (bundle) => bundle.assetBaseStorageUri;
|
|
366
|
-
const isBundlePatchArtifact = (value) => {
|
|
367
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
368
|
-
const candidate = value;
|
|
369
|
-
return typeof candidate.baseBundleId === "string" && typeof candidate.baseFileHash === "string" && typeof candidate.patchFileHash === "string" && typeof candidate.patchStorageUri === "string";
|
|
370
|
-
};
|
|
371
|
-
const readBundlePatchArray = (patches) => {
|
|
372
|
-
if (!Array.isArray(patches)) return [];
|
|
373
|
-
return patches.filter(isBundlePatchArtifact);
|
|
374
|
-
};
|
|
375
|
-
const getBundlePatches = (bundle) => {
|
|
376
|
-
const patches = readBundlePatchArray(bundle.patches);
|
|
377
|
-
const seenBaseBundleIds = /* @__PURE__ */ new Set();
|
|
378
|
-
return patches.filter((patch) => {
|
|
379
|
-
if (seenBaseBundleIds.has(patch.baseBundleId)) return false;
|
|
380
|
-
seenBaseBundleIds.add(patch.baseBundleId);
|
|
381
|
-
return true;
|
|
382
|
-
});
|
|
383
|
-
};
|
|
384
|
-
const getBundlePatch = (bundle, baseBundleId) => {
|
|
385
|
-
return getBundlePatches(bundle).find((patch) => patch.baseBundleId === baseBundleId) ?? null;
|
|
386
|
-
};
|
|
387
|
-
const NUMERIC_COHORT_SIZE = 1e3;
|
|
388
|
-
const DEFAULT_ROLLOUT_COHORT_COUNT = NUMERIC_COHORT_SIZE;
|
|
389
|
-
const CUSTOM_COHORT_PATTERN = /^[a-z0-9-]+$/;
|
|
390
|
-
function parseNumericCohortValue(cohort) {
|
|
391
|
-
if (!/^\d+$/.test(cohort)) return null;
|
|
392
|
-
const parsed = Number.parseInt(cohort, 10);
|
|
393
|
-
if (Number.isNaN(parsed) || parsed < 1 || parsed > 1e3) return null;
|
|
394
|
-
return parsed;
|
|
395
|
-
}
|
|
396
|
-
function positiveMod(value, modulus) {
|
|
397
|
-
return (value % modulus + modulus) % modulus;
|
|
398
|
-
}
|
|
399
|
-
function hashString(value) {
|
|
400
|
-
let hash = 0;
|
|
401
|
-
for (let i = 0; i < value.length; i++) {
|
|
402
|
-
const char = value.charCodeAt(i);
|
|
403
|
-
hash = (hash << 5) - hash + char;
|
|
404
|
-
hash |= 0;
|
|
405
|
-
}
|
|
406
|
-
return hash;
|
|
407
|
-
}
|
|
408
|
-
function gcd(a, b) {
|
|
409
|
-
let x = Math.abs(a);
|
|
410
|
-
let y = Math.abs(b);
|
|
411
|
-
while (y !== 0) {
|
|
412
|
-
const next = x % y;
|
|
413
|
-
x = y;
|
|
414
|
-
y = next;
|
|
415
|
-
}
|
|
416
|
-
return x;
|
|
417
|
-
}
|
|
418
|
-
function modularInverse(value, modulus) {
|
|
419
|
-
let t = 0;
|
|
420
|
-
let newT = 1;
|
|
421
|
-
let r = modulus;
|
|
422
|
-
let newR = positiveMod(value, modulus);
|
|
423
|
-
while (newR !== 0) {
|
|
424
|
-
const quotient = Math.floor(r / newR);
|
|
425
|
-
[t, newT] = [newT, t - quotient * newT];
|
|
426
|
-
[r, newR] = [newR, r - quotient * newR];
|
|
427
|
-
}
|
|
428
|
-
if (r > 1) throw new Error(`No modular inverse for ${value} mod ${modulus}`);
|
|
429
|
-
return positiveMod(t, modulus);
|
|
430
|
-
}
|
|
431
|
-
function getRolloutShuffleParameters(bundleId) {
|
|
432
|
-
let multiplier = positiveMod(hashString(`${bundleId}:multiplier`), 997);
|
|
433
|
-
if (multiplier === 0) multiplier = 1;
|
|
434
|
-
while (gcd(multiplier, NUMERIC_COHORT_SIZE) !== 1) {
|
|
435
|
-
multiplier = positiveMod(multiplier + 1, NUMERIC_COHORT_SIZE);
|
|
436
|
-
if (multiplier === 0) multiplier = 1;
|
|
437
|
-
}
|
|
438
|
-
const offset = positiveMod(hashString(`${bundleId}:offset`), NUMERIC_COHORT_SIZE);
|
|
439
|
-
return {
|
|
440
|
-
multiplier,
|
|
441
|
-
offset,
|
|
442
|
-
inverseMultiplier: modularInverse(multiplier, NUMERIC_COHORT_SIZE)
|
|
443
|
-
};
|
|
444
|
-
}
|
|
445
|
-
function normalizeRolloutCohortCount(rolloutCohortCount) {
|
|
446
|
-
if (rolloutCohortCount === null || rolloutCohortCount === void 0) return DEFAULT_ROLLOUT_COHORT_COUNT;
|
|
447
|
-
if (rolloutCohortCount <= 0) return 0;
|
|
448
|
-
if (rolloutCohortCount >= 1e3) return NUMERIC_COHORT_SIZE;
|
|
449
|
-
return Math.floor(rolloutCohortCount);
|
|
450
|
-
}
|
|
451
|
-
function normalizeCohortValue(cohort) {
|
|
452
|
-
const normalized = cohort.trim().toLowerCase();
|
|
453
|
-
const numericCohort = parseNumericCohortValue(normalized);
|
|
454
|
-
if (numericCohort !== null) return String(numericCohort);
|
|
455
|
-
return normalized;
|
|
456
|
-
}
|
|
457
|
-
function getNumericCohortValue(cohort) {
|
|
458
|
-
return parseNumericCohortValue(normalizeCohortValue(cohort));
|
|
459
|
-
}
|
|
460
|
-
function isNumericCohort(cohort) {
|
|
461
|
-
return getNumericCohortValue(cohort) !== null;
|
|
462
|
-
}
|
|
463
|
-
function isCustomCohort(cohort) {
|
|
464
|
-
const normalized = normalizeCohortValue(cohort);
|
|
465
|
-
return normalized.length > 0 && normalized.length <= 64 && !/^\d+$/.test(normalized) && CUSTOM_COHORT_PATTERN.test(normalized);
|
|
466
|
-
}
|
|
467
|
-
function isValidCohort(cohort) {
|
|
468
|
-
const normalized = normalizeCohortValue(cohort);
|
|
469
|
-
return isNumericCohort(normalized) || isCustomCohort(normalized);
|
|
470
|
-
}
|
|
471
|
-
function getNumericCohortRolloutPosition(bundleId, cohortValue) {
|
|
472
|
-
if (cohortValue < 1 || cohortValue > 1e3) throw new Error(`Invalid numeric cohort: ${cohortValue}`);
|
|
473
|
-
const { offset, inverseMultiplier } = getRolloutShuffleParameters(bundleId);
|
|
474
|
-
return positiveMod(inverseMultiplier * (cohortValue - 1 - offset), NUMERIC_COHORT_SIZE);
|
|
475
|
-
}
|
|
476
|
-
function getRolledOutNumericCohorts(bundleId, rolloutCohortCount) {
|
|
477
|
-
const normalizedRolloutCount = normalizeRolloutCohortCount(rolloutCohortCount);
|
|
478
|
-
if (normalizedRolloutCount <= 0) return [];
|
|
479
|
-
if (normalizedRolloutCount >= 1e3) return Array.from({ length: NUMERIC_COHORT_SIZE }, (_, index) => index + 1);
|
|
480
|
-
const { multiplier, offset } = getRolloutShuffleParameters(bundleId);
|
|
481
|
-
return Array.from({ length: normalizedRolloutCount }, (_, position) => positiveMod(multiplier * position + offset, NUMERIC_COHORT_SIZE)).map((zeroBasedCohort) => zeroBasedCohort + 1).sort((left, right) => left - right);
|
|
482
|
-
}
|
|
483
|
-
const RELEASE_CATALOG_FALLBACK_POLICY = "BUILTIN_IF_ACTIVE_INELIGIBLE";
|
|
484
|
-
const MAX_COMPILED_CATALOG_BYTES = 262144;
|
|
485
363
|
const BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
486
364
|
const MAX_CATALOG_SEGMENT_LENGTH = 255;
|
|
487
365
|
function assertCatalogSegment(value, name) {
|
|
@@ -606,6 +484,130 @@ const NIL_UUID = "00000000-0000-0000-0000-000000000000";
|
|
|
606
484
|
const UUID_V7_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
607
485
|
const isUUIDv7 = (value) => typeof value === "string" && UUID_V7_PATTERN.test(value);
|
|
608
486
|
//#endregion
|
|
487
|
+
//#region ../../packages/protocol/dist/index.mjs
|
|
488
|
+
const getManifestStorageUri = (bundle) => bundle.manifestStorageUri;
|
|
489
|
+
const getManifestFileHash = (bundle) => bundle.manifestFileHash;
|
|
490
|
+
const getAssetBaseStorageUri = (bundle) => bundle.assetBaseStorageUri;
|
|
491
|
+
const isBundlePatchArtifact = (value) => {
|
|
492
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
493
|
+
const candidate = value;
|
|
494
|
+
return typeof candidate.baseBundleId === "string" && typeof candidate.baseFileHash === "string" && typeof candidate.patchFileHash === "string" && typeof candidate.patchStorageUri === "string";
|
|
495
|
+
};
|
|
496
|
+
const readBundlePatchArray = (patches) => {
|
|
497
|
+
if (!Array.isArray(patches)) return [];
|
|
498
|
+
return patches.filter(isBundlePatchArtifact);
|
|
499
|
+
};
|
|
500
|
+
const getBundlePatches = (bundle) => {
|
|
501
|
+
const patches = readBundlePatchArray(bundle.patches);
|
|
502
|
+
const seenBaseBundleIds = /* @__PURE__ */ new Set();
|
|
503
|
+
return patches.filter((patch) => {
|
|
504
|
+
if (seenBaseBundleIds.has(patch.baseBundleId)) return false;
|
|
505
|
+
seenBaseBundleIds.add(patch.baseBundleId);
|
|
506
|
+
return true;
|
|
507
|
+
});
|
|
508
|
+
};
|
|
509
|
+
const getBundlePatch = (bundle, baseBundleId) => {
|
|
510
|
+
return getBundlePatches(bundle).find((patch) => patch.baseBundleId === baseBundleId) ?? null;
|
|
511
|
+
};
|
|
512
|
+
const NUMERIC_COHORT_SIZE = 1e3;
|
|
513
|
+
const DEFAULT_ROLLOUT_COHORT_COUNT = NUMERIC_COHORT_SIZE;
|
|
514
|
+
const CUSTOM_COHORT_PATTERN = /^[a-z0-9-]+$/;
|
|
515
|
+
function parseNumericCohortValue(cohort) {
|
|
516
|
+
if (!/^\d+$/.test(cohort)) return null;
|
|
517
|
+
const parsed = Number.parseInt(cohort, 10);
|
|
518
|
+
if (Number.isNaN(parsed) || parsed < 1 || parsed > 1e3) return null;
|
|
519
|
+
return parsed;
|
|
520
|
+
}
|
|
521
|
+
function positiveMod(value, modulus) {
|
|
522
|
+
return (value % modulus + modulus) % modulus;
|
|
523
|
+
}
|
|
524
|
+
function hashString(value) {
|
|
525
|
+
let hash = 0;
|
|
526
|
+
for (let i = 0; i < value.length; i++) {
|
|
527
|
+
const char = value.charCodeAt(i);
|
|
528
|
+
hash = (hash << 5) - hash + char;
|
|
529
|
+
hash |= 0;
|
|
530
|
+
}
|
|
531
|
+
return hash;
|
|
532
|
+
}
|
|
533
|
+
function gcd(a, b) {
|
|
534
|
+
let x = Math.abs(a);
|
|
535
|
+
let y = Math.abs(b);
|
|
536
|
+
while (y !== 0) {
|
|
537
|
+
const next = x % y;
|
|
538
|
+
x = y;
|
|
539
|
+
y = next;
|
|
540
|
+
}
|
|
541
|
+
return x;
|
|
542
|
+
}
|
|
543
|
+
function modularInverse(value, modulus) {
|
|
544
|
+
let t = 0;
|
|
545
|
+
let newT = 1;
|
|
546
|
+
let r = modulus;
|
|
547
|
+
let newR = positiveMod(value, modulus);
|
|
548
|
+
while (newR !== 0) {
|
|
549
|
+
const quotient = Math.floor(r / newR);
|
|
550
|
+
[t, newT] = [newT, t - quotient * newT];
|
|
551
|
+
[r, newR] = [newR, r - quotient * newR];
|
|
552
|
+
}
|
|
553
|
+
if (r > 1) throw new Error(`No modular inverse for ${value} mod ${modulus}`);
|
|
554
|
+
return positiveMod(t, modulus);
|
|
555
|
+
}
|
|
556
|
+
function getRolloutShuffleParameters(bundleId) {
|
|
557
|
+
let multiplier = positiveMod(hashString(`${bundleId}:multiplier`), 997);
|
|
558
|
+
if (multiplier === 0) multiplier = 1;
|
|
559
|
+
while (gcd(multiplier, NUMERIC_COHORT_SIZE) !== 1) {
|
|
560
|
+
multiplier = positiveMod(multiplier + 1, NUMERIC_COHORT_SIZE);
|
|
561
|
+
if (multiplier === 0) multiplier = 1;
|
|
562
|
+
}
|
|
563
|
+
const offset = positiveMod(hashString(`${bundleId}:offset`), NUMERIC_COHORT_SIZE);
|
|
564
|
+
return {
|
|
565
|
+
multiplier,
|
|
566
|
+
offset,
|
|
567
|
+
inverseMultiplier: modularInverse(multiplier, NUMERIC_COHORT_SIZE)
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
function normalizeRolloutCohortCount(rolloutCohortCount) {
|
|
571
|
+
if (rolloutCohortCount === null || rolloutCohortCount === void 0) return DEFAULT_ROLLOUT_COHORT_COUNT;
|
|
572
|
+
if (rolloutCohortCount <= 0) return 0;
|
|
573
|
+
if (rolloutCohortCount >= 1e3) return NUMERIC_COHORT_SIZE;
|
|
574
|
+
return Math.floor(rolloutCohortCount);
|
|
575
|
+
}
|
|
576
|
+
function normalizeCohortValue(cohort) {
|
|
577
|
+
const normalized = cohort.trim().toLowerCase();
|
|
578
|
+
const numericCohort = parseNumericCohortValue(normalized);
|
|
579
|
+
if (numericCohort !== null) return String(numericCohort);
|
|
580
|
+
return normalized;
|
|
581
|
+
}
|
|
582
|
+
function getNumericCohortValue(cohort) {
|
|
583
|
+
return parseNumericCohortValue(normalizeCohortValue(cohort));
|
|
584
|
+
}
|
|
585
|
+
function isNumericCohort(cohort) {
|
|
586
|
+
return getNumericCohortValue(cohort) !== null;
|
|
587
|
+
}
|
|
588
|
+
function isCustomCohort(cohort) {
|
|
589
|
+
const normalized = normalizeCohortValue(cohort);
|
|
590
|
+
return normalized.length > 0 && normalized.length <= 64 && !/^\d+$/.test(normalized) && CUSTOM_COHORT_PATTERN.test(normalized);
|
|
591
|
+
}
|
|
592
|
+
function isValidCohort(cohort) {
|
|
593
|
+
const normalized = normalizeCohortValue(cohort);
|
|
594
|
+
return isNumericCohort(normalized) || isCustomCohort(normalized);
|
|
595
|
+
}
|
|
596
|
+
function getNumericCohortRolloutPosition(bundleId, cohortValue) {
|
|
597
|
+
if (cohortValue < 1 || cohortValue > 1e3) throw new Error(`Invalid numeric cohort: ${cohortValue}`);
|
|
598
|
+
const { offset, inverseMultiplier } = getRolloutShuffleParameters(bundleId);
|
|
599
|
+
return positiveMod(inverseMultiplier * (cohortValue - 1 - offset), NUMERIC_COHORT_SIZE);
|
|
600
|
+
}
|
|
601
|
+
function getRolledOutNumericCohorts(bundleId, rolloutCohortCount) {
|
|
602
|
+
const normalizedRolloutCount = normalizeRolloutCohortCount(rolloutCohortCount);
|
|
603
|
+
if (normalizedRolloutCount <= 0) return [];
|
|
604
|
+
if (normalizedRolloutCount >= 1e3) return Array.from({ length: NUMERIC_COHORT_SIZE }, (_, index) => index + 1);
|
|
605
|
+
const { multiplier, offset } = getRolloutShuffleParameters(bundleId);
|
|
606
|
+
return Array.from({ length: normalizedRolloutCount }, (_, position) => positiveMod(multiplier * position + offset, NUMERIC_COHORT_SIZE)).map((zeroBasedCohort) => zeroBasedCohort + 1).sort((left, right) => left - right);
|
|
607
|
+
}
|
|
608
|
+
const RELEASE_CATALOG_FALLBACK_POLICY = "BUILTIN_IF_ACTIVE_INELIGIBLE";
|
|
609
|
+
const MAX_COMPILED_CATALOG_BYTES = 262144;
|
|
610
|
+
//#endregion
|
|
609
611
|
//#region ../plugin-core/dist/databaseRows.mjs
|
|
610
612
|
const releaseRowToRelease = (row) => ({
|
|
611
613
|
id: row.id,
|
|
@@ -1925,12 +1927,17 @@ const SETTINGS_TABLE = {
|
|
|
1925
1927
|
const ENGINE_SCHEMA_KEY = "schema.engine";
|
|
1926
1928
|
const CORE_SCHEMA_KEY = "schema.core";
|
|
1927
1929
|
const stored = ({ key, expected, found }) => `"${key}" is ${found === null ? "missing" : `"${found}"`}; expected "${expected}"`;
|
|
1928
|
-
|
|
1930
|
+
/**
|
|
1931
|
+
* How to migrate a database the fence refused: doctor names the command for
|
|
1932
|
+
* it, init again for a deployment init manages, or the database's own
|
|
1933
|
+
* tooling in the server's project.
|
|
1934
|
+
*/
|
|
1935
|
+
const MIGRATE_SCHEMA_FIX = "Run `npx hot-updater doctor` in the project whose hot-updater.config.ts names this database: it names the command that migrates it. In a server project, `hot-updater db migrate` migrates the database its server file names.";
|
|
1936
|
+
const settingMessage = (adapterName, { key, expected, found }) => `Hot Updater schema setting "${key}" for ${adapterName} is ${found === null ? "missing" : `"${found}"`}; expected "${expected}". ${MIGRATE_SCHEMA_FIX}`;
|
|
1929
1937
|
/** The plugin a `schema.<id>` setting belongs to; undefined for core's and the engine's. */
|
|
1930
1938
|
const pluginOf = ({ key }) => key.startsWith("schema.") && key !== "schema.engine" && key !== CORE_SCHEMA_KEY ? key.slice(7) : void 0;
|
|
1931
1939
|
const listed = (items) => items.length < 2 ? items.join("") : `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
|
|
1932
|
-
|
|
1933
|
-
const PLUGIN_TABLES_FIX = "Run `hot-updater db migrate`. A managed server runs only its provider's plugins, and rerunning `hot-updater init --provider <provider>` applies the provider's migrations. Run `hot-updater db` commands in the server's project: they load the server file named on the command line, or src/hotUpdater.* or src/db.*, and don't read hot-updater.config.ts.";
|
|
1940
|
+
const settingsMessage = (adapterName, settings) => `Hot Updater schema settings for ${adapterName} are not migrated: ${settings.map(stored).join(", and ")}. ${MIGRATE_SCHEMA_FIX}`;
|
|
1934
1941
|
const pluginsMessage = (adapterName, plugins, settings, fix) => `The tables of ${plugins.length === 1 ? "plugin" : "plugins"} ${listed(plugins.map((id) => `"${id}"`))} are not migrated on ${adapterName}: schema ${settings.length === 1 ? "setting" : "settings"} ${settings.map(stored).join(", and ")}. ${fix}`;
|
|
1935
1942
|
/** The database needs `hot-updater db migrate`, or a new database; the handler answers 503. */
|
|
1936
1943
|
var HotUpdaterSchemaMigrationRequiredError = class extends Error {
|
|
@@ -1947,7 +1954,7 @@ var HotUpdaterSchemaMigrationRequiredError = class extends Error {
|
|
|
1947
1954
|
constructor(adapterName, currentVersion, setting, options) {
|
|
1948
1955
|
const settings = options?.settings ?? (setting === void 0 ? [] : [setting]);
|
|
1949
1956
|
const plugins = settings.flatMap((mismatch) => pluginOf(mismatch) ?? []);
|
|
1950
|
-
super(setting === void 0 ? currentVersion === void 0 ? `Hot Updater database schema is not initialized for ${adapterName}.
|
|
1957
|
+
super(setting === void 0 ? currentVersion === void 0 ? `Hot Updater database schema is not initialized for ${adapterName}. ${MIGRATE_SCHEMA_FIX}` : `Hot Updater v1 cannot migrate schema ${currentVersion} in place. Create a new empty database and run \`hot-updater db migrate\`.` : plugins.length > 0 && plugins.length === settings.length ? pluginsMessage(adapterName, plugins, settings, options?.fix ?? MIGRATE_SCHEMA_FIX) : settings.length > 1 && !(setting.key === "schema.engine" && setting.found === null) ? settingsMessage(adapterName, settings) : settingMessage(adapterName, setting), options?.cause === void 0 ? void 0 : { cause: options.cause });
|
|
1951
1958
|
this.adapterName = adapterName;
|
|
1952
1959
|
this.currentVersion = currentVersion;
|
|
1953
1960
|
this.setting = setting;
|
|
@@ -1995,352 +2002,502 @@ const checkSchemaFence = async (adapter, adapterName, expected, fix) => {
|
|
|
1995
2002
|
found: null
|
|
1996
2003
|
}, { cause });
|
|
1997
2004
|
}
|
|
1998
|
-
const settings = keys.flatMap((key, position) => {
|
|
1999
|
-
const found = rows[position]?.value;
|
|
2000
|
-
return found === expected[key] ? [] : [{
|
|
2001
|
-
key,
|
|
2002
|
-
expected: expected[key],
|
|
2003
|
-
found: typeof found === "string" ? found : null
|
|
2004
|
-
}];
|
|
2005
|
-
});
|
|
2006
|
-
const [first] = settings;
|
|
2007
|
-
if (first !== void 0) throw new HotUpdaterSchemaMigrationRequiredError(adapterName, void 0, first, {
|
|
2008
|
-
settings,
|
|
2009
|
-
...fix === void 0 ? {} : { fix }
|
|
2010
|
-
});
|
|
2011
|
-
};
|
|
2012
|
-
/** The database name a fenced adapter carries, so a server can fence its plugins' rows too. */
|
|
2013
|
-
const FENCED = Symbol.for("@hot-updater/server/schema-fence");
|
|
2014
|
-
/**
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
*
|
|
2018
|
-
*
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
const
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
};
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
}
|
|
2091
|
-
|
|
2092
|
-
|
|
2005
|
+
const settings = keys.flatMap((key, position) => {
|
|
2006
|
+
const found = rows[position]?.value;
|
|
2007
|
+
return found === expected[key] ? [] : [{
|
|
2008
|
+
key,
|
|
2009
|
+
expected: expected[key],
|
|
2010
|
+
found: typeof found === "string" ? found : null
|
|
2011
|
+
}];
|
|
2012
|
+
});
|
|
2013
|
+
const [first] = settings;
|
|
2014
|
+
if (first !== void 0) throw new HotUpdaterSchemaMigrationRequiredError(adapterName, void 0, first, {
|
|
2015
|
+
settings,
|
|
2016
|
+
...fix === void 0 ? {} : { fix }
|
|
2017
|
+
});
|
|
2018
|
+
};
|
|
2019
|
+
/** The database name a fenced adapter carries, so a server can fence its plugins' rows too. */
|
|
2020
|
+
const FENCED = Symbol.for("@hot-updater/server/schema-fence");
|
|
2021
|
+
/**
|
|
2022
|
+
* The adapter a fence reads its settings through and the settings it
|
|
2023
|
+
* expects, so a fence over it checks them with its own in one read. Another
|
|
2024
|
+
* key than `FENCED`, whose value another copy of plugin-core reads as the
|
|
2025
|
+
* name.
|
|
2026
|
+
*/
|
|
2027
|
+
const FENCED_SETTINGS = Symbol.for("@hot-updater/server/schema-fence-settings");
|
|
2028
|
+
/** The database name an adapter behind the schema fence reports under. */
|
|
2029
|
+
const fencedName = (adapter) => adapter[FENCED];
|
|
2030
|
+
/**
|
|
2031
|
+
* The adapter behind the schema fence: a process's first read or write
|
|
2032
|
+
* checks the settings first. Success is kept; a failure is checked again on
|
|
2033
|
+
* the next call. A fence over another one, as a server's plugins' over
|
|
2034
|
+
* core's, checks the inner fence's settings with its own in that one read,
|
|
2035
|
+
* so its refusal lists every setting missing or different.
|
|
2036
|
+
*/
|
|
2037
|
+
const withSchemaFence = (adapter, adapterName, expected, fix) => {
|
|
2038
|
+
const inner = adapter[FENCED_SETTINGS];
|
|
2039
|
+
const settings = inner === void 0 ? {
|
|
2040
|
+
adapter,
|
|
2041
|
+
expected
|
|
2042
|
+
} : {
|
|
2043
|
+
adapter: inner.adapter,
|
|
2044
|
+
expected: {
|
|
2045
|
+
...inner.expected,
|
|
2046
|
+
...expected
|
|
2047
|
+
}
|
|
2048
|
+
};
|
|
2049
|
+
let ready;
|
|
2050
|
+
const fence = () => {
|
|
2051
|
+
ready ??= checkSchemaFence(settings.adapter, adapterName, settings.expected, fix).catch((error) => {
|
|
2052
|
+
ready = void 0;
|
|
2053
|
+
throw error;
|
|
2054
|
+
});
|
|
2055
|
+
return ready;
|
|
2056
|
+
};
|
|
2057
|
+
return {
|
|
2058
|
+
...adapter,
|
|
2059
|
+
[FENCED]: adapterName,
|
|
2060
|
+
[FENCED_SETTINGS]: settings,
|
|
2061
|
+
get: (table, keys) => fence().then(() => adapter.get(table, keys)),
|
|
2062
|
+
query: (table, request) => fence().then(() => adapter.query(table, request)),
|
|
2063
|
+
write: (ops) => fence().then(() => adapter.write(ops)),
|
|
2064
|
+
...adapter.prune && { prune: (table, before, limit) => fence().then(() => adapter.prune(table, before, limit)) }
|
|
2065
|
+
};
|
|
2066
|
+
};
|
|
2067
|
+
//#endregion
|
|
2068
|
+
//#region ../plugin-core/dist/engine/database/readMeter.mjs
|
|
2069
|
+
const METER = Symbol.for("@hot-updater/plugin-core/read-meter");
|
|
2070
|
+
/** The meter `meterReads` gave `database`, if it did. */
|
|
2071
|
+
const readMeterOf = (database) => typeof database === "object" && database !== null ? database[METER] : void 0;
|
|
2072
|
+
//#endregion
|
|
2073
|
+
//#region ../plugin-core/dist/engine/db/schemaSettings.mjs
|
|
2074
|
+
const storedSettings = async (adapter, keys) => {
|
|
2075
|
+
const rows = await readSettings(adapter, keys);
|
|
2076
|
+
return new Map(keys.map((key, position) => [key, rows[position] ?? null]));
|
|
2077
|
+
};
|
|
2078
|
+
/** Writes the settings rows after every table exists. */
|
|
2079
|
+
const writeSchemaSettings = async (adapter, adapterName, settings) => {
|
|
2080
|
+
const stored = await storedSettings(adapter, Object.keys(settings));
|
|
2081
|
+
const ops = Object.entries(settings).flatMap(([key, value]) => {
|
|
2082
|
+
const row = stored.get(key);
|
|
2083
|
+
if (row === null || row === void 0) return [{
|
|
2084
|
+
type: "insert",
|
|
2085
|
+
table: SETTINGS_TABLE,
|
|
2086
|
+
row: {
|
|
2087
|
+
key,
|
|
2088
|
+
value,
|
|
2089
|
+
_v: 0
|
|
2090
|
+
}
|
|
2091
|
+
}];
|
|
2092
|
+
if (row.value === value) return [];
|
|
2093
|
+
return [{
|
|
2094
|
+
type: "patch",
|
|
2095
|
+
table: SETTINGS_TABLE,
|
|
2096
|
+
key: [key],
|
|
2097
|
+
set: { value },
|
|
2098
|
+
guard: { v: Number(row._v) },
|
|
2099
|
+
previous: row
|
|
2100
|
+
}];
|
|
2101
|
+
});
|
|
2102
|
+
if (ops.length === 0) return;
|
|
2103
|
+
if (!(await adapter.write(ops)).ok) throw new Error(`${adapterName}: the schema settings changed during the migration; run it again.`);
|
|
2104
|
+
};
|
|
2105
|
+
/** Creates or updates every table, then writes the settings rows last. */
|
|
2106
|
+
const migrateSchema = async (adapter, adapterName, tables, settings) => {
|
|
2107
|
+
if (adapter.migrations === void 0) throw new Error(`${adapterName} creates its tables with its own tooling; write only the settings rows.`);
|
|
2108
|
+
await adapter.migrations.apply([...tables, SETTINGS_TABLE]);
|
|
2109
|
+
await writeSchemaSettings(adapter, adapterName, settings);
|
|
2110
|
+
};
|
|
2111
|
+
//#endregion
|
|
2112
|
+
//#region ../plugin-core/dist/engine/core/schema.mjs
|
|
2113
|
+
/** A channel's id is `channel:` and its name's channel key, so ids share channel_key's bound. */
|
|
2114
|
+
const CHANNEL_ID = {
|
|
2115
|
+
type: "string",
|
|
2116
|
+
maxLength: 1408,
|
|
2117
|
+
ascii: true
|
|
2118
|
+
};
|
|
2119
|
+
/**
|
|
2120
|
+
* Core's schema version: the `schema.core` settings row. Bump it when core
|
|
2121
|
+
* adds a table, then regenerate the managed migrations with
|
|
2122
|
+
* HOT_UPDATER_UPDATE_SQL=1 (the D1 and Supabase schema specs), so the next
|
|
2123
|
+
* release's numbered migration carries the change and `hot-updater init`
|
|
2124
|
+
* applies it to managed databases an earlier release created. The official
|
|
2125
|
+
* plugins' `schemaVersion`s follow the same rule. A bump only adds tables:
|
|
2126
|
+
* every database's migrator creates missing tables and then records the
|
|
2127
|
+
* version, and none of them changes an existing table, so changing one
|
|
2128
|
+
* needs a migration step of its own first. The schema fence answers 503 on
|
|
2129
|
+
* a database whose row differs, until it runs.
|
|
2130
|
+
*/
|
|
2131
|
+
const HOT_UPDATER_SCHEMA_VERSION = "1.2.0";
|
|
2132
|
+
const bundles = defineTable({
|
|
2133
|
+
id: {
|
|
2134
|
+
type: "string",
|
|
2135
|
+
maxLength: 36
|
|
2136
|
+
},
|
|
2137
|
+
platform: {
|
|
2138
|
+
type: "string",
|
|
2139
|
+
maxLength: 16
|
|
2140
|
+
},
|
|
2141
|
+
git_commit_hash: {
|
|
2142
|
+
type: "string",
|
|
2143
|
+
maxLength: 255,
|
|
2144
|
+
required: false
|
|
2145
|
+
},
|
|
2146
|
+
metadata: { type: "json" },
|
|
2147
|
+
manifest_storage_uri: { type: "string" },
|
|
2148
|
+
manifest_file_hash: { type: "string" },
|
|
2149
|
+
asset_base_storage_uri: { type: "string" }
|
|
2150
|
+
}, {
|
|
2151
|
+
key: ["id"],
|
|
2152
|
+
indexes: {
|
|
2153
|
+
byPlatform: {
|
|
2154
|
+
eq: ["platform"],
|
|
2155
|
+
sort: ["id"]
|
|
2156
|
+
},
|
|
2157
|
+
all: {
|
|
2158
|
+
eq: [],
|
|
2159
|
+
sort: ["id"]
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
});
|
|
2163
|
+
const bundlePatches = defineTable({
|
|
2164
|
+
id: {
|
|
2165
|
+
type: "string",
|
|
2166
|
+
maxLength: 255
|
|
2167
|
+
},
|
|
2168
|
+
bundle_id: {
|
|
2169
|
+
type: "string",
|
|
2170
|
+
maxLength: 36,
|
|
2171
|
+
references: {
|
|
2172
|
+
model: "bundles",
|
|
2173
|
+
onDelete: "cascade"
|
|
2174
|
+
}
|
|
2175
|
+
},
|
|
2176
|
+
base_bundle_id: {
|
|
2177
|
+
type: "string",
|
|
2178
|
+
maxLength: 36,
|
|
2179
|
+
references: {
|
|
2180
|
+
model: "bundles",
|
|
2181
|
+
onDelete: "cascade"
|
|
2182
|
+
}
|
|
2183
|
+
},
|
|
2184
|
+
base_file_hash: { type: "string" },
|
|
2185
|
+
patch_file_hash: { type: "string" },
|
|
2186
|
+
patch_storage_uri: { type: "string" },
|
|
2187
|
+
byte_size: { type: "integer" },
|
|
2188
|
+
order_index: { type: "integer" }
|
|
2189
|
+
}, {
|
|
2190
|
+
key: ["id"],
|
|
2191
|
+
indexes: {
|
|
2192
|
+
pair: {
|
|
2193
|
+
eq: ["bundle_id", "base_bundle_id"],
|
|
2194
|
+
sort: [],
|
|
2195
|
+
unique: true
|
|
2196
|
+
},
|
|
2197
|
+
byBundle: {
|
|
2198
|
+
eq: ["bundle_id"],
|
|
2199
|
+
sort: ["order_index"],
|
|
2200
|
+
root: { model: "bundles" }
|
|
2201
|
+
},
|
|
2202
|
+
byBase: {
|
|
2203
|
+
eq: ["base_bundle_id"],
|
|
2204
|
+
sort: ["bundle_id"]
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
});
|
|
2208
|
+
const releases = defineTable({
|
|
2209
|
+
id: {
|
|
2210
|
+
type: "string",
|
|
2211
|
+
maxLength: 36
|
|
2212
|
+
},
|
|
2213
|
+
revision: { type: "integer" },
|
|
2214
|
+
scope_key: {
|
|
2215
|
+
type: "string",
|
|
2216
|
+
maxLength: 2048,
|
|
2217
|
+
ascii: true
|
|
2218
|
+
},
|
|
2219
|
+
channel_id: {
|
|
2220
|
+
...CHANNEL_ID,
|
|
2221
|
+
references: {
|
|
2222
|
+
model: "channels",
|
|
2223
|
+
onDelete: "restrict"
|
|
2224
|
+
}
|
|
2225
|
+
},
|
|
2226
|
+
platform: {
|
|
2227
|
+
type: "string",
|
|
2228
|
+
maxLength: 16
|
|
2229
|
+
},
|
|
2230
|
+
kind: {
|
|
2231
|
+
type: "string",
|
|
2232
|
+
maxLength: 16
|
|
2233
|
+
},
|
|
2234
|
+
bundle_id: {
|
|
2235
|
+
type: "string",
|
|
2236
|
+
maxLength: 36,
|
|
2237
|
+
required: false,
|
|
2238
|
+
references: {
|
|
2239
|
+
model: "bundles",
|
|
2240
|
+
onDelete: "restrict"
|
|
2241
|
+
}
|
|
2242
|
+
},
|
|
2243
|
+
strategy: {
|
|
2244
|
+
type: "string",
|
|
2245
|
+
maxLength: 16
|
|
2246
|
+
},
|
|
2247
|
+
target_app_version: {
|
|
2248
|
+
type: "string",
|
|
2249
|
+
required: false
|
|
2250
|
+
},
|
|
2251
|
+
fingerprint_hash: {
|
|
2252
|
+
type: "string",
|
|
2253
|
+
maxLength: 255,
|
|
2254
|
+
required: false
|
|
2255
|
+
},
|
|
2256
|
+
enabled: { type: "boolean" },
|
|
2257
|
+
should_force_update: { type: "boolean" },
|
|
2258
|
+
message: {
|
|
2259
|
+
type: "string",
|
|
2260
|
+
required: false
|
|
2261
|
+
},
|
|
2262
|
+
rollout_cohort_count: { type: "integer" },
|
|
2263
|
+
target_cohorts: { type: "json" },
|
|
2264
|
+
operation: {
|
|
2265
|
+
type: "string",
|
|
2266
|
+
maxLength: 16
|
|
2267
|
+
},
|
|
2268
|
+
source_release_id: {
|
|
2269
|
+
type: "string",
|
|
2270
|
+
maxLength: 36,
|
|
2271
|
+
required: false,
|
|
2272
|
+
references: {
|
|
2273
|
+
model: "releases",
|
|
2274
|
+
onDelete: "none"
|
|
2275
|
+
}
|
|
2276
|
+
},
|
|
2277
|
+
created_at_ms: { type: "integer" },
|
|
2278
|
+
updated_at_ms: { type: "integer" }
|
|
2279
|
+
}, {
|
|
2280
|
+
key: ["id"],
|
|
2281
|
+
indexes: {
|
|
2282
|
+
byScope: {
|
|
2283
|
+
eq: ["scope_key"],
|
|
2284
|
+
sort: ["id"],
|
|
2285
|
+
root: { model: "release_catalogs" }
|
|
2286
|
+
},
|
|
2287
|
+
byScopeEnabled: {
|
|
2288
|
+
eq: ["scope_key", "enabled"],
|
|
2289
|
+
sort: ["id"],
|
|
2290
|
+
root: { model: "release_catalogs" }
|
|
2291
|
+
},
|
|
2292
|
+
byChannelPlatform: {
|
|
2293
|
+
eq: ["channel_id", "platform"],
|
|
2294
|
+
sort: ["id"]
|
|
2295
|
+
},
|
|
2296
|
+
byChannelPlatformEnabled: {
|
|
2297
|
+
eq: [
|
|
2298
|
+
"channel_id",
|
|
2299
|
+
"platform",
|
|
2300
|
+
"enabled"
|
|
2301
|
+
],
|
|
2302
|
+
sort: ["id"]
|
|
2303
|
+
},
|
|
2304
|
+
byBundle: {
|
|
2305
|
+
eq: ["bundle_id"],
|
|
2306
|
+
sort: ["id"]
|
|
2307
|
+
},
|
|
2308
|
+
all: {
|
|
2309
|
+
eq: [],
|
|
2310
|
+
sort: ["id"]
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
});
|
|
2314
|
+
/** One compiled catalog per scope; the update check reads exactly this row. */
|
|
2315
|
+
const releaseCatalogs = defineTable({
|
|
2316
|
+
scope_key: {
|
|
2317
|
+
type: "string",
|
|
2318
|
+
maxLength: 2048,
|
|
2319
|
+
ascii: true
|
|
2320
|
+
},
|
|
2321
|
+
catalog_id: {
|
|
2322
|
+
type: "string",
|
|
2323
|
+
maxLength: 255
|
|
2324
|
+
},
|
|
2325
|
+
strategy: {
|
|
2326
|
+
type: "string",
|
|
2327
|
+
maxLength: 16
|
|
2328
|
+
},
|
|
2329
|
+
channel_id: {
|
|
2330
|
+
...CHANNEL_ID,
|
|
2331
|
+
references: {
|
|
2332
|
+
model: "channels",
|
|
2333
|
+
onDelete: "none"
|
|
2334
|
+
}
|
|
2335
|
+
},
|
|
2336
|
+
channel_key: {
|
|
2337
|
+
type: "string",
|
|
2338
|
+
maxLength: 1400,
|
|
2339
|
+
ascii: true
|
|
2340
|
+
},
|
|
2341
|
+
platform: {
|
|
2342
|
+
type: "string",
|
|
2343
|
+
maxLength: 16
|
|
2344
|
+
},
|
|
2345
|
+
fingerprint_hash: {
|
|
2346
|
+
type: "string",
|
|
2347
|
+
maxLength: 255,
|
|
2348
|
+
required: false
|
|
2349
|
+
},
|
|
2350
|
+
generation: { type: "integer" },
|
|
2351
|
+
payload: { type: "string" },
|
|
2352
|
+
catalog_hash: {
|
|
2353
|
+
type: "string",
|
|
2354
|
+
maxLength: 71
|
|
2355
|
+
},
|
|
2356
|
+
byte_size: { type: "integer" },
|
|
2357
|
+
is_tombstone: { type: "boolean" },
|
|
2358
|
+
updated_at_ms: { type: "integer" }
|
|
2359
|
+
}, {
|
|
2360
|
+
key: ["scope_key"],
|
|
2361
|
+
indexes: { all: {
|
|
2362
|
+
eq: [],
|
|
2363
|
+
sort: ["scope_key"]
|
|
2364
|
+
} }
|
|
2365
|
+
});
|
|
2366
|
+
const channels = defineTable({
|
|
2367
|
+
id: CHANNEL_ID,
|
|
2368
|
+
name: {
|
|
2369
|
+
type: "string",
|
|
2370
|
+
maxLength: 255,
|
|
2371
|
+
unique: true
|
|
2372
|
+
}
|
|
2373
|
+
}, {
|
|
2374
|
+
key: ["id"],
|
|
2375
|
+
indexes: { all: {
|
|
2376
|
+
eq: [],
|
|
2377
|
+
sort: ["name"]
|
|
2378
|
+
} }
|
|
2379
|
+
});
|
|
2380
|
+
/**
|
|
2381
|
+
* The native builds `native-build android` and `native-build ios` track, newest first
|
|
2382
|
+
* in each scope: a platform, a channel, and the fingerprint or app version
|
|
2383
|
+
* the build compiled in. `min_bundle_id` names the build's built-in bundle,
|
|
2384
|
+
* a `bundles` row no release publishes. Its references keep no counters, so
|
|
2385
|
+
* adding this table changes no existing one; a channel with builds is
|
|
2386
|
+
* refused deletion through `byChannel`. `build_key` is the native build key
|
|
2387
|
+
* of every input the artifact was built from (`nbk1:` and a SHA-256), which
|
|
2388
|
+
* a build with the same inputs reuses it by; a build registered without
|
|
2389
|
+
* one is never reused. `build_number` is the artifact's `CFBundleVersion`
|
|
2390
|
+
* or `versionCode`. `submitted_to`, `submitted_at_ms` and
|
|
2391
|
+
* `submitted_version` record the store track a build went to, when, and
|
|
2392
|
+
* the store's label of the build; they stay null for a build no store took.
|
|
2393
|
+
*/
|
|
2394
|
+
const nativeBuilds = defineTable({
|
|
2395
|
+
id: {
|
|
2396
|
+
type: "string",
|
|
2397
|
+
maxLength: 36
|
|
2398
|
+
},
|
|
2399
|
+
scope_key: {
|
|
2400
|
+
type: "string",
|
|
2401
|
+
maxLength: 2048,
|
|
2402
|
+
ascii: true
|
|
2403
|
+
},
|
|
2404
|
+
platform: {
|
|
2405
|
+
type: "string",
|
|
2406
|
+
maxLength: 16
|
|
2407
|
+
},
|
|
2408
|
+
channel_id: {
|
|
2409
|
+
...CHANNEL_ID,
|
|
2410
|
+
references: {
|
|
2411
|
+
model: "channels",
|
|
2412
|
+
onDelete: "none"
|
|
2413
|
+
}
|
|
2414
|
+
},
|
|
2415
|
+
fingerprint_hash: {
|
|
2416
|
+
type: "string",
|
|
2417
|
+
maxLength: 255,
|
|
2418
|
+
required: false
|
|
2419
|
+
},
|
|
2420
|
+
app_version: {
|
|
2421
|
+
type: "string",
|
|
2422
|
+
required: false
|
|
2423
|
+
},
|
|
2424
|
+
min_bundle_id: {
|
|
2425
|
+
type: "string",
|
|
2426
|
+
maxLength: 36,
|
|
2427
|
+
unique: true,
|
|
2428
|
+
references: {
|
|
2429
|
+
model: "bundles",
|
|
2430
|
+
onDelete: "none"
|
|
2431
|
+
}
|
|
2432
|
+
},
|
|
2433
|
+
artifact_kind: {
|
|
2434
|
+
type: "string",
|
|
2435
|
+
maxLength: 16
|
|
2436
|
+
},
|
|
2437
|
+
artifact_storage_uri: { type: "string" },
|
|
2438
|
+
byte_size: { type: "integer" },
|
|
2439
|
+
git_commit_hash: {
|
|
2440
|
+
type: "string",
|
|
2441
|
+
maxLength: 255,
|
|
2442
|
+
required: false
|
|
2443
|
+
},
|
|
2444
|
+
created_at_ms: { type: "integer" },
|
|
2445
|
+
build_key: {
|
|
2446
|
+
type: "string",
|
|
2447
|
+
maxLength: 69,
|
|
2448
|
+
ascii: true,
|
|
2449
|
+
required: false
|
|
2450
|
+
},
|
|
2451
|
+
build_number: {
|
|
2452
|
+
type: "string",
|
|
2453
|
+
maxLength: 64,
|
|
2454
|
+
required: false
|
|
2455
|
+
},
|
|
2456
|
+
submitted_to: {
|
|
2457
|
+
type: "string",
|
|
2458
|
+
maxLength: 16,
|
|
2459
|
+
required: false
|
|
2460
|
+
},
|
|
2461
|
+
submitted_at_ms: {
|
|
2462
|
+
type: "integer",
|
|
2463
|
+
required: false
|
|
2464
|
+
},
|
|
2465
|
+
submitted_version: {
|
|
2466
|
+
type: "string",
|
|
2467
|
+
maxLength: 255,
|
|
2468
|
+
required: false
|
|
2469
|
+
}
|
|
2470
|
+
}, {
|
|
2471
|
+
key: ["id"],
|
|
2472
|
+
indexes: {
|
|
2473
|
+
byScope: {
|
|
2474
|
+
eq: ["scope_key"],
|
|
2475
|
+
sort: ["id"]
|
|
2476
|
+
},
|
|
2477
|
+
byBuildKey: {
|
|
2478
|
+
eq: ["scope_key", "build_key"],
|
|
2479
|
+
sort: ["id"]
|
|
2480
|
+
},
|
|
2481
|
+
byChannel: {
|
|
2482
|
+
eq: ["channel_id"],
|
|
2483
|
+
sort: ["id"],
|
|
2484
|
+
root: { model: "channels" }
|
|
2485
|
+
},
|
|
2486
|
+
all: {
|
|
2487
|
+
eq: [],
|
|
2488
|
+
sort: ["id"]
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
});
|
|
2093
2492
|
/** Core's module: always installed, never a plugin. */
|
|
2094
2493
|
const coreModule = {
|
|
2095
2494
|
id: "core",
|
|
2096
2495
|
schema: {
|
|
2097
|
-
bundles
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
platform: {
|
|
2103
|
-
type: "string",
|
|
2104
|
-
maxLength: 16
|
|
2105
|
-
},
|
|
2106
|
-
git_commit_hash: {
|
|
2107
|
-
type: "string",
|
|
2108
|
-
maxLength: 255,
|
|
2109
|
-
required: false
|
|
2110
|
-
},
|
|
2111
|
-
metadata: { type: "json" },
|
|
2112
|
-
manifest_storage_uri: { type: "string" },
|
|
2113
|
-
manifest_file_hash: { type: "string" },
|
|
2114
|
-
asset_base_storage_uri: { type: "string" }
|
|
2115
|
-
}, {
|
|
2116
|
-
key: ["id"],
|
|
2117
|
-
indexes: {
|
|
2118
|
-
byPlatform: {
|
|
2119
|
-
eq: ["platform"],
|
|
2120
|
-
sort: ["id"]
|
|
2121
|
-
},
|
|
2122
|
-
all: {
|
|
2123
|
-
eq: [],
|
|
2124
|
-
sort: ["id"]
|
|
2125
|
-
}
|
|
2126
|
-
}
|
|
2127
|
-
}),
|
|
2128
|
-
bundle_patches: defineTable({
|
|
2129
|
-
id: {
|
|
2130
|
-
type: "string",
|
|
2131
|
-
maxLength: 255
|
|
2132
|
-
},
|
|
2133
|
-
bundle_id: {
|
|
2134
|
-
type: "string",
|
|
2135
|
-
maxLength: 36,
|
|
2136
|
-
references: {
|
|
2137
|
-
model: "bundles",
|
|
2138
|
-
onDelete: "cascade"
|
|
2139
|
-
}
|
|
2140
|
-
},
|
|
2141
|
-
base_bundle_id: {
|
|
2142
|
-
type: "string",
|
|
2143
|
-
maxLength: 36,
|
|
2144
|
-
references: {
|
|
2145
|
-
model: "bundles",
|
|
2146
|
-
onDelete: "cascade"
|
|
2147
|
-
}
|
|
2148
|
-
},
|
|
2149
|
-
base_file_hash: { type: "string" },
|
|
2150
|
-
patch_file_hash: { type: "string" },
|
|
2151
|
-
patch_storage_uri: { type: "string" },
|
|
2152
|
-
byte_size: { type: "integer" },
|
|
2153
|
-
order_index: { type: "integer" }
|
|
2154
|
-
}, {
|
|
2155
|
-
key: ["id"],
|
|
2156
|
-
indexes: {
|
|
2157
|
-
pair: {
|
|
2158
|
-
eq: ["bundle_id", "base_bundle_id"],
|
|
2159
|
-
sort: [],
|
|
2160
|
-
unique: true
|
|
2161
|
-
},
|
|
2162
|
-
byBundle: {
|
|
2163
|
-
eq: ["bundle_id"],
|
|
2164
|
-
sort: ["order_index"],
|
|
2165
|
-
root: { model: "bundles" }
|
|
2166
|
-
},
|
|
2167
|
-
byBase: {
|
|
2168
|
-
eq: ["base_bundle_id"],
|
|
2169
|
-
sort: ["bundle_id"]
|
|
2170
|
-
}
|
|
2171
|
-
}
|
|
2172
|
-
}),
|
|
2173
|
-
releases: defineTable({
|
|
2174
|
-
id: {
|
|
2175
|
-
type: "string",
|
|
2176
|
-
maxLength: 36
|
|
2177
|
-
},
|
|
2178
|
-
revision: { type: "integer" },
|
|
2179
|
-
scope_key: {
|
|
2180
|
-
type: "string",
|
|
2181
|
-
maxLength: 2048,
|
|
2182
|
-
ascii: true
|
|
2183
|
-
},
|
|
2184
|
-
channel_id: {
|
|
2185
|
-
...CHANNEL_ID,
|
|
2186
|
-
references: {
|
|
2187
|
-
model: "channels",
|
|
2188
|
-
onDelete: "restrict"
|
|
2189
|
-
}
|
|
2190
|
-
},
|
|
2191
|
-
platform: {
|
|
2192
|
-
type: "string",
|
|
2193
|
-
maxLength: 16
|
|
2194
|
-
},
|
|
2195
|
-
kind: {
|
|
2196
|
-
type: "string",
|
|
2197
|
-
maxLength: 16
|
|
2198
|
-
},
|
|
2199
|
-
bundle_id: {
|
|
2200
|
-
type: "string",
|
|
2201
|
-
maxLength: 36,
|
|
2202
|
-
required: false,
|
|
2203
|
-
references: {
|
|
2204
|
-
model: "bundles",
|
|
2205
|
-
onDelete: "restrict"
|
|
2206
|
-
}
|
|
2207
|
-
},
|
|
2208
|
-
strategy: {
|
|
2209
|
-
type: "string",
|
|
2210
|
-
maxLength: 16
|
|
2211
|
-
},
|
|
2212
|
-
target_app_version: {
|
|
2213
|
-
type: "string",
|
|
2214
|
-
required: false
|
|
2215
|
-
},
|
|
2216
|
-
fingerprint_hash: {
|
|
2217
|
-
type: "string",
|
|
2218
|
-
maxLength: 255,
|
|
2219
|
-
required: false
|
|
2220
|
-
},
|
|
2221
|
-
enabled: { type: "boolean" },
|
|
2222
|
-
should_force_update: { type: "boolean" },
|
|
2223
|
-
message: {
|
|
2224
|
-
type: "string",
|
|
2225
|
-
required: false
|
|
2226
|
-
},
|
|
2227
|
-
rollout_cohort_count: { type: "integer" },
|
|
2228
|
-
target_cohorts: { type: "json" },
|
|
2229
|
-
operation: {
|
|
2230
|
-
type: "string",
|
|
2231
|
-
maxLength: 16
|
|
2232
|
-
},
|
|
2233
|
-
source_release_id: {
|
|
2234
|
-
type: "string",
|
|
2235
|
-
maxLength: 36,
|
|
2236
|
-
required: false,
|
|
2237
|
-
references: {
|
|
2238
|
-
model: "releases",
|
|
2239
|
-
onDelete: "none"
|
|
2240
|
-
}
|
|
2241
|
-
},
|
|
2242
|
-
created_at_ms: { type: "integer" },
|
|
2243
|
-
updated_at_ms: { type: "integer" }
|
|
2244
|
-
}, {
|
|
2245
|
-
key: ["id"],
|
|
2246
|
-
indexes: {
|
|
2247
|
-
byScope: {
|
|
2248
|
-
eq: ["scope_key"],
|
|
2249
|
-
sort: ["id"],
|
|
2250
|
-
root: { model: "release_catalogs" }
|
|
2251
|
-
},
|
|
2252
|
-
byScopeEnabled: {
|
|
2253
|
-
eq: ["scope_key", "enabled"],
|
|
2254
|
-
sort: ["id"],
|
|
2255
|
-
root: { model: "release_catalogs" }
|
|
2256
|
-
},
|
|
2257
|
-
byChannelPlatform: {
|
|
2258
|
-
eq: ["channel_id", "platform"],
|
|
2259
|
-
sort: ["id"]
|
|
2260
|
-
},
|
|
2261
|
-
byChannelPlatformEnabled: {
|
|
2262
|
-
eq: [
|
|
2263
|
-
"channel_id",
|
|
2264
|
-
"platform",
|
|
2265
|
-
"enabled"
|
|
2266
|
-
],
|
|
2267
|
-
sort: ["id"]
|
|
2268
|
-
},
|
|
2269
|
-
byBundle: {
|
|
2270
|
-
eq: ["bundle_id"],
|
|
2271
|
-
sort: ["id"]
|
|
2272
|
-
},
|
|
2273
|
-
all: {
|
|
2274
|
-
eq: [],
|
|
2275
|
-
sort: ["id"]
|
|
2276
|
-
}
|
|
2277
|
-
}
|
|
2278
|
-
}),
|
|
2279
|
-
release_catalogs: defineTable({
|
|
2280
|
-
scope_key: {
|
|
2281
|
-
type: "string",
|
|
2282
|
-
maxLength: 2048,
|
|
2283
|
-
ascii: true
|
|
2284
|
-
},
|
|
2285
|
-
catalog_id: {
|
|
2286
|
-
type: "string",
|
|
2287
|
-
maxLength: 255
|
|
2288
|
-
},
|
|
2289
|
-
strategy: {
|
|
2290
|
-
type: "string",
|
|
2291
|
-
maxLength: 16
|
|
2292
|
-
},
|
|
2293
|
-
channel_id: {
|
|
2294
|
-
...CHANNEL_ID,
|
|
2295
|
-
references: {
|
|
2296
|
-
model: "channels",
|
|
2297
|
-
onDelete: "none"
|
|
2298
|
-
}
|
|
2299
|
-
},
|
|
2300
|
-
channel_key: {
|
|
2301
|
-
type: "string",
|
|
2302
|
-
maxLength: 1400,
|
|
2303
|
-
ascii: true
|
|
2304
|
-
},
|
|
2305
|
-
platform: {
|
|
2306
|
-
type: "string",
|
|
2307
|
-
maxLength: 16
|
|
2308
|
-
},
|
|
2309
|
-
fingerprint_hash: {
|
|
2310
|
-
type: "string",
|
|
2311
|
-
maxLength: 255,
|
|
2312
|
-
required: false
|
|
2313
|
-
},
|
|
2314
|
-
generation: { type: "integer" },
|
|
2315
|
-
payload: { type: "string" },
|
|
2316
|
-
catalog_hash: {
|
|
2317
|
-
type: "string",
|
|
2318
|
-
maxLength: 71
|
|
2319
|
-
},
|
|
2320
|
-
byte_size: { type: "integer" },
|
|
2321
|
-
is_tombstone: { type: "boolean" },
|
|
2322
|
-
updated_at_ms: { type: "integer" }
|
|
2323
|
-
}, {
|
|
2324
|
-
key: ["scope_key"],
|
|
2325
|
-
indexes: { all: {
|
|
2326
|
-
eq: [],
|
|
2327
|
-
sort: ["scope_key"]
|
|
2328
|
-
} }
|
|
2329
|
-
}),
|
|
2330
|
-
channels: defineTable({
|
|
2331
|
-
id: CHANNEL_ID,
|
|
2332
|
-
name: {
|
|
2333
|
-
type: "string",
|
|
2334
|
-
maxLength: 255,
|
|
2335
|
-
unique: true
|
|
2336
|
-
}
|
|
2337
|
-
}, {
|
|
2338
|
-
key: ["id"],
|
|
2339
|
-
indexes: { all: {
|
|
2340
|
-
eq: [],
|
|
2341
|
-
sort: ["name"]
|
|
2342
|
-
} }
|
|
2343
|
-
}),
|
|
2496
|
+
bundles,
|
|
2497
|
+
bundle_patches: bundlePatches,
|
|
2498
|
+
releases,
|
|
2499
|
+
release_catalogs: releaseCatalogs,
|
|
2500
|
+
channels,
|
|
2344
2501
|
bundle_totals: defineAggregate({ platform_key: {
|
|
2345
2502
|
type: "string",
|
|
2346
2503
|
maxLength: 16
|
|
@@ -2351,7 +2508,8 @@ const coreModule = {
|
|
|
2351
2508
|
eq: ["platform_key"],
|
|
2352
2509
|
sort: []
|
|
2353
2510
|
} }
|
|
2354
|
-
})
|
|
2511
|
+
}),
|
|
2512
|
+
native_builds: nativeBuilds
|
|
2355
2513
|
}
|
|
2356
2514
|
};
|
|
2357
2515
|
//#endregion
|
|
@@ -3466,14 +3624,14 @@ const createEngineMigrator = (options) => {
|
|
|
3466
3624
|
return {
|
|
3467
3625
|
getVersion,
|
|
3468
3626
|
getNameVariants: async () => void 0,
|
|
3469
|
-
next: async () => await getVersion() === "1.
|
|
3627
|
+
next: async () => await getVersion() === "1.2.0" ? void 0 : { version: HOT_UPDATER_SCHEMA_VERSION },
|
|
3470
3628
|
previous: async () => void 0,
|
|
3471
3629
|
up: makeResult,
|
|
3472
3630
|
down: async () => {
|
|
3473
3631
|
throw new Error("No previous schema to migrate to.");
|
|
3474
3632
|
},
|
|
3475
3633
|
migrateTo: async (version, migrate) => {
|
|
3476
|
-
if (version !== "1.
|
|
3634
|
+
if (version !== "1.2.0") throw new Error(`Invalid version ${version}`);
|
|
3477
3635
|
return makeResult(migrate);
|
|
3478
3636
|
},
|
|
3479
3637
|
migrateToLatest: makeResult
|
|
@@ -4205,9 +4363,9 @@ const createDatabaseEngine = ({ batching, now = Date.now, meter, ...options }) =
|
|
|
4205
4363
|
*/
|
|
4206
4364
|
const pluginTablesFix = (database) => {
|
|
4207
4365
|
const { createMigrator, generateSchema } = database;
|
|
4208
|
-
const
|
|
4366
|
+
const doctor = "Run `npx hot-updater doctor` in the project whose hot-updater.config.ts names this database: it names the command that migrates it.";
|
|
4209
4367
|
const serverProject = "Run `hot-updater db` commands in the server's project: they load the server file named on the command line, or src/hotUpdater.* or src/db.*, and don't read hot-updater.config.ts.";
|
|
4210
|
-
return createMigrator !== void 0 ?
|
|
4368
|
+
return createMigrator !== void 0 ? `${doctor} In a server project, run \`hot-updater db migrate\`. ${serverProject}` : generateSchema !== void 0 ? `${doctor} On a database \`hot-updater init\` created, that is init again with its saved inputs; for your own server, run \`hot-updater db generate\` and apply the file it writes. ${serverProject}` : `${doctor} On a database \`hot-updater init\` created, that is init again with its saved inputs.`;
|
|
4211
4369
|
};
|
|
4212
4370
|
/**
|
|
4213
4371
|
* The storage engine over a database, as a server runs it: core's tables
|
|
@@ -4549,7 +4707,7 @@ const createKvAdapter = ({ store, tablePrefix = "" }) => {
|
|
|
4549
4707
|
};
|
|
4550
4708
|
//#endregion
|
|
4551
4709
|
//#region ../../packages/server/dist/version.mjs
|
|
4552
|
-
const HOT_UPDATER_SERVER_VERSION = "1.
|
|
4710
|
+
const HOT_UPDATER_SERVER_VERSION = "1.2.0-rc.0";
|
|
4553
4711
|
//#endregion
|
|
4554
4712
|
//#region ../../packages/server/dist/handlerVersionRoutes.mjs
|
|
4555
4713
|
/**
|
|
@@ -5342,8 +5500,8 @@ const baseBundleIdsOf = (row, range, bundleId, limit) => {
|
|
|
5342
5500
|
//#endregion
|
|
5343
5501
|
//#region ../../packages/server/dist/core/reads.mjs
|
|
5344
5502
|
const PAGE$3 = 500;
|
|
5345
|
-
const pick = (fields) => (row) => Object.fromEntries(fields.map((field) => [field, row[field]]));
|
|
5346
|
-
const toBundleRow = pick([
|
|
5503
|
+
const pick$1 = (fields) => (row) => Object.fromEntries(fields.map((field) => [field, row[field]]));
|
|
5504
|
+
const toBundleRow = pick$1([
|
|
5347
5505
|
"id",
|
|
5348
5506
|
"platform",
|
|
5349
5507
|
"git_commit_hash",
|
|
@@ -5352,7 +5510,7 @@ const toBundleRow = pick([
|
|
|
5352
5510
|
"manifest_file_hash",
|
|
5353
5511
|
"asset_base_storage_uri"
|
|
5354
5512
|
]);
|
|
5355
|
-
const toPatchRow = pick([
|
|
5513
|
+
const toPatchRow = pick$1([
|
|
5356
5514
|
"id",
|
|
5357
5515
|
"bundle_id",
|
|
5358
5516
|
"base_bundle_id",
|
|
@@ -5362,7 +5520,7 @@ const toPatchRow = pick([
|
|
|
5362
5520
|
"byte_size",
|
|
5363
5521
|
"order_index"
|
|
5364
5522
|
]);
|
|
5365
|
-
const toReleaseRow = pick([
|
|
5523
|
+
const toReleaseRow = pick$1([
|
|
5366
5524
|
"id",
|
|
5367
5525
|
"revision",
|
|
5368
5526
|
"scope_key",
|
|
@@ -5383,7 +5541,7 @@ const toReleaseRow = pick([
|
|
|
5383
5541
|
"created_at_ms",
|
|
5384
5542
|
"updated_at_ms"
|
|
5385
5543
|
]);
|
|
5386
|
-
const toCatalogRow = pick([
|
|
5544
|
+
const toCatalogRow = pick$1([
|
|
5387
5545
|
"scope_key",
|
|
5388
5546
|
"catalog_id",
|
|
5389
5547
|
"strategy",
|
|
@@ -5398,7 +5556,7 @@ const toCatalogRow = pick([
|
|
|
5398
5556
|
"is_tombstone",
|
|
5399
5557
|
"updated_at_ms"
|
|
5400
5558
|
]);
|
|
5401
|
-
const toChannelRow = pick(["id", "name"]);
|
|
5559
|
+
const toChannelRow = pick$1(["id", "name"]);
|
|
5402
5560
|
/** Every page of one read, in order. */
|
|
5403
5561
|
const drain$1 = async (read) => {
|
|
5404
5562
|
const rows = [];
|
|
@@ -5410,6 +5568,8 @@ const drain$1 = async (read) => {
|
|
|
5410
5568
|
} while (cursor !== void 0);
|
|
5411
5569
|
return rows;
|
|
5412
5570
|
};
|
|
5571
|
+
/** A keyset page's range: the keys strictly after `after`, in `order`. */
|
|
5572
|
+
const after = (input) => input.after === void 0 ? {} : { range: (input.order ?? "asc") === "asc" ? { gt: input.after } : { lt: input.after } };
|
|
5413
5573
|
/** Core's reads, each through one declared index or key. */
|
|
5414
5574
|
const createCoreReads = (db, storage) => {
|
|
5415
5575
|
/** A bundle row's patches, exactly as many as its reference counter holds. */
|
|
@@ -5434,7 +5594,6 @@ const createCoreReads = (db, storage) => {
|
|
|
5434
5594
|
childCount: row._refs_bundle_patches_base_bundle_id ?? 0
|
|
5435
5595
|
};
|
|
5436
5596
|
};
|
|
5437
|
-
const after = (input) => input.after === void 0 ? {} : { range: (input.order ?? "asc") === "asc" ? { gt: input.after } : { lt: input.after } };
|
|
5438
5597
|
return {
|
|
5439
5598
|
/** The update check: one point read of its scope's catalog. */
|
|
5440
5599
|
async getReleaseCatalog(input) {
|
|
@@ -5625,9 +5784,13 @@ const insertBundle = (tx, row) => {
|
|
|
5625
5784
|
tx.create("bundles", row);
|
|
5626
5785
|
countBundle(tx, row.platform, 1);
|
|
5627
5786
|
};
|
|
5628
|
-
|
|
5787
|
+
/**
|
|
5788
|
+
* Updates a bundle; a platform change moves it between platform totals,
|
|
5789
|
+
* except for a native build's built-in bundle, which they never count.
|
|
5790
|
+
*/
|
|
5791
|
+
const updateBundle = (tx, current, set, { counted = true } = {}) => {
|
|
5629
5792
|
tx.update("bundles", current, set);
|
|
5630
|
-
if (set.platform !== void 0 && set.platform !== current.platform) {
|
|
5793
|
+
if (counted && set.platform !== void 0 && set.platform !== current.platform) {
|
|
5631
5794
|
countBundle(tx, current.platform, -1);
|
|
5632
5795
|
countBundle(tx, set.platform, 1);
|
|
5633
5796
|
}
|
|
@@ -5688,7 +5851,7 @@ const insertChannel = async (db, row) => {
|
|
|
5688
5851
|
throw error;
|
|
5689
5852
|
}
|
|
5690
5853
|
};
|
|
5691
|
-
/** Deletes a channel no release uses: 1 read
|
|
5854
|
+
/** Deletes a channel no release or native build uses: 1 read, 1 query, 1 write. */
|
|
5692
5855
|
const deleteChannel = (db, id) => db.transaction(async (tx) => {
|
|
5693
5856
|
const row = await tx.findOne("channels", { id });
|
|
5694
5857
|
if (row === null) return {
|
|
@@ -5699,10 +5862,243 @@ const deleteChannel = (db, id) => db.transaction(async (tx) => {
|
|
|
5699
5862
|
deleted: false,
|
|
5700
5863
|
reason: "not_empty"
|
|
5701
5864
|
};
|
|
5865
|
+
const { rows: builds } = await tx.findMany("native_builds", {
|
|
5866
|
+
index: "byChannel",
|
|
5867
|
+
where: { channel_id: id },
|
|
5868
|
+
limit: 1
|
|
5869
|
+
});
|
|
5870
|
+
if (builds.length > 0) return {
|
|
5871
|
+
deleted: false,
|
|
5872
|
+
reason: "not_empty"
|
|
5873
|
+
};
|
|
5702
5874
|
await tx.delete("channels", row);
|
|
5703
5875
|
return { deleted: true };
|
|
5704
5876
|
});
|
|
5705
5877
|
//#endregion
|
|
5878
|
+
//#region ../../packages/server/dist/core/nativeBuilds.mjs
|
|
5879
|
+
/** The artifacts each platform's build produces. */
|
|
5880
|
+
const ARTIFACT_KINDS = {
|
|
5881
|
+
android: ["apk", "aab"],
|
|
5882
|
+
ios: ["ipa", "app"]
|
|
5883
|
+
};
|
|
5884
|
+
/** `native_builds.scope_key`'s bound in core's schema. */
|
|
5885
|
+
const SCOPE_KEY_MAX_LENGTH = 2048;
|
|
5886
|
+
/** The `git_commit_hash` bound of `bundles` and `native_builds`. */
|
|
5887
|
+
const GIT_COMMIT_HASH_MAX_LENGTH = 255;
|
|
5888
|
+
/**
|
|
5889
|
+
* A native build key: `nbk1:` and a SHA-256, as the CLI computes it. The
|
|
5890
|
+
* format is the CLI's convention, implemented here so the registry stores
|
|
5891
|
+
* and looks up nothing else.
|
|
5892
|
+
*/
|
|
5893
|
+
const BUILD_KEY = /^nbk1:[0-9a-f]{64}$/;
|
|
5894
|
+
/** A native build or scope core cannot store or look up: the caller's input. */
|
|
5895
|
+
var NativeBuildInputError = class extends TypeError {
|
|
5896
|
+
name = "NativeBuildInputError";
|
|
5897
|
+
};
|
|
5898
|
+
const pick = (row) => ({
|
|
5899
|
+
id: row.id,
|
|
5900
|
+
scope_key: row.scope_key,
|
|
5901
|
+
platform: row.platform,
|
|
5902
|
+
channel_id: row.channel_id,
|
|
5903
|
+
fingerprint_hash: row.fingerprint_hash ?? null,
|
|
5904
|
+
app_version: row.app_version ?? null,
|
|
5905
|
+
min_bundle_id: row.min_bundle_id,
|
|
5906
|
+
artifact_kind: row.artifact_kind,
|
|
5907
|
+
artifact_storage_uri: row.artifact_storage_uri,
|
|
5908
|
+
byte_size: row.byte_size,
|
|
5909
|
+
git_commit_hash: row.git_commit_hash ?? null,
|
|
5910
|
+
created_at_ms: row.created_at_ms,
|
|
5911
|
+
build_key: row.build_key ?? null,
|
|
5912
|
+
build_number: row.build_number ?? null,
|
|
5913
|
+
submitted_to: row.submitted_to ?? null,
|
|
5914
|
+
submitted_at_ms: row.submitted_at_ms ?? null,
|
|
5915
|
+
submitted_version: row.submitted_version ?? null
|
|
5916
|
+
});
|
|
5917
|
+
/** The store track each platform's builds are submitted to. */
|
|
5918
|
+
const SUBMISSION_DESTINATIONS = {
|
|
5919
|
+
android: "play-internal",
|
|
5920
|
+
ios: "testflight"
|
|
5921
|
+
};
|
|
5922
|
+
/** `native_builds.build_number`'s bound in core's schema. */
|
|
5923
|
+
const BUILD_NUMBER_MAX_LENGTH = 64;
|
|
5924
|
+
/** `native_builds.submitted_version`'s bound in core's schema. */
|
|
5925
|
+
const SUBMITTED_VERSION_MAX_LENGTH = 255;
|
|
5926
|
+
/**
|
|
5927
|
+
* A native build scope's key: the Release Catalog scope key of a
|
|
5928
|
+
* fingerprint, or of the app-version strategy followed by the exact app
|
|
5929
|
+
* version the build compiled in. One key serves both strategies, so one
|
|
5930
|
+
* index finds the newest build of either.
|
|
5931
|
+
*/
|
|
5932
|
+
const nativeBuildScopeKey = (scope) => {
|
|
5933
|
+
let scopeKey;
|
|
5934
|
+
try {
|
|
5935
|
+
const channelKey = encodeChannelKey(scope.channel);
|
|
5936
|
+
scopeKey = scope.fingerprintHash === null ? `${createReleaseCatalogScopeKey({
|
|
5937
|
+
channelKey,
|
|
5938
|
+
platform: scope.platform,
|
|
5939
|
+
strategy: "APP_VERSION"
|
|
5940
|
+
})}:${encodeURIComponent(scope.appVersion)}` : createReleaseCatalogScopeKey({
|
|
5941
|
+
channelKey,
|
|
5942
|
+
fingerprintHash: scope.fingerprintHash,
|
|
5943
|
+
platform: scope.platform,
|
|
5944
|
+
strategy: "FINGERPRINT"
|
|
5945
|
+
});
|
|
5946
|
+
} catch (error) {
|
|
5947
|
+
throw new NativeBuildInputError(`Invalid native build scope: ${error instanceof Error ? error.message : String(error)}.`);
|
|
5948
|
+
}
|
|
5949
|
+
if (scopeKey.length > SCOPE_KEY_MAX_LENGTH) throw new NativeBuildInputError(`Invalid native build scope: its key is longer than ${SCOPE_KEY_MAX_LENGTH} characters; shorten the channel or app version.`);
|
|
5950
|
+
return scopeKey;
|
|
5951
|
+
};
|
|
5952
|
+
/** Whether `bundleId` is a native build's built-in bundle: one unique read. */
|
|
5953
|
+
const isBuiltInBundle = async (db, bundleId) => await db.findOne("native_builds", { min_bundle_id: bundleId }) !== null;
|
|
5954
|
+
const problemOf = ({ id, minBundleId, platform, channel, fingerprintHash, appVersion, gitCommitHash, buildKey, buildNumber, artifact }) => {
|
|
5955
|
+
if (!isUUIDv7(id)) return "id must be a UUIDv7";
|
|
5956
|
+
if (!isUUIDv7(minBundleId)) return "minBundleId must be the UUIDv7 a release build compiles in";
|
|
5957
|
+
if (platform !== "ios" && platform !== "android") return "platform must be ios or android";
|
|
5958
|
+
if (channel.trim().length === 0) return "channel is required";
|
|
5959
|
+
if (gitCommitHash !== null && gitCommitHash.length > GIT_COMMIT_HASH_MAX_LENGTH) return `gitCommitHash must be at most ${GIT_COMMIT_HASH_MAX_LENGTH} characters`;
|
|
5960
|
+
if (fingerprintHash === null === (appVersion === null)) return "exactly one of fingerprintHash and appVersion is required";
|
|
5961
|
+
if (buildKey !== null && !BUILD_KEY.test(buildKey)) return "buildKey must be nbk1: and a SHA-256, or null";
|
|
5962
|
+
if (buildNumber !== void 0 && buildNumber !== null && (buildNumber.trim().length === 0 || buildNumber.length > BUILD_NUMBER_MAX_LENGTH)) return `buildNumber must be the artifact's build number, up to ${BUILD_NUMBER_MAX_LENGTH} characters, or null`;
|
|
5963
|
+
const kinds = ARTIFACT_KINDS[platform];
|
|
5964
|
+
if (!kinds.includes(artifact.kind)) return `artifact.kind of an ${platform} build must be ${kinds.join(" or ")}`;
|
|
5965
|
+
if (!Number.isSafeInteger(artifact.byteSize) || artifact.byteSize < 0) return "artifact.byteSize must be a byte count";
|
|
5966
|
+
return null;
|
|
5967
|
+
};
|
|
5968
|
+
/** The native build registry: core's `native_builds` and the built-in bundle rows. */
|
|
5969
|
+
const createNativeBuildApi = (db, { now = Date.now } = {}) => ({
|
|
5970
|
+
async registerNativeBuild(registration) {
|
|
5971
|
+
const problem = problemOf(registration);
|
|
5972
|
+
if (problem !== null) throw new NativeBuildInputError(`Invalid native build: ${problem}.`);
|
|
5973
|
+
const scopeKey = nativeBuildScopeKey(registration);
|
|
5974
|
+
const channelId = `channel:${encodeChannelKey(registration.channel)}`;
|
|
5975
|
+
const { embeddedBundle } = registration;
|
|
5976
|
+
const row = {
|
|
5977
|
+
id: registration.id,
|
|
5978
|
+
scope_key: scopeKey,
|
|
5979
|
+
platform: registration.platform,
|
|
5980
|
+
channel_id: channelId,
|
|
5981
|
+
fingerprint_hash: registration.fingerprintHash,
|
|
5982
|
+
app_version: registration.appVersion,
|
|
5983
|
+
min_bundle_id: registration.minBundleId,
|
|
5984
|
+
artifact_kind: registration.artifact.kind,
|
|
5985
|
+
artifact_storage_uri: registration.artifact.storageUri,
|
|
5986
|
+
byte_size: registration.artifact.byteSize,
|
|
5987
|
+
git_commit_hash: registration.gitCommitHash,
|
|
5988
|
+
created_at_ms: now(),
|
|
5989
|
+
build_key: registration.buildKey,
|
|
5990
|
+
build_number: registration.buildNumber ?? null,
|
|
5991
|
+
submitted_to: null,
|
|
5992
|
+
submitted_at_ms: null,
|
|
5993
|
+
submitted_version: null
|
|
5994
|
+
};
|
|
5995
|
+
for (let attempt = 1;; attempt += 1) {
|
|
5996
|
+
await insertChannel(db, {
|
|
5997
|
+
id: channelId,
|
|
5998
|
+
name: registration.channel
|
|
5999
|
+
});
|
|
6000
|
+
try {
|
|
6001
|
+
await db.transaction(async (tx) => {
|
|
6002
|
+
tx.create("bundles", {
|
|
6003
|
+
id: registration.minBundleId,
|
|
6004
|
+
platform: registration.platform,
|
|
6005
|
+
git_commit_hash: registration.gitCommitHash,
|
|
6006
|
+
metadata: embeddedBundle.metadata ?? {},
|
|
6007
|
+
manifest_storage_uri: embeddedBundle.manifestStorageUri,
|
|
6008
|
+
manifest_file_hash: embeddedBundle.manifestFileHash,
|
|
6009
|
+
asset_base_storage_uri: embeddedBundle.assetBaseStorageUri
|
|
6010
|
+
});
|
|
6011
|
+
tx.create("native_builds", row);
|
|
6012
|
+
});
|
|
6013
|
+
break;
|
|
6014
|
+
} catch (error) {
|
|
6015
|
+
if (!(error instanceof DatabaseConstraintError)) throw error;
|
|
6016
|
+
if (error.reason === "exists" || error.reason === "unique") throw new DatabaseConstraintError(error.reason, "native_builds");
|
|
6017
|
+
if (attempt < 3 && error.reason === "not_found" && error.model === "channels") continue;
|
|
6018
|
+
throw error;
|
|
6019
|
+
}
|
|
6020
|
+
}
|
|
6021
|
+
return row;
|
|
6022
|
+
},
|
|
6023
|
+
async getNativeBuild(id) {
|
|
6024
|
+
const row = await db.findOne("native_builds", { id });
|
|
6025
|
+
return row === null ? null : pick(row);
|
|
6026
|
+
},
|
|
6027
|
+
async findNativeBuild(scope, options) {
|
|
6028
|
+
const scopeKey = nativeBuildScopeKey(scope);
|
|
6029
|
+
const buildKey = options?.buildKey;
|
|
6030
|
+
if (buildKey !== void 0 && !BUILD_KEY.test(buildKey)) throw new NativeBuildInputError("Invalid native build key: it must be nbk1: and a SHA-256.");
|
|
6031
|
+
const { rows } = buildKey === void 0 ? await db.findMany("native_builds", {
|
|
6032
|
+
index: "byScope",
|
|
6033
|
+
where: { scope_key: scopeKey },
|
|
6034
|
+
order: "desc",
|
|
6035
|
+
limit: 1
|
|
6036
|
+
}) : await db.findMany("native_builds", {
|
|
6037
|
+
index: "byBuildKey",
|
|
6038
|
+
where: {
|
|
6039
|
+
scope_key: scopeKey,
|
|
6040
|
+
build_key: buildKey
|
|
6041
|
+
},
|
|
6042
|
+
order: "desc",
|
|
6043
|
+
limit: 1
|
|
6044
|
+
});
|
|
6045
|
+
return rows[0] === void 0 ? null : pick(rows[0]);
|
|
6046
|
+
},
|
|
6047
|
+
async recordNativeBuildSubmission(id, { destination, version }) {
|
|
6048
|
+
if (version.trim().length === 0 || version.length > SUBMITTED_VERSION_MAX_LENGTH) throw new NativeBuildInputError(`Invalid native build submission: version must be the store's label of the build, up to ${SUBMITTED_VERSION_MAX_LENGTH} characters.`);
|
|
6049
|
+
return db.transaction(async (tx) => {
|
|
6050
|
+
const row = await tx.findOne("native_builds", { id });
|
|
6051
|
+
if (row === null) return null;
|
|
6052
|
+
const build = pick(row);
|
|
6053
|
+
if (SUBMISSION_DESTINATIONS[build.platform] !== destination) throw new NativeBuildInputError(`Invalid native build submission: an ${build.platform} build is submitted to ${SUBMISSION_DESTINATIONS[build.platform]}, not ${destination}.`);
|
|
6054
|
+
const set = {
|
|
6055
|
+
submitted_to: destination,
|
|
6056
|
+
submitted_at_ms: now(),
|
|
6057
|
+
submitted_version: version
|
|
6058
|
+
};
|
|
6059
|
+
tx.update("native_builds", row, set);
|
|
6060
|
+
return {
|
|
6061
|
+
...build,
|
|
6062
|
+
...set
|
|
6063
|
+
};
|
|
6064
|
+
});
|
|
6065
|
+
},
|
|
6066
|
+
async deleteNativeBuild(id) {
|
|
6067
|
+
return db.transaction(async (tx) => {
|
|
6068
|
+
const row = await tx.findOne("native_builds", { id });
|
|
6069
|
+
if (row === null) return {
|
|
6070
|
+
deleted: false,
|
|
6071
|
+
reason: "not_found"
|
|
6072
|
+
};
|
|
6073
|
+
await tx.delete("native_builds", row);
|
|
6074
|
+
const bundle = await tx.findOne("bundles", { id: row.min_bundle_id });
|
|
6075
|
+
if (bundle !== null) await tx.delete("bundles", bundle);
|
|
6076
|
+
return {
|
|
6077
|
+
deleted: true,
|
|
6078
|
+
nativeBuild: pick(row)
|
|
6079
|
+
};
|
|
6080
|
+
});
|
|
6081
|
+
},
|
|
6082
|
+
async listNativeBuilds(input) {
|
|
6083
|
+
const page = {
|
|
6084
|
+
order: input.order ?? "asc",
|
|
6085
|
+
limit: input.limit
|
|
6086
|
+
};
|
|
6087
|
+
const { rows } = input.channelId === void 0 ? await db.findMany("native_builds", {
|
|
6088
|
+
index: "all",
|
|
6089
|
+
where: {},
|
|
6090
|
+
...page,
|
|
6091
|
+
...after(input)
|
|
6092
|
+
}) : await db.findMany("native_builds", {
|
|
6093
|
+
index: "byChannel",
|
|
6094
|
+
where: { channel_id: input.channelId },
|
|
6095
|
+
...page,
|
|
6096
|
+
...after(input)
|
|
6097
|
+
});
|
|
6098
|
+
return rows.map(pick);
|
|
6099
|
+
}
|
|
6100
|
+
});
|
|
6101
|
+
//#endregion
|
|
5706
6102
|
//#region ../../packages/server/dist/core/releases.mjs
|
|
5707
6103
|
const PAGE$1 = 500;
|
|
5708
6104
|
const fail$1 = (code, message) => {
|
|
@@ -6016,6 +6412,7 @@ const createCoreOperations = (db, { now = Date.now } = {}) => {
|
|
|
6016
6412
|
if (!("bundleId" in deployment) || platforms.has(deployment.bundleId)) continue;
|
|
6017
6413
|
const row = await db.findOne("bundles", { id: deployment.bundleId });
|
|
6018
6414
|
if (row === null) throw new DatabaseBundleNotFoundError(deployment.bundleId);
|
|
6415
|
+
if (await isBuiltInBundle(db, deployment.bundleId)) throw new ReleaseManagementError("TARGET_RELEASE_INVALID", `Bundle "${deployment.bundleId}" is a native build's built-in bundle, which no release publishes.`);
|
|
6019
6416
|
platforms.set(deployment.bundleId, toBundleRow(row).platform);
|
|
6020
6417
|
}
|
|
6021
6418
|
const updatedAtMs = now();
|
|
@@ -6211,7 +6608,8 @@ const createCoreOperations = (db, { now = Date.now } = {}) => {
|
|
|
6211
6608
|
id
|
|
6212
6609
|
};
|
|
6213
6610
|
const { id: _id, ...set } = bundleToRow(next);
|
|
6214
|
-
|
|
6611
|
+
const counted = set.platform === current.platform || !await isBuiltInBundle(tx, id);
|
|
6612
|
+
updateBundle(tx, current, set, { counted });
|
|
6215
6613
|
if (patches !== void 0) await replaceBundlePatches(tx, current, bundleToPatchRows({
|
|
6216
6614
|
...next,
|
|
6217
6615
|
patches
|
|
@@ -6222,7 +6620,9 @@ const createCoreOperations = (db, { now = Date.now } = {}) => {
|
|
|
6222
6620
|
await db.transaction(async (tx) => {
|
|
6223
6621
|
for (const id of new Set(ids)) {
|
|
6224
6622
|
const current = await tx.findOne("bundles", { id });
|
|
6225
|
-
if (current
|
|
6623
|
+
if (current === null) continue;
|
|
6624
|
+
if (await isBuiltInBundle(tx, id)) throw new DatabaseRowReferencedError();
|
|
6625
|
+
await deleteBundle(tx, current);
|
|
6226
6626
|
}
|
|
6227
6627
|
});
|
|
6228
6628
|
} catch (error) {
|
|
@@ -6274,6 +6674,7 @@ const createCoreApi = (db, storage, options = {}) => {
|
|
|
6274
6674
|
return {
|
|
6275
6675
|
...reads,
|
|
6276
6676
|
...createCoreOperations(purgingCachedRoutes(db, options.onCachedRoutesChange), options),
|
|
6677
|
+
...createNativeBuildApi(db, options),
|
|
6277
6678
|
/** One read, which checks the schema settings first. */
|
|
6278
6679
|
ready: async () => {
|
|
6279
6680
|
await reads.listReleaseCatalogs({ limit: 1 });
|
|
@@ -6474,6 +6875,10 @@ const answer = async (operation) => {
|
|
|
6474
6875
|
code: "REFERENCED",
|
|
6475
6876
|
error: error.message
|
|
6476
6877
|
}, 409);
|
|
6878
|
+
if (error instanceof DatabaseConstraintError && error.model === "native_builds") return json$2({
|
|
6879
|
+
code: "NATIVE_BUILD_EXISTS",
|
|
6880
|
+
error: "A native build with this ID or built-in bundle ID is already registered."
|
|
6881
|
+
}, 409);
|
|
6477
6882
|
throw error;
|
|
6478
6883
|
}
|
|
6479
6884
|
};
|
|
@@ -6576,6 +6981,42 @@ const bundleUpdateOf = (value, bundleId) => {
|
|
|
6576
6981
|
if (id !== void 0 && id !== bundleId) throw new HandlerBadRequestError("Bundle id mismatch");
|
|
6577
6982
|
return update;
|
|
6578
6983
|
};
|
|
6984
|
+
const NATIVE_BUILD_ARTIFACT_KINDS = /* @__PURE__ */ new Set([
|
|
6985
|
+
"apk",
|
|
6986
|
+
"aab",
|
|
6987
|
+
"ipa",
|
|
6988
|
+
"app"
|
|
6989
|
+
]);
|
|
6990
|
+
/** A native build's scope: `fingerprintHash` or `appVersion`, never both. */
|
|
6991
|
+
const nativeBuildScopeOf = (url) => {
|
|
6992
|
+
const params = url.searchParams;
|
|
6993
|
+
const platform = platformOf(url);
|
|
6994
|
+
const channel = params.get("channel");
|
|
6995
|
+
const fingerprintHash = params.get("fingerprintHash");
|
|
6996
|
+
const appVersion = params.get("appVersion");
|
|
6997
|
+
if (platform === void 0 || !isText$2(channel) || fingerprintHash === null === (appVersion === null) || fingerprintHash === "" || appVersion === "") throw new HandlerBadRequestError("A native build lookup needs platform, channel, and fingerprintHash or appVersion.");
|
|
6998
|
+
return fingerprintHash !== null ? {
|
|
6999
|
+
platform,
|
|
7000
|
+
channel,
|
|
7001
|
+
fingerprintHash,
|
|
7002
|
+
appVersion: null
|
|
7003
|
+
} : {
|
|
7004
|
+
platform,
|
|
7005
|
+
channel,
|
|
7006
|
+
fingerprintHash: null,
|
|
7007
|
+
appVersion
|
|
7008
|
+
};
|
|
7009
|
+
};
|
|
7010
|
+
/** A native build or scope core refuses: a bad request. */
|
|
7011
|
+
const refusingInvalid = async (operation) => {
|
|
7012
|
+
try {
|
|
7013
|
+
return await operation();
|
|
7014
|
+
} catch (error) {
|
|
7015
|
+
if (error instanceof NativeBuildInputError) throw new HandlerBadRequestError(error.message);
|
|
7016
|
+
throw error;
|
|
7017
|
+
}
|
|
7018
|
+
};
|
|
7019
|
+
const isNativeBuildRegistration = (value) => isRecord$5(value) && isText$2(value.id) && isText$2(value.minBundleId) && typeof value.platform === "string" && isPlatform(value.platform) && isText$2(value.channel) && isTextOrNull$1(value.fingerprintHash) && isTextOrNull$1(value.appVersion) && value.fingerprintHash === null !== (value.appVersion === null) && isTextOrNull$1(value.gitCommitHash) && isTextOrNull$1(value.buildKey) && (value.buildNumber === void 0 || isTextOrNull$1(value.buildNumber)) && isRecord$5(value.artifact) && typeof value.artifact.kind === "string" && NATIVE_BUILD_ARTIFACT_KINDS.has(value.artifact.kind) && isText$2(value.artifact.storageUri) && Number.isSafeInteger(value.artifact.byteSize) && value.artifact.byteSize >= 0 && isRecord$5(value.embeddedBundle) && isText$2(value.embeddedBundle.manifestStorageUri) && isText$2(value.embeddedBundle.manifestFileHash) && isText$2(value.embeddedBundle.assetBaseStorageUri) && (value.embeddedBundle.metadata === void 0 || isRecord$5(value.embeddedBundle.metadata));
|
|
6579
7020
|
const scopeKeyOf = (params) => decodeMaybe(requireRouteParam(params, "scopeKey")) ?? "";
|
|
6580
7021
|
/** Every admin route's handler, by the name `ADMIN_ROUTES` mounts it under. */
|
|
6581
7022
|
const createAdminRouteHandlers = () => ({
|
|
@@ -6695,6 +7136,37 @@ const createAdminRouteHandlers = () => ({
|
|
|
6695
7136
|
}) });
|
|
6696
7137
|
}),
|
|
6697
7138
|
preflightReleaseCatalogRebuild: onCore(async (core, _request, params) => json$2({ data: await core.preflightReleaseCatalogRebuild(scopeKeyOf(params)) })),
|
|
7139
|
+
registerNativeBuild: onCore(async (core, request) => {
|
|
7140
|
+
const input = await body(request);
|
|
7141
|
+
if (!isNativeBuildRegistration(input)) throw new HandlerBadRequestError("Invalid native build");
|
|
7142
|
+
return json$2({ data: await refusingInvalid(() => core.registerNativeBuild(input)) }, 201);
|
|
7143
|
+
}),
|
|
7144
|
+
findNativeBuild: onCore(async (core, request) => {
|
|
7145
|
+
const url = new URL(request.url);
|
|
7146
|
+
const scope = nativeBuildScopeOf(url);
|
|
7147
|
+
const buildKey = url.searchParams.get("buildKey");
|
|
7148
|
+
return json$2({ data: await refusingInvalid(() => buildKey === null ? core.findNativeBuild(scope) : core.findNativeBuild(scope, { buildKey })) });
|
|
7149
|
+
}),
|
|
7150
|
+
getNativeBuild: onCore(async (core, _request, params) => json$2({ data: await core.getNativeBuild(requireRouteParam(params, "id")) })),
|
|
7151
|
+
listNativeBuilds: onCore(async (core, request) => {
|
|
7152
|
+
const url = new URL(request.url);
|
|
7153
|
+
const input = keyset(url);
|
|
7154
|
+
const channelId = url.searchParams.get("channelId");
|
|
7155
|
+
return page(await core.listNativeBuilds({
|
|
7156
|
+
...input,
|
|
7157
|
+
...channelId === null ? {} : { channelId }
|
|
7158
|
+
}), input, ({ id }) => id);
|
|
7159
|
+
}),
|
|
7160
|
+
recordNativeBuildSubmission: onCore(async (core, request, params) => {
|
|
7161
|
+
const input = await body(request);
|
|
7162
|
+
if (!isRecord$5(input) || input.destination !== "testflight" && input.destination !== "play-internal" || !isText$2(input.version)) throw new HandlerBadRequestError("A native build submission needs destination (testflight or play-internal) and version.");
|
|
7163
|
+
const submission = {
|
|
7164
|
+
destination: input.destination,
|
|
7165
|
+
version: input.version
|
|
7166
|
+
};
|
|
7167
|
+
return json$2({ data: await refusingInvalid(() => core.recordNativeBuildSubmission(requireRouteParam(params, "id"), submission)) });
|
|
7168
|
+
}),
|
|
7169
|
+
deleteNativeBuild: onCore(async (core, _request, params) => json$2({ data: await core.deleteNativeBuild(requireRouteParam(params, "id")) })),
|
|
6698
7170
|
deleteBundles: onCore(async (core, request) => {
|
|
6699
7171
|
const { ids } = await body(request);
|
|
6700
7172
|
if (!Array.isArray(ids) || !ids.every((id) => typeof id === "string" && id.length > 0)) throw new HandlerBadRequestError("Bundle deletion needs ids");
|
|
@@ -6813,6 +7285,36 @@ const ADMIN_ROUTES = [
|
|
|
6813
7285
|
method: "GET",
|
|
6814
7286
|
path: "/base-candidates/:candidateKey",
|
|
6815
7287
|
handler: "findBaseCandidates"
|
|
7288
|
+
},
|
|
7289
|
+
{
|
|
7290
|
+
method: "POST",
|
|
7291
|
+
path: "/native-builds",
|
|
7292
|
+
handler: "registerNativeBuild"
|
|
7293
|
+
},
|
|
7294
|
+
{
|
|
7295
|
+
method: "GET",
|
|
7296
|
+
path: "/native-builds/latest",
|
|
7297
|
+
handler: "findNativeBuild"
|
|
7298
|
+
},
|
|
7299
|
+
{
|
|
7300
|
+
method: "GET",
|
|
7301
|
+
path: "/native-builds/:id",
|
|
7302
|
+
handler: "getNativeBuild"
|
|
7303
|
+
},
|
|
7304
|
+
{
|
|
7305
|
+
method: "GET",
|
|
7306
|
+
path: "/native-builds",
|
|
7307
|
+
handler: "listNativeBuilds"
|
|
7308
|
+
},
|
|
7309
|
+
{
|
|
7310
|
+
method: "POST",
|
|
7311
|
+
path: "/native-builds/:id/submission",
|
|
7312
|
+
handler: "recordNativeBuildSubmission"
|
|
7313
|
+
},
|
|
7314
|
+
{
|
|
7315
|
+
method: "DELETE",
|
|
7316
|
+
path: "/native-builds/:id",
|
|
7317
|
+
handler: "deleteNativeBuild"
|
|
6816
7318
|
}
|
|
6817
7319
|
];
|
|
6818
7320
|
//#endregion
|
|
@@ -13807,6 +14309,14 @@ const adapterOf = ({ tableName, ...clientConfig }) => {
|
|
|
13807
14309
|
};
|
|
13808
14310
|
};
|
|
13809
14311
|
/**
|
|
14312
|
+
* The resource this database reaches, without credentials, under the key
|
|
14313
|
+
* `hot-updater doctor` reads to tell the database `hot-updater init` saved
|
|
14314
|
+
* from another (a convention CLAUDE.md documents).
|
|
14315
|
+
*/
|
|
14316
|
+
const DATABASE_TARGET = Symbol.for("@hot-updater/database-target");
|
|
14317
|
+
/** `target` under {@link DATABASE_TARGET}, to spread into the database. */
|
|
14318
|
+
const marked = (target) => ({ [DATABASE_TARGET]: target });
|
|
14319
|
+
/**
|
|
13810
14320
|
* Hot Updater's database on one DynamoDB table, through the storage engine.
|
|
13811
14321
|
* With a distribution, it gives core the CloudFront invalidation core runs
|
|
13812
14322
|
* after a write that changes what the update-check routes answer.
|
|
@@ -13821,6 +14331,7 @@ const dynamoDB = (config) => {
|
|
|
13821
14331
|
onCachedRoutesChange: cloudFront?.invalidate,
|
|
13822
14332
|
aggregateBatching: config.aggregateBatching ?? {}
|
|
13823
14333
|
}),
|
|
14334
|
+
...marked(`dynamodb/${config.tableName}`),
|
|
13824
14335
|
async dispose() {
|
|
13825
14336
|
await adapter.dispose?.();
|
|
13826
14337
|
client.destroy();
|