@docupace/ihub-config 1.1.26 → 1.1.27-sync.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.
- package/dist/config-extension.d.ts +15 -2
- package/dist/config-extension.d.ts.map +1 -1
- package/dist/config-extension.js.map +1 -1
- package/dist/config-service.d.ts +19 -2
- package/dist/config-service.d.ts.map +1 -1
- package/dist/config-service.js +146 -66
- package/dist/config-service.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/named-array-merge.d.ts +29 -0
- package/dist/lib/named-array-merge.d.ts.map +1 -0
- package/dist/lib/named-array-merge.js +158 -0
- package/dist/lib/named-array-merge.js.map +1 -0
- package/dist/mappers/config-mapper.d.ts.map +1 -1
- package/dist/mappers/config-mapper.js +1 -0
- package/dist/mappers/config-mapper.js.map +1 -1
- package/dist/model/graphs/config/fields.graphql +15 -0
- package/dist/model/graphs/config/panel.graphql +7 -0
- package/dist/model/graphs/config/ui-components.graphql +1 -0
- package/dist/providers/index.d.ts +4 -0
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +5 -0
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/screen-layout-patch.d.ts +6 -0
- package/dist/providers/screen-layout-patch.d.ts.map +1 -0
- package/dist/providers/screen-layout-patch.js +57 -0
- package/dist/providers/screen-layout-patch.js.map +1 -0
- package/dist/providers/whole-patch.d.ts +6 -0
- package/dist/providers/whole-patch.d.ts.map +1 -0
- package/dist/providers/whole-patch.js +2 -0
- package/dist/providers/whole-patch.js.map +1 -0
- package/model/graphs/config/fields.graphql +15 -0
- package/model/graphs/config/panel.graphql +7 -0
- package/model/graphs/config/ui-components.graphql +1 -0
- package/package.json +1 -1
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { ExtensionConfig } from '@docupace/ihub-core/types';
|
|
2
2
|
import { ConfigStoreParams } from './config-service.js';
|
|
3
3
|
import type { ConfigInjectorResolvers } from './injector-resolvers/config-injector.js';
|
|
4
|
-
import type { ConfigProviders } from './providers/index.js';
|
|
4
|
+
import type { ConfigProviders, WholePatchMergers } from './providers/index.js';
|
|
5
5
|
import type { ConfigResolversConfig } from './resolvers/index.js';
|
|
6
6
|
export interface ConfigurationOverride {
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Schema-typed CLOB, but the underlying Core attribute is JSON — for a
|
|
9
|
+
* JSON-shaped override (e.g. a screen layout patch), this comes back
|
|
10
|
+
* already parsed as an object rather than as its original JSON string.
|
|
11
|
+
* Consumers that need to `JSON.parse` this must handle both shapes.
|
|
12
|
+
*/
|
|
13
|
+
value: string | Record<string, any>;
|
|
8
14
|
contentType?: string | null;
|
|
9
15
|
}
|
|
10
16
|
export interface ConfigurationOverrideResult {
|
|
@@ -63,6 +69,13 @@ export interface ConfigurationOverrideProvider {
|
|
|
63
69
|
export interface ConfigExtensionOptions {
|
|
64
70
|
configInjectorResolvers?: ConfigInjectorResolvers;
|
|
65
71
|
providers?: ConfigProviders;
|
|
72
|
+
/**
|
|
73
|
+
* Mergers for whole-object patch overrides, keyed by the `contentType` that marks a
|
|
74
|
+
* `whole` override as that kind of patch rather than a full-file replacement (see
|
|
75
|
+
* `WholePatchMerger`). Defaults to `builtInWholePatchMergers` (currently just the
|
|
76
|
+
* Screen Designer layout patch) merged with anything passed here.
|
|
77
|
+
*/
|
|
78
|
+
wholePatchMergers?: WholePatchMergers;
|
|
66
79
|
resolvers?: ConfigResolversConfig;
|
|
67
80
|
configStores?: ConfigStoreParams[];
|
|
68
81
|
configurationOverrideProvider?: ConfigurationOverrideProvider;
|
|
@@ -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;
|
|
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,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC/E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAGlE,MAAM,WAAW,qBAAqB;IACpC;;;;;OAKG;IACH,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,2BAA2B;IAC1C,iEAAiE;IACjE,KAAK,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACpC;;;;;;;;;;OAUG;IACH,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC;IAC3C;;;;;;;;;;;;;;;OAeG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC;CACtD;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IAC/B;qCACiC;IACjC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC;CAC7C;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;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,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"}
|
|
@@ -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;
|
|
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;AAuFrD,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"}
|
package/dist/config-service.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
-
import type { ConfigExtensionOptions, SubPathOverrideLayers } from './config-extension.js';
|
|
1
|
+
import type { ConfigExtensionOptions, ConfigurationOverride, SubPathOverrideLayers } from './config-extension.js';
|
|
2
2
|
import { Model } from '@docupace/ihub-core';
|
|
3
|
+
export interface ScopedConfigLookup {
|
|
4
|
+
/** A domain 51 Role's name (not its id — see the comment above
|
|
5
|
+
* `sanitizeRoleNameSegment`). Falls back to the global path when unset or
|
|
6
|
+
* when no role-scoped file exists yet. */
|
|
7
|
+
roleName?: string;
|
|
8
|
+
/** A JSONPath sub-path (e.g. `$.columns`) — resolves to the sibling file
|
|
9
|
+
* convention instead of the base config path. */
|
|
10
|
+
subPath?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function buildScopedConfigPath(configPath: string, { roleName, subPath }?: ScopedConfigLookup): string;
|
|
3
13
|
export interface ConfigStore {
|
|
4
14
|
supportsRefresh(): boolean;
|
|
5
15
|
hasConfig(path: string): Promise<boolean>;
|
|
@@ -22,6 +32,7 @@ export interface DefaultConfigServiceParams {
|
|
|
22
32
|
configStores: ConfigStoreParams[];
|
|
23
33
|
configInjectorResolvers: ConfigExtensionOptions['configInjectorResolvers'];
|
|
24
34
|
providers: ConfigExtensionOptions['providers'];
|
|
35
|
+
wholePatchMergers?: ConfigExtensionOptions['wholePatchMergers'];
|
|
25
36
|
resolvers: ConfigExtensionOptions['resolvers'];
|
|
26
37
|
configurationOverrideProvider: ConfigExtensionOptions['configurationOverrideProvider'];
|
|
27
38
|
}
|
|
@@ -40,6 +51,9 @@ export declare class ConfigFileStore implements ConfigStore {
|
|
|
40
51
|
hasConfig(configPath: string): Promise<boolean>;
|
|
41
52
|
listConfigs(configPath: string): Promise<string[]>;
|
|
42
53
|
updateConfig(configPath: string, config: any): Promise<void>;
|
|
54
|
+
hasScopedConfig(configPath: string, lookup?: ScopedConfigLookup): Promise<boolean>;
|
|
55
|
+
getScopedConfig(configPath: string, lookup?: ScopedConfigLookup): Promise<any | undefined>;
|
|
56
|
+
updateScopedConfig(configPath: string, config: any, lookup?: ScopedConfigLookup): Promise<void>;
|
|
43
57
|
init(): Promise<void>;
|
|
44
58
|
refresh(): Promise<void>;
|
|
45
59
|
}
|
|
@@ -48,6 +62,7 @@ declare class ConfigService {
|
|
|
48
62
|
private readonly model?;
|
|
49
63
|
private readonly configInjectorResolvers;
|
|
50
64
|
private readonly providers;
|
|
65
|
+
private readonly wholePatchMergers;
|
|
51
66
|
private readonly resolvers;
|
|
52
67
|
private readonly configurationOverrideProvider;
|
|
53
68
|
constructor(model: Model | undefined, configStores: ConfigStore[], extensionOptions?: ConfigExtensionOptions);
|
|
@@ -79,10 +94,12 @@ declare class ConfigService {
|
|
|
79
94
|
getConfigFromStore(configPath: string, context?: any): Promise<{
|
|
80
95
|
config: any;
|
|
81
96
|
subPathOverrides: SubPathOverrideLayers | null;
|
|
97
|
+
whole: ConfigurationOverride | null;
|
|
82
98
|
}>;
|
|
83
99
|
hasConfig(configPath: string): Promise<boolean>;
|
|
84
100
|
getConfig(configPath: string, context: any, values?: any, processProviders?: boolean): Promise<any>;
|
|
85
101
|
getDirectConfig(configPath: string, context: any, values?: any, processProviders?: boolean): Promise<any>;
|
|
102
|
+
private applyWholePatchIfPresent;
|
|
86
103
|
listDirectConfigs(configPath: string): Promise<any[]>;
|
|
87
104
|
updateConfig(configPath: string, config: any, context: any): Promise<any>;
|
|
88
105
|
private normalizePathForMatching;
|
|
@@ -108,6 +125,6 @@ declare class ConfigService {
|
|
|
108
125
|
getPageConfig(pagePath: string, context: any, processConfig?: boolean, rulesContext?: any): Promise<any>;
|
|
109
126
|
init(): Promise<void[]>;
|
|
110
127
|
}
|
|
111
|
-
export declare const defaultConfigService: ({ model, configStores, configInjectorResolvers, providers, resolvers, configurationOverrideProvider, }: DefaultConfigServiceParams) => ConfigService;
|
|
128
|
+
export declare const defaultConfigService: ({ model, configStores, configInjectorResolvers, providers, wholePatchMergers, resolvers, configurationOverrideProvider, }: DefaultConfigServiceParams) => ConfigService;
|
|
112
129
|
export default ConfigService;
|
|
113
130
|
//# sourceMappingURL=config-service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-service.d.ts","sourceRoot":"","sources":["../src/config-service.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,sBAAsB,
|
|
1
|
+
{"version":3,"file":"config-service.d.ts","sourceRoot":"","sources":["../src/config-service.ts"],"names":[],"mappings":"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"}
|
package/dist/config-service.js
CHANGED
|
@@ -7,19 +7,14 @@ import { fileExists } from './lib/file-system.js';
|
|
|
7
7
|
import replaceDotsOutsideExpressions from './lib/replace-dots-outside-expressions.js';
|
|
8
8
|
import { createHash } from 'node:crypto';
|
|
9
9
|
import { injectConfigValue, } from './injector-resolvers/config-injector.js';
|
|
10
|
-
import { builtInProviders } from './providers/index.js';
|
|
10
|
+
import { builtInProviders, builtInWholePatchMergers, } 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 { isNamedObjectArray, mergeNamedObjectArray, } from './lib/named-array-merge.js';
|
|
14
15
|
import jp from 'jsonpath';
|
|
15
16
|
const FULL_OBJECT_EXPRESSION_PATTERN = /^\${(.*?)}$/;
|
|
16
17
|
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
18
|
// An override element counts as "self-contained" when it carries enough of
|
|
24
19
|
// its own definition to render on its own — a real attribute path (or paths)
|
|
25
20
|
// or a computed expression — as opposed to a thin `{ name, visible }`-style
|
|
@@ -36,58 +31,20 @@ function isSelfContainedOverrideElement(item) {
|
|
|
36
31
|
(typeof item.expression === 'string' && item.expression.length > 0));
|
|
37
32
|
}
|
|
38
33
|
/**
|
|
39
|
-
* Merge semantics for a config sub-path override against the value already
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* what makes a saved override non-destructive:
|
|
48
|
-
* - Reordering falls out of the override array's own element order.
|
|
49
|
-
* - "Hiding" an element is an attribute override (e.g. `visible: false`) on
|
|
50
|
-
* an element that's still present, not a deletion.
|
|
51
|
-
* - A base element the override never mentions is appended unchanged, so a
|
|
52
|
-
* base element added later still surfaces for someone with an existing
|
|
53
|
-
* override, instead of being permanently absent from their frozen array.
|
|
54
|
-
* - An override element naming something not found in the base is kept
|
|
55
|
-
* as-is when it's self-contained (has its own path(s)/expression — e.g. a
|
|
56
|
-
* hand-authored column), or silently dropped when it's a thin reference
|
|
57
|
-
* whose target (e.g. a predefined column) no longer exists — this never
|
|
58
|
-
* throws; the stale reference just doesn't round-trip into the next save.
|
|
59
|
-
*
|
|
60
|
-
* Any other shape (scalars, plain objects, arrays without a `name` key) is
|
|
61
|
-
* returned untouched, so the caller's plain whole-value replace applies
|
|
62
|
-
* exactly as it did before this merge existed.
|
|
34
|
+
* Merge semantics for a config sub-path override against the value already at that
|
|
35
|
+
* JSONPath. Generic — used for any typename/sub-path the configPath/subPath mechanism
|
|
36
|
+
* resolves, not just `Table.columns` — a thin wrapper over the shared
|
|
37
|
+
* `mergeNamedObjectArray` (see `named-array-merge.ts` for the full algorithm), fixed to
|
|
38
|
+
* this call site's own definition of "self-contained" (`isSelfContainedOverrideElement`).
|
|
39
|
+
* Any other shape (scalars, plain objects, arrays without a `name` key) is returned
|
|
40
|
+
* untouched, so the caller's plain whole-value replace applies exactly as it did before
|
|
41
|
+
* this merge existed.
|
|
63
42
|
*/
|
|
64
43
|
function mergeSubPathOverrideValue(base, override) {
|
|
65
44
|
if (!isNamedObjectArray(override) || !isNamedObjectArray(base)) {
|
|
66
45
|
return override;
|
|
67
46
|
}
|
|
68
|
-
|
|
69
|
-
const merged = [];
|
|
70
|
-
const seenNames = new Set();
|
|
71
|
-
for (const overrideItem of override) {
|
|
72
|
-
// Guard against a corrupted/hand-edited override array carrying two
|
|
73
|
-
// elements with the same name — only the first is kept, so the merged
|
|
74
|
-
// result never has duplicate names.
|
|
75
|
-
if (seenNames.has(overrideItem.name)) {
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
seenNames.add(overrideItem.name);
|
|
79
|
-
const baseItem = baseByName.get(overrideItem.name);
|
|
80
|
-
if (baseItem) {
|
|
81
|
-
merged.push({ ...baseItem, ...overrideItem });
|
|
82
|
-
}
|
|
83
|
-
else if (isSelfContainedOverrideElement(overrideItem)) {
|
|
84
|
-
merged.push(overrideItem);
|
|
85
|
-
}
|
|
86
|
-
// else: a thin reference to a name that no longer resolves — drop it.
|
|
87
|
-
}
|
|
88
|
-
const mergedNames = new Set(merged.map((item) => item.name));
|
|
89
|
-
const untouchedBaseItems = base.filter((item) => !mergedNames.has(item.name));
|
|
90
|
-
return [...merged, ...untouchedBaseItems];
|
|
47
|
+
return mergeNamedObjectArray(base, override, isSelfContainedOverrideElement);
|
|
91
48
|
}
|
|
92
49
|
// Marks each element of a fully-folded named-object array with whether it
|
|
93
50
|
// would still show up — present *and* not hidden — without the layer stored
|
|
@@ -303,6 +260,46 @@ function applySubPathOverrides(config, subPathPatch) {
|
|
|
303
260
|
}
|
|
304
261
|
return result;
|
|
305
262
|
}
|
|
263
|
+
// Domain 735 rows scoped to a Role sync under a `roles/<roleName>/**` tree
|
|
264
|
+
// that mirrors the existing `global/**` tree, instead of a filename suffix —
|
|
265
|
+
// keeps the sibling sub-path convention below identical regardless of scope.
|
|
266
|
+
// Keyed by the role's name rather than its id so the path is human-readable
|
|
267
|
+
// in the site repo; callers are responsible for keeping writes (Config
|
|
268
|
+
// Sync) and reads (`configuration-override-service.ts`'s `resolve()`)
|
|
269
|
+
// pointed at the same role name — renaming a role orphans its previously
|
|
270
|
+
// synced file, and two roles sharing a name would collide on this path.
|
|
271
|
+
function sanitizeSubPathSegment(subPath) {
|
|
272
|
+
return subPath.replace(/[^A-Za-z0-9_.$()[\]'@=<> -]/g, '_');
|
|
273
|
+
}
|
|
274
|
+
// Role names are admin-entered free text (unlike a role id) — strip
|
|
275
|
+
// anything that isn't safe as a single flat directory segment (notably
|
|
276
|
+
// `/`, which would otherwise nest into subdirectories).
|
|
277
|
+
function sanitizeRoleNameSegment(roleName) {
|
|
278
|
+
return roleName.replace(/[^A-Za-z0-9_.-]/g, '_');
|
|
279
|
+
}
|
|
280
|
+
// The sub-path sibling-file convention: a JSONPath override (e.g. `$.columns`)
|
|
281
|
+
// on `dataTypes/IContact/tabs/employmentTab/fields` lives at
|
|
282
|
+
// `dataTypes/IContact/tabs/employmentTab/fields.$.columns`, next to the base
|
|
283
|
+
// config it patches, rather than inside it.
|
|
284
|
+
function siblingConfigPath(basePath, subPath) {
|
|
285
|
+
const dir = path.dirname(basePath);
|
|
286
|
+
const siblingName = `${path.basename(basePath)}.${sanitizeSubPathSegment(subPath)}`;
|
|
287
|
+
return dir === '.' ? siblingName : path.join(dir, siblingName);
|
|
288
|
+
}
|
|
289
|
+
// Builds the repo-relative path a (configPath, roleName?, subPath?) tuple
|
|
290
|
+
// resolves to under a `ConfigFileStore`'s root — `global/<configPath>` (or
|
|
291
|
+
// its `roles/<roleName>/<configPath>` sibling), with the sub-path suffix
|
|
292
|
+
// applied first when present. Exported as a pure function so callers that
|
|
293
|
+
// only need the path (e.g. computing a sync target path for a diff) don't
|
|
294
|
+
// need a `ConfigFileStore` instance to do the math.
|
|
295
|
+
export function buildScopedConfigPath(configPath, { roleName, subPath } = {}) {
|
|
296
|
+
const effectivePath = subPath
|
|
297
|
+
? siblingConfigPath(configPath, subPath)
|
|
298
|
+
: configPath;
|
|
299
|
+
return roleName
|
|
300
|
+
? path.join('roles', sanitizeRoleNameSegment(roleName), effectivePath)
|
|
301
|
+
: path.join('global', effectivePath);
|
|
302
|
+
}
|
|
306
303
|
export class ConfigFileStore {
|
|
307
304
|
localPath;
|
|
308
305
|
remotePath;
|
|
@@ -399,8 +396,43 @@ export class ConfigFileStore {
|
|
|
399
396
|
const fullPath = path.join(this.localPath, configPath);
|
|
400
397
|
const finalPath = await this.getFinalPath(fullPath);
|
|
401
398
|
const yamlOutput = dump(config);
|
|
399
|
+
// A role-scoped (`roles/<roleName>/...`) or sub-path sibling file may be
|
|
400
|
+
// the first file ever written under its directory — unlike the
|
|
401
|
+
// pre-existing `global/**` tree, that directory isn't guaranteed to
|
|
402
|
+
// exist yet.
|
|
403
|
+
await fsPromises.mkdir(path.dirname(finalPath), { recursive: true });
|
|
402
404
|
await fsPromises.writeFile(finalPath, yamlOutput, 'utf8');
|
|
403
405
|
}
|
|
406
|
+
// Role-aware, sub-path-aware lookup on top of this store's plain
|
|
407
|
+
// `hasConfig`/`getConfig`/`updateConfig` — resolves a role-scoped file
|
|
408
|
+
// first when `roleName` is given, falling back to the global path when no
|
|
409
|
+
// role-scoped file exists yet (mirrors domain 735's GENERAL→ROLE cascade).
|
|
410
|
+
async hasScopedConfig(configPath, lookup = {}) {
|
|
411
|
+
if (lookup.roleName) {
|
|
412
|
+
const rolePath = buildScopedConfigPath(configPath, lookup);
|
|
413
|
+
if (await this.hasConfig(rolePath)) {
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return this.hasConfig(buildScopedConfigPath(configPath, { subPath: lookup.subPath }));
|
|
418
|
+
}
|
|
419
|
+
async getScopedConfig(configPath, lookup = {}) {
|
|
420
|
+
if (lookup.roleName) {
|
|
421
|
+
const rolePath = buildScopedConfigPath(configPath, lookup);
|
|
422
|
+
if (await this.hasConfig(rolePath)) {
|
|
423
|
+
return this.getConfig(rolePath);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
const globalPath = buildScopedConfigPath(configPath, {
|
|
427
|
+
subPath: lookup.subPath,
|
|
428
|
+
});
|
|
429
|
+
return (await this.hasConfig(globalPath))
|
|
430
|
+
? this.getConfig(globalPath)
|
|
431
|
+
: undefined;
|
|
432
|
+
}
|
|
433
|
+
async updateScopedConfig(configPath, config, lookup = {}) {
|
|
434
|
+
await this.updateConfig(buildScopedConfigPath(configPath, lookup), config);
|
|
435
|
+
}
|
|
404
436
|
async init() {
|
|
405
437
|
if (this.remotePath) {
|
|
406
438
|
try {
|
|
@@ -444,6 +476,7 @@ class ConfigService {
|
|
|
444
476
|
model;
|
|
445
477
|
configInjectorResolvers;
|
|
446
478
|
providers;
|
|
479
|
+
wholePatchMergers;
|
|
447
480
|
resolvers;
|
|
448
481
|
configurationOverrideProvider;
|
|
449
482
|
constructor(model, configStores, extensionOptions = {}) {
|
|
@@ -455,6 +488,9 @@ class ConfigService {
|
|
|
455
488
|
this.providers = extensionOptions.providers
|
|
456
489
|
? { ...builtInProviders, ...extensionOptions.providers }
|
|
457
490
|
: { ...builtInProviders };
|
|
491
|
+
this.wholePatchMergers = extensionOptions.wholePatchMergers
|
|
492
|
+
? { ...builtInWholePatchMergers, ...extensionOptions.wholePatchMergers }
|
|
493
|
+
: { ...builtInWholePatchMergers };
|
|
458
494
|
this.resolvers = extensionOptions.resolvers
|
|
459
495
|
? { ...extensionOptions.resolvers }
|
|
460
496
|
: {};
|
|
@@ -857,10 +893,14 @@ class ConfigService {
|
|
|
857
893
|
}
|
|
858
894
|
const override = await this.configurationOverrideProvider?.resolve(refConfigPath, context);
|
|
859
895
|
if (override?.whole?.contentType) {
|
|
896
|
+
// A real-MIME-type asset override (image/svg+xml, etc.) is always base64 text, never the
|
|
897
|
+
// JSON-shaped value the CLOB/JSON attribute mismatch affects — see the screen-layout-patch
|
|
898
|
+
// branch below for that case.
|
|
899
|
+
const data = override.whole.value;
|
|
860
900
|
return {
|
|
861
|
-
data
|
|
901
|
+
data,
|
|
862
902
|
mimeType: override.whole.contentType,
|
|
863
|
-
hash: createHash('sha256').update(
|
|
903
|
+
hash: createHash('sha256').update(data).digest('hex'),
|
|
864
904
|
};
|
|
865
905
|
}
|
|
866
906
|
let standardStore;
|
|
@@ -918,20 +958,24 @@ class ConfigService {
|
|
|
918
958
|
// configPath together, in one provider round trip, instead of fetching
|
|
919
959
|
// them separately at different points of the processing pipeline. The
|
|
920
960
|
// sub-path overrides are handed back alongside the config so callers can
|
|
921
|
-
// thread them into processConfig rather than re-resolving them later
|
|
961
|
+
// thread them into processConfig rather than re-resolving them later; the
|
|
962
|
+
// whole override is handed back too so a screen-layout-patch (a whole
|
|
963
|
+
// override distinguished by its contentType) can be applied later in the
|
|
964
|
+
// pipeline without a second resolve() call.
|
|
922
965
|
async getConfigFromStore(configPath, context) {
|
|
923
966
|
const override = await this.configurationOverrideProvider?.resolve(configPath, context);
|
|
924
967
|
const subPathOverrides = override
|
|
925
968
|
? toSubPathOverrideLayers(override)
|
|
926
969
|
: null;
|
|
927
|
-
|
|
928
|
-
|
|
970
|
+
const whole = override?.whole ?? null;
|
|
971
|
+
if (whole && !whole.contentType) {
|
|
972
|
+
return { config: load(whole.value), subPathOverrides, whole };
|
|
929
973
|
}
|
|
930
974
|
const exceptions = [];
|
|
931
975
|
for (const [index, configStore] of this.configStores.entries()) {
|
|
932
976
|
try {
|
|
933
977
|
const config = await configStore.getConfig(configPath);
|
|
934
|
-
return { config, subPathOverrides };
|
|
978
|
+
return { config, subPathOverrides, whole };
|
|
935
979
|
}
|
|
936
980
|
catch (e) {
|
|
937
981
|
exceptions.push(e);
|
|
@@ -940,7 +984,7 @@ class ConfigService {
|
|
|
940
984
|
}
|
|
941
985
|
}
|
|
942
986
|
}
|
|
943
|
-
return { config: undefined, subPathOverrides };
|
|
987
|
+
return { config: undefined, subPathOverrides, whole };
|
|
944
988
|
}
|
|
945
989
|
async hasConfig(configPath) {
|
|
946
990
|
return (await Promise.all(this.configStores.map((configStore) => configStore.hasConfig(configPath)))).some(Boolean);
|
|
@@ -950,10 +994,40 @@ class ConfigService {
|
|
|
950
994
|
return this.getDirectConfig(globalPath, context, values, processProviders);
|
|
951
995
|
}
|
|
952
996
|
async getDirectConfig(configPath, context, values, processProviders) {
|
|
953
|
-
const { config: obj, subPathOverrides } = await this.getConfigFromStore(configPath, context);
|
|
997
|
+
const { config: obj, subPathOverrides, whole, } = await this.getConfigFromStore(configPath, context);
|
|
954
998
|
const fullObj = await this.processObject(configPath, obj, context, values);
|
|
955
999
|
const processedObj = await this.processConfig(fullObj, context, processProviders, subPathOverrides);
|
|
956
|
-
|
|
1000
|
+
// A whole-object patch override needs the fully $ref/rules-resolved config to merge
|
|
1001
|
+
// onto (its own document is name-keyed against that resolved shape), so it's applied
|
|
1002
|
+
// last — after the whole-file-replacement branch in getConfigFromStore (which this is
|
|
1003
|
+
// mutually exclusive with; that branch short-circuits before $ref resolution) and
|
|
1004
|
+
// after processConfig, not folded into either.
|
|
1005
|
+
return this.applyWholePatchIfPresent(processedObj, whole);
|
|
1006
|
+
}
|
|
1007
|
+
// Shared by getDirectConfig and getPageConfig — both resolve a fully $ref/rules-processed
|
|
1008
|
+
// config that a whole-object patch override (if any exists for this path) needs to merge
|
|
1009
|
+
// onto, so both need this as their final step. `whole` is the same whole-override already
|
|
1010
|
+
// resolved once by getConfigFromStore — no second resolve() call needed.
|
|
1011
|
+
//
|
|
1012
|
+
// This has no knowledge of what any particular contentType's patch document looks like —
|
|
1013
|
+
// it only knows how to look up a WholePatchMerger by contentType and delegate to it. A
|
|
1014
|
+
// `whole` override with no contentType is a full-file replacement, already substituted in
|
|
1015
|
+
// by getConfigFromStore, so there's nothing left to merge here; a contentType with no
|
|
1016
|
+
// registered merger is left untouched rather than guessed at.
|
|
1017
|
+
applyWholePatchIfPresent(processedObj, whole) {
|
|
1018
|
+
const merger = whole?.contentType
|
|
1019
|
+
? this.wholePatchMergers[whole.contentType]
|
|
1020
|
+
: undefined;
|
|
1021
|
+
if (!merger) {
|
|
1022
|
+
return processedObj;
|
|
1023
|
+
}
|
|
1024
|
+
// `Configuration.value` is typed CLOB, but the underlying attribute is JSON — for a
|
|
1025
|
+
// JSON-shaped value like a whole patch, it can come back already parsed as an object
|
|
1026
|
+
// rather than as the JSON string it was saved as. Handle both shapes.
|
|
1027
|
+
const patch = whole.value && typeof whole.value === 'object'
|
|
1028
|
+
? whole.value
|
|
1029
|
+
: JSON.parse(whole.value);
|
|
1030
|
+
return merger.merge(processedObj, patch);
|
|
957
1031
|
}
|
|
958
1032
|
async listDirectConfigs(configPath) {
|
|
959
1033
|
const configs = await Promise.all(this.configStores.map((configStore) => configStore.listConfigs(configPath)));
|
|
@@ -1303,20 +1377,26 @@ class ConfigService {
|
|
|
1303
1377
|
}
|
|
1304
1378
|
async getPageConfig(pagePath, context, processConfig, rulesContext) {
|
|
1305
1379
|
const globalPath = path.join('global', 'pages', pagePath);
|
|
1306
|
-
const { config: obj, subPathOverrides } = await this.getConfigFromStore(globalPath, context);
|
|
1380
|
+
const { config: obj, subPathOverrides, whole, } = await this.getConfigFromStore(globalPath, context);
|
|
1307
1381
|
const fullObj = await this.processObject(globalPath, obj, context, null);
|
|
1308
1382
|
const matchingRuleGroups = await this.getRuleGroups(rulesContext, context);
|
|
1309
1383
|
const configToProcess = this.applyFieldRuleOverrides(fullObj, matchingRuleGroups);
|
|
1310
|
-
|
|
1384
|
+
const processedObj = await this.processConfig(configToProcess, context, processConfig, subPathOverrides);
|
|
1385
|
+
// Keyed by globalPath — the same `global/pages/<pagePath>` path a whole-patch save
|
|
1386
|
+
// (e.g. the Screen Designer's, see toPageConfigOverridePath in docupace-ui-main) writes
|
|
1387
|
+
// under, so a patch saved against a route like `contacts/{id}/details` is found here
|
|
1388
|
+
// too, not just in getDirectConfig.
|
|
1389
|
+
return this.applyWholePatchIfPresent(processedObj, whole);
|
|
1311
1390
|
}
|
|
1312
1391
|
async init() {
|
|
1313
1392
|
return Promise.all(this.configStores.map((configStore) => configStore.init()));
|
|
1314
1393
|
}
|
|
1315
1394
|
}
|
|
1316
|
-
export const defaultConfigService = ({ model, configStores, configInjectorResolvers, providers, resolvers, configurationOverrideProvider, }) => {
|
|
1395
|
+
export const defaultConfigService = ({ model, configStores, configInjectorResolvers, providers, wholePatchMergers, resolvers, configurationOverrideProvider, }) => {
|
|
1317
1396
|
const extensionOptions = {
|
|
1318
1397
|
configInjectorResolvers,
|
|
1319
1398
|
providers,
|
|
1399
|
+
wholePatchMergers,
|
|
1320
1400
|
resolvers,
|
|
1321
1401
|
configurationOverrideProvider,
|
|
1322
1402
|
};
|