@form-engine-ts/core 2.7.0 → 2.9.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/README.md CHANGED
@@ -79,6 +79,9 @@ Results are ordered by `submittedAt`, then submission ID. Both boundaries are in
79
79
  `cloneVersionToDraft`, asynchronous `publishDraft`, and `deleteDraft` implement revision-checked version transitions as
80
80
  pure functions. `createCloneTransitionPlan`, `createPublishTransitionPlan`, and `createDeleteDraftTransitionPlan` produce
81
81
  complete persistence plans with the next state, affected records, and immutable audit events.
82
+ Publishing when state already identifies a Published version requires its matching `currentPublishedRecord`; omission or
83
+ a version mismatch returns the typed `missing_published_record` error. The resulting archive preserves the original
84
+ schema, creation timestamp, and metadata.
82
85
  Clone/delete operations accept `expectedRevision`; cloning rejects non-published sources, publish validation failures are
83
86
  returned as typed `validation_failed` issues, and successful publishing archives only a supplied actual published record,
84
87
  preserving its schema and metadata. `createPublishTransitionPlan` returns complete records plus expected/next revisions for
@@ -99,5 +102,11 @@ cursor combines `submittedAt` and response ID, so equal timestamps do not produc
99
102
  and `filter` are applied before page sizing. `filter` accepts a composable `eq`/`in`/`range`/`exists` and/or AST; adapters
100
103
  may push supported nodes to their native query language while preserving identical client-side semantics. Adapters that
101
104
  implement `listTextAnswerPage` expose stable cursor pagination over individual text answers.
105
+ `TextAnswerPageQueryOptions.fieldIds` can select multiple free-text fields for item-level paging.
106
+
107
+ `iterateSubmissionPages(adapter, formId, query, options)` safely traverses every page as an async generator. It supports
108
+ `pageSize`, `maxItems`, and `AbortSignal`, continues through empty pages with a next cursor, and rejects missing or cyclic
109
+ cursors instead of looping indefinitely. Publish transition validation also rejects mismatched form IDs, non-Published
110
+ records, and an unexpected current record with typed errors.
102
111
 
103
112
  See the [project documentation](https://github.com/nitta-a/form-engine-ts#readme) for the complete schema and API guide.
package/dist/index.cjs CHANGED
@@ -46,6 +46,7 @@ __export(index_exports, {
46
46
  exportResponsesToCsvStream: () => exportResponsesToCsvStream,
47
47
  isDisplayConditionSatisfied: () => isDisplayConditionSatisfied,
48
48
  isQuestionVisible: () => isQuestionVisible,
49
+ iterateSubmissionPages: () => iterateSubmissionPages,
49
50
  jsonValuesEqual: () => jsonValuesEqual,
50
51
  matchesSubmissionFilter: () => matchesSubmissionFilter,
51
52
  matchesSubmissionPageFilters: () => matchesSubmissionPageFilters,
@@ -1523,6 +1524,54 @@ function transformFieldType(field, nextType) {
1523
1524
  }
1524
1525
 
1525
1526
  // src/pagination.ts
1527
+ function asFormResponse2(submission) {
1528
+ return {
1529
+ responseId: submission.id,
1530
+ formId: submission.formId,
1531
+ formVersion: submission.formVersion,
1532
+ sourceLocale: submission.locale,
1533
+ answers: submission.values,
1534
+ submittedAt: submission.submittedAt,
1535
+ ...submission.metadata === void 0 ? {} : { metadata: submission.metadata }
1536
+ };
1537
+ }
1538
+ async function* iterateSubmissionPages(adapter, formId, queryOptions = {}, options = {}) {
1539
+ const pageSize = normalizeSubmissionPageSize(options.pageSize ?? queryOptions.pageSize);
1540
+ const maxItems = options.maxItems ?? Number.POSITIVE_INFINITY;
1541
+ if (maxItems !== Number.POSITIVE_INFINITY && (!Number.isSafeInteger(maxItems) || maxItems < 0)) {
1542
+ throw new TypeError("maxItems must be a non-negative safe integer.");
1543
+ }
1544
+ if (maxItems === 0) return;
1545
+ const seenCursors = /* @__PURE__ */ new Set();
1546
+ let cursor = queryOptions.cursor;
1547
+ const { cursor: _initialCursor, pageSize: _initialPageSize, ...baseQueryOptions } = queryOptions;
1548
+ if (cursor !== void 0) seenCursors.add(cursor);
1549
+ let emitted = 0;
1550
+ while (emitted < maxItems) {
1551
+ options.signal?.throwIfAborted();
1552
+ const remaining = maxItems - emitted;
1553
+ const requestedPageSize = Number.isFinite(remaining) ? Math.min(pageSize, remaining) : pageSize;
1554
+ const page = await adapter.listSubmissionPage(formId, {
1555
+ ...baseQueryOptions,
1556
+ pageSize: requestedPageSize,
1557
+ ...cursor === void 0 ? {} : { cursor }
1558
+ });
1559
+ options.signal?.throwIfAborted();
1560
+ const available = Number.isFinite(remaining) ? page.items.slice(0, remaining) : page.items;
1561
+ if (available.length > 0) {
1562
+ emitted += available.length;
1563
+ yield available.map(asFormResponse2);
1564
+ }
1565
+ if (!page.hasMore || emitted >= maxItems) return;
1566
+ const nextCursor = page.nextCursor;
1567
+ if (nextCursor === void 0 || nextCursor.length === 0) {
1568
+ throw new Error("Submission pagination returned hasMore without a next cursor.");
1569
+ }
1570
+ if (seenCursors.has(nextCursor)) throw new Error(`Submission pagination cursor cycle detected: ${nextCursor}`);
1571
+ seenCursors.add(nextCursor);
1572
+ cursor = nextCursor;
1573
+ }
1574
+ }
1526
1575
  var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1527
1576
  function encodeBase64(bytes) {
1528
1577
  let result = "";
@@ -2203,10 +2252,26 @@ function createCloneTransitionPlan(state, sourceRecord, options = {}) {
2203
2252
  };
2204
2253
  }
2205
2254
  function validatePublishedRecord(state, record) {
2206
- if (record === void 0) return;
2207
- if (record.formId !== state.formId || record.version !== state.publishedVersion || record.status !== "published" || record.schema.id !== record.formId || record.schema.version !== record.version) {
2255
+ if (state.publishedVersion === void 0) {
2256
+ return record === void 0 ? void 0 : { success: false, error: { type: "unexpected_published_record" } };
2257
+ }
2258
+ if (record !== void 0 && record.formId !== state.formId) {
2259
+ return { success: false, error: { type: "form_id_mismatch" } };
2260
+ }
2261
+ if (record !== void 0 && record.status !== "published") {
2262
+ return { success: false, error: { type: "invalid_published_status" } };
2263
+ }
2264
+ if (record?.version !== state.publishedVersion) {
2265
+ return {
2266
+ success: false,
2267
+ error: { type: "missing_published_record", expectedVersion: state.publishedVersion }
2268
+ };
2269
+ }
2270
+ if (record === void 0) return void 0;
2271
+ if (record.schema.id !== record.formId || record.schema.version !== record.version) {
2208
2272
  throw new TypeError("currentPublishedRecord must match the state's published version.");
2209
2273
  }
2274
+ return void 0;
2210
2275
  }
2211
2276
  function transitionTimestamp(options) {
2212
2277
  return requireTimestamp(options.publishedAt ?? options.timestamp, "publishedAt");
@@ -2218,7 +2283,8 @@ async function publishDraft(state, draftSchema, options = {}) {
2218
2283
  if (state.draftVersion === void 0 || draftSchema.id !== state.formId || draftSchema.version !== state.draftVersion) {
2219
2284
  return { success: false, error: { type: "draft_not_found" } };
2220
2285
  }
2221
- validatePublishedRecord(state, options.currentPublishedRecord);
2286
+ const publishedRecordError = validatePublishedRecord(state, options.currentPublishedRecord);
2287
+ if (publishedRecordError !== void 0) return publishedRecordError;
2222
2288
  const validation = await options.validate?.(draftSchema);
2223
2289
  if (validation === false || Array.isArray(validation) && validation.length > 0) {
2224
2290
  return {
@@ -2378,6 +2444,7 @@ function assertVersionMutable(status) {
2378
2444
  exportResponsesToCsvStream,
2379
2445
  isDisplayConditionSatisfied,
2380
2446
  isQuestionVisible,
2447
+ iterateSubmissionPages,
2381
2448
  jsonValuesEqual,
2382
2449
  matchesSubmissionFilter,
2383
2450
  matchesSubmissionPageFilters,
package/dist/index.d.cts CHANGED
@@ -37,6 +37,15 @@ type VersionTransitionError = {
37
37
  readonly currentDraftVersion: number;
38
38
  } | {
39
39
  readonly type: "draft_not_found";
40
+ } | {
41
+ readonly type: "missing_published_record";
42
+ readonly expectedVersion: number;
43
+ } | {
44
+ readonly type: "form_id_mismatch";
45
+ } | {
46
+ readonly type: "invalid_published_status";
47
+ } | {
48
+ readonly type: "unexpected_published_record";
40
49
  } | {
41
50
  readonly type: "revision_conflict";
42
51
  readonly expectedRevision: number;
@@ -280,6 +289,9 @@ interface SubmissionPageQueryOptions {
280
289
  /** @deprecated Prefer the generic filter AST. */
281
290
  readonly metadataFilters?: Readonly<Record<string, JsonValue>>;
282
291
  }
292
+ interface TextAnswerPageQueryOptions extends SubmissionPageQueryOptions {
293
+ readonly fieldIds?: readonly string[];
294
+ }
283
295
  type SubmissionFilter = {
284
296
  readonly op: "eq";
285
297
  readonly path: string;
@@ -311,7 +323,7 @@ interface SubmissionPage {
311
323
  }
312
324
  interface PagedSubmissionStorageAdapter extends FormStorageAdapter {
313
325
  listSubmissionPage(formId: string, options?: SubmissionPageQueryOptions): Promise<SubmissionPage>;
314
- listTextAnswerPage?(formId: string, fieldId: string, options?: SubmissionPageQueryOptions): Promise<TextAnswerPage>;
326
+ listTextAnswerPage?(formId: string, fieldIdOrOptions?: string | TextAnswerPageQueryOptions, options?: TextAnswerPageQueryOptions): Promise<TextAnswerPage>;
315
327
  }
316
328
  interface TextAnswerItem {
317
329
  readonly responseId: string;
@@ -348,6 +360,8 @@ type StorageCommitError = {
348
360
  } | {
349
361
  readonly type: "draft_already_exists";
350
362
  readonly currentDraftVersion: number;
363
+ } | {
364
+ readonly type: "transaction_unsupported";
351
365
  } | {
352
366
  readonly type: "invalid_transition";
353
367
  readonly message: string;
@@ -517,6 +531,12 @@ declare function dispatchWebhook<T>(event: FormEvent<T>, config: WebhookConfig,
517
531
  */
518
532
  declare function transformFieldType(field: FormField, nextType: QuestionType): FormField;
519
533
 
534
+ interface PaginationIteratorOptions {
535
+ readonly pageSize?: number;
536
+ readonly maxItems?: number;
537
+ readonly signal?: AbortSignal;
538
+ }
539
+ declare function iterateSubmissionPages(adapter: PagedSubmissionStorageAdapter, formId: string, queryOptions?: SubmissionPageQueryOptions, options?: PaginationIteratorOptions): AsyncIterable<readonly FormResponse[]>;
520
540
  interface SubmissionCursorValue {
521
541
  readonly submittedAt: string;
522
542
  readonly responseId: string;
@@ -607,4 +627,4 @@ declare function calculatePageVisibility(schema: FormSchema, currentAnswers: Rea
607
627
  declare function calculateFieldVisibility(schema: FormSchema, currentAnswers: Readonly<Record<string, unknown>>): Readonly<Record<string, boolean>>;
608
628
  declare function selectVisibleAnswers(schema: FormSchema, currentAnswers: FormValues): FormValues;
609
629
 
610
- export { type AccumulatorReport, type AccumulatorResponse, type AccumulatorSkipReason, type AnswerValidationResult, type AsyncTranslationAdapter, type BaseField, type CheckboxField, type CheckboxQuestionAggregate, type ChoiceDistributionEntry, type ChoiceOption, type ChoiceQuestionAggregate, type CloneVersionOptions, type CollectedLocales, type ConditionOperator, type ConditionValue, type CreateSubmissionOptions, type CrossTabulationResult, type CsvColumnContext, type CsvColumnDef, type CsvExportOptions, type DeleteDraftOptions, type DisplayCondition, type ExtensibleNode, type FieldOption, type FieldType, type FormAnalytics, type FormEvent, type FormEventType, type FormField, type FormPage, type FormPolicy, type FormResponse, type FormSchema, type FormStorageAdapter, type FormSubmission, type FormValue, type FormValues, type FormVersionRecord, type FormVersionState, type FormVersionStatus, type JsonValue, type LocalizedText, type MultiSelectField, type NodeWritableStream, type NumberField, type NumberQuestionAggregate, type NumericSummary, type OptionAggregate, type PagedSubmissionStorageAdapter, type PopulateTranslationOptions, type PublishDraftOptions, type PublishDraftResult, type Question, type QuestionAggregate, type QuestionType, type RatingField, type ResponseAccumulator, type ResponseAccumulatorOptions, type Result, type SchemaIssue, type SchemaStructureIssue, type SchemaStructureIssueType, type SchemaTranslations, type SchemaValidationResult, type SelectField, type StorageAdapter, type StorageCommitError, type StreamCsvOptions, type SubmissionCursorValue, type SubmissionFilter, type SubmissionPage, type SubmissionPageQueryOptions, type SubmissionQueryOptions, type TextAnswerCursorValue, type TextAnswerItem, type TextAnswerPage, type TextField, type TextQuestionAggregate, type TranslationAdapter, type TranslationReport, type TranslationSlot, type ValidateFormSchemaOptions, type ValidationCode, type ValidationError, type ValidationIssue, type VersionTransitionError, type VersionTransitionEvent, type VersionTransitionPlan, type VersionedFormStorageAdapter, type WebhookConfig, type WebhookDispatchResult, aggregateResponses, assertValidFormSchema, assertVersionMutable, calculateChoiceDistribution, calculateCrossTabulation, calculateFieldVisibility, calculateNumericSummary, calculatePageVisibility, cloneVersionToDraft, collectSchemaLocales, createCloneTransitionPlan, createDeleteDraftTransitionPlan, createPublishTransitionPlan, createResponseAccumulator, createSubmission, decodeSubmissionCursor, decodeTextAnswerCursor, deleteDraft, dispatchWebhook, encodeSubmissionCursor, encodeTextAnswerCursor, escapeCsvCell, exportResponsesToCsv, exportResponsesToCsvStream, isDisplayConditionSatisfied, isQuestionVisible, jsonValuesEqual, matchesSubmissionFilter, matchesSubmissionPageFilters, normalizeSubmissionPageSize, pipeResponsesToCsvStream, populateSchemaTranslations, publishDraft, resolveFormTranslation, resolveLocalizedSchema, sanitizeSchema, selectVisibleAnswers, transformFieldType, validateAnswers, validateFormSchema, validatePageAnswers, validateSchemaStructure };
630
+ export { type AccumulatorReport, type AccumulatorResponse, type AccumulatorSkipReason, type AnswerValidationResult, type AsyncTranslationAdapter, type BaseField, type CheckboxField, type CheckboxQuestionAggregate, type ChoiceDistributionEntry, type ChoiceOption, type ChoiceQuestionAggregate, type CloneVersionOptions, type CollectedLocales, type ConditionOperator, type ConditionValue, type CreateSubmissionOptions, type CrossTabulationResult, type CsvColumnContext, type CsvColumnDef, type CsvExportOptions, type DeleteDraftOptions, type DisplayCondition, type ExtensibleNode, type FieldOption, type FieldType, type FormAnalytics, type FormEvent, type FormEventType, type FormField, type FormPage, type FormPolicy, type FormResponse, type FormSchema, type FormStorageAdapter, type FormSubmission, type FormValue, type FormValues, type FormVersionRecord, type FormVersionState, type FormVersionStatus, type JsonValue, type LocalizedText, type MultiSelectField, type NodeWritableStream, type NumberField, type NumberQuestionAggregate, type NumericSummary, type OptionAggregate, type PagedSubmissionStorageAdapter, type PaginationIteratorOptions, type PopulateTranslationOptions, type PublishDraftOptions, type PublishDraftResult, type Question, type QuestionAggregate, type QuestionType, type RatingField, type ResponseAccumulator, type ResponseAccumulatorOptions, type Result, type SchemaIssue, type SchemaStructureIssue, type SchemaStructureIssueType, type SchemaTranslations, type SchemaValidationResult, type SelectField, type StorageAdapter, type StorageCommitError, type StreamCsvOptions, type SubmissionCursorValue, type SubmissionFilter, type SubmissionPage, type SubmissionPageQueryOptions, type SubmissionQueryOptions, type TextAnswerCursorValue, type TextAnswerItem, type TextAnswerPage, type TextAnswerPageQueryOptions, type TextField, type TextQuestionAggregate, type TranslationAdapter, type TranslationReport, type TranslationSlot, type ValidateFormSchemaOptions, type ValidationCode, type ValidationError, type ValidationIssue, type VersionTransitionError, type VersionTransitionEvent, type VersionTransitionPlan, type VersionedFormStorageAdapter, type WebhookConfig, type WebhookDispatchResult, aggregateResponses, assertValidFormSchema, assertVersionMutable, calculateChoiceDistribution, calculateCrossTabulation, calculateFieldVisibility, calculateNumericSummary, calculatePageVisibility, cloneVersionToDraft, collectSchemaLocales, createCloneTransitionPlan, createDeleteDraftTransitionPlan, createPublishTransitionPlan, createResponseAccumulator, createSubmission, decodeSubmissionCursor, decodeTextAnswerCursor, deleteDraft, dispatchWebhook, encodeSubmissionCursor, encodeTextAnswerCursor, escapeCsvCell, exportResponsesToCsv, exportResponsesToCsvStream, isDisplayConditionSatisfied, isQuestionVisible, iterateSubmissionPages, jsonValuesEqual, matchesSubmissionFilter, matchesSubmissionPageFilters, normalizeSubmissionPageSize, pipeResponsesToCsvStream, populateSchemaTranslations, publishDraft, resolveFormTranslation, resolveLocalizedSchema, sanitizeSchema, selectVisibleAnswers, transformFieldType, validateAnswers, validateFormSchema, validatePageAnswers, validateSchemaStructure };
package/dist/index.d.ts CHANGED
@@ -37,6 +37,15 @@ type VersionTransitionError = {
37
37
  readonly currentDraftVersion: number;
38
38
  } | {
39
39
  readonly type: "draft_not_found";
40
+ } | {
41
+ readonly type: "missing_published_record";
42
+ readonly expectedVersion: number;
43
+ } | {
44
+ readonly type: "form_id_mismatch";
45
+ } | {
46
+ readonly type: "invalid_published_status";
47
+ } | {
48
+ readonly type: "unexpected_published_record";
40
49
  } | {
41
50
  readonly type: "revision_conflict";
42
51
  readonly expectedRevision: number;
@@ -280,6 +289,9 @@ interface SubmissionPageQueryOptions {
280
289
  /** @deprecated Prefer the generic filter AST. */
281
290
  readonly metadataFilters?: Readonly<Record<string, JsonValue>>;
282
291
  }
292
+ interface TextAnswerPageQueryOptions extends SubmissionPageQueryOptions {
293
+ readonly fieldIds?: readonly string[];
294
+ }
283
295
  type SubmissionFilter = {
284
296
  readonly op: "eq";
285
297
  readonly path: string;
@@ -311,7 +323,7 @@ interface SubmissionPage {
311
323
  }
312
324
  interface PagedSubmissionStorageAdapter extends FormStorageAdapter {
313
325
  listSubmissionPage(formId: string, options?: SubmissionPageQueryOptions): Promise<SubmissionPage>;
314
- listTextAnswerPage?(formId: string, fieldId: string, options?: SubmissionPageQueryOptions): Promise<TextAnswerPage>;
326
+ listTextAnswerPage?(formId: string, fieldIdOrOptions?: string | TextAnswerPageQueryOptions, options?: TextAnswerPageQueryOptions): Promise<TextAnswerPage>;
315
327
  }
316
328
  interface TextAnswerItem {
317
329
  readonly responseId: string;
@@ -348,6 +360,8 @@ type StorageCommitError = {
348
360
  } | {
349
361
  readonly type: "draft_already_exists";
350
362
  readonly currentDraftVersion: number;
363
+ } | {
364
+ readonly type: "transaction_unsupported";
351
365
  } | {
352
366
  readonly type: "invalid_transition";
353
367
  readonly message: string;
@@ -517,6 +531,12 @@ declare function dispatchWebhook<T>(event: FormEvent<T>, config: WebhookConfig,
517
531
  */
518
532
  declare function transformFieldType(field: FormField, nextType: QuestionType): FormField;
519
533
 
534
+ interface PaginationIteratorOptions {
535
+ readonly pageSize?: number;
536
+ readonly maxItems?: number;
537
+ readonly signal?: AbortSignal;
538
+ }
539
+ declare function iterateSubmissionPages(adapter: PagedSubmissionStorageAdapter, formId: string, queryOptions?: SubmissionPageQueryOptions, options?: PaginationIteratorOptions): AsyncIterable<readonly FormResponse[]>;
520
540
  interface SubmissionCursorValue {
521
541
  readonly submittedAt: string;
522
542
  readonly responseId: string;
@@ -607,4 +627,4 @@ declare function calculatePageVisibility(schema: FormSchema, currentAnswers: Rea
607
627
  declare function calculateFieldVisibility(schema: FormSchema, currentAnswers: Readonly<Record<string, unknown>>): Readonly<Record<string, boolean>>;
608
628
  declare function selectVisibleAnswers(schema: FormSchema, currentAnswers: FormValues): FormValues;
609
629
 
610
- export { type AccumulatorReport, type AccumulatorResponse, type AccumulatorSkipReason, type AnswerValidationResult, type AsyncTranslationAdapter, type BaseField, type CheckboxField, type CheckboxQuestionAggregate, type ChoiceDistributionEntry, type ChoiceOption, type ChoiceQuestionAggregate, type CloneVersionOptions, type CollectedLocales, type ConditionOperator, type ConditionValue, type CreateSubmissionOptions, type CrossTabulationResult, type CsvColumnContext, type CsvColumnDef, type CsvExportOptions, type DeleteDraftOptions, type DisplayCondition, type ExtensibleNode, type FieldOption, type FieldType, type FormAnalytics, type FormEvent, type FormEventType, type FormField, type FormPage, type FormPolicy, type FormResponse, type FormSchema, type FormStorageAdapter, type FormSubmission, type FormValue, type FormValues, type FormVersionRecord, type FormVersionState, type FormVersionStatus, type JsonValue, type LocalizedText, type MultiSelectField, type NodeWritableStream, type NumberField, type NumberQuestionAggregate, type NumericSummary, type OptionAggregate, type PagedSubmissionStorageAdapter, type PopulateTranslationOptions, type PublishDraftOptions, type PublishDraftResult, type Question, type QuestionAggregate, type QuestionType, type RatingField, type ResponseAccumulator, type ResponseAccumulatorOptions, type Result, type SchemaIssue, type SchemaStructureIssue, type SchemaStructureIssueType, type SchemaTranslations, type SchemaValidationResult, type SelectField, type StorageAdapter, type StorageCommitError, type StreamCsvOptions, type SubmissionCursorValue, type SubmissionFilter, type SubmissionPage, type SubmissionPageQueryOptions, type SubmissionQueryOptions, type TextAnswerCursorValue, type TextAnswerItem, type TextAnswerPage, type TextField, type TextQuestionAggregate, type TranslationAdapter, type TranslationReport, type TranslationSlot, type ValidateFormSchemaOptions, type ValidationCode, type ValidationError, type ValidationIssue, type VersionTransitionError, type VersionTransitionEvent, type VersionTransitionPlan, type VersionedFormStorageAdapter, type WebhookConfig, type WebhookDispatchResult, aggregateResponses, assertValidFormSchema, assertVersionMutable, calculateChoiceDistribution, calculateCrossTabulation, calculateFieldVisibility, calculateNumericSummary, calculatePageVisibility, cloneVersionToDraft, collectSchemaLocales, createCloneTransitionPlan, createDeleteDraftTransitionPlan, createPublishTransitionPlan, createResponseAccumulator, createSubmission, decodeSubmissionCursor, decodeTextAnswerCursor, deleteDraft, dispatchWebhook, encodeSubmissionCursor, encodeTextAnswerCursor, escapeCsvCell, exportResponsesToCsv, exportResponsesToCsvStream, isDisplayConditionSatisfied, isQuestionVisible, jsonValuesEqual, matchesSubmissionFilter, matchesSubmissionPageFilters, normalizeSubmissionPageSize, pipeResponsesToCsvStream, populateSchemaTranslations, publishDraft, resolveFormTranslation, resolveLocalizedSchema, sanitizeSchema, selectVisibleAnswers, transformFieldType, validateAnswers, validateFormSchema, validatePageAnswers, validateSchemaStructure };
630
+ export { type AccumulatorReport, type AccumulatorResponse, type AccumulatorSkipReason, type AnswerValidationResult, type AsyncTranslationAdapter, type BaseField, type CheckboxField, type CheckboxQuestionAggregate, type ChoiceDistributionEntry, type ChoiceOption, type ChoiceQuestionAggregate, type CloneVersionOptions, type CollectedLocales, type ConditionOperator, type ConditionValue, type CreateSubmissionOptions, type CrossTabulationResult, type CsvColumnContext, type CsvColumnDef, type CsvExportOptions, type DeleteDraftOptions, type DisplayCondition, type ExtensibleNode, type FieldOption, type FieldType, type FormAnalytics, type FormEvent, type FormEventType, type FormField, type FormPage, type FormPolicy, type FormResponse, type FormSchema, type FormStorageAdapter, type FormSubmission, type FormValue, type FormValues, type FormVersionRecord, type FormVersionState, type FormVersionStatus, type JsonValue, type LocalizedText, type MultiSelectField, type NodeWritableStream, type NumberField, type NumberQuestionAggregate, type NumericSummary, type OptionAggregate, type PagedSubmissionStorageAdapter, type PaginationIteratorOptions, type PopulateTranslationOptions, type PublishDraftOptions, type PublishDraftResult, type Question, type QuestionAggregate, type QuestionType, type RatingField, type ResponseAccumulator, type ResponseAccumulatorOptions, type Result, type SchemaIssue, type SchemaStructureIssue, type SchemaStructureIssueType, type SchemaTranslations, type SchemaValidationResult, type SelectField, type StorageAdapter, type StorageCommitError, type StreamCsvOptions, type SubmissionCursorValue, type SubmissionFilter, type SubmissionPage, type SubmissionPageQueryOptions, type SubmissionQueryOptions, type TextAnswerCursorValue, type TextAnswerItem, type TextAnswerPage, type TextAnswerPageQueryOptions, type TextField, type TextQuestionAggregate, type TranslationAdapter, type TranslationReport, type TranslationSlot, type ValidateFormSchemaOptions, type ValidationCode, type ValidationError, type ValidationIssue, type VersionTransitionError, type VersionTransitionEvent, type VersionTransitionPlan, type VersionedFormStorageAdapter, type WebhookConfig, type WebhookDispatchResult, aggregateResponses, assertValidFormSchema, assertVersionMutable, calculateChoiceDistribution, calculateCrossTabulation, calculateFieldVisibility, calculateNumericSummary, calculatePageVisibility, cloneVersionToDraft, collectSchemaLocales, createCloneTransitionPlan, createDeleteDraftTransitionPlan, createPublishTransitionPlan, createResponseAccumulator, createSubmission, decodeSubmissionCursor, decodeTextAnswerCursor, deleteDraft, dispatchWebhook, encodeSubmissionCursor, encodeTextAnswerCursor, escapeCsvCell, exportResponsesToCsv, exportResponsesToCsvStream, isDisplayConditionSatisfied, isQuestionVisible, iterateSubmissionPages, jsonValuesEqual, matchesSubmissionFilter, matchesSubmissionPageFilters, normalizeSubmissionPageSize, pipeResponsesToCsvStream, populateSchemaTranslations, publishDraft, resolveFormTranslation, resolveLocalizedSchema, sanitizeSchema, selectVisibleAnswers, transformFieldType, validateAnswers, validateFormSchema, validatePageAnswers, validateSchemaStructure };
package/dist/index.js CHANGED
@@ -1456,6 +1456,54 @@ function transformFieldType(field, nextType) {
1456
1456
  }
1457
1457
 
1458
1458
  // src/pagination.ts
1459
+ function asFormResponse2(submission) {
1460
+ return {
1461
+ responseId: submission.id,
1462
+ formId: submission.formId,
1463
+ formVersion: submission.formVersion,
1464
+ sourceLocale: submission.locale,
1465
+ answers: submission.values,
1466
+ submittedAt: submission.submittedAt,
1467
+ ...submission.metadata === void 0 ? {} : { metadata: submission.metadata }
1468
+ };
1469
+ }
1470
+ async function* iterateSubmissionPages(adapter, formId, queryOptions = {}, options = {}) {
1471
+ const pageSize = normalizeSubmissionPageSize(options.pageSize ?? queryOptions.pageSize);
1472
+ const maxItems = options.maxItems ?? Number.POSITIVE_INFINITY;
1473
+ if (maxItems !== Number.POSITIVE_INFINITY && (!Number.isSafeInteger(maxItems) || maxItems < 0)) {
1474
+ throw new TypeError("maxItems must be a non-negative safe integer.");
1475
+ }
1476
+ if (maxItems === 0) return;
1477
+ const seenCursors = /* @__PURE__ */ new Set();
1478
+ let cursor = queryOptions.cursor;
1479
+ const { cursor: _initialCursor, pageSize: _initialPageSize, ...baseQueryOptions } = queryOptions;
1480
+ if (cursor !== void 0) seenCursors.add(cursor);
1481
+ let emitted = 0;
1482
+ while (emitted < maxItems) {
1483
+ options.signal?.throwIfAborted();
1484
+ const remaining = maxItems - emitted;
1485
+ const requestedPageSize = Number.isFinite(remaining) ? Math.min(pageSize, remaining) : pageSize;
1486
+ const page = await adapter.listSubmissionPage(formId, {
1487
+ ...baseQueryOptions,
1488
+ pageSize: requestedPageSize,
1489
+ ...cursor === void 0 ? {} : { cursor }
1490
+ });
1491
+ options.signal?.throwIfAborted();
1492
+ const available = Number.isFinite(remaining) ? page.items.slice(0, remaining) : page.items;
1493
+ if (available.length > 0) {
1494
+ emitted += available.length;
1495
+ yield available.map(asFormResponse2);
1496
+ }
1497
+ if (!page.hasMore || emitted >= maxItems) return;
1498
+ const nextCursor = page.nextCursor;
1499
+ if (nextCursor === void 0 || nextCursor.length === 0) {
1500
+ throw new Error("Submission pagination returned hasMore without a next cursor.");
1501
+ }
1502
+ if (seenCursors.has(nextCursor)) throw new Error(`Submission pagination cursor cycle detected: ${nextCursor}`);
1503
+ seenCursors.add(nextCursor);
1504
+ cursor = nextCursor;
1505
+ }
1506
+ }
1459
1507
  var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1460
1508
  function encodeBase64(bytes) {
1461
1509
  let result = "";
@@ -2136,10 +2184,26 @@ function createCloneTransitionPlan(state, sourceRecord, options = {}) {
2136
2184
  };
2137
2185
  }
2138
2186
  function validatePublishedRecord(state, record) {
2139
- if (record === void 0) return;
2140
- if (record.formId !== state.formId || record.version !== state.publishedVersion || record.status !== "published" || record.schema.id !== record.formId || record.schema.version !== record.version) {
2187
+ if (state.publishedVersion === void 0) {
2188
+ return record === void 0 ? void 0 : { success: false, error: { type: "unexpected_published_record" } };
2189
+ }
2190
+ if (record !== void 0 && record.formId !== state.formId) {
2191
+ return { success: false, error: { type: "form_id_mismatch" } };
2192
+ }
2193
+ if (record !== void 0 && record.status !== "published") {
2194
+ return { success: false, error: { type: "invalid_published_status" } };
2195
+ }
2196
+ if (record?.version !== state.publishedVersion) {
2197
+ return {
2198
+ success: false,
2199
+ error: { type: "missing_published_record", expectedVersion: state.publishedVersion }
2200
+ };
2201
+ }
2202
+ if (record === void 0) return void 0;
2203
+ if (record.schema.id !== record.formId || record.schema.version !== record.version) {
2141
2204
  throw new TypeError("currentPublishedRecord must match the state's published version.");
2142
2205
  }
2206
+ return void 0;
2143
2207
  }
2144
2208
  function transitionTimestamp(options) {
2145
2209
  return requireTimestamp(options.publishedAt ?? options.timestamp, "publishedAt");
@@ -2151,7 +2215,8 @@ async function publishDraft(state, draftSchema, options = {}) {
2151
2215
  if (state.draftVersion === void 0 || draftSchema.id !== state.formId || draftSchema.version !== state.draftVersion) {
2152
2216
  return { success: false, error: { type: "draft_not_found" } };
2153
2217
  }
2154
- validatePublishedRecord(state, options.currentPublishedRecord);
2218
+ const publishedRecordError = validatePublishedRecord(state, options.currentPublishedRecord);
2219
+ if (publishedRecordError !== void 0) return publishedRecordError;
2155
2220
  const validation = await options.validate?.(draftSchema);
2156
2221
  if (validation === false || Array.isArray(validation) && validation.length > 0) {
2157
2222
  return {
@@ -2310,6 +2375,7 @@ export {
2310
2375
  exportResponsesToCsvStream,
2311
2376
  isDisplayConditionSatisfied,
2312
2377
  isQuestionVisible,
2378
+ iterateSubmissionPages,
2313
2379
  jsonValuesEqual,
2314
2380
  matchesSubmissionFilter,
2315
2381
  matchesSubmissionPageFilters,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@form-engine-ts/core",
3
- "version": "2.7.0",
3
+ "version": "2.9.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },