@hot-updater/aws 1.0.0-rc.1 → 1.0.0-rc.16

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.
@@ -6,7 +6,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
9
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
10
10
  var __copyProps = (to, from, except, desc) => {
11
11
  if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
12
12
  key = keys[i];
@@ -17,25 +17,25 @@ var __copyProps = (to, from, except, desc) => {
17
17
  }
18
18
  return to;
19
19
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
21
21
  value: mod,
22
22
  enumerable: true
23
23
  }) : target, mod));
24
24
  //#endregion
25
25
  let _hot_updater_cli_tools = require("@hot-updater/cli-tools");
26
- let _hot_updater_server = require("@hot-updater/server");
26
+ let _hot_updater_server_db = require("@hot-updater/server/db");
27
27
  let node_url = require("node:url");
28
28
  let node_child_process = require("node:child_process");
29
29
  let node_string_decoder = require("node:string_decoder");
30
30
  let node_util = require("node:util");
31
31
  let node_process = require("node:process");
32
- node_process = __toESM(node_process);
32
+ node_process = __toESM(node_process, 1);
33
33
  let node_tty = require("node:tty");
34
- node_tty = __toESM(node_tty);
34
+ node_tty = __toESM(node_tty, 1);
35
35
  let node_path = require("node:path");
36
- node_path = __toESM(node_path);
36
+ node_path = __toESM(node_path, 1);
37
37
  let path = require("path");
38
- path = __toESM(path);
38
+ path = __toESM(path, 1);
39
39
  let node_timers_promises = require("node:timers/promises");
40
40
  let node_os = require("node:os");
41
41
  let node_events = require("node:events");
@@ -44,20 +44,21 @@ let node_fs = require("node:fs");
44
44
  let node_stream_promises = require("node:stream/promises");
45
45
  let node_stream = require("node:stream");
46
46
  let node_buffer = require("node:buffer");
47
- let _aws_sdk_client_cloudfront = require("@aws-sdk/client-cloudfront");
48
47
  let _aws_sdk_client_dynamodb = require("@aws-sdk/client-dynamodb");
48
+ let _hot_updater_server_database = require("@hot-updater/server/database");
49
+ let _aws_sdk_client_cloudfront = require("@aws-sdk/client-cloudfront");
49
50
  let _aws_sdk_lib_dynamodb = require("@aws-sdk/lib-dynamodb");
50
- let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
51
- let _hot_updater_plugin_core_internal = require("@hot-updater/plugin-core/internal");
51
+ let _hot_updater_server_plugins_api_keys = require("@hot-updater/server/plugins/api-keys");
52
+ let _hot_updater_server_plugins_insights = require("@hot-updater/server/plugins/insights");
52
53
  let _aws_sdk_credential_providers = require("@aws-sdk/credential-providers");
53
54
  let _aws_sdk_client_lambda = require("@aws-sdk/client-lambda");
54
- let crypto = require("crypto");
55
- crypto = __toESM(crypto);
55
+ let crypto$1 = require("crypto");
56
+ crypto$1 = __toESM(crypto$1, 1);
56
57
  let node_crypto = require("node:crypto");
57
58
  let _aws_sdk_client_iam = require("@aws-sdk/client-iam");
58
59
  let _aws_sdk_client_sts = require("@aws-sdk/client-sts");
59
60
  let fs_promises = require("fs/promises");
60
- fs_promises = __toESM(fs_promises);
61
+ fs_promises = __toESM(fs_promises, 1);
61
62
  let _aws_sdk_client_s3 = require("@aws-sdk/client-s3");
62
63
  let _aws_sdk_client_ssm = require("@aws-sdk/client-ssm");
63
64
  //#region ../../node_modules/.pnpm/is-plain-obj@4.1.0/node_modules/is-plain-obj/index.js
@@ -158,7 +159,8 @@ const parseTemplate = ({ templates, expressions, tokens, index, template }) => {
158
159
  const newTokens = concatTokens(tokens, nextTokens, leadingWhitespaces);
159
160
  if (index === expressions.length) return newTokens;
160
161
  const expression = expressions[index];
161
- return concatTokens(newTokens, Array.isArray(expression) ? expression.map((expression) => parseExpression(expression)) : [parseExpression(expression)], trailingWhitespaces);
162
+ const expressionTokens = Array.isArray(expression) ? expression.map((expression) => parseExpression(expression)) : [parseExpression(expression)];
163
+ return concatTokens(newTokens, expressionTokens, trailingWhitespaces);
162
164
  };
163
165
  const splitByWhitespaces = (template, rawTemplate) => {
164
166
  if (rawTemplate.length === 0) return {
@@ -191,7 +193,7 @@ const splitByWhitespaces = (template, rawTemplate) => {
191
193
  trailingWhitespaces
192
194
  };
193
195
  };
194
- const DELIMITERS = new Set([
196
+ const DELIMITERS = /* @__PURE__ */ new Set([
195
197
  " ",
196
198
  " ",
197
199
  "\r",
@@ -243,7 +245,8 @@ const normalizeFdSpecificOptions = (options) => {
243
245
  };
244
246
  const normalizeFdSpecificOption = (options, optionName) => {
245
247
  const optionBaseArray = Array.from({ length: getStdioLength(options) + 1 });
246
- return addDefaultValue$1(normalizeFdSpecificValue(options[optionName], optionBaseArray, optionName), optionName);
248
+ const optionArray = normalizeFdSpecificValue(options[optionName], optionBaseArray, optionName);
249
+ return addDefaultValue$1(optionArray, optionName);
247
250
  };
248
251
  const getStdioLength = ({ stdio }) => Array.isArray(stdio) ? Math.max(stdio.length, STANDARD_STREAMS_ALIASES.length) : STANDARD_STREAMS_ALIASES.length;
249
252
  const normalizeFdSpecificValue = (optionValue, optionArray, optionName) => isPlainObject(optionValue) ? normalizeOptionObject(optionValue, optionArray, optionName) : optionArray.fill(optionValue);
@@ -276,7 +279,7 @@ const addDefaultValue$1 = (optionArray, optionName) => optionArray.map((optionVa
276
279
  const DEFAULT_OPTIONS = {
277
280
  lines: false,
278
281
  buffer: true,
279
- maxBuffer: 1e3 * 1e3 * 100,
282
+ maxBuffer: 1e8,
280
283
  verbose: (0, node_util.debuglog)("execa").enabled ? "full" : "none",
281
284
  stripFinalNewline: true
282
285
  };
@@ -324,7 +327,7 @@ const escapeControlCharacter = (character) => {
324
327
  };
325
328
  const getSpecialCharRegExp = () => {
326
329
  try {
327
- return /* @__PURE__ */ new RegExp("\\p{Separator}|\\p{Other}", "gu");
330
+ return new RegExp("\\p{Separator}|\\p{Other}", "gu");
328
331
  } catch {
329
332
  return /[\s\u0000-\u001F\u007F-\u009F\u00AD]/g;
330
333
  }
@@ -744,11 +747,13 @@ const appendNewline = (printedLine) => printedLine.endsWith("\n") ? printedLine
744
747
  //#endregion
745
748
  //#region ../../node_modules/.pnpm/execa@9.5.2/node_modules/execa/lib/verbose/log.js
746
749
  const verboseLog = ({ type, verboseMessage, fdNumber, verboseInfo, result }) => {
747
- const finalLines = applyVerboseOnLines(getPrintedLines(verboseMessage, getVerboseObject({
750
+ const verboseObject = getVerboseObject({
748
751
  type,
749
752
  result,
750
753
  verboseInfo
751
- })), verboseInfo, fdNumber);
754
+ });
755
+ const printedLines = getPrintedLines(verboseMessage, verboseObject);
756
+ const finalLines = applyVerboseOnLines(printedLines, verboseInfo, fdNumber);
752
757
  if (finalLines !== "") console.warn(finalLines.slice(0, -1));
753
758
  };
754
759
  const getVerboseObject = ({ type, result, verboseInfo: { escapedCommand, commandId, rawOptions: { piped = false, ...options } } }) => ({
@@ -771,7 +776,8 @@ const getPrintedLine = (verboseObject) => {
771
776
  };
772
777
  };
773
778
  const serializeVerboseMessage = (message) => {
774
- return escapeLines(typeof message === "string" ? message : (0, node_util.inspect)(message)).replaceAll(" ", " ".repeat(TAB_SIZE));
779
+ const messageString = typeof message === "string" ? message : (0, node_util.inspect)(message);
780
+ return escapeLines(messageString).replaceAll(" ", " ".repeat(TAB_SIZE));
775
781
  };
776
782
  const TAB_SIZE = 2;
777
783
  //#endregion
@@ -816,7 +822,8 @@ const getDurationMs = (startTime) => Number(node_process.hrtime.bigint() - start
816
822
  const handleCommand = (filePath, rawArguments, rawOptions) => {
817
823
  const startTime = getStartTime();
818
824
  const { command, escapedCommand } = joinCommand(filePath, rawArguments);
819
- const verboseInfo = getVerboseInfo(normalizeFdSpecificOption(rawOptions, "verbose"), escapedCommand, { ...rawOptions });
825
+ const verbose = normalizeFdSpecificOption(rawOptions, "verbose");
826
+ const verboseInfo = getVerboseInfo(verbose, escapedCommand, { ...rawOptions });
820
827
  logCommand(escapedCommand, verboseInfo);
821
828
  return {
822
829
  command,
@@ -937,7 +944,8 @@ var require_which = /* @__PURE__ */ __commonJSMin(((exports, module) => {
937
944
  const getNotFoundError = (cmd) => Object.assign(/* @__PURE__ */ new Error(`not found: ${cmd}`), { code: "ENOENT" });
938
945
  const getPathInfo = (cmd, opt) => {
939
946
  const colon = opt.colon || COLON;
940
- const pathEnv = cmd.match(/\//) || isWindows && cmd.match(/\\/) ? [""] : [...isWindows ? [process.cwd()] : [], ...(opt.path || process.env.PATH || "").split(colon)];
947
+ const pathEnv = cmd.match(/\//) || isWindows && cmd.match(/\\/) ? [""] : [...isWindows ? [process.cwd()] : [], ...(opt.path || process.env.PATH ||
948
+ /* istanbul ignore next: very unusual */ "").split(colon)];
941
949
  const pathExtExe = isWindows ? opt.pathExt || process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM" : "";
942
950
  const pathExt = isWindows ? pathExtExe.split(colon) : [""];
943
951
  if (isWindows) {
@@ -962,14 +970,17 @@ var require_which = /* @__PURE__ */ __commonJSMin(((exports, module) => {
962
970
  const ppRaw = pathEnv[i];
963
971
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
964
972
  const pCmd = path$9.join(pathPart, cmd);
965
- resolve(subStep(!pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd, i, 0));
973
+ const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
974
+ resolve(subStep(p, i, 0));
966
975
  });
967
976
  const subStep = (p, i, ii) => new Promise((resolve, reject) => {
968
977
  if (ii === pathExt.length) return resolve(step(i + 1));
969
978
  const ext = pathExt[ii];
970
979
  isexe(p + ext, { pathExt: pathExtExe }, (er, is) => {
971
- if (!er && is) if (opt.all) found.push(p + ext);
972
- else return resolve(p + ext);
980
+ if (!er && is) {
981
+ if (opt.all) found.push(p + ext);
982
+ else return resolve(p + ext);
983
+ }
973
984
  return resolve(subStep(p, i, ii + 1));
974
985
  });
975
986
  });
@@ -987,8 +998,10 @@ var require_which = /* @__PURE__ */ __commonJSMin(((exports, module) => {
987
998
  for (let j = 0; j < pathExt.length; j++) {
988
999
  const cur = p + pathExt[j];
989
1000
  try {
990
- if (isexe.sync(cur, { pathExt: pathExtExe })) if (opt.all) found.push(cur);
991
- else return cur;
1001
+ if (isexe.sync(cur, { pathExt: pathExtExe })) {
1002
+ if (opt.all) found.push(cur);
1003
+ else return cur;
1004
+ }
992
1005
  } catch (ex) {}
993
1006
  }
994
1007
  }
@@ -1679,7 +1692,7 @@ const normalizeForceKillAfterDelay = (forceKillAfterDelay) => {
1679
1692
  if (!Number.isFinite(forceKillAfterDelay) || forceKillAfterDelay < 0) throw new TypeError(`Expected the \`forceKillAfterDelay\` option to be a non-negative integer, got \`${forceKillAfterDelay}\` (${typeof forceKillAfterDelay})`);
1680
1693
  return forceKillAfterDelay;
1681
1694
  };
1682
- const DEFAULT_FORCE_KILL_TIMEOUT = 1e3 * 5;
1695
+ const DEFAULT_FORCE_KILL_TIMEOUT = 5e3;
1683
1696
  const subprocessKill = ({ kill, options: { forceKillAfterDelay, killSignal }, onInternalError, context, controller }, signalOrError, errorArgument) => {
1684
1697
  const { signal, error } = parseKillArguments(signalOrError, errorArgument, killSignal);
1685
1698
  emitKillError(error, onInternalError);
@@ -1781,7 +1794,7 @@ const handleSerializationError = ({ error, methodName, isSubprocess, message })
1781
1794
  if (isSerializationError(error)) throw new Error(`${getMethodName(methodName, isSubprocess)}'s argument type is invalid: the message cannot be serialized: ${String(message)}.`, { cause: error });
1782
1795
  };
1783
1796
  const isSerializationError = ({ code, message }) => SERIALIZATION_ERROR_CODES.has(code) || SERIALIZATION_ERROR_MESSAGES.some((serializationErrorMessage) => message.includes(serializationErrorMessage));
1784
- const SERIALIZATION_ERROR_CODES = new Set(["ERR_MISSING_ARGS", "ERR_INVALID_ARG_TYPE"]);
1797
+ const SERIALIZATION_ERROR_CODES = /* @__PURE__ */ new Set(["ERR_MISSING_ARGS", "ERR_INVALID_ARG_TYPE"]);
1785
1798
  const SERIALIZATION_ERROR_MESSAGES = [
1786
1799
  "could not be cloned",
1787
1800
  "circular structure",
@@ -1991,7 +2004,8 @@ const isConnected = (anyProcess) => {
1991
2004
  //#region ../../node_modules/.pnpm/execa@9.5.2/node_modules/execa/lib/ipc/strict.js
1992
2005
  const handleSendStrict = ({ anyProcess, channel, isSubprocess, message, strict }) => {
1993
2006
  if (!strict) return message;
1994
- const hasListeners = hasMessageListeners(anyProcess, getIpcEmitter(anyProcess, channel, isSubprocess));
2007
+ const ipcEmitter = getIpcEmitter(anyProcess, channel, isSubprocess);
2008
+ const hasListeners = hasMessageListeners(anyProcess, ipcEmitter);
1995
2009
  return {
1996
2010
  id: count++,
1997
2011
  type: REQUEST_TYPE,
@@ -2223,7 +2237,8 @@ const throwOnGracefulCancel = ({ subprocess, cancelSignal, gracefulCancel, force
2223
2237
  })] : [];
2224
2238
  const sendOnAbort = async ({ subprocess, cancelSignal, forceKillAfterDelay, context, controller: { signal } }) => {
2225
2239
  await onAbortedSignal(cancelSignal, signal);
2226
- await sendAbort(subprocess, getReason(cancelSignal));
2240
+ const reason = getReason(cancelSignal);
2241
+ await sendAbort(subprocess, reason);
2227
2242
  killOnTimeout({
2228
2243
  kill: subprocess.kill,
2229
2244
  forceKillAfterDelay,
@@ -2335,8 +2350,8 @@ Please rename it to ${serializeEncoding(correctEncoding)}.`);
2335
2350
  throw new TypeError(`Invalid option \`encoding: ${serializeEncoding(encoding)}\`.
2336
2351
  Please rename it to one of: ${correctEncodings}.`);
2337
2352
  };
2338
- const TEXT_ENCODINGS = new Set(["utf8", "utf16le"]);
2339
- const BINARY_ENCODINGS = new Set([
2353
+ const TEXT_ENCODINGS = /* @__PURE__ */ new Set(["utf8", "utf16le"]);
2354
+ const BINARY_ENCODINGS = /* @__PURE__ */ new Set([
2340
2355
  "buffer",
2341
2356
  "hex",
2342
2357
  "base64",
@@ -2344,7 +2359,7 @@ const BINARY_ENCODINGS = new Set([
2344
2359
  "latin1",
2345
2360
  "ascii"
2346
2361
  ]);
2347
- const ENCODINGS = new Set([...TEXT_ENCODINGS, ...BINARY_ENCODINGS]);
2362
+ const ENCODINGS = /* @__PURE__ */ new Set([...TEXT_ENCODINGS, ...BINARY_ENCODINGS]);
2348
2363
  const getCorrectEncoding = (encoding) => {
2349
2364
  if (encoding === null) return "buffer";
2350
2365
  if (typeof encoding !== "string") return;
@@ -2391,7 +2406,8 @@ const normalizeOptions = (filePath, rawArguments, rawOptions) => {
2391
2406
  rawOptions.cwd = normalizeCwd(rawOptions.cwd);
2392
2407
  const [processedFile, processedArguments, processedOptions] = handleNodeOption(filePath, rawArguments, rawOptions);
2393
2408
  const { command: file, args: commandArguments, options: initialOptions } = import_cross_spawn.default._parse(processedFile, processedArguments, processedOptions);
2394
- const options = addDefaultOptions(normalizeFdSpecificOptions(initialOptions));
2409
+ const fdOptions = normalizeFdSpecificOptions(initialOptions);
2410
+ const options = addDefaultOptions(fdOptions);
2395
2411
  validateTimeout(options);
2396
2412
  validateEncoding(options);
2397
2413
  validateIpcInputOption(options);
@@ -2592,14 +2608,17 @@ const getStreamContents$1 = async (stream, { init, convertChunk, getSize, trunca
2592
2608
  const state = init();
2593
2609
  state.length = 0;
2594
2610
  try {
2595
- for await (const chunk of asyncIterable) appendChunk({
2596
- convertedChunk: convertChunk[getChunkType(chunk)](chunk, state),
2597
- state,
2598
- getSize,
2599
- truncateChunk,
2600
- addChunk,
2601
- maxBuffer
2602
- });
2611
+ for await (const chunk of asyncIterable) {
2612
+ const convertedChunk = convertChunk[getChunkType(chunk)](chunk, state);
2613
+ appendChunk({
2614
+ convertedChunk,
2615
+ state,
2616
+ getSize,
2617
+ truncateChunk,
2618
+ addChunk,
2619
+ maxBuffer
2620
+ });
2621
+ }
2603
2622
  appendFinalChunk({
2604
2623
  state,
2605
2624
  convertChunk,
@@ -2885,7 +2904,8 @@ const getErrorPrefix = ({ originalError, timedOut, timeout, isMaxBuffer, maxBuff
2885
2904
  const getForcefulSuffix = (isForcefullyTerminated, forceKillAfterDelay) => isForcefullyTerminated ? ` and was forcefully terminated after ${forceKillAfterDelay} milliseconds` : "";
2886
2905
  const getOriginalMessage = (originalError, cwd) => {
2887
2906
  if (originalError instanceof DiscardedError) return;
2888
- const escapedOriginalMessage = escapeLines(fixCwdError(isExecaError(originalError) ? originalError.originalMessage : String(originalError?.message ?? originalError), cwd));
2907
+ const originalMessage = isExecaError(originalError) ? originalError.originalMessage : String(originalError?.message ?? originalError);
2908
+ const escapedOriginalMessage = escapeLines(fixCwdError(originalMessage, cwd));
2889
2909
  return escapedOriginalMessage === "" ? void 0 : escapedOriginalMessage;
2890
2910
  };
2891
2911
  const serializeIpcMessage = (ipcMessage) => typeof ipcMessage === "string" ? ipcMessage : (0, node_util.inspect)(ipcMessage);
@@ -3096,26 +3116,28 @@ function prettyMilliseconds(milliseconds, options) {
3096
3116
  add(Number(parsed.hours), "hour", "h");
3097
3117
  }
3098
3118
  add(Number(parsed.minutes), "minute", "m");
3099
- if (!options.hideSeconds) if (options.separateMilliseconds || options.formatSubMilliseconds || !options.colonNotation && milliseconds < 1e3 && !options.subSecondsAsDecimals) {
3100
- const seconds = Number(parsed.seconds);
3101
- const milliseconds = Number(parsed.milliseconds);
3102
- const microseconds = Number(parsed.microseconds);
3103
- const nanoseconds = Number(parsed.nanoseconds);
3104
- add(seconds, "second", "s");
3105
- if (options.formatSubMilliseconds) {
3106
- add(milliseconds, "millisecond", "ms");
3107
- add(microseconds, "microsecond", "µs");
3108
- add(nanoseconds, "nanosecond", "ns");
3119
+ if (!options.hideSeconds) {
3120
+ if (options.separateMilliseconds || options.formatSubMilliseconds || !options.colonNotation && milliseconds < 1e3 && !options.subSecondsAsDecimals) {
3121
+ const seconds = Number(parsed.seconds);
3122
+ const milliseconds = Number(parsed.milliseconds);
3123
+ const microseconds = Number(parsed.microseconds);
3124
+ const nanoseconds = Number(parsed.nanoseconds);
3125
+ add(seconds, "second", "s");
3126
+ if (options.formatSubMilliseconds) {
3127
+ add(milliseconds, "millisecond", "ms");
3128
+ add(microseconds, "microsecond", "µs");
3129
+ add(nanoseconds, "nanosecond", "ns");
3130
+ } else {
3131
+ const millisecondsAndBelow = milliseconds + microseconds / 1e3 + nanoseconds / 1e6;
3132
+ const millisecondsDecimalDigits = typeof options.millisecondsDecimalDigits === "number" ? options.millisecondsDecimalDigits : 0;
3133
+ const millisecondsString = millisecondsDecimalDigits ? millisecondsAndBelow.toFixed(millisecondsDecimalDigits) : millisecondsAndBelow >= 1 ? Math.round(millisecondsAndBelow) : Math.ceil(millisecondsAndBelow);
3134
+ add(Number.parseFloat(millisecondsString), "millisecond", "ms", millisecondsString);
3135
+ }
3109
3136
  } else {
3110
- const millisecondsAndBelow = milliseconds + microseconds / 1e3 + nanoseconds / 1e6;
3111
- const millisecondsDecimalDigits = typeof options.millisecondsDecimalDigits === "number" ? options.millisecondsDecimalDigits : 0;
3112
- const millisecondsString = millisecondsDecimalDigits ? millisecondsAndBelow.toFixed(millisecondsDecimalDigits) : millisecondsAndBelow >= 1 ? Math.round(millisecondsAndBelow) : Math.ceil(millisecondsAndBelow);
3113
- add(Number.parseFloat(millisecondsString), "millisecond", "ms", millisecondsString);
3137
+ const secondsFixed = floorDecimals((isBigInt ? Number(milliseconds % ONE_DAY_IN_MILLISECONDS) : milliseconds) / 1e3 % 60, typeof options.secondsDecimalDigits === "number" ? options.secondsDecimalDigits : 1);
3138
+ const secondsString = options.keepDecimalsOnWholeSeconds ? secondsFixed : secondsFixed.replace(/\.0+$/, "");
3139
+ add(Number.parseFloat(secondsString), "second", "s", secondsString);
3114
3140
  }
3115
- } else {
3116
- const secondsFixed = floorDecimals((isBigInt ? Number(milliseconds % ONE_DAY_IN_MILLISECONDS) : milliseconds) / 1e3 % 60, typeof options.secondsDecimalDigits === "number" ? options.secondsDecimalDigits : 1);
3117
- const secondsString = options.keepDecimalsOnWholeSeconds ? secondsFixed : secondsFixed.replace(/\.0+$/, "");
3118
- add(Number.parseFloat(secondsString), "second", "s", secondsString);
3119
3141
  }
3120
3142
  if (result.length === 0) return sign + "0" + (options.verbose ? " milliseconds" : "ms");
3121
3143
  const separator = options.colonNotation ? ":" : " ";
@@ -3140,9 +3162,10 @@ const logResult = (result, verboseInfo) => {
3140
3162
  logDuration(result, verboseInfo);
3141
3163
  };
3142
3164
  const logDuration = (result, verboseInfo) => {
3165
+ const verboseMessage = `(done in ${prettyMilliseconds(result.durationMs)})`;
3143
3166
  verboseLog({
3144
3167
  type: "duration",
3145
- verboseMessage: `(done in ${prettyMilliseconds(result.durationMs)})`,
3168
+ verboseMessage,
3146
3169
  verboseInfo,
3147
3170
  result
3148
3171
  });
@@ -3210,10 +3233,10 @@ const isTransformOptions = (value) => isPlainObject(value) && (value.transform !
3210
3233
  const isUrl = (value) => Object.prototype.toString.call(value) === "[object URL]";
3211
3234
  const isRegularUrl = (value) => isUrl(value) && value.protocol !== "file:";
3212
3235
  const isFilePathObject = (value) => isPlainObject(value) && Object.keys(value).length > 0 && Object.keys(value).every((key) => FILE_PATH_KEYS.has(key)) && isFilePathString(value.file);
3213
- const FILE_PATH_KEYS = new Set(["file", "append"]);
3236
+ const FILE_PATH_KEYS = /* @__PURE__ */ new Set(["file", "append"]);
3214
3237
  const isFilePathString = (file) => typeof file === "string";
3215
3238
  const isUnknownStdioString = (type, value) => type === "native" && typeof value === "string" && !KNOWN_STDIO_STRINGS.has(value);
3216
- const KNOWN_STDIO_STRINGS = new Set([
3239
+ const KNOWN_STDIO_STRINGS = /* @__PURE__ */ new Set([
3217
3240
  "ipc",
3218
3241
  "ignore",
3219
3242
  "inherit",
@@ -3227,24 +3250,24 @@ const isTransformStream = (value) => isReadableStream(value?.readable) && isWrit
3227
3250
  const isAsyncIterableObject = (value) => isObject(value) && typeof value[Symbol.asyncIterator] === "function";
3228
3251
  const isIterableObject = (value) => isObject(value) && typeof value[Symbol.iterator] === "function";
3229
3252
  const isObject = (value) => typeof value === "object" && value !== null;
3230
- const TRANSFORM_TYPES = new Set([
3253
+ const TRANSFORM_TYPES = /* @__PURE__ */ new Set([
3231
3254
  "generator",
3232
3255
  "asyncGenerator",
3233
3256
  "duplex",
3234
3257
  "webTransform"
3235
3258
  ]);
3236
- const FILE_TYPES = new Set([
3259
+ const FILE_TYPES = /* @__PURE__ */ new Set([
3237
3260
  "fileUrl",
3238
3261
  "filePath",
3239
3262
  "fileNumber"
3240
3263
  ]);
3241
- const SPECIAL_DUPLICATE_TYPES_SYNC = new Set(["fileUrl", "filePath"]);
3242
- const SPECIAL_DUPLICATE_TYPES = new Set([
3264
+ const SPECIAL_DUPLICATE_TYPES_SYNC = /* @__PURE__ */ new Set(["fileUrl", "filePath"]);
3265
+ const SPECIAL_DUPLICATE_TYPES = /* @__PURE__ */ new Set([
3243
3266
  ...SPECIAL_DUPLICATE_TYPES_SYNC,
3244
3267
  "webStream",
3245
3268
  "nodeStream"
3246
3269
  ]);
3247
- const FORBID_DUPLICATE_TYPES = new Set(["webTransform", "duplex"]);
3270
+ const FORBID_DUPLICATE_TYPES = /* @__PURE__ */ new Set(["webTransform", "duplex"]);
3248
3271
  const TYPE_TO_MESSAGE = {
3249
3272
  generator: "a generator",
3250
3273
  asyncGenerator: "an async generator",
@@ -3577,7 +3600,8 @@ const validateDuplicateStreamSync = ({ otherStdioItems, type, value, optionName,
3577
3600
  const getDuplicateStreamInstance = ({ otherStdioItems, type, value, optionName, direction }) => {
3578
3601
  const duplicateStdioItems = otherStdioItems.filter((stdioItem) => hasSameValue(stdioItem, value));
3579
3602
  if (duplicateStdioItems.length === 0) return;
3580
- throwOnDuplicateStream(duplicateStdioItems.find((stdioItem) => stdioItem.direction !== direction), optionName, type);
3603
+ const differentStdioItem = duplicateStdioItems.find((stdioItem) => stdioItem.direction !== direction);
3604
+ throwOnDuplicateStream(differentStdioItem, optionName, type);
3581
3605
  return direction === "output" ? duplicateStdioItems[0].stream : void 0;
3582
3606
  };
3583
3607
  const hasSameValue = ({ type, value }, secondValue) => {
@@ -3586,7 +3610,8 @@ const hasSameValue = ({ type, value }, secondValue) => {
3586
3610
  return value === secondValue;
3587
3611
  };
3588
3612
  const validateDuplicateTransform = ({ otherStdioItems, type, value, optionName }) => {
3589
- throwOnDuplicateStream(otherStdioItems.find(({ value: { transform } }) => transform === value.transform), optionName, type);
3613
+ const duplicateStdioItem = otherStdioItems.find(({ value: { transform } }) => transform === value.transform);
3614
+ throwOnDuplicateStream(duplicateStdioItem, optionName, type);
3590
3615
  };
3591
3616
  const throwOnDuplicateStream = (stdioItem, optionName, type) => {
3592
3617
  if (stdioItem !== void 0) throw new TypeError(`The \`${stdioItem.optionName}\` and \`${optionName}\` options must not target ${TYPE_TO_MESSAGE[type]} that is the same.`);
@@ -3594,13 +3619,14 @@ const throwOnDuplicateStream = (stdioItem, optionName, type) => {
3594
3619
  //#endregion
3595
3620
  //#region ../../node_modules/.pnpm/execa@9.5.2/node_modules/execa/lib/stdio/handle.js
3596
3621
  const handleStdio = (addProperties, options, verboseInfo, isSync) => {
3622
+ const initialFileDescriptors = normalizeStdioOption(options, verboseInfo, isSync).map((stdioOption, fdNumber) => getFileDescriptor({
3623
+ stdioOption,
3624
+ fdNumber,
3625
+ options,
3626
+ isSync
3627
+ }));
3597
3628
  const fileDescriptors = getFinalFileDescriptors({
3598
- initialFileDescriptors: normalizeStdioOption(options, verboseInfo, isSync).map((stdioOption, fdNumber) => getFileDescriptor({
3599
- stdioOption,
3600
- fdNumber,
3601
- options,
3602
- isSync
3603
- })),
3629
+ initialFileDescriptors,
3604
3630
  addProperties,
3605
3631
  options,
3606
3632
  isSync
@@ -3617,13 +3643,14 @@ const getFileDescriptor = ({ stdioOption, fdNumber, options, isSync }) => {
3617
3643
  optionName
3618
3644
  });
3619
3645
  const direction = getStreamDirection(initialStdioItems, fdNumber, optionName);
3620
- const normalizedStdioItems = normalizeTransforms(initialStdioItems.map((stdioItem) => handleNativeStream({
3646
+ const stdioItems = initialStdioItems.map((stdioItem) => handleNativeStream({
3621
3647
  stdioItem,
3622
3648
  isStdioArray,
3623
3649
  fdNumber,
3624
3650
  direction,
3625
3651
  isSync
3626
- })), optionName, direction, options);
3652
+ }));
3653
+ const normalizedStdioItems = normalizeTransforms(stdioItems, optionName, direction, options);
3627
3654
  const objectMode = getFdObjectMode(normalizedStdioItems, direction);
3628
3655
  validateFileObjectMode(normalizedStdioItems, objectMode);
3629
3656
  return {
@@ -3633,7 +3660,8 @@ const getFileDescriptor = ({ stdioOption, fdNumber, options, isSync }) => {
3633
3660
  };
3634
3661
  };
3635
3662
  const initializeStdioItems = ({ stdioOption, fdNumber, options, optionName }) => {
3636
- const stdioItems = filterDuplicates([...(Array.isArray(stdioOption) ? stdioOption : [stdioOption]).map((value) => initializeStdioItem(value, optionName)), ...handleInputOptions(options, fdNumber)]);
3663
+ const initialStdioItems = [...(Array.isArray(stdioOption) ? stdioOption : [stdioOption]).map((value) => initializeStdioItem(value, optionName)), ...handleInputOptions(options, fdNumber)];
3664
+ const stdioItems = filterDuplicates(initialStdioItems);
3637
3665
  const isStdioArray = stdioItems.length > 1;
3638
3666
  validateStdioArray(stdioItems, isStdioArray, optionName);
3639
3667
  validateStreams(stdioItems);
@@ -3652,7 +3680,7 @@ const validateStdioArray = (stdioItems, isStdioArray, optionName) => {
3652
3680
  if (!isStdioArray) return;
3653
3681
  for (const { value, optionName } of stdioItems) if (INVALID_STDIO_ARRAY_OPTIONS.has(value)) throw new Error(`The \`${optionName}\` option must not include \`${value}\`.`);
3654
3682
  };
3655
- const INVALID_STDIO_ARRAY_OPTIONS = new Set(["ignore", "ipc"]);
3683
+ const INVALID_STDIO_ARRAY_OPTIONS = /* @__PURE__ */ new Set(["ignore", "ipc"]);
3656
3684
  const validateStreams = (stdioItems) => {
3657
3685
  for (const stdioItem of stdioItems) validateFileStdio(stdioItem);
3658
3686
  };
@@ -3981,7 +4009,10 @@ const generatorToStream = ({ value, value: { transform, final, writableObjectMod
3981
4009
  const runGeneratorsSync = (chunks, stdioItems, encoding, isInput) => {
3982
4010
  const generators = stdioItems.filter(({ type }) => type === "generator");
3983
4011
  const reversedGenerators = isInput ? generators.reverse() : generators;
3984
- for (const { value, optionName } of reversedGenerators) chunks = runTransformSync(addInternalGenerators(value, encoding, optionName), chunks);
4012
+ for (const { value, optionName } of reversedGenerators) {
4013
+ const generators = addInternalGenerators(value, encoding, optionName);
4014
+ chunks = runTransformSync(generators, chunks);
4015
+ }
3985
4016
  return chunks;
3986
4017
  };
3987
4018
  const addInternalGenerators = ({ transform, final, binary, writableObjectMode, readableObjectMode, preserveNewlines }, encoding, optionName) => {
@@ -4017,7 +4048,8 @@ const addInputOptionSync = (fileDescriptors, fdNumber, options) => {
4017
4048
  const [{ type, optionName }] = allStdioItems;
4018
4049
  throw new TypeError(`Only the \`stdin\` option, not \`${optionName}\`, can be ${TYPE_TO_MESSAGE[type]} with synchronous methods.`);
4019
4050
  }
4020
- options.input = joinToUint8Array(allStdioItems.map(({ contents }) => contents).map((contents) => applySingleInputGeneratorsSync(contents, stdioItems)));
4051
+ const transformedContents = allStdioItems.map(({ contents }) => contents).map((contents) => applySingleInputGeneratorsSync(contents, stdioItems));
4052
+ options.input = joinToUint8Array(transformedContents);
4021
4053
  };
4022
4054
  const applySingleInputGeneratorsSync = (contents, stdioItems) => {
4023
4055
  const newContents = runGeneratorsSync(contents, stdioItems, "utf8", true);
@@ -4032,7 +4064,7 @@ const validateSerializable = (newContents) => {
4032
4064
  //#region ../../node_modules/.pnpm/execa@9.5.2/node_modules/execa/lib/verbose/output.js
4033
4065
  const shouldLogOutput = ({ stdioItems, encoding, verboseInfo, fdNumber }) => fdNumber !== "all" && isFullVerbose(verboseInfo, fdNumber) && !BINARY_ENCODINGS.has(encoding) && fdUsesVerbose(fdNumber) && (stdioItems.some(({ type, value }) => type === "native" && PIPED_STDIO_VALUES.has(value)) || stdioItems.every(({ type }) => TRANSFORM_TYPES.has(type)));
4034
4066
  const fdUsesVerbose = (fdNumber) => fdNumber === 1 || fdNumber === 2;
4035
- const PIPED_STDIO_VALUES = new Set(["pipe", "overlapped"]);
4067
+ const PIPED_STDIO_VALUES = /* @__PURE__ */ new Set(["pipe", "overlapped"]);
4036
4068
  const logLines = async (linesIterable, stream, fdNumber, verboseInfo) => {
4037
4069
  for await (const line of linesIterable) if (!isPipingStream(stream)) logLine(line, fdNumber, verboseInfo);
4038
4070
  };
@@ -4041,9 +4073,10 @@ const logLinesSync = (linesArray, fdNumber, verboseInfo) => {
4041
4073
  };
4042
4074
  const isPipingStream = (stream) => stream._readableState.pipes.length > 0;
4043
4075
  const logLine = (line, fdNumber, verboseInfo) => {
4076
+ const verboseMessage = serializeVerboseMessage(line);
4044
4077
  verboseLog({
4045
4078
  type: "output",
4046
- verboseMessage: serializeVerboseMessage(line),
4079
+ verboseMessage,
4047
4080
  fdNumber,
4048
4081
  verboseInfo
4049
4082
  });
@@ -4069,10 +4102,12 @@ const transformOutputSync = ({ fileDescriptors, syncResult: { output }, options,
4069
4102
  };
4070
4103
  const transformOutputResultSync = ({ result, fileDescriptors, fdNumber, state, outputFiles, isMaxBuffer, verboseInfo }, { buffer, encoding, lines, stripFinalNewline, maxBuffer }) => {
4071
4104
  if (result === null) return;
4072
- const uint8ArrayResult = bufferToUint8Array(truncateMaxBufferSync(result, isMaxBuffer, maxBuffer));
4105
+ const truncatedResult = truncateMaxBufferSync(result, isMaxBuffer, maxBuffer);
4106
+ const uint8ArrayResult = bufferToUint8Array(truncatedResult);
4073
4107
  const { stdioItems, objectMode } = fileDescriptors[fdNumber];
4108
+ const chunks = runOutputGeneratorsSync([uint8ArrayResult], stdioItems, encoding, state);
4074
4109
  const { serializedResult, finalResult = serializedResult } = serializeChunks({
4075
- chunks: runOutputGeneratorsSync([uint8ArrayResult], stdioItems, encoding, state),
4110
+ chunks,
4076
4111
  objectMode,
4077
4112
  encoding,
4078
4113
  lines,
@@ -4196,7 +4231,7 @@ const getResultError = (error, exitCode, signal) => {
4196
4231
  //#region ../../node_modules/.pnpm/execa@9.5.2/node_modules/execa/lib/methods/main-sync.js
4197
4232
  const execaCoreSync = (rawFile, rawArguments, rawOptions) => {
4198
4233
  const { file, commandArguments, command, escapedCommand, startTime, verboseInfo, options, fileDescriptors } = handleSyncArguments(rawFile, rawArguments, rawOptions);
4199
- return handleResult(spawnSubprocessSync({
4234
+ const result = spawnSubprocessSync({
4200
4235
  file,
4201
4236
  commandArguments,
4202
4237
  options,
@@ -4205,11 +4240,13 @@ const execaCoreSync = (rawFile, rawArguments, rawOptions) => {
4205
4240
  verboseInfo,
4206
4241
  fileDescriptors,
4207
4242
  startTime
4208
- }), verboseInfo, options);
4243
+ });
4244
+ return handleResult(result, verboseInfo, options);
4209
4245
  };
4210
4246
  const handleSyncArguments = (rawFile, rawArguments, rawOptions) => {
4211
4247
  const { command, escapedCommand, startTime, verboseInfo } = handleCommand(rawFile, rawArguments, rawOptions);
4212
- const { file, commandArguments, options } = normalizeOptions(rawFile, rawArguments, normalizeSyncOptions(rawOptions));
4248
+ const syncOptions = normalizeSyncOptions(rawOptions);
4249
+ const { file, commandArguments, options } = normalizeOptions(rawFile, rawArguments, syncOptions);
4213
4250
  validateSyncOptions(options);
4214
4251
  return {
4215
4252
  file,
@@ -4254,14 +4291,16 @@ const spawnSubprocessSync = ({ file, commandArguments, options, command, escaped
4254
4291
  isMaxBuffer,
4255
4292
  verboseInfo
4256
4293
  });
4294
+ const stdio = output.map((stdioOutput, fdNumber) => stripNewline(stdioOutput, options, fdNumber));
4295
+ const all = stripNewline(getAllSync(output, options), options, "all");
4257
4296
  return getSyncResult({
4258
4297
  error,
4259
4298
  exitCode,
4260
4299
  signal,
4261
4300
  timedOut,
4262
4301
  isMaxBuffer,
4263
- stdio: output.map((stdioOutput, fdNumber) => stripNewline(stdioOutput, options, fdNumber)),
4264
- all: stripNewline(getAllSync(output, options), options, "all"),
4302
+ stdio,
4303
+ all,
4265
4304
  options,
4266
4305
  command,
4267
4306
  escapedCommand,
@@ -4271,7 +4310,8 @@ const spawnSubprocessSync = ({ file, commandArguments, options, command, escaped
4271
4310
  const runSubprocessSync = ({ file, commandArguments, options, command, escapedCommand, fileDescriptors, startTime }) => {
4272
4311
  try {
4273
4312
  addInputOptionsSync(fileDescriptors, options);
4274
- return (0, node_child_process.spawnSync)(file, commandArguments, normalizeSpawnSyncOptions(options));
4313
+ const normalizedOptions = normalizeSpawnSyncOptions(options);
4314
+ return (0, node_child_process.spawnSync)(file, commandArguments, normalizedOptions);
4275
4315
  } catch (error) {
4276
4316
  return makeEarlyError({
4277
4317
  error,
@@ -4485,17 +4525,18 @@ const handleEarlyError = ({ error, command, escapedCommand, fileDescriptors, opt
4485
4525
  writable,
4486
4526
  duplex
4487
4527
  });
4528
+ const earlyError = makeEarlyError({
4529
+ error,
4530
+ command,
4531
+ escapedCommand,
4532
+ fileDescriptors,
4533
+ options,
4534
+ startTime,
4535
+ isSync: false
4536
+ });
4488
4537
  return {
4489
4538
  subprocess,
4490
- promise: handleDummyPromise(makeEarlyError({
4491
- error,
4492
- command,
4493
- escapedCommand,
4494
- fileDescriptors,
4495
- options,
4496
- startTime,
4497
- isSync: false
4498
- }), verboseInfo, options)
4539
+ promise: handleDummyPromise(earlyError, verboseInfo, options)
4499
4540
  };
4500
4541
  };
4501
4542
  const createDummyStreams = (subprocess, fileDescriptors) => {
@@ -4679,8 +4720,10 @@ const endWhenStreamsDone = async ({ passThroughStream, stream, streams, ended, a
4679
4720
  controller.abort();
4680
4721
  updateMaxListeners(passThroughStream, -PASSTHROUGH_LISTENERS_PER_STREAM);
4681
4722
  }
4682
- if (streams.size > 0 && streams.size === ended.size + aborted.size) if (ended.size === 0 && aborted.size > 0) abortStream(passThroughStream);
4683
- else endStream(passThroughStream);
4723
+ if (streams.size > 0 && streams.size === ended.size + aborted.size) {
4724
+ if (ended.size === 0 && aborted.size > 0) abortStream(passThroughStream);
4725
+ else endStream(passThroughStream);
4726
+ }
4684
4727
  };
4685
4728
  const afterMergedStreamFinished = async (onFinished, stream, { signal }) => {
4686
4729
  try {
@@ -4786,7 +4829,10 @@ const pipeOutputAsync = (subprocess, fileDescriptors, controller) => {
4786
4829
  controller
4787
4830
  });
4788
4831
  }
4789
- for (const [outputStream, inputStreams] of pipeGroups.entries()) pipeStreams(inputStreams.length === 1 ? inputStreams[0] : mergeStreams(inputStreams), outputStream);
4832
+ for (const [outputStream, inputStreams] of pipeGroups.entries()) {
4833
+ const inputStream = inputStreams.length === 1 ? inputStreams[0] : mergeStreams(inputStreams);
4834
+ pipeStreams(inputStream, outputStream);
4835
+ }
4790
4836
  };
4791
4837
  const pipeTransform = (subprocess, stream, direction, fdNumber) => {
4792
4838
  if (direction === "output") pipeStreams(subprocess.stdio[fdNumber], stream);
@@ -5275,12 +5321,13 @@ const stopReadingOnStreamEnd = async (onStreamEnd, controller, stream) => {
5275
5321
  }
5276
5322
  };
5277
5323
  const iterateOnStream = ({ stream, controller, binary, shouldEncode, encoding, shouldSplit, preserveNewlines }) => {
5324
+ const onStdoutChunk = (0, node_events.on)(stream, "data", {
5325
+ signal: controller.signal,
5326
+ highWaterMark: HIGH_WATER_MARK,
5327
+ highWatermark: HIGH_WATER_MARK
5328
+ });
5278
5329
  return iterateOnData({
5279
- onStdoutChunk: (0, node_events.on)(stream, "data", {
5280
- signal: controller.signal,
5281
- highWaterMark: HIGH_WATER_MARK,
5282
- highWatermark: HIGH_WATER_MARK
5283
- }),
5330
+ onStdoutChunk,
5284
5331
  controller,
5285
5332
  binary,
5286
5333
  shouldEncode,
@@ -5324,12 +5371,13 @@ const getStreamOutput = async ({ stream, onStreamEnd, fdNumber, encoding, buffer
5324
5371
  await Promise.all([resumeStream(stream), logPromise]);
5325
5372
  return;
5326
5373
  }
5374
+ const stripFinalNewlineValue = getStripFinalNewline(stripFinalNewline, fdNumber);
5327
5375
  const iterable = iterateForResult({
5328
5376
  stream,
5329
5377
  onStreamEnd,
5330
5378
  lines,
5331
5379
  encoding,
5332
- stripFinalNewline: getStripFinalNewline(stripFinalNewline, fdNumber),
5380
+ stripFinalNewline: stripFinalNewlineValue,
5333
5381
  allMixed
5334
5382
  });
5335
5383
  const [output] = await Promise.all([getStreamContents({
@@ -5349,14 +5397,15 @@ const logOutputAsync = async ({ stream, onStreamEnd, fdNumber, encoding, allMixe
5349
5397
  verboseInfo,
5350
5398
  fdNumber
5351
5399
  })) return;
5352
- await logLines(iterateForResult({
5400
+ const linesIterable = iterateForResult({
5353
5401
  stream,
5354
5402
  onStreamEnd,
5355
5403
  lines: true,
5356
5404
  encoding,
5357
5405
  stripFinalNewline: true,
5358
5406
  allMixed
5359
- }), stream, fdNumber, verboseInfo);
5407
+ });
5408
+ await logLines(linesIterable, stream, fdNumber, verboseInfo);
5360
5409
  };
5361
5410
  const resumeStream = async (stream) => {
5362
5411
  await (0, node_timers_promises.setImmediate)();
@@ -5502,9 +5551,10 @@ const getAllMixed = ({ all, stdout, stderr }) => all && stdout && stderr && stdo
5502
5551
  //#region ../../node_modules/.pnpm/execa@9.5.2/node_modules/execa/lib/verbose/ipc.js
5503
5552
  const shouldLogIpc = (verboseInfo) => isFullVerbose(verboseInfo, "ipc");
5504
5553
  const logIpcOutput = (message, verboseInfo) => {
5554
+ const verboseMessage = serializeVerboseMessage(message);
5505
5555
  verboseLog({
5506
5556
  type: "ipc",
5507
- verboseMessage: serializeVerboseMessage(message),
5557
+ verboseMessage,
5508
5558
  fdNumber: "ipc",
5509
5559
  verboseInfo
5510
5560
  });
@@ -5900,14 +5950,15 @@ const onDuplexDestroy = async ({ subprocessStdout, subprocessStdin, subprocess,
5900
5950
  const createIterable = (subprocess, encoding, { from, binary: binaryOption = false, preserveNewlines = false } = {}) => {
5901
5951
  const binary = binaryOption || BINARY_ENCODINGS.has(encoding);
5902
5952
  const subprocessStdout = getFromStream(subprocess, from);
5903
- return iterateOnStdoutData(iterateOnSubprocessStream({
5953
+ const onStdoutData = iterateOnSubprocessStream({
5904
5954
  subprocessStdout,
5905
5955
  subprocess,
5906
5956
  binary,
5907
5957
  shouldEncode: true,
5908
5958
  encoding,
5909
5959
  preserveNewlines
5910
- }), subprocessStdout, subprocess);
5960
+ });
5961
+ return iterateOnStdoutData(onStdoutData, subprocessStdout, subprocess);
5911
5962
  };
5912
5963
  const iterateOnStdoutData = async function* (onStdoutData, subprocessStdout, subprocess) {
5913
5964
  try {
@@ -6067,19 +6118,22 @@ const handlePromise = async ({ subprocess, options, startTime, verboseInfo, file
6067
6118
  });
6068
6119
  controller.abort();
6069
6120
  onInternalError.resolve();
6070
- return handleResult(getAsyncResult({
6121
+ const stdio = stdioResults.map((stdioResult, fdNumber) => stripNewline(stdioResult, options, fdNumber));
6122
+ const all = stripNewline(allResult, options, "all");
6123
+ const result = getAsyncResult({
6071
6124
  errorInfo,
6072
6125
  exitCode,
6073
6126
  signal,
6074
- stdio: stdioResults.map((stdioResult, fdNumber) => stripNewline(stdioResult, options, fdNumber)),
6075
- all: stripNewline(allResult, options, "all"),
6127
+ stdio,
6128
+ all,
6076
6129
  ipcOutput,
6077
6130
  context,
6078
6131
  options,
6079
6132
  command,
6080
6133
  escapedCommand,
6081
6134
  startTime
6082
- }), verboseInfo, options);
6135
+ });
6136
+ return handleResult(result, verboseInfo, options);
6083
6137
  };
6084
6138
  const getAsyncResult = ({ errorInfo, exitCode, signal, stdio, all, ipcOutput, context, options, command, escapedCommand, startTime }) => "error" in errorInfo ? makeError({
6085
6139
  error: errorInfo.error,
@@ -6123,7 +6177,7 @@ const mergeOption = (optionName, boundOptionValue, optionValue) => {
6123
6177
  };
6124
6178
  return optionValue;
6125
6179
  };
6126
- const DEEP_OPTIONS = new Set(["env", ...FD_SPECIFIC_OPTIONS]);
6180
+ const DEEP_OPTIONS = /* @__PURE__ */ new Set(["env", ...FD_SPECIFIC_OPTIONS]);
6127
6181
  //#endregion
6128
6182
  //#region ../../node_modules/.pnpm/execa@9.5.2/node_modules/execa/lib/methods/create.js
6129
6183
  const createExeca = (mapArguments, boundOptions, deepOptions, setBoundExeca) => {
@@ -6150,7 +6204,8 @@ const callBoundExeca = ({ mapArguments, deepOptions = {}, boundOptions = {}, set
6150
6204
  return isSync ? execaCoreSync(file, commandArguments, options) : execaCoreAsync(file, commandArguments, options, createNested);
6151
6205
  };
6152
6206
  const parseArguments = ({ mapArguments, firstArgument, nextArguments, deepOptions, boundOptions }) => {
6153
- const [initialFile, initialArguments, initialOptions] = normalizeParameters(...isTemplateString(firstArgument) ? parseTemplates(firstArgument, nextArguments) : [firstArgument, ...nextArguments]);
6207
+ const callArguments = isTemplateString(firstArgument) ? parseTemplates(firstArgument, nextArguments) : [firstArgument, ...nextArguments];
6208
+ const [initialFile, initialArguments, initialOptions] = normalizeParameters(...callArguments);
6154
6209
  const mergedOptions = mergeOptions(mergeOptions(deepOptions, boundOptions), initialOptions);
6155
6210
  const { file = initialFile, commandArguments = initialArguments, options = mergedOptions, isSync = false } = mapArguments({
6156
6211
  file: initialFile,
@@ -6221,8 +6276,8 @@ const { sendMessage, getOneMessage, getEachMessage, getCancelSignal } = getIpcEx
6221
6276
  //#endregion
6222
6277
  //#region src/cloudFrontInvalidation.ts
6223
6278
  const DEFAULT_POLL_INTERVAL_MS = 2e3;
6224
- const DEFAULT_TIMEOUT_MS = 300 * 1e3;
6225
- const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
6279
+ const DEFAULT_TIMEOUT_MS = 3e5;
6280
+ const sleep$1 = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
6226
6281
  const invalidateCloudFront = async (client, distributionId, paths, options) => {
6227
6282
  if (paths.length === 0) return;
6228
6283
  const response = await client.send(new _aws_sdk_client_cloudfront.CreateInvalidationCommand({
@@ -6240,7 +6295,7 @@ const invalidateCloudFront = async (client, distributionId, paths, options) => {
6240
6295
  if (!invalidationId) throw new Error("CloudFront invalidation response is missing Invalidation.Id");
6241
6296
  const deadline = Date.now() + DEFAULT_TIMEOUT_MS;
6242
6297
  while (Date.now() < deadline) {
6243
- await sleep(DEFAULT_POLL_INTERVAL_MS);
6298
+ await sleep$1(DEFAULT_POLL_INTERVAL_MS);
6244
6299
  if ((await client.send(new _aws_sdk_client_cloudfront.GetInvalidationCommand({
6245
6300
  DistributionId: distributionId,
6246
6301
  Id: invalidationId
@@ -6248,1751 +6303,294 @@ const invalidateCloudFront = async (client, distributionId, paths, options) => {
6248
6303
  }
6249
6304
  throw new Error(`Timed out waiting for CloudFront invalidation ${invalidationId}.`);
6250
6305
  };
6251
- //#endregion
6252
- //#region src/dynamoDB.ts
6253
- const DYNAMODB_MAX_METADATA_ITEM_BYTES = 8 * 1024;
6254
- const DYNAMODB_MAX_CATALOG_ITEM_BYTES = 400 * 1024;
6255
- var DynamoDBMetadataItemSizeError = class extends Error {
6256
- name = "DynamoDBMetadataItemSizeError";
6257
- constructor(byteLength) {
6258
- super(`DynamoDB metadata item is ${byteLength} bytes; maximum is ${DYNAMODB_MAX_METADATA_ITEM_BYTES}`);
6259
- this.byteLength = byteLength;
6260
- }
6261
- };
6262
- const boundedDynamoDBMetadataItem = (item) => {
6263
- const byteLength = new TextEncoder().encode(JSON.stringify(item)).byteLength;
6264
- if (byteLength > 8192) throw new DynamoDBMetadataItemSizeError(byteLength);
6265
- return item;
6266
- };
6267
- var DynamoDBCatalogItemSizeError = class extends Error {
6268
- name = "DynamoDBCatalogItemSizeError";
6269
- constructor(byteLength) {
6270
- super(`DynamoDB catalog item is ${byteLength} bytes; maximum is ${DYNAMODB_MAX_CATALOG_ITEM_BYTES}`);
6271
- this.byteLength = byteLength;
6272
- }
6273
- };
6274
- const boundedDynamoDBCatalogItem = (item) => {
6275
- const byteLength = new TextEncoder().encode(JSON.stringify(item)).byteLength;
6276
- if (byteLength > 409600) throw new DynamoDBCatalogItemSizeError(byteLength);
6277
- return item;
6278
- };
6279
- const DYNAMODB_CHANNEL_PARTITION = "channels";
6280
- const DYNAMODB_CHANNEL_NAME_PARTITION = "_hot-updater#channel-names";
6281
- var DynamoDBStoredItemError = class extends Error {
6282
- name = "DynamoDBStoredItemError";
6283
- constructor() {
6284
- super("DynamoDB contains an invalid Hot Updater row");
6285
- }
6286
- };
6287
- const field = (value, name) => Reflect.get(value, name);
6288
- const isNullableString = (value) => value === null || typeof value === "string";
6289
- const isByteSize = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
6290
- const withLegacyArchiveByteSize = (value) => typeof value === "object" && value !== null && field(value, "archive_byte_size") === void 0 ? {
6291
- ...value,
6292
- archive_byte_size: 0
6293
- } : value;
6294
- const isBundleRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "file_hash") === "string" && isNullableString(field(value, "git_commit_hash")) && typeof field(value, "storage_uri") === "string" && isByteSize(field(value, "archive_byte_size")) && (0, _hot_updater_plugin_core.isDatabaseMetadataObject)(field(value, "metadata")) && isNullableString(field(value, "manifest_storage_uri")) && isNullableString(field(value, "manifest_file_hash")) && isNullableString(field(value, "asset_base_storage_uri"));
6295
- const isPatchRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "bundle_id") === "string" && typeof field(value, "base_bundle_id") === "string" && typeof field(value, "base_file_hash") === "string" && typeof field(value, "patch_file_hash") === "string" && typeof field(value, "patch_storage_uri") === "string" && isByteSize(field(value, "byte_size")) && typeof field(value, "order_index") === "number";
6296
- const isChannelRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "name") === "string";
6297
- const isReleaseRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "revision") === "number" && typeof field(value, "scope_key") === "string" && typeof field(value, "channel_id") === "string" && (field(value, "platform") === "ios" || field(value, "platform") === "android") && (field(value, "kind") === "BUNDLE" || field(value, "kind") === "EMBEDDED") && isNullableString(field(value, "bundle_id")) && (field(value, "strategy") === "APP_VERSION" || field(value, "strategy") === "FINGERPRINT") && isNullableString(field(value, "target_app_version")) && isNullableString(field(value, "fingerprint_hash")) && typeof field(value, "enabled") === "boolean" && typeof field(value, "should_force_update") === "boolean" && isNullableString(field(value, "message")) && typeof field(value, "rollout_cohort_count") === "number" && Array.isArray(field(value, "target_cohorts")) && field(value, "target_cohorts").every((cohort) => typeof cohort === "string") && (field(value, "operation") === "DEPLOY" || field(value, "operation") === "PROMOTE" || field(value, "operation") === "ROLLBACK") && isNullableString(field(value, "source_release_id")) && typeof field(value, "created_at_ms") === "number" && typeof field(value, "updated_at_ms") === "number";
6298
- const isReleaseCatalogRow = (value) => typeof value === "object" && value !== null && typeof field(value, "scope_key") === "string" && typeof field(value, "catalog_id") === "string" && (field(value, "strategy") === "APP_VERSION" || field(value, "strategy") === "FINGERPRINT") && typeof field(value, "channel_id") === "string" && typeof field(value, "channel_key") === "string" && (field(value, "platform") === "ios" || field(value, "platform") === "android") && isNullableString(field(value, "fingerprint_hash")) && typeof field(value, "generation") === "number" && typeof field(value, "payload") === "string" && typeof field(value, "catalog_hash") === "string" && typeof field(value, "byte_size") === "number" && typeof field(value, "is_tombstone") === "boolean" && typeof field(value, "updated_at_ms") === "number";
6299
- const parseDynamoDBItem = (value) => {
6300
- const pk = value.pk;
6301
- const sk = value.sk;
6302
- const version = value.version;
6303
- const relationCount = value.relation_count;
6304
- const ownedPatchCount = value.owned_patch_count;
6305
- const gsi1pk = value.gsi1pk;
6306
- const gsi1sk = value.gsi1sk;
6307
- const row = pk === "bundles" ? withLegacyArchiveByteSize(value.row) : value.row;
6308
- if (typeof sk !== "string" || typeof version !== "number") throw new DynamoDBStoredItemError();
6309
- if (pk === "channels" && typeof value.reference_count === "number" && isChannelRow(row)) return {
6310
- pk,
6311
- sk,
6312
- version,
6313
- reference_count: value.reference_count,
6314
- row
6315
- };
6316
- if (typeof pk === "string" && pk.startsWith("release-scope#") && isReleaseRow(row)) return {
6317
- pk,
6318
- sk,
6319
- version,
6320
- row
6321
- };
6322
- if (pk === "release_catalogs" && isReleaseCatalogRow(row)) return {
6323
- pk,
6324
- sk,
6325
- version,
6326
- row
6327
- };
6328
- if (typeof gsi1pk !== "string" || typeof gsi1sk !== "string") throw new DynamoDBStoredItemError();
6329
- if (pk === "bundles" && isBundleRow(row)) {
6330
- if (typeof relationCount !== "number" || typeof ownedPatchCount !== "number") throw new DynamoDBStoredItemError();
6331
- return {
6332
- pk,
6333
- sk,
6334
- version,
6335
- relation_count: relationCount,
6336
- owned_patch_count: ownedPatchCount,
6337
- gsi1pk,
6338
- gsi1sk,
6339
- row
6340
- };
6341
- }
6342
- if (pk === "bundle_patches" && isPatchRow(row)) return {
6343
- pk,
6344
- sk,
6345
- version,
6346
- gsi1pk,
6347
- gsi1sk,
6348
- row
6349
- };
6350
- throw new DynamoDBStoredItemError();
6351
- };
6352
- const bundlePlatformPartition = (row) => `bundle#${row.platform}`;
6353
- const patchOwnerPartition = (bundleId) => `patch-owner#${bundleId}`;
6354
- const patchOrderKey = (row) => `${row.order_index.toString().padStart(10, "0")}#${row.id}`;
6355
- const toDynamoDBBundleItem = (row, version = 1, relationCount = 0, ownedPatchCount = 0) => ({
6356
- pk: "bundles",
6357
- sk: row.id,
6358
- version,
6359
- relation_count: relationCount,
6360
- owned_patch_count: ownedPatchCount,
6361
- gsi1pk: bundlePlatformPartition(row),
6362
- gsi1sk: row.id,
6363
- row
6364
- });
6365
- const toDynamoDBPatchItem = (row, version = 1) => {
6366
- const orderKey = patchOrderKey(row);
6306
+ /**
6307
+ * The update-check routes' CloudFront invalidation: `invalidate` purges their
6308
+ * cached copies and `destroy` releases the client; undefined without a
6309
+ * distribution. A failed invalidation only warns, so the write that changed
6310
+ * the routes still succeeds.
6311
+ */
6312
+ const createUpdateRouteInvalidation = ({ apiBasePath = "/release-catalogs", cloudfrontDistributionId, shouldWaitForInvalidation = false, credentials, region }) => {
6313
+ if (!cloudfrontDistributionId) return void 0;
6314
+ const client = new _aws_sdk_client_cloudfront.CloudFrontClient({
6315
+ credentials,
6316
+ region
6317
+ });
6367
6318
  return {
6368
- pk: "bundle_patches",
6369
- sk: row.id,
6370
- version,
6371
- gsi1pk: patchOwnerPartition(row.bundle_id),
6372
- gsi1sk: orderKey,
6373
- row
6374
- };
6375
- };
6376
- const toDynamoDBChannelItem = (row, version = 1, referenceCount = 0) => ({
6377
- pk: DYNAMODB_CHANNEL_PARTITION,
6378
- sk: row.id,
6379
- version,
6380
- reference_count: referenceCount,
6381
- row
6382
- });
6383
- const releaseScopePartition = (scopeKey) => `release-scope#${scopeKey}`;
6384
- const toDynamoDBReleaseItem = (row, version = 1) => ({
6385
- pk: releaseScopePartition(row.scope_key),
6386
- sk: row.id,
6387
- version,
6388
- row
6389
- });
6390
- const toDynamoDBReleaseCatalogItem = (row, version = 1) => ({
6391
- pk: "release_catalogs",
6392
- sk: row.scope_key,
6393
- version,
6394
- row
6395
- });
6396
- const itemKey = (model, id) => ({
6397
- pk: model,
6398
- sk: id
6399
- });
6400
- const exactDynamoDBId = (where) => {
6401
- if (where?.length !== 1) return void 0;
6402
- const condition = where[0];
6403
- if (Reflect.get(condition, "field") !== "id" || (Reflect.get(condition, "operator") ?? "eq") !== "eq" || Reflect.get(condition, "mode") === "insensitive") return;
6404
- const value = Reflect.get(condition, "value");
6405
- return typeof value === "string" ? value : void 0;
6406
- };
6407
- const exactDynamoDBPatchOwner = (where) => {
6408
- const owners = exactDynamoDBPatchOwners(where);
6409
- return owners?.length === 1 ? owners[0] : void 0;
6410
- };
6411
- const exactDynamoDBPatchOwners = (where) => {
6412
- if (!where || where.some((condition) => Reflect.get(condition, "connector") === "OR")) return;
6413
- const ownerConditions = where.filter((condition) => Reflect.get(condition, "field") === "bundle_id");
6414
- if (ownerConditions.length !== 1) return void 0;
6415
- const condition = ownerConditions[0];
6416
- if (Reflect.get(condition, "mode") === "insensitive") return;
6417
- const operator = Reflect.get(condition, "operator") ?? "eq";
6418
- const value = Reflect.get(condition, "value");
6419
- if (operator === "eq") return typeof value === "string" ? [value] : void 0;
6420
- if (operator === "in" && Array.isArray(value) && value.every((id) => typeof id === "string")) return value;
6421
- };
6422
- const exactDynamoDBBundleIds = (where) => {
6423
- if (!where || where.some((condition) => Reflect.get(condition, "connector") === "OR")) return;
6424
- for (const condition of where) {
6425
- if (Reflect.get(condition, "field") !== "id" || Reflect.get(condition, "mode") === "insensitive") continue;
6426
- const operator = Reflect.get(condition, "operator") ?? "eq";
6427
- const value = Reflect.get(condition, "value");
6428
- if (operator === "eq" && typeof value === "string") return [value];
6429
- if (operator === "in" && Array.isArray(value) && value.every((id) => typeof id === "string")) return value;
6430
- }
6431
- };
6432
- const dynamoDBBundlePageStart = (where, direction) => {
6433
- if ((where ?? []).some((condition) => Reflect.get(condition, "connector") === "OR")) return;
6434
- let result;
6435
- for (const condition of where ?? []) {
6436
- if (Reflect.get(condition, "field") !== "id") continue;
6437
- const operator = Reflect.get(condition, "operator") ?? "eq";
6438
- const value = Reflect.get(condition, "value");
6439
- if (!(direction === "asc" ? operator === "gt" || operator === "gte" : operator === "lt" || operator === "lte") || typeof value !== "string") continue;
6440
- const inclusive = operator === "gte" || operator === "lte";
6441
- if (result === void 0 || (direction === "asc" ? value > result.id : value < result.id) || value === result.id && !inclusive) result = {
6442
- id: value,
6443
- inclusive
6444
- };
6445
- }
6446
- return result;
6447
- };
6448
- const parseStoredItem = (item) => parseDynamoDBItem(item.pk === "release_catalogs" ? boundedDynamoDBCatalogItem(item) : boundedDynamoDBMetadataItem(item));
6449
- const queryItems = async (store, input) => {
6450
- let exclusiveStartKey;
6451
- const items = [];
6452
- do {
6453
- const sortCondition = input.minimumSortKey ? " AND #sortKey >= :minimumSortKey" : "";
6454
- const page = await store.client.send(new _aws_sdk_lib_dynamodb.QueryCommand({
6455
- TableName: store.tableName,
6456
- ...input.indexName ? { IndexName: input.indexName } : {},
6457
- ...input.consistentRead ? { ConsistentRead: true } : {},
6458
- ExclusiveStartKey: exclusiveStartKey,
6459
- KeyConditionExpression: `#partitionKey = :partition${sortCondition}`,
6460
- ExpressionAttributeNames: {
6461
- "#partitionKey": input.indexName ? "gsi1pk" : "pk",
6462
- ...input.minimumSortKey ? { "#sortKey": input.indexName ? "gsi1sk" : "sk" } : {}
6463
- },
6464
- ExpressionAttributeValues: {
6465
- ":partition": input.partition,
6466
- ...input.minimumSortKey ? { ":minimumSortKey": input.minimumSortKey } : {}
6319
+ invalidate: async () => {
6320
+ try {
6321
+ await invalidateCloudFront(client, cloudfrontDistributionId, [`${apiBasePath.replace(/\/+$/, "")}/*`], { shouldWait: shouldWaitForInvalidation });
6322
+ } catch (error) {
6323
+ console.warn("[hot-updater/aws] CloudFront invalidation failed; continuing without cache invalidation.", {
6324
+ distributionId: cloudfrontDistributionId,
6325
+ error: error instanceof Error ? error.message : "Unknown error"
6326
+ });
6467
6327
  }
6468
- }));
6469
- const pageItems = (page.Items ?? []).map(parseStoredItem);
6470
- items.push(...pageItems);
6471
- exclusiveStartKey = page.LastEvaluatedKey;
6472
- } while (exclusiveStartKey !== void 0);
6473
- return items;
6474
- };
6475
- const queryBundleItemsPage = async (store, input) => {
6476
- if (input.limit === 0) return [];
6477
- let exclusiveStartKey;
6478
- const items = [];
6479
- do {
6480
- const startOperator = input.start?.inclusive ? input.direction === "asc" ? ">=" : "<=" : input.direction === "asc" ? ">" : "<";
6481
- const page = await store.client.send(new _aws_sdk_lib_dynamodb.QueryCommand({
6482
- TableName: store.tableName,
6483
- ExclusiveStartKey: exclusiveStartKey,
6484
- KeyConditionExpression: input.start ? `#pk = :pk AND #sk ${startOperator} :start` : "#pk = :pk",
6485
- ExpressionAttributeNames: {
6486
- "#pk": "pk",
6487
- ...input.start ? { "#sk": "sk" } : {}
6488
- },
6489
- ExpressionAttributeValues: {
6490
- ":pk": "bundles",
6491
- ...input.start ? { ":start": input.start.id } : {}
6492
- },
6493
- Limit: Math.max(input.limit - items.length, 100),
6494
- ScanIndexForward: input.direction === "asc"
6495
- }));
6496
- for (const item of (page.Items ?? []).map(parseStoredItem)) {
6497
- if (item.pk === "bundles" && input.matches(item.row)) items.push(item);
6498
- if (items.length === input.limit) return items;
6499
- }
6500
- exclusiveStartKey = page.LastEvaluatedKey;
6501
- } while (exclusiveStartKey !== void 0);
6502
- return items;
6503
- };
6504
- const loadModelItems = (store, model) => queryItems(store, {
6505
- partition: model,
6506
- consistentRead: true
6507
- });
6508
- const loadModelItem = async (store, model, id) => {
6509
- const { Item } = await store.client.send(new _aws_sdk_lib_dynamodb.GetCommand({
6510
- TableName: store.tableName,
6511
- Key: itemKey(model, id),
6512
- ConsistentRead: true
6513
- }));
6514
- return Item === void 0 ? void 0 : parseStoredItem(Item);
6515
- };
6516
- const loadBundleItem = async (store, id) => {
6517
- const item = await loadModelItem(store, "bundles", id);
6518
- return item?.pk === "bundles" ? item : void 0;
6519
- };
6520
- const loadPatchItem = async (store, id) => {
6521
- const item = await loadModelItem(store, "bundle_patches", id);
6522
- return item?.pk === "bundle_patches" ? item : void 0;
6523
- };
6524
- const loadChannelItem = async (store, id) => {
6525
- const item = await loadModelItem(store, DYNAMODB_CHANNEL_PARTITION, id);
6526
- return item?.pk === "channels" ? item : void 0;
6527
- };
6528
- const loadBundleItems = async (store) => {
6529
- return (await loadModelItems(store, "bundles")).filter((item) => item.pk === "bundles");
6530
- };
6531
- const loadPatchItems = async (store) => {
6532
- return (await loadModelItems(store, "bundle_patches")).filter((item) => item.pk === "bundle_patches");
6533
- };
6534
- const loadChannelItems = async (store) => {
6535
- return (await loadModelItems(store, DYNAMODB_CHANNEL_PARTITION)).filter((item) => item.pk === DYNAMODB_CHANNEL_PARTITION);
6536
- };
6537
- const DYNAMODB_RELEASE_ID_PARTITION = "_hot-updater#release-scope-by-id";
6538
- const releaseLocatorItem = (row) => ({
6539
- pk: DYNAMODB_RELEASE_ID_PARTITION,
6540
- sk: row.id,
6541
- scope_key: row.scope_key
6542
- });
6543
- const loadReleaseItem = async (store, id) => {
6544
- const locator = await store.client.send(new _aws_sdk_lib_dynamodb.GetCommand({
6545
- TableName: store.tableName,
6546
- Key: {
6547
- pk: DYNAMODB_RELEASE_ID_PARTITION,
6548
- sk: id
6549
6328
  },
6550
- ConsistentRead: true
6551
- }));
6552
- if (locator.Item === void 0) return void 0;
6553
- const scopeKey = locator.Item.scope_key;
6554
- if (typeof scopeKey !== "string") throw new DynamoDBStoredItemError();
6555
- const result = await store.client.send(new _aws_sdk_lib_dynamodb.GetCommand({
6556
- TableName: store.tableName,
6557
- Key: {
6558
- pk: releaseScopePartition(scopeKey),
6559
- sk: id
6560
- },
6561
- ConsistentRead: true
6562
- }));
6563
- if (result.Item === void 0) throw new DynamoDBStoredItemError();
6564
- const item = parseStoredItem(result.Item);
6565
- if (!isReleaseRow(item.row) || item.sk !== id) throw new DynamoDBStoredItemError();
6566
- return item;
6567
- };
6568
- const loadReleaseItemsByScope = async (store, scopeKey) => (await queryItems(store, {
6569
- partition: releaseScopePartition(scopeKey),
6570
- consistentRead: true
6571
- })).filter((item) => item.pk === releaseScopePartition(scopeKey) && isReleaseRow(item.row));
6572
- const loadReleaseItems = async (store) => {
6573
- let exclusiveStartKey;
6574
- const items = [];
6575
- do {
6576
- const page = await store.client.send(new _aws_sdk_lib_dynamodb.ScanCommand({
6577
- TableName: store.tableName,
6578
- ExclusiveStartKey: exclusiveStartKey,
6579
- ConsistentRead: true,
6580
- FilterExpression: "begins_with(#pk, :prefix)",
6581
- ExpressionAttributeNames: { "#pk": "pk" },
6582
- ExpressionAttributeValues: { ":prefix": "release-scope#" }
6583
- }));
6584
- for (const value of page.Items ?? []) {
6585
- const item = parseStoredItem(value);
6586
- if (isReleaseRow(item.row)) items.push(item);
6587
- }
6588
- exclusiveStartKey = page.LastEvaluatedKey;
6589
- } while (exclusiveStartKey !== void 0);
6590
- return items;
6591
- };
6592
- const loadReleaseCatalogItem = async (store, scopeKey) => {
6593
- const result = await store.client.send(new _aws_sdk_lib_dynamodb.GetCommand({
6594
- TableName: store.tableName,
6595
- Key: itemKey("release_catalogs", scopeKey),
6596
- ConsistentRead: true
6597
- }));
6598
- if (result.Item === void 0) return void 0;
6599
- const item = parseStoredItem(result.Item);
6600
- return item.pk === "release_catalogs" ? item : void 0;
6601
- };
6602
- const loadReleaseCatalogItems = async (store) => (await queryItems(store, {
6603
- partition: "release_catalogs",
6604
- consistentRead: true
6605
- })).filter((item) => item.pk === "release_catalogs");
6606
- const loadChannelByName = async (store, name) => {
6607
- const { Item } = await store.client.send(new _aws_sdk_lib_dynamodb.GetCommand({
6608
- TableName: store.tableName,
6609
- Key: {
6610
- pk: DYNAMODB_CHANNEL_NAME_PARTITION,
6611
- sk: name
6612
- },
6613
- ConsistentRead: true
6614
- }));
6615
- const id = Item?.channel_id;
6616
- if (Item === void 0) return void 0;
6617
- if (typeof id !== "string") throw new DynamoDBStoredItemError();
6618
- const channel = await loadChannelItem(store, id);
6619
- if (channel === void 0 || channel.row.name !== name) throw new DynamoDBStoredItemError();
6620
- return channel;
6621
- };
6622
- const insertDynamoDBChannel = async (store, input) => {
6623
- const existing = await loadChannelByName(store, input.row.name);
6624
- if (existing !== void 0) return {
6625
- row: existing.row,
6626
- inserted: false
6627
- };
6628
- const existingId = await loadChannelItem(store, input.row.id);
6629
- if (existingId !== void 0) {
6630
- if (existingId.row.name === input.row.name) return {
6631
- row: existingId.row,
6632
- inserted: false
6633
- };
6634
- throw new DynamoDBStoredItemError();
6635
- }
6636
- try {
6637
- await commitDynamoDBTransaction(store, [{ Put: {
6638
- TableName: store.tableName,
6639
- Item: boundedDynamoDBMetadataItem(toDynamoDBChannelItem(input.row)),
6640
- ConditionExpression: "attribute_not_exists(#pk)",
6641
- ExpressionAttributeNames: { "#pk": "pk" }
6642
- } }, { Put: {
6643
- TableName: store.tableName,
6644
- Item: {
6645
- pk: DYNAMODB_CHANNEL_NAME_PARTITION,
6646
- sk: input.row.name,
6647
- channel_id: input.row.id
6648
- },
6649
- ConditionExpression: "attribute_not_exists(#pk)",
6650
- ExpressionAttributeNames: { "#pk": "pk" }
6651
- } }]);
6652
- return {
6653
- row: input.row,
6654
- inserted: true
6655
- };
6656
- } catch (error) {
6657
- const canonical = await loadChannelByName(store, input.row.name);
6658
- if (canonical !== void 0) return {
6659
- row: canonical.row,
6660
- inserted: false
6661
- };
6662
- throw error;
6663
- }
6664
- };
6665
- const deleteDynamoDBChannel = async (store, input) => {
6666
- const current = await loadChannelItem(store, input.id);
6667
- if (current === void 0) return {
6668
- deleted: false,
6669
- reason: "not_found"
6670
- };
6671
- if (current.reference_count !== 0) return {
6672
- deleted: false,
6673
- reason: "not_empty"
6329
+ destroy: () => client.destroy()
6674
6330
  };
6675
- try {
6676
- await commitDynamoDBTransaction(store, [{ Delete: {
6677
- TableName: store.tableName,
6678
- Key: itemKey(DYNAMODB_CHANNEL_PARTITION, current.sk),
6679
- ConditionExpression: "#version = :version AND #referenceCount = :zero",
6680
- ExpressionAttributeNames: {
6681
- "#version": "version",
6682
- "#referenceCount": "reference_count"
6683
- },
6684
- ExpressionAttributeValues: {
6685
- ":version": current.version,
6686
- ":zero": 0
6687
- }
6688
- } }, { Delete: {
6689
- TableName: store.tableName,
6690
- Key: {
6691
- pk: DYNAMODB_CHANNEL_NAME_PARTITION,
6692
- sk: current.row.name
6693
- },
6694
- ConditionExpression: "#channelId = :channelId",
6695
- ExpressionAttributeNames: { "#channelId": "channel_id" },
6696
- ExpressionAttributeValues: { ":channelId": current.sk }
6697
- } }]);
6698
- return { deleted: true };
6699
- } catch (error) {
6700
- const latest = await loadChannelItem(store, input.id);
6701
- if (latest !== void 0 && latest.reference_count > 0) return {
6702
- deleted: false,
6703
- reason: "not_empty"
6704
- };
6705
- if (latest === void 0) return {
6706
- deleted: false,
6707
- reason: "not_found"
6708
- };
6709
- throw error;
6710
- }
6711
6331
  };
6712
- const queryOwnerPatchIds = async (store, indexName, bundleId) => {
6713
- return (await queryItems(store, {
6714
- partition: patchOwnerPartition(bundleId),
6715
- indexName
6716
- })).filter((item) => item.pk === "bundle_patches").map(({ sk }) => sk);
6717
- };
6718
- var DynamoDBTransactionLimitError = class extends Error {
6719
- name = "DynamoDBTransactionLimitError";
6720
- constructor(actionCount) {
6721
- super(`DynamoDB transaction requires ${actionCount} actions; maximum is 100`);
6722
- this.actionCount = actionCount;
6723
- }
6724
- };
6725
- const metadataUpdate = (store, delta) => {
6726
- const entries = Object.entries(delta).filter(([, value]) => value !== 0);
6727
- if (entries.length === 0) return void 0;
6332
+ //#endregion
6333
+ //#region src/dynamoDBStore.ts
6334
+ /** DynamoDB's limits: 100 items and 4 MB a transaction, 400 KB an item, and 2,048- and 1,024-byte keys. */
6335
+ const DYNAMODB_LIMITS = {
6336
+ items: 100,
6337
+ bytes: 4e6,
6338
+ itemBytes: 4e5,
6339
+ keyBytes: {
6340
+ pk: 2048,
6341
+ sk: 1024
6342
+ }
6343
+ };
6344
+ /** BatchGetItem takes 100 keys a call. */
6345
+ const GET_BATCH = 100;
6346
+ /** A column named like a key attribute is stored with a leading dot, which no column name has. */
6347
+ const attribute = (column) => column === "pk" || column === "sk" ? `.${column}` : column;
6348
+ const renamed = (record, rename) => Object.fromEntries(Object.entries(record).map(([name, value]) => [rename(name), value]));
6349
+ const toItem = (key, row) => ({
6350
+ ...renamed(row, attribute),
6351
+ pk: key.pk,
6352
+ sk: key.sk
6353
+ });
6354
+ const toRow = ({ pk: _pk, sk: _sk, ...item }) => renamed(item, (name) => name.replace(/^\./, ""));
6355
+ const idOf = ({ pk, sk }) => JSON.stringify([pk, sk]);
6356
+ const nameOf = (error) => error.name;
6357
+ const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
6358
+ /**
6359
+ * One transaction item. Its expressions bind every column name and value, so
6360
+ * no column clashes with a reserved word; `pk` and `sk` are none.
6361
+ */
6362
+ const toTransactItem = (tableName, op) => {
6728
6363
  const names = {};
6729
6364
  const values = {};
6730
- const updates = [];
6731
- const conditions = [];
6732
- for (const [model, change] of entries) {
6733
- const name = model === "bundles" ? "bundles" : "patches";
6734
- const valueName = model === "bundles" ? "bundle" : "patch";
6735
- names[`#${name}`] = name;
6736
- values[`:${valueName}Delta`] = change;
6737
- updates.push(`#${name} :${valueName}Delta`);
6738
- if (change < 0) {
6739
- values[`:${valueName}Removal`] = -change;
6740
- conditions.push(`#${name} >= :${valueName}Removal`);
6741
- }
6742
- }
6743
- return { Update: {
6744
- TableName: store.tableName,
6745
- Key: {
6746
- pk: "_hot-updater",
6747
- sk: "limits.metadata"
6748
- },
6749
- UpdateExpression: `ADD ${updates.join(", ")}`,
6750
- ...conditions.length > 0 ? { ConditionExpression: conditions.join(" AND ") } : {},
6751
- ExpressionAttributeNames: names,
6752
- ExpressionAttributeValues: values
6753
- } };
6754
- };
6755
- const updateBundleRelation = (store, bundleId, delta, ownedPatchDelta = 0) => {
6756
- const names = {
6757
- "#pk": "pk",
6758
- "#version": "version"
6365
+ const binder = (map, prefix) => (entry) => {
6366
+ const token = `${prefix}${Object.keys(map).length}`;
6367
+ map[token] = entry;
6368
+ return token;
6759
6369
  };
6760
- const values = { ":one": 1 };
6761
- const additions = [];
6762
- let condition = "attribute_exists(#pk)";
6763
- let update = "SET #version = #version + :one";
6764
- if (delta !== 0) {
6765
- names["#relationCount"] = "relation_count";
6766
- values[":relationDelta"] = delta;
6767
- additions.push("#relationCount :relationDelta");
6768
- if (delta < 0) {
6769
- values[":relationRemoval"] = -delta;
6770
- condition += " AND #relationCount >= :relationRemoval";
6771
- }
6772
- }
6773
- if (ownedPatchDelta !== 0) {
6774
- names["#ownedPatchCount"] = "owned_patch_count";
6775
- values[":ownedPatchDelta"] = ownedPatchDelta;
6776
- additions.push("#ownedPatchCount :ownedPatchDelta");
6777
- if (ownedPatchDelta < 0) {
6778
- values[":ownedPatchRemoval"] = -ownedPatchDelta;
6779
- condition += " AND #ownedPatchCount >= :ownedPatchRemoval";
6370
+ const name = binder(names, "#n");
6371
+ const value = binder(values, ":v");
6372
+ const { condition } = op;
6373
+ const guard = condition === void 0 ? void 0 : "v" in condition ? `${name("_v")} = ${value(condition.v)}` : `${condition.exists ? "attribute_exists" : "attribute_not_exists"}(pk)`;
6374
+ const item = (fields) => ({
6375
+ TableName: tableName,
6376
+ ConditionExpression: guard,
6377
+ ...fields,
6378
+ ExpressionAttributeNames: Object.keys(names).length ? names : void 0,
6379
+ ExpressionAttributeValues: Object.keys(values).length ? values : void 0
6380
+ });
6381
+ const Key = {
6382
+ pk: op.key.pk,
6383
+ sk: op.key.sk
6384
+ };
6385
+ switch (op.type) {
6386
+ case "put": return { Put: item({ Item: toItem(op.key, op.value) }) };
6387
+ case "delete": return { Delete: item({ Key }) };
6388
+ case "check": return { ConditionCheck: item({
6389
+ Key,
6390
+ ConditionExpression: guard
6391
+ }) };
6392
+ case "add": {
6393
+ const init = op.init ?? {};
6394
+ return { Update: item({
6395
+ Key,
6396
+ UpdateExpression: `SET ${Object.keys({
6397
+ ...init,
6398
+ ...op.by
6399
+ }).map((column) => {
6400
+ const path = name(attribute(column));
6401
+ const by = op.by[column];
6402
+ if (by === void 0) return `${path} = if_not_exists(${path}, ${value(init[column])})`;
6403
+ return `${path} = if_not_exists(${path}, ${value(Number(init[column] ?? 0))}) + ${value(by)}`;
6404
+ }).join(", ")}`
6405
+ }) };
6780
6406
  }
6781
6407
  }
6782
- if (additions.length > 0) update += ` ADD ${additions.join(", ")}`;
6783
- return { Update: {
6784
- TableName: store.tableName,
6785
- Key: itemKey("bundles", bundleId),
6786
- ConditionExpression: condition,
6787
- ExpressionAttributeNames: names,
6788
- ExpressionAttributeValues: values,
6789
- UpdateExpression: update
6790
- } };
6791
- };
6792
- const commitDynamoDBTransaction = async (store, actions) => {
6793
- if (actions.length > 100) throw new DynamoDBTransactionLimitError(actions.length);
6794
- await store.client.send(new _aws_sdk_lib_dynamodb.TransactWriteCommand({ TransactItems: [...actions] }));
6795
- };
6796
- const DYNAMODB_BATCH_GET_ATTEMPTS = 5;
6797
- const DYNAMODB_BATCH_GET_BASE_DELAY_MS = 25;
6798
- var DynamoDBBatchGetExhaustedError = class extends Error {
6799
- name = "DynamoDBBatchGetExhaustedError";
6800
- constructor(unprocessedKeyCount) {
6801
- super(`DynamoDB did not process ${unprocessedKeyCount} batch-get keys after ${DYNAMODB_BATCH_GET_ATTEMPTS} attempts`);
6802
- this.unprocessedKeyCount = unprocessedKeyCount;
6803
- }
6804
6408
  };
6805
- const waitBeforeRetry = (attempt) => new Promise((resolve) => setTimeout(resolve, DYNAMODB_BATCH_GET_BASE_DELAY_MS * 2 ** attempt));
6806
- const batchGetDynamoDBItems = async (store, keys) => {
6807
- const items = [];
6808
- for (let offset = 0; offset < keys.length; offset += 100) {
6809
- let pending = keys.slice(offset, offset + 100);
6810
- for (let attempt = 0; attempt < DYNAMODB_BATCH_GET_ATTEMPTS && pending.length > 0; attempt++) {
6811
- const { Responses, UnprocessedKeys } = await store.client.send(new _aws_sdk_lib_dynamodb.BatchGetCommand({ RequestItems: { [store.tableName]: {
6812
- ConsistentRead: true,
6813
- Keys: pending
6814
- } } }));
6815
- items.push(...Responses?.[store.tableName] ?? []);
6816
- pending = UnprocessedKeys?.[store.tableName]?.Keys ?? [];
6817
- if (pending.length > 0 && attempt + 1 < DYNAMODB_BATCH_GET_ATTEMPTS) await waitBeforeRetry(attempt);
6818
- }
6819
- if (pending.length > 0) throw new DynamoDBBatchGetExhaustedError(pending.length);
6820
- }
6821
- return items;
6822
- };
6823
- const parseKeyReadItem = (item) => parseDynamoDBItem(boundedDynamoDBMetadataItem(item));
6824
- const loadBundleItemsById = async (store, ids) => {
6825
- const uniqueIds = [...new Set(ids)];
6826
- if (uniqueIds.length === 0) return [];
6827
- return (await batchGetDynamoDBItems(store, uniqueIds.map((id) => itemKey("bundles", id)))).map(parseKeyReadItem).filter((item) => item.pk === "bundles");
6828
- };
6829
- const loadPatchItemsById = async (store, ids) => {
6830
- const uniqueIds = [...new Set(ids)];
6831
- if (uniqueIds.length === 0) return [];
6832
- return (await batchGetDynamoDBItems(store, uniqueIds.map((id) => itemKey("bundle_patches", id)))).map(parseKeyReadItem).filter((item) => item.pk === "bundle_patches");
6833
- };
6834
- const loadMetadataCount = async (store, model) => {
6835
- if (model !== "bundles" && model !== "bundle_patches") return void 0;
6836
- const field = model === "bundles" ? "bundles" : "patches";
6837
- const { Item } = await store.client.send(new _aws_sdk_lib_dynamodb.GetCommand({
6838
- TableName: store.tableName,
6839
- Key: {
6840
- pk: "_hot-updater",
6841
- sk: "limits.metadata"
6842
- },
6843
- ConsistentRead: true,
6844
- ProjectionExpression: "#count",
6845
- ExpressionAttributeNames: { "#count": field }
6846
- }));
6847
- const count = Item?.[field];
6848
- return typeof count === "number" && Number.isSafeInteger(count) && count >= 0 ? count : void 0;
6849
- };
6850
- var DynamoDBPatchIndexConsistencyError = class extends Error {
6851
- name = "DynamoDBPatchIndexConsistencyError";
6852
- constructor(bundleId, expectedCount) {
6853
- super(`DynamoDB patch index did not converge to ${expectedCount} rows for bundle "${bundleId}"`);
6854
- this.bundleId = bundleId;
6855
- this.expectedCount = expectedCount;
6856
- }
6857
- };
6858
- const waitForIndex = (attempt) => new Promise((resolve) => setTimeout(resolve, 20 * (attempt + 1)));
6859
- const queryCompleteOwnerPatches = async (store, indexName, bundleId) => {
6860
- const owner = await loadBundleItem(store, bundleId);
6861
- if (!owner || owner.owned_patch_count === 0) return [];
6862
- for (let attempt = 0; attempt < 3; attempt++) {
6863
- const patchIds = await queryOwnerPatchIds(store, indexName, bundleId);
6864
- const patchIdSet = new Set(patchIds);
6865
- const patches = (await loadPatchItemsById(store, patchIds)).filter(({ row }) => row.bundle_id === bundleId);
6866
- if (patchIds.length === owner.owned_patch_count && patchIdSet.size === owner.owned_patch_count && patches.length === owner.owned_patch_count && patches.every(({ sk }) => patchIdSet.has(sk))) return patches.map(({ row }) => row).sort((left, right) => left.order_index - right.order_index || left.id.localeCompare(right.id));
6867
- if (attempt < 2) await waitForIndex(attempt);
6868
- }
6869
- throw new DynamoDBPatchIndexConsistencyError(bundleId, owner.owned_patch_count);
6870
- };
6871
- const queryCompleteOwnersPatches = async (store, indexName, bundleIds) => {
6872
- const owners = (await loadBundleItemsById(store, bundleIds)).filter(({ owned_patch_count }) => owned_patch_count > 0);
6873
- const firstOwner = owners[0];
6874
- if (!firstOwner) return [];
6875
- let failedOwner = firstOwner;
6876
- for (let attempt = 0; attempt < 3; attempt++) {
6877
- const candidateIdsByOwner = await Promise.all(owners.map(({ sk }) => queryOwnerPatchIds(store, indexName, sk)));
6878
- const patches = await loadPatchItemsById(store, candidateIdsByOwner.flat());
6879
- const patchesById = new Map(patches.map((patch) => [patch.sk, patch]));
6880
- const mismatchedOwner = owners.find((owner, ownerIndex) => {
6881
- const ids = candidateIdsByOwner[ownerIndex] ?? [];
6882
- const uniqueIds = new Set(ids);
6883
- return ids.length !== owner.owned_patch_count || uniqueIds.size !== owner.owned_patch_count || ids.some((id) => patchesById.get(id)?.row.bundle_id !== owner.sk);
6884
- });
6885
- if (!mismatchedOwner) return patches.map(({ row }) => row);
6886
- failedOwner = mismatchedOwner;
6887
- if (attempt < 2) await waitForIndex(attempt);
6888
- }
6889
- throw new DynamoDBPatchIndexConsistencyError(failedOwner.sk, failedOwner.owned_patch_count);
6890
- };
6891
- const createDynamoDBBundlePatchTable = (store, indexName) => ({ findByBundleIds: (bundleIds) => bundleIds.length === 0 ? Promise.resolve([]) : queryCompleteOwnersPatches(store, indexName, bundleIds) });
6892
- const compare = (left, right) => {
6893
- if (typeof left === "number" && typeof right === "number") return left - right;
6894
- if (left == null) return right == null ? 0 : -1;
6895
- if (right == null) return 1;
6896
- return JSON.stringify(left).localeCompare(JSON.stringify(right));
6897
- };
6898
- const compareString = (actual, expected, mode, predicate) => {
6899
- if (typeof actual !== "string") return false;
6900
- return mode === "insensitive" ? predicate(actual.toLocaleLowerCase(), expected.toLocaleLowerCase()) : predicate(actual, expected);
6901
- };
6902
- const matchesCondition = (row, condition) => {
6903
- const actual = Reflect.get(row, condition.field);
6904
- const expected = Reflect.get(condition, "value");
6905
- switch (Reflect.get(condition, "operator") ?? "eq") {
6906
- case "eq": return typeof expected === "string" ? compareString(actual, expected, Reflect.get(condition, "mode"), (value, query) => value === query) : actual === expected;
6907
- case "ne":
6908
- if (actual == null) return false;
6909
- return typeof expected === "string" ? !compareString(actual, expected, Reflect.get(condition, "mode"), (value, query) => value === query) : actual !== expected;
6910
- case "gt": return actual != null && compare(actual, expected) > 0;
6911
- case "gte": return actual != null && compare(actual, expected) >= 0;
6912
- case "lt": return actual != null && compare(actual, expected) < 0;
6913
- case "lte": return actual != null && compare(actual, expected) <= 0;
6914
- case "in": return Array.isArray(expected) && expected.some((candidate) => candidate === actual);
6915
- case "not_in": return Array.isArray(expected) && (expected.length === 0 || actual != null && expected.every((candidate) => candidate !== actual));
6916
- case "contains": return typeof expected === "string" ? compareString(actual, expected, Reflect.get(condition, "mode"), (value, query) => value.includes(query)) : false;
6917
- case "starts_with": return typeof expected === "string" ? compareString(actual, expected, Reflect.get(condition, "mode"), (value, query) => value.startsWith(query)) : false;
6918
- case "ends_with": return typeof expected === "string" ? compareString(actual, expected, Reflect.get(condition, "mode"), (value, query) => value.endsWith(query)) : false;
6919
- default: return false;
6920
- }
6921
- };
6922
- const matchesDynamoDBWhere = (row, where) => {
6923
- if (!where || where.length === 0) return true;
6924
- let result = matchesCondition(row, where[0]);
6925
- for (const condition of where.slice(1)) {
6926
- const current = matchesCondition(row, condition);
6927
- result = condition.connector === "OR" ? result || current : result && current;
6928
- }
6929
- return result;
6930
- };
6931
- const compareRows = (left, right, orderBy) => {
6932
- for (const clause of orderBy) {
6933
- const leftValue = Reflect.get(left, clause.field);
6934
- const rightValue = Reflect.get(right, clause.field);
6935
- if (leftValue == null || rightValue == null) {
6936
- if (leftValue == null && rightValue == null) continue;
6937
- const nulls = clause.nulls ?? (clause.direction === "asc" ? "last" : "first");
6938
- const order = leftValue == null ? -1 : 1;
6939
- return nulls === "first" ? order : -order;
6940
- }
6941
- const order = compare(leftValue, rightValue);
6942
- if (order !== 0) return clause.direction === "asc" ? order : -order;
6943
- }
6944
- return 0;
6945
- };
6946
- const distinctKey = (row, distinctOn) => JSON.stringify(distinctOn.fields.map((field) => Reflect.get(row, field)));
6947
- const queryDynamoDBRows = (rows, input) => {
6948
- const filtered = rows.filter((row) => matchesDynamoDBWhere(row, input.where));
6949
- const orderBy = input.orderBy;
6950
- const ordered = orderBy ? filtered.toSorted((left, right) => compareRows(left, right, orderBy)) : filtered;
6951
- const distinctOn = input.distinctOn;
6952
- const distinct = distinctOn ? (() => {
6953
- const seen = /* @__PURE__ */ new Set();
6954
- return ordered.filter((row) => {
6955
- const key = distinctKey(row, distinctOn);
6956
- if (seen.has(key)) return false;
6957
- seen.add(key);
6958
- return true;
6959
- });
6960
- })() : ordered;
6961
- const offset = input.offset ?? 0;
6962
- return distinct.slice(offset, offset + (input.limit ?? 100));
6963
- };
6964
- const countDistinctDynamoDBRows = (rows, fields) => {
6965
- if (fields === void 0) return rows.length;
6966
- return new Set(rows.map((row) => JSON.stringify(fields.map((field) => Reflect.get(row, field))))).size;
6967
- };
6968
- const createDynamoDBBundle = async (store, row) => {
6969
- const counter = metadataUpdate(store, { bundles: 1 });
6970
- if (!counter) return;
6971
- await commitDynamoDBTransaction(store, [counter, { Put: {
6972
- TableName: store.tableName,
6973
- Item: boundedDynamoDBMetadataItem(toDynamoDBBundleItem(row)),
6974
- ConditionExpression: "attribute_not_exists(#pk)",
6975
- ExpressionAttributeNames: { "#pk": "pk" }
6976
- } }]);
6977
- };
6978
- const createDynamoDBPatch = async (store, row) => {
6979
- const bundleIds = [...new Set([row.bundle_id, row.base_bundle_id])];
6980
- const counter = metadataUpdate(store, { bundle_patches: 1 });
6981
- if (!counter) return;
6982
- await commitDynamoDBTransaction(store, [
6983
- counter,
6984
- ...bundleIds.map((bundleId) => updateBundleRelation(store, bundleId, 1, bundleId === row.bundle_id ? 1 : 0)),
6985
- { Put: {
6986
- TableName: store.tableName,
6987
- Item: boundedDynamoDBMetadataItem(toDynamoDBPatchItem(row)),
6988
- ConditionExpression: "attribute_not_exists(#pk)",
6989
- ExpressionAttributeNames: { "#pk": "pk" }
6990
- } }
6991
- ]);
6992
- };
6993
- const replaceDynamoDBBundle = async (store, current, row) => {
6994
- await store.client.send(new _aws_sdk_lib_dynamodb.PutCommand({
6995
- TableName: store.tableName,
6996
- Item: boundedDynamoDBMetadataItem(toDynamoDBBundleItem(row, current.version + 1, current.relation_count, current.owned_patch_count)),
6997
- ConditionExpression: "#version = :currentVersion",
6998
- ExpressionAttributeNames: { "#version": "version" },
6999
- ExpressionAttributeValues: { ":currentVersion": current.version }
7000
- }));
7001
- };
7002
- const deleteAction = (store, item) => ({ Delete: {
7003
- TableName: store.tableName,
7004
- Key: {
7005
- pk: item.pk,
7006
- sk: item.sk
7007
- },
7008
- ConditionExpression: "#version = :currentVersion",
7009
- ExpressionAttributeNames: { "#version": "version" },
7010
- ExpressionAttributeValues: { ":currentVersion": item.version }
7011
- } });
7012
- const deleteDynamoDBPatch = async (store, item) => {
7013
- const counter = metadataUpdate(store, { bundle_patches: -1 });
7014
- if (!counter) return;
7015
- await commitDynamoDBTransaction(store, [
7016
- counter,
7017
- ...[...new Set([item.row.bundle_id, item.row.base_bundle_id])].map((id) => updateBundleRelation(store, id, -1, id === item.row.bundle_id ? -1 : 0)),
7018
- deleteAction(store, item)
7019
- ]);
7020
- };
7021
- const deleteBundle = async (store, bundle, patches) => {
7022
- const counter = metadataUpdate(store, {
7023
- bundles: -1,
7024
- bundle_patches: -patches.length
7025
- });
7026
- if (!counter) return;
7027
- const otherEndpointCounts = /* @__PURE__ */ new Map();
7028
- for (const { row } of patches) for (const id of new Set([row.bundle_id, row.base_bundle_id])) {
7029
- if (id === bundle.sk) continue;
7030
- const current = otherEndpointCounts.get(id) ?? {
7031
- owned: 0,
7032
- relations: 0
7033
- };
7034
- otherEndpointCounts.set(id, {
7035
- owned: current.owned + (id === row.bundle_id ? 1 : 0),
7036
- relations: current.relations + 1
7037
- });
7038
- }
7039
- await commitDynamoDBTransaction(store, [
7040
- counter,
7041
- ...[...otherEndpointCounts].map(([id, count]) => updateBundleRelation(store, id, -count.relations, -count.owned)),
7042
- ...patches.map((patch) => deleteAction(store, patch)),
7043
- deleteAction(store, bundle)
7044
- ]);
7045
- };
7046
- const deleteDynamoDBBundles = async (store, bundles, allPatches) => {
7047
- let patches = [...allPatches];
7048
- const versionIncrements = /* @__PURE__ */ new Map();
7049
- for (const originalBundle of bundles) {
7050
- const bundle = {
7051
- ...originalBundle,
7052
- version: originalBundle.version + (versionIncrements.get(originalBundle.sk) ?? 0)
7053
- };
7054
- const related = patches.filter(({ row }) => row.bundle_id === bundle.sk || row.base_bundle_id === bundle.sk);
7055
- await deleteBundle(store, bundle, related);
7056
- const updatedBundleIds = new Set(related.flatMap(({ row }) => [row.bundle_id, row.base_bundle_id]));
7057
- updatedBundleIds.delete(bundle.sk);
7058
- for (const id of updatedBundleIds) versionIncrements.set(id, (versionIncrements.get(id) ?? 0) + 1);
7059
- const removedPatchIds = new Set(related.map(({ sk }) => sk));
7060
- patches = patches.filter(({ sk }) => !removedPatchIds.has(sk));
7061
- }
6409
+ /** Errors, and cancellation reasons, that a retry of the whole write can clear. */
6410
+ const TRANSIENT = /* @__PURE__ */ new Set([
6411
+ "TransactionConflict",
6412
+ "ThrottlingError",
6413
+ "ProvisionedThroughputExceeded",
6414
+ "RequestLimitExceeded",
6415
+ "TransactionInProgressException",
6416
+ "ProvisionedThroughputExceededException",
6417
+ "ThrottlingException",
6418
+ "InternalServerError"
6419
+ ]);
6420
+ /** A missing table reads as one to the schema fence, like SQL's 42P01. */
6421
+ const missingTable = (error) => {
6422
+ throw nameOf(error) === "ResourceNotFoundException" ? Object.assign(new Error(error.message, { cause: error }), { code: "42P01" }) : error;
7062
6423
  };
7063
- var DynamoDBUnsupportedModelError = class extends Error {
7064
- name = "DynamoDBUnsupportedModelError";
7065
- constructor() {
7066
- super("DynamoDB received an unsupported database model");
7067
- }
7068
- };
7069
- const distinctFields = (fields) => fields;
7070
- const exactDynamoDBField = (where, fieldName) => {
7071
- const conditions = (where ?? []).filter((condition) => Reflect.get(condition, "field") === fieldName);
7072
- if (conditions.length !== 1) return void 0;
7073
- const condition = conditions[0];
7074
- if ((Reflect.get(condition, "operator") ?? "eq") !== "eq" || Reflect.get(condition, "mode") === "insensitive") return;
7075
- const value = Reflect.get(condition, "value");
7076
- return typeof value === "string" ? value : void 0;
7077
- };
7078
- const createDynamoDBCrud = (store, updateIndexName) => ({
7079
- async create(input) {
7080
- switch (input.model) {
7081
- case "bundles":
7082
- await createDynamoDBBundle(store, input.data);
7083
- return input.data;
7084
- case "bundle_patches":
7085
- await createDynamoDBPatch(store, input.data);
7086
- return input.data;
7087
- case "releases": {
7088
- const channel = await loadChannelItem(store, input.data.channel_id);
7089
- if (channel === void 0) throw new DynamoDBStoredItemError();
7090
- await commitDynamoDBTransaction(store, [
7091
- updateChannelReferenceCount(store, channel, 1),
7092
- { Put: {
7093
- TableName: store.tableName,
7094
- Item: boundedDynamoDBMetadataItem(toDynamoDBReleaseItem(input.data)),
7095
- ConditionExpression: "attribute_not_exists(#pk)",
7096
- ExpressionAttributeNames: { "#pk": "pk" }
7097
- } },
7098
- { Put: {
7099
- TableName: store.tableName,
7100
- Item: releaseLocatorItem(input.data),
7101
- ConditionExpression: "attribute_not_exists(#pk)",
7102
- ExpressionAttributeNames: { "#pk": "pk" }
7103
- } }
7104
- ]);
7105
- return input.data;
7106
- }
7107
- case "release_catalogs":
7108
- await store.client.send(new _aws_sdk_lib_dynamodb.PutCommand({
7109
- TableName: store.tableName,
7110
- Item: boundedDynamoDBCatalogItem(toDynamoDBReleaseCatalogItem(input.data)),
7111
- ConditionExpression: "attribute_not_exists(#pk)",
7112
- ExpressionAttributeNames: { "#pk": "pk" }
6424
+ /**
6425
+ * The key-value store over one DynamoDB table keyed by `pk` and `sk`, with
6426
+ * no secondary index. Reads are strongly consistent; a write is one
6427
+ * `TransactWriteItems` with a client request token.
6428
+ */
6429
+ const createDynamoDBStore = ({ client, tableName, nativePageSize }) => {
6430
+ const documents = _aws_sdk_lib_dynamodb.DynamoDBDocumentClient.from(client, { marshallOptions: { removeUndefinedValues: true } });
6431
+ return {
6432
+ id: "dynamoDB",
6433
+ limits: DYNAMODB_LIMITS,
6434
+ async get(keys) {
6435
+ const found = /* @__PURE__ */ new Map();
6436
+ const unique = [...new Map(keys.map((key) => [idOf(key), key])).values()];
6437
+ for (let at = 0; at < unique.length; at += GET_BATCH) {
6438
+ let pending = unique.slice(at, at + GET_BATCH).map(({ pk, sk }) => ({
6439
+ pk,
6440
+ sk
7113
6441
  }));
7114
- return input.data;
7115
- case "channels": return (await insertDynamoDBChannel(store, {
7116
- row: input.data,
7117
- onConflict: "returnExisting"
7118
- })).row;
7119
- }
7120
- throw new DynamoDBUnsupportedModelError();
7121
- },
7122
- async update(input) {
7123
- if (input.model === "releases") {
7124
- const id = exactDynamoDBId(input.where);
7125
- if (id === void 0) throw new DynamoDBUnsupportedModelError();
7126
- const current = await loadReleaseItem(store, id);
7127
- if (current === void 0) return null;
7128
- const updated = {
7129
- ...current.row,
7130
- ...input.update
7131
- };
7132
- const nextPartition = releaseScopePartition(updated.scope_key);
7133
- const moved = current.pk !== nextPartition;
7134
- await commitDynamoDBTransaction(store, [
7135
- ...moved ? [{ Delete: {
7136
- TableName: store.tableName,
7137
- Key: {
7138
- pk: current.pk,
7139
- sk: current.sk
7140
- },
7141
- ConditionExpression: "#version = :version",
7142
- ExpressionAttributeNames: { "#version": "version" },
7143
- ExpressionAttributeValues: { ":version": current.version }
7144
- } }] : [],
7145
- { Put: {
7146
- TableName: store.tableName,
7147
- Item: boundedDynamoDBMetadataItem(toDynamoDBReleaseItem(updated, current.version + 1)),
7148
- ConditionExpression: moved ? "attribute_not_exists(#pk)" : "#version = :version",
7149
- ExpressionAttributeNames: moved ? { "#pk": "pk" } : { "#version": "version" },
7150
- ...moved ? {} : { ExpressionAttributeValues: { ":version": current.version } }
7151
- } },
7152
- { Put: {
7153
- TableName: store.tableName,
7154
- Item: releaseLocatorItem(updated),
7155
- ConditionExpression: "#scopeKey = :scopeKey",
7156
- ExpressionAttributeNames: { "#scopeKey": "scope_key" },
7157
- ExpressionAttributeValues: { ":scopeKey": current.row.scope_key }
7158
- } }
7159
- ]);
7160
- return updated;
7161
- }
7162
- if (input.model === "release_catalogs") {
7163
- const scopeKey = exactDynamoDBField(input.where, "scope_key");
7164
- if (scopeKey === void 0) throw new DynamoDBUnsupportedModelError();
7165
- const current = await loadReleaseCatalogItem(store, scopeKey);
7166
- if (current === void 0) return null;
7167
- const updated = {
7168
- ...current.row,
7169
- ...input.update
7170
- };
7171
- await store.client.send(new _aws_sdk_lib_dynamodb.PutCommand({
7172
- TableName: store.tableName,
7173
- Item: boundedDynamoDBCatalogItem(toDynamoDBReleaseCatalogItem(updated, current.version + 1)),
7174
- ConditionExpression: "#version = :version",
7175
- ExpressionAttributeNames: { "#version": "version" },
7176
- ExpressionAttributeValues: { ":version": current.version }
7177
- }));
7178
- return updated;
7179
- }
7180
- if (input.model !== "bundles") throw new DynamoDBUnsupportedModelError();
7181
- const id = exactDynamoDBId(input.where);
7182
- const current = id === void 0 ? (await loadBundleItems(store)).find(({ row }) => matchesDynamoDBWhere(row, input.where)) : await loadBundleItem(store, id);
7183
- if (!current) return null;
7184
- const updated = {
7185
- ...current.row,
7186
- ...input.update
7187
- };
7188
- await replaceDynamoDBBundle(store, current, updated);
7189
- return updated;
7190
- },
7191
- async delete(input) {
7192
- if (input.model === "channels") {
7193
- const id = exactDynamoDBId(input.where);
7194
- if (id !== void 0) await deleteDynamoDBChannel(store, { id });
7195
- return;
7196
- }
7197
- if (input.model === "bundle_patches") {
7198
- const items = (await loadPatchItems(store)).filter(({ row }) => matchesDynamoDBWhere(row, input.where));
7199
- for (const item of items) await deleteDynamoDBPatch(store, item);
7200
- return;
7201
- }
7202
- if (input.model === "releases") {
7203
- const items = (await loadReleaseItems(store)).filter(({ row }) => matchesDynamoDBWhere(row, input.where));
7204
- for (const item of items) {
7205
- const channel = await loadChannelItem(store, item.row.channel_id);
7206
- if (channel === void 0) throw new DynamoDBStoredItemError();
7207
- await commitDynamoDBTransaction(store, [
7208
- updateChannelReferenceCount(store, channel, -1),
7209
- deleteAction(store, item),
7210
- { Delete: {
7211
- TableName: store.tableName,
7212
- Key: {
7213
- pk: DYNAMODB_RELEASE_ID_PARTITION,
7214
- sk: item.sk
7215
- },
7216
- ConditionExpression: "#scopeKey = :scopeKey",
7217
- ExpressionAttributeNames: { "#scopeKey": "scope_key" },
7218
- ExpressionAttributeValues: { ":scopeKey": item.row.scope_key }
7219
- } }
7220
- ]);
7221
- }
7222
- return;
7223
- }
7224
- if (input.model !== "bundles") throw new DynamoDBUnsupportedModelError();
7225
- const bundleItems = (await loadBundleItems(store)).filter(({ row }) => matchesDynamoDBWhere(row, input.where));
7226
- if (bundleItems.length === 0) return;
7227
- await deleteDynamoDBBundles(store, bundleItems, await loadPatchItems(store));
7228
- },
7229
- async count(input) {
7230
- if ((input.where === void 0 || input.where.length === 0) && input.distinct === void 0) {
7231
- const count = await loadMetadataCount(store, input.model);
7232
- if (count !== void 0) return count;
7233
- }
7234
- switch (input.model) {
7235
- case "bundles": return countDistinctDynamoDBRows((await loadBundleItems(store)).map(({ row }) => row).filter((row) => matchesDynamoDBWhere(row, input.where)), distinctFields(input.distinct));
7236
- case "bundle_patches": {
7237
- const ownerId = exactDynamoDBPatchOwner(input.where);
7238
- return countDistinctDynamoDBRows((ownerId ? await queryCompleteOwnerPatches(store, updateIndexName, ownerId) : (await loadPatchItems(store)).map(({ row }) => row)).filter((row) => matchesDynamoDBWhere(row, input.where)), distinctFields(input.distinct));
7239
- }
7240
- case "releases": {
7241
- const scopeKey = exactDynamoDBField(input.where, "scope_key");
7242
- return countDistinctDynamoDBRows((scopeKey === void 0 ? await loadReleaseItems(store) : await loadReleaseItemsByScope(store, scopeKey)).map(({ row }) => row).filter((row) => matchesDynamoDBWhere(row, input.where)), distinctFields(input.distinct));
7243
- }
7244
- }
7245
- throw new DynamoDBUnsupportedModelError();
7246
- },
7247
- async findOne(input) {
7248
- const id = exactDynamoDBId(input.where);
7249
- switch (input.model) {
7250
- case "bundles":
7251
- if (id !== void 0) return (await loadBundleItem(store, id))?.row ?? null;
7252
- return (await loadBundleItems(store)).map(({ row }) => row).find((row) => matchesDynamoDBWhere(row, input.where)) ?? null;
7253
- case "bundle_patches":
7254
- if (id !== void 0) return (await loadPatchItem(store, id))?.row ?? null;
7255
- {
7256
- const ownerId = exactDynamoDBPatchOwner(input.where);
7257
- if (ownerId === void 0) break;
7258
- return (await queryCompleteOwnerPatches(store, updateIndexName, ownerId)).find((row) => matchesDynamoDBWhere(row, input.where)) ?? null;
7259
- }
7260
- break;
7261
- case "channels":
7262
- if (id !== void 0) return (await loadChannelItem(store, id))?.row ?? null;
7263
- return (await loadChannelItems(store)).map(({ row }) => row).find((row) => matchesDynamoDBWhere(row, input.where)) ?? null;
7264
- case "releases":
7265
- if (id !== void 0) return (await loadReleaseItem(store, id))?.row ?? null;
7266
- {
7267
- const scopeKey = exactDynamoDBField(input.where, "scope_key");
7268
- return (scopeKey === void 0 ? await loadReleaseItems(store) : await loadReleaseItemsByScope(store, scopeKey)).map(({ row }) => row).find((row) => matchesDynamoDBWhere(row, input.where)) ?? null;
7269
- }
7270
- case "release_catalogs": {
7271
- const scopeKey = exactDynamoDBField(input.where, "scope_key");
7272
- if (scopeKey !== void 0) return (await loadReleaseCatalogItem(store, scopeKey))?.row ?? null;
7273
- return (await loadReleaseCatalogItems(store)).map(({ row }) => row).find((row) => matchesDynamoDBWhere(row, input.where)) ?? null;
7274
- }
7275
- }
7276
- if (input.model === "bundle_patches") return (await loadPatchItems(store)).map(({ row }) => row).find((row) => matchesDynamoDBWhere(row, input.where)) ?? null;
7277
- throw new DynamoDBUnsupportedModelError();
7278
- },
7279
- async findMany(input) {
7280
- if (input.limit === 0) return [];
7281
- switch (input.model) {
7282
- case "bundles": {
7283
- const ids = exactDynamoDBBundleIds(input.where);
7284
- if (ids !== void 0) return queryDynamoDBRows((await loadBundleItemsById(store, ids)).map(({ row }) => row), input);
7285
- const orderBy = input.orderBy;
7286
- const direction = orderBy?.[0]?.direction;
7287
- if ((input.offset ?? 0) === 0 && input.distinctOn === void 0 && orderBy?.length === 1 && orderBy[0]?.field === "id" && (direction === "asc" || direction === "desc")) return (await queryBundleItemsPage(store, {
7288
- direction,
7289
- limit: input.limit ?? 100,
7290
- matches: (row) => matchesDynamoDBWhere(row, input.where),
7291
- start: dynamoDBBundlePageStart(input.where, direction)
7292
- })).map(({ row }) => row);
7293
- return queryDynamoDBRows((await loadBundleItems(store)).map(({ row }) => row), input);
7294
- }
7295
- case "bundle_patches": {
7296
- const ownerIds = exactDynamoDBPatchOwners(input.where);
7297
- if (ownerIds !== void 0) {
7298
- const ownerId = ownerIds.length === 1 ? ownerIds[0] : void 0;
7299
- return queryDynamoDBRows(ownerId !== void 0 ? await queryCompleteOwnerPatches(store, updateIndexName, ownerId) : await queryCompleteOwnersPatches(store, updateIndexName, ownerIds), input);
6442
+ for (let round = 0; pending.length > 0; round += 1) {
6443
+ if (round > 0) await sleep(2 ** round * 10);
6444
+ const result = await documents.send(new _aws_sdk_lib_dynamodb.BatchGetCommand({ RequestItems: { [tableName]: {
6445
+ Keys: pending,
6446
+ ConsistentRead: true
6447
+ } } })).catch(missingTable);
6448
+ for (const item of result.Responses?.[tableName] ?? []) found.set(idOf(item), toRow(item));
6449
+ pending = result.UnprocessedKeys?.[tableName]?.Keys ?? [];
7300
6450
  }
7301
- return queryDynamoDBRows((await loadPatchItems(store)).map(({ row }) => row), input);
7302
- }
7303
- case "channels": return queryDynamoDBRows((await loadChannelItems(store)).map(({ row }) => row), input);
7304
- case "releases": {
7305
- const scopeKey = exactDynamoDBField(input.where, "scope_key");
7306
- return queryDynamoDBRows((scopeKey === void 0 ? await loadReleaseItems(store) : await loadReleaseItemsByScope(store, scopeKey)).map(({ row }) => row), input);
7307
6451
  }
7308
- case "release_catalogs": return queryDynamoDBRows((await loadReleaseCatalogItems(store)).map(({ row }) => row), input);
7309
- }
7310
- throw new DynamoDBUnsupportedModelError();
7311
- },
7312
- insertChannel: (input) => insertDynamoDBChannel(store, input),
7313
- deleteChannel: (input) => deleteDynamoDBChannel(store, input)
7314
- });
7315
- var DynamoDBDuplicatePatchError = class extends Error {
7316
- name = "DynamoDBDuplicatePatchError";
7317
- constructor(patchId) {
7318
- super(`DynamoDB bundle mutation contains duplicate patch "${patchId}"`);
7319
- this.patchId = patchId;
7320
- }
7321
- };
7322
- const putNewBundle = (store, row, relationCount, ownedPatchCount = relationCount) => ({ Put: {
7323
- TableName: store.tableName,
7324
- Item: boundedDynamoDBMetadataItem(toDynamoDBBundleItem(row, 1, relationCount, ownedPatchCount)),
7325
- ConditionExpression: "attribute_not_exists(#pk)",
7326
- ExpressionAttributeNames: { "#pk": "pk" }
7327
- } });
7328
- const putUpdatedBundle = (store, current, row, relationCount, ownedPatchCount) => ({ Put: {
7329
- TableName: store.tableName,
7330
- Item: boundedDynamoDBMetadataItem(toDynamoDBBundleItem(row, current.version + 1, relationCount, ownedPatchCount)),
7331
- ConditionExpression: "#version = :currentVersion",
7332
- ExpressionAttributeNames: { "#version": "version" },
7333
- ExpressionAttributeValues: { ":currentVersion": current.version }
7334
- } });
7335
- const putPatch = (store, row, current) => ({ Put: {
7336
- TableName: store.tableName,
7337
- Item: boundedDynamoDBMetadataItem(toDynamoDBPatchItem(row, (current?.version ?? 0) + 1)),
7338
- ConditionExpression: current ? "#version = :currentVersion" : "attribute_not_exists(#pk)",
7339
- ExpressionAttributeNames: current ? { "#version": "version" } : { "#pk": "pk" },
7340
- ...current ? { ExpressionAttributeValues: { ":currentVersion": current.version } } : {}
7341
- } });
7342
- const deletePatch = (store, item) => ({ Delete: {
7343
- TableName: store.tableName,
7344
- Key: itemKey(item.pk, item.sk),
7345
- ConditionExpression: "#version = :currentVersion",
7346
- ExpressionAttributeNames: { "#version": "version" },
7347
- ExpressionAttributeValues: { ":currentVersion": item.version }
7348
- } });
7349
- var DynamoDBCommitStateError = class extends Error {
7350
- name = "DynamoDBCommitStateError";
7351
- constructor(message) {
7352
- super(message);
7353
- }
7354
- };
7355
- const rowsEqual = (left, right) => JSON.stringify(left) === JSON.stringify(right);
7356
- const putChannel = (store, row, current, referenceCount) => ({ Put: {
7357
- TableName: store.tableName,
7358
- Item: boundedDynamoDBMetadataItem(toDynamoDBChannelItem(row, (current?.version ?? 0) + 1, referenceCount)),
7359
- ConditionExpression: current ? "#version = :version" : "attribute_not_exists(#pk)",
7360
- ExpressionAttributeNames: current ? { "#version": "version" } : { "#pk": "pk" },
7361
- ...current ? { ExpressionAttributeValues: { ":version": current.version } } : {}
7362
- } });
7363
- const deleteChannelActions = (store, current) => [{ Delete: {
7364
- TableName: store.tableName,
7365
- Key: itemKey(DYNAMODB_CHANNEL_PARTITION, current.sk),
7366
- ConditionExpression: "#version = :version",
7367
- ExpressionAttributeNames: { "#version": "version" },
7368
- ExpressionAttributeValues: { ":version": current.version }
7369
- } }, { Delete: {
7370
- TableName: store.tableName,
7371
- Key: {
7372
- pk: DYNAMODB_CHANNEL_NAME_PARTITION,
7373
- sk: current.row.name
7374
- },
7375
- ConditionExpression: "#channelId = :channelId",
7376
- ExpressionAttributeNames: { "#channelId": "channel_id" },
7377
- ExpressionAttributeValues: { ":channelId": current.sk }
7378
- } }];
7379
- const updateChannelReferenceCount = (store, current, delta) => ({ Update: {
7380
- TableName: store.tableName,
7381
- Key: itemKey(DYNAMODB_CHANNEL_PARTITION, current.sk),
7382
- ConditionExpression: "#row.#name = :name" + (delta < 0 ? " AND #referenceCount >= :removal" : ""),
7383
- UpdateExpression: "SET #version = #version + :one ADD #referenceCount :delta",
7384
- ExpressionAttributeNames: {
7385
- "#row": "row",
7386
- "#name": "name",
7387
- "#version": "version",
7388
- "#referenceCount": "reference_count"
7389
- },
7390
- ExpressionAttributeValues: {
7391
- ":name": current.row.name,
7392
- ":one": 1,
7393
- ":delta": delta,
7394
- ...delta < 0 ? { ":removal": -delta } : {}
7395
- }
7396
- } });
7397
- const loadApiKeyByHash = async (store, hash) => {
7398
- const { Item } = await store.client.send(new _aws_sdk_lib_dynamodb.GetCommand({
7399
- TableName: store.tableName,
7400
- Key: {
7401
- pk: DYNAMODB_API_KEY_HASH_PARTITION,
7402
- sk: hash
6452
+ return keys.map((key) => found.get(idOf(key)) ?? null);
7403
6453
  },
7404
- ConsistentRead: true
7405
- }));
7406
- if (Item === void 0) return null;
7407
- const id = Item.api_key_id;
7408
- if (typeof id !== "string") throw new DynamoDBStoredItemError();
7409
- return loadApiKey(store, id);
7410
- };
7411
- const compileAndCommitDynamoDBChanges = async (store, input) => {
7412
- const [originalBundleItems, originalPatchItems, originalChannelItems, originalReleaseItems, originalReleaseCatalogItems] = await Promise.all([
7413
- loadBundleItems(store),
7414
- loadPatchItems(store),
7415
- loadChannelItems(store),
7416
- loadReleaseItems(store),
7417
- loadReleaseCatalogItems(store)
7418
- ]);
7419
- const bundles = new Map(originalBundleItems.map(({ sk, row }) => [sk, row]));
7420
- const patches = new Map(originalPatchItems.map(({ sk, row }) => [sk, row]));
7421
- const channels = new Map(originalChannelItems.map(({ sk, row }) => [sk, row]));
7422
- const channelNames = new Map(originalChannelItems.map(({ sk, row }) => [row.name, sk]));
7423
- const releases = new Map(originalReleaseItems.map(({ sk, row }) => [sk, row]));
7424
- const releaseCatalogs = new Map(originalReleaseCatalogItems.map(({ sk, row }) => [sk, row]));
7425
- const originalApiKeys = /* @__PURE__ */ new Map();
7426
- const apiKeys = /* @__PURE__ */ new Map();
7427
- const apiKeyHashes = /* @__PURE__ */ new Map();
7428
- const insights = /* @__PURE__ */ new Map();
7429
- const rememberApiKey = (value) => {
7430
- if (value === null || apiKeys.has(value.row.id)) return;
7431
- originalApiKeys.set(value.row.id, value);
7432
- apiKeys.set(value.row.id, value);
7433
- apiKeyHashes.set(value.row.hash, value.row.id);
7434
- };
7435
- const requireReleaseReferences = (row) => {
7436
- if (!channels.has(row.channel_id)) throw new DynamoDBCommitStateError(`Release "${row.id}" references an invalid channel`);
7437
- if (row.bundle_id !== null) {
7438
- const bundle = bundles.get(row.bundle_id);
7439
- if (bundle === void 0 || bundle.platform !== row.platform) throw new DynamoDBCommitStateError(`Release "${row.id}" references an invalid bundle`);
7440
- }
7441
- };
7442
- for (const expectation of input.expectations ?? []) {
7443
- const actualVersion = expectation.model === "releases" ? releases.get(expectation.id)?.revision ?? null : releaseCatalogs.get(expectation.scopeKey)?.generation ?? null;
7444
- const expectedVersion = expectation.model === "releases" ? expectation.revision : expectation.generation;
7445
- if (actualVersion !== expectedVersion) return {
7446
- committed: false,
7447
- conflict: {
7448
- actualVersion,
7449
- changeIndex: -1,
7450
- expectedVersion,
7451
- key: expectation.model === "releases" ? expectation.id : expectation.scopeKey,
7452
- model: expectation.model,
7453
- reason: "version_conflict"
7454
- }
7455
- };
7456
- }
7457
- for (const [changeIndex, change] of input.changes.entries()) switch (change.model) {
7458
- case "channels":
7459
- if (change.operation === "insert") {
7460
- if (channelNames.get(change.row.name) !== void 0) break;
7461
- if (channels.get(change.row.id) !== void 0) throw new DynamoDBCommitStateError(`Channel id "${change.row.id}" already exists`);
7462
- channels.set(change.row.id, change.row);
7463
- channelNames.set(change.row.name, change.row.id);
7464
- } else {
7465
- const current = channels.get(change.where.id);
7466
- if (current === void 0) break;
7467
- if ([...releases.values()].some(({ channel_id }) => channel_id === change.where.id)) return {
7468
- committed: false,
7469
- conflict: {
7470
- changeIndex,
7471
- reason: "referenced"
7472
- }
7473
- };
7474
- channels.delete(change.where.id);
7475
- channelNames.delete(current.name);
7476
- }
7477
- break;
7478
- case "bundles":
7479
- if (change.operation === "insert") {
7480
- if (bundles.has(change.row.id)) throw new DynamoDBCommitStateError(`Bundle "${change.row.id}" already exists`);
7481
- bundles.set(change.row.id, change.row);
7482
- } else {
7483
- const current = bundles.get(change.where.id);
7484
- if (current === void 0) {
7485
- if (change.operation === "update") return {
7486
- committed: false,
7487
- conflict: {
7488
- changeIndex,
7489
- reason: "not_found"
7490
- }
7491
- };
7492
- break;
7493
- }
7494
- if (change.operation === "update") {
7495
- const row = {
7496
- ...current,
7497
- ...change.update
7498
- };
7499
- bundles.set(row.id, row);
7500
- } else {
7501
- if ([...releases.values()].some(({ bundle_id }) => bundle_id === change.where.id)) return {
7502
- committed: false,
7503
- conflict: {
7504
- changeIndex,
7505
- reason: "referenced"
7506
- }
7507
- };
7508
- bundles.delete(change.where.id);
7509
- for (const [id, patch] of patches) if (patch.bundle_id === change.where.id || patch.base_bundle_id === change.where.id) patches.delete(id);
7510
- }
7511
- }
7512
- break;
7513
- case "releases":
7514
- if (change.operation === "insert") {
7515
- requireReleaseReferences(change.row);
7516
- if (releases.has(change.row.id)) throw new DynamoDBCommitStateError(`Release "${change.row.id}" already exists`);
7517
- releases.set(change.row.id, change.row);
7518
- } else {
7519
- const current = releases.get(change.where.id);
7520
- if (current === void 0) {
7521
- if (change.operation === "update") return {
7522
- committed: false,
7523
- conflict: {
7524
- changeIndex,
7525
- reason: "not_found"
7526
- }
7527
- };
7528
- break;
7529
- }
7530
- if (change.operation === "update") {
7531
- const row = {
7532
- ...current,
7533
- ...change.update
7534
- };
7535
- requireReleaseReferences(row);
7536
- releases.set(row.id, row);
7537
- } else releases.delete(change.where.id);
7538
- }
7539
- break;
7540
- case "releaseCatalogs":
7541
- if (!channels.has(change.row.channel_id)) throw new DynamoDBCommitStateError(`Release catalog "${change.row.scope_key}" references an invalid channel`);
7542
- releaseCatalogs.set(change.row.scope_key, change.row);
7543
- break;
7544
- case "bundlePatches":
7545
- if (change.operation === "insert") {
7546
- if (!bundles.has(change.row.bundle_id) || !bundles.has(change.row.base_bundle_id)) throw new DynamoDBCommitStateError(`Patch "${change.row.id}" references a missing bundle`);
7547
- if (patches.has(change.row.id)) throw new DynamoDBDuplicatePatchError(change.row.id);
7548
- patches.set(change.row.id, change.row);
7549
- } else for (const [id, patch] of patches) if (patch.bundle_id === change.where.bundleId) patches.delete(id);
7550
- break;
7551
- case "insights": {
7552
- const key = insightsSortKey(change.row);
7553
- if (insights.has(key)) throw new DynamoDBCommitStateError(`Insights event "${change.row.id}" is duplicated`);
7554
- insights.set(key, change.row);
7555
- break;
7556
- }
7557
- case "apiKeys":
7558
- if (change.operation === "insert") {
7559
- rememberApiKey(await loadApiKeyByHash(store, change.row.hash));
7560
- if (apiKeyHashes.has(change.row.hash)) break;
7561
- rememberApiKey(await loadApiKey(store, change.row.id));
7562
- if (apiKeys.has(change.row.id)) throw new DynamoDBCommitStateError(`API key id "${change.row.id}" already exists`);
7563
- const inserted = {
7564
- row: change.row,
7565
- version: 1
7566
- };
7567
- apiKeys.set(change.row.id, inserted);
7568
- apiKeyHashes.set(change.row.hash, change.row.id);
7569
- } else {
7570
- rememberApiKey(await loadApiKey(store, change.where.id));
7571
- const current = apiKeys.get(change.where.id);
7572
- if (current === void 0) return {
7573
- committed: false,
7574
- conflict: {
7575
- changeIndex,
7576
- reason: "not_found"
7577
- }
7578
- };
7579
- apiKeys.set(change.where.id, {
7580
- row: {
7581
- ...current.row,
7582
- revoked_at_ms: change.update.revokedAtMs
7583
- },
7584
- version: current.version
7585
- });
7586
- }
7587
- break;
7588
- }
7589
- const relationCounts = /* @__PURE__ */ new Map();
7590
- const ownedPatchCounts = /* @__PURE__ */ new Map();
7591
- for (const patch of patches.values()) {
7592
- for (const id of new Set([patch.bundle_id, patch.base_bundle_id])) relationCounts.set(id, (relationCounts.get(id) ?? 0) + 1);
7593
- ownedPatchCounts.set(patch.bundle_id, (ownedPatchCounts.get(patch.bundle_id) ?? 0) + 1);
7594
- }
7595
- const channelReferenceCounts = /* @__PURE__ */ new Map();
7596
- for (const release of releases.values()) channelReferenceCounts.set(release.channel_id, (channelReferenceCounts.get(release.channel_id) ?? 0) + 1);
7597
- const actions = [];
7598
- const originalChannels = new Map(originalChannelItems.map((item) => [item.sk, item]));
7599
- for (const original of originalChannelItems) if (!channels.has(original.sk)) actions.push(...deleteChannelActions(store, original));
7600
- for (const [id, row] of channels) {
7601
- const original = originalChannels.get(id);
7602
- const referenceCount = channelReferenceCounts.get(id) ?? 0;
7603
- if (original === void 0) {
7604
- actions.push(putChannel(store, row, original, referenceCount));
7605
- actions.push({ Put: {
7606
- TableName: store.tableName,
7607
- Item: {
7608
- pk: DYNAMODB_CHANNEL_NAME_PARTITION,
7609
- sk: row.name,
7610
- channel_id: id
7611
- },
7612
- ConditionExpression: "attribute_not_exists(#pk)",
7613
- ExpressionAttributeNames: { "#pk": "pk" }
7614
- } });
7615
- } else {
7616
- if (!rowsEqual(original.row, row)) throw new DynamoDBCommitStateError(`Channel "${id}" cannot be updated`);
7617
- const delta = referenceCount - original.reference_count;
7618
- if (delta !== 0) actions.push(updateChannelReferenceCount(store, original, delta));
7619
- }
7620
- }
7621
- const originalBundles = new Map(originalBundleItems.map((item) => [item.sk, item]));
7622
- for (const original of originalBundleItems) if (!bundles.has(original.sk)) actions.push(deleteAction(store, original));
7623
- for (const [id, row] of bundles) {
7624
- const original = originalBundles.get(id);
7625
- const relationCount = relationCounts.get(id) ?? 0;
7626
- const ownedPatchCount = ownedPatchCounts.get(id) ?? 0;
7627
- if (original === void 0) actions.push(putNewBundle(store, row, relationCount, ownedPatchCount));
7628
- else if (!rowsEqual(original.row, row)) actions.push(putUpdatedBundle(store, original, row, relationCount, ownedPatchCount));
7629
- else {
7630
- const relationDelta = relationCount - original.relation_count;
7631
- const ownedPatchDelta = ownedPatchCount - original.owned_patch_count;
7632
- if (relationDelta !== 0 || ownedPatchDelta !== 0) actions.push(updateBundleRelation(store, id, relationDelta, ownedPatchDelta));
7633
- }
7634
- }
7635
- const originalPatches = new Map(originalPatchItems.map((item) => [item.sk, item]));
7636
- for (const original of originalPatchItems) if (!patches.has(original.sk)) actions.push(deletePatch(store, original));
7637
- for (const [id, row] of patches) {
7638
- const original = originalPatches.get(id);
7639
- if (original === void 0 || !rowsEqual(original.row, row)) actions.push(putPatch(store, row, original));
7640
- }
7641
- const originalReleases = new Map(originalReleaseItems.map((item) => [item.sk, item]));
7642
- for (const original of originalReleaseItems) {
7643
- if (releases.get(original.sk) !== void 0) continue;
7644
- actions.push(deleteAction(store, original));
7645
- actions.push({ Delete: {
7646
- TableName: store.tableName,
7647
- Key: {
7648
- pk: DYNAMODB_RELEASE_ID_PARTITION,
7649
- sk: original.sk
7650
- },
7651
- ConditionExpression: "#scopeKey = :scopeKey",
7652
- ExpressionAttributeNames: { "#scopeKey": "scope_key" },
7653
- ExpressionAttributeValues: { ":scopeKey": original.row.scope_key }
7654
- } });
7655
- }
7656
- for (const [id, row] of releases) {
7657
- const original = originalReleases.get(id);
7658
- if (original === void 0) {
7659
- actions.push({ Put: {
7660
- TableName: store.tableName,
7661
- Item: boundedDynamoDBMetadataItem(toDynamoDBReleaseItem(row)),
7662
- ConditionExpression: "attribute_not_exists(#pk)",
7663
- ExpressionAttributeNames: { "#pk": "pk" }
7664
- } });
7665
- actions.push({ Put: {
7666
- TableName: store.tableName,
7667
- Item: releaseLocatorItem(row),
7668
- ConditionExpression: "attribute_not_exists(#pk)",
7669
- ExpressionAttributeNames: { "#pk": "pk" }
7670
- } });
7671
- } else if (!rowsEqual(original.row, row)) {
7672
- const moved = original.row.scope_key !== row.scope_key;
7673
- if (moved) actions.push(deleteAction(store, original));
7674
- actions.push({ Put: {
7675
- TableName: store.tableName,
7676
- Item: boundedDynamoDBMetadataItem(toDynamoDBReleaseItem(row, original.version + 1)),
7677
- ConditionExpression: moved ? "attribute_not_exists(#pk)" : "#version = :version",
7678
- ExpressionAttributeNames: moved ? { "#pk": "pk" } : { "#version": "version" },
7679
- ...moved ? {} : { ExpressionAttributeValues: { ":version": original.version } }
7680
- } });
7681
- if (moved) actions.push({ Put: {
7682
- TableName: store.tableName,
7683
- Item: releaseLocatorItem(row),
7684
- ConditionExpression: "#scopeKey = :scopeKey",
7685
- ExpressionAttributeNames: { "#scopeKey": "scope_key" },
7686
- ExpressionAttributeValues: { ":scopeKey": original.row.scope_key }
7687
- } });
7688
- }
7689
- }
7690
- const originalReleaseCatalogs = new Map(originalReleaseCatalogItems.map((item) => [item.sk, item]));
7691
- for (const [scopeKey, row] of releaseCatalogs) {
7692
- const original = originalReleaseCatalogs.get(scopeKey);
7693
- if (original === void 0 || !rowsEqual(original.row, row)) actions.push({ Put: {
7694
- TableName: store.tableName,
7695
- Item: boundedDynamoDBCatalogItem(toDynamoDBReleaseCatalogItem(row, (original?.version ?? 0) + 1)),
7696
- ConditionExpression: original ? "#version = :version" : "attribute_not_exists(#pk)",
7697
- ExpressionAttributeNames: original ? { "#version": "version" } : { "#pk": "pk" },
7698
- ...original ? { ExpressionAttributeValues: { ":version": original.version } } : {}
7699
- } });
7700
- }
7701
- const changedReleaseIds = new Set([...new Set([...originalReleases.keys(), ...releases.keys()])].filter((id) => {
7702
- const original = originalReleases.get(id)?.row;
7703
- const next = releases.get(id);
7704
- return original === void 0 || next === void 0 || !rowsEqual(original, next);
7705
- }));
7706
- const changedCatalogScopes = new Set([...releaseCatalogs].flatMap(([scopeKey, row]) => {
7707
- const original = originalReleaseCatalogs.get(scopeKey)?.row;
7708
- return original === void 0 || !rowsEqual(original, row) ? [scopeKey] : [];
7709
- }));
7710
- for (const expectation of input.expectations ?? []) {
7711
- if (expectation.model === "releases" && !changedReleaseIds.has(expectation.id)) {
7712
- const original = originalReleases.get(expectation.id);
7713
- actions.push({ ConditionCheck: {
7714
- TableName: store.tableName,
7715
- Key: original === void 0 ? {
7716
- pk: DYNAMODB_RELEASE_ID_PARTITION,
7717
- sk: expectation.id
7718
- } : {
7719
- pk: original.pk,
7720
- sk: original.sk
7721
- },
7722
- ConditionExpression: original === void 0 ? "attribute_not_exists(#pk)" : "#version = :version",
7723
- ExpressionAttributeNames: original === void 0 ? { "#pk": "pk" } : { "#version": "version" },
7724
- ...original === void 0 ? {} : { ExpressionAttributeValues: { ":version": original.version } }
7725
- } });
7726
- }
7727
- if (expectation.model === "releaseCatalogs" && !changedCatalogScopes.has(expectation.scopeKey)) {
7728
- const original = originalReleaseCatalogs.get(expectation.scopeKey);
7729
- actions.push({ ConditionCheck: {
7730
- TableName: store.tableName,
7731
- Key: itemKey("release_catalogs", expectation.scopeKey),
7732
- ConditionExpression: original === void 0 ? "attribute_not_exists(#pk)" : "#version = :version",
7733
- ExpressionAttributeNames: original === void 0 ? { "#pk": "pk" } : { "#version": "version" },
7734
- ...original === void 0 ? {} : { ExpressionAttributeValues: { ":version": original.version } }
7735
- } });
7736
- }
7737
- }
7738
- const counter = metadataUpdate(store, {
7739
- bundles: bundles.size - originalBundles.size,
7740
- bundle_patches: patches.size - originalPatches.size
7741
- });
7742
- if (counter !== void 0) actions.push(counter);
7743
- for (const [key, row] of insights) actions.push({ Put: {
7744
- TableName: store.tableName,
7745
- Item: boundedDynamoDBMetadataItem({
7746
- pk: DYNAMODB_INSIGHTS_PARTITION,
7747
- sk: key,
7748
- version: 1,
7749
- row
7750
- }),
7751
- ConditionExpression: "attribute_not_exists(#pk)",
7752
- ExpressionAttributeNames: { "#pk": "pk" }
7753
- } });
7754
- for (const [id, current] of apiKeys) {
7755
- const original = originalApiKeys.get(id);
7756
- if (original !== void 0 && rowsEqual(original.row, current.row)) continue;
7757
- actions.push({ Put: {
7758
- TableName: store.tableName,
7759
- Item: boundedDynamoDBMetadataItem(apiKeyItem(current.row, original === void 0 ? 1 : original.version + 1)),
7760
- ConditionExpression: original ? "#version = :version" : "attribute_not_exists(#pk)",
7761
- ExpressionAttributeNames: original ? { "#version": "version" } : { "#pk": "pk" },
7762
- ...original ? { ExpressionAttributeValues: { ":version": original.version } } : {}
7763
- } });
7764
- if (original === void 0) actions.push({ Put: {
7765
- TableName: store.tableName,
7766
- Item: {
7767
- pk: DYNAMODB_API_KEY_HASH_PARTITION,
7768
- sk: current.row.hash,
7769
- api_key_id: current.row.id
7770
- },
7771
- ConditionExpression: "attribute_not_exists(#pk)",
7772
- ExpressionAttributeNames: { "#pk": "pk" }
7773
- } });
7774
- }
7775
- if (actions.length > 0) await commitDynamoDBTransaction(store, actions);
7776
- return { committed: true };
7777
- };
7778
- const isDynamoDBTransactionConflict = (error) => typeof error === "object" && error !== null && Reflect.get(error, "name") === "TransactionCanceledException";
7779
- const createDynamoDBCommit = (store) => async (input) => {
7780
- try {
7781
- return await compileAndCommitDynamoDBChanges(store, input);
7782
- } catch (error) {
7783
- if (!isDynamoDBTransactionConflict(error)) throw error;
7784
- return compileAndCommitDynamoDBChanges(store, input);
7785
- }
7786
- };
7787
- const DYNAMODB_INSIGHTS_PARTITION = "bundle_events";
7788
- const DYNAMODB_API_KEY_PARTITION = "api_keys";
7789
- const DYNAMODB_API_KEY_HASH_PARTITION = "_hot-updater#api-key-hashes";
7790
- const hasValidBundleEventShape = (value) => {
7791
- const type = field(value, "type");
7792
- const fromBundleId = field(value, "from_bundle_id");
7793
- const updateStrategy = field(value, "update_strategy");
7794
- return (type === "UPDATE_APPLIED" || type === "RECOVERED" || type === "RELEASE_ADOPTED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null;
7795
- };
7796
- const isBundleEventRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "from_release_id")) && isNullableString(field(value, "to_release_id")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "received_at_ms") === "number" && hasValidBundleEventShape(value);
7797
- const isApiKeyRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "hash") === "string" && typeof field(value, "name") === "string" && typeof field(value, "prefix") === "string" && field(value, "role") === "client" && typeof field(value, "created_at_ms") === "number" && (field(value, "revoked_at_ms") === null || typeof field(value, "revoked_at_ms") === "number");
7798
- const parseOfficialRowItem = (value, partition, isRow) => {
7799
- if (value.pk !== partition || typeof value.sk !== "string" || typeof value.version !== "number" || !isRow(value.row)) throw new DynamoDBStoredItemError();
7800
- return {
7801
- row: value.row,
7802
- version: value.version
7803
- };
7804
- };
7805
- const timestampSortKey = (timestampMs) => Math.trunc(timestampMs).toString().padStart(16, "0");
7806
- const insightsSortKey = (row) => `${timestampSortKey(row.received_at_ms)}#${row.id}`;
7807
- const createDynamoDBInsightsTable = (store) => ({
7808
- async append(row) {
7809
- await store.client.send(new _aws_sdk_lib_dynamodb.PutCommand({
7810
- TableName: store.tableName,
7811
- Item: boundedDynamoDBMetadataItem({
7812
- pk: DYNAMODB_INSIGHTS_PARTITION,
7813
- sk: insightsSortKey(row),
7814
- version: 1,
7815
- row
7816
- }),
7817
- ConditionExpression: "attribute_not_exists(#pk)",
7818
- ExpressionAttributeNames: { "#pk": "pk" }
7819
- }));
7820
- },
7821
- async scan(input) {
7822
- if (input.limit <= 0) return [];
7823
- if (input.after !== void 0 && input.after.receivedAtMs >= input.beforeReceivedAtMs) return [];
7824
- const rows = [];
7825
- const afterKey = input.after === void 0 ? void 0 : `${timestampSortKey(input.after.receivedAtMs)}#${input.after.id}`;
7826
- let exclusiveStartKey;
7827
- do {
7828
- const page = await store.client.send(new _aws_sdk_lib_dynamodb.QueryCommand({
7829
- TableName: store.tableName,
7830
- ExclusiveStartKey: exclusiveStartKey,
7831
- KeyConditionExpression: afterKey === void 0 ? "#pk = :pk AND #sk < :before" : "#pk = :pk AND #sk BETWEEN :after AND :before",
7832
- ExpressionAttributeNames: {
7833
- "#pk": "pk",
7834
- "#sk": "sk"
7835
- },
6454
+ async query({ pk, gte, lt, lte, order, limit, after }) {
6455
+ let condition = "pk = :pk";
6456
+ if (gte !== void 0 && lt !== void 0) condition += " AND sk BETWEEN :gte AND :lte";
6457
+ else if (gte !== void 0) condition += " AND sk >= :gte";
6458
+ else if (lt !== void 0) condition += " AND sk < :lt";
6459
+ const result = await documents.send(new _aws_sdk_lib_dynamodb.QueryCommand({
6460
+ TableName: tableName,
6461
+ KeyConditionExpression: condition,
7836
6462
  ExpressionAttributeValues: {
7837
- ":before": `${timestampSortKey(input.beforeReceivedAtMs)}#`,
7838
- ":pk": DYNAMODB_INSIGHTS_PARTITION,
7839
- ...afterKey === void 0 ? {} : { ":after": afterKey }
6463
+ ":pk": pk,
6464
+ ":gte": gte,
6465
+ ...gte !== void 0 && lt !== void 0 ? { ":lte": lte ?? lt } : { ":lt": lt }
7840
6466
  },
7841
- Limit: input.limit - rows.length + (afterKey === void 0 ? 0 : 1),
7842
- ScanIndexForward: true
7843
- }));
7844
- for (const item of page.Items ?? []) {
7845
- if (item.sk === afterKey) continue;
7846
- rows.push(parseOfficialRowItem(item, DYNAMODB_INSIGHTS_PARTITION, isBundleEventRow).row);
7847
- if (rows.length === input.limit) return rows;
7848
- }
7849
- exclusiveStartKey = page.LastEvaluatedKey;
7850
- } while (exclusiveStartKey !== void 0);
7851
- return rows;
7852
- }
7853
- });
7854
- const apiKeyItem = (row, version) => ({
7855
- pk: DYNAMODB_API_KEY_PARTITION,
7856
- sk: row.id,
7857
- version,
7858
- row
7859
- });
7860
- const loadApiKey = async (store, id) => {
7861
- const result = await store.client.send(new _aws_sdk_lib_dynamodb.GetCommand({
7862
- TableName: store.tableName,
7863
- Key: {
7864
- pk: DYNAMODB_API_KEY_PARTITION,
7865
- sk: id
6467
+ ScanIndexForward: order === "asc",
6468
+ ConsistentRead: true,
6469
+ Limit: Math.min(limit, nativePageSize ?? limit),
6470
+ ExclusiveStartKey: after === void 0 ? void 0 : {
6471
+ pk,
6472
+ sk: after
6473
+ }
6474
+ })).catch(missingTable);
6475
+ return {
6476
+ items: (result.Items ?? []).map((item) => ({
6477
+ sk: item.sk,
6478
+ value: toRow(item)
6479
+ })),
6480
+ more: result.LastEvaluatedKey !== void 0
6481
+ };
7866
6482
  },
7867
- ConsistentRead: true
7868
- }));
7869
- return result.Item === void 0 ? null : parseOfficialRowItem(result.Item, DYNAMODB_API_KEY_PARTITION, isApiKeyRow);
7870
- };
7871
- const createDynamoDBApiKeyTable = (store) => {
7872
- const findByHash = async (hash) => {
7873
- const lookup = await store.client.send(new _aws_sdk_lib_dynamodb.GetCommand({
7874
- TableName: store.tableName,
7875
- Key: {
7876
- pk: DYNAMODB_API_KEY_HASH_PARTITION,
7877
- sk: hash
7878
- },
7879
- ConsistentRead: true
7880
- }));
7881
- const id = lookup.Item?.api_key_id;
7882
- if (lookup.Item === void 0) return null;
7883
- if (typeof id !== "string") throw new DynamoDBStoredItemError();
7884
- return (await loadApiKey(store, id))?.row ?? null;
7885
- };
7886
- return {
7887
- async create(row) {
7888
- if (await findByHash(row.hash) !== null) return "existing";
6483
+ async write(ops) {
7889
6484
  try {
7890
- await commitDynamoDBTransaction(store, [{ Put: {
7891
- TableName: store.tableName,
7892
- Item: boundedDynamoDBMetadataItem(apiKeyItem(row, 1)),
7893
- ConditionExpression: "attribute_not_exists(#pk)",
7894
- ExpressionAttributeNames: { "#pk": "pk" }
7895
- } }, { Put: {
7896
- TableName: store.tableName,
7897
- Item: {
7898
- pk: DYNAMODB_API_KEY_HASH_PARTITION,
7899
- sk: row.hash,
7900
- api_key_id: row.id
7901
- },
7902
- ConditionExpression: "attribute_not_exists(#pk)",
7903
- ExpressionAttributeNames: { "#pk": "pk" }
7904
- } }]);
7905
- return "created";
6485
+ await documents.send(new _aws_sdk_lib_dynamodb.TransactWriteCommand({
6486
+ TransactItems: ops.map((op) => toTransactItem(tableName, op)),
6487
+ ClientRequestToken: crypto.randomUUID()
6488
+ }));
6489
+ return { ok: true };
7906
6490
  } catch (error) {
7907
- if (await findByHash(row.hash) !== null) return "existing";
7908
- throw error;
6491
+ const { CancellationReasons = [] } = error;
6492
+ const codes = CancellationReasons.map(({ Code }) => Code);
6493
+ const failed = codes.indexOf("ConditionalCheckFailed");
6494
+ if (failed !== -1) return {
6495
+ ok: false,
6496
+ failedOp: failed
6497
+ };
6498
+ return [nameOf(error), ...codes].some((code) => TRANSIENT.has(code)) ? {
6499
+ ok: false,
6500
+ retry: true
6501
+ } : missingTable(error);
7909
6502
  }
7910
6503
  },
7911
- findByHash,
7912
- async list() {
7913
- const rows = [];
7914
- let exclusiveStartKey;
7915
- do {
7916
- const page = await store.client.send(new _aws_sdk_lib_dynamodb.QueryCommand({
7917
- TableName: store.tableName,
7918
- ExclusiveStartKey: exclusiveStartKey,
7919
- KeyConditionExpression: "#pk = :pk",
7920
- ExpressionAttributeNames: { "#pk": "pk" },
7921
- ExpressionAttributeValues: { ":pk": DYNAMODB_API_KEY_PARTITION },
7922
- ConsistentRead: true
7923
- }));
7924
- rows.push(...(page.Items ?? []).map((item) => parseOfficialRowItem(item, DYNAMODB_API_KEY_PARTITION, isApiKeyRow).row));
7925
- exclusiveStartKey = page.LastEvaluatedKey;
7926
- } while (exclusiveStartKey !== void 0);
7927
- return rows.sort((left, right) => right.created_at_ms - left.created_at_ms || left.id.localeCompare(right.id));
7928
- },
7929
- async revoke({ id, revokedAtMs }) {
7930
- const current = await loadApiKey(store, id);
7931
- if (current === null) return null;
7932
- const row = {
7933
- ...current.row,
7934
- revoked_at_ms: revokedAtMs
7935
- };
7936
- await store.client.send(new _aws_sdk_lib_dynamodb.PutCommand({
7937
- TableName: store.tableName,
7938
- Item: boundedDynamoDBMetadataItem(apiKeyItem(row, current.version + 1)),
7939
- ConditionExpression: "#version = :currentVersion",
7940
- ExpressionAttributeNames: { "#version": "version" },
7941
- ExpressionAttributeValues: { ":currentVersion": current.version }
6504
+ migrations: {
6505
+ /** Creates the table for local and test runs; `hot-updater init` creates it in AWS. */
6506
+ async apply() {
6507
+ try {
6508
+ await client.send(new _aws_sdk_client_dynamodb.DescribeTableCommand({ TableName: tableName }));
6509
+ return;
6510
+ } catch (error) {
6511
+ if (nameOf(error) !== "ResourceNotFoundException") throw error;
6512
+ }
6513
+ await client.send(new _aws_sdk_client_dynamodb.CreateTableCommand({
6514
+ TableName: tableName,
6515
+ AttributeDefinitions: [{
6516
+ AttributeName: "pk",
6517
+ AttributeType: "S"
6518
+ }, {
6519
+ AttributeName: "sk",
6520
+ AttributeType: "S"
6521
+ }],
6522
+ KeySchema: [{
6523
+ AttributeName: "pk",
6524
+ KeyType: "HASH"
6525
+ }, {
6526
+ AttributeName: "sk",
6527
+ KeyType: "RANGE"
6528
+ }],
6529
+ BillingMode: "PAY_PER_REQUEST"
7942
6530
  }));
7943
- return row;
7944
- }
6531
+ await (0, _aws_sdk_client_dynamodb.waitUntilTableExists)({
6532
+ client,
6533
+ maxWaitTime: 120,
6534
+ minDelay: 1,
6535
+ maxDelay: 1
6536
+ }, { TableName: tableName });
6537
+ } }
7945
6538
  };
7946
6539
  };
7947
- const DYNAMODB_UPDATE_INDEX_NAME = "hot-updater-update-index";
7948
- const dynamoDB = (config) => {
7949
- const { apiBasePath = "/release-catalogs", cloudfrontDistributionId, shouldWaitForInvalidation = false, tableName, ...clientConfig } = config;
7950
- const client = _aws_sdk_lib_dynamodb.DynamoDBDocumentClient.from(new _aws_sdk_client_dynamodb.DynamoDBClient(clientConfig), { marshallOptions: { removeUndefinedValues: true } });
7951
- const cloudFront = cloudfrontDistributionId ? new _aws_sdk_client_cloudfront.CloudFrontClient({
7952
- credentials: clientConfig.credentials,
7953
- region: clientConfig.region
7954
- }) : null;
7955
- const store = {
6540
+ //#endregion
6541
+ //#region src/dynamoDB.ts
6542
+ /** The DynamoDB client ignores the CloudFront settings in its config. */
6543
+ const adapterOf = ({ tableName, ...clientConfig }) => {
6544
+ const client = new _aws_sdk_client_dynamodb.DynamoDBClient(clientConfig);
6545
+ return {
7956
6546
  client,
7957
- tableName
7958
- };
7959
- const crud = createDynamoDBCrud(store, DYNAMODB_UPDATE_INDEX_NAME);
7960
- const invalidateUpdateRoutes = async () => {
7961
- if (!cloudFront || !cloudfrontDistributionId) return;
7962
- try {
7963
- await invalidateCloudFront(cloudFront, cloudfrontDistributionId, [`${apiBasePath.replace(/\/+$/, "")}/*`], { shouldWait: shouldWaitForInvalidation });
7964
- } catch (error) {
7965
- console.warn("[hot-updater/aws] CloudFront invalidation failed; continuing without cache invalidation.", {
7966
- distributionId: cloudfrontDistributionId,
7967
- error: error instanceof Error ? error.message : "Unknown error"
7968
- });
7969
- }
6547
+ adapter: (0, _hot_updater_server_database.createKvAdapter)({ store: createDynamoDBStore({
6548
+ client,
6549
+ tableName
6550
+ }) })
7970
6551
  };
7971
- const adapter = (0, _hot_updater_plugin_core_internal.createDatabasePluginAdapter)("dynamoDB", {
7972
- ...crud,
7973
- commit: createDynamoDBCommit(store),
7974
- dispose: async () => {
6552
+ };
6553
+ /**
6554
+ * Creates the table when it is missing and writes the schema settings the
6555
+ * database checks before its first read. `hot-updater init` runs it in AWS.
6556
+ */
6557
+ const migrateDynamoDB = async (config) => {
6558
+ const { client, adapter } = adapterOf(config);
6559
+ try {
6560
+ await (0, _hot_updater_server_database.migrateBuiltInSchema)(adapter, "dynamoDB");
6561
+ } finally {
6562
+ client.destroy();
6563
+ }
6564
+ };
6565
+ /**
6566
+ * Hot Updater's database on one DynamoDB table, through the storage engine.
6567
+ * With a distribution, it gives core the CloudFront invalidation core runs
6568
+ * after a write that changes what the update-check routes answer.
6569
+ */
6570
+ const dynamoDB = (config) => {
6571
+ const { client, adapter } = adapterOf(config);
6572
+ const cloudFront = createUpdateRouteInvalidation(config);
6573
+ return {
6574
+ ...(0, _hot_updater_server_database.createEngineDatabase)({
6575
+ name: "dynamoDB",
6576
+ adapter,
6577
+ onCachedRoutesChange: cloudFront?.invalidate
6578
+ }),
6579
+ async dispose() {
6580
+ await adapter.dispose?.();
7975
6581
  client.destroy();
7976
6582
  cloudFront?.destroy();
7977
6583
  }
7978
- });
7979
- return (0, _hot_updater_plugin_core.createDatabasePlugin)({
7980
- name: "dynamoDB",
7981
- models: {
7982
- ...adapter.models,
7983
- bundlePatches: createDynamoDBBundlePatchTable(store, DYNAMODB_UPDATE_INDEX_NAME),
7984
- insights: createDynamoDBInsightsTable(store),
7985
- apiKeys: createDynamoDBApiKeyTable(store)
7986
- },
7987
- async commit(input) {
7988
- const result = await adapter.commit(input);
7989
- if (result.committed && input.changes.some(({ model }) => model === "bundles" || model === "bundlePatches")) await invalidateUpdateRoutes();
7990
- return result;
7991
- },
7992
- dispose: adapter.dispose
7993
- });
6584
+ };
7994
6585
  };
7995
6586
  //#endregion
6587
+ //#region src/plugins.ts
6588
+ /**
6589
+ * The plugins the managed AWS server runs: Insights, and API keys on every
6590
+ * client route. Its Lambda@Edge function imports this list.
6591
+ */
6592
+ const plugins = [(0, _hot_updater_server_plugins_insights.insights)(), (0, _hot_updater_server_plugins_api_keys.apiKeys)()];
6593
+ //#endregion
7996
6594
  //#region iac/init/index.ts
7997
6595
  const AWS_AUTH_MODES = [
7998
6596
  "local-session",
@@ -8881,7 +7479,7 @@ var CloudFrontManager = class {
8881
7479
  return policyId;
8882
7480
  }
8883
7481
  async getOrCreateKeyGroup(publicKey) {
8884
- const publicKeyHash = crypto.default.createHash("sha256").update(publicKey).digest("hex").slice(0, 16);
7482
+ const publicKeyHash = crypto$1.default.createHash("sha256").update(publicKey).digest("hex").slice(0, 16);
8885
7483
  const cloudfrontClient = new _aws_sdk_client_cloudfront.CloudFront({
8886
7484
  region: this.region,
8887
7485
  credentials: this.credentials
@@ -9106,13 +7704,16 @@ var CloudFrontManager = class {
9106
7704
  //#region iac/dynamodb.ts
9107
7705
  const DYNAMODB_DESCRIBE_TABLE_ACTION = "dynamodb:DescribeTable";
9108
7706
  var DynamoDBTableSchemaError = class extends Error {
7707
+ tableName;
9109
7708
  name = "DynamoDBTableSchemaError";
9110
7709
  constructor(tableName) {
9111
- super(`DynamoDB table "${tableName}" does not match the Hot Updater key and index schema`);
7710
+ super(`DynamoDB table "${tableName}" does not match the Hot Updater schema: a pk and sk key and no secondary index. A table from before 1.0 keeps its update index; delete it and rerun init to create it again.`);
9112
7711
  this.tableName = tableName;
9113
7712
  }
9114
7713
  };
9115
7714
  var DynamoDBPermissionError = class extends _hot_updater_cli_tools.InitError {
7715
+ tableName;
7716
+ region;
9116
7717
  name = "DynamoDBPermissionError";
9117
7718
  requiredAction = DYNAMODB_DESCRIBE_TABLE_ACTION;
9118
7719
  constructor(tableName, region, cause) {
@@ -9135,30 +7736,47 @@ const primaryKeySchema = [{
9135
7736
  AttributeName: "sk",
9136
7737
  KeyType: "RANGE"
9137
7738
  }];
9138
- const updateIndexKeySchema = [{
9139
- AttributeName: "gsi1pk",
9140
- KeyType: "HASH"
9141
- }, {
9142
- AttributeName: "gsi1sk",
9143
- KeyType: "RANGE"
9144
- }];
9145
- const keyAttributes = [
9146
- "pk",
9147
- "sk",
9148
- "gsi1pk",
9149
- "gsi1sk"
9150
- ];
7739
+ const keyAttributes = ["pk", "sk"];
9151
7740
  const onDemandThroughput = {
9152
7741
  MaxReadRequestUnits: 4e3,
9153
7742
  MaxWriteRequestUnits: 100
9154
7743
  };
9155
7744
  const hasKeySchema = (actual, expected) => actual?.length === expected.length && expected.every(({ AttributeName, KeyType }) => actual.some((key) => key.AttributeName === AttributeName && key.KeyType === KeyType));
9156
7745
  const hasExpectedSchema = (table) => {
9157
- const updateIndex = table?.GlobalSecondaryIndexes?.find(({ IndexName }) => IndexName === DYNAMODB_UPDATE_INDEX_NAME);
9158
- return keyAttributes.every((attributeName) => table?.AttributeDefinitions?.some(({ AttributeName, AttributeType }) => AttributeName === attributeName && AttributeType === "S")) && table?.BillingModeSummary?.BillingMode === "PAY_PER_REQUEST" && table.OnDemandThroughput?.MaxReadRequestUnits === onDemandThroughput.MaxReadRequestUnits && table.OnDemandThroughput?.MaxWriteRequestUnits === onDemandThroughput.MaxWriteRequestUnits && hasKeySchema(table?.KeySchema, primaryKeySchema) && hasKeySchema(updateIndex?.KeySchema, updateIndexKeySchema) && updateIndex?.OnDemandThroughput?.MaxReadRequestUnits === onDemandThroughput.MaxReadRequestUnits && updateIndex?.OnDemandThroughput?.MaxWriteRequestUnits === onDemandThroughput.MaxWriteRequestUnits && updateIndex?.Projection?.ProjectionType === "ALL";
7746
+ return keyAttributes.every((attributeName) => table?.AttributeDefinitions?.some(({ AttributeName, AttributeType }) => AttributeName === attributeName && AttributeType === "S")) && table?.BillingModeSummary?.BillingMode === "PAY_PER_REQUEST" && table.OnDemandThroughput?.MaxReadRequestUnits === onDemandThroughput.MaxReadRequestUnits && table.OnDemandThroughput?.MaxWriteRequestUnits === onDemandThroughput.MaxWriteRequestUnits && hasKeySchema(table?.KeySchema, primaryKeySchema) && (table?.GlobalSecondaryIndexes ?? []).length === 0;
9159
7747
  };
9160
7748
  const isResourceNotFound = (error) => typeof error === "object" && error !== null && Reflect.get(error, "name") === "ResourceNotFoundException";
7749
+ const buildDynamoDBCreateTableInput = (tableName) => ({
7750
+ AttributeDefinitions: [{
7751
+ AttributeName: "pk",
7752
+ AttributeType: "S"
7753
+ }, {
7754
+ AttributeName: "sk",
7755
+ AttributeType: "S"
7756
+ }],
7757
+ BillingMode: "PAY_PER_REQUEST",
7758
+ DeletionProtectionEnabled: true,
7759
+ KeySchema: [...primaryKeySchema],
7760
+ OnDemandThroughput: onDemandThroughput,
7761
+ TableName: tableName
7762
+ });
7763
+ /**
7764
+ * The schema settings items the plugin checks before its first read, as one
7765
+ * BatchWriteItem request: what `migrateDynamoDB` writes to a new table.
7766
+ */
7767
+ const buildDynamoDBSchemaSettingsInput = (tableName) => ({ RequestItems: { [tableName]: Object.entries(_hot_updater_server_database.builtInSettings).map(([key, value]) => ({ PutRequest: { Item: {
7768
+ pk: { S: _hot_updater_server_database.SETTINGS_TABLE.name },
7769
+ sk: { S: (0, _hot_updater_server_database.encodeKvKey)([key]) },
7770
+ key: { S: key },
7771
+ value: { S: value },
7772
+ _v: { N: "0" }
7773
+ } } })) } });
7774
+ const buildDynamoDBBackupInput = (tableName) => ({
7775
+ PointInTimeRecoverySpecification: { PointInTimeRecoveryEnabled: true },
7776
+ TableName: tableName
7777
+ });
9161
7778
  var DynamoDBManager = class {
7779
+ region;
9162
7780
  client;
9163
7781
  constructor(region, credentials) {
9164
7782
  this.region = region;
@@ -9177,37 +7795,7 @@ var DynamoDBManager = class {
9177
7795
  if (error instanceof Error && error.name === "AccessDeniedException" && error.message.includes(DYNAMODB_DESCRIBE_TABLE_ACTION)) throw new DynamoDBPermissionError(tableName, this.region, error);
9178
7796
  if (!isResourceNotFound(error)) throw error;
9179
7797
  }
9180
- await this.client.createTable({
9181
- AttributeDefinitions: [
9182
- {
9183
- AttributeName: "pk",
9184
- AttributeType: "S"
9185
- },
9186
- {
9187
- AttributeName: "sk",
9188
- AttributeType: "S"
9189
- },
9190
- {
9191
- AttributeName: "gsi1pk",
9192
- AttributeType: "S"
9193
- },
9194
- {
9195
- AttributeName: "gsi1sk",
9196
- AttributeType: "S"
9197
- }
9198
- ],
9199
- BillingMode: "PAY_PER_REQUEST",
9200
- DeletionProtectionEnabled: true,
9201
- GlobalSecondaryIndexes: [{
9202
- IndexName: DYNAMODB_UPDATE_INDEX_NAME,
9203
- KeySchema: [...updateIndexKeySchema],
9204
- Projection: { ProjectionType: "ALL" },
9205
- OnDemandThroughput: onDemandThroughput
9206
- }],
9207
- KeySchema: [...primaryKeySchema],
9208
- OnDemandThroughput: onDemandThroughput,
9209
- TableName: tableName
9210
- });
7798
+ await this.client.createTable(buildDynamoDBCreateTableInput(tableName));
9211
7799
  await (0, _aws_sdk_client_dynamodb.waitUntilTableExists)({
9212
7800
  client: this.client,
9213
7801
  maxWaitTime: 120
@@ -9217,14 +7805,66 @@ var DynamoDBManager = class {
9217
7805
  async ensureLifecycle(tableName) {
9218
7806
  const { ContinuousBackupsDescription } = await this.client.describeContinuousBackups({ TableName: tableName });
9219
7807
  if (ContinuousBackupsDescription?.PointInTimeRecoveryDescription?.PointInTimeRecoveryStatus === "ENABLED") return;
9220
- await this.client.updateContinuousBackups({
9221
- PointInTimeRecoverySpecification: { PointInTimeRecoveryEnabled: true },
9222
- TableName: tableName
9223
- });
7808
+ await this.client.updateContinuousBackups(buildDynamoDBBackupInput(tableName));
9224
7809
  }
9225
7810
  };
9226
7811
  //#endregion
9227
7812
  //#region iac/iam.ts
7813
+ /** The partitions the plugin's items use: each table's rows, and its index items after `#`. */
7814
+ const dynamoDBLeadingKeys = () => [..._hot_updater_server_database.builtInSchema.tables, _hot_updater_server_database.SETTINGS_TABLE].flatMap(({ name }) => [name, `${name}#*`]);
7815
+ const buildDynamoDBPolicy = (region, accountId, tableName) => {
7816
+ const tableArn = `arn:aws:dynamodb:${region}:${accountId}:table/${tableName}`;
7817
+ return {
7818
+ Version: "2012-10-17",
7819
+ Statement: [{
7820
+ Action: [
7821
+ "dynamodb:BatchGetItem",
7822
+ "dynamodb:ConditionCheckItem",
7823
+ "dynamodb:DeleteItem",
7824
+ "dynamodb:GetItem",
7825
+ "dynamodb:PutItem",
7826
+ "dynamodb:Query",
7827
+ "dynamodb:TransactWriteItems",
7828
+ "dynamodb:UpdateItem"
7829
+ ],
7830
+ Condition: { "ForAllValues:StringLike": { "dynamodb:LeadingKeys": dynamoDBLeadingKeys() } },
7831
+ Effect: "Allow",
7832
+ Resource: [tableArn]
7833
+ }]
7834
+ };
7835
+ };
7836
+ const buildS3Policy = (bucketName) => {
7837
+ return {
7838
+ Version: "2012-10-17",
7839
+ Statement: [{
7840
+ Action: ["s3:ListBucket"],
7841
+ Effect: "Allow",
7842
+ Resource: [`arn:aws:s3:::${bucketName}`]
7843
+ }, {
7844
+ Action: ["s3:GetObject"],
7845
+ Effect: "Allow",
7846
+ Resource: [`arn:aws:s3:::${bucketName}/*`]
7847
+ }]
7848
+ };
7849
+ };
7850
+ const buildSsmPolicy = (region, accountId, parameterName) => {
7851
+ return {
7852
+ Version: "2012-10-17",
7853
+ Statement: [{
7854
+ Effect: "Allow",
7855
+ Action: ["ssm:GetParameter"],
7856
+ Resource: `arn:aws:ssm:${region}:${accountId}:parameter/${parameterName.replace(/^\/+/, "")}`
7857
+ }]
7858
+ };
7859
+ };
7860
+ const LAMBDA_EDGE_TRUST_POLICY = {
7861
+ Version: "2012-10-17",
7862
+ Statement: [{
7863
+ Effect: "Allow",
7864
+ Principal: { Service: ["lambda.amazonaws.com", "edgelambda.amazonaws.com"] },
7865
+ Action: "sts:AssumeRole"
7866
+ }]
7867
+ };
9228
7868
  var IAMManager = class {
9229
7869
  region;
9230
7870
  credentials;
@@ -9241,71 +7881,22 @@ var IAMManager = class {
9241
7881
  });
9242
7882
  }
9243
7883
  async ensureDynamoDBPolicy(iamClient, roleName, accountId, tableName) {
9244
- const tableArn = `arn:aws:dynamodb:${this.region}:${accountId}:table/${tableName}`;
9245
7884
  await iamClient.putRolePolicy({
9246
- PolicyDocument: JSON.stringify({
9247
- Version: "2012-10-17",
9248
- Statement: [{
9249
- Action: ["dynamodb:Query"],
9250
- Effect: "Allow",
9251
- Resource: [`${tableArn}/index/${DYNAMODB_UPDATE_INDEX_NAME}`]
9252
- }, {
9253
- Action: [
9254
- "dynamodb:BatchGetItem",
9255
- "dynamodb:DeleteItem",
9256
- "dynamodb:GetItem",
9257
- "dynamodb:PutItem",
9258
- "dynamodb:Query",
9259
- "dynamodb:TransactWriteItems",
9260
- "dynamodb:UpdateItem"
9261
- ],
9262
- Condition: { "ForAllValues:StringEquals": { "dynamodb:LeadingKeys": [
9263
- "_hot-updater",
9264
- "bundles",
9265
- "bundle_patches",
9266
- DYNAMODB_CHANNEL_PARTITION,
9267
- DYNAMODB_CHANNEL_NAME_PARTITION,
9268
- DYNAMODB_INSIGHTS_PARTITION,
9269
- DYNAMODB_API_KEY_PARTITION,
9270
- DYNAMODB_API_KEY_HASH_PARTITION
9271
- ] } },
9272
- Effect: "Allow",
9273
- Resource: [tableArn]
9274
- }]
9275
- }),
7885
+ PolicyDocument: JSON.stringify(buildDynamoDBPolicy(this.region, accountId, tableName)),
9276
7886
  PolicyName: "HotUpdaterDynamoDBReadAccess",
9277
7887
  RoleName: roleName
9278
7888
  });
9279
7889
  }
9280
7890
  async ensureS3Policy(iamClient, roleName, bucketName) {
9281
7891
  await iamClient.putRolePolicy({
9282
- PolicyDocument: JSON.stringify({
9283
- Version: "2012-10-17",
9284
- Statement: [{
9285
- Action: ["s3:ListBucket"],
9286
- Effect: "Allow",
9287
- Resource: [`arn:aws:s3:::${bucketName}`]
9288
- }, {
9289
- Action: ["s3:GetObject"],
9290
- Effect: "Allow",
9291
- Resource: [`arn:aws:s3:::${bucketName}/*`]
9292
- }]
9293
- }),
7892
+ PolicyDocument: JSON.stringify(buildS3Policy(bucketName)),
9294
7893
  PolicyName: "HotUpdaterS3ReadAccess",
9295
7894
  RoleName: roleName
9296
7895
  });
9297
7896
  }
9298
7897
  async ensureSsmPolicy(iamClient, roleName, accountId, parameterName) {
9299
- const parameterPath = parameterName.replace(/^\/+/, "");
9300
7898
  await iamClient.putRolePolicy({
9301
- PolicyDocument: JSON.stringify({
9302
- Version: "2012-10-17",
9303
- Statement: [{
9304
- Effect: "Allow",
9305
- Action: ["ssm:GetParameter"],
9306
- Resource: `arn:aws:ssm:${this.region}:${accountId}:parameter/${parameterPath}`
9307
- }]
9308
- }),
7899
+ PolicyDocument: JSON.stringify(buildSsmPolicy(this.region, accountId, parameterName)),
9309
7900
  PolicyName: "HotUpdaterSSMAccess",
9310
7901
  RoleName: roleName
9311
7902
  });
@@ -9320,14 +7911,7 @@ var IAMManager = class {
9320
7911
  credentials: this.credentials
9321
7912
  }).getCallerIdentity({})).Account;
9322
7913
  if (!accountId) throw new Error("Failed to get AWS account ID");
9323
- const assumeRolePolicyDocument = JSON.stringify({
9324
- Version: "2012-10-17",
9325
- Statement: [{
9326
- Effect: "Allow",
9327
- Principal: { Service: ["lambda.amazonaws.com", "edgelambda.amazonaws.com"] },
9328
- Action: "sts:AssumeRole"
9329
- }]
9330
- });
7914
+ const assumeRolePolicyDocument = JSON.stringify(LAMBDA_EDGE_TRUST_POLICY);
9331
7915
  const roleName = `hot-updater-edge-${(0, node_crypto.createHash)("sha256").update(options.lambdaName).digest("hex").slice(0, 16)}`;
9332
7916
  try {
9333
7917
  const { Role: existingRole } = await iamClient.getRole({ RoleName: roleName });
@@ -9403,7 +7987,8 @@ var LambdaEdgeDeployer = class {
9403
7987
  async deploy(lambdaRoleArn, lambdaName, config) {
9404
7988
  const cwd = (0, _hot_updater_cli_tools.getCwd)();
9405
7989
  const lambdaPath = require.resolve("@hot-updater/aws/lambda");
9406
- const { tmpDir, removeTmpDir } = await (0, _hot_updater_cli_tools.copyDirToTmp)(path.default.dirname(lambdaPath));
7990
+ const lambdaDir = path.default.dirname(lambdaPath);
7991
+ const { tmpDir, removeTmpDir } = await (0, _hot_updater_cli_tools.copyDirToTmp)(lambdaDir);
9407
7992
  const indexPath = path.default.join(tmpDir, "index.cjs");
9408
7993
  const code = (0, _hot_updater_cli_tools.transformEnv)(indexPath, {
9409
7994
  CLOUDFRONT_KEY_PAIR_ID: config.publicKeyId,
@@ -9636,7 +8221,7 @@ var SSMKeyPairManager = class {
9636
8221
  _hot_updater_cli_tools.p.log.info("Using existing CloudFront key pair from SSM Parameter Store");
9637
8222
  return keyPair;
9638
8223
  }
9639
- const { publicKey, privateKey } = crypto.default.generateKeyPairSync("rsa", {
8224
+ const { publicKey, privateKey } = crypto$1.default.generateKeyPairSync("rsa", {
9640
8225
  modulusLength: 2048,
9641
8226
  publicKeyEncoding: {
9642
8227
  type: "spki",
@@ -9648,7 +8233,7 @@ var SSMKeyPairManager = class {
9648
8233
  }
9649
8234
  });
9650
8235
  const keyPair = {
9651
- keyPairId: `HOTUPDATER-${crypto.default.randomBytes(4).toString("hex").toUpperCase()}`,
8236
+ keyPairId: `HOTUPDATER-${crypto$1.default.randomBytes(4).toString("hex").toUpperCase()}`,
9652
8237
  publicKey,
9653
8238
  privateKey
9654
8239
  };
@@ -9711,12 +8296,11 @@ const awsOptions = {
9711
8296
  };`.trim()
9712
8297
  }];
9713
8298
  break;
9714
- case "account":
9715
- helperStatements = [{
9716
- name: "awsOptions",
9717
- strategy: "merge-object",
9718
- replaceIncompatibleProperties: ["credentials"],
9719
- code: `
8299
+ case "account": helperStatements = [{
8300
+ name: "awsOptions",
8301
+ strategy: "merge-object",
8302
+ replaceIncompatibleProperties: ["credentials"],
8303
+ code: `
9720
8304
  const awsOptions = {
9721
8305
  region: process.env.HOT_UPDATER_S3_REGION!,
9722
8306
  credentials: {
@@ -9724,8 +8308,7 @@ const awsOptions = {
9724
8308
  secretAccessKey: process.env.HOT_UPDATER_S3_SECRET_ACCESS_KEY!,
9725
8309
  },
9726
8310
  };`.trim()
9727
- }];
9728
- break;
8311
+ }];
9729
8312
  }
9730
8313
  const builder = new _hot_updater_cli_tools.ConfigBuilder().setBuildType(build).setStorage(storageConfig).setDatabase(databaseConfig).setIntermediateCode(helperStatements.map((statement) => statement.code.trim()).join("\n\n"));
9731
8314
  switch (authMode.mode) {
@@ -9735,13 +8318,10 @@ const awsOptions = {
9735
8318
  named: ["fromSSO"]
9736
8319
  });
9737
8320
  break;
9738
- case "local":
9739
- builder.addImport({
9740
- pkg: "@aws-sdk/credential-providers",
9741
- named: [authMode.profile ? "fromIni" : "fromNodeProviderChain"]
9742
- });
9743
- break;
9744
- case "account": break;
8321
+ case "local": builder.addImport({
8322
+ pkg: "@aws-sdk/credential-providers",
8323
+ named: [authMode.profile ? "fromIni" : "fromNodeProviderChain"]
8324
+ });
9745
8325
  }
9746
8326
  return (0, _hot_updater_cli_tools.createHotUpdaterConfigScaffoldFromBuilder)(builder, { helperStatements });
9747
8327
  };
@@ -9776,11 +8356,12 @@ const isAwsRegion = (value) => {
9776
8356
  return value !== void 0 && Object.hasOwn(regionLocationMap, value);
9777
8357
  };
9778
8358
  const prepareDynamoDBDeployment = async (input) => {
9779
- return new DynamoDBManager(input.region, input.credentials).ensureTable(input.tableName);
8359
+ await new DynamoDBManager(input.region, input.credentials).ensureTable(input.tableName);
8360
+ await migrateDynamoDB(input);
9780
8361
  };
8362
+ /** The app's client key, through the managed server's apiKeys() plugin, on the table it reads. */
9781
8363
  const prepareDynamoDBApiKey = async (input) => {
9782
- return (await (0, _hot_updater_server.provisionApiKey)({
9783
- apiKeys: input.apiKeys,
8364
+ return (await (0, _hot_updater_server_db.createDatabasePluginApis)(input.database, plugins).apiKeys.provision({
9784
8365
  existingApiKey: input.existingApiKey,
9785
8366
  name: "AWS init"
9786
8367
  })).apiKey;
@@ -9905,13 +8486,14 @@ const runInit = async ({ build, envFile }) => {
9905
8486
  dynamodbTableName: resolvedDynamoDBTableName,
9906
8487
  lambdaName
9907
8488
  };
8489
+ const persistCredentialInputs = await (0, _hot_updater_cli_tools.confirmInitInputPersistence)({
8490
+ existingEnv: managedEnv,
8491
+ inputs: resolvedInputs,
8492
+ nonInteractive,
8493
+ provider: initProvider
8494
+ });
9908
8495
  const initEnv = (0, _hot_updater_cli_tools.getInitProviderEnvVars)({
9909
- includeConsentInputs: await (0, _hot_updater_cli_tools.confirmInitInputPersistence)({
9910
- existingEnv: managedEnv,
9911
- inputs: resolvedInputs,
9912
- nonInteractive,
9913
- provider: initProvider
9914
- }),
8496
+ includeConsentInputs: persistCredentialInputs,
9915
8497
  inputs: resolvedInputs,
9916
8498
  provider: initProvider
9917
8499
  });
@@ -9943,7 +8525,7 @@ const runInit = async ({ build, envFile }) => {
9943
8525
  let apiKey;
9944
8526
  try {
9945
8527
  apiKey = await prepareDynamoDBApiKey({
9946
- apiKeys: databasePlugin.models.apiKeys,
8528
+ database: databasePlugin,
9947
8529
  existingApiKey: providerEnv.HOT_UPDATER_API_KEY
9948
8530
  });
9949
8531
  await (0, _hot_updater_cli_tools.makeEnv)({ HOT_UPDATER_API_KEY: apiKey });
@@ -9990,6 +8572,7 @@ const runInit = async ({ build, envFile }) => {
9990
8572
  if (configWriteResult.status === "created") _hot_updater_cli_tools.p.log.success("Generated 'hot-updater.config.ts' file with AWS settings.");
9991
8573
  else if (configWriteResult.status === "merged") _hot_updater_cli_tools.p.log.success("Updated 'hot-updater.config.ts' file with AWS settings.");
9992
8574
  else _hot_updater_cli_tools.p.log.warn(`Kept existing 'hot-updater.config.ts' unchanged: ${configWriteResult.reason}`);
8575
+ await (0, _hot_updater_cli_tools.generateHotUpdaterPlugins)("@hot-updater/aws");
9993
8576
  const sourceUrl = `https://${distributionDomain}`;
9994
8577
  _hot_updater_cli_tools.p.note((0, _hot_updater_cli_tools.transformTemplate)(SOURCE_TEMPLATE, {
9995
8578
  apiKey: JSON.stringify(apiKey),
@@ -10001,6 +8584,13 @@ const runInit = async ({ build, envFile }) => {
10001
8584
  _hot_updater_cli_tools.p.log.success("Done! 🎉");
10002
8585
  };
10003
8586
  //#endregion
8587
+ exports.LAMBDA_EDGE_TRUST_POLICY = LAMBDA_EDGE_TRUST_POLICY;
8588
+ exports.buildDynamoDBBackupInput = buildDynamoDBBackupInput;
8589
+ exports.buildDynamoDBCreateTableInput = buildDynamoDBCreateTableInput;
8590
+ exports.buildDynamoDBPolicy = buildDynamoDBPolicy;
8591
+ exports.buildDynamoDBSchemaSettingsInput = buildDynamoDBSchemaSettingsInput;
8592
+ exports.buildS3Policy = buildS3Policy;
8593
+ exports.buildSsmPolicy = buildSsmPolicy;
10004
8594
  exports.prepareDynamoDBApiKey = prepareDynamoDBApiKey;
10005
8595
  exports.prepareDynamoDBDeployment = prepareDynamoDBDeployment;
10006
8596
  exports.runInit = runInit;