@dcupl/common 2.0.0-beta.1 → 2.0.0-beta.10

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 (36) hide show
  1. package/dist/esm/analytics.controller.d.ts +1 -1
  2. package/dist/esm/changeDetection.d.ts +2 -2
  3. package/dist/esm/computeSuggestions.d.ts +2 -2
  4. package/dist/esm/deep-query.service.d.ts +26 -1
  5. package/dist/esm/getAggregation.d.ts +1 -1
  6. package/dist/esm/getFacets.d.ts +26 -3
  7. package/dist/esm/helper.d.ts +1 -1
  8. package/dist/esm/index.d.ts +23 -22
  9. package/dist/esm/index.js +766 -599
  10. package/dist/esm/index.js.map +1 -1
  11. package/dist/esm/indices.controller.d.ts +1 -1
  12. package/dist/esm/logger/index.d.ts +2 -2
  13. package/dist/esm/pivot.d.ts +2 -2
  14. package/dist/esm/property-parser.d.ts +27 -2
  15. package/dist/esm/query-builder.d.ts +2 -2
  16. package/dist/esm/query.helper.d.ts +1 -1
  17. package/dist/esm/queryData.d.ts +2 -2
  18. package/dist/esm/resource-id.d.ts +43 -0
  19. package/dist/esm/types/app-loader.types.d.ts +94 -4
  20. package/dist/esm/types/data.types.d.ts +7 -3
  21. package/dist/esm/types/dcupl.types.d.ts +21 -9
  22. package/dist/esm/types/facets.types.d.ts +6 -1
  23. package/dist/esm/types/filter.types.d.ts +3 -3
  24. package/dist/esm/types/group-by.types.d.ts +2 -2
  25. package/dist/esm/types/index.d.ts +17 -16
  26. package/dist/esm/types/internal.types.d.ts +1 -1
  27. package/dist/esm/types/key-property.types.d.ts +11 -0
  28. package/dist/esm/types/list.types.d.ts +20 -4
  29. package/dist/esm/types/model.types.d.ts +54 -5
  30. package/dist/esm/types/quality.types.d.ts +59 -1
  31. package/dist/esm/types/query.types.d.ts +19 -10
  32. package/dist/esm/types/section.types.d.ts +2 -2
  33. package/dist/esm/types/suggestion.types.d.ts +1 -1
  34. package/dist/node/index.cjs +2 -2
  35. package/dist/node/index.cjs.map +1 -1
  36. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
- import { InverseIndexValuesMap, InverseIndexDataValuesMap, ListItem } from './types';
1
+ import { InverseIndexValuesMap, InverseIndexDataValuesMap, ListItem } from './types/index.js';
2
2
  /**
3
3
  * Statistics about indices for monitoring
4
4
  */
@@ -1,2 +1,2 @@
1
- export { Logger, logger } from './logger';
2
- export type { LogLevel, LoggerConfig, LogEntry, TrackedErrorEntry } from './logger';
1
+ export { Logger, logger } from './logger.js';
2
+ export type { LogLevel, LoggerConfig, LogEntry, TrackedErrorEntry } from './logger.js';
@@ -1,5 +1,5 @@
1
- import { IndicesController } from './indices.controller';
2
- import { ListItem, Aggregation, AggregationOptions } from './types';
1
+ import { IndicesController } from './indices.controller.js';
2
+ import { ListItem, Aggregation, AggregationOptions } from './types/index.js';
3
3
  type PivotRowOption = {
4
4
  attribute: string;
5
5
  calculateTotals?: boolean;
@@ -1,5 +1,30 @@
1
- import { Property, PropertyType } from './types';
2
- export declare const getPropertiesForData: (data: any[], deep: boolean) => Property[];
1
+ import { Property, PropertyType } from './types/index.js';
2
+ export declare const coerceStringType: (value: string) => PropertyType;
3
+ export declare const looksDateLike: (value: string) => boolean;
4
+ export type GetPropertiesForDataOptions = {
5
+ deep?: boolean;
6
+ sampleSize?: number;
7
+ /** @deprecated No longer honored — empty values are always skipped for type
8
+ * inference and counted toward ColumnDiagnostic.emptyRate. */
9
+ ignoreEmpty?: boolean;
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;
27
+ export declare const getPropertiesForData: (data: any[], deepOrOptions?: boolean | GetPropertiesForDataOptions) => Property[];
3
28
  export declare const isInt: (n: any) => boolean;
4
29
  export declare const isFloat: (n: any) => boolean;
5
30
  export declare const getDcuplPropertyTypeForNativeType: (value: any) => PropertyType;
@@ -1,5 +1,5 @@
1
- import { DcuplGlobalQueryOptions, DcuplItemsOptions, DcuplQuery, DcuplQueryGroup } from './types';
2
- import { DeepQueryService } from './deep-query.service';
1
+ import { DcuplGlobalQueryOptions, DcuplItemsOptions, DcuplQuery, DcuplQueryGroup } from './types/index.js';
2
+ import { DeepQueryService } from './deep-query.service.js';
3
3
  export type QueryAttributeEntry = {
4
4
  attribute: string;
5
5
  reference?: string;
@@ -1,4 +1,4 @@
1
- import { BooleanFilterItem, BooleanFilterOptions, DateFilterItem, DateFilterOptions, DcuplQueryGroup, DcuplQueryStatement, NumberFilterItem, NumberFilterOptions, ReferenceFilterItem, ReferenceFilterOptions, TextFilterItem, TextFilterOptions } from './types';
1
+ import { BooleanFilterItem, BooleanFilterOptions, DateFilterItem, DateFilterOptions, DcuplQueryGroup, DcuplQueryStatement, NumberFilterItem, NumberFilterOptions, ReferenceFilterItem, ReferenceFilterOptions, TextFilterItem, TextFilterOptions } from './types/index.js';
2
2
  export declare const generateTextQuery: (options: TextFilterOptions, filterItem?: TextFilterItem) => DcuplQueryGroup;
3
3
  export declare const generateBooleanQuery: (options: BooleanFilterOptions, filterItem?: BooleanFilterItem) => DcuplQueryGroup;
4
4
  export declare const generateNumberAndDateQuery: (options: NumberFilterOptions | DateFilterOptions, filterItem?: NumberFilterItem | DateFilterItem) => DcuplQueryGroup;
@@ -1,5 +1,5 @@
1
- import { ListItem, InverseIndexReferenceMap, DcuplQueryTransformOptions, CustomOperatorFn, DcuplGlobalQueryOptions } from './types';
2
- import { ScriptController } from './script.controller';
1
+ import { ListItem, InverseIndexReferenceMap, DcuplQueryTransformOptions, CustomOperatorFn, DcuplGlobalQueryOptions } from './types/index.js';
2
+ import { ScriptController } from './script.controller.js';
3
3
  import { ModelParser } from '@dcupl/core/model-parser';
4
4
  import { ItemService } from '@dcupl/core/catalog';
5
5
  export declare class QueryManager {
@@ -0,0 +1,43 @@
1
+ /** Authored `resource.key` values are namespaced so they can never collide with a derived hash. */
2
+ export declare const AUTHORED_RESOURCE_ID_PREFIX = "k:";
3
+ export declare const DERIVED_RESOURCE_ID_PREFIX = "h:";
4
+ /**
5
+ * The fields a resource identity is derived from.
6
+ *
7
+ * Structural rather than `AppLoaderConfiguration.Resource` so the same function serves a raw config
8
+ * entry and a runtime progress item — `model` only exists on data resources, `rawUrl` only at runtime.
9
+ */
10
+ export type ResourceIdentityInput = {
11
+ key?: string;
12
+ type: string;
13
+ url: string;
14
+ rawUrl?: string;
15
+ model?: string;
16
+ };
17
+ /**
18
+ * A stable id for one resource.
19
+ *
20
+ * An authored `key` wins and is used verbatim — that id survives a url edit, which is the reason to
21
+ * author keys at all. Otherwise the id is a hash of the RAW url plus type/model. Deriving from the
22
+ * raw url (never the resolved one) is what makes the id identical on both sides of the config↔runtime
23
+ * boundary, since `${variable}` substitution happens only on the runtime side.
24
+ *
25
+ * A derived id therefore changes when the url, type or model changes. That is the accepted limit:
26
+ * it is stable across runs, not across edits.
27
+ *
28
+ * `occurrence` disambiguates config entries that are identical in type/url/model. Prefer
29
+ * `resolveResourceIds` for a list — it assigns occurrences in document order, so reordering the
30
+ * config does not churn ids.
31
+ */
32
+ export declare function resolveResourceId(resource: ResourceIdentityInput, occurrence?: number): string;
33
+ /**
34
+ * Ids for a resource list, positionally aligned with the input, guaranteed unique.
35
+ *
36
+ * Uniqueness is enforced on the OUTPUT, not just on the (type, url, model) basis, because two other
37
+ * things can collide: two entries authored with the same `key` (nothing in the config validates key
38
+ * uniqueness — `loader.resources.get()` simply returns the first match), and a hash collision, which
39
+ * `generateObjectHash` admits by construction since it is a 32-bit rolling hash. Random collisions
40
+ * are vanishingly rare at realistic config sizes, but a duplicate id silently merges two nodes in a
41
+ * consumer's graph, so it is worth ruling out rather than reasoning about.
42
+ */
43
+ export declare function resolveResourceIds(resources: readonly ResourceIdentityInput[]): string[];
@@ -1,4 +1,5 @@
1
- import { DataContainerType } from './';
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
@@ -55,7 +56,7 @@ export declare namespace AppLoaderConfiguration {
55
56
  defaultDataContainerUpdateType?: DataContainerType;
56
57
  csvParserOptions?: CsvParserOptions;
57
58
  missingModelHandling?: {
58
- autoGenerateProperties?: boolean;
59
+ autoGenerateProperties?: AutoGeneratePropertiesOption;
59
60
  };
60
61
  };
61
62
  export type CsvParserOptions = {
@@ -181,9 +182,25 @@ 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
- autoGenerateProperties?: boolean;
203
+ autoGenerateProperties?: AutoGeneratePropertiesOption;
187
204
  csvParserOptions?: CsvParserOptions;
188
205
  };
189
206
  export type Resource = ModelResource | DataResource | TransformerResource | CustomOperatorResource | CustomScriptResource;
@@ -191,6 +208,45 @@ export declare namespace AppLoaderConfiguration {
191
208
  key: string;
192
209
  value: string;
193
210
  };
211
+ /** Why a declared resource was not scheduled for a run. */
212
+ export type SkipReason =
213
+ /** The run filters by resourceTags, and the resource carries no tags at all. */
214
+ 'no-tags'
215
+ /** The run filters by resourceTags, and none of them matched the resource's tags. */
216
+ | 'tag-mismatch';
217
+ export type SkippedResource = {
218
+ resource: Resource;
219
+ reason: SkipReason;
220
+ /**
221
+ * The same identity a scheduled item would carry — see `AppLoaderProgress.ResourceItem.id`.
222
+ *
223
+ * Assigned here rather than left to the consumer because ids are allotted over the FULL declared
224
+ * list: recomputing one in isolation cannot know its occurrence index, so duplicate entries would
225
+ * come out with a different id than the SDK gave them.
226
+ */
227
+ id?: string;
228
+ };
229
+ /**
230
+ * The outcome of resolving a configuration against process options, WITHOUT fetching anything.
231
+ *
232
+ * `scheduled` is what a `process()` call would actually load (tag-filtered, urls resolved);
233
+ * `skipped` is every declared resource that dropped out, with the reason. Together they cover
234
+ * `config.resources` exactly, which is what lets a consumer show "declared but not in this app"
235
+ * without re-implementing the filter.
236
+ */
237
+ export type ResourcePlan = {
238
+ scheduled: AppLoaderProgress.ResourceItem[];
239
+ skipped: SkippedResource[];
240
+ /** The variables the scheduled urls were resolved with. */
241
+ variables: EnvironmentVariableAsObject;
242
+ /** The process options the plan was built for. */
243
+ run: RunContext;
244
+ };
245
+ export type RunContext = {
246
+ applicationKey?: string;
247
+ resourceTags?: StringOrStringArray;
248
+ environmentKeys?: string[];
249
+ };
194
250
  export {};
195
251
  }
196
252
  export type FilesApiStatusResponse = {
@@ -205,6 +261,30 @@ export declare namespace AppLoaderProgress {
205
261
  type ResourceItem = AppLoaderConfiguration.Resource & {
206
262
  success?: boolean;
207
263
  status?: number;
264
+ /**
265
+ * Stable identity for this resource, derived once at plan time.
266
+ *
267
+ * Authored `resource.key` wins (`k:<key>`); otherwise a deterministic hash of the resource's
268
+ * RAW url plus type/model (`h:<hash>`). Because it is derived from the raw url it is the same
269
+ * value a consumer computes from the config file, which is what makes declared↔runtime joins
270
+ * possible without url matching. See `resolveResourceId`.
271
+ */
272
+ id?: string;
273
+ /**
274
+ * The url as authored in the config, BEFORE `${variable}` substitution.
275
+ *
276
+ * `url` is overwritten with the resolved value at plan time; without this the template — and
277
+ * therefore the link back to the config entry — would be unrecoverable at runtime.
278
+ */
279
+ rawUrl?: string;
280
+ /**
281
+ * Variables referenced by `rawUrl` that had no value at resolve time.
282
+ *
283
+ * `evaluateTemplate` substitutes an unknown variable with an empty string, so an unset
284
+ * `${baseUrl}` silently yields a mangled url and a download failure that looks like a missing
285
+ * file. A non-empty array here is the difference between "file is gone" and "config is wrong".
286
+ */
287
+ unresolvedVariables?: string[];
208
288
  };
209
289
  type ProgressItem = {
210
290
  total: number;
@@ -213,11 +293,21 @@ export declare namespace AppLoaderProgress {
213
293
  progress: number;
214
294
  context: string;
215
295
  currentItem: ResourceItem;
296
+ /** The resources SCHEDULED for this run — already tag-filtered and url-resolved. */
216
297
  totalItems: ResourceItem[];
217
298
  processedItems: ResourceItem[];
218
299
  downloadedItems: ResourceItem[];
219
300
  failedItems: ResourceItem[];
220
301
  remainingItems: ResourceItem[];
302
+ /**
303
+ * Declared resources that this run never scheduled, with the reason.
304
+ *
305
+ * `totalItems` covers only what the run's resourceTags selected, so on its own it cannot
306
+ * distinguish "loaded everything" from "loaded everything it was allowed to see".
307
+ */
308
+ skippedItems?: AppLoaderConfiguration.SkippedResource[];
309
+ /** The application/tags/environments this run resolved against. */
310
+ run?: AppLoaderConfiguration.RunContext;
221
311
  };
222
312
  type CallbackFunction = (item: ProgressItem) => void;
223
313
  }
@@ -1,3 +1,5 @@
1
+ import { AutoGeneratePropertiesOption } from './model.types.js';
2
+ import { KeyPropertySpec } from './key-property.types.js';
1
3
  export type RawItem = any;
2
4
  export type ListItem = {
3
5
  key: string;
@@ -7,15 +9,17 @@ export type DataContainer<ListType extends RawItem | ListItem = ListItem, ModelN
7
9
  model: ModelNames;
8
10
  type?: DataContainerType;
9
11
  data: ListType[];
10
- keyProperty?: string;
12
+ keyProperty?: KeyPropertySpec;
13
+ keyPropertySeparator?: string;
11
14
  autoGenerateKey?: boolean;
12
- autoGenerateProperties?: boolean;
15
+ autoGenerateProperties?: AutoGeneratePropertiesOption;
13
16
  placeholderUid?: string;
14
17
  };
15
18
  export type DataContainerType = 'upsert' | 'update' | 'set' | 'remove';
16
19
  export type ReferenceDataTypes<ListType extends ListItem = ListItem> = string | string[] | ListType | ListType[];
17
20
  export type DataOptions<ModelNames extends string = string> = {
18
21
  model: ModelNames;
19
- keyProperty?: string;
22
+ keyProperty?: KeyPropertySpec;
23
+ keyPropertySeparator?: string;
20
24
  autoGenerateKey?: boolean;
21
25
  };
@@ -1,12 +1,12 @@
1
- import { DcuplUpdateMessage } from '../changeDetection';
2
- import { PivotOptions } from '../pivot';
3
- import { AggregationOptions } from './aggregation.types';
4
- import { DcuplFacetOptions } from './facets.types';
5
- import { DcuplGroupByOptions } from './group-by.types';
6
- import { DcuplItemsOptions } from './list.types';
7
- import { DcuplGlobalQueryOptions } from './query.types';
8
- import { SuggestionOptions } from './suggestion.types';
9
- import { LogLevel, LogEntry } from '../logger';
1
+ import { DcuplUpdateMessage } from '../changeDetection.js';
2
+ import { PivotOptions } from '../pivot.js';
3
+ import { AggregationOptions } from './aggregation.types.js';
4
+ import { DcuplFacetOptions } from './facets.types.js';
5
+ import { DcuplGroupByOptions } from './group-by.types.js';
6
+ import { DcuplItemsOptions } from './list.types.js';
7
+ import { DcuplGlobalQueryOptions } from './query.types.js';
8
+ import { SuggestionOptions } from './suggestion.types.js';
9
+ import { LogLevel, LogEntry } from '../logger/index.js';
10
10
  export type DcuplInitOptions = {
11
11
  config?: DcuplInitConfig;
12
12
  /**
@@ -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;
@@ -1,4 +1,4 @@
1
- import { ListItem } from './data.types';
1
+ import { ListItem } from './data.types.js';
2
2
  export type DcuplFacetOptions = {
3
3
  attribute: string;
4
4
  count?: number;
@@ -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,6 +1,6 @@
1
- import { DcuplQueryGroup, ListItem } from '.';
2
- import { CatalogApiOptions } from './list.types';
3
- import { FilterComparisonType } from './model.types';
1
+ import { DcuplQueryGroup, ListItem } from './index.js';
2
+ import { CatalogApiOptions } from './list.types.js';
3
+ import { FilterComparisonType } from './model.types.js';
4
4
  export type NumberConstraintOperators = {
5
5
  gt?: number;
6
6
  lt?: number;
@@ -1,5 +1,5 @@
1
- import { AggregationOptions } from './aggregation.types';
2
- import { DcuplItemsOptions } from './list.types';
1
+ import { AggregationOptions } from './aggregation.types.js';
2
+ import { DcuplItemsOptions } from './list.types.js';
3
3
  export type DcuplSectionItemOption<T> = DcuplItemsOptions<T> & {
4
4
  key: string;
5
5
  };
@@ -1,16 +1,17 @@
1
- export * from './aggregation.types';
2
- export * from './app-loader.types';
3
- export * from './data.types';
4
- export * from './dcupl.types';
5
- export * from './facets.types';
6
- export * from './filter.types';
7
- export * from './group-by.types';
8
- export * from './internal.types';
9
- export * from './list.types';
10
- export * from './model.types';
11
- export * from './projection.types';
12
- export * from './quality.types';
13
- export * from './query.types';
14
- export * from './section.types';
15
- export * from './suggestion.types';
16
- export * from './testing.types';
1
+ export * from './aggregation.types.js';
2
+ export * from './app-loader.types.js';
3
+ export * from './key-property.types.js';
4
+ export * from './data.types.js';
5
+ export * from './dcupl.types.js';
6
+ export * from './facets.types.js';
7
+ export * from './filter.types.js';
8
+ export * from './group-by.types.js';
9
+ export * from './internal.types.js';
10
+ export * from './list.types.js';
11
+ export * from './model.types.js';
12
+ export * from './projection.types.js';
13
+ export * from './quality.types.js';
14
+ export * from './query.types.js';
15
+ export * from './section.types.js';
16
+ export * from './suggestion.types.js';
17
+ export * from './testing.types.js';
@@ -1,4 +1,4 @@
1
- import { ListItem } from './data.types';
1
+ import { ListItem } from './data.types.js';
2
2
  export type InverseIndexReferenceMap = Map<string, InverseIndexValuesMap>;
3
3
  export type InverseIndexValuesMap = Map<string, Set<string>>;
4
4
  export type InverseIndexDataMap = Map<string, InverseIndexDataValuesMap>;
@@ -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[];
@@ -1,7 +1,7 @@
1
- import { DcuplGlobalQueryOptions } from './query.types';
2
- import { ViewDefinition, ModelAttribute } from './model.types';
3
- import { Projection, SortingProjection } from './projection.types';
4
- import { ListItem } from './data.types';
1
+ import { DcuplGlobalQueryOptions } from './query.types.js';
2
+ import { ViewDefinition, ModelAttribute } from './model.types.js';
3
+ import { Projection, SortingProjection } from './projection.types.js';
4
+ import { ListItem } from './data.types.js';
5
5
  export type DcuplItemsOptions<T> = {
6
6
  start?: number;
7
7
  count?: number;
@@ -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,10 +1,28 @@
1
- import { AggregationOptions, DcuplGlobalQueryOptions, DcuplQuery, DcuplQueryGroup, RawItem } from '.';
2
- import { PivotOptions } from '../pivot';
1
+ import { AggregationOptions, DcuplGlobalQueryOptions, DcuplQuery, DcuplQueryGroup, RawItem } from './index.js';
2
+ import { KeyPropertySpec } from './key-property.types.js';
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';
5
6
  export declare const PropertyTypesAsArray: PropertyType[];
6
7
  export declare const NumberPropertyTypes: PropertyType[];
7
8
  export declare const DatePropertyTypes: PropertyType[];
9
+ /**
10
+ * Configures how property auto-generation inspects input data.
11
+ *
12
+ * - `true` / `false`: enable/disable auto-generation. When enabled with `true`,
13
+ * only the first row of input data is inspected (fast, default behavior).
14
+ * - Object form lets you scan more rows:
15
+ * - `deep: true` walks every row and reconciles types across them.
16
+ * - `sampleSize: N` walks at most N rows (implies deep). Useful for large datasets
17
+ * where row 1 alone may not represent the schema.
18
+ *
19
+ * When more than one row is scanned, empty values (`null`, `undefined`, `''`)
20
+ * are ignored during inference so a single blank cell does not poison a column's type.
21
+ */
22
+ export type AutoGeneratePropertiesOption = boolean | {
23
+ deep?: boolean;
24
+ sampleSize?: number;
25
+ };
8
26
  /**
9
27
  * must be singleValued or multiValued
10
28
  * singleValued contains only one key to an object
@@ -308,8 +326,19 @@ export type ValueMappingConfig = {
308
326
  };
309
327
  export type ModelAttribute = Property | Reference;
310
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
+ */
311
334
  enabled?: boolean;
312
- 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'>;
313
342
  };
314
343
  export type ValidatorErrorType = 'loose' | 'strict';
315
344
  export type ValidatorConfig = {
@@ -333,6 +362,18 @@ export type AttributeValidatorConfig = {
333
362
  unique?: ValidatorConfig;
334
363
  startsWith?: ValidatorConfig;
335
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
+ */
336
377
  export type AttributeQualityConfig = {
337
378
  required?: boolean;
338
379
  nullable?: boolean;
@@ -355,9 +396,17 @@ export type ModelDefinition<ModelNames extends string = string> = {
355
396
  properties?: Property[];
356
397
  references?: Reference[];
357
398
  data?: RawItem[];
358
- 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;
359
408
  autoGenerateKey?: boolean;
360
- autoGenerateProperties?: boolean;
409
+ autoGenerateProperties?: AutoGeneratePropertiesOption;
361
410
  supportsAutoCreation?: boolean;
362
411
  valueMappings?: ValueMappingConfig[];
363
412
  meta?: ModelMetadata;
@@ -1,6 +1,64 @@
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
+ * - `MissingKey` — A data row has no value for the model's key
35
+ * attribute, so it cannot be identified; the row is dropped on ingest.
36
+ * - `DuplicatePropertyKey` — A model definition declares the same property
37
+ * key more than once; the first definition is kept, the duplicate ignored.
38
+ * - `MissingModel` — A reference points to a model key that has not
39
+ * been registered.
40
+ * - `MissingProperty` — Emitted only during model-definition validation
41
+ * when a *derived property* declares `derive.remoteProperty` pointing to a
42
+ * property that does not exist on the remote model. Not used in any
43
+ * CSV/JSON data-ingest path — those are covered by `UndefinedAttribute`,
44
+ * `UndefinedValue`, and `WrongDataType`. See model.helper.ts (derived-
45
+ * property validator) for the sole emit site.
46
+ * - `MissingReference` — A derived property's `derive.localReference` names
47
+ * a reference that does not exist on the local model.
48
+ * - `UnknownExpressionVariable` — An expression property references a
49
+ * variable that is neither a property nor a reference on the local model.
50
+ * - `RemoteReferenceKeyNotFound` — A foreign-key value does not match any
51
+ * key in the referenced remote model. Fires per-row.
52
+ * - `InvalidModelDefinition` — Catch-all for structural problems detected
53
+ * when registering a model.
54
+ * - `UnknownColumn` — A CSV (or otherwise source-tracked) data
55
+ * container contains a column / attribute that is not declared on the
56
+ * target model. Values for the column are silently dropped during ingest;
57
+ * this error surfaces that data loss without failing the load. Fires once
58
+ * per (model, attribute). Only emitted when the loader registers source
59
+ * metadata for the container (so inline `model.data` is unaffected).
60
+ */
61
+ type ModelErrorType = 'UndefinedAttribute' | 'InvalidValidator' | 'UndefinedValue' | 'NullValue' | 'WrongDataType' | 'NonUniqueKey' | 'MissingKey' | 'DuplicatePropertyKey' | 'MissingModel' | 'MissingProperty' | 'MissingReference' | 'UnknownExpressionVariable' | 'RemoteReferenceKeyNotFound' | 'InvalidModelDefinition' | 'UnknownColumn';
4
62
  type DcuplErrorType = 'model' | 'loader';
5
63
  type DcuplErrorBase = {
6
64
  key?: string;