@quatrain/core 1.2.5 → 1.2.7

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 (67) hide show
  1. package/dist/Core.d.ts.map +1 -1
  2. package/dist/Core.js.map +1 -1
  3. package/dist/common/ResourcesErrors.d.ts.map +1 -1
  4. package/dist/common/ResourcesErrors.js.map +1 -1
  5. package/dist/components/AbstractObject.d.ts.map +1 -1
  6. package/dist/components/AbstractObject.js.map +1 -1
  7. package/dist/components/BaseObject.d.ts.map +1 -1
  8. package/dist/components/BaseObject.js.map +1 -1
  9. package/dist/components/DataObject.d.ts.map +1 -1
  10. package/dist/components/DataObject.js.map +1 -1
  11. package/dist/components/Entity.d.ts.map +1 -1
  12. package/dist/components/Entity.js.map +1 -1
  13. package/dist/components/ObjectUri.d.ts.map +1 -1
  14. package/dist/components/ObjectUri.js.map +1 -1
  15. package/dist/components/User.d.ts.map +1 -1
  16. package/dist/components/User.js.map +1 -1
  17. package/dist/properties/ArrayProperty.d.ts.map +1 -1
  18. package/dist/properties/ArrayProperty.js.map +1 -1
  19. package/dist/properties/BaseProperty.d.ts.map +1 -1
  20. package/dist/properties/BaseProperty.js +20 -2
  21. package/dist/properties/BaseProperty.js.map +1 -1
  22. package/dist/properties/BooleanProperty.d.ts.map +1 -1
  23. package/dist/properties/BooleanProperty.js.map +1 -1
  24. package/dist/properties/CollectionProperty.d.ts.map +1 -1
  25. package/dist/properties/CollectionProperty.js.map +1 -1
  26. package/dist/properties/DateTimeProperty.d.ts.map +1 -1
  27. package/dist/properties/DateTimeProperty.js.map +1 -1
  28. package/dist/properties/EnumProperty.d.ts.map +1 -1
  29. package/dist/properties/EnumProperty.js.map +1 -1
  30. package/dist/properties/FileProperty.d.ts.map +1 -1
  31. package/dist/properties/FileProperty.js.map +1 -1
  32. package/dist/properties/HashProperty.d.ts.map +1 -1
  33. package/dist/properties/HashProperty.js +3 -0
  34. package/dist/properties/HashProperty.js.map +1 -1
  35. package/dist/properties/MapProperty.d.ts.map +1 -1
  36. package/dist/properties/MapProperty.js.map +1 -1
  37. package/dist/properties/NumberProperty.d.ts.map +1 -1
  38. package/dist/properties/NumberProperty.js.map +1 -1
  39. package/dist/properties/ObjectProperty.d.ts.map +1 -1
  40. package/dist/properties/ObjectProperty.js.map +1 -1
  41. package/dist/properties/Property.d.ts.map +1 -1
  42. package/dist/properties/Property.js.map +1 -1
  43. package/dist/properties/StringProperty.d.ts.map +1 -1
  44. package/dist/properties/StringProperty.js.map +1 -1
  45. package/package.json +3 -3
  46. package/src/Core.ts +96 -0
  47. package/src/common/ResourcesErrors.ts +12 -0
  48. package/src/components/AbstractObject.ts +33 -3
  49. package/src/components/BaseObject.ts +37 -0
  50. package/src/components/DataObject.ts +45 -0
  51. package/src/components/Entity.ts +12 -0
  52. package/src/components/ObjectUri.ts +11 -0
  53. package/src/components/User.ts +12 -0
  54. package/src/properties/ArrayProperty.ts +48 -0
  55. package/src/properties/BaseProperty.test.ts +31 -0
  56. package/src/properties/BaseProperty.ts +81 -4
  57. package/src/properties/BooleanProperty.ts +26 -0
  58. package/src/properties/CollectionProperty.ts +35 -0
  59. package/src/properties/DateTimeProperty.ts +35 -0
  60. package/src/properties/EnumProperty.ts +34 -0
  61. package/src/properties/FileProperty.ts +17 -0
  62. package/src/properties/HashProperty.ts +63 -0
  63. package/src/properties/MapProperty.ts +31 -0
  64. package/src/properties/NumberProperty.ts +55 -0
  65. package/src/properties/ObjectProperty.ts +46 -0
  66. package/src/properties/Property.ts +24 -0
  67. package/src/properties/StringProperty.ts +54 -0
package/src/Core.ts CHANGED
@@ -7,14 +7,30 @@ import {
7
7
  import { spawn } from 'node:child_process'
8
8
  import which from 'which'
9
9
 
10
+ /**
11
+ * Core foundation class for Quatrain architecture.
12
+ * Manages central configuration, logger registry, storage binding, and class mapping.
13
+ */
10
14
  export class Core {
15
+ /** Identifying namespace for this core component. */
11
16
  static readonly me = this.name
17
+ /** Persistent key-value storage engine reference. */
12
18
  static readonly storage = require('node-persist')
19
+ /** Context prefix string for scoped storage keys. */
13
20
  static readonly storagePrefix = 'core'
21
+ /** Dictionary holding registered active Quatrain models/components. */
14
22
  static readonly classRegistry: { [key: string]: any } = {}
23
+ /** System-wide base log verbosity. */
15
24
  static readonly logLevel = LogLevel.DEBUG
25
+ /** Active logger instance for the Core domain. */
16
26
  static readonly logger: AbstractLoggerAdapter = this.addLogger()
17
27
 
28
+ /**
29
+ * Injects a new logger block under a specific namespace alias.
30
+ *
31
+ * @param alias - The logging context name.
32
+ * @returns Instantiated LoggerAdapter.
33
+ */
18
34
  static addLogger(alias: string = this.name) {
19
35
  return Log.addLogger(
20
36
  '@' + alias,
@@ -23,13 +39,27 @@ export class Core {
23
39
  )
24
40
  }
25
41
 
42
+ /**
43
+ * Mutates the underlying verbosity constraints.
44
+ *
45
+ * @param level - Active LogLevel filter.
46
+ */
26
47
  static setLogLevel(level: LogLevel) {
27
48
  this.logger.logLevel(level)
28
49
  }
29
50
 
30
51
  // How timestamp are formatted
52
+ /**
53
+ * Returns an ISO string representing the current time.
54
+ */
31
55
  static readonly timestamp = () => new Date().toISOString()
32
56
 
57
+ /**
58
+ * Deprecated: Reserved schema definition hook.
59
+ *
60
+ * @param key - The property block to generate.
61
+ * @returns Field definitions block.
62
+ */
33
63
  static definition(key: string) {
34
64
  return {
35
65
  manifest: {
@@ -39,6 +69,12 @@ export class Core {
39
69
  }
40
70
  }
41
71
 
72
+ /**
73
+ * Stores a primitive value durably in the core storage instance.
74
+ *
75
+ * @param key - Identification string.
76
+ * @param value - Value.
77
+ */
42
78
  static async addConfig(key: string, value: any) {
43
79
  if (!this.storage.set) {
44
80
  await this.storage.init()
@@ -46,6 +82,12 @@ export class Core {
46
82
  await this.storage.set(`${this.storagePrefix}_${key}`, value)
47
83
  }
48
84
 
85
+ /**
86
+ * Recovers a durably persisted value from the storage layer.
87
+ *
88
+ * @param key - The target identifier.
89
+ * @returns The recovered value.
90
+ */
49
91
  static async getConfig(key: string) {
50
92
  if (!this.storage.get) {
51
93
  await this.storage.init()
@@ -53,10 +95,22 @@ export class Core {
53
95
  return await this.storage.get(`${this.storagePrefix}_${key}`)
54
96
  }
55
97
 
98
+ /**
99
+ * Maps a specific entity class to an active name so the factory reflection can locate it.
100
+ *
101
+ * @param name - Semantic registry name.
102
+ * @param obj - Class constructor.
103
+ */
56
104
  static addClass(name: string, obj: any) {
57
105
  Core.classRegistry[name] = obj
58
106
  }
59
107
 
108
+ /**
109
+ * Returns an injected class constructor by its registry identifier.
110
+ *
111
+ * @param name - The semantic name to resolve.
112
+ * @returns Class definition.
113
+ */
60
114
  static getClass(name: string) {
61
115
  return Core.classRegistry[name]
62
116
  }
@@ -104,9 +158,21 @@ export class Core {
104
158
  }
105
159
  }
106
160
 
161
+ /**
162
+ * Utility lookup to find executable paths in the system using `which`.
163
+ *
164
+ * @param command - The executable.
165
+ * @returns The resolved system path.
166
+ */
107
167
  static readonly getSystemCommandPath = (command: string): Promise<string> =>
108
168
  which(command)
109
169
 
170
+ /**
171
+ * Execution suspension utility blocking the event loop context.
172
+ *
173
+ * @param seconds - Duration count.
174
+ * @returns The promise to await.
175
+ */
110
176
  static sleep(seconds: number = 1) {
111
177
  return new Promise((resolve) => setTimeout(resolve, seconds * 1000))
112
178
  }
@@ -119,26 +185,56 @@ export class Core {
119
185
  this.addClass('User', cls)
120
186
  }
121
187
 
188
+ /**
189
+ * Triggers a standard log on the core logger.
190
+ *
191
+ * @param message - Content to log.
192
+ */
122
193
  static log(...message: any): void {
123
194
  return this.logger.log(message)
124
195
  }
125
196
 
197
+ /**
198
+ * Triggers a debug log on the core logger.
199
+ *
200
+ * @param message - Content to log.
201
+ */
126
202
  static debug(...message: any): void {
127
203
  return this.logger.debug(message)
128
204
  }
129
205
 
206
+ /**
207
+ * Triggers a warning log on the core logger.
208
+ *
209
+ * @param message - Content to log.
210
+ */
130
211
  static warn(...message: any): void {
131
212
  return this.logger.warn(message)
132
213
  }
133
214
 
215
+ /**
216
+ * Triggers an info log on the core logger.
217
+ *
218
+ * @param message - Content to log.
219
+ */
134
220
  static info(...message: any): void {
135
221
  return this.logger.info(message)
136
222
  }
137
223
 
224
+ /**
225
+ * Triggers an error log on the core logger.
226
+ *
227
+ * @param message - Content to log.
228
+ */
138
229
  static error(...message: any): void {
139
230
  return this.logger.error(message)
140
231
  }
141
232
 
233
+ /**
234
+ * Triggers a trace log on the core logger.
235
+ *
236
+ * @param message - Content to log.
237
+ */
142
238
  static trace(...message: any): void {
143
239
  return this.logger.trace(message)
144
240
  }
@@ -1,3 +1,6 @@
1
+ /**
2
+ * Global abstraction identifying Quatrain-specific execution exceptions.
3
+ */
1
4
  export class ResourceError extends Error {
2
5
  constructor(message: string) {
3
6
  super(message)
@@ -6,17 +9,26 @@ export class ResourceError extends Error {
6
9
  }
7
10
  }
8
11
 
12
+ /** Indicates a structurally flawed request (e.g., HTTP 400). */
9
13
  export class BadRequestError extends ResourceError {}
10
14
 
15
+ /** Indicates missing or invalid authentication credentials (e.g., HTTP 401). */
11
16
  export class UnauthorizedError extends ResourceError {}
12
17
 
18
+ /** Indicates an authenticated action denied by privileges (e.g., HTTP 403). */
13
19
  export class ForbiddenError extends ResourceError {}
14
20
 
21
+ /** Indicates a non-existent database or file resource lookup (e.g., HTTP 404). */
15
22
  export class NotFoundError extends ResourceError {}
16
23
 
24
+ /** Indicates an originally valid asset that has been purged (e.g., HTTP 410). */
17
25
  export class GoneError extends ResourceError {}
18
26
 
27
+ /**
28
+ * Indicates property rejection. Holds a payload of granular property-specific validation issues.
29
+ */
19
30
  export class ValidationError extends ResourceError {
31
+ /** Detailed key-value map linking property names to specific violation causes. */
20
32
  public errors: Record<string, string>
21
33
 
22
34
  constructor(message: string, errors: Record<string, string> = {}) {
@@ -1,16 +1,22 @@
1
1
  import { DataObjectClass } from './types/DataObjectClass'
2
2
  import { DataObjectProperties } from '../properties'
3
3
 
4
+ /**
5
+ * Foundational wrapper for interacting with properties dynamically.
6
+ */
4
7
  export abstract class AbstractObject {
8
+ /** Array defining the structure and constraints of properties belonging to this model. */
5
9
  static PROPS_DEFINITION: DataObjectProperties = []
6
10
 
7
- // Which name to use in backend as table/collection identifer
11
+ /** The backend identifier (table or collection name) representing this class. */
8
12
  static COLLECTION: string | undefined
9
13
 
10
- // which property handles relation to parent
14
+ /** The name of the property handling hierarchical parent relationships. */
11
15
  static PARENT_PROP: string | undefined
12
16
 
13
- // Which property's value to use in backend as label for object reference
17
+ /**
18
+ * Which property's value to use in backend as label for object reference
19
+ */
14
20
  static LABEL_KEY = 'name'
15
21
 
16
22
  protected _dataObject: DataObjectClass<any>
@@ -28,10 +34,23 @@ export abstract class AbstractObject {
28
34
  return this._dataObject.get(key)
29
35
  }
30
36
 
37
+ /**
38
+ * Proxies a set command to the underlying data object.
39
+ *
40
+ * @param key - The property key.
41
+ * @param val - The value to assign.
42
+ * @returns The DataObject instance for chaining.
43
+ */
31
44
  set(key: string, val: any) {
32
45
  return this._dataObject.set(key, val)
33
46
  }
34
47
 
48
+ /**
49
+ * Fetches the value of a property.
50
+ *
51
+ * @param key - The property key.
52
+ * @returns The property value or null if undefined.
53
+ */
35
54
  val(key: string): any {
36
55
  const prop = this.get(key)
37
56
  if (prop) {
@@ -41,6 +60,12 @@ export abstract class AbstractObject {
41
60
  }
42
61
  }
43
62
 
63
+ /**
64
+ * Checks if a property exists on the data object.
65
+ *
66
+ * @param key - The property key.
67
+ * @returns True if the property exists.
68
+ */
44
69
  has(key: string) {
45
70
  return Reflect.has(this._dataObject.properties, key)
46
71
  }
@@ -65,6 +90,11 @@ export abstract class AbstractObject {
65
90
  return this._dataObject.uri
66
91
  }
67
92
 
93
+ /**
94
+ * Serializes the current object path / identity.
95
+ *
96
+ * @returns The JSON serialized ObjectUri.
97
+ */
68
98
  toJSON() {
69
99
  return typeof this.uri === 'string' ? this.uri : this.uri?.toJSON()
70
100
  }
@@ -7,14 +7,31 @@ import { AbstractObject } from './AbstractObject'
7
7
  import { DataObjectType } from './types/DataObjectType'
8
8
  import { ValidationError } from '../common/ResourcesErrors'
9
9
 
10
+ /**
11
+ * Base generic model class. All Quatrain models inherit from this object.
12
+ * Provides the lifecycle methods and structural properties logic.
13
+ */
10
14
  export class BaseObject extends AbstractObject {
11
15
  // implements BaseObjectClass {
16
+ /** Standard properties inherited by all children models. */
12
17
  static PROPS_DEFINITION: any /*DataObjectProperties*/ = BaseObjectProperties
13
18
 
19
+ /**
20
+ * Returns the definition of a specific property from the model schema.
21
+ *
22
+ * @param key - The property name.
23
+ * @returns The property definition object.
24
+ */
14
25
  static getProperty(key: string) {
15
26
  return BaseObject.PROPS_DEFINITION.find((prop: any) => prop.name === key)
16
27
  }
17
28
 
29
+ /**
30
+ * Compiles the merged properties of a given child class down to its parents.
31
+ *
32
+ * @param child - The target child class.
33
+ * @returns The resulting populated DataObject.
34
+ */
18
35
  static fillProperties(child: any = this) {
19
36
  // merge base properties with additional or redefined ones
20
37
  const base = [...BaseObject.PROPS_DEFINITION]
@@ -38,6 +55,13 @@ export class BaseObject extends AbstractObject {
38
55
  return dao
39
56
  }
40
57
 
58
+ /**
59
+ * Instantiates the DataObject for a specific model class.
60
+ *
61
+ * @param src - Potential source path or object.
62
+ * @param child - The class constructor context.
63
+ * @returns A promise resolving to the inner DataObject payload.
64
+ */
41
65
  static async daoFactory(
42
66
  src: string | ObjectUri | DataObjectType | undefined = undefined,
43
67
  child: any = this
@@ -76,6 +100,14 @@ export class BaseObject extends AbstractObject {
76
100
  return obj
77
101
  }
78
102
 
103
+ /**
104
+ * Main initialization factory. Evaluates path or object sources and returns a fully
105
+ * initialized model instance.
106
+ *
107
+ * @param src - Source data payload or path reference.
108
+ * @param child - Model subclass context.
109
+ * @returns The generated model instance.
110
+ */
79
111
  static async factory(
80
112
  src: string | ObjectUri | BaseObjectType | undefined = undefined,
81
113
  child: any = this
@@ -128,6 +160,11 @@ export class BaseObject extends AbstractObject {
128
160
  return obj //.toProxy()
129
161
  }
130
162
 
163
+ /**
164
+ * Serializes the object down to an identifier reference map.
165
+ *
166
+ * @returns The Object reference payload.
167
+ */
131
168
  asReference() {
132
169
  return this._dataObject.toReference()
133
170
  }
@@ -83,6 +83,11 @@ export class DataObject implements DataObjectType {
83
83
  })
84
84
  }
85
85
 
86
+ /**
87
+ * Forces a completely new set of properties into the registry.
88
+ *
89
+ * @param properties - The dictionary of PropertyClassType entities.
90
+ */
86
91
  public setProperties(properties: Properties) {
87
92
  // TODO check if doable
88
93
  this._properties = properties
@@ -108,6 +113,11 @@ export class DataObject implements DataObjectType {
108
113
  return this._properties
109
114
  }
110
115
 
116
+ /**
117
+ * Appends a new property definition instance to the registry dynamically.
118
+ *
119
+ * @param property - The instantiated Property element.
120
+ */
111
121
  public addProperty(property: PropertyClassType) {
112
122
  if (Object.keys(this._properties).includes(property.name)) {
113
123
  throw new Error(`Property ${property.name} already exists`)
@@ -178,6 +188,11 @@ export class DataObject implements DataObjectType {
178
188
  return this
179
189
  }
180
190
 
191
+ /**
192
+ * State check indicating if the object is fully populated.
193
+ *
194
+ * @returns true if populated.
195
+ */
181
196
  isPopulated() {
182
197
  return this._populated
183
198
  }
@@ -232,6 +247,12 @@ export class DataObject implements DataObjectType {
232
247
  this._parentProp = str
233
248
  }
234
249
 
250
+ /**
251
+ * Checks if a specific property exists.
252
+ *
253
+ * @param key - The property name.
254
+ * @returns Boolean indicating existence.
255
+ */
235
256
  has(key: string) {
236
257
  return Reflect.has(this._properties, key)
237
258
  }
@@ -248,6 +269,13 @@ export class DataObject implements DataObjectType {
248
269
  return Reflect.get(this._properties, key)
249
270
  }
250
271
 
272
+ /**
273
+ * Modifies a property value directly.
274
+ *
275
+ * @param key - The property name.
276
+ * @param val - The desired value.
277
+ * @returns The updated DataObject for chaining.
278
+ */
251
279
  set(key: string, val: any) {
252
280
  if (!this.has(key)) {
253
281
  throw new Error(`Unknown property in data object: ${key}`)
@@ -272,6 +300,12 @@ export class DataObject implements DataObjectType {
272
300
  }
273
301
  }
274
302
 
303
+ /**
304
+ * Serializes the data object using advanced configuration params.
305
+ *
306
+ * @param params - Serialization settings (e.g. resolve references, remove nulls).
307
+ * @returns The raw serialized dictionary.
308
+ */
275
309
  toJSON(params: boolean | toJSONParams = false): { [x: string]: any } {
276
310
  let objectsAsReferences: boolean = false,
277
311
  withoutURIData: boolean = false,
@@ -300,6 +334,11 @@ export class DataObject implements DataObjectType {
300
334
  }
301
335
  }
302
336
 
337
+ /**
338
+ * Flattens the object to a standard ObjectUri wrapper reference format.
339
+ *
340
+ * @returns Reference object format.
341
+ */
303
342
  toReference() {
304
343
  return {
305
344
  ...this._objectUri.toReference(),
@@ -400,6 +439,12 @@ export class DataObject implements DataObjectType {
400
439
  }
401
440
  }
402
441
 
442
+ /**
443
+ * Returns a completely duplicated payload and registry.
444
+ *
445
+ * @param data - Optional data overrides.
446
+ * @returns The cloned DataObject.
447
+ */
403
448
  async clone(data: any = {}): Promise<DataObject> {
404
449
  const cloned = await (this.constructor as any).factory()
405
450
  cloned.uri.class = this.uri.class
@@ -10,9 +10,15 @@ export interface EntityType extends BaseObjectType {
10
10
  // users?: User[]
11
11
  }
12
12
 
13
+ /**
14
+ * Represents a generic grouping structure, such as a company or organization,
15
+ * to which Users may be associated.
16
+ */
13
17
  export class Entity extends BaseObject {
18
+ /** Base collection scope name. */
14
19
  static COLLECTION = 'entities'
15
20
 
21
+ /** Component structure declaration. */
16
22
  static PROPS_DEFINITION = [
17
23
  ...BaseObjectProperties,
18
24
  {
@@ -32,6 +38,12 @@ export class Entity extends BaseObject {
32
38
  // },
33
39
  ]
34
40
 
41
+ /**
42
+ * Entity creation factory.
43
+ *
44
+ * @param src - Identifier or content array.
45
+ * @returns The generated Entity instance.
46
+ */
35
47
  static async factory(src: any = undefined): Promise<Entity> {
36
48
  return super.factory(src, Entity)
37
49
  }
@@ -1,5 +1,11 @@
1
+ /**
2
+ * Unique global reference system for all Quatrain models.
3
+ * Used for backend identification and cross-system relational links.
4
+ */
1
5
  export class ObjectUri {
6
+ /** Root path divider. */
2
7
  static DEFAULT = '/'
8
+ /** Placeholder used when collections cannot be guessed. */
3
9
  static MISSING_COLLECTION = '_?_'
4
10
 
5
11
  protected _str: string
@@ -164,6 +170,11 @@ export class ObjectUri {
164
170
  }
165
171
  }
166
172
 
173
+ /**
174
+ * Output pure object dictionary representation.
175
+ *
176
+ * @returns Rendered URI block.
177
+ */
167
178
  toJSON() {
168
179
  return {
169
180
  backend: this._backend,
@@ -101,10 +101,22 @@ export const UserProperties: any = [
101
101
  },
102
102
  ]
103
103
 
104
+ /**
105
+ * Built-in User representation model handling authentication, profiles,
106
+ * and entity bindings out of the box.
107
+ */
104
108
  export class User extends BaseObject {
109
+ /** User internal schema layout. */
105
110
  static PROPS_DEFINITION = UserProperties
111
+ /** Standard persistence namespace. */
106
112
  static COLLECTION = 'user'
107
113
 
114
+ /**
115
+ * Constructs a new User.
116
+ *
117
+ * @param src - Values or identifier.
118
+ * @returns The generated user object.
119
+ */
108
120
  static async factory(src: any = undefined): Promise<User> {
109
121
  return super.factory(src, User)
110
122
  }
@@ -1,6 +1,17 @@
1
1
  import { BaseProperty, BasePropertyType } from './BaseProperty'
2
2
  import { StringProperty } from './StringProperty'
3
3
 
4
+ /**
5
+ * Configuration dictionary for instantiating an `ArrayProperty`.
6
+ * Restricts the length and content types of an array.
7
+ *
8
+ * | Parameter | Type | Description | Default |
9
+ * | :--- | :--- | :--- | :--- |
10
+ * | `minLength` | number | Minimum number of elements required in the array. | `0` |
11
+ * | `maxLength` | number | Maximum number of elements allowed in the array. | `0` (unlimited) |
12
+ * | `allowNumbers` | boolean | If false, throws an error if the array contains numeric elements. | `true` |
13
+ * | `allowStrings` | boolean | If false, throws an error if the array contains string elements. | `true` |
14
+ */
4
15
  export interface ArrayPropertyType extends BasePropertyType {
5
16
  minLength?: number
6
17
  maxLength?: number
@@ -8,7 +19,25 @@ export interface ArrayPropertyType extends BasePropertyType {
8
19
  allowStrings?: boolean
9
20
  }
10
21
 
22
+ /**
23
+ * A property type that validates and manages arrays of primitive values.
24
+ * Allows enforcement of minimum and maximum element counts, as well as primitive type restrictions.
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * const tags = new ArrayProperty({
29
+ * name: 'tags',
30
+ * minLength: 1,
31
+ * maxLength: 5,
32
+ * allowNumbers: false // Only string tags allowed
33
+ * });
34
+ *
35
+ * tags.set(['typescript', 'quatrain']); // OK
36
+ * tags.set([123]); // Throws Error: Numbers are not allowed in value
37
+ * ```
38
+ */
11
39
  export class ArrayProperty extends BaseProperty {
40
+ /** The string literal type identifier for this property. */
12
41
  static TYPE = 'array'
13
42
  protected _value: Array<any> | undefined = undefined
14
43
 
@@ -42,6 +71,14 @@ export class ArrayProperty extends BaseProperty {
42
71
  return this._maxLength
43
72
  }
44
73
 
74
+ /**
75
+ * Assigns a new array value while enforcing length constraints and content type rules.
76
+ *
77
+ * @param value - The array to assign. Null values are cast to empty arrays `[]`.
78
+ * @param setChanged - Whether to mark the property as modified.
79
+ * @returns The property instance for chaining.
80
+ * @throws {Error} If the value is not an array, violates length bounds, or contains forbidden types.
81
+ */
45
82
  set(value: Array<any>, setChanged = true) {
46
83
  if (value === null) {
47
84
  value = []
@@ -77,10 +114,21 @@ export class ArrayProperty extends BaseProperty {
77
114
  return super.set(value, setChanged)
78
115
  }
79
116
 
117
+ /**
118
+ * Retrieves the array, optionally applying a mapping/transformation function.
119
+ *
120
+ * @param transform - A custom function to apply to the array before returning it.
121
+ * @returns The raw or transformed array.
122
+ */
80
123
  get(transform: Function | undefined = undefined) {
81
124
  return transform ? transform(this._value) : this._value
82
125
  }
83
126
 
127
+ /**
128
+ * Serializes the array for JSON stringification.
129
+ *
130
+ * @returns The raw internal array.
131
+ */
84
132
  toJSON() {
85
133
  return this._value
86
134
  }
@@ -76,4 +76,35 @@ describe('BaseProperty', () => {
76
76
  propStr.set('')
77
77
  expect(propStr.val()).toBe('')
78
78
  })
79
+
80
+ it('should set hasChanged to true when mutating an object by reference and setting it again', () => {
81
+ const prop = new BaseProperty({ name: 'obj' })
82
+ const arr = [1, 2]
83
+ prop.set(arr)
84
+ expect(prop.hasChanged).toBe(true)
85
+
86
+ prop.hasChanged = false
87
+ arr.push(3)
88
+ prop.set(arr)
89
+ expect(prop.hasChanged).toBe(true)
90
+ })
91
+
92
+ it('should set hasChanged to false when setting a different object reference with identical contents', () => {
93
+ const prop = new BaseProperty({ name: 'obj' })
94
+ prop.set([1, 2])
95
+ expect(prop.hasChanged).toBe(true)
96
+
97
+ prop.hasChanged = false
98
+ prop.set([1, 2])
99
+ expect(prop.hasChanged).toBe(false)
100
+ })
101
+
102
+ it('should handle circular references safely without throwing and mark as changed', () => {
103
+ const prop = new BaseProperty({ name: 'circular' })
104
+ const obj: any = { a: 1 }
105
+ obj.self = obj
106
+
107
+ expect(() => prop.set(obj)).not.toThrow()
108
+ expect(prop.hasChanged).toBe(true)
109
+ })
79
110
  })