@dcupl/common 2.0.0-beta.5 → 2.0.0-beta.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.
@@ -1,9 +1,29 @@
1
1
  import { Property, PropertyType } from './types/index.js';
2
+ export declare const coerceStringType: (value: string) => PropertyType;
3
+ export declare const looksDateLike: (value: string) => boolean;
2
4
  export type GetPropertiesForDataOptions = {
3
5
  deep?: boolean;
4
6
  sampleSize?: number;
7
+ /** @deprecated No longer honored — empty values are always skipped for type
8
+ * inference and counted toward ColumnDiagnostic.emptyRate. */
5
9
  ignoreEmpty?: boolean;
6
10
  };
11
+ export type ColumnDiagnostic = {
12
+ column: string;
13
+ inferredType: PropertyType;
14
+ observedTypes: PropertyType[];
15
+ emptyRate: number;
16
+ conflict: boolean;
17
+ hint?: string;
18
+ };
19
+ export type InferModelOptions = GetPropertiesForDataOptions & {
20
+ coerceStrings?: boolean;
21
+ };
22
+ export type InferModelResult = {
23
+ properties: Property[];
24
+ diagnostics: ColumnDiagnostic[];
25
+ };
26
+ export declare const inferModel: (data: any[], options?: InferModelOptions) => InferModelResult;
7
27
  export declare const getPropertiesForData: (data: any[], deepOrOptions?: boolean | GetPropertiesForDataOptions) => Property[];
8
28
  export declare const isInt: (n: any) => boolean;
9
29
  export declare const isFloat: (n: any) => boolean;
@@ -1,4 +1,5 @@
1
1
  import { AutoGeneratePropertiesOption, DataContainerType } from './index.js';
2
+ import { KeyPropertySpec } from './key-property.types.js';
2
3
  export declare namespace AppLoaderConfiguration {
3
4
  /**
4
5
  * The base structure for configuration file contains all resources, evnironments and headers
@@ -181,7 +182,23 @@ export declare namespace AppLoaderConfiguration {
181
182
  };
182
183
  export type DataResourceOptions = {
183
184
  updateType?: DataContainerType;
184
- keyProperty?: string;
185
+ /**
186
+ * Property name(s) on each row that uniquely identify it.
187
+ *
188
+ * Pass a single string for a simple key (`keyProperty: 'ID'`) or a
189
+ * non-empty array for a composite key (`keyProperty: ['ID', 'Language']`).
190
+ * Composite parts are joined into the internal `.key` using
191
+ * `keyPropertySeparator` (default `::`). Order is preserved literally.
192
+ *
193
+ * Each part is stringified via `String(value)`, so `null`, `undefined`,
194
+ * and `''` produce distinct keys (`'null'`, `'undefined'`, `''`).
195
+ */
196
+ keyProperty?: KeyPropertySpec;
197
+ /**
198
+ * Separator joining composite `keyProperty` parts. Default `'::'`.
199
+ * Ignored when `keyProperty` is a string or a single-element array.
200
+ */
201
+ keyPropertySeparator?: string;
185
202
  autoGenerateKey?: boolean;
186
203
  autoGenerateProperties?: AutoGeneratePropertiesOption;
187
204
  csvParserOptions?: CsvParserOptions;
@@ -1,4 +1,5 @@
1
1
  import { AutoGeneratePropertiesOption } from './model.types.js';
2
+ import { KeyPropertySpec } from './key-property.types.js';
2
3
  export type RawItem = any;
3
4
  export type ListItem = {
4
5
  key: string;
@@ -8,7 +9,8 @@ export type DataContainer<ListType extends RawItem | ListItem = ListItem, ModelN
8
9
  model: ModelNames;
9
10
  type?: DataContainerType;
10
11
  data: ListType[];
11
- keyProperty?: string;
12
+ keyProperty?: KeyPropertySpec;
13
+ keyPropertySeparator?: string;
12
14
  autoGenerateKey?: boolean;
13
15
  autoGenerateProperties?: AutoGeneratePropertiesOption;
14
16
  placeholderUid?: string;
@@ -17,6 +19,7 @@ export type DataContainerType = 'upsert' | 'update' | 'set' | 'remove';
17
19
  export type ReferenceDataTypes<ListType extends ListItem = ListItem> = string | string[] | ListType | ListType[];
18
20
  export type DataOptions<ModelNames extends string = string> = {
19
21
  model: ModelNames;
20
- keyProperty?: string;
22
+ keyProperty?: KeyPropertySpec;
23
+ keyPropertySeparator?: string;
21
24
  autoGenerateKey?: boolean;
22
25
  };
@@ -98,6 +98,18 @@ export type DcuplPartialUpdateConfig = {
98
98
  };
99
99
  export type DcuplInitQualityConfig = {
100
100
  enabled: boolean;
101
+ /**
102
+ * Maximum number of stored error objects per (model, property, errorType)
103
+ * group. Quality errors are still counted exactly beyond this limit and are
104
+ * reachable via `getErrorCounts()`; only the retained error objects (and the
105
+ * `$DcuplErrorTrackingErrors` model) are bounded. Default: 1000.
106
+ *
107
+ * Note: for error groups whose true count exceeds `maxErrorsPerGroup`,
108
+ * partial-update cleanup may cause `getErrorCounts()` to slightly
109
+ * over-report, since over-cap occurrences are counted but never stored as
110
+ * error objects and therefore cannot be decremented on removal.
111
+ */
112
+ maxErrorsPerGroup?: number;
101
113
  };
102
114
  export type DcuplInitErrorTrackingConfig = {
103
115
  enabled: boolean;
@@ -6,6 +6,11 @@ export type DcuplFacetOptions = {
6
6
  excludeUndefineds?: boolean;
7
7
  excludeUnresolved?: boolean;
8
8
  calculateResults?: boolean;
9
+ /**
10
+ * Opt-in deterministic ordering. See `DcuplFilterOptions.sort` for full semantics.
11
+ * When omitted the fast (insertion-order) path is preserved.
12
+ */
13
+ sort?: 'size-desc' | 'size-asc' | 'value-asc' | 'value-desc';
9
14
  };
10
15
  export type DcuplFacet = {
11
16
  key: string;
@@ -1,5 +1,6 @@
1
1
  export * from './aggregation.types.js';
2
2
  export * from './app-loader.types.js';
3
+ export * from './key-property.types.js';
3
4
  export * from './data.types.js';
4
5
  export * from './dcupl.types.js';
5
6
  export * from './facets.types.js';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Specifies the property (or properties) on a row that uniquely identifies it.
3
+ *
4
+ * - A single string names one property: `keyProperty: 'id'`.
5
+ * - A non-empty array of strings produces a composite key by joining the
6
+ * stringified values of each property with the configured separator
7
+ * (default `::`): `keyProperty: ['ID', 'Language']` → `.key === 'A1::en'`.
8
+ *
9
+ * The order of array entries is significant and preserved verbatim.
10
+ */
11
+ export type KeyPropertySpec = string | readonly string[];
@@ -22,6 +22,22 @@ export type DcuplFilterOptions = CatalogApiOptions & {
22
22
  excludeUndefineds?: boolean;
23
23
  excludeUnresolved?: boolean;
24
24
  count?: number;
25
+ /**
26
+ * Opt-in deterministic ordering for facet results.
27
+ *
28
+ * When omitted (default), results are returned in inverse-index iteration
29
+ * order and `count` truncation is applied mid-iteration. This is the
30
+ * fast path and matches all existing call sites.
31
+ *
32
+ * When set, `getFacets` walks all facet keys, computes real sizes
33
+ * (overriding `calculateFacets: false`), applies the chosen sort with
34
+ * deterministic tie-break, and truncates to `count` after sorting.
35
+ * This is the slow but correct path — opt in when ordering matters.
36
+ *
37
+ * Tie-break for size sorts is always `value` ascending with `undefined`
38
+ * last. `value-desc` keeps `undefined` last as well.
39
+ */
40
+ sort?: 'size-desc' | 'size-asc' | 'value-asc' | 'value-desc';
25
41
  };
26
42
  export type DcuplFiltersOptions = DcuplFilterOptions & {
27
43
  filterKeys?: string[];
@@ -1,4 +1,5 @@
1
1
  import { AggregationOptions, DcuplGlobalQueryOptions, DcuplQuery, DcuplQueryGroup, RawItem } from './index.js';
2
+ import { KeyPropertySpec } from './key-property.types.js';
2
3
  import { PivotOptions } from '../pivot.js';
3
4
  type AggregationOptionsModel = Omit<AggregationOptions, 'attribute'>;
4
5
  export type PropertyType = 'any' | 'Array<date>' | 'Array<float>' | 'Array<int>' | 'Array<string>' | 'boolean' | 'date' | 'float' | 'int' | 'json' | 'string';
@@ -325,8 +326,19 @@ export type ValueMappingConfig = {
325
326
  };
326
327
  export type ModelAttribute = Property | Reference;
327
328
  export type ModelQualityConfig = {
329
+ /**
330
+ * Master switch for quality checks on this model. Default: true.
331
+ * Checks only run when error tracking is also enabled globally
332
+ * (two-tier enablement).
333
+ */
328
334
  enabled?: boolean;
329
- attributes?: AttributeQualityConfig;
335
+ /**
336
+ * Model-wide defaults for the attribute quality FLAGS
337
+ * (required / nullable / forceStrictDataType / validatorHandling),
338
+ * merged under each attribute's own `quality`.
339
+ * Concrete `validators` are per-attribute only and cannot be set here.
340
+ */
341
+ attributes?: Omit<AttributeQualityConfig, 'validators'>;
330
342
  };
331
343
  export type ValidatorErrorType = 'loose' | 'strict';
332
344
  export type ValidatorConfig = {
@@ -350,6 +362,18 @@ export type AttributeValidatorConfig = {
350
362
  unique?: ValidatorConfig;
351
363
  startsWith?: ValidatorConfig;
352
364
  };
365
+ /**
366
+ * Quality rules for a single property or reference.
367
+ *
368
+ * Defaults: required: true, nullable: false, forceStrictDataType: false,
369
+ * validatorHandling: 'loose'.
370
+ *
371
+ * validatorHandling decides what happens when a validator fails:
372
+ * 'loose' (default) reports an InvalidValidator error and keeps the value;
373
+ * 'strict' reports AND drops the value from the loaded data.
374
+ * forceStrictDataType: true disables type coercion — wrong-typed raw
375
+ * values are reported as WrongDataType and dropped.
376
+ */
353
377
  export type AttributeQualityConfig = {
354
378
  required?: boolean;
355
379
  nullable?: boolean;
@@ -372,7 +396,15 @@ export type ModelDefinition<ModelNames extends string = string> = {
372
396
  properties?: Property[];
373
397
  references?: Reference[];
374
398
  data?: RawItem[];
375
- keyProperty?: string;
399
+ /**
400
+ * Property name(s) on each row that uniquely identify it.
401
+ * See `KeyPropertySpec` for composite-key behavior.
402
+ */
403
+ keyProperty?: KeyPropertySpec;
404
+ /**
405
+ * Separator joining composite `keyProperty` parts. Default `'::'`.
406
+ */
407
+ keyPropertySeparator?: string;
376
408
  autoGenerateKey?: boolean;
377
409
  autoGenerateProperties?: AutoGeneratePropertiesOption;
378
410
  supportsAutoCreation?: boolean;
@@ -1,6 +1,60 @@
1
1
  export declare namespace QualityAnalyzer {
2
2
  type ModelErrorGroup = 'ReferenceDataError' | 'PropertyDataError' | 'DataContainerError' | 'ModelDefinitionError';
3
- type ModelErrorType = 'UndefinedAttribute' | 'InvalidValidator' | 'UndefinedValue' | 'NullValue' | 'WrongDataType' | 'NonUniqueKey' | 'MissingModel' | 'MissingProperty' | 'MissingReference' | 'UnknownExpressionVariable' | 'RemoteReferenceKeyNotFound' | 'InvalidModelDefinition';
3
+ /**
4
+ * Error types emitted by the quality controller during model registration
5
+ * and data ingest.
6
+ *
7
+ * - `UndefinedAttribute` — A declared attribute has no corresponding column /
8
+ * field in an ingested data container. Fires once per (model, attribute).
9
+ * - `InvalidValidator` — A validator on a property could not be parsed or
10
+ * evaluated against the data.
11
+ * - `UndefinedValue` — A required property has no value on a specific
12
+ * data entry (the cell is `undefined`). Fires per-row.
13
+ * - `NullValue` — A non-nullable property has an explicit `null` on
14
+ * a specific data entry. Fires per-row.
15
+ * - `WrongDataType` — A value present on a data entry could not be
16
+ * coerced to the declared property type. Fires per-row. Also covers *lossy*
17
+ * numeric coercions where coercion succeeds but silently drops information —
18
+ * trailing/embedded non-numeric content (e.g. `"1.2 lbs"` → `1.2`) or
19
+ * fractional truncation for `int` properties (e.g. `"1.2"` → `1`). Lossy
20
+ * records carry `meta.lossy: true` plus `meta.rawValue` and
21
+ * `meta.coercedValue`; the coerced value is still kept on the data entry.
22
+ * Representation-only differences that preserve the value (leading/trailing
23
+ * zeros, surrounding whitespace, `"4.0"` → `4`) are not flagged.
24
+ * Aggregation (#197): when one or more cells of a typed column fail to coerce,
25
+ * the per-row signals are consolidated into a single `WrongDataType` per
26
+ * `(model, attribute)` with `meta.rowCount` = the number of failing rows
27
+ * (exact and cap-independent for loader/CSV sources; bounded by
28
+ * `maxErrorsPerGroup` for inline data). `getErrorCounts()` reports the stored
29
+ * *record* count (1 for an aggregated column), which is intentionally distinct
30
+ * from `meta.rowCount`. A column entirely absent from the source still
31
+ * produces `UndefinedAttribute`, not `WrongDataType`.
32
+ * - `NonUniqueKey` — Two or more data entries share the same key value
33
+ * in the same model; later rows are dropped.
34
+ * - `MissingModel` — A reference points to a model key that has not
35
+ * been registered.
36
+ * - `MissingProperty` — Emitted only during model-definition validation
37
+ * when a *derived property* declares `derive.remoteProperty` pointing to a
38
+ * property that does not exist on the remote model. Not used in any
39
+ * CSV/JSON data-ingest path — those are covered by `UndefinedAttribute`,
40
+ * `UndefinedValue`, and `WrongDataType`. See model.helper.ts (derived-
41
+ * property validator) for the sole emit site.
42
+ * - `MissingReference` — A derived property's `derive.localReference` names
43
+ * a reference that does not exist on the local model.
44
+ * - `UnknownExpressionVariable` — An expression property references a
45
+ * variable that is neither a property nor a reference on the local model.
46
+ * - `RemoteReferenceKeyNotFound` — A foreign-key value does not match any
47
+ * key in the referenced remote model. Fires per-row.
48
+ * - `InvalidModelDefinition` — Catch-all for structural problems detected
49
+ * when registering a model.
50
+ * - `UnknownColumn` — A CSV (or otherwise source-tracked) data
51
+ * container contains a column / attribute that is not declared on the
52
+ * target model. Values for the column are silently dropped during ingest;
53
+ * this error surfaces that data loss without failing the load. Fires once
54
+ * per (model, attribute). Only emitted when the loader registers source
55
+ * metadata for the container (so inline `model.data` is unaffected).
56
+ */
57
+ type ModelErrorType = 'UndefinedAttribute' | 'InvalidValidator' | 'UndefinedValue' | 'NullValue' | 'WrongDataType' | 'NonUniqueKey' | 'MissingModel' | 'MissingProperty' | 'MissingReference' | 'UnknownExpressionVariable' | 'RemoteReferenceKeyNotFound' | 'InvalidModelDefinition' | 'UnknownColumn';
4
58
  type DcuplErrorType = 'model' | 'loader';
5
59
  type DcuplErrorBase = {
6
60
  key?: string;
@@ -13,7 +13,7 @@ export declare const DcuplQueryOperatorsAsArray: readonly ["eq", "find", "gt", "
13
13
  *
14
14
  * Available operators:
15
15
  * - `eq`: Equality check - matches exact values
16
- * - `find`: Partial text or object match - searches within strings or objects
16
+ * - `find`: Regex / exact / object match - bare string is exact-equality, `/regex/` for substring/pattern, object for property match
17
17
  * - `gt`: Greater than - numeric/date comparison (exclusive)
18
18
  * - `gte`: Greater than or equal - numeric/date comparison (inclusive)
19
19
  * - `lt`: Less than - numeric/date comparison (exclusive)
@@ -30,8 +30,8 @@ export declare const DcuplQueryOperatorsAsArray: readonly ["eq", "find", "gt", "
30
30
  * // Numeric comparison
31
31
  * { operator: 'gte', attribute: 'price', value: 100 }
32
32
  *
33
- * // Text search
34
- * { operator: 'find', attribute: 'name', value: 'search term' }
33
+ * // Substring search (slash-delimited regex; bare string would be exact-match)
34
+ * { operator: 'find', attribute: 'name', value: '/search term/' }
35
35
  * ```
36
36
  */
37
37
  export type DcuplQueryOperator = (typeof DcuplQueryOperatorsAsArray)[number];
@@ -128,13 +128,22 @@ type DcuplQueryEq = DcuplQueryBase & {
128
128
  value: any;
129
129
  };
130
130
  /**
131
- * Find query - partial text or object match.
132
- * Searches within strings (substring match) or checks object property existence.
131
+ * Find query - regex / exact / object match.
132
+ *
133
+ * Matching depends on the shape of `value`:
134
+ * - **Bare string** (e.g. `'search'`) — exact-equality against the *whole* field
135
+ * value, NOT a substring. `find` with `'Ball'` does not match `'Soccer Ball'`.
136
+ * - **Slash-delimited regex** (e.g. `'/search/'`, `'/^foo/i'`) — JS regex literal;
137
+ * use this for substring and pattern matching.
138
+ * - **Object** (e.g. `{ key: 'value' }`) — property matching against the field.
133
139
  *
134
140
  * @example
135
141
  * ```typescript
136
- * // Find items containing 'search' in description
137
- * { operator: 'find', attribute: 'description', value: 'search' }
142
+ * // Substring search — slash-delimited regex (bare string would be exact-match)
143
+ * { operator: 'find', attribute: 'description', value: '/search/' }
144
+ *
145
+ * // Exact-match the whole field value
146
+ * { operator: 'find', attribute: 'status', value: 'active' }
138
147
  *
139
148
  * // Find items with specific nested property
140
149
  * { operator: 'find', attribute: 'metadata', value: { key: 'value' } }
@@ -143,7 +152,7 @@ type DcuplQueryEq = DcuplQueryBase & {
143
152
  type DcuplQueryFind = DcuplQueryBase & {
144
153
  operator: 'find';
145
154
  /**
146
- * Text to search for (substring) or object with properties to match.
155
+ * Bare string (exact-match), `/regex/` (substring/pattern), or object (property match).
147
156
  */
148
157
  value: Record<string, unknown> | string;
149
158
  };