@form-engine-ts/storage-localstorage 7.18.1 → 8.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -123,6 +123,30 @@ function createLocalStorageAdapter(storagePrefix = "pf_", injectedStorage, optio
123
123
  if (storage.getItem(key) !== null) throw new Error(`A submission with ID "${submission.id}" already exists.`);
124
124
  storage.setItem(key, JSON.stringify(submission));
125
125
  },
126
+ async saveSubmissionWithinLimit(submission, maxResponses, saveOptions = {}) {
127
+ if (!Number.isInteger(maxResponses) || maxResponses < 1)
128
+ throw new RangeError("maxResponses must be a positive integer.");
129
+ const key = submissionKey(submission.id);
130
+ const payloadHash = await (0, import_core.hashFormSubmissionPayload)(submission);
131
+ const existingValue = storage.getItem(key);
132
+ if (existingValue !== null) {
133
+ if (saveOptions.idempotent !== true) throw new Error(`A submission with ID "${submission.id}" already exists.`);
134
+ const existing = parseSubmission(existingValue, key);
135
+ const existingPayloadHash = await (0, import_core.hashFormSubmissionPayload)(existing);
136
+ if (existingPayloadHash === payloadHash)
137
+ return { status: "duplicate", submission: cloneJson(existing), payloadHash };
138
+ return { status: "conflict", submissionId: submission.id, payloadHash, existingPayloadHash };
139
+ }
140
+ const count = prefixedKeys(submissionPrefix).reduce((total, key2) => {
141
+ const value = storage.getItem(key2);
142
+ if (value === null) return total;
143
+ const candidate = parseSubmission(value, key2);
144
+ return total + (candidate.formId === submission.formId && candidate.formVersion === submission.formVersion ? 1 : 0);
145
+ }, 0);
146
+ if (count >= maxResponses) return { status: "limit_reached" };
147
+ storage.setItem(key, JSON.stringify(submission));
148
+ if (saveOptions.idempotent === true) return { status: "created", submission: cloneJson(submission), payloadHash };
149
+ },
126
150
  async listSubmissions(formId, formVersion, options2) {
127
151
  return prefixedKeys(submissionPrefix).map((key) => {
128
152
  const value = storage.getItem(key);
@@ -132,6 +156,14 @@ function createLocalStorageAdapter(storagePrefix = "pf_", injectedStorage, optio
132
156
  (submission) => submission.formId === formId && (formVersion === void 0 || submission.formVersion === formVersion) && (options2?.since === void 0 || submission.submittedAt >= options2.since) && (options2?.until === void 0 || submission.submittedAt <= options2.until)
133
157
  ).sort((left, right) => left.submittedAt.localeCompare(right.submittedAt) || left.id.localeCompare(right.id)).map(cloneJson);
134
158
  },
159
+ async countSubmissions(formId, formVersion, options2) {
160
+ return prefixedKeys(submissionPrefix).reduce((count, key) => {
161
+ const value = storage.getItem(key);
162
+ if (value === null) throw new Error(`Stored submission at "${key}" disappeared during reading.`);
163
+ const submission = parseSubmission(value, key);
164
+ return count + (submission.formId === formId && (formVersion === void 0 || submission.formVersion === formVersion) && (options2?.since === void 0 || submission.submittedAt >= options2.since) && (options2?.until === void 0 || submission.submittedAt <= options2.until) ? 1 : 0);
165
+ }, 0);
166
+ },
135
167
  async deleteSubmission(submissionId) {
136
168
  storage.removeItem(submissionKey(submissionId));
137
169
  },
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  // src/index.ts
2
2
  import {
3
3
  assertValidFormSchema,
4
- createFormLifecycleAdapter
4
+ createFormLifecycleAdapter,
5
+ hashFormSubmissionPayload
5
6
  } from "@form-engine-ts/core";
6
7
  function isRecord(value) {
7
8
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -102,6 +103,30 @@ function createLocalStorageAdapter(storagePrefix = "pf_", injectedStorage, optio
102
103
  if (storage.getItem(key) !== null) throw new Error(`A submission with ID "${submission.id}" already exists.`);
103
104
  storage.setItem(key, JSON.stringify(submission));
104
105
  },
106
+ async saveSubmissionWithinLimit(submission, maxResponses, saveOptions = {}) {
107
+ if (!Number.isInteger(maxResponses) || maxResponses < 1)
108
+ throw new RangeError("maxResponses must be a positive integer.");
109
+ const key = submissionKey(submission.id);
110
+ const payloadHash = await hashFormSubmissionPayload(submission);
111
+ const existingValue = storage.getItem(key);
112
+ if (existingValue !== null) {
113
+ if (saveOptions.idempotent !== true) throw new Error(`A submission with ID "${submission.id}" already exists.`);
114
+ const existing = parseSubmission(existingValue, key);
115
+ const existingPayloadHash = await hashFormSubmissionPayload(existing);
116
+ if (existingPayloadHash === payloadHash)
117
+ return { status: "duplicate", submission: cloneJson(existing), payloadHash };
118
+ return { status: "conflict", submissionId: submission.id, payloadHash, existingPayloadHash };
119
+ }
120
+ const count = prefixedKeys(submissionPrefix).reduce((total, key2) => {
121
+ const value = storage.getItem(key2);
122
+ if (value === null) return total;
123
+ const candidate = parseSubmission(value, key2);
124
+ return total + (candidate.formId === submission.formId && candidate.formVersion === submission.formVersion ? 1 : 0);
125
+ }, 0);
126
+ if (count >= maxResponses) return { status: "limit_reached" };
127
+ storage.setItem(key, JSON.stringify(submission));
128
+ if (saveOptions.idempotent === true) return { status: "created", submission: cloneJson(submission), payloadHash };
129
+ },
105
130
  async listSubmissions(formId, formVersion, options2) {
106
131
  return prefixedKeys(submissionPrefix).map((key) => {
107
132
  const value = storage.getItem(key);
@@ -111,6 +136,14 @@ function createLocalStorageAdapter(storagePrefix = "pf_", injectedStorage, optio
111
136
  (submission) => submission.formId === formId && (formVersion === void 0 || submission.formVersion === formVersion) && (options2?.since === void 0 || submission.submittedAt >= options2.since) && (options2?.until === void 0 || submission.submittedAt <= options2.until)
112
137
  ).sort((left, right) => left.submittedAt.localeCompare(right.submittedAt) || left.id.localeCompare(right.id)).map(cloneJson);
113
138
  },
139
+ async countSubmissions(formId, formVersion, options2) {
140
+ return prefixedKeys(submissionPrefix).reduce((count, key) => {
141
+ const value = storage.getItem(key);
142
+ if (value === null) throw new Error(`Stored submission at "${key}" disappeared during reading.`);
143
+ const submission = parseSubmission(value, key);
144
+ return count + (submission.formId === formId && (formVersion === void 0 || submission.formVersion === formVersion) && (options2?.since === void 0 || submission.submittedAt >= options2.since) && (options2?.until === void 0 || submission.submittedAt <= options2.until) ? 1 : 0);
145
+ }, 0);
146
+ },
114
147
  async deleteSubmission(submissionId) {
115
148
  storage.removeItem(submissionKey(submissionId));
116
149
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@form-engine-ts/storage-localstorage",
3
- "version": "7.18.1",
3
+ "version": "8.0.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -39,10 +39,10 @@
39
39
  "typescript"
40
40
  ],
41
41
  "dependencies": {
42
- "@form-engine-ts/core": "7.18.1"
42
+ "@form-engine-ts/core": "8.0.1"
43
43
  },
44
44
  "devDependencies": {
45
- "@form-engine-ts/storage": "7.18.1"
45
+ "@form-engine-ts/storage": "8.0.1"
46
46
  },
47
47
  "scripts": {
48
48
  "build": "tsup src/index.ts --format esm,cjs --dts --clean --external @form-engine-ts/core",