run402 3.7.1 → 3.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/lib/functions.mjs +234 -2
  2. package/package.json +1 -1
  3. package/sdk/dist/config.d.ts +10 -1
  4. package/sdk/dist/config.d.ts.map +1 -1
  5. package/sdk/dist/config.js +30 -0
  6. package/sdk/dist/config.js.map +1 -1
  7. package/sdk/dist/index.d.ts +6 -1
  8. package/sdk/dist/index.d.ts.map +1 -1
  9. package/sdk/dist/index.js +14 -1
  10. package/sdk/dist/index.js.map +1 -1
  11. package/sdk/dist/namespaces/deploy.d.ts.map +1 -1
  12. package/sdk/dist/namespaces/deploy.js +115 -15
  13. package/sdk/dist/namespaces/deploy.js.map +1 -1
  14. package/sdk/dist/namespaces/deploy.types.d.ts +21 -2
  15. package/sdk/dist/namespaces/deploy.types.d.ts.map +1 -1
  16. package/sdk/dist/namespaces/deploy.types.js.map +1 -1
  17. package/sdk/dist/namespaces/functions.d.ts +25 -1
  18. package/sdk/dist/namespaces/functions.d.ts.map +1 -1
  19. package/sdk/dist/namespaces/functions.js +272 -2
  20. package/sdk/dist/namespaces/functions.js.map +1 -1
  21. package/sdk/dist/namespaces/functions.types.d.ts +87 -1
  22. package/sdk/dist/namespaces/functions.types.d.ts.map +1 -1
  23. package/sdk/dist/node/config.d.ts +2 -2
  24. package/sdk/dist/node/config.d.ts.map +1 -1
  25. package/sdk/dist/node/config.js +1 -1
  26. package/sdk/dist/node/config.js.map +1 -1
  27. package/sdk/dist/node/deploy-manifest.d.ts.map +1 -1
  28. package/sdk/dist/node/deploy-manifest.js +3 -0
  29. package/sdk/dist/node/deploy-manifest.js.map +1 -1
  30. package/sdk/dist/node/index.d.ts +2 -2
  31. package/sdk/dist/node/index.d.ts.map +1 -1
  32. package/sdk/dist/node/index.js +1 -1
  33. package/sdk/dist/node/index.js.map +1 -1
  34. package/sdk/dist/scoped.d.ts +14 -1
  35. package/sdk/dist/scoped.d.ts.map +1 -1
  36. package/sdk/dist/scoped.js +31 -0
  37. package/sdk/dist/scoped.js.map +1 -1
@@ -718,6 +718,7 @@ function functionToCoreSpec(fn) {
718
718
  ...(fn.entrypoint !== undefined ? { entrypoint: fn.entrypoint } : {}),
719
719
  ...(fn.config !== undefined ? { config: fn.config } : {}),
720
720
  ...(fn.deps !== undefined ? { deps: fn.deps } : {}),
721
+ ...(fn.triggers !== undefined ? { triggers: fn.triggers } : {}),
721
722
  ...(fn.schedule !== undefined ? { schedule: fn.schedule } : {}),
722
723
  ...(fn.requireAuth !== undefined ? { requireAuth: fn.requireAuth } : {}),
723
724
  ...(fn.requireRole !== undefined ? { requireRole: fn.requireRole } : {}),
@@ -740,6 +741,7 @@ function functionToWire(fn) {
740
741
  }
741
742
  : {}),
742
743
  ...(fn.deps !== undefined ? { deps: fn.deps } : {}),
744
+ ...(fn.triggers !== undefined ? { triggers: fn.triggers } : {}),
743
745
  ...(fn.schedule !== undefined ? { schedule: fn.schedule } : {}),
744
746
  ...(fn.requireAuth !== undefined ? { require_auth: fn.requireAuth } : {}),
745
747
  ...(fn.requireRole !== undefined
@@ -1016,11 +1018,14 @@ async function preflightTierFunctionLimits(client, spec, ciCredentials) {
1016
1018
  max_function_memory_mb: limits.maxMemoryMb.value,
1017
1019
  });
1018
1020
  }
1019
- if (isScheduledCron(entry.fn.schedule) && limits.minCronIntervalMinutes) {
1020
- const intervalMinutes = estimateCronMinimumIntervalMinutes(entry.fn.schedule);
1021
+ for (const trigger of scheduleTriggersForFunction(entry.fn)) {
1022
+ if (!limits.minCronIntervalMinutes)
1023
+ continue;
1024
+ const intervalMinutes = estimateCronMinimumIntervalMinutes(trigger.cron);
1021
1025
  if (intervalMinutes !== null &&
1022
1026
  intervalMinutes < limits.minCronIntervalMinutes.value) {
1023
- throw tierLimitError(`Function ${entry.name} schedule runs every ${intervalMinutes} minute(s), below the ${limits.tier} tier minimum interval of ${limits.minCronIntervalMinutes.value} minutes.`, `${entry.fieldPrefix}.schedule`, entry.fn.schedule, limits, limits.minCronIntervalMinutes, {
1027
+ const triggerLabel = trigger.id === "__legacy__" ? "schedule" : `trigger ${trigger.id}`;
1028
+ throw tierLimitError(`Function ${entry.name} ${triggerLabel} runs every ${intervalMinutes} minute(s), below the ${limits.tier} tier minimum interval of ${limits.minCronIntervalMinutes.value} minutes.`, `${entry.fieldPrefix}.${trigger.fieldSuffix}`, trigger.cron, limits, limits.minCronIntervalMinutes, {
1024
1029
  interval_minutes: intervalMinutes,
1025
1030
  min_interval_minutes: limits.minCronIntervalMinutes.value,
1026
1031
  min_cron_interval_minutes: limits.minCronIntervalMinutes.value,
@@ -1044,7 +1049,7 @@ function hasFunctionTierPreflightInputs(functions) {
1044
1049
  return false;
1045
1050
  return collectFunctionPreflightEntries(functions).some((entry) => (entry.fn.config?.timeoutSeconds !== undefined ||
1046
1051
  entry.fn.config?.memoryMb !== undefined ||
1047
- isScheduledCron(entry.fn.schedule)));
1052
+ scheduleTriggersForFunction(entry.fn).length > 0));
1048
1053
  }
1049
1054
  function collectFunctionPreflightEntries(functions) {
1050
1055
  if (!functions)
@@ -1160,23 +1165,21 @@ async function computeDesiredScheduledFunctionCount(client, spec) {
1160
1165
  function countScheduledFunctionsInSetEntries(functions) {
1161
1166
  if (!functions)
1162
1167
  return 0;
1163
- const names = new Set();
1164
- for (const [name, fn] of Object.entries(functions.replace ?? {})) {
1165
- if (isScheduledCron(fn.schedule))
1166
- names.add(name);
1168
+ let count = 0;
1169
+ for (const fn of Object.values(functions.replace ?? {})) {
1170
+ count += scheduleTriggersForFunction(fn).length;
1167
1171
  }
1168
- for (const [name, fn] of Object.entries(functions.patch?.set ?? {})) {
1169
- if (isScheduledCron(fn.schedule))
1170
- names.add(name);
1172
+ for (const fn of Object.values(functions.patch?.set ?? {})) {
1173
+ count += scheduleTriggersForFunction(fn).length;
1171
1174
  }
1172
- return names.size;
1175
+ return count;
1173
1176
  }
1174
1177
  function scheduledFunctionNames(functions) {
1175
1178
  if (!functions)
1176
1179
  return null;
1177
1180
  const scheduled = new Set();
1178
1181
  for (const [name, fn] of Object.entries(functions)) {
1179
- if (isScheduledCron(fn.schedule))
1182
+ if (scheduleTriggersForFunction(fn).length > 0)
1180
1183
  scheduled.add(name);
1181
1184
  }
1182
1185
  return scheduled;
@@ -1188,7 +1191,7 @@ function applyScheduledFunctionPatch(scheduled, patch) {
1188
1191
  for (const [name, fn] of Object.entries(patch?.set ?? {})) {
1189
1192
  if (fn.schedule === null)
1190
1193
  scheduled.delete(name);
1191
- else if (isScheduledCron(fn.schedule))
1194
+ else if (scheduleTriggersForFunction(fn).length > 0)
1192
1195
  scheduled.add(name);
1193
1196
  }
1194
1197
  }
@@ -1205,11 +1208,33 @@ async function readActiveScheduledFunctionNames(client, projectId) {
1205
1208
  }
1206
1209
  const scheduled = new Set();
1207
1210
  for (const fn of inventory.functions ?? []) {
1208
- if (isScheduledCron(fn.schedule))
1211
+ if (isScheduledCron(fn.schedule) || scheduleTriggersForFunction(fn).length > 0)
1209
1212
  scheduled.add(fn.name);
1210
1213
  }
1211
1214
  return scheduled;
1212
1215
  }
1216
+ function scheduleTriggersForFunction(fn) {
1217
+ if (fn.triggers && fn.triggers.length > 0) {
1218
+ return fn.triggers
1219
+ .filter((trigger) => trigger.type === "schedule")
1220
+ .map((trigger) => ({
1221
+ ...trigger,
1222
+ fieldSuffix: `triggers.${trigger.id}.cron`,
1223
+ }));
1224
+ }
1225
+ return isScheduledCron(fn.schedule)
1226
+ ? [{
1227
+ id: "__legacy__",
1228
+ type: "schedule",
1229
+ cron: fn.schedule,
1230
+ timezone: "UTC",
1231
+ misfire_policy: "skip",
1232
+ overlap_policy: "allow",
1233
+ run: { event_type: "legacy.schedule", payload: {} },
1234
+ fieldSuffix: "schedule",
1235
+ }]
1236
+ : [];
1237
+ }
1213
1238
  function scheduledCountTierLimitError(count, limits, limit, countSource) {
1214
1239
  return tierLimitError(`Deploy would have ${count} scheduled function(s), exceeding the ${limits.tier} tier maximum of ${limit.value}.`, "functions.scheduled_count", count, limits, limit, {
1215
1240
  tier_max: limit.value,
@@ -1890,6 +1915,7 @@ const FUNCTION_SPEC_FIELDS = new Set([
1890
1915
  "entrypoint",
1891
1916
  "config",
1892
1917
  "deps",
1918
+ "triggers",
1893
1919
  "schedule",
1894
1920
  "requireAuth",
1895
1921
  "requireRole",
@@ -1897,6 +1923,8 @@ const FUNCTION_SPEC_FIELDS = new Set([
1897
1923
  "capabilities",
1898
1924
  ]);
1899
1925
  const FUNCTION_CONFIG_FIELDS = new Set(["timeoutSeconds", "memoryMb"]);
1926
+ const FUNCTION_TRIGGER_FIELDS = new Set(["id", "type", "cron", "timezone", "misfire_policy", "overlap_policy", "run"]);
1927
+ const FUNCTION_TRIGGER_RUN_FIELDS = new Set(["event_type", "payload", "retry", "expires_after_seconds"]);
1900
1928
  const SITE_SPEC_FIELDS = new Set(["replace", "patch", "public_paths"]);
1901
1929
  const SITE_PATCH_FIELDS = new Set(["put", "delete"]);
1902
1930
  const SITE_PUBLIC_PATHS_FIELDS = new Set(["mode", "replace"]);
@@ -2029,6 +2057,56 @@ function validateFunctionMap(value, resource) {
2029
2057
  if (entry.files !== undefined) {
2030
2058
  requireObject(entry.files, `${resource}.${name}.files`);
2031
2059
  }
2060
+ validateFunctionTriggers(entry.triggers, `${resource}.${name}.triggers`);
2061
+ }
2062
+ }
2063
+ function validateFunctionTriggers(value, resource) {
2064
+ if (value === undefined)
2065
+ return;
2066
+ if (!Array.isArray(value))
2067
+ throw invalidSpec(`ReleaseSpec.${resource} must be an array`, resource);
2068
+ const seen = new Set();
2069
+ for (const [index, trigger] of value.entries()) {
2070
+ const path = `${resource}.${index}`;
2071
+ const obj = requireObject(trigger, path);
2072
+ validateKnownFields(obj, path, FUNCTION_TRIGGER_FIELDS, {
2073
+ delivery: "Remove delivery; schedule triggers always start durable function runs.",
2074
+ });
2075
+ if (typeof obj.id !== "string" || obj.id.trim() === "") {
2076
+ throw invalidSpec(`ReleaseSpec.${path}.id is required for schedule triggers`, `${path}.id`);
2077
+ }
2078
+ if (seen.has(obj.id))
2079
+ throw invalidSpec(`ReleaseSpec.${path}.id duplicates ${JSON.stringify(obj.id)}`, `${path}.id`);
2080
+ seen.add(obj.id);
2081
+ if (obj.type !== "schedule")
2082
+ throw invalidSpec(`ReleaseSpec.${path}.type must be "schedule"`, `${path}.type`);
2083
+ if (typeof obj.cron !== "string" || obj.cron.trim().split(/\s+/).length !== 5) {
2084
+ throw invalidSpec(`ReleaseSpec.${path}.cron must be a 5-field cron expression`, `${path}.cron`);
2085
+ }
2086
+ if (obj.timezone !== undefined && typeof obj.timezone !== "string") {
2087
+ throw invalidSpec(`ReleaseSpec.${path}.timezone must be a string`, `${path}.timezone`);
2088
+ }
2089
+ if (obj.misfire_policy !== undefined && obj.misfire_policy !== "skip") {
2090
+ throw invalidSpec(`ReleaseSpec.${path}.misfire_policy must be "skip"`, `${path}.misfire_policy`);
2091
+ }
2092
+ if (obj.overlap_policy !== undefined && obj.overlap_policy !== "allow") {
2093
+ throw invalidSpec(`ReleaseSpec.${path}.overlap_policy must be "allow"`, `${path}.overlap_policy`);
2094
+ }
2095
+ const run = requireObject(obj.run, `${path}.run`);
2096
+ validateKnownFields(run, `${path}.run`, FUNCTION_TRIGGER_RUN_FIELDS);
2097
+ if (typeof run.event_type !== "string" || run.event_type.trim() === "") {
2098
+ throw invalidSpec(`ReleaseSpec.${path}.run.event_type is required`, `${path}.run.event_type`);
2099
+ }
2100
+ if (run.payload !== undefined)
2101
+ requireObject(run.payload, `${path}.run.payload`);
2102
+ if (run.retry !== undefined)
2103
+ requireObject(run.retry, `${path}.run.retry`);
2104
+ if (run.expires_after_seconds !== undefined &&
2105
+ (typeof run.expires_after_seconds !== "number" ||
2106
+ !Number.isSafeInteger(run.expires_after_seconds) ||
2107
+ run.expires_after_seconds <= 0)) {
2108
+ throw invalidSpec(`ReleaseSpec.${path}.run.expires_after_seconds must be a positive integer`, `${path}.run.expires_after_seconds`);
2109
+ }
2032
2110
  }
2033
2111
  }
2034
2112
  /**
@@ -2934,6 +3012,8 @@ async function normalizeFunction(fn, remember) {
2934
3012
  out.config = fn.config;
2935
3013
  if (fn.deps !== undefined)
2936
3014
  out.deps = fn.deps;
3015
+ if (fn.triggers !== undefined)
3016
+ out.triggers = normalizeFunctionTriggers(fn.triggers);
2937
3017
  if (fn.schedule !== undefined)
2938
3018
  out.schedule = fn.schedule;
2939
3019
  if (fn.entrypoint)
@@ -2955,6 +3035,26 @@ async function normalizeFunction(fn, remember) {
2955
3035
  }
2956
3036
  return out;
2957
3037
  }
3038
+ function normalizeFunctionTriggers(triggers) {
3039
+ return triggers
3040
+ .map((trigger) => ({
3041
+ id: trigger.id,
3042
+ type: "schedule",
3043
+ cron: trigger.cron,
3044
+ timezone: trigger.timezone ?? "UTC",
3045
+ misfire_policy: trigger.misfire_policy ?? "skip",
3046
+ overlap_policy: trigger.overlap_policy ?? "allow",
3047
+ run: {
3048
+ event_type: trigger.run.event_type,
3049
+ payload: trigger.run.payload ?? {},
3050
+ ...(trigger.run.retry !== undefined ? { retry: { ...trigger.run.retry } } : {}),
3051
+ ...(trigger.run.expires_after_seconds !== undefined
3052
+ ? { expires_after_seconds: trigger.run.expires_after_seconds }
3053
+ : {}),
3054
+ },
3055
+ }))
3056
+ .sort((a, b) => a.id.localeCompare(b.id));
3057
+ }
2958
3058
  async function normalizeFileSet(set, remember) {
2959
3059
  // `dir(path)` produces a LocalDirRef sentinel that is documented as a
2960
3060
  // valid site.replace / site.patch.put input. Expand it to a plain FileSet