@form-engine-ts/storage-azure-table 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/README.md CHANGED
@@ -42,3 +42,7 @@ and field indexes, so a page can resume inside a multi-answer entity without gap
42
42
  consume the item limit, and scanning remains bounded by `maxScanPages`. Cursor format version 1 also records the form,
43
43
  version, sorted fields, and a SHA-256 filter fingerprint. Reusing a cursor with different query context throws
44
44
  `invalid_cursor_context`.
45
+
46
+ `createLegacyAzureTableCodec()` decodes older entities using `PartitionKey`, `RowKey`, `answers`, `answeredAt`, and
47
+ `surveyVersion` into the canonical `FormSubmission` shape. Custom partition- and row-key generators can be supplied
48
+ for migration tooling.
package/dist/index.cjs CHANGED
@@ -21,12 +21,36 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  createAzureTableStorage: () => createAzureTableStorage,
24
+ createLegacyAzureTableCodec: () => createLegacyAzureTableCodec,
24
25
  defaultAzureTableSubmissionCodec: () => defaultAzureTableSubmissionCodec,
25
26
  metadataFiltersToOData: () => metadataFiltersToOData,
26
27
  submissionFilterToOData: () => submissionFilterToOData
27
28
  });
28
29
  module.exports = __toCommonJS(index_exports);
29
30
  var import_core = require("@form-engine-ts/core");
31
+ var createLegacyAzureTableCodec = (options = {}) => {
32
+ const createPartitionKey = options.partitionKeyGenerator ?? ((formId) => formId);
33
+ const createRowKey = options.rowKeyGenerator ?? ((submittedAt, submissionId) => `${submittedAt}_${submissionId}`);
34
+ return {
35
+ decode: (entity) => {
36
+ const values = entity.answers === void 0 ? {} : parseLegacyJsonObject(entity.answers, "answers");
37
+ const submittedAt = entity.answeredAt ?? entity.Timestamp;
38
+ if (submittedAt === void 0 || submittedAt.trim().length === 0) {
39
+ throw new Error("Azure Table legacy submission is missing answeredAt or Timestamp.");
40
+ }
41
+ return {
42
+ id: entity.RowKey,
43
+ formId: entity.PartitionKey,
44
+ formVersion: entity.surveyVersion ?? 1,
45
+ values,
46
+ metadata: {},
47
+ submittedAt
48
+ };
49
+ },
50
+ createPartitionKey: (formId, submissionId) => createPartitionKey(formId, submissionId),
51
+ createRowKey: (submittedAt, submissionId) => createRowKey(submittedAt, submissionId)
52
+ };
53
+ };
30
54
  var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
31
55
  function encodeBase64(bytes) {
32
56
  let result = "";
@@ -135,6 +159,11 @@ function parseJson(value, location) {
135
159
  throw new Error(`Azure Table ${location} payload is invalid.`, { cause });
136
160
  }
137
161
  }
162
+ function parseLegacyJsonObject(value, property) {
163
+ const parsed = parseJson(value, `legacy ${property}`);
164
+ if (!isRecord(parsed)) throw new Error(`Azure Table legacy ${property} payload must be an object.`);
165
+ return parsed;
166
+ }
138
167
  function parseSubmission(value, location) {
139
168
  const parsed = typeof value === "string" ? parseJson(value, location) : value;
140
169
  if (!isRecord(parsed) || typeof parsed.id !== "string" || typeof parsed.formId !== "string" || !Number.isInteger(parsed.formVersion) || typeof parsed.locale !== "string" || typeof parsed.submittedAt !== "string" || !isRecord(parsed.values) || !Object.values(parsed.values).every(isFormValue)) {
@@ -142,6 +171,60 @@ function parseSubmission(value, location) {
142
171
  }
143
172
  return cloneJson(parsed);
144
173
  }
174
+ function propertyName(mapping, logicalName, fallback) {
175
+ return mapping?.customPropertyMappings?.[logicalName] ?? (logicalName === "formId" ? mapping?.formId : void 0) ?? (logicalName === "formVersion" ? mapping?.formVersion : void 0) ?? (logicalName === "submittedAt" ? mapping?.submittedAt : void 0) ?? (logicalName === "values" ? mapping?.values : void 0) ?? (logicalName === "metadata" ? mapping?.metadata : void 0) ?? fallback;
176
+ }
177
+ function physicalKeyNames(mapping) {
178
+ return {
179
+ partitionKey: mapping?.partitionKeyProperty ?? "PartitionKey",
180
+ rowKey: mapping?.rowKeyProperty ?? "RowKey"
181
+ };
182
+ }
183
+ function entityKey(entity, property, compatibilityProperty) {
184
+ return entity[property] ?? entity[compatibilityProperty];
185
+ }
186
+ function mappedSubmissionEntity(entity, submission, mapping, valueCodec) {
187
+ if (mapping === void 0 && valueCodec === void 0) return entity;
188
+ const keys = physicalKeyNames(mapping);
189
+ const values = valueCodec?.encodeValues?.({ ...submission.values }) ?? JSON.stringify(submission.values);
190
+ const metadata = submission.metadata === void 0 ? void 0 : JSON.stringify(submission.metadata);
191
+ return {
192
+ ...entity,
193
+ [keys.partitionKey]: submission.formId,
194
+ [keys.rowKey]: entity.rowKey ?? defaultSubmissionRowKey(submission),
195
+ [propertyName(mapping, "formId", "formId")]: submission.formId,
196
+ [propertyName(mapping, "formVersion", "formVersion")]: submission.formVersion,
197
+ [propertyName(mapping, "submittedAt", "submittedAt")]: submission.submittedAt,
198
+ [propertyName(mapping, "values", "values")]: values,
199
+ ...metadata === void 0 ? {} : { [propertyName(mapping, "metadata", "metadata")]: metadata }
200
+ };
201
+ }
202
+ function mappedSubmissionFromEntity(entity, mapping, valueCodec) {
203
+ const id = typeof entity.responseId === "string" ? entity.responseId : entity.id;
204
+ const formId = entity[propertyName(mapping, "formId", "formId")];
205
+ const formVersion = entity[propertyName(mapping, "formVersion", "formVersion")];
206
+ const submittedAt = entity[propertyName(mapping, "submittedAt", "submittedAt")];
207
+ const rawValues = entity[propertyName(mapping, "values", "values")];
208
+ if (typeof id !== "string" || typeof formId !== "string" || typeof formVersion !== "number" || typeof submittedAt !== "string" || typeof rawValues !== "string")
209
+ return void 0;
210
+ const values = valueCodec?.decodeValues?.(rawValues) ?? parseJson(rawValues, "submission values");
211
+ if (!isRecord(values) || !Object.values(values).every(isFormValue))
212
+ throw new Error("Azure Table values are invalid.");
213
+ const rawMetadata = entity[propertyName(mapping, "metadata", "metadata")];
214
+ const metadata = rawMetadata === void 0 ? void 0 : parseJson(rawMetadata, "submission metadata");
215
+ return parseSubmission(
216
+ {
217
+ id,
218
+ formId,
219
+ formVersion,
220
+ locale: typeof entity.locale === "string" ? entity.locale : "",
221
+ values,
222
+ ...metadata === void 0 || !isRecord(metadata) ? {} : { metadata },
223
+ submittedAt
224
+ },
225
+ "mapped submission entity"
226
+ );
227
+ }
145
228
  function schemaRowKey(version) {
146
229
  return `schema_${version}`;
147
230
  }
@@ -201,12 +284,16 @@ function parseSchemaEntity(value) {
201
284
  }
202
285
  return cloneJson(schema);
203
286
  }
204
- function parseSubmissionEntity(value, codec) {
205
- if (typeof value.partitionKey !== "string" || typeof value.rowKey !== "string" || !codec.matchesEntity(value)) {
287
+ function parseSubmissionEntity(value, codec, mapping, valueCodec) {
288
+ const keys = physicalKeyNames(mapping);
289
+ const partitionKey = entityKey(value, keys.partitionKey, "partitionKey");
290
+ const rowKey = entityKey(value, keys.rowKey, "rowKey");
291
+ if (typeof partitionKey !== "string" || typeof rowKey !== "string" || !codec.matchesEntity(value)) {
206
292
  throw new Error("Azure Table submission entity is invalid.");
207
293
  }
208
- const submission = parseSubmission(codec.deserialize(value), `submission ${value.partitionKey}/${value.rowKey}`);
209
- if (codec.createPartitionKey(submission) !== value.partitionKey || codec.createRowKey(submission) !== value.rowKey) {
294
+ const submissionValue = mappedSubmissionFromEntity(value, mapping, valueCodec) ?? codec.deserialize(value);
295
+ const submission = parseSubmission(submissionValue, `submission ${partitionKey}/${rowKey}`);
296
+ if (codec.createPartitionKey(submission) !== partitionKey || codec.createRowKey(submission) !== rowKey) {
210
297
  throw new Error("Azure Table submission entity has inconsistent keys.");
211
298
  }
212
299
  return submission;
@@ -221,30 +308,36 @@ function valueToOData(value) {
221
308
  if (typeof value === "boolean") return String(value);
222
309
  throw new TypeError("Azure Table OData filters support only scalar JSON values.");
223
310
  }
224
- function metadataFiltersToOData(options) {
311
+ function metadataFiltersToOData(options, mapping) {
225
312
  return Object.entries(options.metadataFilters ?? {}).map(([key, value]) => {
226
313
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) throw new TypeError(`Invalid Azure Table property name: ${key}`);
227
- return `${key} eq ${valueToOData(value)}`;
314
+ const property = mapping?.customPropertyMappings?.[`metadata.${key}`] ?? mapping?.customPropertyMappings?.[key] ?? key;
315
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(property))
316
+ throw new TypeError(`Invalid Azure Table property name: ${property}`);
317
+ return `${property} eq ${valueToOData(value)}`;
228
318
  }).join(" and ");
229
319
  }
230
- function odataProperty(path) {
320
+ function odataProperty(path, mapping) {
231
321
  if (path === "id" || path === "responseId") return "responseId";
232
- if (["formVersion", "locale", "submittedAt"].includes(path)) return path;
322
+ if (["formVersion", "locale", "submittedAt"].includes(path)) {
323
+ return propertyName(mapping, path, path);
324
+ }
233
325
  if (path.startsWith("metadata.")) {
234
326
  const property = path.slice("metadata.".length);
235
- return /^[A-Za-z_][A-Za-z0-9_]*$/.test(property) ? property : void 0;
327
+ const mapped = mapping?.customPropertyMappings?.[path] ?? mapping?.customPropertyMappings?.[property] ?? property;
328
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(mapped) ? mapped : void 0;
236
329
  }
237
330
  return void 0;
238
331
  }
239
- function submissionFilterToOData(filter) {
332
+ function submissionFilterToOData(filter, mapping) {
240
333
  if (filter.op === "and" || filter.op === "or") {
241
- const converted = filter.filters.map(submissionFilterToOData);
334
+ const converted = filter.filters.map((child) => submissionFilterToOData(child, mapping));
242
335
  if (filter.op === "or" && converted.some((value) => value === void 0)) return void 0;
243
336
  const available = converted.filter((value) => value !== void 0 && value.length > 0);
244
337
  if (available.length === 0) return void 0;
245
338
  return available.map((value) => `(${value})`).join(filter.op === "and" ? " and " : " or ");
246
339
  }
247
- const property = odataProperty(filter.path);
340
+ const property = odataProperty(filter.path, mapping);
248
341
  if (property === void 0) return void 0;
249
342
  if (filter.op === "eq") return `${property} eq ${valueToOData(filter.value)}`;
250
343
  if (filter.op === "in") {
@@ -257,15 +350,16 @@ function submissionFilterToOData(filter) {
257
350
  ...filter.to === void 0 ? [] : [`${property} le ${valueToOData(filter.to)}`]
258
351
  ].join(" and ");
259
352
  }
260
- function defaultSubmissionFilter(codec, formId, options, legacyExtension) {
353
+ function defaultSubmissionFilter(codec, formId, options, legacyExtension, mapping) {
261
354
  const partitionKey = codec.createPartitionKeyFromQuery(formId, options);
262
- const ast = options.filter === void 0 || typeof options.filter === "function" ? void 0 : submissionFilterToOData(options.filter);
355
+ const ast = options.filter === void 0 || typeof options.filter === "function" ? void 0 : submissionFilterToOData(options.filter, mapping);
356
+ const keys = physicalKeyNames(mapping);
263
357
  return [
264
- ...partitionKey === void 0 ? [] : [`PartitionKey eq '${escapeOData(partitionKey)}'`],
358
+ ...partitionKey === void 0 ? [] : [`${keys.partitionKey} eq '${escapeOData(partitionKey)}'`],
265
359
  ...codec === defaultAzureTableSubmissionCodec ? ["kind eq 'submission'"] : [],
266
- ...options.version === void 0 ? [] : [`formVersion eq ${options.version}`],
267
- ...options.since === void 0 ? [] : [`submittedAt ge '${escapeOData(options.since)}'`],
268
- ...options.until === void 0 ? [] : [`submittedAt le '${escapeOData(options.until)}'`],
360
+ ...options.version === void 0 ? [] : [`${propertyName(mapping, "formVersion", "formVersion")} eq ${options.version}`],
361
+ ...options.since === void 0 ? [] : [`${propertyName(mapping, "submittedAt", "submittedAt")} ge '${escapeOData(options.since)}'`],
362
+ ...options.until === void 0 ? [] : [`${propertyName(mapping, "submittedAt", "submittedAt")} le '${escapeOData(options.until)}'`],
269
363
  ...options.locale === void 0 ? [] : [`locale eq '${escapeOData(options.locale)}'`],
270
364
  ...ast === void 0 || ast.length === 0 ? [] : [`(${ast})`],
271
365
  ...legacyExtension.trim().length === 0 ? [] : [`(${legacyExtension})`]
@@ -284,7 +378,9 @@ function requireClient(client, name) {
284
378
  function createAzureTableStorage(options = {}) {
285
379
  const staticSchemas = options.schemasTableClient ?? options.client;
286
380
  const staticSubmissions = options.submissionsTableClient ?? options.client;
287
- const codec = options.codec ?? (options.submissionCodec === void 0 ? defaultAzureTableSubmissionCodec : legacyCodec(options.submissionCodec));
381
+ const configuredCodec = options.codec;
382
+ const valueCodec = configuredCodec !== void 0 && "encodeValues" in configuredCodec ? configuredCodec : void 0;
383
+ const codec = (configuredCodec !== void 0 && "createEntity" in configuredCodec ? configuredCodec : void 0) ?? (options.submissionCodec === void 0 ? defaultAzureTableSubmissionCodec : legacyCodec(options.submissionCodec));
288
384
  const maxScanPages = options.maxScanPages ?? 5;
289
385
  if (!Number.isSafeInteger(maxScanPages) || maxScanPages < 1) {
290
386
  throw new TypeError("maxScanPages must be a positive safe integer.");
@@ -301,10 +397,14 @@ function createAzureTableStorage(options = {}) {
301
397
  codec,
302
398
  formId,
303
399
  query,
304
- options.toODataFilter?.(query) ?? metadataFiltersToOData(query)
400
+ options.toODataFilter?.(query) ?? metadataFiltersToOData(query, options.fieldMapping),
401
+ options.fieldMapping
305
402
  );
306
403
  };
307
- const deserializeIfMatching = (entity) => codec.matchesEntity(entity) ? parseSubmissionEntity(entity, codec) : void 0;
404
+ const deserializeIfMatching = (entity) => codec.matchesEntity(entity) ? parseSubmissionEntity(entity, codec, options.fieldMapping, valueCodec) : void 0;
405
+ const ensureWritable = () => {
406
+ if (options.readOnly === true) throw new Error("Azure Table storage is read-only.");
407
+ };
308
408
  const listSubmissionCandidates = async (formId, query) => {
309
409
  const client = await submissionClient(formId, query);
310
410
  const found = [];
@@ -319,6 +419,7 @@ function createAzureTableStorage(options = {}) {
319
419
  };
320
420
  return {
321
421
  async saveSchema(schema) {
422
+ ensureWritable();
322
423
  (0, import_core.assertValidFormSchema)(schema);
323
424
  const entity = {
324
425
  partitionKey: schema.id,
@@ -345,14 +446,20 @@ function createAzureTableStorage(options = {}) {
345
446
  return found.sort((left, right) => left.id.localeCompare(right.id) || left.version - right.version);
346
447
  },
347
448
  async deleteSchema(formId, formVersion) {
449
+ ensureWritable();
348
450
  await (await schemaClient(formId)).deleteEntity(formId, schemaRowKey(formVersion));
349
451
  },
350
452
  async saveSubmission(submission) {
453
+ ensureWritable();
351
454
  const stored = parseSubmission(submission, `input ${String(submission?.id)}`);
455
+ const mappedEntity = mappedSubmissionEntity(codec.createEntity(stored), stored, options.fieldMapping, valueCodec);
456
+ const keys = physicalKeyNames(options.fieldMapping);
352
457
  await (await submissionClient(stored.formId)).createEntity({
353
- ...codec.createEntity(stored),
458
+ ...mappedEntity,
354
459
  partitionKey: codec.createPartitionKey(stored),
355
- rowKey: codec.createRowKey(stored)
460
+ rowKey: codec.createRowKey(stored),
461
+ [keys.partitionKey]: codec.createPartitionKey(stored),
462
+ [keys.rowKey]: codec.createRowKey(stored)
356
463
  });
357
464
  },
358
465
  async listSubmissions(formId, formVersion, queryOptions = {}) {
@@ -433,6 +540,11 @@ function createAzureTableStorage(options = {}) {
433
540
  continue;
434
541
  }
435
542
  const answers = submissionTextAnswers(submission, fieldIds);
543
+ const metadata = submission.metadata === void 0 ? void 0 : Object.fromEntries(
544
+ Object.entries(submission.metadata).filter(
545
+ (entry) => entry[1] !== void 0
546
+ )
547
+ );
436
548
  for (let fieldIndex = entityIndex === entityStartIndex ? fieldStartIndex : 0; fieldIndex < answers.length; fieldIndex += 1) {
437
549
  const answer = answers[fieldIndex];
438
550
  if (answer === void 0) continue;
@@ -442,9 +554,9 @@ function createAzureTableStorage(options = {}) {
442
554
  formVersion: submission.formVersion,
443
555
  fieldId: answer.fieldId,
444
556
  text: answer.text,
445
- locale: submission.locale,
557
+ ...submission.locale === void 0 ? {} : { locale: submission.locale },
446
558
  submittedAt: submission.submittedAt,
447
- ...submission.metadata === void 0 ? {} : { metadata: submission.metadata }
559
+ ...metadata === void 0 ? {} : { metadata }
448
560
  });
449
561
  if (items.length < pageSize) continue;
450
562
  const nextFieldIndex = fieldIndex + 1;
@@ -491,6 +603,7 @@ function createAzureTableStorage(options = {}) {
491
603
  };
492
604
  },
493
605
  async deleteSubmission(submissionId) {
606
+ ensureWritable();
494
607
  const client = await submissionClient("");
495
608
  for await (const raw of client.listEntities()) {
496
609
  const submission = deserializeIfMatching(raw);
@@ -503,6 +616,7 @@ function createAzureTableStorage(options = {}) {
503
616
  }
504
617
  },
505
618
  async clearResponses(formId) {
619
+ ensureWritable();
506
620
  const query = {};
507
621
  const client = await submissionClient(formId, query);
508
622
  for await (const raw of client.listEntities({ queryOptions: { filter: queryFilter(formId, query) } })) {
@@ -514,6 +628,7 @@ function createAzureTableStorage(options = {}) {
514
628
  }
515
629
  },
516
630
  async clear() {
631
+ ensureWritable();
517
632
  const schemas = await schemaClient("");
518
633
  for await (const raw of schemas.listEntities()) {
519
634
  if (typeof raw.partitionKey === "string" && typeof raw.rowKey === "string") {
@@ -533,6 +648,7 @@ function createAzureTableStorage(options = {}) {
533
648
  // Annotate the CommonJS export names for ESM import in node:
534
649
  0 && (module.exports = {
535
650
  createAzureTableStorage,
651
+ createLegacyAzureTableCodec,
536
652
  defaultAzureTableSubmissionCodec,
537
653
  metadataFiltersToOData,
538
654
  submissionFilterToOData
package/dist/index.d.cts CHANGED
@@ -30,6 +30,24 @@ interface AzureTableEntityCodec<T> {
30
30
  readonly serialize: (submission: T) => Record<string, unknown>;
31
31
  readonly deserialize: (entity: Record<string, unknown>) => T;
32
32
  }
33
+ interface AzureTableLegacyEntity {
34
+ readonly PartitionKey: string;
35
+ readonly RowKey: string;
36
+ readonly answers?: string;
37
+ readonly answeredAt?: string;
38
+ readonly surveyVersion?: number;
39
+ readonly Timestamp?: string;
40
+ readonly [key: string]: unknown;
41
+ }
42
+ interface AzureTableLegacyCodec {
43
+ readonly decode: (entity: AzureTableLegacyEntity) => FormSubmission;
44
+ readonly createPartitionKey: (formId: string, submissionId: string) => string;
45
+ readonly createRowKey: (submittedAt: string, submissionId: string) => string;
46
+ }
47
+ declare const createLegacyAzureTableCodec: (options?: {
48
+ readonly partitionKeyGenerator?: (formId: string, submissionId: string) => string;
49
+ readonly rowKeyGenerator?: (submittedAt: string, submissionId: string) => string;
50
+ }) => AzureTableLegacyCodec;
33
51
  interface AzureTableSubmissionCodec<T = FormSubmission> {
34
52
  readonly createEntity: (value: T) => Record<string, unknown>;
35
53
  readonly deserialize: (entity: Record<string, unknown>) => T;
@@ -38,6 +56,20 @@ interface AzureTableSubmissionCodec<T = FormSubmission> {
38
56
  readonly createPartitionKeyFromQuery: (formId: string, query: SubmissionPageQueryOptions) => string | undefined;
39
57
  readonly createRowKey: (value: T) => string;
40
58
  }
59
+ interface AzureTableFieldMapping {
60
+ readonly partitionKeyProperty?: string;
61
+ readonly rowKeyProperty?: string;
62
+ readonly formId?: string;
63
+ readonly formVersion?: string;
64
+ readonly submittedAt?: string;
65
+ readonly values?: string;
66
+ readonly metadata?: string;
67
+ readonly customPropertyMappings?: Readonly<Record<string, string>>;
68
+ }
69
+ interface AzureTableValueCodec {
70
+ readonly encodeValues?: (values: Record<string, unknown>) => string;
71
+ readonly decodeValues?: (raw: string) => Record<string, unknown>;
72
+ }
41
73
  interface AzureTableStorageOptions<T = FormSubmission> {
42
74
  /** @deprecated Use schemasTableClient, submissionsTableClient, or clientResolver. */
43
75
  readonly client?: AzureTableClientLike;
@@ -47,13 +79,15 @@ interface AzureTableStorageOptions<T = FormSubmission> {
47
79
  readonly formId: string;
48
80
  readonly query?: SubmissionPageQueryOptions;
49
81
  }) => AzureTableClientLike | Promise<AzureTableClientLike>;
50
- readonly codec?: AzureTableSubmissionCodec<T>;
82
+ readonly codec?: AzureTableSubmissionCodec<T> | AzureTableValueCodec;
51
83
  /** @deprecated Use codec. */
52
84
  readonly submissionCodec?: AzureTableEntityCodec<FormSubmission>;
53
85
  readonly buildSubmissionFilter?: (formId: string, query: SubmissionPageQueryOptions) => string;
54
86
  /** @deprecated Use buildSubmissionFilter. */
55
87
  readonly toODataFilter?: (options: SubmissionPageQueryOptions) => string;
56
88
  readonly maxScanPages?: number;
89
+ readonly fieldMapping?: AzureTableFieldMapping;
90
+ readonly readOnly?: boolean;
57
91
  }
58
92
  interface AzureTextAnswerCursorPayload {
59
93
  readonly formatVersion: 1;
@@ -66,8 +100,8 @@ interface AzureTextAnswerCursorPayload {
66
100
  readonly fieldIndex: number;
67
101
  }
68
102
  declare const defaultAzureTableSubmissionCodec: AzureTableSubmissionCodec<FormSubmission>;
69
- declare function metadataFiltersToOData(options: SubmissionPageQueryOptions): string;
70
- declare function submissionFilterToOData(filter: SubmissionFilter): string | undefined;
103
+ declare function metadataFiltersToOData(options: SubmissionPageQueryOptions, mapping?: AzureTableFieldMapping): string;
104
+ declare function submissionFilterToOData(filter: SubmissionFilter, mapping?: AzureTableFieldMapping): string | undefined;
71
105
  declare function createAzureTableStorage(options?: AzureTableStorageOptions): PagedSubmissionStorageAdapter;
72
106
 
73
- export { type AzureTableClientLike, type AzureTableEntityCodec, type AzureTableEntityIterator, type AzureTableEntityPage, type AzureTableListOptions, type AzureTablePageSettings, type AzureTableStorageOptions, type AzureTableSubmissionCodec, type AzureTextAnswerCursorPayload, createAzureTableStorage, defaultAzureTableSubmissionCodec, metadataFiltersToOData, submissionFilterToOData };
107
+ export { type AzureTableClientLike, type AzureTableEntityCodec, type AzureTableEntityIterator, type AzureTableEntityPage, type AzureTableFieldMapping, type AzureTableLegacyCodec, type AzureTableLegacyEntity, type AzureTableListOptions, type AzureTablePageSettings, type AzureTableStorageOptions, type AzureTableSubmissionCodec, type AzureTableValueCodec, type AzureTextAnswerCursorPayload, createAzureTableStorage, createLegacyAzureTableCodec, defaultAzureTableSubmissionCodec, metadataFiltersToOData, submissionFilterToOData };
package/dist/index.d.ts CHANGED
@@ -30,6 +30,24 @@ interface AzureTableEntityCodec<T> {
30
30
  readonly serialize: (submission: T) => Record<string, unknown>;
31
31
  readonly deserialize: (entity: Record<string, unknown>) => T;
32
32
  }
33
+ interface AzureTableLegacyEntity {
34
+ readonly PartitionKey: string;
35
+ readonly RowKey: string;
36
+ readonly answers?: string;
37
+ readonly answeredAt?: string;
38
+ readonly surveyVersion?: number;
39
+ readonly Timestamp?: string;
40
+ readonly [key: string]: unknown;
41
+ }
42
+ interface AzureTableLegacyCodec {
43
+ readonly decode: (entity: AzureTableLegacyEntity) => FormSubmission;
44
+ readonly createPartitionKey: (formId: string, submissionId: string) => string;
45
+ readonly createRowKey: (submittedAt: string, submissionId: string) => string;
46
+ }
47
+ declare const createLegacyAzureTableCodec: (options?: {
48
+ readonly partitionKeyGenerator?: (formId: string, submissionId: string) => string;
49
+ readonly rowKeyGenerator?: (submittedAt: string, submissionId: string) => string;
50
+ }) => AzureTableLegacyCodec;
33
51
  interface AzureTableSubmissionCodec<T = FormSubmission> {
34
52
  readonly createEntity: (value: T) => Record<string, unknown>;
35
53
  readonly deserialize: (entity: Record<string, unknown>) => T;
@@ -38,6 +56,20 @@ interface AzureTableSubmissionCodec<T = FormSubmission> {
38
56
  readonly createPartitionKeyFromQuery: (formId: string, query: SubmissionPageQueryOptions) => string | undefined;
39
57
  readonly createRowKey: (value: T) => string;
40
58
  }
59
+ interface AzureTableFieldMapping {
60
+ readonly partitionKeyProperty?: string;
61
+ readonly rowKeyProperty?: string;
62
+ readonly formId?: string;
63
+ readonly formVersion?: string;
64
+ readonly submittedAt?: string;
65
+ readonly values?: string;
66
+ readonly metadata?: string;
67
+ readonly customPropertyMappings?: Readonly<Record<string, string>>;
68
+ }
69
+ interface AzureTableValueCodec {
70
+ readonly encodeValues?: (values: Record<string, unknown>) => string;
71
+ readonly decodeValues?: (raw: string) => Record<string, unknown>;
72
+ }
41
73
  interface AzureTableStorageOptions<T = FormSubmission> {
42
74
  /** @deprecated Use schemasTableClient, submissionsTableClient, or clientResolver. */
43
75
  readonly client?: AzureTableClientLike;
@@ -47,13 +79,15 @@ interface AzureTableStorageOptions<T = FormSubmission> {
47
79
  readonly formId: string;
48
80
  readonly query?: SubmissionPageQueryOptions;
49
81
  }) => AzureTableClientLike | Promise<AzureTableClientLike>;
50
- readonly codec?: AzureTableSubmissionCodec<T>;
82
+ readonly codec?: AzureTableSubmissionCodec<T> | AzureTableValueCodec;
51
83
  /** @deprecated Use codec. */
52
84
  readonly submissionCodec?: AzureTableEntityCodec<FormSubmission>;
53
85
  readonly buildSubmissionFilter?: (formId: string, query: SubmissionPageQueryOptions) => string;
54
86
  /** @deprecated Use buildSubmissionFilter. */
55
87
  readonly toODataFilter?: (options: SubmissionPageQueryOptions) => string;
56
88
  readonly maxScanPages?: number;
89
+ readonly fieldMapping?: AzureTableFieldMapping;
90
+ readonly readOnly?: boolean;
57
91
  }
58
92
  interface AzureTextAnswerCursorPayload {
59
93
  readonly formatVersion: 1;
@@ -66,8 +100,8 @@ interface AzureTextAnswerCursorPayload {
66
100
  readonly fieldIndex: number;
67
101
  }
68
102
  declare const defaultAzureTableSubmissionCodec: AzureTableSubmissionCodec<FormSubmission>;
69
- declare function metadataFiltersToOData(options: SubmissionPageQueryOptions): string;
70
- declare function submissionFilterToOData(filter: SubmissionFilter): string | undefined;
103
+ declare function metadataFiltersToOData(options: SubmissionPageQueryOptions, mapping?: AzureTableFieldMapping): string;
104
+ declare function submissionFilterToOData(filter: SubmissionFilter, mapping?: AzureTableFieldMapping): string | undefined;
71
105
  declare function createAzureTableStorage(options?: AzureTableStorageOptions): PagedSubmissionStorageAdapter;
72
106
 
73
- export { type AzureTableClientLike, type AzureTableEntityCodec, type AzureTableEntityIterator, type AzureTableEntityPage, type AzureTableListOptions, type AzureTablePageSettings, type AzureTableStorageOptions, type AzureTableSubmissionCodec, type AzureTextAnswerCursorPayload, createAzureTableStorage, defaultAzureTableSubmissionCodec, metadataFiltersToOData, submissionFilterToOData };
107
+ export { type AzureTableClientLike, type AzureTableEntityCodec, type AzureTableEntityIterator, type AzureTableEntityPage, type AzureTableFieldMapping, type AzureTableLegacyCodec, type AzureTableLegacyEntity, type AzureTableListOptions, type AzureTablePageSettings, type AzureTableStorageOptions, type AzureTableSubmissionCodec, type AzureTableValueCodec, type AzureTextAnswerCursorPayload, createAzureTableStorage, createLegacyAzureTableCodec, defaultAzureTableSubmissionCodec, metadataFiltersToOData, submissionFilterToOData };
package/dist/index.js CHANGED
@@ -1,5 +1,28 @@
1
1
  // src/index.ts
2
2
  import { assertValidFormSchema, matchesSubmissionPageFilters, normalizeSubmissionPageSize } from "@form-engine-ts/core";
3
+ var createLegacyAzureTableCodec = (options = {}) => {
4
+ const createPartitionKey = options.partitionKeyGenerator ?? ((formId) => formId);
5
+ const createRowKey = options.rowKeyGenerator ?? ((submittedAt, submissionId) => `${submittedAt}_${submissionId}`);
6
+ return {
7
+ decode: (entity) => {
8
+ const values = entity.answers === void 0 ? {} : parseLegacyJsonObject(entity.answers, "answers");
9
+ const submittedAt = entity.answeredAt ?? entity.Timestamp;
10
+ if (submittedAt === void 0 || submittedAt.trim().length === 0) {
11
+ throw new Error("Azure Table legacy submission is missing answeredAt or Timestamp.");
12
+ }
13
+ return {
14
+ id: entity.RowKey,
15
+ formId: entity.PartitionKey,
16
+ formVersion: entity.surveyVersion ?? 1,
17
+ values,
18
+ metadata: {},
19
+ submittedAt
20
+ };
21
+ },
22
+ createPartitionKey: (formId, submissionId) => createPartitionKey(formId, submissionId),
23
+ createRowKey: (submittedAt, submissionId) => createRowKey(submittedAt, submissionId)
24
+ };
25
+ };
3
26
  var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4
27
  function encodeBase64(bytes) {
5
28
  let result = "";
@@ -108,6 +131,11 @@ function parseJson(value, location) {
108
131
  throw new Error(`Azure Table ${location} payload is invalid.`, { cause });
109
132
  }
110
133
  }
134
+ function parseLegacyJsonObject(value, property) {
135
+ const parsed = parseJson(value, `legacy ${property}`);
136
+ if (!isRecord(parsed)) throw new Error(`Azure Table legacy ${property} payload must be an object.`);
137
+ return parsed;
138
+ }
111
139
  function parseSubmission(value, location) {
112
140
  const parsed = typeof value === "string" ? parseJson(value, location) : value;
113
141
  if (!isRecord(parsed) || typeof parsed.id !== "string" || typeof parsed.formId !== "string" || !Number.isInteger(parsed.formVersion) || typeof parsed.locale !== "string" || typeof parsed.submittedAt !== "string" || !isRecord(parsed.values) || !Object.values(parsed.values).every(isFormValue)) {
@@ -115,6 +143,60 @@ function parseSubmission(value, location) {
115
143
  }
116
144
  return cloneJson(parsed);
117
145
  }
146
+ function propertyName(mapping, logicalName, fallback) {
147
+ return mapping?.customPropertyMappings?.[logicalName] ?? (logicalName === "formId" ? mapping?.formId : void 0) ?? (logicalName === "formVersion" ? mapping?.formVersion : void 0) ?? (logicalName === "submittedAt" ? mapping?.submittedAt : void 0) ?? (logicalName === "values" ? mapping?.values : void 0) ?? (logicalName === "metadata" ? mapping?.metadata : void 0) ?? fallback;
148
+ }
149
+ function physicalKeyNames(mapping) {
150
+ return {
151
+ partitionKey: mapping?.partitionKeyProperty ?? "PartitionKey",
152
+ rowKey: mapping?.rowKeyProperty ?? "RowKey"
153
+ };
154
+ }
155
+ function entityKey(entity, property, compatibilityProperty) {
156
+ return entity[property] ?? entity[compatibilityProperty];
157
+ }
158
+ function mappedSubmissionEntity(entity, submission, mapping, valueCodec) {
159
+ if (mapping === void 0 && valueCodec === void 0) return entity;
160
+ const keys = physicalKeyNames(mapping);
161
+ const values = valueCodec?.encodeValues?.({ ...submission.values }) ?? JSON.stringify(submission.values);
162
+ const metadata = submission.metadata === void 0 ? void 0 : JSON.stringify(submission.metadata);
163
+ return {
164
+ ...entity,
165
+ [keys.partitionKey]: submission.formId,
166
+ [keys.rowKey]: entity.rowKey ?? defaultSubmissionRowKey(submission),
167
+ [propertyName(mapping, "formId", "formId")]: submission.formId,
168
+ [propertyName(mapping, "formVersion", "formVersion")]: submission.formVersion,
169
+ [propertyName(mapping, "submittedAt", "submittedAt")]: submission.submittedAt,
170
+ [propertyName(mapping, "values", "values")]: values,
171
+ ...metadata === void 0 ? {} : { [propertyName(mapping, "metadata", "metadata")]: metadata }
172
+ };
173
+ }
174
+ function mappedSubmissionFromEntity(entity, mapping, valueCodec) {
175
+ const id = typeof entity.responseId === "string" ? entity.responseId : entity.id;
176
+ const formId = entity[propertyName(mapping, "formId", "formId")];
177
+ const formVersion = entity[propertyName(mapping, "formVersion", "formVersion")];
178
+ const submittedAt = entity[propertyName(mapping, "submittedAt", "submittedAt")];
179
+ const rawValues = entity[propertyName(mapping, "values", "values")];
180
+ if (typeof id !== "string" || typeof formId !== "string" || typeof formVersion !== "number" || typeof submittedAt !== "string" || typeof rawValues !== "string")
181
+ return void 0;
182
+ const values = valueCodec?.decodeValues?.(rawValues) ?? parseJson(rawValues, "submission values");
183
+ if (!isRecord(values) || !Object.values(values).every(isFormValue))
184
+ throw new Error("Azure Table values are invalid.");
185
+ const rawMetadata = entity[propertyName(mapping, "metadata", "metadata")];
186
+ const metadata = rawMetadata === void 0 ? void 0 : parseJson(rawMetadata, "submission metadata");
187
+ return parseSubmission(
188
+ {
189
+ id,
190
+ formId,
191
+ formVersion,
192
+ locale: typeof entity.locale === "string" ? entity.locale : "",
193
+ values,
194
+ ...metadata === void 0 || !isRecord(metadata) ? {} : { metadata },
195
+ submittedAt
196
+ },
197
+ "mapped submission entity"
198
+ );
199
+ }
118
200
  function schemaRowKey(version) {
119
201
  return `schema_${version}`;
120
202
  }
@@ -174,12 +256,16 @@ function parseSchemaEntity(value) {
174
256
  }
175
257
  return cloneJson(schema);
176
258
  }
177
- function parseSubmissionEntity(value, codec) {
178
- if (typeof value.partitionKey !== "string" || typeof value.rowKey !== "string" || !codec.matchesEntity(value)) {
259
+ function parseSubmissionEntity(value, codec, mapping, valueCodec) {
260
+ const keys = physicalKeyNames(mapping);
261
+ const partitionKey = entityKey(value, keys.partitionKey, "partitionKey");
262
+ const rowKey = entityKey(value, keys.rowKey, "rowKey");
263
+ if (typeof partitionKey !== "string" || typeof rowKey !== "string" || !codec.matchesEntity(value)) {
179
264
  throw new Error("Azure Table submission entity is invalid.");
180
265
  }
181
- const submission = parseSubmission(codec.deserialize(value), `submission ${value.partitionKey}/${value.rowKey}`);
182
- if (codec.createPartitionKey(submission) !== value.partitionKey || codec.createRowKey(submission) !== value.rowKey) {
266
+ const submissionValue = mappedSubmissionFromEntity(value, mapping, valueCodec) ?? codec.deserialize(value);
267
+ const submission = parseSubmission(submissionValue, `submission ${partitionKey}/${rowKey}`);
268
+ if (codec.createPartitionKey(submission) !== partitionKey || codec.createRowKey(submission) !== rowKey) {
183
269
  throw new Error("Azure Table submission entity has inconsistent keys.");
184
270
  }
185
271
  return submission;
@@ -194,30 +280,36 @@ function valueToOData(value) {
194
280
  if (typeof value === "boolean") return String(value);
195
281
  throw new TypeError("Azure Table OData filters support only scalar JSON values.");
196
282
  }
197
- function metadataFiltersToOData(options) {
283
+ function metadataFiltersToOData(options, mapping) {
198
284
  return Object.entries(options.metadataFilters ?? {}).map(([key, value]) => {
199
285
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) throw new TypeError(`Invalid Azure Table property name: ${key}`);
200
- return `${key} eq ${valueToOData(value)}`;
286
+ const property = mapping?.customPropertyMappings?.[`metadata.${key}`] ?? mapping?.customPropertyMappings?.[key] ?? key;
287
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(property))
288
+ throw new TypeError(`Invalid Azure Table property name: ${property}`);
289
+ return `${property} eq ${valueToOData(value)}`;
201
290
  }).join(" and ");
202
291
  }
203
- function odataProperty(path) {
292
+ function odataProperty(path, mapping) {
204
293
  if (path === "id" || path === "responseId") return "responseId";
205
- if (["formVersion", "locale", "submittedAt"].includes(path)) return path;
294
+ if (["formVersion", "locale", "submittedAt"].includes(path)) {
295
+ return propertyName(mapping, path, path);
296
+ }
206
297
  if (path.startsWith("metadata.")) {
207
298
  const property = path.slice("metadata.".length);
208
- return /^[A-Za-z_][A-Za-z0-9_]*$/.test(property) ? property : void 0;
299
+ const mapped = mapping?.customPropertyMappings?.[path] ?? mapping?.customPropertyMappings?.[property] ?? property;
300
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(mapped) ? mapped : void 0;
209
301
  }
210
302
  return void 0;
211
303
  }
212
- function submissionFilterToOData(filter) {
304
+ function submissionFilterToOData(filter, mapping) {
213
305
  if (filter.op === "and" || filter.op === "or") {
214
- const converted = filter.filters.map(submissionFilterToOData);
306
+ const converted = filter.filters.map((child) => submissionFilterToOData(child, mapping));
215
307
  if (filter.op === "or" && converted.some((value) => value === void 0)) return void 0;
216
308
  const available = converted.filter((value) => value !== void 0 && value.length > 0);
217
309
  if (available.length === 0) return void 0;
218
310
  return available.map((value) => `(${value})`).join(filter.op === "and" ? " and " : " or ");
219
311
  }
220
- const property = odataProperty(filter.path);
312
+ const property = odataProperty(filter.path, mapping);
221
313
  if (property === void 0) return void 0;
222
314
  if (filter.op === "eq") return `${property} eq ${valueToOData(filter.value)}`;
223
315
  if (filter.op === "in") {
@@ -230,15 +322,16 @@ function submissionFilterToOData(filter) {
230
322
  ...filter.to === void 0 ? [] : [`${property} le ${valueToOData(filter.to)}`]
231
323
  ].join(" and ");
232
324
  }
233
- function defaultSubmissionFilter(codec, formId, options, legacyExtension) {
325
+ function defaultSubmissionFilter(codec, formId, options, legacyExtension, mapping) {
234
326
  const partitionKey = codec.createPartitionKeyFromQuery(formId, options);
235
- const ast = options.filter === void 0 || typeof options.filter === "function" ? void 0 : submissionFilterToOData(options.filter);
327
+ const ast = options.filter === void 0 || typeof options.filter === "function" ? void 0 : submissionFilterToOData(options.filter, mapping);
328
+ const keys = physicalKeyNames(mapping);
236
329
  return [
237
- ...partitionKey === void 0 ? [] : [`PartitionKey eq '${escapeOData(partitionKey)}'`],
330
+ ...partitionKey === void 0 ? [] : [`${keys.partitionKey} eq '${escapeOData(partitionKey)}'`],
238
331
  ...codec === defaultAzureTableSubmissionCodec ? ["kind eq 'submission'"] : [],
239
- ...options.version === void 0 ? [] : [`formVersion eq ${options.version}`],
240
- ...options.since === void 0 ? [] : [`submittedAt ge '${escapeOData(options.since)}'`],
241
- ...options.until === void 0 ? [] : [`submittedAt le '${escapeOData(options.until)}'`],
332
+ ...options.version === void 0 ? [] : [`${propertyName(mapping, "formVersion", "formVersion")} eq ${options.version}`],
333
+ ...options.since === void 0 ? [] : [`${propertyName(mapping, "submittedAt", "submittedAt")} ge '${escapeOData(options.since)}'`],
334
+ ...options.until === void 0 ? [] : [`${propertyName(mapping, "submittedAt", "submittedAt")} le '${escapeOData(options.until)}'`],
242
335
  ...options.locale === void 0 ? [] : [`locale eq '${escapeOData(options.locale)}'`],
243
336
  ...ast === void 0 || ast.length === 0 ? [] : [`(${ast})`],
244
337
  ...legacyExtension.trim().length === 0 ? [] : [`(${legacyExtension})`]
@@ -257,7 +350,9 @@ function requireClient(client, name) {
257
350
  function createAzureTableStorage(options = {}) {
258
351
  const staticSchemas = options.schemasTableClient ?? options.client;
259
352
  const staticSubmissions = options.submissionsTableClient ?? options.client;
260
- const codec = options.codec ?? (options.submissionCodec === void 0 ? defaultAzureTableSubmissionCodec : legacyCodec(options.submissionCodec));
353
+ const configuredCodec = options.codec;
354
+ const valueCodec = configuredCodec !== void 0 && "encodeValues" in configuredCodec ? configuredCodec : void 0;
355
+ const codec = (configuredCodec !== void 0 && "createEntity" in configuredCodec ? configuredCodec : void 0) ?? (options.submissionCodec === void 0 ? defaultAzureTableSubmissionCodec : legacyCodec(options.submissionCodec));
261
356
  const maxScanPages = options.maxScanPages ?? 5;
262
357
  if (!Number.isSafeInteger(maxScanPages) || maxScanPages < 1) {
263
358
  throw new TypeError("maxScanPages must be a positive safe integer.");
@@ -274,10 +369,14 @@ function createAzureTableStorage(options = {}) {
274
369
  codec,
275
370
  formId,
276
371
  query,
277
- options.toODataFilter?.(query) ?? metadataFiltersToOData(query)
372
+ options.toODataFilter?.(query) ?? metadataFiltersToOData(query, options.fieldMapping),
373
+ options.fieldMapping
278
374
  );
279
375
  };
280
- const deserializeIfMatching = (entity) => codec.matchesEntity(entity) ? parseSubmissionEntity(entity, codec) : void 0;
376
+ const deserializeIfMatching = (entity) => codec.matchesEntity(entity) ? parseSubmissionEntity(entity, codec, options.fieldMapping, valueCodec) : void 0;
377
+ const ensureWritable = () => {
378
+ if (options.readOnly === true) throw new Error("Azure Table storage is read-only.");
379
+ };
281
380
  const listSubmissionCandidates = async (formId, query) => {
282
381
  const client = await submissionClient(formId, query);
283
382
  const found = [];
@@ -292,6 +391,7 @@ function createAzureTableStorage(options = {}) {
292
391
  };
293
392
  return {
294
393
  async saveSchema(schema) {
394
+ ensureWritable();
295
395
  assertValidFormSchema(schema);
296
396
  const entity = {
297
397
  partitionKey: schema.id,
@@ -318,14 +418,20 @@ function createAzureTableStorage(options = {}) {
318
418
  return found.sort((left, right) => left.id.localeCompare(right.id) || left.version - right.version);
319
419
  },
320
420
  async deleteSchema(formId, formVersion) {
421
+ ensureWritable();
321
422
  await (await schemaClient(formId)).deleteEntity(formId, schemaRowKey(formVersion));
322
423
  },
323
424
  async saveSubmission(submission) {
425
+ ensureWritable();
324
426
  const stored = parseSubmission(submission, `input ${String(submission?.id)}`);
427
+ const mappedEntity = mappedSubmissionEntity(codec.createEntity(stored), stored, options.fieldMapping, valueCodec);
428
+ const keys = physicalKeyNames(options.fieldMapping);
325
429
  await (await submissionClient(stored.formId)).createEntity({
326
- ...codec.createEntity(stored),
430
+ ...mappedEntity,
327
431
  partitionKey: codec.createPartitionKey(stored),
328
- rowKey: codec.createRowKey(stored)
432
+ rowKey: codec.createRowKey(stored),
433
+ [keys.partitionKey]: codec.createPartitionKey(stored),
434
+ [keys.rowKey]: codec.createRowKey(stored)
329
435
  });
330
436
  },
331
437
  async listSubmissions(formId, formVersion, queryOptions = {}) {
@@ -406,6 +512,11 @@ function createAzureTableStorage(options = {}) {
406
512
  continue;
407
513
  }
408
514
  const answers = submissionTextAnswers(submission, fieldIds);
515
+ const metadata = submission.metadata === void 0 ? void 0 : Object.fromEntries(
516
+ Object.entries(submission.metadata).filter(
517
+ (entry) => entry[1] !== void 0
518
+ )
519
+ );
409
520
  for (let fieldIndex = entityIndex === entityStartIndex ? fieldStartIndex : 0; fieldIndex < answers.length; fieldIndex += 1) {
410
521
  const answer = answers[fieldIndex];
411
522
  if (answer === void 0) continue;
@@ -415,9 +526,9 @@ function createAzureTableStorage(options = {}) {
415
526
  formVersion: submission.formVersion,
416
527
  fieldId: answer.fieldId,
417
528
  text: answer.text,
418
- locale: submission.locale,
529
+ ...submission.locale === void 0 ? {} : { locale: submission.locale },
419
530
  submittedAt: submission.submittedAt,
420
- ...submission.metadata === void 0 ? {} : { metadata: submission.metadata }
531
+ ...metadata === void 0 ? {} : { metadata }
421
532
  });
422
533
  if (items.length < pageSize) continue;
423
534
  const nextFieldIndex = fieldIndex + 1;
@@ -464,6 +575,7 @@ function createAzureTableStorage(options = {}) {
464
575
  };
465
576
  },
466
577
  async deleteSubmission(submissionId) {
578
+ ensureWritable();
467
579
  const client = await submissionClient("");
468
580
  for await (const raw of client.listEntities()) {
469
581
  const submission = deserializeIfMatching(raw);
@@ -476,6 +588,7 @@ function createAzureTableStorage(options = {}) {
476
588
  }
477
589
  },
478
590
  async clearResponses(formId) {
591
+ ensureWritable();
479
592
  const query = {};
480
593
  const client = await submissionClient(formId, query);
481
594
  for await (const raw of client.listEntities({ queryOptions: { filter: queryFilter(formId, query) } })) {
@@ -487,6 +600,7 @@ function createAzureTableStorage(options = {}) {
487
600
  }
488
601
  },
489
602
  async clear() {
603
+ ensureWritable();
490
604
  const schemas = await schemaClient("");
491
605
  for await (const raw of schemas.listEntities()) {
492
606
  if (typeof raw.partitionKey === "string" && typeof raw.rowKey === "string") {
@@ -505,6 +619,7 @@ function createAzureTableStorage(options = {}) {
505
619
  }
506
620
  export {
507
621
  createAzureTableStorage,
622
+ createLegacyAzureTableCodec,
508
623
  defaultAzureTableSubmissionCodec,
509
624
  metadataFiltersToOData,
510
625
  submissionFilterToOData
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@form-engine-ts/storage-azure-table",
3
- "version": "5.0.1",
3
+ "version": "6.0.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -39,7 +39,7 @@
39
39
  "typescript"
40
40
  ],
41
41
  "dependencies": {
42
- "@form-engine-ts/core": "5.0.1"
42
+ "@form-engine-ts/core": "6.0.0"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "@azure/data-tables": "^13.3.2"