@form-engine-ts/core 5.0.1 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1386,7 +1386,7 @@ function aggregateResponses(schema, submissions) {
1386
1386
  function responseValues(submission) {
1387
1387
  if (typeof submission !== "object" || submission === null) return void 0;
1388
1388
  if ("values" in submission) return submission.values;
1389
- return "answers" in submission ? submission.answers : void 0;
1389
+ return submission.answers;
1390
1390
  }
1391
1391
  function responseIdentifier(submission) {
1392
1392
  if (typeof submission !== "object" || submission === null) return "<unknown>";
@@ -1582,14 +1582,17 @@ function serializeValue(value) {
1582
1582
  }
1583
1583
  function asFormResponse(submission) {
1584
1584
  if (!("values" in submission)) return submission;
1585
+ const metadata = submission.metadata === void 0 ? void 0 : Object.fromEntries(
1586
+ Object.entries(submission.metadata).filter((entry) => entry[1] !== void 0)
1587
+ );
1585
1588
  return {
1586
1589
  responseId: submission.id,
1587
1590
  formId: submission.formId,
1588
- sourceLocale: submission.locale,
1591
+ ...submission.locale === void 0 ? {} : { sourceLocale: submission.locale },
1589
1592
  formVersion: submission.formVersion,
1590
1593
  answers: submission.values,
1591
1594
  submittedAt: submission.submittedAt,
1592
- ...submission.metadata === void 0 ? {} : { metadata: submission.metadata },
1595
+ ...metadata === void 0 ? {} : { metadata },
1593
1596
  ...submission.translationMetadata === void 0 ? {} : { translationMetadata: submission.translationMetadata }
1594
1597
  };
1595
1598
  }
@@ -1598,15 +1601,28 @@ function serializeUnknown(value) {
1598
1601
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
1599
1602
  return JSON.stringify(value);
1600
1603
  }
1604
+ function submissionWithMetadata(submission) {
1605
+ return { ...submission, metadata: submission.metadata ?? {} };
1606
+ }
1601
1607
  async function* exportResponsesToCsvStream(schema, submissions, options = {}) {
1602
1608
  assertValidFormSchema(schema);
1603
1609
  const includeDefaultColumns = options.includeDefaultColumns ?? true;
1604
1610
  const customColumns = options.columns ?? [];
1611
+ const contractColumns = options.customColumns ?? [];
1612
+ const includeLocale = options.includeLocale ?? true;
1613
+ const includePiiStatus = options.includePiiStatus ?? false;
1605
1614
  const headers = [
1606
- ...includeDefaultColumns ? ["submissionId", "submittedAt", "locale", ...schema.fields.map((field) => field.id)] : [],
1607
- ...customColumns.map((column) => column.header)
1615
+ ...includeDefaultColumns ? [
1616
+ "submissionId",
1617
+ "submittedAt",
1618
+ ...includeLocale ? ["locale"] : [],
1619
+ ...includePiiStatus ? ["piiStatus"] : [],
1620
+ ...schema.fields.map((field) => field.id)
1621
+ ] : [],
1622
+ ...customColumns.map((column) => column.header),
1623
+ ...contractColumns.map((column) => column.header)
1608
1624
  ];
1609
- const neutralizeFormulas = options.neutralizeFormulas ?? true;
1625
+ const neutralizeFormulas = options.preventFormulaInjection ?? options.neutralizeFormulas ?? true;
1610
1626
  const header = headers.map((value) => escapeCsvCell(value, neutralizeFormulas)).join(",");
1611
1627
  yield `${options.withBom ?? true ? "\uFEFF" : ""}${header}`;
1612
1628
  for await (const submission of submissions) {
@@ -1615,10 +1631,12 @@ async function* exportResponsesToCsvStream(schema, submissions, options = {}) {
1615
1631
  const response = asFormResponse(submission);
1616
1632
  const answers = response.answers;
1617
1633
  const visible = selectVisibleAnswers(schema, answers);
1634
+ const piiStatus = response.metadata?.piiConfirmed === true ? "confirmed" : "unconfirmed";
1618
1635
  const defaultCells = includeDefaultColumns ? [
1619
1636
  response.responseId,
1620
1637
  response.submittedAt,
1621
- response.sourceLocale ?? "",
1638
+ ...includeLocale ? [response.sourceLocale ?? ""] : [],
1639
+ ...includePiiStatus ? [piiStatus] : [],
1622
1640
  ...schema.fields.map((field) => serializeUnknown(visible[field.id]))
1623
1641
  ] : [];
1624
1642
  const context = {
@@ -1628,8 +1646,9 @@ async function* exportResponsesToCsvStream(schema, submissions, options = {}) {
1628
1646
  schema
1629
1647
  };
1630
1648
  const customCells = await Promise.all(customColumns.map((column) => column.getValue(context)));
1649
+ const contractCells = "values" in submission ? contractColumns.map((column) => column.getValue(submissionWithMetadata(submission), schema)) : contractColumns.map(() => void 0);
1631
1650
  yield `\r
1632
- ${[...defaultCells, ...customCells].map((value) => escapeCsvCell(value, neutralizeFormulas)).join(",")}`;
1651
+ ${[...defaultCells, ...customCells, ...contractCells].map((value) => escapeCsvCell(value, neutralizeFormulas)).join(",")}`;
1633
1652
  }
1634
1653
  }
1635
1654
  function isWebWritableStream(writable) {
@@ -1686,23 +1705,68 @@ function exportResponsesToCsv(schema, responses, options = {}) {
1686
1705
  throw new TypeError(`Submission ${response.id} does not match ${schema.id}@${schema.version}.`);
1687
1706
  }
1688
1707
  }
1708
+ const includeLocale = options.includeLocale ?? true;
1709
+ const includePiiStatus = options.includePiiStatus ?? false;
1710
+ const customColumns = options.customColumns ?? [];
1711
+ const headers = [
1712
+ "submissionId",
1713
+ "submittedAt",
1714
+ ...includeLocale ? ["locale"] : [],
1715
+ ...includePiiStatus ? ["piiStatus"] : [],
1716
+ ...schema.fields.map((field) => field.id),
1717
+ ...customColumns.map((column) => column.header)
1718
+ ];
1689
1719
  const rows = [
1690
- ["submissionId", "submittedAt", "locale", ...schema.fields.map((field) => field.id)],
1720
+ headers,
1691
1721
  ...responses.map((response) => {
1692
1722
  const visible = selectVisibleAnswers(schema, response.values);
1723
+ const piiStatus = response.metadata?.piiConfirmed === true ? "confirmed" : "unconfirmed";
1724
+ const submissionForCustomColumns = submissionWithMetadata(response);
1693
1725
  return [
1694
1726
  response.id,
1695
1727
  response.submittedAt,
1696
- response.locale,
1697
- ...schema.fields.map((field) => serializeValue(visible[field.id]))
1728
+ ...includeLocale ? [response.locale] : [],
1729
+ ...includePiiStatus ? [piiStatus] : [],
1730
+ ...schema.fields.map((field) => serializeValue(visible[field.id])),
1731
+ ...customColumns.map((column) => column.getValue(submissionForCustomColumns, schema))
1698
1732
  ];
1699
1733
  })
1700
1734
  ];
1701
- const neutralizeFormulas = options.neutralizeFormulas ?? true;
1735
+ const neutralizeFormulas = options.preventFormulaInjection ?? options.neutralizeFormulas ?? true;
1702
1736
  const csv = rows.map((row) => row.map((cell) => escapeCsvCell(cell, neutralizeFormulas)).join(",")).join("\r\n");
1703
- return options.withBom ?? true ? `\uFEFF${csv}` : csv;
1737
+ return options.useBom ?? options.withBom ?? true ? `\uFEFF${csv}` : csv;
1704
1738
  }
1705
1739
 
1740
+ // src/compat/legacy.ts
1741
+ var fromLegacyFormSubmission = (legacy) => {
1742
+ const { id, formId, formVersion, answers, locale, metadata, submittedAt, schemaRevision } = legacy;
1743
+ return {
1744
+ id,
1745
+ formId,
1746
+ formVersion,
1747
+ values: { ...answers },
1748
+ ...locale === void 0 ? {} : { locale },
1749
+ metadata,
1750
+ submittedAt,
1751
+ ...schemaRevision === void 0 ? {} : { schemaRevision }
1752
+ };
1753
+ };
1754
+
1755
+ // src/errors.ts
1756
+ var FormSubmissionError = class extends Error {
1757
+ payload;
1758
+ constructor(payload) {
1759
+ super(payload.messageKey);
1760
+ this.name = "FormSubmissionError";
1761
+ this.payload = payload;
1762
+ }
1763
+ toJSON() {
1764
+ return this.payload;
1765
+ }
1766
+ };
1767
+ var serializeSubmissionError = (error) => error.toJSON();
1768
+ var deserializeSubmissionError = (json) => new FormSubmissionError(json);
1769
+
1706
1770
  // src/events.ts
1707
1771
  function bytesToHex(bytes) {
1708
1772
  return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
@@ -2025,7 +2089,20 @@ var EN_MESSAGES = Object.freeze({
2025
2089
  "workspace.errors.maxLocalesExceeded": "The maximum number of locales ({{max}}) has been reached.",
2026
2090
  "workspace.errors.readOnly": "This workspace is read-only.",
2027
2091
  "workspace.errors.adapterNotConfigured": "A translation adapter is not configured.",
2028
- "workspace.errors.translationFailed": "Translation failed."
2092
+ "workspace.errors.translationFailed": "Translation failed.",
2093
+ "workspace.header.title": "Translation workspace",
2094
+ "workspace.header.sourceLocale": "Source language",
2095
+ "workspace.header.targetLocale": "Target language",
2096
+ "workspace.header.translateAll": "Translate all",
2097
+ "workspace.header.progress": "{{translated}}/{{total}} translated ({{percent}}%)",
2098
+ "workspace.slot.sourceText": "Source",
2099
+ "workspace.slot.translatedText": "Translation",
2100
+ "workspace.slot.translateSingle": "Translate this slot",
2101
+ "workspace.slot.revertManual": "Revert manual translation",
2102
+ "workspace.confirm.removeLocaleTitle": "Remove language?",
2103
+ "workspace.confirm.removeLocaleMessage": "This will remove {{locale}} and its translations.",
2104
+ "workspace.empty.noTargetLocales": "No target languages are configured.",
2105
+ "workspace.empty.noSlotsToTranslate": "There are no translation slots for this language."
2029
2106
  });
2030
2107
 
2031
2108
  // src/i18n/catalogs/ja.ts
@@ -2197,7 +2274,20 @@ var JA_MESSAGES = Object.freeze({
2197
2274
  "workspace.errors.maxLocalesExceeded": "\u767B\u9332\u53EF\u80FD\u306A\u6700\u5927\u8A00\u8A9E\u6570 ({{max}}) \u306B\u9054\u3057\u307E\u3057\u305F\u3002",
2198
2275
  "workspace.errors.readOnly": "\u8AAD\u307F\u53D6\u308A\u5C02\u7528\u30E2\u30FC\u30C9\u306E\u305F\u3081\u5909\u66F4\u3067\u304D\u307E\u305B\u3093\u3002",
2199
2276
  "workspace.errors.adapterNotConfigured": "\u7FFB\u8A33\u30A2\u30C0\u30D7\u30BF\u30FC\u304C\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
2200
- "workspace.errors.translationFailed": "\u7FFB\u8A33\u51E6\u7406\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002"
2277
+ "workspace.errors.translationFailed": "\u7FFB\u8A33\u51E6\u7406\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002",
2278
+ "workspace.header.title": "\u591A\u8A00\u8A9E\u7FFB\u8A33\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9",
2279
+ "workspace.header.sourceLocale": "\u5143\u8A00\u8A9E",
2280
+ "workspace.header.targetLocale": "\u7FFB\u8A33\u8A00\u8A9E",
2281
+ "workspace.header.translateAll": "\u4E00\u62EC\u81EA\u52D5\u7FFB\u8A33",
2282
+ "workspace.header.progress": "{{translated}}/{{total}} \u7FFB\u8A33\u6E08\u307F ({{percent}}%)",
2283
+ "workspace.slot.sourceText": "\u539F\u6587",
2284
+ "workspace.slot.translatedText": "\u7FFB\u8A33\u6587",
2285
+ "workspace.slot.translateSingle": "\u3053\u306E\u9805\u76EE\u3092\u7FFB\u8A33",
2286
+ "workspace.slot.revertManual": "\u624B\u52D5\u7FFB\u8A33\u3092\u5143\u306B\u623B\u3059",
2287
+ "workspace.confirm.removeLocaleTitle": "\u8A00\u8A9E\u3092\u524A\u9664\u3057\u307E\u3059\u304B\uFF1F",
2288
+ "workspace.confirm.removeLocaleMessage": "{{locale}} \u3068\u305D\u306E\u7FFB\u8A33\u3092\u524A\u9664\u3057\u307E\u3059\u3002",
2289
+ "workspace.empty.noTargetLocales": "\u7FFB\u8A33\u5148\u306E\u8A00\u8A9E\u304C\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
2290
+ "workspace.empty.noSlotsToTranslate": "\u3053\u306E\u8A00\u8A9E\u306B\u306F\u7FFB\u8A33\u9805\u76EE\u304C\u3042\u308A\u307E\u305B\u3093\u3002"
2201
2291
  });
2202
2292
 
2203
2293
  // src/i18n/translator.ts
@@ -2259,14 +2349,17 @@ async function paginateWithFilter(params) {
2259
2349
  return { items: collected, hasMore: false, totalScannedCount };
2260
2350
  }
2261
2351
  function asFormResponse2(submission) {
2352
+ const metadata = submission.metadata === void 0 ? void 0 : Object.fromEntries(
2353
+ Object.entries(submission.metadata).filter((entry) => entry[1] !== void 0)
2354
+ );
2262
2355
  return {
2263
2356
  responseId: submission.id,
2264
2357
  formId: submission.formId,
2265
2358
  formVersion: submission.formVersion,
2266
- sourceLocale: submission.locale,
2359
+ ...submission.locale === void 0 ? {} : { sourceLocale: submission.locale },
2267
2360
  answers: submission.values,
2268
2361
  submittedAt: submission.submittedAt,
2269
- ...submission.metadata === void 0 ? {} : { metadata: submission.metadata }
2362
+ ...metadata === void 0 ? {} : { metadata }
2270
2363
  };
2271
2364
  }
2272
2365
  async function* iterateSubmissionPages(adapter, formId, queryOptions = {}, options = {}) {
@@ -2486,6 +2579,20 @@ function matchesSubmissionPageFilters(submission, options) {
2486
2579
  );
2487
2580
  }
2488
2581
 
2582
+ // src/schemas/submission.zod.ts
2583
+ import { z } from "zod";
2584
+ var FormSubmissionMetadataSchema = z.record(z.string(), z.unknown());
2585
+ var FormSubmissionWireSchema = z.object({
2586
+ id: z.string().min(1),
2587
+ formId: z.string().min(1),
2588
+ formVersion: z.number().int().positive(),
2589
+ values: z.record(z.string(), z.unknown()),
2590
+ locale: z.string().min(1).optional(),
2591
+ metadata: FormSubmissionMetadataSchema,
2592
+ submittedAt: z.string().datetime(),
2593
+ schemaRevision: z.number().int().optional()
2594
+ });
2595
+
2489
2596
  // src/validation.ts
2490
2597
  var DEFAULT_MESSAGES = {
2491
2598
  required: "validation.required",
@@ -2666,6 +2773,33 @@ function toFormValues(answers) {
2666
2773
  function isFormValue(value) {
2667
2774
  return value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean" || Array.isArray(value) && value.every((item) => typeof item === "string");
2668
2775
  }
2776
+ function toFormSubmissionWire(submission) {
2777
+ const { id, formId, formVersion, values, locale, metadata, submittedAt, schemaRevision } = submission;
2778
+ return {
2779
+ id,
2780
+ formId,
2781
+ formVersion,
2782
+ values: { ...values },
2783
+ ...locale === void 0 ? {} : { locale },
2784
+ metadata: { ...metadata ?? {} },
2785
+ submittedAt,
2786
+ ...schemaRevision === void 0 ? {} : { schemaRevision }
2787
+ };
2788
+ }
2789
+ function fromFormSubmissionWire(wire) {
2790
+ const { id, formId, formVersion, values, locale, metadata, submittedAt, schemaRevision } = wire;
2791
+ const canonicalMetadata = { ...metadata };
2792
+ return {
2793
+ id,
2794
+ formId,
2795
+ formVersion,
2796
+ values: { ...values },
2797
+ ...locale === void 0 ? {} : { locale },
2798
+ metadata: canonicalMetadata,
2799
+ submittedAt,
2800
+ ...schemaRevision === void 0 ? {} : { schemaRevision }
2801
+ };
2802
+ }
2669
2803
  function createSubmission(schemaOrInput, values, options) {
2670
2804
  if ("answers" in schemaOrInput) {
2671
2805
  const input = schemaOrInput;
@@ -2685,7 +2819,6 @@ function createSubmission(schemaOrInput, values, options) {
2685
2819
  formVersion: input.formVersion,
2686
2820
  locale: "",
2687
2821
  values: Object.freeze(cloneValues(toFormValues(answers))),
2688
- answers,
2689
2822
  metadata: input.metadata,
2690
2823
  submittedAt,
2691
2824
  ...input.schemaRevision === void 0 ? {} : { schemaRevision: input.schemaRevision }
@@ -2712,7 +2845,6 @@ function createSubmission(schemaOrInput, values, options) {
2712
2845
  formVersion: schema.version,
2713
2846
  locale: options.locale,
2714
2847
  values: Object.freeze(cloneValues(visibleValues)),
2715
- answers: Object.freeze({ ...visibleValues }),
2716
2848
  submittedAt: options.submittedAt,
2717
2849
  ...options.metadata === void 0 ? {} : { metadata: Object.freeze({ ...options.metadata }) },
2718
2850
  ...options.translationMetadata === void 0 ? {} : { translationMetadata: Object.freeze({ ...options.translationMetadata }) }
@@ -3089,18 +3221,20 @@ var migrateSchemaTranslationMetadata = (schema, migratorOrOptions) => {
3089
3221
  };
3090
3222
  };
3091
3223
  var removeLocaleFromSchema = (schema, localeToRemove) => {
3092
- if (localeToRemove === schema.defaultLocale) {
3224
+ const normalizedLocaleToRemove = normalizeLocale(localeToRemove) ?? localeToRemove;
3225
+ const normalizedDefaultLocale = schema.defaultLocale === void 0 ? void 0 : normalizeLocale(schema.defaultLocale) ?? schema.defaultLocale;
3226
+ if (normalizedLocaleToRemove === normalizedDefaultLocale) {
3093
3227
  throw new Error(`Cannot remove defaultLocale: ${localeToRemove}`);
3094
3228
  }
3095
- const form = removeLocalizedNodeLocale(schema, localeToRemove);
3229
+ const form = removeLocalizedNodeLocale(schema, normalizedLocaleToRemove);
3096
3230
  const fields = schema.fields.map((field) => {
3097
- const localizedField = removeLocalizedNodeLocale(field, localeToRemove);
3231
+ const localizedField = removeLocalizedNodeLocale(field, normalizedLocaleToRemove);
3098
3232
  if (!("options" in localizedField)) return localizedField;
3099
3233
  return {
3100
3234
  ...localizedField,
3101
3235
  options: localizedField.options.map((option) => {
3102
- const translations = removeLocaleRecord(option.translations, localeToRemove);
3103
- const translationMetadata = removeLocaleRecord(option.translationMetadata, localeToRemove);
3236
+ const translations = removeLocaleRecord(option.translations, normalizedLocaleToRemove);
3237
+ const translationMetadata = removeLocaleRecord(option.translationMetadata, normalizedLocaleToRemove);
3104
3238
  const { translations: _translations, translationMetadata: _translationMetadata, ...base } = option;
3105
3239
  return {
3106
3240
  ...base,
@@ -3110,10 +3244,12 @@ var removeLocaleFromSchema = (schema, localeToRemove) => {
3110
3244
  })
3111
3245
  };
3112
3246
  });
3113
- const pages = schema.pages?.map((page) => removeLocalizedNodeLocale(page, localeToRemove));
3247
+ const pages = schema.pages?.map((page) => removeLocalizedNodeLocale(page, normalizedLocaleToRemove));
3114
3248
  return {
3115
3249
  ...form,
3116
- supportedLocales: (schema.supportedLocales ?? []).filter((locale) => locale !== localeToRemove),
3250
+ supportedLocales: (schema.supportedLocales ?? []).filter(
3251
+ (locale) => (normalizeLocale(locale) ?? locale) !== normalizedLocaleToRemove
3252
+ ),
3117
3253
  fields,
3118
3254
  ...pages === void 0 ? {} : { pages }
3119
3255
  };
@@ -3571,6 +3707,9 @@ async function commitVersionTransition(options) {
3571
3707
  export {
3572
3708
  DEFAULT_FIELD_TYPE_DEFINITIONS,
3573
3709
  EN_MESSAGES,
3710
+ FormSubmissionError,
3711
+ FormSubmissionMetadataSchema,
3712
+ FormSubmissionWireSchema,
3574
3713
  JA_MESSAGES,
3575
3714
  aggregateResponses,
3576
3715
  applyTransitionPlan,
@@ -3597,6 +3736,7 @@ export {
3597
3736
  decodeSubmissionCursor,
3598
3737
  decodeTextAnswerCursor,
3599
3738
  deleteDraft,
3739
+ deserializeSubmissionError,
3600
3740
  dispatchWebhook,
3601
3741
  encodeStorageSubmissionCursor,
3602
3742
  encodeStorageTextAnswerCursor,
@@ -3605,6 +3745,8 @@ export {
3605
3745
  escapeCsvCell,
3606
3746
  exportResponsesToCsv,
3607
3747
  exportResponsesToCsvStream,
3748
+ fromFormSubmissionWire,
3749
+ fromLegacyFormSubmission,
3608
3750
  getTranslationStatus,
3609
3751
  isDisplayConditionGroupSatisfied,
3610
3752
  isDisplayConditionSatisfied,
@@ -3626,6 +3768,8 @@ export {
3626
3768
  resolveLocalizedSchema,
3627
3769
  sanitizeSchema,
3628
3770
  selectVisibleAnswers,
3771
+ serializeSubmissionError,
3772
+ toFormSubmissionWire,
3629
3773
  transformFieldType,
3630
3774
  validateAnswers,
3631
3775
  validateFormSchema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@form-engine-ts/core",
3
- "version": "5.0.1",
3
+ "version": "6.0.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -38,6 +38,9 @@
38
38
  "validation",
39
39
  "typescript"
40
40
  ],
41
+ "dependencies": {
42
+ "zod": "^4.0.0"
43
+ },
41
44
  "scripts": {
42
45
  "build": "tsup src/index.ts --format esm,cjs --dts --clean",
43
46
  "check": "biome check . && tsc --noEmit",