@marktiderman/genesis-forms 0.3.0 → 0.3.1

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.
@@ -62,6 +62,11 @@ async function timedFetch(deps, url, init, signal) {
62
62
  const timeout = AbortSignal.timeout(deps.timeoutMs ?? REQUEST_TIMEOUT_MS);
63
63
  const any = AbortSignal.any;
64
64
  const combined = signal ? any ? any([timeout, signal]) : signal.aborted ? signal : timeout : timeout;
65
+ try {
66
+ new Request(url, { method: init.method ?? "GET", headers: init.headers ?? {} });
67
+ } catch {
68
+ throw new DeliveryError("Invalid request (a header value or the URL is malformed)", { retryable: false });
69
+ }
65
70
  try {
66
71
  return await (deps.fetch ?? fetch)(url, { ...init, signal: combined });
67
72
  } catch (error) {
@@ -758,7 +763,7 @@ async function googleAccessToken(account, options = {}) {
758
763
  if (!clientEmail || !privateKey) throw new DeliveryError("The Google service account email or private key is not set");
759
764
  const scope = options.scope ?? SHEETS_SCOPE;
760
765
  const now = options.now?.() ?? Date.now();
761
- const cacheKey = `${clientEmail} ${scope}`;
766
+ const cacheKey = `${clientEmail} ${scope} ${await sha256Hex(privateKey.replace(/\\n/g, "\n").trim())}`;
762
767
  const hit = cache.get(cacheKey);
763
768
  if (hit && hit.expiresAt - 6e4 > now) return hit.token;
764
769
  const iat = Math.floor(now / 1e3);
@@ -789,6 +794,7 @@ var SHEETS_API = "https://sheets.googleapis.com/v4/spreadsheets";
789
794
  var DEFAULT_SHEET_NAME = "Responses";
790
795
  var DEDUPE_WINDOW = 500;
791
796
  var HEADER_SCAN_ROWS = 100;
797
+ var HEADER_WRITE_ATTEMPTS = 3;
792
798
  function mergeHeader(existing, keys, width = 0) {
793
799
  const header = existing.map((h) => h == null ? "" : String(h));
794
800
  while (header.length < width) header.push("");
@@ -884,35 +890,52 @@ function sheetsDestination(options) {
884
890
  });
885
891
  const keys = [...new Set(rows.flatMap((r) => r.columns))];
886
892
  async function setUp(previous) {
887
- const meta = await call(
888
- "read",
889
- `${base}?fields=${encodeURIComponent("sheets.properties(sheetId,title,gridProperties.columnCount)")}`
890
- );
891
- let sheet = meta.sheets?.map((s) => s.properties).find((p) => p?.title === sheetName);
893
+ const readTabs = async () => {
894
+ const meta = await call(
895
+ "read",
896
+ `${base}?fields=${encodeURIComponent("sheets.properties(sheetId,title,gridProperties.columnCount)")}`
897
+ );
898
+ return meta.sheets?.map((s) => s.properties).find((p) => p?.title === sheetName);
899
+ };
900
+ let sheet = await readTabs();
892
901
  if (!sheet) {
893
- const added = await batchUpdate("add tab", [{ addSheet: { properties: { title: sheetName } } }]);
894
- sheet = added.replies?.[0]?.addSheet?.properties ?? { sheetId: 0, title: sheetName };
895
- }
896
- const read = await call(
897
- "read header",
898
- `${range(`A1:ZZZ${HEADER_SCAN_ROWS}`)}?majorDimension=COLUMNS`
899
- );
900
- const used = read.values ?? [];
901
- const existing = used.map((column) => column[0] ?? "");
902
- const header = mergeHeader(existing, keys, used.length);
903
- if (!header.includes("id")) {
904
- if (used.length) header.push("id");
905
- else header.unshift("id");
902
+ try {
903
+ const added = await batchUpdate("add tab", [{ addSheet: { properties: { title: sheetName } } }]);
904
+ sheet = added.replies?.[0]?.addSheet?.properties ?? { sheetId: 0, title: sheetName };
905
+ } catch (error) {
906
+ if (!(error instanceof DeliveryError) || error.status !== 400) throw error;
907
+ sheet = await readTabs();
908
+ if (!sheet) throw error;
909
+ }
906
910
  }
911
+ const readHeader = async () => {
912
+ const read = await call(
913
+ "read header",
914
+ `${range(`A1:ZZZ${HEADER_SCAN_ROWS}`)}?majorDimension=COLUMNS`
915
+ );
916
+ const used2 = read.values ?? [];
917
+ return { used: used2.length, existing: used2.map((column) => column[0] ?? "") };
918
+ };
907
919
  let columns = sheet.gridProperties?.columnCount ?? 26;
908
- if (header.length > columns) {
909
- await batchUpdate("add columns", [
910
- { appendDimension: { sheetId: sheet.sheetId, dimension: "COLUMNS", length: header.length - columns } }
911
- ]);
912
- columns = header.length;
913
- }
914
- const changed = header.length !== existing.length || header.some((h, i) => h !== String(existing[i] ?? ""));
915
- if (changed) {
920
+ let { used, existing } = await readHeader();
921
+ let header = [];
922
+ for (let attempt = 0; ; attempt++) {
923
+ header = mergeHeader(existing, keys, used);
924
+ if (!header.includes("id")) {
925
+ if (used) header.push("id");
926
+ else header.unshift("id");
927
+ }
928
+ const changed = header.length !== existing.length || header.some((h, i) => h !== String(existing[i] ?? ""));
929
+ if (!changed) break;
930
+ if (attempt >= HEADER_WRITE_ATTEMPTS) {
931
+ throw new DeliveryError("Google Sheets header kept changing while it was written", { retryable: true });
932
+ }
933
+ if (header.length > columns) {
934
+ await batchUpdate("add columns", [
935
+ { appendDimension: { sheetId: sheet.sheetId, dimension: "COLUMNS", length: header.length - columns } }
936
+ ]);
937
+ columns = header.length;
938
+ }
916
939
  await call("write header", `${range("A1")}?valueInputOption=RAW`, {
917
940
  method: "PUT",
918
941
  body: JSON.stringify({ values: [header] })
@@ -927,6 +950,7 @@ function sheetsDestination(options) {
927
950
  }
928
951
  ]);
929
952
  }
953
+ ({ used, existing } = await readHeader());
930
954
  }
931
955
  return {
932
956
  spreadsheet_id: spreadsheetId,