@solidxai/core 0.1.13-beta.12 → 0.1.13-beta.13

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 (79) hide show
  1. package/.claude/settings.local.json +16 -0
  2. package/CHANGELOG.md +570 -0
  3. package/CLAUDE.md +26 -0
  4. package/dist/seeders/seed-data/solid-core-metadata.json +1 -1
  5. package/dist/services/chatter-message.service.d.ts +4 -1
  6. package/dist/services/chatter-message.service.d.ts.map +1 -1
  7. package/dist/services/chatter-message.service.js +4 -1
  8. package/dist/services/chatter-message.service.js.map +1 -1
  9. package/dist/services/crud-helper.service.d.ts +1 -2
  10. package/dist/services/crud-helper.service.d.ts.map +1 -1
  11. package/dist/services/crud-helper.service.js +2 -16
  12. package/dist/services/crud-helper.service.js.map +1 -1
  13. package/dist/services/crud.service.d.ts.map +1 -1
  14. package/dist/services/crud.service.js +1 -1
  15. package/dist/services/crud.service.js.map +1 -1
  16. package/dist/services/draft-publish-helper.service.d.ts +1 -0
  17. package/dist/services/draft-publish-helper.service.d.ts.map +1 -1
  18. package/dist/services/draft-publish-helper.service.js +38 -4
  19. package/dist/services/draft-publish-helper.service.js.map +1 -1
  20. package/dist/services/settings/default-settings-provider.service.d.ts +62 -6
  21. package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
  22. package/dist/services/settings/default-settings-provider.service.js +34 -3
  23. package/dist/services/settings/default-settings-provider.service.js.map +1 -1
  24. package/dist/theme/theme-registry.d.ts +14 -0
  25. package/dist/theme/theme-registry.d.ts.map +1 -0
  26. package/dist/theme/theme-registry.js +147 -0
  27. package/dist/theme/theme-registry.js.map +1 -0
  28. package/package.json +1 -1
  29. package/src/seeders/seed-data/solid-core-metadata.json +1 -1
  30. package/src/services/1.js +6 -0
  31. package/src/services/chatter-message.service.ts +22 -1
  32. package/src/services/crud-helper.service.ts +19 -16
  33. package/src/services/crud.service.ts +2 -1
  34. package/src/services/draft-publish-helper.service.ts +56 -4
  35. package/src/services/settings/default-settings-provider.service.ts +34 -3
  36. package/src/theme/theme-registry.ts +181 -0
  37. package/dist-tests/api/authenticate.spec.js +0 -119
  38. package/dist-tests/api/authenticate.spec.js.map +0 -1
  39. package/dist-tests/api/crud-service.findOne.cityMaster.spec.js +0 -97
  40. package/dist-tests/api/crud-service.findOne.cityMaster.spec.js.map +0 -1
  41. package/dist-tests/api/ping.spec.js +0 -21
  42. package/dist-tests/api/ping.spec.js.map +0 -1
  43. package/dist-tests/helpers/auth.js +0 -41
  44. package/dist-tests/helpers/auth.js.map +0 -1
  45. package/dist-tests/helpers/env.js +0 -11
  46. package/dist-tests/helpers/env.js.map +0 -1
  47. package/docs/agent-builder/notebook-lm-prompt-for-agent-builder.md +0 -136
  48. package/docs/agent-builder/team-ready-note-agent-builder.md +0 -305
  49. package/docs/agent-hub-grooming.md +0 -301
  50. package/docs/dashboards/AGENTIC_DASHBOARD_IMPLEMENTATION_PLAN.md +0 -438
  51. package/docs/dashboards/dashboard-curl-smoke-tests.txt +0 -146
  52. package/docs/dashboards/delete-legacy-dashboard-metadata.sql +0 -172
  53. package/docs/datasource-introspection-ddl-analysis.md +0 -326
  54. package/docs/datasource-introspection-implementation-plan.md +0 -306
  55. package/docs/grouping-enhancements.md +0 -89
  56. package/docs/java-spring/README.md +0 -3
  57. package/docs/java-spring/solid-core-module-deep-dive-report.md +0 -1317
  58. package/docs/module-package-import-handoff.md +0 -691
  59. package/docs/seed-changes.md +0 -65
  60. package/docs/test-data-workflow.md +0 -200
  61. package/docs/type-declaration-import-issue.md +0 -24
  62. package/docs/workflow/business-automation-example-notes.md +0 -309
  63. package/docs/workflow/control-flow-node-addition-sop.md +0 -324
  64. package/docs/workflow/data-engineering-pipeline-example-notes.md +0 -330
  65. package/docs/workflow/foreach-example-notes.md +0 -187
  66. package/docs/workflow/hello-world-example-notes.md +0 -271
  67. package/docs/workflow/kestra-concepts-plugins-blueprints.md +0 -315
  68. package/docs/workflow/loop-until-example-notes.md +0 -198
  69. package/docs/workflow/microservices-and-apis-example-notes.md +0 -264
  70. package/docs/workflow/samples.md +0 -25
  71. package/docs/workflow/what-is-kestra.md +0 -79
  72. package/docs/workflow/workflow-core-module-handoff-summary.md +0 -191
  73. package/docs/workflow/workflow-module-brd.md +0 -185
  74. package/docs/workflow/workflow-module-domain-model.md +0 -252
  75. package/docs/workflow/workflow-module-metadata-dsl.md +0 -302
  76. package/docs/workflow/workflow-module-technical-spec.md +0 -293
  77. package/docs/workflow/workflow-node-type-contracts.md +0 -635
  78. package/docs/workflow/workflow-node-ui-metadata-contract.md +0 -1262
  79. package/docs/workflow/workflow-solidx-model-field-spec.md +0 -284
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getThemeModeFromThemeKey = getThemeModeFromThemeKey;
7
+ exports.getCoreThemes = getCoreThemes;
8
+ exports.getRegisteredThemes = getRegisteredThemes;
9
+ exports.getThemesByMode = getThemesByMode;
10
+ exports.getDefaultThemeKey = getDefaultThemeKey;
11
+ exports.isRegisteredThemeKey = isRegisteredThemeKey;
12
+ const fs_1 = __importDefault(require("fs"));
13
+ const path_1 = __importDefault(require("path"));
14
+ const DEFAULT_THEME_KEYS = {
15
+ light: "solid-light-purple",
16
+ dark: "solid-dark-purple",
17
+ };
18
+ const THEME_MODE_TOKENS = {
19
+ light: "-light-",
20
+ dark: "-dark-",
21
+ };
22
+ const THEME_MODE_SUFFIXES = {
23
+ light: "-light",
24
+ dark: "-dark",
25
+ };
26
+ function normalizeThemeKey(value) {
27
+ const normalized = value?.trim().toLowerCase();
28
+ return normalized || null;
29
+ }
30
+ function matchesThemeMode(value, mode) {
31
+ return (value.includes(THEME_MODE_TOKENS[mode])
32
+ || value.endsWith(THEME_MODE_SUFFIXES[mode])
33
+ || value === mode);
34
+ }
35
+ function titleCase(value) {
36
+ return value
37
+ .split(/[-_\s]+/)
38
+ .filter(Boolean)
39
+ .map((token) => {
40
+ if (token.toLowerCase() === "solid")
41
+ return "SolidX";
42
+ return token.charAt(0).toUpperCase() + token.slice(1);
43
+ })
44
+ .join(" ");
45
+ }
46
+ function getThemeModeFromThemeKey(value) {
47
+ const normalized = normalizeThemeKey(value);
48
+ if (!normalized)
49
+ return null;
50
+ if (matchesThemeMode(normalized, "light"))
51
+ return "light";
52
+ if (matchesThemeMode(normalized, "dark"))
53
+ return "dark";
54
+ return null;
55
+ }
56
+ function createThemeDefinition(key, defaultTheme = false) {
57
+ const mode = getThemeModeFromThemeKey(key);
58
+ if (!mode) {
59
+ throw new Error(`Invalid theme key: ${key}`);
60
+ }
61
+ return {
62
+ key,
63
+ label: titleCase(key),
64
+ mode,
65
+ default: defaultTheme,
66
+ };
67
+ }
68
+ function createDefaultTheme(mode) {
69
+ return createThemeDefinition(DEFAULT_THEME_KEYS[mode], true);
70
+ }
71
+ function groupThemesByMode(themes, mode) {
72
+ return themes.filter((theme) => theme.mode === mode);
73
+ }
74
+ function pickDefaultThemeKey(themes, mode) {
75
+ const themesForMode = groupThemesByMode(themes, mode);
76
+ return (themesForMode.find((theme) => theme.default)?.key
77
+ ?? themesForMode[0]?.key
78
+ ?? DEFAULT_THEME_KEYS[mode]);
79
+ }
80
+ function resolveThemeDirectory() {
81
+ const themeDirectory = path_1.default.resolve(process.cwd(), "..", "solid-ui", "public", "themes");
82
+ try {
83
+ if (fs_1.default.existsSync(themeDirectory) && fs_1.default.statSync(themeDirectory).isDirectory()) {
84
+ return path_1.default.resolve(themeDirectory);
85
+ }
86
+ }
87
+ catch {
88
+ return null;
89
+ }
90
+ return null;
91
+ }
92
+ function isThemeDirectory(themeDirectory, entryName) {
93
+ return fs_1.default.existsSync(path_1.default.join(themeDirectory, entryName, "theme.css"));
94
+ }
95
+ function readThemesFromPath() {
96
+ const themeDirectory = resolveThemeDirectory();
97
+ if (!themeDirectory) {
98
+ return [];
99
+ }
100
+ try {
101
+ const directoryEntries = fs_1.default.readdirSync(themeDirectory, { withFileTypes: true });
102
+ return directoryEntries
103
+ .filter((entry) => entry.isDirectory())
104
+ .map((entry) => entry.name.trim())
105
+ .filter(Boolean)
106
+ .filter((entryName) => isThemeDirectory(themeDirectory, entryName))
107
+ .flatMap((entryName) => {
108
+ const mode = getThemeModeFromThemeKey(entryName);
109
+ if (!mode)
110
+ return [];
111
+ return [createThemeDefinition(entryName, entryName === DEFAULT_THEME_KEYS[mode])];
112
+ })
113
+ .sort((left, right) => left.label.localeCompare(right.label));
114
+ }
115
+ catch {
116
+ return [];
117
+ }
118
+ }
119
+ function getThemes() {
120
+ const discoveredThemes = readThemesFromPath();
121
+ if (discoveredThemes.length) {
122
+ return discoveredThemes;
123
+ }
124
+ return [createDefaultTheme("light"), createDefaultTheme("dark")];
125
+ }
126
+ function getCoreThemes() {
127
+ return getThemes();
128
+ }
129
+ function getRegisteredThemes() {
130
+ return getThemes();
131
+ }
132
+ function getThemesByMode(mode) {
133
+ return groupThemesByMode(getRegisteredThemes(), mode);
134
+ }
135
+ function getDefaultThemeKey(mode = "light") {
136
+ return pickDefaultThemeKey(getRegisteredThemes(), mode);
137
+ }
138
+ function isRegisteredThemeKey(value, mode) {
139
+ if (!value)
140
+ return false;
141
+ return getRegisteredThemes().some((theme) => {
142
+ if (theme.key !== value)
143
+ return false;
144
+ return mode ? theme.mode === mode : true;
145
+ });
146
+ }
147
+ //# sourceMappingURL=theme-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-registry.js","sourceRoot":"","sources":["../../src/theme/theme-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\n\n/**\n * Backend theme registry.\n *\n * Responsibilities:\n * - discover theme folders from the filesystem\n * - classify each theme as light/dark from its key\n * - expose default and grouped theme helpers for backend settings\n *\n * Shared pure parsing helpers are intentionally mirrored in solid-core-ui,\n * but all filesystem discovery stays here.\n */\nexport type ThemeMode = \"light\" | \"dark\";\n\nexport type AppThemeDefinition = {\n key: string;\n label: string;\n mode: ThemeMode;\n default?: boolean;\n};\n\nconst DEFAULT_THEME_KEYS: Record<ThemeMode, string> = {\n light: \"solid-light-purple\",\n dark: \"solid-dark-purple\",\n};\n\nconst THEME_MODE_TOKENS: Record<ThemeMode, string> = {\n light: \"-light-\",\n dark: \"-dark-\",\n};\n\nconst THEME_MODE_SUFFIXES: Record<ThemeMode, string> = {\n light: \"-light\",\n dark: \"-dark\",\n};\n\nfunction normalizeThemeKey(value?: string | null): string | null {\n const normalized = value?.trim().toLowerCase();\n return normalized || null;\n}\n\nfunction matchesThemeMode(value: string, mode: ThemeMode): boolean {\n return (\n value.includes(THEME_MODE_TOKENS[mode])\n || value.endsWith(THEME_MODE_SUFFIXES[mode])\n || value === mode\n );\n}\n\nfunction titleCase(value: string): string {\n return value\n .split(/[-_\\s]+/)\n .filter(Boolean)\n .map((token) => {\n if (token.toLowerCase() === \"solid\") return \"SolidX\";\n return token.charAt(0).toUpperCase() + token.slice(1);\n })\n .join(\" \");\n}\n\nexport function getThemeModeFromThemeKey(value?: string | null): ThemeMode | null {\n const normalized = normalizeThemeKey(value);\n if (!normalized) return null;\n\n if (matchesThemeMode(normalized, \"light\")) return \"light\";\n if (matchesThemeMode(normalized, \"dark\")) return \"dark\";\n\n return null;\n}\n\nfunction createThemeDefinition(key: string, defaultTheme = false): AppThemeDefinition {\n const mode = getThemeModeFromThemeKey(key);\n if (!mode) {\n throw new Error(`Invalid theme key: ${key}`);\n }\n\n return {\n key,\n label: titleCase(key),\n mode,\n default: defaultTheme,\n };\n}\n\nfunction createDefaultTheme(mode: ThemeMode): AppThemeDefinition {\n return createThemeDefinition(DEFAULT_THEME_KEYS[mode], true);\n}\n\nfunction groupThemesByMode(themes: AppThemeDefinition[], mode: ThemeMode): AppThemeDefinition[] {\n return themes.filter((theme) => theme.mode === mode);\n}\n\nfunction pickDefaultThemeKey(themes: AppThemeDefinition[], mode: ThemeMode): string {\n const themesForMode = groupThemesByMode(themes, mode);\n\n return (\n themesForMode.find((theme) => theme.default)?.key\n ?? themesForMode[0]?.key\n ?? DEFAULT_THEME_KEYS[mode]\n );\n}\n\nfunction resolveThemeDirectory(): string | null {\n const themeDirectory = path.resolve(process.cwd(), \"..\", \"solid-ui\", \"public\", \"themes\");\n try {\n if (fs.existsSync(themeDirectory) && fs.statSync(themeDirectory).isDirectory()) {\n return path.resolve(themeDirectory);\n }\n } catch {\n return null;\n }\n\n return null;\n}\n\nfunction isThemeDirectory(themeDirectory: string, entryName: string): boolean {\n return fs.existsSync(path.join(themeDirectory, entryName, \"theme.css\"));\n}\n\nfunction readThemesFromPath(): AppThemeDefinition[] {\n const themeDirectory = resolveThemeDirectory();\n if (!themeDirectory) {\n return [];\n }\n\n try {\n const directoryEntries = fs.readdirSync(themeDirectory, { withFileTypes: true });\n\n return directoryEntries\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name.trim())\n .filter(Boolean)\n .filter((entryName) => isThemeDirectory(themeDirectory, entryName))\n .flatMap((entryName) => {\n const mode = getThemeModeFromThemeKey(entryName);\n if (!mode) return [];\n\n return [createThemeDefinition(entryName, entryName === DEFAULT_THEME_KEYS[mode])];\n })\n .sort((left, right) => left.label.localeCompare(right.label));\n } catch {\n return [];\n }\n}\n\nfunction getThemes(): AppThemeDefinition[] {\n const discoveredThemes = readThemesFromPath();\n\n if (discoveredThemes.length) {\n return discoveredThemes;\n }\n\n return [createDefaultTheme(\"light\"), createDefaultTheme(\"dark\")];\n}\n\nexport function getCoreThemes(): AppThemeDefinition[] {\n return getThemes();\n}\n\nexport function getRegisteredThemes(): AppThemeDefinition[] {\n return getThemes();\n}\n\nexport function getThemesByMode(mode: ThemeMode): AppThemeDefinition[] {\n return groupThemesByMode(getRegisteredThemes(), mode);\n}\n\nexport function getDefaultThemeKey(mode: ThemeMode = \"light\"): string {\n return pickDefaultThemeKey(getRegisteredThemes(), mode);\n}\n\nexport function isRegisteredThemeKey(value?: string | null, mode?: ThemeMode): value is string {\n if (!value) return false;\n\n return getRegisteredThemes().some((theme) => {\n if (theme.key !== value) return false;\n return mode ? theme.mode === mode : true;\n });\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidxai/core",
3
- "version": "0.1.13-beta.12",
3
+ "version": "0.1.13-beta.13",
4
4
  "description": "This module is a NestJS module containing all the required core providers required by a Solid application",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12591,7 +12591,7 @@
12591
12591
  50
12592
12592
  ],
12593
12593
  "enableGlobalSearch": true,
12594
- "create": true,
12594
+ "create": false,
12595
12595
  "edit": true,
12596
12596
  "delete": true,
12597
12597
  "import": true,
@@ -0,0 +1,6 @@
1
+ 1. Do i need to create a storeStreams method for aws service too?
2
+ - Handle later
3
+ 2. queues handling -> if queues is enabled by default, i.e triggerExport(exportTransactionEntity.id).
4
+ - startExport should either return the data or return the transaction id
5
+ 3. How to handle scenarios wherein, nested related exist.(do i need to only get the userkey)
6
+ - show the userKey
@@ -37,6 +37,16 @@ interface ChatterMention {
37
37
  id?: string | number;
38
38
  }
39
39
 
40
+ /**
41
+ * Server-side-only overrides for `postMessage`. Not part of `PostChatterMessageDto`, so
42
+ * nothing here is settable over HTTP - only in-process callers such as migration or
43
+ * back-fill services can supply it.
44
+ */
45
+ export interface PostChatterMessageOptions {
46
+ /** Backdate the message instead of stamping it with the current time. */
47
+ createdAt?: Date;
48
+ }
49
+
40
50
  @Injectable()
41
51
  export class ChatterMessageService extends CRUDService<ChatterMessage> {
42
52
  private readonly _logger = new Logger(ChatterMessageService.name);
@@ -389,7 +399,14 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
389
399
  return this.trimMessageUser(savedMessage);
390
400
  }
391
401
 
392
- async postMessage(postDto: PostChatterMessageDto, files: Express.Multer.File[] = []) {
402
+ /**
403
+ * Post a chatter message.
404
+ *
405
+ * `options` is deliberately kept out of `PostChatterMessageDto` so it can only be supplied
406
+ * by server-side callers (migrations, back-fills). The controller binds the DTO alone, so
407
+ * an API client has no way to backdate a message.
408
+ */
409
+ async postMessage(postDto: PostChatterMessageDto, files: Express.Multer.File[] = [], options: PostChatterMessageOptions = {}) {
393
410
  const coModelName = lowerFirst(postDto.coModelName);
394
411
  await this.assertRecordAccess(coModelName, postDto.coModelEntityId);
395
412
 
@@ -404,6 +421,10 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
404
421
  chatterMessage.coModelEntityId = postDto.coModelEntityId;
405
422
  chatterMessage.coModelName = coModelName;
406
423
  chatterMessage.modelUserKey = postDto.modelUserKey ?? null;
424
+ // Left unset otherwise, so the @CreateDateColumn default still applies.
425
+ if (options.createdAt) {
426
+ chatterMessage.createdAt = options.createdAt;
427
+ }
407
428
 
408
429
  const model = await this.modelMetadataRepo.findOne({
409
430
  where: { singularName: coModelName },
@@ -889,25 +889,25 @@ export class CrudHelperService {
889
889
  };
890
890
  }
891
891
 
892
- async countGroupedRecords(qb: SelectQueryBuilder<any>, basicFilterDto: BasicFilterDto, entityAlias: string) { //TODO : Check how to pass a type to SelectQueryBuilder instead of any
893
- const { limit, offset, ...rest } = basicFilterDto;
894
- const filteredDto = { ...rest, limit: undefined, offset: undefined };
892
+ // async countGroupedRecords(qb: SelectQueryBuilder<any>, basicFilterDto: BasicFilterDto, entityAlias: string, internationalisation?: boolean, draftPublishWorkflow?: boolean, moduleRef?: any) { //TODO : Check how to pass a type to SelectQueryBuilder instead of any
893
+ // const { limit, offset, ...rest } = basicFilterDto;
894
+ // const filteredDto = { ...rest, limit: undefined, offset: undefined };
895
895
 
896
- const filteredQB = this.buildFilterQuery(qb, filteredDto as BasicFilterDto, entityAlias, undefined, undefined, undefined, FilterCombinator.AND, false, false);
896
+ // const filteredQB = this.buildFilterQuery(qb, filteredDto as BasicFilterDto, entityAlias, internationalisation, draftPublishWorkflow, moduleRef, FilterCombinator.AND, false, false);
897
897
 
898
- const groupByFields = this.normalize(filteredDto.groupBy);
898
+ // const groupByFields = this.normalize(filteredDto.groupBy);
899
899
 
900
- if (!groupByFields || groupByFields.length === 0) {
901
- throw new Error(ERROR_MESSAGES.INVALID_GROUP_BY_COUNT);
902
- }
900
+ // if (!groupByFields || groupByFields.length === 0) {
901
+ // throw new Error(ERROR_MESSAGES.INVALID_GROUP_BY_COUNT);
902
+ // }
903
903
 
904
- this.applyGroupBySelections(filteredQB, groupByFields, entityAlias);
905
- this.applyAggregates(filteredQB, ['count'], entityAlias);
906
- filteredQB.limit(undefined).offset(undefined).take(undefined).skip(undefined);
904
+ // this.applyGroupBySelections(filteredQB, groupByFields, entityAlias);
905
+ // this.applyAggregates(filteredQB, ['count'], entityAlias);
906
+ // filteredQB.limit(undefined).offset(undefined).take(undefined).skip(undefined);
907
907
 
908
- const rawResults = await filteredQB.getRawMany();
909
- return rawResults.length;
910
- }
908
+ // const rawResults = await filteredQB.getRawMany();
909
+ // return rawResults.length;
910
+ // }
911
911
 
912
912
  hasReadPermissionOnModel = (activeUser: ActiveUserData, modelName: string) => {
913
913
  const permissionNames = [`${classify(modelName)}Controller.findOne`, `${classify(modelName)}Controller.findMany`];
@@ -980,7 +980,10 @@ export class CrudHelperService {
980
980
  basicFilterDto: BasicFilterDto,
981
981
  alias: string,
982
982
  createQbFn: () => Promise<SelectQueryBuilder<T>>,
983
- postProcessEntities?: (entities: T[]) => Promise<void>
983
+ postProcessEntities?: (entities: T[]) => Promise<void>,
984
+ internationalisation?: boolean,
985
+ draftPublishWorkflow?: boolean,
986
+ moduleRef?: any
984
987
  ): Promise<{ meta: { totalRecords: number }; groupMeta: any[]; groupRecords: any[] }> {
985
988
  const groupByFields = this.normalize(basicFilterDto.groupBy);
986
989
  if (!groupByFields.length) throw new BadRequestException(ERROR_MESSAGES.INVALID_GROUP_BY_COUNT);
@@ -1015,7 +1018,7 @@ export class CrudHelperService {
1015
1018
  aggregates: undefined,
1016
1019
  sort: basicFilterDto.groupFilter?.sort,
1017
1020
  };
1018
- groupQb = this.buildFilterQuery(groupQb, groupFilterDto, alias);
1021
+ groupQb = this.buildFilterQuery(groupQb, groupFilterDto, alias, internationalisation, draftPublishWorkflow, moduleRef);
1019
1022
  groupQb = this.buildGroupByRecordsQuery(groupQb, group, alias, groupAliasMap, aggregateAliasMap, groupExpressionMap);
1020
1023
  const [entities, count] = await groupQb.getManyAndCount();
1021
1024
  if (postProcessEntities) await postProcessEntities(entities);
@@ -583,7 +583,8 @@ private async prepareManyToManyAuditSnapshot(entity: T,id: number,modelSingularN
583
583
  async (entities) => {
584
584
  if (populateUserIdFields?.length) await this.handlePopulateUserIdFields(populateUserIdFields, entities);
585
585
  if (populateMedia?.length) await this.handlePopulateMedia(populateMedia, entities);
586
- }
586
+ },
587
+ internationalisation, draftPublishWorkflow, this.moduleRef
587
588
  );
588
589
  }
589
590
  else {
@@ -333,13 +333,65 @@ export class DraftPublishHelperService {
333
333
  throw new BadRequestException(`${modelName} with id ${id} is already unpublished`);
334
334
  }
335
335
 
336
- return repo.save({
337
- ...entity,
338
- isPublished: false,
339
- updatedBy: activeUser?.sub ?? entity.updatedBy,
336
+ const chainId = entity.initialEntityVersionId || entity.id;
337
+
338
+ return repo.manager.transaction(async (manager) => {
339
+ const transactionalRepo = manager.getRepository(repo.metadata.target);
340
+
341
+ // Release the "na" published tracker before anything else can claim it, mirroring
342
+ // publishRecord's archive-others-then-publish ordering in reverse. unpublishRecord
343
+ // only ever acts on the latest version (see loadLatestEntityForPublishAction), so
344
+ // this always becomes a Draft rather than an Archived version — clear publishedAt
345
+ // to match applyCreateDefaults' Draft convention instead of leaving a stale date.
346
+ const unpublishedEntity = await transactionalRepo.save({
347
+ ...entity,
348
+ isPublished: false,
349
+ publishedAt: null,
350
+ publishedTracker: this.createPublishedVersionTracker(entity.id),
351
+ updatedBy: activeUser?.sub ?? entity.updatedBy,
352
+ } as any) as unknown as T;
353
+
354
+ const chainVersions = await transactionalRepo.find({
355
+ where: [
356
+ { initialEntityVersionId: chainId },
357
+ { id: chainId },
358
+ ] as any,
359
+ }) as unknown as T[];
360
+
361
+ const versionToRestore = this.pickMostRecentlyPublishedVersion(
362
+ chainVersions.filter(version => version.id !== entity.id),
363
+ );
364
+
365
+ if (versionToRestore) {
366
+ await transactionalRepo.save({
367
+ ...versionToRestore,
368
+ isPublished: true,
369
+ publishedAt: new Date(),
370
+ publishedTracker: "na",
371
+ } as any);
372
+ }
373
+
374
+ return unpublishedEntity;
340
375
  });
341
376
  }
342
377
 
378
+ /**
379
+ * Among the other versions in a chain, find the one that was live most recently before
380
+ * being superseded (i.e. the "Archived" version unpublishRecord should restore). Ties on
381
+ * publishedAt fall back to the highest id, matching pickCurrentLocaleRecord's ordering.
382
+ */
383
+ private pickMostRecentlyPublishedVersion<T extends CommonEntity>(versions: T[]): T | null {
384
+ const previouslyPublished = versions.filter(version => Boolean(version.publishedAt));
385
+ if (previouslyPublished.length === 0) return null;
386
+
387
+ return previouslyPublished.sort((a, b) => {
388
+ const aPublishedAt = new Date(a.publishedAt as any).getTime();
389
+ const bPublishedAt = new Date(b.publishedAt as any).getTime();
390
+ if (aPublishedAt !== bPublishedAt) return bPublishedAt - aPublishedAt;
391
+ return Number(b.id) - Number(a.id);
392
+ })[0];
393
+ }
394
+
343
395
  /**
344
396
  * Load the latest version of a draft/publish-enabled record for a publish/unpublish
345
397
  * action, applying the two checks both actions share (workflow enabled, version is latest).
@@ -6,6 +6,7 @@ import {
6
6
  SettingDefinition,
7
7
  SettingLevel,
8
8
  } from "src/interfaces";
9
+ import { getDefaultThemeKey, getThemesByMode } from "src/theme/theme-registry";
9
10
 
10
11
  export const DEFAULT_MEDIA_UPLOAD_DIR = "media-uploads";
11
12
  export const DEFAULT_MEDIA_FILE_STORAGE_DIR = "media-files-storage";
@@ -224,14 +225,44 @@ const getSolidCoreSettings = (isProd: boolean) =>
224
225
  },
225
226
  {
226
227
  moduleName: "solid-core",
227
- key: "enableDarkMode",
228
+ key: "showThemeToggle",
228
229
  value: true,
229
230
  level: SettingLevel.SystemAdminEditable,
230
- label: "Enable Dark Mode",
231
+ label: "Show Theme Toggle",
231
232
  group: "app-settings",
232
233
  sortOrder: 120,
233
234
  controlType: "boolean",
234
- helpText: "Enables dark theme support for frontend surfaces that honor the global appearance setting.",
235
+ helpText: "Shows the theme toggle in the frontend so users can switch between light and dark themes.",
236
+ },
237
+ {
238
+ moduleName: "solid-core",
239
+ key: "lightTheme",
240
+ value: getDefaultThemeKey("light"),
241
+ level: SettingLevel.SystemAdminEditable,
242
+ label: "Light Theme",
243
+ group: "app-settings",
244
+ sortOrder: 125,
245
+ controlType: "selectionStatic",
246
+ options: getThemesByMode("light").map((theme) => ({
247
+ label: theme.label,
248
+ value: theme.key,
249
+ })),
250
+ helpText: "Selects the theme used whenever the application is in light mode.",
251
+ },
252
+ {
253
+ moduleName: "solid-core",
254
+ key: "darkTheme",
255
+ value: getDefaultThemeKey("dark"),
256
+ level: SettingLevel.SystemAdminEditable,
257
+ label: "Dark Theme",
258
+ group: "app-settings",
259
+ sortOrder: 126,
260
+ controlType: "selectionStatic",
261
+ options: getThemesByMode("dark").map((theme) => ({
262
+ label: theme.label,
263
+ value: theme.key,
264
+ })),
265
+ helpText: "Selects the theme used whenever the application is in dark mode.",
235
266
  },
236
267
  {
237
268
  moduleName: "solid-core",
@@ -0,0 +1,181 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ /**
5
+ * Backend theme registry.
6
+ *
7
+ * Responsibilities:
8
+ * - discover theme folders from the filesystem
9
+ * - classify each theme as light/dark from its key
10
+ * - expose default and grouped theme helpers for backend settings
11
+ *
12
+ * Shared pure parsing helpers are intentionally mirrored in solid-core-ui,
13
+ * but all filesystem discovery stays here.
14
+ */
15
+ export type ThemeMode = "light" | "dark";
16
+
17
+ export type AppThemeDefinition = {
18
+ key: string;
19
+ label: string;
20
+ mode: ThemeMode;
21
+ default?: boolean;
22
+ };
23
+
24
+ const DEFAULT_THEME_KEYS: Record<ThemeMode, string> = {
25
+ light: "solid-light-purple",
26
+ dark: "solid-dark-purple",
27
+ };
28
+
29
+ const THEME_MODE_TOKENS: Record<ThemeMode, string> = {
30
+ light: "-light-",
31
+ dark: "-dark-",
32
+ };
33
+
34
+ const THEME_MODE_SUFFIXES: Record<ThemeMode, string> = {
35
+ light: "-light",
36
+ dark: "-dark",
37
+ };
38
+
39
+ function normalizeThemeKey(value?: string | null): string | null {
40
+ const normalized = value?.trim().toLowerCase();
41
+ return normalized || null;
42
+ }
43
+
44
+ function matchesThemeMode(value: string, mode: ThemeMode): boolean {
45
+ return (
46
+ value.includes(THEME_MODE_TOKENS[mode])
47
+ || value.endsWith(THEME_MODE_SUFFIXES[mode])
48
+ || value === mode
49
+ );
50
+ }
51
+
52
+ function titleCase(value: string): string {
53
+ return value
54
+ .split(/[-_\s]+/)
55
+ .filter(Boolean)
56
+ .map((token) => {
57
+ if (token.toLowerCase() === "solid") return "SolidX";
58
+ return token.charAt(0).toUpperCase() + token.slice(1);
59
+ })
60
+ .join(" ");
61
+ }
62
+
63
+ export function getThemeModeFromThemeKey(value?: string | null): ThemeMode | null {
64
+ const normalized = normalizeThemeKey(value);
65
+ if (!normalized) return null;
66
+
67
+ if (matchesThemeMode(normalized, "light")) return "light";
68
+ if (matchesThemeMode(normalized, "dark")) return "dark";
69
+
70
+ return null;
71
+ }
72
+
73
+ function createThemeDefinition(key: string, defaultTheme = false): AppThemeDefinition {
74
+ const mode = getThemeModeFromThemeKey(key);
75
+ if (!mode) {
76
+ throw new Error(`Invalid theme key: ${key}`);
77
+ }
78
+
79
+ return {
80
+ key,
81
+ label: titleCase(key),
82
+ mode,
83
+ default: defaultTheme,
84
+ };
85
+ }
86
+
87
+ function createDefaultTheme(mode: ThemeMode): AppThemeDefinition {
88
+ return createThemeDefinition(DEFAULT_THEME_KEYS[mode], true);
89
+ }
90
+
91
+ function groupThemesByMode(themes: AppThemeDefinition[], mode: ThemeMode): AppThemeDefinition[] {
92
+ return themes.filter((theme) => theme.mode === mode);
93
+ }
94
+
95
+ function pickDefaultThemeKey(themes: AppThemeDefinition[], mode: ThemeMode): string {
96
+ const themesForMode = groupThemesByMode(themes, mode);
97
+
98
+ return (
99
+ themesForMode.find((theme) => theme.default)?.key
100
+ ?? themesForMode[0]?.key
101
+ ?? DEFAULT_THEME_KEYS[mode]
102
+ );
103
+ }
104
+
105
+ function resolveThemeDirectory(): string | null {
106
+ const themeDirectory = path.resolve(process.cwd(), "..", "solid-ui", "public", "themes");
107
+ try {
108
+ if (fs.existsSync(themeDirectory) && fs.statSync(themeDirectory).isDirectory()) {
109
+ return path.resolve(themeDirectory);
110
+ }
111
+ } catch {
112
+ return null;
113
+ }
114
+
115
+ return null;
116
+ }
117
+
118
+ function isThemeDirectory(themeDirectory: string, entryName: string): boolean {
119
+ return fs.existsSync(path.join(themeDirectory, entryName, "theme.css"));
120
+ }
121
+
122
+ function readThemesFromPath(): AppThemeDefinition[] {
123
+ const themeDirectory = resolveThemeDirectory();
124
+ if (!themeDirectory) {
125
+ return [];
126
+ }
127
+
128
+ try {
129
+ const directoryEntries = fs.readdirSync(themeDirectory, { withFileTypes: true });
130
+
131
+ return directoryEntries
132
+ .filter((entry) => entry.isDirectory())
133
+ .map((entry) => entry.name.trim())
134
+ .filter(Boolean)
135
+ .filter((entryName) => isThemeDirectory(themeDirectory, entryName))
136
+ .flatMap((entryName) => {
137
+ const mode = getThemeModeFromThemeKey(entryName);
138
+ if (!mode) return [];
139
+
140
+ return [createThemeDefinition(entryName, entryName === DEFAULT_THEME_KEYS[mode])];
141
+ })
142
+ .sort((left, right) => left.label.localeCompare(right.label));
143
+ } catch {
144
+ return [];
145
+ }
146
+ }
147
+
148
+ function getThemes(): AppThemeDefinition[] {
149
+ const discoveredThemes = readThemesFromPath();
150
+
151
+ if (discoveredThemes.length) {
152
+ return discoveredThemes;
153
+ }
154
+
155
+ return [createDefaultTheme("light"), createDefaultTheme("dark")];
156
+ }
157
+
158
+ export function getCoreThemes(): AppThemeDefinition[] {
159
+ return getThemes();
160
+ }
161
+
162
+ export function getRegisteredThemes(): AppThemeDefinition[] {
163
+ return getThemes();
164
+ }
165
+
166
+ export function getThemesByMode(mode: ThemeMode): AppThemeDefinition[] {
167
+ return groupThemesByMode(getRegisteredThemes(), mode);
168
+ }
169
+
170
+ export function getDefaultThemeKey(mode: ThemeMode = "light"): string {
171
+ return pickDefaultThemeKey(getRegisteredThemes(), mode);
172
+ }
173
+
174
+ export function isRegisteredThemeKey(value?: string | null, mode?: ThemeMode): value is string {
175
+ if (!value) return false;
176
+
177
+ return getRegisteredThemes().some((theme) => {
178
+ if (theme.key !== value) return false;
179
+ return mode ? theme.mode === mode : true;
180
+ });
181
+ }