@blueprint-ts/core 4.1.0-beta.4 → 4.1.0-beta.6
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/persistenceDrivers/MemoryPersistenceDriver.d.ts +11 -0
- package/dist/persistenceDrivers/MemoryPersistenceDriver.js +22 -0
- package/dist/persistenceDrivers/index.d.ts +2 -1
- package/dist/persistenceDrivers/index.js +2 -1
- package/dist/requests/BaseRequest.d.ts +2 -0
- package/dist/requests/BaseRequest.js +7 -2
- package/dist/requests/contracts/BaseRequestContract.d.ts +2 -0
- package/dist/requests/drivers/mock/MockRequestAssertionError.d.ts +3 -0
- package/dist/requests/drivers/mock/MockRequestAssertionError.js +6 -0
- package/dist/requests/drivers/mock/MockRequestDriver.d.ts +160 -0
- package/dist/requests/drivers/mock/MockRequestDriver.js +588 -0
- package/dist/requests/drivers/mock/MockRequestTestHelpers.d.ts +20 -0
- package/dist/requests/drivers/mock/MockRequestTestHelpers.js +70 -0
- package/dist/requests/drivers/mock/MockResponseHandler.d.ts +22 -0
- package/dist/requests/drivers/mock/MockResponseHandler.js +59 -0
- package/dist/requests/index.d.ts +5 -2
- package/dist/requests/index.js +4 -1
- package/dist/vue/forms/BaseForm.d.ts +4 -0
- package/dist/vue/forms/BaseForm.js +40 -43
- package/dist/vue/forms/index.d.ts +5 -2
- package/dist/vue/forms/index.js +3 -1
- package/dist/vue/forms/persistence/StrictPersistenceRestorePolicy.d.ts +4 -0
- package/dist/vue/forms/persistence/StrictPersistenceRestorePolicy.js +42 -0
- package/dist/vue/forms/persistence/index.d.ts +3 -0
- package/dist/vue/forms/persistence/index.js +2 -0
- package/dist/vue/forms/persistence/types.d.ts +23 -0
- package/dist/vue/forms/persistence/types.js +1 -0
- package/dist/vue/forms/persistence/utils.d.ts +2 -0
- package/dist/vue/forms/persistence/utils.js +77 -0
- package/package.json +1 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ResolvedHeadersContract } from '../../contracts/HeadersContract';
|
|
2
|
+
import { type ResponseHandlerContract } from '../contracts/ResponseHandlerContract';
|
|
3
|
+
export type MockResponseBody = string | Blob | BufferSource | object | null | undefined;
|
|
4
|
+
export interface MockResponseDefinition {
|
|
5
|
+
status?: number;
|
|
6
|
+
statusText?: string;
|
|
7
|
+
headers?: ResolvedHeadersContract;
|
|
8
|
+
body?: MockResponseBody;
|
|
9
|
+
}
|
|
10
|
+
export declare class MockResponseHandler implements ResponseHandlerContract {
|
|
11
|
+
protected response: Response;
|
|
12
|
+
constructor(definition: MockResponseDefinition);
|
|
13
|
+
getStatusCode(): number | undefined;
|
|
14
|
+
getHeaders(): ResolvedHeadersContract;
|
|
15
|
+
getRawResponse(): Response;
|
|
16
|
+
json<ResponseBodyInterface>(): Promise<ResponseBodyInterface>;
|
|
17
|
+
text(): Promise<string>;
|
|
18
|
+
blob(): Promise<Blob>;
|
|
19
|
+
protected resolveBody(body: MockResponseBody, headers: Headers): BodyInit | null | undefined;
|
|
20
|
+
protected isJsonValue(value: MockResponseBody): value is object;
|
|
21
|
+
protected hasHeader(headers: Headers, key: string): boolean;
|
|
22
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
export class MockResponseHandler {
|
|
11
|
+
constructor(definition) {
|
|
12
|
+
var _a;
|
|
13
|
+
const headers = new Headers(definition.headers);
|
|
14
|
+
const body = this.resolveBody(definition.body, headers);
|
|
15
|
+
this.response = new Response(body, Object.assign({ status: (_a = definition.status) !== null && _a !== void 0 ? _a : 200, headers }, (definition.statusText !== undefined ? { statusText: definition.statusText } : {})));
|
|
16
|
+
}
|
|
17
|
+
getStatusCode() {
|
|
18
|
+
return this.response.status;
|
|
19
|
+
}
|
|
20
|
+
getHeaders() {
|
|
21
|
+
return Object.fromEntries(this.response.headers);
|
|
22
|
+
}
|
|
23
|
+
getRawResponse() {
|
|
24
|
+
return this.response;
|
|
25
|
+
}
|
|
26
|
+
json() {
|
|
27
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
28
|
+
return yield this.response.clone().json();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
text() {
|
|
32
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
33
|
+
return yield this.response.clone().text();
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
blob() {
|
|
37
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
38
|
+
return yield this.response.clone().blob();
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
resolveBody(body, headers) {
|
|
42
|
+
if (body === undefined || body === null) {
|
|
43
|
+
return body;
|
|
44
|
+
}
|
|
45
|
+
if (this.isJsonValue(body)) {
|
|
46
|
+
if (!this.hasHeader(headers, 'content-type')) {
|
|
47
|
+
headers.set('content-type', 'application/json');
|
|
48
|
+
}
|
|
49
|
+
return JSON.stringify(body);
|
|
50
|
+
}
|
|
51
|
+
return body;
|
|
52
|
+
}
|
|
53
|
+
isJsonValue(value) {
|
|
54
|
+
return typeof value === 'object' && value !== null && !(value instanceof Blob) && !(value instanceof ArrayBuffer) && !ArrayBuffer.isView(value);
|
|
55
|
+
}
|
|
56
|
+
hasHeader(headers, key) {
|
|
57
|
+
return Array.from(headers.keys()).some((headerKey) => headerKey.toLowerCase() === key.toLowerCase());
|
|
58
|
+
}
|
|
59
|
+
}
|
package/dist/requests/index.d.ts
CHANGED
|
@@ -13,6 +13,9 @@ import { BinaryBody } from './bodies/BinaryBody';
|
|
|
13
13
|
import { JsonBodyFactory } from './factories/JsonBodyFactory';
|
|
14
14
|
import { BinaryBodyFactory, type BinaryBodyContent } from './factories/BinaryBodyFactory';
|
|
15
15
|
import { FormDataFactory } from './factories/FormDataFactory';
|
|
16
|
+
import { MockRequestDriver, MockRequestExpectationBuilder, getMockRequestJsonBody, getMockRequestQuery, getMockRequestTextBody, type MockNormalizedRequestBody, type MockRequestBody, type MockRequestBodyMatchContext, type MockRequestBodyMatcher, type MockRequestDriverOptions, type MockRequestExpectation, type MockRequestExpectationCriteria, type MockRequestHeadersMatcher, type MockRequestHistoryEntry, type MockRequestMatchMode, type MockRequestMethodMatcher, type MockRequestPredicate, type MockRequestQuery, type MockRequestQueryMatcher, type MockRequestUrlMatcher, type MockResponseBody, type MockResponseDefinition } from './drivers/mock/MockRequestDriver';
|
|
17
|
+
import { MockRequestAssertionError } from './drivers/mock/MockRequestAssertionError';
|
|
18
|
+
import { emptyResponse, expectJsonBody, installMockRequestDriver, jsonResponse, matchHeaders, matchQuery, resetMockRequestDriver, validationError, type InstallMockRequestDriverOptions } from './drivers/mock/MockRequestTestHelpers';
|
|
16
19
|
import { type BodyContent, type BodyContract } from './contracts/BodyContract';
|
|
17
20
|
import { type RequestLoaderContract } from './contracts/RequestLoaderContract';
|
|
18
21
|
import { type RequestDriverContract } from './contracts/RequestDriverContract';
|
|
@@ -27,5 +30,5 @@ import { type HeaderValue, type HeadersContract, type ResolvedHeadersContract }
|
|
|
27
30
|
import { type RequestConcurrencyOptions } from './types/RequestConcurrencyOptions';
|
|
28
31
|
import { type RequestUploadProgress } from './types/RequestUploadProgress';
|
|
29
32
|
import { XMLHttpRequestDriver } from './drivers/xhr/XMLHttpRequestDriver';
|
|
30
|
-
export { FetchDriver, BaseResponse, JsonResponse, BlobResponse, PlainTextResponse, BaseRequest, ErrorHandler, RequestErrorRouter, RequestEvents, RequestMethodEnum, RequestConcurrencyMode, ResponseException, StaleResponseException, BinaryBody, JsonBodyFactory, BinaryBodyFactory, FormDataFactory, XMLHttpRequestDriver };
|
|
31
|
-
export type { BodyContent, BinaryBodyContent, RequestDriverContract, RequestLoaderContract, BodyContract, RequestLoaderFactoryContract, DriverConfigContract, BodyFactoryContract, ResponseHandlerContract, BaseRequestContract, HeaderValue, HeadersContract, ResolvedHeadersContract, RequestConcurrencyOptions, RequestUploadProgress };
|
|
33
|
+
export { FetchDriver, BaseResponse, JsonResponse, BlobResponse, PlainTextResponse, BaseRequest, ErrorHandler, RequestErrorRouter, RequestEvents, RequestMethodEnum, RequestConcurrencyMode, ResponseException, StaleResponseException, BinaryBody, JsonBodyFactory, BinaryBodyFactory, FormDataFactory, XMLHttpRequestDriver, MockRequestDriver, MockRequestExpectationBuilder, MockRequestAssertionError, expectJsonBody, matchHeaders, matchQuery, jsonResponse, validationError, emptyResponse, installMockRequestDriver, resetMockRequestDriver, getMockRequestJsonBody, getMockRequestTextBody, getMockRequestQuery };
|
|
34
|
+
export type { BodyContent, BinaryBodyContent, RequestDriverContract, RequestLoaderContract, BodyContract, RequestLoaderFactoryContract, DriverConfigContract, BodyFactoryContract, ResponseHandlerContract, BaseRequestContract, HeaderValue, HeadersContract, ResolvedHeadersContract, RequestConcurrencyOptions, RequestUploadProgress, MockNormalizedRequestBody, MockRequestBody, MockRequestBodyMatchContext, MockRequestBodyMatcher, MockRequestDriverOptions, MockRequestExpectation, MockRequestExpectationCriteria, MockRequestHeadersMatcher, MockRequestHistoryEntry, MockRequestMatchMode, MockRequestMethodMatcher, MockRequestPredicate, MockRequestQuery, MockRequestQueryMatcher, MockRequestUrlMatcher, MockResponseBody, MockResponseDefinition, InstallMockRequestDriverOptions };
|
package/dist/requests/index.js
CHANGED
|
@@ -13,7 +13,10 @@ import { BinaryBody } from './bodies/BinaryBody';
|
|
|
13
13
|
import { JsonBodyFactory } from './factories/JsonBodyFactory';
|
|
14
14
|
import { BinaryBodyFactory } from './factories/BinaryBodyFactory';
|
|
15
15
|
import { FormDataFactory } from './factories/FormDataFactory';
|
|
16
|
+
import { MockRequestDriver, MockRequestExpectationBuilder, getMockRequestJsonBody, getMockRequestQuery, getMockRequestTextBody } from './drivers/mock/MockRequestDriver';
|
|
17
|
+
import { MockRequestAssertionError } from './drivers/mock/MockRequestAssertionError';
|
|
18
|
+
import { emptyResponse, expectJsonBody, installMockRequestDriver, jsonResponse, matchHeaders, matchQuery, resetMockRequestDriver, validationError } from './drivers/mock/MockRequestTestHelpers';
|
|
16
19
|
import { ResponseException } from './exceptions/ResponseException';
|
|
17
20
|
import { StaleResponseException } from './exceptions/StaleResponseException';
|
|
18
21
|
import { XMLHttpRequestDriver } from './drivers/xhr/XMLHttpRequestDriver';
|
|
19
|
-
export { FetchDriver, BaseResponse, JsonResponse, BlobResponse, PlainTextResponse, BaseRequest, ErrorHandler, RequestErrorRouter, RequestEvents, RequestMethodEnum, RequestConcurrencyMode, ResponseException, StaleResponseException, BinaryBody, JsonBodyFactory, BinaryBodyFactory, FormDataFactory, XMLHttpRequestDriver };
|
|
22
|
+
export { FetchDriver, BaseResponse, JsonResponse, BlobResponse, PlainTextResponse, BaseRequest, ErrorHandler, RequestErrorRouter, RequestEvents, RequestMethodEnum, RequestConcurrencyMode, ResponseException, StaleResponseException, BinaryBody, JsonBodyFactory, BinaryBodyFactory, FormDataFactory, XMLHttpRequestDriver, MockRequestDriver, MockRequestExpectationBuilder, MockRequestAssertionError, expectJsonBody, matchHeaders, matchQuery, jsonResponse, validationError, emptyResponse, installMockRequestDriver, resetMockRequestDriver, getMockRequestJsonBody, getMockRequestTextBody, getMockRequestQuery };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type PersistenceDriver } from '../../persistenceDrivers/types/PersistenceDriver';
|
|
2
2
|
import { PropertyAwareArray, type PropertyAwareField } from './PropertyAwareArray';
|
|
3
3
|
import { PropertyAwareObject } from './PropertyAwareObject';
|
|
4
|
+
import { type PersistenceDebugEvent, type PersistenceRestorePolicy } from './persistence/types';
|
|
4
5
|
import { type ValidationGroups, type ValidationRules } from './validation';
|
|
5
6
|
type ErrorMessages = string[];
|
|
6
7
|
interface ErrorObject {
|
|
@@ -82,6 +83,9 @@ export declare abstract class BaseForm<RequestBody extends object, FormBody exte
|
|
|
82
83
|
* Child classes can override this method to return a different driver.
|
|
83
84
|
*/
|
|
84
85
|
protected getPersistenceDriver(_suffix: string | undefined): PersistenceDriver;
|
|
86
|
+
protected getPersistenceRestorePolicy(): PersistenceRestorePolicy<FormBody>;
|
|
87
|
+
protected shouldLogPersistenceDebug(): boolean;
|
|
88
|
+
protected logPersistenceDebug(event: PersistenceDebugEvent<FormBody>): void;
|
|
85
89
|
/**
|
|
86
90
|
* Helper: recursively computes the dirty state for a value based on the original.
|
|
87
91
|
* For plain arrays we compare the entire array (a single flag), not each element.
|
|
@@ -12,6 +12,7 @@ import { camelCase, upperFirst, cloneDeep, debounce, isEqual } from 'lodash-es';
|
|
|
12
12
|
import { NonPersistentDriver } from '../../persistenceDrivers/NonPersistentDriver';
|
|
13
13
|
import { PropertyAwareArray } from './PropertyAwareArray';
|
|
14
14
|
import { PropertyAwareObject, PROPERTY_AWARE_OBJECT_MARKER } from './PropertyAwareObject';
|
|
15
|
+
import { StrictPersistenceRestorePolicy } from './persistence/StrictPersistenceRestorePolicy';
|
|
15
16
|
import { BaseRule } from './validation/rules/BaseRule';
|
|
16
17
|
import { ValidationMode } from './validation';
|
|
17
18
|
function isRecord(value) {
|
|
@@ -140,39 +141,6 @@ function restorePropertyAwareStructure(defaults, value) {
|
|
|
140
141
|
}
|
|
141
142
|
return restoreSerializedPropertyAwareValue(value);
|
|
142
143
|
}
|
|
143
|
-
function normalizePropertyAwareEqualityValue(value) {
|
|
144
|
-
if (value instanceof PropertyAwareArray) {
|
|
145
|
-
return Array.from(value, (item) => normalizePropertyAwareEqualityValue(item));
|
|
146
|
-
}
|
|
147
|
-
if (isPropertyAwareObject(value) || isSerializedPropertyAwareObject(value)) {
|
|
148
|
-
const normalized = {};
|
|
149
|
-
for (const [key, child] of Object.entries(value)) {
|
|
150
|
-
if (key === PROPERTY_AWARE_OBJECT_MARKER) {
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
normalized[key] = normalizePropertyAwareEqualityValue(child);
|
|
154
|
-
}
|
|
155
|
-
return normalized;
|
|
156
|
-
}
|
|
157
|
-
if (Array.isArray(value)) {
|
|
158
|
-
return value.map((item) => normalizePropertyAwareEqualityValue(item));
|
|
159
|
-
}
|
|
160
|
-
if (isRecord(value)) {
|
|
161
|
-
const normalized = {};
|
|
162
|
-
for (const [key, child] of Object.entries(value)) {
|
|
163
|
-
normalized[key] = normalizePropertyAwareEqualityValue(child);
|
|
164
|
-
}
|
|
165
|
-
return normalized;
|
|
166
|
-
}
|
|
167
|
-
return value;
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* Compare values while ignoring persistence-only markers on property-aware structures.
|
|
171
|
-
* This avoids false negatives when comparing persisted state to defaults.
|
|
172
|
-
*/
|
|
173
|
-
function propertyAwareDeepEqual(a, b) {
|
|
174
|
-
return isEqual(normalizePropertyAwareEqualityValue(a), normalizePropertyAwareEqualityValue(b));
|
|
175
|
-
}
|
|
176
144
|
/**
|
|
177
145
|
* A generic base class for forms.
|
|
178
146
|
*
|
|
@@ -191,6 +159,20 @@ export class BaseForm {
|
|
|
191
159
|
getPersistenceDriver(_suffix) {
|
|
192
160
|
return new NonPersistentDriver();
|
|
193
161
|
}
|
|
162
|
+
getPersistenceRestorePolicy() {
|
|
163
|
+
return new StrictPersistenceRestorePolicy();
|
|
164
|
+
}
|
|
165
|
+
shouldLogPersistenceDebug() {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
logPersistenceDebug(event) {
|
|
169
|
+
if (!this.shouldLogPersistenceDebug()) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const suffixLabel = event.persistSuffix ? ` (${event.persistSuffix})` : '';
|
|
173
|
+
const details = event.details ? ` ${JSON.stringify(event.details)}` : '';
|
|
174
|
+
console.debug(`[BaseForm persistence] ${event.formName}${suffixLabel}: ${event.action} (${event.reason})${details}`);
|
|
175
|
+
}
|
|
194
176
|
/**
|
|
195
177
|
* Helper: recursively computes the dirty state for a value based on the original.
|
|
196
178
|
* For plain arrays we compare the entire array (a single flag), not each element.
|
|
@@ -324,6 +306,7 @@ export class BaseForm {
|
|
|
324
306
|
}
|
|
325
307
|
}
|
|
326
308
|
constructor(defaults, options) {
|
|
309
|
+
var _a;
|
|
327
310
|
this.options = options;
|
|
328
311
|
this._errors = reactive({});
|
|
329
312
|
this._asyncErrors = reactive({});
|
|
@@ -342,21 +325,35 @@ export class BaseForm {
|
|
|
342
325
|
let initialData;
|
|
343
326
|
const driver = this.getPersistenceDriver(options === null || options === void 0 ? void 0 : options.persistSuffix);
|
|
344
327
|
if (persist) {
|
|
345
|
-
const persisted = driver.get(this.constructor.name);
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
328
|
+
const persisted = (_a = driver.get(this.constructor.name)) !== null && _a !== void 0 ? _a : null;
|
|
329
|
+
const restoreDecision = this.getPersistenceRestorePolicy().resolve({
|
|
330
|
+
formName: this.constructor.name,
|
|
331
|
+
persistSuffix: options === null || options === void 0 ? void 0 : options.persistSuffix,
|
|
332
|
+
defaults,
|
|
333
|
+
persisted
|
|
334
|
+
});
|
|
335
|
+
this.logPersistenceDebug({
|
|
336
|
+
formName: this.constructor.name,
|
|
337
|
+
persistSuffix: options === null || options === void 0 ? void 0 : options.persistSuffix,
|
|
338
|
+
action: restoreDecision.action,
|
|
339
|
+
reason: restoreDecision.reason,
|
|
340
|
+
details: restoreDecision.details
|
|
341
|
+
});
|
|
342
|
+
if (restoreDecision.action === 'restore' && restoreDecision.persisted) {
|
|
343
|
+
initialData = restorePropertyAwareStructure(defaults, restoreDecision.persisted.state);
|
|
344
|
+
this.original = restorePropertyAwareStructure(defaults, cloneDeep(restoreDecision.persisted.original));
|
|
345
|
+
this.dirty = reactive(restoreDecision.persisted.dirty);
|
|
346
|
+
this.touched = reactive(restoreDecision.persisted.touched || {});
|
|
351
347
|
}
|
|
352
348
|
else {
|
|
353
|
-
console.log('Discarding persisted data for ' + this.constructor.name + " because it doesn't match the defaults.");
|
|
354
349
|
initialData = defaults;
|
|
355
350
|
this.original = restorePropertyAwareStructure(defaults, cloneDeep(defaults));
|
|
356
351
|
const init = this.initDirtyTouched(defaults);
|
|
357
352
|
this.dirty = init.dirty;
|
|
358
353
|
this.touched = init.touched;
|
|
359
|
-
|
|
354
|
+
if (restoreDecision.action === 'discard') {
|
|
355
|
+
driver.remove(this.constructor.name);
|
|
356
|
+
}
|
|
360
357
|
}
|
|
361
358
|
}
|
|
362
359
|
else {
|
|
@@ -379,7 +376,7 @@ export class BaseForm {
|
|
|
379
376
|
set: (newVal) => {
|
|
380
377
|
const next = Array.isArray(newVal) ? Array.from(newVal) : [];
|
|
381
378
|
this.replacePropertyAwareArray(key, next);
|
|
382
|
-
this.dirty[key] =
|
|
379
|
+
this.dirty[key] = this.computeDirtyState(this.state[key], this.original[key]);
|
|
383
380
|
this.markFieldUpdated(key, driver);
|
|
384
381
|
}
|
|
385
382
|
});
|
|
@@ -1193,7 +1190,7 @@ export class BaseForm {
|
|
|
1193
1190
|
if (currentVal instanceof PropertyAwareArray) {
|
|
1194
1191
|
const values = newVal instanceof PropertyAwareArray || Array.isArray(newVal) ? Array.from(newVal) : [];
|
|
1195
1192
|
this.replacePropertyAwareArray(key, values);
|
|
1196
|
-
this.dirty[key] =
|
|
1193
|
+
this.dirty[key] = this.computeDirtyState(this.state[key], this.original[key]);
|
|
1197
1194
|
this.touched[key] = true;
|
|
1198
1195
|
continue;
|
|
1199
1196
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { BaseForm, propertyAwareToRaw } from './BaseForm';
|
|
2
2
|
import { type PersistedForm } from './types/PersistedForm';
|
|
3
3
|
import { LocalStorageDriver } from '../../persistenceDrivers/LocalStorageDriver';
|
|
4
|
+
import { MemoryPersistenceDriver } from '../../persistenceDrivers/MemoryPersistenceDriver';
|
|
4
5
|
import { NonPersistentDriver } from '../../persistenceDrivers/NonPersistentDriver';
|
|
5
6
|
import { SessionStorageDriver } from '../../persistenceDrivers/SessionStorageDriver';
|
|
6
7
|
import { type PersistenceDriver } from '../../persistenceDrivers/types/PersistenceDriver';
|
|
7
8
|
import { PropertyAwareArray, type PropertyAwareField, type PropertyAware } from './PropertyAwareArray';
|
|
8
9
|
import { PropertyAwareObject } from './PropertyAwareObject';
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
import { StrictPersistenceRestorePolicy } from './persistence';
|
|
11
|
+
import type { PersistenceDebugEvent, PersistenceRestoreContext, PersistenceRestorePolicy, PersistenceRestoreResult } from './persistence';
|
|
12
|
+
export { BaseForm, propertyAwareToRaw, PropertyAwareArray, PropertyAwareObject, NonPersistentDriver, SessionStorageDriver, LocalStorageDriver, MemoryPersistenceDriver, StrictPersistenceRestorePolicy };
|
|
13
|
+
export type { PersistedForm, PersistenceDriver, PropertyAwareField, PropertyAware, PersistenceDebugEvent, PersistenceRestoreContext, PersistenceRestorePolicy, PersistenceRestoreResult };
|
package/dist/vue/forms/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { BaseForm, propertyAwareToRaw } from './BaseForm';
|
|
2
2
|
import { LocalStorageDriver } from '../../persistenceDrivers/LocalStorageDriver';
|
|
3
|
+
import { MemoryPersistenceDriver } from '../../persistenceDrivers/MemoryPersistenceDriver';
|
|
3
4
|
import { NonPersistentDriver } from '../../persistenceDrivers/NonPersistentDriver';
|
|
4
5
|
import { SessionStorageDriver } from '../../persistenceDrivers/SessionStorageDriver';
|
|
5
6
|
import { PropertyAwareArray } from './PropertyAwareArray';
|
|
6
7
|
import { PropertyAwareObject } from './PropertyAwareObject';
|
|
7
|
-
|
|
8
|
+
import { StrictPersistenceRestorePolicy } from './persistence';
|
|
9
|
+
export { BaseForm, propertyAwareToRaw, PropertyAwareArray, PropertyAwareObject, NonPersistentDriver, SessionStorageDriver, LocalStorageDriver, MemoryPersistenceDriver, StrictPersistenceRestorePolicy };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type PersistenceRestoreContext, type PersistenceRestorePolicy, type PersistenceRestoreResult } from './types';
|
|
2
|
+
export declare class StrictPersistenceRestorePolicy<FormBody extends object> implements PersistenceRestorePolicy<FormBody> {
|
|
3
|
+
resolve(context: PersistenceRestoreContext<FormBody>): PersistenceRestoreResult<FormBody>;
|
|
4
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { collectPropertyAwareMismatchPaths, propertyAwareDeepEqual } from './utils';
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
function isPersistedFormLike(value) {
|
|
6
|
+
if (!isRecord(value)) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
return 'state' in value && 'original' in value && 'dirty' in value;
|
|
10
|
+
}
|
|
11
|
+
export class StrictPersistenceRestorePolicy {
|
|
12
|
+
resolve(context) {
|
|
13
|
+
const { defaults, persisted } = context;
|
|
14
|
+
if (persisted === null) {
|
|
15
|
+
return {
|
|
16
|
+
action: 'ignore',
|
|
17
|
+
reason: 'no_persisted_state'
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
if (!isPersistedFormLike(persisted)) {
|
|
21
|
+
return {
|
|
22
|
+
action: 'discard',
|
|
23
|
+
reason: 'invalid_persisted_state'
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (propertyAwareDeepEqual(defaults, persisted.original)) {
|
|
27
|
+
return {
|
|
28
|
+
action: 'restore',
|
|
29
|
+
reason: 'defaults_match',
|
|
30
|
+
persisted
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
action: 'discard',
|
|
35
|
+
reason: 'defaults_mismatch',
|
|
36
|
+
persisted,
|
|
37
|
+
details: {
|
|
38
|
+
mismatchPaths: collectPropertyAwareMismatchPaths(defaults, persisted.original)
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type PersistedForm } from '../types/PersistedForm';
|
|
2
|
+
export interface PersistenceRestoreContext<FormBody extends object> {
|
|
3
|
+
formName: string;
|
|
4
|
+
persistSuffix?: string | undefined;
|
|
5
|
+
defaults: FormBody;
|
|
6
|
+
persisted: PersistedForm<FormBody> | null;
|
|
7
|
+
}
|
|
8
|
+
export interface PersistenceRestoreResult<FormBody extends object> {
|
|
9
|
+
action: 'restore' | 'discard' | 'ignore';
|
|
10
|
+
reason: string;
|
|
11
|
+
persisted?: PersistedForm<FormBody> | undefined;
|
|
12
|
+
details?: Record<string, unknown> | undefined;
|
|
13
|
+
}
|
|
14
|
+
export interface PersistenceDebugEvent<FormBody extends object> {
|
|
15
|
+
formName: string;
|
|
16
|
+
persistSuffix?: string | undefined;
|
|
17
|
+
action: PersistenceRestoreResult<FormBody>['action'];
|
|
18
|
+
reason: string;
|
|
19
|
+
details?: Record<string, unknown> | undefined;
|
|
20
|
+
}
|
|
21
|
+
export interface PersistenceRestorePolicy<FormBody extends object> {
|
|
22
|
+
resolve(context: PersistenceRestoreContext<FormBody>): PersistenceRestoreResult<FormBody>;
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { isEqual } from 'lodash-es';
|
|
2
|
+
import { PropertyAwareArray } from '../PropertyAwareArray';
|
|
3
|
+
import { PropertyAwareObject, PROPERTY_AWARE_OBJECT_MARKER } from '../PropertyAwareObject';
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
function isPropertyAwareObject(value) {
|
|
8
|
+
return value instanceof PropertyAwareObject;
|
|
9
|
+
}
|
|
10
|
+
function isSerializedPropertyAwareObject(value) {
|
|
11
|
+
return isRecord(value) && value[PROPERTY_AWARE_OBJECT_MARKER] === true;
|
|
12
|
+
}
|
|
13
|
+
function normalizePropertyAwareEqualityValue(value) {
|
|
14
|
+
if (value instanceof PropertyAwareArray) {
|
|
15
|
+
return Array.from(value, (item) => normalizePropertyAwareEqualityValue(item));
|
|
16
|
+
}
|
|
17
|
+
if (isPropertyAwareObject(value) || isSerializedPropertyAwareObject(value)) {
|
|
18
|
+
const normalized = {};
|
|
19
|
+
for (const [key, child] of Object.entries(value)) {
|
|
20
|
+
if (key === PROPERTY_AWARE_OBJECT_MARKER) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
normalized[key] = normalizePropertyAwareEqualityValue(child);
|
|
24
|
+
}
|
|
25
|
+
return normalized;
|
|
26
|
+
}
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
return value.map((item) => normalizePropertyAwareEqualityValue(item));
|
|
29
|
+
}
|
|
30
|
+
if (isRecord(value)) {
|
|
31
|
+
const normalized = {};
|
|
32
|
+
for (const [key, child] of Object.entries(value)) {
|
|
33
|
+
normalized[key] = normalizePropertyAwareEqualityValue(child);
|
|
34
|
+
}
|
|
35
|
+
return normalized;
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
export function propertyAwareDeepEqual(a, b) {
|
|
40
|
+
return isEqual(normalizePropertyAwareEqualityValue(a), normalizePropertyAwareEqualityValue(b));
|
|
41
|
+
}
|
|
42
|
+
export function collectPropertyAwareMismatchPaths(a, b, path = '', maxPaths = 10) {
|
|
43
|
+
const mismatches = [];
|
|
44
|
+
const visit = (left, right, currentPath) => {
|
|
45
|
+
if (mismatches.length >= maxPaths) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const normalizedLeft = normalizePropertyAwareEqualityValue(left);
|
|
49
|
+
const normalizedRight = normalizePropertyAwareEqualityValue(right);
|
|
50
|
+
if (isEqual(normalizedLeft, normalizedRight)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (Array.isArray(normalizedLeft) && Array.isArray(normalizedRight)) {
|
|
54
|
+
if (normalizedLeft.length !== normalizedRight.length) {
|
|
55
|
+
mismatches.push(currentPath || '(root)');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
normalizedLeft.forEach((item, index) => {
|
|
59
|
+
visit(item, normalizedRight[index], currentPath ? `${currentPath}.${index}` : String(index));
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (isRecord(normalizedLeft) && isRecord(normalizedRight)) {
|
|
64
|
+
const keys = new Set([...Object.keys(normalizedLeft), ...Object.keys(normalizedRight)]);
|
|
65
|
+
for (const key of keys) {
|
|
66
|
+
visit(normalizedLeft[key], normalizedRight[key], currentPath ? `${currentPath}.${key}` : key);
|
|
67
|
+
if (mismatches.length >= maxPaths) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
mismatches.push(currentPath || '(root)');
|
|
74
|
+
};
|
|
75
|
+
visit(a, b, path);
|
|
76
|
+
return mismatches;
|
|
77
|
+
}
|