@burnsred/entity 0.6.1 → 1.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.
Files changed (82) hide show
  1. package/README.md +174 -1
  2. package/dist/cleaner/as-boolean.d.ts +7 -0
  3. package/dist/cleaner/as-boolean.js +10 -0
  4. package/dist/cleaner/as-boolean.js.map +1 -0
  5. package/dist/cleaner/as-date.d.ts +1 -0
  6. package/dist/cleaner/as-date.js +5 -0
  7. package/dist/cleaner/as-date.js.map +1 -0
  8. package/dist/cleaner/as-integer.d.ts +1 -0
  9. package/dist/cleaner/as-integer.js +5 -0
  10. package/dist/cleaner/as-integer.js.map +1 -0
  11. package/dist/cleaner/as-number.d.ts +1 -0
  12. package/dist/cleaner/as-number.js +5 -0
  13. package/dist/cleaner/as-number.js.map +1 -0
  14. package/dist/cleaner/as-string.d.ts +1 -0
  15. package/dist/cleaner/as-string.js +4 -0
  16. package/dist/cleaner/as-string.js.map +1 -0
  17. package/dist/cleaner/collapse-spaces.d.ts +4 -0
  18. package/dist/cleaner/collapse-spaces.js +7 -0
  19. package/dist/cleaner/collapse-spaces.js.map +1 -0
  20. package/dist/cleaner/index.d.ts +7 -0
  21. package/dist/cleaner/index.js +8 -0
  22. package/dist/cleaner/index.js.map +1 -0
  23. package/dist/cleaner/trim.d.ts +1 -0
  24. package/dist/cleaner/trim.js +4 -0
  25. package/dist/cleaner/trim.js.map +1 -0
  26. package/dist/entity/Entity.d.ts +20 -0
  27. package/dist/entity/Entity.js +63 -0
  28. package/dist/entity/Entity.js.map +1 -0
  29. package/dist/entity/index.d.ts +8 -0
  30. package/dist/entity/index.js +9 -0
  31. package/dist/entity/index.js.map +1 -0
  32. package/dist/field/BooleanField.d.ts +7 -0
  33. package/dist/field/BooleanField.js +8 -0
  34. package/dist/field/BooleanField.js.map +1 -0
  35. package/dist/field/CharField.d.ts +7 -0
  36. package/dist/field/CharField.js +8 -0
  37. package/dist/field/CharField.js.map +1 -0
  38. package/dist/field/DateField.d.ts +9 -0
  39. package/dist/field/DateField.js +19 -0
  40. package/dist/field/DateField.js.map +1 -0
  41. package/dist/field/DateTimeField.d.ts +9 -0
  42. package/dist/field/DateTimeField.js +14 -0
  43. package/dist/field/DateTimeField.js.map +1 -0
  44. package/dist/field/EntityField.d.ts +22 -0
  45. package/dist/field/EntityField.js +55 -0
  46. package/dist/field/EntityField.js.map +1 -0
  47. package/dist/field/Field.d.ts +42 -0
  48. package/dist/field/Field.js +97 -0
  49. package/dist/field/Field.js.map +1 -0
  50. package/dist/field/IdField.d.ts +15 -0
  51. package/dist/field/IdField.js +23 -0
  52. package/dist/field/IdField.js.map +1 -0
  53. package/dist/field/IntegerField.d.ts +6 -0
  54. package/dist/field/IntegerField.js +9 -0
  55. package/dist/field/IntegerField.js.map +1 -0
  56. package/dist/field/NumberField.d.ts +7 -0
  57. package/dist/field/NumberField.js +8 -0
  58. package/dist/field/NumberField.js.map +1 -0
  59. package/dist/field/TextField.d.ts +4 -0
  60. package/dist/field/TextField.js +5 -0
  61. package/dist/field/TextField.js.map +1 -0
  62. package/dist/field/index.d.ts +68 -0
  63. package/dist/field/index.js +12 -0
  64. package/dist/field/index.js.map +1 -0
  65. package/dist/index.d.ts +67 -0
  66. package/dist/index.js +5 -5
  67. package/dist/index.js.map +1 -0
  68. package/dist/validator/entity-valid.d.ts +9 -0
  69. package/dist/validator/entity-valid.js +12 -0
  70. package/dist/validator/entity-valid.js.map +1 -0
  71. package/dist/validator/index.d.ts +2 -0
  72. package/dist/validator/index.js +3 -0
  73. package/dist/validator/index.js.map +1 -0
  74. package/dist/validator/value-range.d.ts +8 -0
  75. package/dist/validator/value-range.js +24 -0
  76. package/dist/validator/value-range.js.map +1 -0
  77. package/package.json +21 -34
  78. package/CHANGELOG.md +0 -85
  79. package/LICENSE +0 -21
  80. package/dist/development.js +0 -1377
  81. package/dist/development.js.map +0 -1
  82. package/dist/production.min.js +0 -1
package/README.md CHANGED
@@ -1 +1,174 @@
1
- # @burnsred/entity
1
+ # Entity
2
+
3
+ The Entity package provides the fundamental Entity class and it supporting
4
+ utilities.
5
+
6
+ An Entity is used to provide cleaned, validated data records.
7
+
8
+ ## Defining an Entity
9
+
10
+ ```js
11
+ import { Entity, CharField } from "@burnsred/entity";
12
+
13
+ type User {
14
+ username: string;
15
+ email: string;
16
+ name?: string;
17
+ }
18
+
19
+ const UserEntity = new Entity<User>({
20
+ idField: 'username',
21
+ fields: {
22
+ username: new CharField(),
23
+ email: new CharField(),
24
+ name: new CharField({ blank: true }),
25
+ }
26
+ });
27
+ ```
28
+
29
+ Once your entity is defined, you can use it to construct an `User` from
30
+ API response data:
31
+
32
+ ```js
33
+ const rec = UserEntity.fromData(data);
34
+ ```
35
+
36
+ ## Declaring Fields
37
+
38
+ Each field can specify its own list of validators and cleaners.
39
+
40
+ Cleaners are used to cast input values from the User to the right type.
41
+
42
+ Validators are used to check supplied values, and emit Errors.
43
+
44
+ ```js
45
+ fields: {
46
+ uuid: new CharField({ cleaners=[trim] }),
47
+ size: new IntegerField({ validators=[maxValue(10)] })
48
+ }
49
+ ```
50
+
51
+ ### Common field arguments.
52
+
53
+ blank (boolean) false
54
+ : Is this field allowed to be omitted?
55
+
56
+ cleaners (FieldCleaner[])
57
+ : A list of cleaners to apply when cleaning an record.
58
+
59
+ validators (FieldValidator[])
60
+ : A list of validators to apply when validating an record.
61
+
62
+ many (boolean) false
63
+ : If true, the value for this field is an array of values of its type.
64
+
65
+ choices (any) []
66
+ : A list of valid choices. Can help with select lists.
67
+
68
+ defaultValue
69
+ : Override the field's default value.
70
+
71
+ Any extra properties passed will be stored in the `extra` property.
72
+
73
+ ## Cleaners
74
+
75
+ Cleaner functions are primarily used for cleaning and casting data coming from
76
+ the User.
77
+
78
+ Since most HTML Input fields work with string values, many `Field`s will
79
+ default to using a cleaner which casts to the correct type.
80
+
81
+ ## Validators
82
+
83
+ Validator functions are used to check constraints, emitting FieldErrors to
84
+ identify problems.
85
+
86
+ They can be specified either on the Field or Entitly.
87
+
88
+ Field validators are passed the value for that specific field, and are called
89
+ with `this` being the Field.
90
+
91
+ Entity validators are passed the whole record, and are called with `this` being
92
+ the Entity.
93
+
94
+ As an example, here is an Entity validator that ensures the start date is
95
+ before the end date:
96
+
97
+ ```js
98
+ function dateRange(record) {
99
+ const start = record.get("start_date");
100
+ const end = record.get("end_date");
101
+
102
+ if (start.valueOf() >= end.valueOf()) {
103
+ return {
104
+ code: "date-range",
105
+ message: "Start date must be before End date.",
106
+ };
107
+ }
108
+ }
109
+ ```
110
+
111
+ # Errors
112
+
113
+ Managing and handling validation errors requires records to contain them.
114
+
115
+ An individual validation error consists of a `code`, user meangful `message`,
116
+ and possibly some additional `details`.
117
+
118
+ For a scalar field, this can be managed as a list of such objects.
119
+
120
+ For vector values (i.e. many=True) a list of lists will suffice. Extracting
121
+ errors for a given position is as simple as indexing.
122
+
123
+ For an `EntityField`, a mapping is used, instead. Each value, keyed by
124
+ sub-field name, reflects the structure appropriate as described.
125
+
126
+ ```js
127
+ IntegerField.validate("1") // [{ code: 'type', message: 'Value must be a number' }]
128
+
129
+ EntityField.validate({ ... }) // { field: [...], ...}
130
+ ```
131
+
132
+ From the top down, an Entity will get errors by looking into the Error map for
133
+ the field name.
134
+
135
+ The values will be either a list of Errors, a list-of-lists of Errors, or
136
+ another map.
137
+
138
+ # A more complex example
139
+
140
+ Here's an example showing an Entity with a nested Entity as a field.
141
+
142
+ ```js
143
+ type Post = {
144
+ id: number;
145
+ user: User;
146
+ posted: Date;
147
+ content: string;
148
+ public: boolean;
149
+ }
150
+
151
+
152
+ const PostEntity = new Entity<Post>({
153
+ fields={
154
+ id: IntegerField(),
155
+ user: EntityField<Post>({ entity: UserEntity }),
156
+ posted: DateField(),
157
+ content: TextField(),
158
+ public: BooleanField({ defaultVaule: false }),
159
+ }
160
+ })
161
+ ```
162
+
163
+ Now any record created by this Entity will contain a nested record in the `user`
164
+ field.
165
+
166
+ ```js
167
+
168
+ const post = PostEntity.fromData(...);
169
+
170
+ ...
171
+
172
+ const errors = UserEntity.validate(post.user);
173
+
174
+ ```
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Cleaner function which casts the value to a Boolean
3
+ *
4
+ * @param value
5
+ * @returns boolean
6
+ */
7
+ export default function asBoolean(value: unknown): boolean;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Cleaner function which casts the value to a Boolean
3
+ *
4
+ * @param value
5
+ * @returns boolean
6
+ */
7
+ export default function asBoolean(value) {
8
+ return Boolean(value);
9
+ }
10
+ //# sourceMappingURL=as-boolean.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"as-boolean.js","sourceRoot":"","sources":["../../src/cleaner/as-boolean.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,KAAc;IAC9C,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC;AACxB,CAAC"}
@@ -0,0 +1 @@
1
+ export default function asDate(value: unknown): Date | null;
@@ -0,0 +1,5 @@
1
+ export default function asDate(value) {
2
+ const ts = Date.parse(value);
3
+ return isNaN(ts) ? null : new Date(ts);
4
+ }
5
+ //# sourceMappingURL=as-date.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"as-date.js","sourceRoot":"","sources":["../../src/cleaner/as-date.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,KAAc;IAC3C,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,KAAe,CAAC,CAAC;IAEvC,OAAO,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1 @@
1
+ export default function asInteger(value: unknown): number | null;
@@ -0,0 +1,5 @@
1
+ export default function asInteger(value) {
2
+ const val = Number.parseInt(value, 10);
3
+ return Number.isNaN(val) ? null : val;
4
+ }
5
+ //# sourceMappingURL=as-integer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"as-integer.js","sourceRoot":"","sources":["../../src/cleaner/as-integer.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,KAAc;IAC9C,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAe,EAAE,EAAE,CAAC,CAAC;IAEjD,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;AACxC,CAAC"}
@@ -0,0 +1 @@
1
+ export default function asNumber(value: unknown): number | null;
@@ -0,0 +1,5 @@
1
+ export default function asNumber(value) {
2
+ const val = Number.parseFloat(value);
3
+ return Number.isNaN(val) ? null : val;
4
+ }
5
+ //# sourceMappingURL=as-number.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"as-number.js","sourceRoot":"","sources":["../../src/cleaner/as-number.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,KAAc;IAC7C,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,KAAe,CAAC,CAAC;IAE/C,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;AACxC,CAAC"}
@@ -0,0 +1 @@
1
+ export default function asString(value: unknown): string;
@@ -0,0 +1,4 @@
1
+ export default function asString(value) {
2
+ return value.toString();
3
+ }
4
+ //# sourceMappingURL=as-string.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"as-string.js","sourceRoot":"","sources":["../../src/cleaner/as-string.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,KAAc;IAC7C,OAAQ,KAAgB,CAAC,QAAQ,EAAE,CAAC;AACtC,CAAC"}
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Replaces multiple whitespace characters within a string with a single space.
3
+ */
4
+ export default function collapseSpaces(value: unknown): string;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Replaces multiple whitespace characters within a string with a single space.
3
+ */
4
+ export default function collapseSpaces(value) {
5
+ return value.replace(/\s\s+/g, ' ');
6
+ }
7
+ //# sourceMappingURL=collapse-spaces.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collapse-spaces.js","sourceRoot":"","sources":["../../src/cleaner/collapse-spaces.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,KAAc;IACnD,OAAQ,KAAgB,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;AAClD,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { default as collapseSpaces } from './collapse-spaces';
2
+ export { default as asString } from './as-string';
3
+ export { default as asBoolean } from './as-boolean';
4
+ export { default as asNumber } from './as-number';
5
+ export { default as asInteger } from './as-integer';
6
+ export { default as trim } from './trim';
7
+ export { default as asDate } from './as-date';
@@ -0,0 +1,8 @@
1
+ export { default as collapseSpaces } from './collapse-spaces';
2
+ export { default as asString } from './as-string';
3
+ export { default as asBoolean } from './as-boolean';
4
+ export { default as asNumber } from './as-number';
5
+ export { default as asInteger } from './as-integer';
6
+ export { default as trim } from './trim';
7
+ export { default as asDate } from './as-date';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cleaner/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1 @@
1
+ export default function trim(value: unknown): unknown;
@@ -0,0 +1,4 @@
1
+ export default function trim(value) {
2
+ return value.trim();
3
+ }
4
+ //# sourceMappingURL=trim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trim.js","sourceRoot":"","sources":["../../src/cleaner/trim.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,KAAc;IACzC,OAAQ,KAAgB,CAAC,IAAI,EAAE,CAAC;AAClC,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { EntityRecordCleaner, EntityRecordValidator, EntityType, FieldErrorMap } from '..';
2
+ import { EntityField, type FieldConstructorParams, type FieldType } from '../field';
3
+ type EntityConstructorParams<T> = Pick<EntityType<T>, 'idField' | 'fields'> & Partial<Pick<EntityType<T>, 'cleaners' | 'validators'>>;
4
+ export default class Entity<T> implements EntityType<T> {
5
+ idField: keyof T;
6
+ fields: {
7
+ [F in keyof T]: FieldType<T[F]>;
8
+ };
9
+ cleaners: EntityRecordCleaner<T>[];
10
+ validators: EntityRecordValidator<T>[];
11
+ extra: object;
12
+ constructor({ idField, fields, cleaners, validators, ...extra }: EntityConstructorParams<T>);
13
+ fromData(data: Record<string, unknown>): T;
14
+ toData(record: T): Record<string, unknown>;
15
+ clean(record: T): T;
16
+ validate(record: T): FieldErrorMap;
17
+ getId(record?: T): string | undefined;
18
+ getEntityField(options?: FieldConstructorParams<T>): EntityField<T>;
19
+ }
20
+ export {};
@@ -0,0 +1,63 @@
1
+ import { EntityField, } from '../field';
2
+ export default class Entity {
3
+ idField;
4
+ fields;
5
+ cleaners;
6
+ validators;
7
+ extra;
8
+ constructor({ idField, fields, cleaners = [], validators = [], ...extra }) {
9
+ this.idField = idField;
10
+ this.fields = fields;
11
+ this.cleaners = cleaners;
12
+ this.validators = validators;
13
+ this.extra = extra;
14
+ // Set field names
15
+ Object.entries(this.fields).forEach(([fieldName, field]) => (field.name = fieldName));
16
+ }
17
+ fromData(data) {
18
+ return Object.fromEntries(Object.entries(this.fields).map(([fieldName, field]) => [
19
+ fieldName,
20
+ fieldName in data
21
+ ? field.fromData(data[fieldName])
22
+ : field.default({ data }),
23
+ ]));
24
+ }
25
+ toData(record) {
26
+ const result = {};
27
+ Object.entries(this.fields).forEach(([fieldName, field]) => {
28
+ if (field.readOnly)
29
+ return;
30
+ result[fieldName] = field.toData(record[fieldName]);
31
+ });
32
+ return result;
33
+ }
34
+ clean(record) {
35
+ record = Object.values(this.fields).reduce(
36
+ // TS can't infer the type of Field.clean()
37
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
38
+ // @ts-ignore
39
+ (curr, field) => {
40
+ curr[field.name] = field.clean(curr[field.name]);
41
+ return curr;
42
+ }, record);
43
+ return this.cleaners.reduce((curr, cleaner) => cleaner.call(this, curr), record);
44
+ }
45
+ validate(record) {
46
+ const errors = Object.fromEntries(Object.entries(this.fields)
47
+ .map(([fieldName, field]) => [
48
+ fieldName,
49
+ field.validate(record[fieldName]),
50
+ ])
51
+ // Remove empty entries
52
+ .filter(([_name, err]) => Array.isArray(err) ? err.length : err !== undefined));
53
+ this.validators.forEach((validator) => validator(record, errors));
54
+ return errors;
55
+ }
56
+ getId(record) {
57
+ return record == null ? undefined : record[this.idField];
58
+ }
59
+ getEntityField(options = {}) {
60
+ return new EntityField({ entity: this, ...options });
61
+ }
62
+ }
63
+ //# sourceMappingURL=Entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Entity.js","sourceRoot":"","sources":["../../src/entity/Entity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Cleaners provide casting and conversion functions for user input.
3
+ *
4
+ * This may include type conversion, whitespace trimming/normalising, etc.
5
+ *
6
+ * It does not include validation, and can not yield errors.
7
+ */
8
+ export { default as Entity } from './Entity';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Cleaners provide casting and conversion functions for user input.
3
+ *
4
+ * This may include type conversion, whitespace trimming/normalising, etc.
5
+ *
6
+ * It does not include validation, and can not yield errors.
7
+ */
8
+ export { default as Entity } from './Entity';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/entity/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { asBoolean } from '../cleaner';
2
+ import { Field } from '.';
3
+ export declare class BooleanField extends Field<boolean> {
4
+ static defaultValue: boolean;
5
+ static defaultCleaners: (typeof asBoolean)[];
6
+ type: string;
7
+ }
@@ -0,0 +1,8 @@
1
+ import { asBoolean } from '../cleaner';
2
+ import { Field } from '.';
3
+ export class BooleanField extends Field {
4
+ static defaultValue = false;
5
+ static defaultCleaners = [asBoolean];
6
+ type = 'boolean';
7
+ }
8
+ //# sourceMappingURL=BooleanField.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BooleanField.js","sourceRoot":"","sources":["../../src/field/BooleanField.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC;AAE1B,MAAM,OAAO,YAAa,SAAQ,KAAc;IAC9C,MAAM,CAAC,YAAY,GAAG,KAAK,CAAC;IAC5B,MAAM,CAAC,eAAe,GAAG,CAAC,SAAS,CAAC,CAAC;IAErC,IAAI,GAAG,SAAS,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { asString } from '../cleaner';
2
+ import { Field } from '.';
3
+ export declare class CharField extends Field<string> {
4
+ static defaultValue: string;
5
+ static defaultCleaners: (typeof asString)[];
6
+ type: string;
7
+ }
@@ -0,0 +1,8 @@
1
+ import { asString } from '../cleaner';
2
+ import { Field } from '.';
3
+ export class CharField extends Field {
4
+ static defaultValue = '';
5
+ static defaultCleaners = [asString];
6
+ type = 'char';
7
+ }
8
+ //# sourceMappingURL=CharField.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CharField.js","sourceRoot":"","sources":["../../src/field/CharField.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC;AAE1B,MAAM,OAAO,SAAU,SAAQ,KAAa;IAC1C,MAAM,CAAC,YAAY,GAAG,EAAE,CAAC;IACzB,MAAM,CAAC,eAAe,GAAG,CAAC,QAAQ,CAAC,CAAC;IAEpC,IAAI,GAAG,MAAM,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { asDate } from '../cleaner';
2
+ import { Field } from '.';
3
+ export declare class DateField extends Field<Date | null> {
4
+ static defaultValue: null;
5
+ static defaultCleaners: (typeof asDate)[];
6
+ type: string;
7
+ fromData(value: unknown): Date | null;
8
+ toData(value: Date | null): unknown;
9
+ }
@@ -0,0 +1,19 @@
1
+ import { asDate } from '../cleaner';
2
+ import { Field } from '.';
3
+ export class DateField extends Field {
4
+ static defaultValue = null;
5
+ static defaultCleaners = [asDate];
6
+ type = 'date';
7
+ fromData(value) {
8
+ return asDate(value);
9
+ }
10
+ toData(value) {
11
+ // toISOString always converts to UTC
12
+ // We must adjust our timezone accordingly, lest we send the wrong date
13
+ if (value instanceof Date) {
14
+ value.setMinutes(value.getMinutes() - value.getTimezoneOffset());
15
+ }
16
+ return value ? value.toISOString().split('T')[0] : '';
17
+ }
18
+ }
19
+ //# sourceMappingURL=DateField.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DateField.js","sourceRoot":"","sources":["../../src/field/DateField.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC;AAE1B,MAAM,OAAO,SAAU,SAAQ,KAAkB;IAC/C,MAAM,CAAC,YAAY,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,eAAe,GAAG,CAAC,MAAM,CAAC,CAAC;IAElC,IAAI,GAAG,MAAM,CAAC;IAEd,QAAQ,CAAC,KAAc;QACrB,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAED,MAAM,CAAC,KAAkB;QACvB,qCAAqC;QACrC,uEAAuE;QACvE,IAAI,KAAK,YAAY,IAAI,EAAE;YACzB,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,UAAU,EAAE,GAAG,KAAK,CAAC,iBAAiB,EAAE,CAAC,CAAC;SAClE;QACD,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACxD,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { asDate } from '../cleaner';
2
+ import { Field } from '.';
3
+ export declare class DateTimeField extends Field<Date | null> {
4
+ static defaultValue: null;
5
+ static defaultCleaners: (typeof asDate)[];
6
+ type: string;
7
+ fromData(value: unknown): Date | null;
8
+ toData(value: Date | null): unknown;
9
+ }
@@ -0,0 +1,14 @@
1
+ import { asDate } from '../cleaner';
2
+ import { Field } from '.';
3
+ export class DateTimeField extends Field {
4
+ static defaultValue = null;
5
+ static defaultCleaners = [asDate];
6
+ type = 'date';
7
+ fromData(value) {
8
+ return asDate(value);
9
+ }
10
+ toData(value) {
11
+ return value ? value.toISOString() : '';
12
+ }
13
+ }
14
+ //# sourceMappingURL=DateTimeField.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DateTimeField.js","sourceRoot":"","sources":["../../src/field/DateTimeField.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC;AAE1B,MAAM,OAAO,aAAc,SAAQ,KAAkB;IACnD,MAAM,CAAC,YAAY,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,eAAe,GAAG,CAAC,MAAM,CAAC,CAAC;IAElC,IAAI,GAAG,MAAM,CAAC;IAEd,QAAQ,CAAC,KAAc;QACrB,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAED,MAAM,CAAC,KAAkB;QACvB,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1C,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { type EntityType, type ErrorSet, type FieldErrorMap, type ValidatorResult } from '..';
2
+ import { Field } from '.';
3
+ import type { FieldConstructorParams, FieldType } from '.';
4
+ type EntityFieldConstructorParams<T> = FieldConstructorParams<T> & {
5
+ entity: EntityType<T>;
6
+ };
7
+ export declare class EntityField<T> extends Field<T> {
8
+ type: string;
9
+ entity: EntityType<T>;
10
+ constructor({ entity, ...options }: EntityFieldConstructorParams<T>);
11
+ getValue(value: T, key?: string | number): T | NonNullable<T>[keyof T];
12
+ validate(value: unknown): ValidatorResult;
13
+ /**
14
+ * EntityField is a special case.
15
+ *
16
+ * A Form Input may be asking us for the errors of a specific field.
17
+ */
18
+ getErrors(errors: FieldErrorMap, key?: number | string): ErrorSet;
19
+ getId(value?: T): string | undefined;
20
+ getField(name: keyof T): FieldType<T[keyof T]>;
21
+ }
22
+ export {};
@@ -0,0 +1,55 @@
1
+ import { Field } from '.';
2
+ export class EntityField extends Field {
3
+ type = 'entity';
4
+ entity;
5
+ constructor({ entity, ...options }) {
6
+ super(options);
7
+ this.entity = entity;
8
+ }
9
+ getValue(value, key) {
10
+ if (!value)
11
+ return value;
12
+ switch (typeof key) {
13
+ case 'string':
14
+ if (this.many) {
15
+ throw Error(`Field ${this.name} [${this.type}] does not support field lookups: ${key}`);
16
+ }
17
+ return value[key];
18
+ case 'number':
19
+ if (!this.many) {
20
+ throw Error(`Field ${this.name} [${this.type}] does not support index lookups`);
21
+ }
22
+ return value[key];
23
+ case 'undefined':
24
+ return value;
25
+ }
26
+ }
27
+ validate(value) {
28
+ if (!value) {
29
+ if (!this.blank) {
30
+ return [{ code: 'blank', message: 'Field must not be blank' }];
31
+ }
32
+ return;
33
+ }
34
+ return this.entity.validate(value);
35
+ }
36
+ /**
37
+ * EntityField is a special case.
38
+ *
39
+ * A Form Input may be asking us for the errors of a specific field.
40
+ */
41
+ getErrors(errors, key) {
42
+ if (key !== undefined) {
43
+ return errors[key];
44
+ }
45
+ return errors;
46
+ }
47
+ getId(value) {
48
+ return value && this.entity.getId(value);
49
+ }
50
+ // Used by Form
51
+ getField(name) {
52
+ return this.entity.fields[name];
53
+ }
54
+ }
55
+ //# sourceMappingURL=EntityField.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EntityField.js","sourceRoot":"","sources":["../../src/field/EntityField.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC;AAO1B,MAAM,OAAO,WAAe,SAAQ,KAAQ;IAC1C,IAAI,GAAG,QAAQ,CAAC;IAChB,MAAM,CAAgB;IAEtB,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO,EAAmC;QACjE,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,QAAQ,CAAC,KAAQ,EAAE,GAAqB;QACtC,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QACzB,QAAQ,OAAO,GAAG,EAAE;YAClB,KAAK,QAAQ;gBACX,IAAI,IAAI,CAAC,IAAI,EAAE;oBACb,MAAM,KAAK,CACT,SAAS,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,qCAAqC,GAAG,EAAE,CAC3E,CAAC;iBACH;gBACD,OAAO,KAAK,CAAC,GAAc,CAAC,CAAC;YAC/B,KAAK,QAAQ;gBACX,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;oBACd,MAAM,KAAK,CACT,SAAS,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,kCAAkC,CACnE,CAAC;iBACH;gBACD,OAAQ,KAAa,CAAC,GAAG,CAAC,CAAC;YAC7B,KAAK,WAAW;gBACd,OAAO,KAAK,CAAC;SAChB;IACH,CAAC;IAED,QAAQ,CAAC,KAAc;QACrB,IAAI,CAAC,KAAK,EAAE;YACV,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;gBACf,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC,CAAC;aAChE;YACD,OAAO;SACR;QACD,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAU,CAAC,CAAC;IAC1C,CAAC;IAED;;;;OAIG;IACH,SAAS,CAAC,MAAqB,EAAE,GAAqB;QACpD,IAAI,GAAG,KAAK,SAAS,EAAE;YACrB,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;SACpB;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,KAAS;QACb,OAAO,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAU,CAAC,CAAC;IAChD,CAAC;IAED,eAAe;IACf,QAAQ,CAAC,IAAa;QACpB,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;CACF"}