@docupace/ihub-config 1.1.23 → 1.1.24-admin.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.
@@ -3,11 +3,34 @@ import { ConfigStoreParams } from './config-service.js';
3
3
  import type { ConfigInjectorResolvers } from './injector-resolvers/config-injector.js';
4
4
  import type { ConfigProviders } from './providers/index.js';
5
5
  import type { ConfigResolversConfig } from './resolvers/index.js';
6
+ export interface ConfigurationOverride {
7
+ value: string;
8
+ contentType?: string | null;
9
+ }
10
+ export interface ConfigurationOverrideResult {
11
+ /** The whole-object override for `configPath`, if one is set. */
12
+ whole: ConfigurationOverride | null;
13
+ /**
14
+ * One winning value per distinct JSONPath found for `configPath` (e.g.
15
+ * `{ '$.columns': [...] }`), scope-resolved for the current identity.
16
+ * `null` when there are no sub-path override rows for `configPath` at all.
17
+ */
18
+ subPaths: Record<string, unknown> | null;
19
+ }
20
+ export interface ConfigurationOverrideProvider {
21
+ /**
22
+ * Resolves both the whole-object override and any sub-path overrides for
23
+ * `configPath` in a single lookup, since both are drawn from the same set
24
+ * of override rows.
25
+ */
26
+ resolve(configPath: string, context: any): Promise<ConfigurationOverrideResult>;
27
+ }
6
28
  export interface ConfigExtensionOptions {
7
29
  configInjectorResolvers?: ConfigInjectorResolvers;
8
30
  providers?: ConfigProviders;
9
31
  resolvers?: ConfigResolversConfig;
10
32
  configStores?: ConfigStoreParams[];
33
+ configurationOverrideProvider?: ConfigurationOverrideProvider;
11
34
  }
12
35
  export declare const getConfigExtension: (options?: ConfigExtensionOptions) => Promise<ExtensionConfig>;
13
36
  //# sourceMappingURL=config-extension.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config-extension.d.ts","sourceRoot":"","sources":["../src/config-extension.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,iBAAiB,EAAwB,MAAM,qBAAqB,CAAC;AAE9E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAEvF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAGlE,MAAM,WAAW,sBAAsB;IACrC,uBAAuB,CAAC,EAAE,uBAAuB,CAAC;IAClD,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC,YAAY,CAAC,EAAE,iBAAiB,EAAE,CAAC;CACpC;AA8BD,eAAO,MAAM,kBAAkB,GAC7B,UAAS,sBAA2B,KACnC,OAAO,CAAC,eAAe,CA2BzB,CAAC"}
1
+ {"version":3,"file":"config-extension.d.ts","sourceRoot":"","sources":["../src/config-extension.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,iBAAiB,EAAwB,MAAM,qBAAqB,CAAC;AAE9E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAEvF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAGlE,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,2BAA2B;IAC1C,iEAAiE;IACjE,KAAK,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACpC;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC1C;AAED,MAAM,WAAW,6BAA6B;IAC5C;;;;OAIG;IACH,OAAO,CACL,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,GAAG,GACX,OAAO,CAAC,2BAA2B,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,sBAAsB;IACrC,uBAAuB,CAAC,EAAE,uBAAuB,CAAC;IAClD,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC,YAAY,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACnC,6BAA6B,CAAC,EAAE,6BAA6B,CAAC;CAC/D;AA+BD,eAAO,MAAM,kBAAkB,GAC7B,UAAS,sBAA2B,KACnC,OAAO,CAAC,eAAe,CA4BzB,CAAC"}
@@ -25,6 +25,7 @@ const initializeConfigStores = async (configStores) => {
25
25
  configInjectorResolvers: undefined,
26
26
  providers: undefined,
27
27
  resolvers: undefined,
28
+ configurationOverrideProvider: undefined,
28
29
  });
29
30
  await configService.init();
30
31
  };
@@ -37,6 +38,7 @@ export const getConfigExtension = async (options = {}) => {
37
38
  providers: options.providers,
38
39
  resolvers: options.resolvers,
39
40
  configStores,
41
+ configurationOverrideProvider: options.configurationOverrideProvider,
40
42
  });
41
43
  return {
42
44
  name: 'config',
@@ -1 +1 @@
1
- {"version":3,"file":"config-extension.js","sourceRoot":"","sources":["../src/config-extension.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAqB,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,GAAG,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAGrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AASrD,MAAM,qBAAqB,GAAG,CAAC,YAAiC,EAAE,EAAE;IAClE,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QACxC,GAAG,EAAE,WAAW,CAAC,GAAG;QACpB,IAAI,EAAE,WAAW,CAAC,IAAI;QACtB,QAAQ,EAAE,WAAW,CAAC,QAAQ;QAC9B,SAAS,EAAE,WAAW,CAAC,SAAS;QAChC,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,SAAS,EAAE,OAAO,CAAC;QACvD,UAAU,EAAE,WAAW,CAAC,UAAU;QAClC,YAAY,EAAE,WAAW,CAAC,YAAY;KACvC,CAAC,CAAC,CAAC;AACN,CAAC,CAAC;AAEF,MAAM,sBAAsB,GAAG,KAAK,EAAE,YAAiC,EAAE,EAAE;IACzE,GAAG,CAAC,IAAI,CAAC;QACP,YAAY,EAAE,qBAAqB,CAAC,YAAY,CAAC;KAClD,CAAC,CAAC;IAEH,MAAM,aAAa,GAAG,oBAAoB,CAAC;QACzC,KAAK,EAAE,SAAS;QAChB,YAAY;QACZ,uBAAuB,EAAE,SAAS;QAClC,SAAS,EAAE,SAAS;QACpB,SAAS,EAAE,SAAS;KACrB,CAAC,CAAC;IAEH,MAAM,aAAa,CAAC,IAAI,EAAE,CAAC;AAC7B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EACrC,UAAkC,EAAE,EACV,EAAE;IAC5B,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,eAAe,EAAE,CAAC;IAE/D,MAAM,sBAAsB,CAAC,YAAY,CAAC,CAAC;IAE3C,MAAM,gBAAgB,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CACxD,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,SAAS,EAAE,OAAO,CAAC,CAC7C,CAAC;IAEF,MAAM,WAAW,GAAG,uBAAuB,CAAC;QAC1C,uBAAuB,EAAE,OAAO,CAAC,uBAAuB;QACxD,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,YAAY;KACb,CAAC,CAAC;IAEH,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE,CAAC,mBAAmB,EAAE,EAAE,GAAG,gBAAgB,CAAC;QACxD,OAAO,EAAE;YACP;gBACE,WAAW;aACZ;SACF;QACD,KAAK,EAAE,EAAE;QACT,SAAS,EAAE,EAAE;KACd,CAAC;AACJ,CAAC,CAAC"}
1
+ {"version":3,"file":"config-extension.js","sourceRoot":"","sources":["../src/config-extension.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAqB,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,GAAG,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAGrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAsCrD,MAAM,qBAAqB,GAAG,CAAC,YAAiC,EAAE,EAAE;IAClE,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QACxC,GAAG,EAAE,WAAW,CAAC,GAAG;QACpB,IAAI,EAAE,WAAW,CAAC,IAAI;QACtB,QAAQ,EAAE,WAAW,CAAC,QAAQ;QAC9B,SAAS,EAAE,WAAW,CAAC,SAAS;QAChC,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,SAAS,EAAE,OAAO,CAAC;QACvD,UAAU,EAAE,WAAW,CAAC,UAAU;QAClC,YAAY,EAAE,WAAW,CAAC,YAAY;KACvC,CAAC,CAAC,CAAC;AACN,CAAC,CAAC;AAEF,MAAM,sBAAsB,GAAG,KAAK,EAAE,YAAiC,EAAE,EAAE;IACzE,GAAG,CAAC,IAAI,CAAC;QACP,YAAY,EAAE,qBAAqB,CAAC,YAAY,CAAC;KAClD,CAAC,CAAC;IAEH,MAAM,aAAa,GAAG,oBAAoB,CAAC;QACzC,KAAK,EAAE,SAAS;QAChB,YAAY;QACZ,uBAAuB,EAAE,SAAS;QAClC,SAAS,EAAE,SAAS;QACpB,SAAS,EAAE,SAAS;QACpB,6BAA6B,EAAE,SAAS;KACzC,CAAC,CAAC;IAEH,MAAM,aAAa,CAAC,IAAI,EAAE,CAAC;AAC7B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EACrC,UAAkC,EAAE,EACV,EAAE;IAC5B,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,eAAe,EAAE,CAAC;IAE/D,MAAM,sBAAsB,CAAC,YAAY,CAAC,CAAC;IAE3C,MAAM,gBAAgB,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CACxD,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,SAAS,EAAE,OAAO,CAAC,CAC7C,CAAC;IAEF,MAAM,WAAW,GAAG,uBAAuB,CAAC;QAC1C,uBAAuB,EAAE,OAAO,CAAC,uBAAuB;QACxD,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,YAAY;QACZ,6BAA6B,EAAE,OAAO,CAAC,6BAA6B;KACrE,CAAC,CAAC;IAEH,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE,CAAC,mBAAmB,EAAE,EAAE,GAAG,gBAAgB,CAAC;QACxD,OAAO,EAAE;YACP;gBACE,WAAW;aACZ;SACF;QACD,KAAK,EAAE,EAAE;QACT,SAAS,EAAE,EAAE;KACd,CAAC;AACJ,CAAC,CAAC"}
@@ -23,6 +23,7 @@ export interface DefaultConfigServiceParams {
23
23
  configInjectorResolvers: ConfigExtensionOptions['configInjectorResolvers'];
24
24
  providers: ConfigExtensionOptions['providers'];
25
25
  resolvers: ConfigExtensionOptions['resolvers'];
26
+ configurationOverrideProvider: ConfigExtensionOptions['configurationOverrideProvider'];
26
27
  }
27
28
  export declare class ConfigFileStore implements ConfigStore {
28
29
  private readonly localPath;
@@ -48,6 +49,7 @@ declare class ConfigService {
48
49
  private readonly configInjectorResolvers;
49
50
  private readonly providers;
50
51
  private readonly resolvers;
52
+ private readonly configurationOverrideProvider;
51
53
  constructor(model: Model | undefined, configStores: ConfigStore[], extensionOptions?: ConfigExtensionOptions);
52
54
  private filterConfigsBySearch;
53
55
  private appendTestConfigPaths;
@@ -58,10 +60,14 @@ declare class ConfigService {
58
60
  private processObjectArray;
59
61
  private processObjectProperties;
60
62
  private applyReferenceOverrides;
63
+ private applyReferenceExtensions;
61
64
  private processReferencedObject;
62
65
  private injectConfigValue;
63
66
  generateResolvers(): void;
64
- processConfig(config: any, context: any, processProviders?: boolean): Promise<any>;
67
+ processConfig(config: any, context: any, processProviders?: boolean, rootSubPathOverrides?: Record<string, unknown> | null): Promise<any>;
68
+ private processConfigTree;
69
+ private applyConfigPathOverride;
70
+ private applyTypenameProvider;
65
71
  getRefConfig(configPath: string, refConfigPath: string, context: any, values?: any): Promise<any>;
66
72
  updatePathValues(obj: any, inputPath: string): any;
67
73
  flattenArray(arr: any[]): any[];
@@ -69,7 +75,10 @@ declare class ConfigService {
69
75
  private processImageReference;
70
76
  processObject(configPath: string, obj: any, context: any, values?: any): Promise<any>;
71
77
  private getFileMimeType;
72
- getConfigFromStore(path: string): Promise<any>;
78
+ getConfigFromStore(configPath: string, context?: any): Promise<{
79
+ config: any;
80
+ subPathOverrides: Record<string, unknown> | null;
81
+ }>;
73
82
  hasConfig(configPath: string): Promise<boolean>;
74
83
  getConfig(configPath: string, context: any, values?: any, processProviders?: boolean): Promise<any>;
75
84
  getDirectConfig(configPath: string, context: any, values?: any, processProviders?: boolean): Promise<any>;
@@ -98,6 +107,6 @@ declare class ConfigService {
98
107
  getPageConfig(pagePath: string, context: any, processConfig?: boolean, rulesContext?: any): Promise<any>;
99
108
  init(): Promise<void[]>;
100
109
  }
101
- export declare const defaultConfigService: ({ model, configStores, configInjectorResolvers, providers, resolvers, }: DefaultConfigServiceParams) => ConfigService;
110
+ export declare const defaultConfigService: ({ model, configStores, configInjectorResolvers, providers, resolvers, configurationOverrideProvider, }: DefaultConfigServiceParams) => ConfigService;
102
111
  export default ConfigService;
103
112
  //# sourceMappingURL=config-service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config-service.d.ts","sourceRoot":"","sources":["../src/config-service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config-service.d.ts","sourceRoot":"","sources":["../src/config-service.ts"],"names":[],"mappings":"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"}
@@ -11,8 +11,158 @@ import { builtInProviders } from './providers/index.js';
11
11
  import { deepMergeResolvers, } from './resolvers/index.js';
12
12
  import { dump, load } from 'js-yaml';
13
13
  import { buildQuery } from '@docupace/ihub-core/query';
14
+ import jp from 'jsonpath';
14
15
  const FULL_OBJECT_EXPRESSION_PATTERN = /^\${(.*?)}$/;
15
16
  const INLINE_OBJECT_EXPRESSION_PATTERN = /\${(.*?)}/g;
17
+ function isNamedObjectArray(value) {
18
+ return (Array.isArray(value) &&
19
+ value.every((item) => item !== null &&
20
+ typeof item === 'object' &&
21
+ typeof item.name === 'string'));
22
+ }
23
+ // An override element counts as "self-contained" when it carries enough of
24
+ // its own definition to render on its own — a real attribute path (or paths)
25
+ // or a computed expression — as opposed to a thin `{ name, visible }`-style
26
+ // reference that only makes sense resolved against a base/predefined
27
+ // counterpart of the same name.
28
+ function isSelfContainedOverrideElement(item) {
29
+ const paths = item.paths;
30
+ return ((Array.isArray(paths) && paths.length > 0) ||
31
+ (typeof item.expression === 'string' && item.expression.length > 0));
32
+ }
33
+ /**
34
+ * Merge semantics for a config sub-path override against the value already
35
+ * at that JSONPath. Generic — used for any typename/sub-path the
36
+ * configPath/subPath mechanism resolves, not just `Table.columns`.
37
+ *
38
+ * When both the current value and the override value are arrays of objects
39
+ * each carrying a string `name`, override elements are merged onto their
40
+ * base counterpart by name (override wins per-attribute, everything else
41
+ * inherited) instead of the override replacing the array wholesale. This is
42
+ * what makes a saved override non-destructive:
43
+ * - Reordering falls out of the override array's own element order.
44
+ * - "Hiding" an element is an attribute override (e.g. `visible: false`) on
45
+ * an element that's still present, not a deletion.
46
+ * - A base element the override never mentions is appended unchanged, so a
47
+ * base element added later still surfaces for someone with an existing
48
+ * override, instead of being permanently absent from their frozen array.
49
+ * - An override element naming something not found in the base is kept
50
+ * as-is when it's self-contained (has its own path(s)/expression — e.g. a
51
+ * hand-authored column), or silently dropped when it's a thin reference
52
+ * whose target (e.g. a predefined column) no longer exists — this never
53
+ * throws; the stale reference just doesn't round-trip into the next save.
54
+ *
55
+ * Any other shape (scalars, plain objects, arrays without a `name` key) is
56
+ * returned untouched, so the caller's plain whole-value replace applies
57
+ * exactly as it did before this merge existed.
58
+ */
59
+ function mergeSubPathOverrideValue(base, override) {
60
+ if (!isNamedObjectArray(override) || !isNamedObjectArray(base)) {
61
+ return override;
62
+ }
63
+ const baseByName = new Map(base.map((item) => [item.name, item]));
64
+ const merged = [];
65
+ const seenNames = new Set();
66
+ for (const overrideItem of override) {
67
+ // Guard against a corrupted/hand-edited override array carrying two
68
+ // elements with the same name — only the first is kept, so the merged
69
+ // result never has duplicate names.
70
+ if (seenNames.has(overrideItem.name)) {
71
+ continue;
72
+ }
73
+ seenNames.add(overrideItem.name);
74
+ const baseItem = baseByName.get(overrideItem.name);
75
+ if (baseItem) {
76
+ merged.push({ ...baseItem, ...overrideItem });
77
+ }
78
+ else if (isSelfContainedOverrideElement(overrideItem)) {
79
+ merged.push(overrideItem);
80
+ }
81
+ // else: a thin reference to a name that no longer resolves — drop it.
82
+ }
83
+ const mergedNames = new Set(merged.map((item) => item.name));
84
+ const untouchedBaseItems = base.filter((item) => !mergedNames.has(item.name));
85
+ return [...merged, ...untouchedBaseItems];
86
+ }
87
+ function isRefElement(item) {
88
+ return (item !== null &&
89
+ typeof item === 'object' &&
90
+ typeof item.$ref === 'string');
91
+ }
92
+ // Resolves a config-path override array element's fragment-only `$ref`
93
+ // (e.g. `"#$.predefinedColumns[?(@.name=='email')]"`) against `refRoot` —
94
+ // the config-path's own sibling fields, not another file. This is
95
+ // deliberately narrower than `template`'s YAML `$ref` (processed by
96
+ // `processReferencedObject`/`getRefConfig`, which loads another config
97
+ // file): an override-array `$ref` only ever supports the `#<jsonpath>`
98
+ // self-reference form, resolved with the already-vendored `jsonpath`
99
+ // package (the same one `applyConfigPathOverride` uses for `subPath`
100
+ // strings). A ref that doesn't start with `#`, resolves to nothing, or
101
+ // resolves to a non-object is treated the same as any other unresolved
102
+ // override reference elsewhere in this module — silently dropped rather
103
+ // than thrown, so a stale reference just doesn't round-trip into the next
104
+ // save. Multiple matches take the first, in JSONPath document order.
105
+ //
106
+ // The resolved object keeps the original fragment string under `ref` (not
107
+ // `$ref` — GraphQL field names can't start with `$`) rather than dropping
108
+ // it once resolved. Without this, a client re-saving the array unchanged
109
+ // (e.g. reordering a different column) would have no way to tell this
110
+ // element apart from a plain inline column and would flatten it back into
111
+ // a frozen copy on that save — exactly the staleness problem `$ref` exists
112
+ // to avoid. `Column.ref` (the GraphQL field) surfaces it back to the client
113
+ // so `docupace-ui-main` can keep re-emitting the same `$ref` indefinitely.
114
+ function resolveOverrideElementRef(item, refRoot) {
115
+ const ref = item.$ref;
116
+ if (!ref.startsWith('#')) {
117
+ return undefined;
118
+ }
119
+ const [match] = jp.query(refRoot, ref.slice(1));
120
+ if (match === null || typeof match !== 'object') {
121
+ return undefined;
122
+ }
123
+ const { $ref: _unusedRef, ...localOverrides } = item;
124
+ return {
125
+ ...match,
126
+ ...localOverrides,
127
+ ref,
128
+ };
129
+ }
130
+ // Expands any `$ref` elements of a config-path override array into the
131
+ // full object they point at (merged with whatever local attributes the
132
+ // element carries alongside `$ref`, e.g. `visible`) before the array is
133
+ // handed to `mergeSubPathOverrideValue`. Runs first so that a `{ $ref,
134
+ // visible }` stub — which has no `name` of its own — always arrives at the
135
+ // name-keyed merge as a proper named object; `mergeSubPathOverrideValue`
136
+ // itself needs no `$ref` awareness.
137
+ function expandRefOverrideElements(value, refRoot) {
138
+ if (!Array.isArray(value)) {
139
+ return value;
140
+ }
141
+ return value
142
+ .map((item) => isRefElement(item) ? resolveOverrideElementRef(item, refRoot) : item)
143
+ .filter((item) => item !== undefined);
144
+ }
145
+ // Builds the object a config-path override's `$ref` fragments resolve
146
+ // against: the config being overridden, overlaid with the *raw* (not yet
147
+ // merged) override value of every other simple top-level subPath in this
148
+ // same patch (e.g. `$.predefinedColumns`). Using the raw override value
149
+ // rather than depending on loop order elsewhere in
150
+ // `applyConfigPathOverride` means a `$.columns` override's `$ref` into
151
+ // `$.predefinedColumns` resolves correctly regardless of which subPath
152
+ // happens to be processed first. Only single-segment paths (`$.foo`) are
153
+ // overlaid — nested subPaths (`$.filter.criteria`) fall back to whatever
154
+ // value is already on `config`, which is fine since nothing currently
155
+ // needs to `$ref` into a nested subPath.
156
+ function buildRefResolutionRoot(config, subPathPatch) {
157
+ const overlay = {};
158
+ for (const [jsonPath, value] of Object.entries(subPathPatch)) {
159
+ const topLevelMatch = /^\$\.([^.[]+)$/.exec(jsonPath);
160
+ if (topLevelMatch) {
161
+ overlay[topLevelMatch[1]] = value;
162
+ }
163
+ }
164
+ return { ...config, ...overlay };
165
+ }
16
166
  export class ConfigFileStore {
17
167
  localPath;
18
168
  remotePath;
@@ -155,6 +305,7 @@ class ConfigService {
155
305
  configInjectorResolvers;
156
306
  providers;
157
307
  resolvers;
308
+ configurationOverrideProvider;
158
309
  constructor(model, configStores, extensionOptions = {}) {
159
310
  this.configStores = configStores;
160
311
  this.model = model;
@@ -167,6 +318,8 @@ class ConfigService {
167
318
  this.resolvers = extensionOptions.resolvers
168
319
  ? { ...extensionOptions.resolvers }
169
320
  : {};
321
+ this.configurationOverrideProvider =
322
+ extensionOptions.configurationOverrideProvider;
170
323
  if (this.model) {
171
324
  this.generateResolvers();
172
325
  }
@@ -232,12 +385,34 @@ class ConfigService {
232
385
  }
233
386
  return processedData;
234
387
  }
388
+ applyReferenceExtensions(processedData, referenceConfig) {
389
+ if (typeof referenceConfig?.extend !== 'object') {
390
+ return processedData;
391
+ }
392
+ const { extend } = referenceConfig;
393
+ for (const key in extend) {
394
+ const extensionValue = extend[key];
395
+ if (!Array.isArray(extensionValue)) {
396
+ throw new GraphQLError(`$ref.extend.${key} must be an array; received ${typeof extensionValue}`);
397
+ }
398
+ const baseValue = processedData[key];
399
+ if (baseValue === undefined) {
400
+ processedData[key] = extensionValue;
401
+ continue;
402
+ }
403
+ if (!Array.isArray(baseValue)) {
404
+ throw new GraphQLError(`$ref.extend.${key} can only extend an array property; "${key}" resolved to ${typeof baseValue}`);
405
+ }
406
+ processedData[key] = [...baseValue, ...extensionValue];
407
+ }
408
+ return processedData;
409
+ }
235
410
  async processReferencedObject(configPath, obj, context, values) {
236
411
  const referenceConfig = obj.$ref;
237
412
  const refConfigPath = typeof referenceConfig === 'object' && referenceConfig.refPath
238
413
  ? referenceConfig.refPath
239
414
  : referenceConfig;
240
- const imageData = await this.processImageReference(refConfigPath);
415
+ const imageData = await this.processImageReference(refConfigPath, context);
241
416
  if (imageData) {
242
417
  return imageData;
243
418
  }
@@ -246,6 +421,7 @@ class ConfigService {
246
421
  ? this.updatePathValues(referencedData, referenceConfig.path)
247
422
  : referencedData;
248
423
  processedData = this.applyReferenceOverrides(processedData, referenceConfig);
424
+ processedData = this.applyReferenceExtensions(processedData, referenceConfig);
249
425
  processedData = await this.processObjectExpressions(processedData, values, context);
250
426
  return this.processObject(configPath, processedData, context, values);
251
427
  }
@@ -262,7 +438,16 @@ class ConfigService {
262
438
  const builtInQueryResolvers = {
263
439
  pages: () => ({
264
440
  getPageConfig: async (obj, context) => {
265
- const pageConfig = await this.getPageConfig(obj.path, context, true, obj.context);
441
+ // `impersonateRoleId`/`impersonateGeneral` are a plain
442
+ // pass-through onto context here — this engine never inspects
443
+ // identity itself; the host's resolveCurrentIdentity is what
444
+ // interprets them and enforces the admin-only restriction.
445
+ const impersonation = obj.impersonateRoleId
446
+ ? { impersonateRoleId: obj.impersonateRoleId }
447
+ : obj.impersonateGeneral
448
+ ? { impersonateGeneral: true }
449
+ : null;
450
+ const pageConfig = await this.getPageConfig(obj.path, impersonation ? { ...context, ...impersonation } : context, true, obj.context);
266
451
  return pageConfig;
267
452
  },
268
453
  getAIConfig: async (obj, context) => {
@@ -346,29 +531,37 @@ class ConfigService {
346
531
  return deepMergeResolvers(builtInMutationResolvers, hostMutationResolvers);
347
532
  };
348
533
  }
349
- async processConfig(config, context, processProviders) {
534
+ // Applies any stored config-path sub-path overrides across the whole
535
+ // config tree before processConfig's own recursive walk runs, so that
536
+ // overridden values (which may introduce new sub-config) still go
537
+ // through provider injection/__typename dispatch below.
538
+ //
539
+ // rootSubPathOverrides lets a caller that already resolved overrides for
540
+ // the root config (e.g. getConfigFromStore, which fetches the whole-object
541
+ // and sub-path overrides together in one round trip) hand them in directly
542
+ // instead of having the root node re-resolved a second time below; nested
543
+ // nodes discovered deeper in the tree are still resolved individually,
544
+ // since their configPaths aren't known until the tree has been loaded.
545
+ async processConfig(config, context, processProviders, rootSubPathOverrides) {
546
+ const overriddenConfig = await this.applyConfigPathOverride(config, context, processProviders, rootSubPathOverrides);
547
+ return this.processConfigTree(overriddenConfig, context, processProviders);
548
+ }
549
+ async processConfigTree(config, context, processProviders) {
350
550
  if (!config) {
351
551
  return config;
352
552
  }
353
553
  // Handle arrays
354
554
  if (Array.isArray(config)) {
355
- return Promise.all(config.map((item) => this.processConfig(item, context, processProviders)));
555
+ return Promise.all(config.map((item) => this.processConfigTree(item, context, processProviders)));
356
556
  }
357
557
  // Handle objects
358
558
  if (typeof config === 'object') {
359
559
  // Recursively process all properties of config
360
560
  let processedConfig = { ...config };
361
561
  for (const key in processedConfig) {
362
- processedConfig[key] = await this.processConfig(processedConfig[key], context, processProviders);
363
- }
364
- // First process the current object if it has __typename
365
- if (processedConfig?.__typename && processProviders) {
366
- const providerClass = this.providers[processedConfig.__typename];
367
- if (providerClass) {
368
- const provider = new providerClass();
369
- processedConfig = await provider.processConfig(processedConfig, this, context);
370
- }
562
+ processedConfig[key] = await this.processConfigTree(processedConfig[key], context, processProviders);
371
563
  }
564
+ processedConfig = await this.applyTypenameProvider(processedConfig, context, processProviders);
372
565
  return processedConfig;
373
566
  }
374
567
  if (processProviders) {
@@ -376,6 +569,83 @@ class ConfigService {
376
569
  }
377
570
  return config;
378
571
  }
572
+ // Walks the whole config tree up front (pre-order) and, for every object
573
+ // that declares a configPath, deep-sets any stored sub-path overrides
574
+ // (JSONPath-addressed, e.g. "$.columns" or "$.filter.criteria").
575
+ //
576
+ // preFetchedSubPaths is only honored for the root node of the walk (the
577
+ // one being called with a defined third argument); nested calls always
578
+ // omit it so their own configPath gets resolved fresh.
579
+ async applyConfigPathOverride(config, context, processProviders, preFetchedSubPaths) {
580
+ if (!config) {
581
+ return config;
582
+ }
583
+ if (Array.isArray(config)) {
584
+ return Promise.all(config.map((item) => this.applyConfigPathOverride(item, context, processProviders)));
585
+ }
586
+ if (typeof config !== 'object') {
587
+ return config;
588
+ }
589
+ let result = config;
590
+ if (processProviders) {
591
+ const subPathPatch = preFetchedSubPaths !== undefined
592
+ ? preFetchedSubPaths
593
+ : result.configPath && this.configurationOverrideProvider
594
+ ? (await this.configurationOverrideProvider.resolve(result.configPath, context)).subPaths
595
+ : null;
596
+ if (subPathPatch) {
597
+ const refRoot = buildRefResolutionRoot(result, subPathPatch);
598
+ // Deep-clone once, up front — `jp.value` mutates its target in
599
+ // place, and a shallow `{ ...result }` per jsonPath only protects
600
+ // the top level. A nested subPath (e.g. `$.filter.criteria`) would
601
+ // otherwise still mutate `result.filter`, which is the *same*
602
+ // object reference as the original, potentially-shared config
603
+ // (e.g. reused across tables via `extend`/fragment composition).
604
+ // Config values are always plain JSON (from YAML/domain storage),
605
+ // so a JSON round-trip clone is sufficient and avoids
606
+ // `structuredClone`'s cross-realm `instanceof` pitfalls (its output
607
+ // fails `instanceof Object` checks — e.g. inside the `jsonpath`
608
+ // package — when the calling code runs in a different VM
609
+ // realm/context, as test sandboxes commonly do).
610
+ result = JSON.parse(JSON.stringify(result));
611
+ for (const [jsonPath, value] of Object.entries(subPathPatch)) {
612
+ // A single malformed override (an unparseable `$ref` JSONPath, or
613
+ // a jsonPath that doesn't resolve to a settable location) must not
614
+ // abort resolution of the rest of this config tree — that would
615
+ // take down the page for every user over one bad admin-authored
616
+ // row. Log and skip just this jsonPath instead.
617
+ try {
618
+ const currentValue = jp.value(result, jsonPath);
619
+ const expandedValue = expandRefOverrideElements(value, refRoot);
620
+ const mergedValue = mergeSubPathOverrideValue(currentValue, expandedValue);
621
+ const applied = jp.value(result, jsonPath, mergedValue);
622
+ if (applied === undefined) {
623
+ throw new Error(`does not resolve to a settable location: "${jsonPath}"`);
624
+ }
625
+ }
626
+ catch (error) {
627
+ log.warn(`Skipping configuration override for "${result.configPath}" at "${jsonPath}": ${error.message}`);
628
+ }
629
+ }
630
+ }
631
+ }
632
+ const overriddenEntries = await Promise.all(Object.entries(result).map(async ([key, value]) => [
633
+ key,
634
+ await this.applyConfigPathOverride(value, context, processProviders),
635
+ ]));
636
+ return Object.fromEntries(overriddenEntries);
637
+ }
638
+ async applyTypenameProvider(processedConfig, context, processProviders) {
639
+ if (!processedConfig?.__typename || !processProviders) {
640
+ return processedConfig;
641
+ }
642
+ const providerClass = this.providers[processedConfig.__typename];
643
+ if (!providerClass) {
644
+ return processedConfig;
645
+ }
646
+ const provider = new providerClass();
647
+ return await provider.processConfig(processedConfig, this, context);
648
+ }
379
649
  async getRefConfig(configPath, refConfigPath, context, values) {
380
650
  let fullPath;
381
651
  if (refConfigPath.startsWith('.')) {
@@ -462,13 +732,21 @@ class ConfigService {
462
732
  };
463
733
  return await processValue(object);
464
734
  }
465
- async processImageReference(refConfigPath) {
735
+ async processImageReference(refConfigPath, context) {
466
736
  const SUPPORTED_IMAGE_FORMATS = ['.jpg', '.jpeg', '.png', '.svg'];
467
737
  // Check if the reference is an image file
468
738
  const ext = path.extname(refConfigPath).toLowerCase();
469
739
  if (!SUPPORTED_IMAGE_FORMATS.includes(ext)) {
470
740
  return null;
471
741
  }
742
+ const override = await this.configurationOverrideProvider?.resolve(refConfigPath, context);
743
+ if (override?.whole?.contentType) {
744
+ return {
745
+ data: override.whole.value,
746
+ mimeType: override.whole.contentType,
747
+ hash: createHash('sha256').update(override.whole.value).digest('hex'),
748
+ };
749
+ }
472
750
  let standardStore;
473
751
  for (const store of this.configStores) {
474
752
  if (store instanceof ConfigFileStore) {
@@ -520,19 +798,31 @@ class ConfigService {
520
798
  return 'application/octet-stream';
521
799
  }
522
800
  }
523
- async getConfigFromStore(path) {
801
+ // Resolves the whole-object override and any sub-path overrides for
802
+ // configPath together, in one provider round trip, instead of fetching
803
+ // them separately at different points of the processing pipeline. The
804
+ // sub-path overrides are handed back alongside the config so callers can
805
+ // thread them into processConfig rather than re-resolving them later.
806
+ async getConfigFromStore(configPath, context) {
807
+ const override = await this.configurationOverrideProvider?.resolve(configPath, context);
808
+ const subPathOverrides = override?.subPaths ?? null;
809
+ if (override?.whole && !override.whole.contentType) {
810
+ return { config: load(override.whole.value), subPathOverrides };
811
+ }
524
812
  const exceptions = [];
525
813
  for (const [index, configStore] of this.configStores.entries()) {
526
814
  try {
527
- return await configStore.getConfig(path);
815
+ const config = await configStore.getConfig(configPath);
816
+ return { config, subPathOverrides };
528
817
  }
529
818
  catch (e) {
530
819
  exceptions.push(e);
531
820
  if (index == this.configStores.length - 1) {
532
- throw new GraphQLError(`Config not found in path in any store: ${path}`, { originalError: exceptions });
821
+ throw new GraphQLError(`Config not found in path in any store: ${configPath}`, { originalError: exceptions });
533
822
  }
534
823
  }
535
824
  }
825
+ return { config: undefined, subPathOverrides };
536
826
  }
537
827
  async hasConfig(configPath) {
538
828
  return (await Promise.all(this.configStores.map((configStore) => configStore.hasConfig(configPath)))).some(Boolean);
@@ -542,9 +832,9 @@ class ConfigService {
542
832
  return this.getDirectConfig(globalPath, context, values, processProviders);
543
833
  }
544
834
  async getDirectConfig(configPath, context, values, processProviders) {
545
- const obj = await this.getConfigFromStore(configPath);
835
+ const { config: obj, subPathOverrides } = await this.getConfigFromStore(configPath, context);
546
836
  const fullObj = await this.processObject(configPath, obj, context, values);
547
- const processedObj = await this.processConfig(fullObj, context, processProviders);
837
+ const processedObj = await this.processConfig(fullObj, context, processProviders, subPathOverrides);
548
838
  return processedObj;
549
839
  }
550
840
  async listDirectConfigs(configPath) {
@@ -895,21 +1185,22 @@ class ConfigService {
895
1185
  }
896
1186
  async getPageConfig(pagePath, context, processConfig, rulesContext) {
897
1187
  const globalPath = path.join('global', 'pages', pagePath);
898
- const obj = await this.getConfigFromStore(globalPath);
1188
+ const { config: obj, subPathOverrides } = await this.getConfigFromStore(globalPath, context);
899
1189
  const fullObj = await this.processObject(globalPath, obj, context, null);
900
1190
  const matchingRuleGroups = await this.getRuleGroups(rulesContext, context);
901
1191
  const configToProcess = this.applyFieldRuleOverrides(fullObj, matchingRuleGroups);
902
- return await this.processConfig(configToProcess, context, processConfig);
1192
+ return await this.processConfig(configToProcess, context, processConfig, subPathOverrides);
903
1193
  }
904
1194
  async init() {
905
1195
  return Promise.all(this.configStores.map((configStore) => configStore.init()));
906
1196
  }
907
1197
  }
908
- export const defaultConfigService = ({ model, configStores, configInjectorResolvers, providers, resolvers, }) => {
1198
+ export const defaultConfigService = ({ model, configStores, configInjectorResolvers, providers, resolvers, configurationOverrideProvider, }) => {
909
1199
  const extensionOptions = {
910
1200
  configInjectorResolvers,
911
1201
  providers,
912
1202
  resolvers,
1203
+ configurationOverrideProvider,
913
1204
  };
914
1205
  return new ConfigService(model, configStores.map((configStore) => new ConfigFileStore(configStore)), extensionOptions);
915
1206
  };