@docupace/ihub-config 1.1.25-admin.1 → 1.1.26-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.
Files changed (37) hide show
  1. package/dist/config-extension.d.ts +15 -2
  2. package/dist/config-extension.d.ts.map +1 -1
  3. package/dist/config-extension.js.map +1 -1
  4. package/dist/config-service.d.ts +19 -2
  5. package/dist/config-service.d.ts.map +1 -1
  6. package/dist/config-service.js +146 -66
  7. package/dist/config-service.js.map +1 -1
  8. package/dist/index.d.ts +4 -2
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +2 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/lib/named-array-merge.d.ts +29 -0
  13. package/dist/lib/named-array-merge.d.ts.map +1 -0
  14. package/dist/lib/named-array-merge.js +158 -0
  15. package/dist/lib/named-array-merge.js.map +1 -0
  16. package/dist/mappers/config-mapper.d.ts.map +1 -1
  17. package/dist/mappers/config-mapper.js +1 -0
  18. package/dist/mappers/config-mapper.js.map +1 -1
  19. package/dist/model/graphs/config/fields.graphql +15 -0
  20. package/dist/model/graphs/config/panel.graphql +7 -0
  21. package/dist/model/graphs/config/ui-components.graphql +1 -0
  22. package/dist/providers/index.d.ts +4 -0
  23. package/dist/providers/index.d.ts.map +1 -1
  24. package/dist/providers/index.js +5 -0
  25. package/dist/providers/index.js.map +1 -1
  26. package/dist/providers/screen-layout-patch.d.ts +6 -0
  27. package/dist/providers/screen-layout-patch.d.ts.map +1 -0
  28. package/dist/providers/screen-layout-patch.js +57 -0
  29. package/dist/providers/screen-layout-patch.js.map +1 -0
  30. package/dist/providers/whole-patch.d.ts +6 -0
  31. package/dist/providers/whole-patch.d.ts.map +1 -0
  32. package/dist/providers/whole-patch.js +2 -0
  33. package/dist/providers/whole-patch.js.map +1 -0
  34. package/model/graphs/config/fields.graphql +15 -0
  35. package/model/graphs/config/panel.graphql +7 -0
  36. package/model/graphs/config/ui-components.graphql +1 -0
  37. 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
- value: string;
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;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;;;;;;;;;;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,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
+ {"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;AA0ErD,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"}
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"}
@@ -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":"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"}
1
+ {"version":3,"file":"config-service.d.ts","sourceRoot":"","sources":["../src/config-service.ts"],"names":[],"mappings":"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"}
@@ -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
- * at that JSONPath. Generic — used for any typename/sub-path the
41
- * configPath/subPath mechanism resolves, not just `Table.columns`.
42
- *
43
- * When both the current value and the override value are arrays of objects
44
- * each carrying a string `name`, override elements are merged onto their
45
- * base counterpart by name (override wins per-attribute, everything else
46
- * inherited) instead of the override replacing the array wholesale. This is
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
- const baseByName = new Map(base.map((item) => [item.name, item]));
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: override.whole.value,
901
+ data,
862
902
  mimeType: override.whole.contentType,
863
- hash: createHash('sha256').update(override.whole.value).digest('hex'),
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
- if (override?.whole && !override.whole.contentType) {
928
- return { config: load(override.whole.value), subPathOverrides };
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
- return processedObj;
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
- return await this.processConfig(configToProcess, context, processConfig, subPathOverrides);
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
  };