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