@galadrim/galadmin 0.2.9 → 0.2.11-alpha.1.1

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 (234) hide show
  1. package/.agents/report/blockers.json +1 -0
  2. package/.agents/report/failures/2026-09-23-api-user-filter-contract.md +31 -0
  3. package/.agents/report/failures/2026-09-23-api-values-contract.md +25 -0
  4. package/.agents/report/failures/2026-10-01-cycle-02-create-callback-contract.md +17 -0
  5. package/.agents/report/failures/2026-10-01-cycle-06-delete-callback-id-contract.md +17 -0
  6. package/.agents/report/failures/2026-10-01-cycle-07-panel-delete-callback-id-contract.md +24 -0
  7. package/.agents/report/failures/2026-10-01-cycle-13-update-callback-id-contract.md +17 -0
  8. package/.agents/report/failures/2026-10-01-cycle-15-rich-text-upload-boundary-contract.md +24 -0
  9. package/.agents/report/failures/2026-10-03-cycle-06-record-import-parser-contract.md +11 -0
  10. package/.agents/report/failures/2026-10-03-cycle-07-panel-import-parser-contract.md +24 -0
  11. package/.agents/report/failures/2026-10-07-resume-cycle-02-api-assembly-contract.md +46 -0
  12. package/.agents/report/port/2026-09-23-api-config-enrichment.md +13 -0
  13. package/.agents/report/port/2026-09-23-api-file-upload.md +11 -0
  14. package/.agents/report/port/2026-09-23-api-query-authorization.md +14 -0
  15. package/.agents/report/port/2026-09-23-api-request-configuration.md +11 -0
  16. package/.agents/report/port/2026-09-23-database-manager.md +13 -0
  17. package/.agents/report/port/2026-10-01-api-adjacent-record-id.md +9 -0
  18. package/.agents/report/port/2026-10-01-cycle-01-api-config-route.md +9 -0
  19. package/.agents/report/port/2026-10-01-cycle-03-custom-action.md +9 -0
  20. package/.agents/report/port/2026-10-01-cycle-04-custom-component.md +7 -0
  21. package/.agents/report/port/2026-10-01-cycle-05-custom-query.md +7 -0
  22. package/.agents/report/port/2026-10-01-cycle-08-bulk-delete.md +9 -0
  23. package/.agents/report/port/2026-10-01-cycle-09-duplicate-record.md +9 -0
  24. package/.agents/report/port/2026-10-01-cycle-10-notification-badges.md +9 -0
  25. package/.agents/report/port/2026-10-01-cycle-11-record.md +7 -0
  26. package/.agents/report/port/2026-10-01-cycle-12-search-companies.md +11 -0
  27. package/.agents/report/port/2026-10-01-cycle-14-upload-file.md +7 -0
  28. package/.agents/report/port/2026-10-03-cycle-01-create-record.md +11 -0
  29. package/.agents/report/port/2026-10-03-cycle-02-delete-record.md +11 -0
  30. package/.agents/report/port/2026-10-03-cycle-03-panel-delete-record.md +11 -0
  31. package/.agents/report/port/2026-10-03-cycle-04-update-record.md +11 -0
  32. package/.agents/report/port/2026-10-03-cycle-05-api-values.md +11 -0
  33. package/.agents/report/port/2026-10-06-cycle-01-record-import.md +9 -0
  34. package/.agents/report/port/2026-10-06-cycle-03-rich-text-upload.md +7 -0
  35. package/.agents/report/port/2026-10-06-cycle-04-user-filters.md +11 -0
  36. package/.agents/report/port/2026-10-06-resume-cycle-01-panel-import.md +9 -0
  37. package/.agents/report/port/2026-10-07-cycle-01-api-assembly.md +16 -0
  38. package/.agents/report/port/2026-10-07-cycle-02-index-entry-point.md +13 -0
  39. package/.agents/report/remediation/2026-10-02-api-user-filter-remediation.md +71 -0
  40. package/.agents/report/remediation/2026-10-02-api-values-remediation.md +17 -0
  41. package/.agents/report/remediation/2026-10-03-callback-id-contract-remediation.md +36 -0
  42. package/.agents/report/remediation/2026-10-06-import-parser-remediation.md +41 -0
  43. package/.agents/report/remediation/2026-10-06-rich-text-upload-remediation.md +33 -0
  44. package/.agents/report/remediation/2026-10-07-api-assembly-remediation.md +32 -0
  45. package/.agents/skills/port-javascript-to-typescript/SKILL.md +42 -0
  46. package/.agents/skills/port-javascript-to-typescript/agents/openai.yaml +3 -0
  47. package/.agents/skills/port-javascript-to-typescript/references/blockers.md +31 -0
  48. package/.agents/skills/port-javascript-to-typescript/references/planning.md +43 -0
  49. package/.agents/skills/port-javascript-to-typescript/references/porting.md +9 -0
  50. package/.agents/skills/port-javascript-to-typescript/references/reporting.md +24 -0
  51. package/.agents/skills/port-javascript-to-typescript/references/rules.md +8 -0
  52. package/.agents/skills/port-javascript-to-typescript/references/selection.md +16 -0
  53. package/.agents/skills/port-javascript-to-typescript/references/tools.md +20 -0
  54. package/.agents/skills/port-javascript-to-typescript/scripts/check.sh +17 -0
  55. package/.agents/skills/port-javascript-to-typescript/scripts/graph.sh +30 -0
  56. package/.agents/skills/port-javascript-to-typescript/scripts/madge.json +12 -0
  57. package/.agents/skills/port-javascript-to-typescript/scripts/oxlint.json +14 -0
  58. package/.agents/skills/port-javascript-to-typescript/scripts/test-tools.cjs +76 -0
  59. package/.github/actions/version-bump/action.yml +41 -0
  60. package/.github/workflows/alpha.yml +53 -0
  61. package/.github/workflows/ci.yml +36 -0
  62. package/.github/workflows/main.yml +14 -42
  63. package/AGENTS.md +5 -0
  64. package/README.md +41 -4
  65. package/dist/api/authorization.d.ts +38 -0
  66. package/dist/api/authorization.js +74 -0
  67. package/dist/api/authorization.js.map +1 -0
  68. package/dist/api/config.d.ts +42 -0
  69. package/dist/api/config.js +105 -0
  70. package/dist/api/config.js.map +1 -0
  71. package/dist/api/filterUser.d.ts +2 -0
  72. package/dist/api/filterUser.js +24 -0
  73. package/dist/api/filterUser.js.map +1 -0
  74. package/dist/api/importRecords.d.ts +1 -0
  75. package/dist/api/importRecords.js +16 -0
  76. package/dist/api/importRecords.js.map +1 -0
  77. package/dist/api/queries.d.ts +48 -0
  78. package/dist/api/queries.js +139 -0
  79. package/dist/api/queries.js.map +1 -0
  80. package/dist/api/request.d.ts +29 -0
  81. package/dist/api/request.js +3 -0
  82. package/dist/api/request.js.map +1 -0
  83. package/dist/api/routes/adjacentRecordId.d.ts +10 -0
  84. package/dist/api/routes/adjacentRecordId.js +13 -0
  85. package/dist/api/routes/adjacentRecordId.js.map +1 -0
  86. package/dist/api/routes/config.d.ts +3 -0
  87. package/dist/api/routes/config.js +27 -0
  88. package/dist/api/routes/config.js.map +1 -0
  89. package/dist/api/routes/createRecord.d.ts +19 -0
  90. package/dist/api/routes/createRecord.js +93 -0
  91. package/dist/api/routes/createRecord.js.map +1 -0
  92. package/dist/api/routes/customAction.d.ts +17 -0
  93. package/dist/api/routes/customAction.js +35 -0
  94. package/dist/api/routes/customAction.js.map +1 -0
  95. package/dist/api/routes/customComponent.d.ts +3 -0
  96. package/dist/api/routes/customComponent.js +34 -0
  97. package/dist/api/routes/customComponent.js.map +1 -0
  98. package/dist/api/routes/customQuery.d.ts +7 -0
  99. package/dist/api/routes/customQuery.js +33 -0
  100. package/dist/api/routes/customQuery.js.map +1 -0
  101. package/dist/api/routes/deleteRecord.d.ts +9 -0
  102. package/dist/api/routes/deleteRecord.js +46 -0
  103. package/dist/api/routes/deleteRecord.js.map +1 -0
  104. package/dist/api/routes/deleteRecordFromPanel.d.ts +10 -0
  105. package/dist/api/routes/deleteRecordFromPanel.js +36 -0
  106. package/dist/api/routes/deleteRecordFromPanel.js.map +1 -0
  107. package/dist/api/routes/deleteRecords.d.ts +9 -0
  108. package/dist/api/routes/deleteRecords.js +12 -0
  109. package/dist/api/routes/deleteRecords.js.map +1 -0
  110. package/dist/api/routes/duplicateRecord.d.ts +17 -0
  111. package/dist/api/routes/duplicateRecord.js +55 -0
  112. package/dist/api/routes/duplicateRecord.js.map +1 -0
  113. package/dist/api/routes/foreignSelectRecords.d.ts +10 -0
  114. package/dist/api/routes/foreignSelectRecords.js +61 -0
  115. package/dist/api/routes/foreignSelectRecords.js.map +1 -0
  116. package/dist/api/routes/notificationBadges.d.ts +6 -0
  117. package/dist/api/routes/notificationBadges.js +31 -0
  118. package/dist/api/routes/notificationBadges.js.map +1 -0
  119. package/dist/api/routes/record.d.ts +7 -0
  120. package/dist/api/routes/record.js +32 -0
  121. package/dist/api/routes/record.js.map +1 -0
  122. package/dist/api/routes/records.d.ts +14 -0
  123. package/dist/api/routes/records.js +57 -0
  124. package/dist/api/routes/records.js.map +1 -0
  125. package/dist/api/routes/recordsExport.d.ts +13 -0
  126. package/dist/api/routes/recordsExport.js +67 -0
  127. package/dist/api/routes/recordsExport.js.map +1 -0
  128. package/dist/api/routes/recordsImport.d.ts +4 -0
  129. package/dist/api/routes/recordsImport.js +100 -0
  130. package/dist/api/routes/recordsImport.js.map +1 -0
  131. package/dist/api/routes/searchCompanies.d.ts +17 -0
  132. package/dist/api/routes/searchCompanies.js +72 -0
  133. package/dist/api/routes/searchCompanies.js.map +1 -0
  134. package/dist/api/routes/subrecordsImport.d.ts +4 -0
  135. package/dist/api/routes/subrecordsImport.js +94 -0
  136. package/dist/api/routes/subrecordsImport.js.map +1 -0
  137. package/dist/api/routes/updateRecord.d.ts +13 -0
  138. package/dist/api/routes/updateRecord.js +55 -0
  139. package/dist/api/routes/updateRecord.js.map +1 -0
  140. package/dist/api/routes/uploadFile.d.ts +3 -0
  141. package/dist/api/routes/uploadFile.js +13 -0
  142. package/dist/api/routes/uploadFile.js.map +1 -0
  143. package/dist/api/routes/uploadFileFromBetterRichTextInput.d.ts +3 -0
  144. package/dist/api/routes/uploadFileFromBetterRichTextInput.js +33 -0
  145. package/dist/api/routes/uploadFileFromBetterRichTextInput.js.map +1 -0
  146. package/dist/api/uploadFile.d.ts +4 -0
  147. package/dist/api/uploadFile.js +209 -0
  148. package/dist/api/uploadFile.js.map +1 -0
  149. package/dist/api/values.d.ts +6 -0
  150. package/dist/api/values.js +88 -0
  151. package/dist/api/values.js.map +1 -0
  152. package/dist/api.d.ts +8 -27
  153. package/dist/api.js +80 -1389
  154. package/dist/api.js.map +1 -1
  155. package/dist/index.d.ts +2 -2
  156. package/dist/index.js +8 -2
  157. package/dist/index.js.map +1 -1
  158. package/dist/tsconfig.strict.tsbuildinfo +1 -0
  159. package/dist/types/config.d.ts +17 -7
  160. package/dist/types/config.js +4 -0
  161. package/dist/types/config.js.map +1 -1
  162. package/dist/types/database.d.ts +1 -0
  163. package/dist/types/database.js +3 -0
  164. package/dist/types/database.js.map +1 -0
  165. package/dist/utils/dbManager.d.ts +32 -7
  166. package/dist/utils/dbManager.js +59 -7
  167. package/dist/utils/dbManager.js.map +1 -1
  168. package/package.json +12 -2
  169. package/src/api/authorization.ts +129 -0
  170. package/src/api/config.ts +152 -0
  171. package/src/api/filterUser.ts +28 -0
  172. package/src/api/importRecords.ts +16 -0
  173. package/src/api/queries.ts +194 -0
  174. package/src/api/request.ts +31 -0
  175. package/src/api/routes/adjacentRecordId.ts +20 -0
  176. package/src/api/routes/config.ts +15 -0
  177. package/src/api/routes/createRecord.ts +131 -0
  178. package/src/api/routes/customAction.ts +60 -0
  179. package/src/api/routes/customComponent.ts +44 -0
  180. package/src/api/routes/customQuery.ts +53 -0
  181. package/src/api/routes/deleteRecord.ts +59 -0
  182. package/src/api/routes/deleteRecordFromPanel.ts +58 -0
  183. package/src/api/routes/deleteRecords.ts +28 -0
  184. package/src/api/routes/duplicateRecord.ts +73 -0
  185. package/src/api/routes/foreignSelectRecords.ts +80 -0
  186. package/src/api/routes/notificationBadges.ts +47 -0
  187. package/src/api/routes/record.ts +48 -0
  188. package/src/api/routes/records.ts +91 -0
  189. package/src/api/routes/recordsExport.ts +92 -0
  190. package/src/api/routes/recordsImport.ts +115 -0
  191. package/src/api/routes/searchCompanies.ts +96 -0
  192. package/src/api/routes/subrecordsImport.ts +114 -0
  193. package/src/api/routes/updateRecord.ts +74 -0
  194. package/src/api/routes/uploadFile.ts +16 -0
  195. package/src/api/routes/uploadFileFromBetterRichTextInput.ts +41 -0
  196. package/src/api/uploadFile.ts +249 -0
  197. package/src/api/values.ts +96 -0
  198. package/src/api.ts +94 -1578
  199. package/src/front/ckeditor5-build/node_modules/semver/bin/semver.js +0 -0
  200. package/src/index.ts +42 -25
  201. package/src/types/config.ts +17 -7
  202. package/src/types/database.ts +2 -0
  203. package/src/utils/dbManager.ts +99 -14
  204. package/tests/api-helpers.test.ts +59 -0
  205. package/tests/dbManager.test.ts +16 -1
  206. package/tests/helpers/fakeDb.ts +9 -7
  207. package/tests/integration/authorizationBoundary.integration.test.ts +57 -0
  208. package/tests/integration/configEnrichment.integration.test.ts +79 -0
  209. package/tests/integration/customComponentBoundary.integration.test.ts +58 -0
  210. package/tests/integration/dbManager.integration.test.ts +105 -3
  211. package/tests/integration/importParserChild.cjs +55 -0
  212. package/tests/integration/indexRuntime.integration.test.ts +22 -0
  213. package/tests/integration/indexRuntimeChild.cjs +110 -0
  214. package/tests/integration/queries.integration.test.ts +88 -0
  215. package/tests/integration/requestConfig.integration.test.ts +118 -0
  216. package/tests/integration/routes.authorization.integration.test.ts +1 -0
  217. package/tests/integration/routes.config.integration.test.ts +39 -1
  218. package/tests/integration/routes.crud.integration.test.ts +233 -1
  219. package/tests/integration/routes.custom.integration.test.ts +119 -0
  220. package/tests/integration/routes.foreign.integration.test.ts +3 -2
  221. package/tests/integration/routes.importExport.integration.test.ts +308 -6
  222. package/tests/integration/routes.integration.test.ts +4 -0
  223. package/tests/integration/routes.records.integration.test.ts +82 -6
  224. package/tests/integration/routes.upload.integration.test.ts +178 -0
  225. package/tests/integration/userFilters.integration.test.ts +225 -0
  226. package/tsconfig.json +1 -1
  227. package/tsconfig.strict.json +45 -0
  228. package/dist/front/ckeditor5-build/src/ckeditor.d.ts +0 -43
  229. package/dist/front/ckeditor5-build/src/ckeditor.js +0 -130
  230. package/dist/front/ckeditor5-build/src/ckeditor.js.map +0 -1
  231. package/dist/tsconfig.tsbuildinfo +0 -1
  232. package/src/front/ckeditor5-build/node_modules/.bin/semver +0 -12
  233. package/src/front/ckeditor5-build/node_modules/.bin/semver.cmd +0 -17
  234. package/src/front/ckeditor5-build/node_modules/.bin/semver.ps1 +0 -28
@@ -0,0 +1,14 @@
1
+ {
2
+ "plugins": ["typescript"],
3
+ "categories": { "correctness": "off" },
4
+ "rules": {
5
+ "typescript/no-explicit-any": "warn",
6
+ "typescript/no-non-null-assertion": "warn",
7
+ "typescript/ban-ts-comment": ["warn", {
8
+ "ts-ignore": true,
9
+ "ts-expect-error": true,
10
+ "ts-nocheck": true,
11
+ "ts-check": false
12
+ }]
13
+ }
14
+ }
@@ -0,0 +1,76 @@
1
+ const assert = require('node:assert/strict');
2
+ const fs = require('node:fs');
3
+ const os = require('node:os');
4
+ const path = require('node:path');
5
+ const { spawnSync } = require('node:child_process');
6
+
7
+ const temporary = fs.mkdtempSync(path.join(os.tmpdir(), 'port tools '));
8
+ const tools = path.join(temporary, 'copied skill');
9
+ const repo = path.join(temporary, 'target repo');
10
+ fs.cpSync(__dirname, tools, { recursive: true });
11
+ fs.mkdirSync(repo);
12
+
13
+ function run(script, args, status = 0) {
14
+ const result = spawnSync('bash', [path.join(tools, script), ...args], {
15
+ cwd: repo, encoding: 'utf8', timeout: 120000,
16
+ });
17
+ assert.ifError(result.error);
18
+ assert.equal(result.status, status, result.stderr + result.stdout);
19
+ return result;
20
+ }
21
+
22
+ try {
23
+ const inputs = {
24
+ 'tsconfig.json': JSON.stringify({ compilerOptions: {
25
+ baseUrl: '.', paths: { '@left': ['./left.ts'] }, allowJs: true,
26
+ } }),
27
+ 'package.json': JSON.stringify({ madge: { excludeRegExp: ['.*'] } }),
28
+ 'entry.ts': 'import { left } from "@left"; import type { Value } from "./types";\n'
29
+ + 'const common = require("./common.cjs"); import("./dynamic");\n',
30
+ 'left.ts': 'import "./right"; export const left = 1;',
31
+ 'right.ts': 'import "./left";',
32
+ 'types.ts': 'export type Value = string;',
33
+ 'common.cjs': 'module.exports = 1;',
34
+ 'dynamic.ts': 'export const dynamic = 1;',
35
+ 'missing.ts': 'import "./does-not-exist";',
36
+ 'warnings.ts': 'let value: any;\nvalue!.field;\n'
37
+ + '// @ts-expect-error documented suppression\nvalue.call();\n'
38
+ + '// @ts-check\nconst unused = 1; debugger;\n',
39
+ '.oxlintrc.json': JSON.stringify({ rules: {
40
+ 'typescript/no-explicit-any': 'off', 'no-unused-vars': 'error',
41
+ } }),
42
+ '.eslintignore': 'warnings.ts\n',
43
+ 'broken.ts': 'const = ;',
44
+ };
45
+ for (const [file, content] of Object.entries(inputs)) {
46
+ fs.writeFileSync(path.join(repo, file), content);
47
+ }
48
+ for (const script of ['graph.sh', 'check.sh']) {
49
+ run(script, ['--help']);
50
+ run(script, [], 2);
51
+ }
52
+ const graph = JSON.parse(run('graph.sh', ['tsconfig.json', 'entry.ts']).stdout);
53
+ assert.deepEqual(graph['entry.ts'], ['common.cjs', 'dynamic.ts', 'left.ts', 'types.ts']);
54
+ assert.deepEqual(graph['left.ts'], ['right.ts']);
55
+ assert.deepEqual(graph['right.ts'], ['left.ts']);
56
+ const missing = run('graph.sh', ['tsconfig.json', 'missing.ts']);
57
+ assert.match(missing.stderr, /does-not-exist/);
58
+ assert.deepEqual(JSON.parse(missing.stdout)['missing.ts'], []);
59
+ run('graph.sh', ['tsconfig.json', 'absent.ts'], 1);
60
+
61
+ const lint = JSON.parse(run('check.sh', ['warnings.ts']).stdout);
62
+ assert.equal(lint.number_of_files, 1);
63
+ assert.equal(lint.diagnostics.length, 3);
64
+ assert.ok(lint.diagnostics.every(diagnostic => diagnostic.severity === 'warning'));
65
+ for (const rule of ['no-explicit-any', 'no-non-null-assertion', 'ban-ts-comment']) {
66
+ assert.ok(lint.diagnostics.some(diagnostic => diagnostic.code.includes(rule)), rule);
67
+ }
68
+ run('check.sh', ['broken.ts'], 1);
69
+ run('check.sh', ['absent.ts'], 1);
70
+ for (const [file, content] of Object.entries(inputs)) {
71
+ assert.equal(fs.readFileSync(path.join(repo, file), 'utf8'), content);
72
+ }
73
+ console.log('Tool integration checks passed.');
74
+ } finally {
75
+ fs.rmSync(temporary, { recursive: true, force: true });
76
+ }
@@ -0,0 +1,41 @@
1
+ name: 'Automated Version Bump'
2
+ description: >-
3
+ Bumps the package version, commits package.json + package-lock.json,
4
+ tags the result and pushes both to the current branch.
5
+ Requires a checkout with persisted credentials and a job granting
6
+ `permissions: contents: write`.
7
+
8
+ inputs:
9
+ version-type:
10
+ description: 'SemVer release type: patch, minor or major'
11
+ required: false
12
+ default: 'patch'
13
+
14
+ outputs:
15
+ version:
16
+ description: 'The new version, without the v prefix (e.g. 0.2.12)'
17
+ value: ${{ steps.bump.outputs.version }}
18
+
19
+ runs:
20
+ using: 'composite'
21
+ steps:
22
+ - name: Configure git author
23
+ shell: bash
24
+ run: |
25
+ git config user.name "github-actions[bot]"
26
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
27
+
28
+ - name: Bump, commit and tag
29
+ id: bump
30
+ shell: bash
31
+ run: |
32
+ VERSION=$(npm version "${{ inputs.version-type }}" --no-git-tag-version)
33
+ VERSION="${VERSION#v}"
34
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
35
+ git add package.json package-lock.json
36
+ git commit -m "ci: version bump to $VERSION"
37
+ git tag "$VERSION"
38
+
39
+ - name: Push commit and tag
40
+ shell: bash
41
+ run: git push origin "HEAD:${{ github.ref_name }}" "${{ steps.bump.outputs.version }}"
@@ -0,0 +1,53 @@
1
+ name: Publish alpha
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ permissions:
7
+ contents: read
8
+
9
+ concurrency:
10
+ group: alpha-publish
11
+
12
+ jobs:
13
+ test:
14
+ uses: ./.github/workflows/ci.yml
15
+
16
+ build-and-publish-alpha:
17
+ needs: test
18
+ runs-on: ubicloud-standard-2
19
+ env:
20
+ CI: false
21
+ steps:
22
+ - name: Checkout code
23
+ uses: actions/checkout@v7
24
+ - uses: actions/setup-node@v7
25
+ with:
26
+ node-version: 22
27
+ registry-url: 'https://registry.npmjs.org'
28
+ cache: 'npm'
29
+ cache-dependency-path: |
30
+ package-lock.json
31
+ src/front/package-lock.json
32
+ - name: Compute alpha version
33
+ run: |
34
+ BASE=$(node -p "require('./package.json').version")
35
+ npm version "$BASE-alpha.${{ github.run_number }}.${{ github.run_attempt }}" --no-git-tag-version --allow-same-version
36
+ - name: Dependencies installation
37
+ run: npm ci
38
+ - name: Front-end - Dependencies installation
39
+ run: npm ci
40
+ working-directory: ./src/front
41
+ - name: Front-end - Generate build
42
+ run: npm run build
43
+ working-directory: ./src/front
44
+ - name: Generate build
45
+ run: npm run build
46
+ - name: Publish package
47
+ run: npm publish --tag alpha
48
+ env:
49
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
50
+ - name: Verify dist-tags
51
+ run: npm view @galadrim/galadmin dist-tags --json
52
+ env:
53
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -0,0 +1,36 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ workflow_call:
6
+
7
+ jobs:
8
+ test:
9
+ runs-on: ubicloud-standard-2
10
+ steps:
11
+ - name: Checkout code
12
+ uses: actions/checkout@v7
13
+ - uses: actions/setup-node@v7
14
+ with:
15
+ node-version: 22
16
+ cache: 'npm'
17
+ cache-dependency-path: |
18
+ package-lock.json
19
+ src/front/package-lock.json
20
+ - name: Dependencies installation
21
+ run: npm ci
22
+ - name: Front-end - Dependencies installation
23
+ run: npm ci
24
+ working-directory: ./src/front
25
+ # The root tsconfig includes the custom CKEditor source under src/front.
26
+ - name: Typecheck
27
+ run: npm run typecheck
28
+ - name: Run unit tests
29
+ run: npm test
30
+ - name: Run integration tests (real MySQL/PostgreSQL via testcontainers)
31
+ run: npm run test:integration
32
+ env:
33
+ TEST_INTEGRATION: '1'
34
+ - name: Run front-end tests
35
+ run: npm test -- --watchAll=false
36
+ working-directory: ./src/front
@@ -6,61 +6,33 @@ on:
6
6
 
7
7
  jobs:
8
8
  test:
9
- runs-on: ubicloud-standard-2
10
- env:
11
- CI: false
12
- steps:
13
- - name: Checkout code
14
- uses: actions/checkout@v2
15
- - uses: actions/setup-node@v3
16
- with:
17
- node-version: 22
18
- - name: Dependencies installation
19
- run: npm install
20
- - name: Run unit tests
21
- run: npm test
22
- - name: Run integration tests (real MySQL/PostgreSQL via testcontainers)
23
- run: npm run test:integration
24
- env:
25
- TEST_INTEGRATION: '1'
26
- - name: Front-end - Load cache
27
- uses: actions/cache@v4
28
- with:
29
- path: '**/src/front/node_modules'
30
- key: ${{ runner.os }}-npm-front-${{ hashFiles('**/src/front/package-lock.json') }}
31
- - name: Front-end - Dependencies installation
32
- run: npm install
33
- working-directory: ./src/front
34
- - name: Run front-end tests
35
- run: npm test -- --watchAll=false
36
- working-directory: ./src/front
9
+ uses: ./.github/workflows/ci.yml
37
10
 
38
11
  build-and-deploy:
39
12
  needs: test
40
13
  runs-on: ubicloud-standard-2
14
+ permissions:
15
+ contents: write
41
16
  env:
42
17
  CI: false
43
18
  steps:
44
19
  - name: Checkout code
45
- uses: actions/checkout@v2
46
- - uses: actions/setup-node@v3
20
+ uses: actions/checkout@v7
21
+ - uses: actions/setup-node@v7
47
22
  with:
48
23
  node-version: 22
24
+ cache: 'npm'
25
+ cache-dependency-path: |
26
+ package-lock.json
27
+ src/front/package-lock.json
49
28
  - name: 'Automated Version Bump'
50
- uses: 'phips28/gh-action-bump-version@master'
51
- env:
52
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
29
+ uses: ./.github/actions/version-bump
53
30
  with:
54
- version-type: 'patch'
31
+ version-type: 'patch'
55
32
  - name: Dependencies installation
56
- run: npm install
57
- - name: Front-end - Load cache
58
- uses: actions/cache@v4
59
- with:
60
- path: '**/src/front/node_modules'
61
- key: ${{ runner.os }}-npm-front-${{ hashFiles('**/src/front/package-lock.json') }}
33
+ run: npm ci
62
34
  - name: Front-end - Dependencies installation
63
- run: npm install
35
+ run: npm ci
64
36
  working-directory: ./src/front
65
37
  - name: Front-end - Generate build
66
38
  run: npm run build
@@ -68,6 +40,6 @@ jobs:
68
40
  - name: Generate build
69
41
  run: npm run build
70
42
  - name: Publish package
71
- uses: JS-DevTools/npm-publish@v1
43
+ uses: JS-DevTools/npm-publish@v4
72
44
  with:
73
45
  token: ${{ secrets.NPM_TOKEN }}
package/AGENTS.md ADDED
@@ -0,0 +1,5 @@
1
+ # Testing
2
+
3
+ - When adding tests, keep mocks to the minimum necessary.
4
+ - Never mock the database.
5
+ - If a test would require mocking, extend the integration tests instead.
package/README.md CHANGED
@@ -229,6 +229,7 @@ upload of a file onto AWS S3. A basic configuration would be as follow:
229
229
  dataType: 'file',
230
230
  s3Config: {
231
231
  host: process.env.AWS_S3_HOST,
232
+ /** Optional. When omitted, the AWS SDK default credential chain is used. */
232
233
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
233
234
  secretAccessKey: process.env.AWS_SECRET_KEY,
234
235
  bucket: process.env.AWS_S3_PODCAST_BUCKET_NAME,
@@ -240,6 +241,35 @@ upload of a file onto AWS S3. A basic configuration would be as follow:
240
241
  }]
241
242
  ```
242
243
 
244
+ When `accessKeyId` is omitted or empty, the AWS SDK will try to resolve the credentials itself (first match wins):
245
+
246
+ 1. Env vars AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY (+ AWS_SESSION_TOKEN), skipped if AWS_PROFILE is set
247
+ 2. Profile AWS_PROFILE (or "default") from ~/.aws/config and ~/.aws/credentials (SSO, static keys, assumed role, credential_process)
248
+ 3. Web identity token: AWS_WEB_IDENTITY_TOKEN_FILE + AWS_ROLE_ARN (e.g. EKS)
249
+ 4. ECS container role: AWS_CONTAINER_CREDENTIALS_RELATIVE_URI or AWS_CONTAINER_CREDENTIALS_FULL_URI
250
+ 5. EC2 instance role via the instance metadata service, unless AWS_EC2_METADATA_DISABLED is set to anything but `false`
251
+
252
+ It's recommended to rely on a role rather than have long-lived AWS credentials added to the server: the instance profile role on EC2/Elastic Beanstalk, or the task role (not the execution role) on ECS/Fargate. Add the following permissions to that role:
253
+
254
+ ```
255
+ {
256
+ "Version": "2012-10-17",
257
+ "Statement": [{
258
+ "Effect": "Allow",
259
+ "Action": [
260
+ "s3:PutObject",
261
+ "s3:PutObjectAcl"
262
+ ],
263
+ "Resource": "arn:aws:s3:::<bucket>/<subfolder>/*"
264
+ }]
265
+ }
266
+ ```
267
+
268
+ - Without `subfolder`, use `arn:aws:s3:::<bucket>/*` as the resource.
269
+ - By default, files are uploaded with a `public-read` ACL, so the bucket must allow ACLs: its Object Ownership must not be "Bucket owner enforced" (otherwise `AccessControlListNotSupported`), and `BlockPublicAcls` must be off (otherwise `AccessDenied`).
270
+ - With `signedUrlNeeded: true`, no ACL is set: only `s3:PutObject` is needed and the bucket can stay private.
271
+ - If the bucket is encrypted with SSE-KMS, also allow `kms:GenerateDataKey` on its KMS key.
272
+
243
273
  ### File upload on Google Cloud Platform (GCP)
244
274
 
245
275
  You can also use the FileColumn to upload files to Google Cloud Storage. To use GCP instead of AWS S3, configure the gcpConfig as follows:
@@ -302,6 +332,7 @@ engines (see below); the unit suite covers pure logic and configuration wiring
302
332
  without any database.
303
333
 
304
334
  ```bash
335
+ npm run typecheck # check backend TypeScript without emitting build files
305
336
  npm test # run the unit suite (no database required)
306
337
  npm run test:watch # watch mode
307
338
  npm run test:coverage # unit + integration merged report (Docker required)
@@ -323,6 +354,12 @@ npm run test:all # unit suite + integration suite
323
354
  reachable Docker daemon and fails without one, so a broken Docker setup in CI
324
355
  cannot pass silently.
325
356
 
357
+ The `CI` GitHub Actions workflow runs typechecking, backend unit and integration
358
+ tests, and frontend tests on pull requests. The deployment workflow reuses the
359
+ same workflow and only builds and publishes after it passes. To require these
360
+ checks before merging, configure the CI test check as required in the target
361
+ branch's protection rules.
362
+
326
363
  ### Known robustness limitations
327
364
 
328
365
  The former `tests/security-known-gaps.test.ts` suite encoded unfixed security
@@ -334,14 +371,14 @@ them.
334
371
  A few robustness limitations remain documented (not assertable over HTTP
335
372
  because the error escapes as an unhandled rejection under express 4):
336
373
 
337
- * A throwing `parseImportFile` in `/recordsImport` or `/subrecordsImport` is
374
+ - A throwing `parseImportFile` in `/recordsImport` or `/subrecordsImport` is
338
375
  awaited OUTSIDE the try/catch of the multiparty callback — the 500 is never
339
376
  sent and the process can crash.
340
- * `formatValueForInsertion(false)` returns `"NULL"`, so a boolean `false`
377
+ - `formatValueForInsertion(false)` returns `"NULL"`, so a boolean `false`
341
378
  cannot be imported.
342
- * The local-fs upload backend requires `subdirectory` (publicPath +
379
+ - The local-fs upload backend requires `subdirectory` (publicPath +
343
380
  undefined concat yields a nonexistent upload dir).
344
- * A throwing `editCallbackFunction` in `/updateRecord` is awaited OUTSIDE any
381
+ - A throwing `editCallbackFunction` in `/updateRecord` is awaited OUTSIDE any
345
382
  try/catch — the update is committed but the request hangs forever.
346
383
 
347
384
  ### Frontend tests
@@ -0,0 +1,38 @@
1
+ import type { BaseTypeView, TablePanel } from "../types/config";
2
+ type SessionRequest = {
3
+ session?: object;
4
+ };
5
+ type WritableView = Pick<BaseTypeView, "roles"> & Pick<TablePanel, "tableName"> & {
6
+ recordViewPanels?: (Pick<TablePanel, "tableName"> & {
7
+ createConfig?: Pick<TablePanel, "tableName">;
8
+ })[];
9
+ };
10
+ export declare const ROOT_ROLE = "root";
11
+ export declare const roleOf: (req: SessionRequest) => string | undefined;
12
+ export declare const canWriteView: (view: Pick<BaseTypeView, "roles"> | undefined, role: string | undefined) => boolean;
13
+ export declare const viewFromRequest: <T = WritableView>(req: {
14
+ body?: {
15
+ viewIndex?: unknown;
16
+ };
17
+ query?: {
18
+ viewIndex?: unknown;
19
+ };
20
+ config?: {
21
+ views?: T[];
22
+ };
23
+ }) => T | undefined;
24
+ export declare const MUTATING_VIEW_ROUTES: Set<string>;
25
+ export declare const areSafeColumnNames: (data: unknown) => boolean;
26
+ export declare const writableTables: (req: SessionRequest & {
27
+ config?: {
28
+ views?: WritableView[];
29
+ };
30
+ }) => Set<string>;
31
+ export declare const looksLikeSqlBreakout: (frag: unknown) => boolean;
32
+ type HiddenColumnsView = {
33
+ __hiddenColumns?: string[];
34
+ };
35
+ export declare const hiddenColumnsOf: (view: HiddenColumnsView | undefined) => string[];
36
+ export declare const stripHiddenColumns: <T extends Record<string, unknown>>(view: HiddenColumnsView | undefined, record: T) => T;
37
+ export declare const touchesHiddenColumn: (view: HiddenColumnsView | undefined, data: unknown) => boolean;
38
+ export {};
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.touchesHiddenColumn = exports.stripHiddenColumns = exports.hiddenColumnsOf = exports.looksLikeSqlBreakout = exports.writableTables = exports.areSafeColumnNames = exports.MUTATING_VIEW_ROUTES = exports.viewFromRequest = exports.canWriteView = exports.roleOf = exports.ROOT_ROLE = void 0;
4
+ exports.ROOT_ROLE = "root";
5
+ const roleOf = (req) => {
6
+ const user = req.session && Reflect.get(req.session, "user");
7
+ if (!user || typeof user !== "object")
8
+ return undefined;
9
+ const role = Reflect.get(user, "role");
10
+ return typeof role === "string" ? role : undefined;
11
+ };
12
+ exports.roleOf = roleOf;
13
+ const canWriteView = (view, role) => role === exports.ROOT_ROLE || !view || !view.roles || (role !== undefined && view.roles.includes(role));
14
+ exports.canWriteView = canWriteView;
15
+ const viewFromRequest = (req) => {
16
+ const idx = req.body && req.body.viewIndex !== undefined
17
+ ? req.body.viewIndex
18
+ : req.query && req.query.viewIndex !== undefined
19
+ ? req.query.viewIndex
20
+ : undefined;
21
+ if (idx === undefined || !req.config || !req.config.views)
22
+ return undefined;
23
+ const key = String(idx);
24
+ const index = Number(key);
25
+ if (!Number.isInteger(index) || index < 0 || index >= req.config.views.length || String(index) !== key) {
26
+ return undefined;
27
+ }
28
+ return req.config.views[index];
29
+ };
30
+ exports.viewFromRequest = viewFromRequest;
31
+ exports.MUTATING_VIEW_ROUTES = new Set([
32
+ "/updateRecord",
33
+ "/createRecord",
34
+ "/deleteRecord",
35
+ "/deleteRecords",
36
+ "/deleteRecordFromPanel",
37
+ "/customAction",
38
+ "/uploadFile",
39
+ ]);
40
+ const areSafeColumnNames = (data) => !data || Object.keys(data).every((k) => /^[A-Za-z0-9_]+$/.test(k));
41
+ exports.areSafeColumnNames = areSafeColumnNames;
42
+ const writableTables = (req) => {
43
+ var _a, _b;
44
+ const role = (0, exports.roleOf)(req);
45
+ const tables = new Set();
46
+ for (const view of ((_a = req.config) === null || _a === void 0 ? void 0 : _a.views) || []) {
47
+ if (!(0, exports.canWriteView)(view, role))
48
+ continue;
49
+ if (view.tableName)
50
+ tables.add(view.tableName);
51
+ for (const panel of view.recordViewPanels || []) {
52
+ if (panel.tableName)
53
+ tables.add(panel.tableName);
54
+ if ((_b = panel.createConfig) === null || _b === void 0 ? void 0 : _b.tableName)
55
+ tables.add(panel.createConfig.tableName);
56
+ }
57
+ }
58
+ return tables;
59
+ };
60
+ exports.writableTables = writableTables;
61
+ const looksLikeSqlBreakout = (frag) => typeof frag === "string" &&
62
+ /(;|--|\/\*|\bunion\b|\binto\s+(out|dump)file\b|\bsleep\s*\(|\bbenchmark\s*\(|\bload_file\s*\()/i.test(frag);
63
+ exports.looksLikeSqlBreakout = looksLikeSqlBreakout;
64
+ const hiddenColumnsOf = (view) => (view && view.__hiddenColumns) || [];
65
+ exports.hiddenColumnsOf = hiddenColumnsOf;
66
+ const stripHiddenColumns = (view, record) => {
67
+ for (const name of (0, exports.hiddenColumnsOf)(view))
68
+ delete record[name];
69
+ return record;
70
+ };
71
+ exports.stripHiddenColumns = stripHiddenColumns;
72
+ const touchesHiddenColumn = (view, data) => (0, exports.hiddenColumnsOf)(view).some((name) => data && Object.prototype.hasOwnProperty.call(data, name));
73
+ exports.touchesHiddenColumn = touchesHiddenColumn;
74
+ //# sourceMappingURL=authorization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorization.js","sourceRoot":"","sources":["../../src/api/authorization.ts"],"names":[],"mappings":";;;AAmBa,QAAA,SAAS,GAAG,MAAM,CAAC;AAKzB,MAAM,MAAM,GAAG,CAAC,GAAmB,EAAE,EAAE;IAC5C,MAAM,IAAI,GAAY,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACtE,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACxD,MAAM,IAAI,GAAY,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAChD,OAAO,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AACrD,CAAC,CAAC;AALW,QAAA,MAAM,UAKjB;AAEK,MAAM,YAAY,GAAG,CAAC,IAA6C,EAAE,IAAwB,EAAE,EAAE,CACtG,IAAI,KAAK,iBAAS,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;AADrF,QAAA,YAAY,gBACyE;AAE3F,MAAM,eAAe,GAAG,CAAmB,GAIjD,EAAiB,EAAE;IAClB,MAAM,GAAG,GACP,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,SAAS,KAAK,SAAS;QAC1C,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS;QACpB,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,SAAS,KAAK,SAAS;YAC9C,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS;YACrB,CAAC,CAAC,SAAS,CAAC;IAClB,IAAI,GAAG,KAAK,SAAS,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAM5E,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACxB,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC1B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC;QACvG,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AACjC,CAAC,CAAC;AAvBW,QAAA,eAAe,mBAuB1B;AAKW,QAAA,oBAAoB,GAAG,IAAI,GAAG,CAAC;IAC1C,eAAe;IACf,eAAe;IACf,eAAe;IACf,gBAAgB;IAChB,wBAAwB;IACxB,eAAe;IACf,aAAa;CACd,CAAC,CAAC;AAMI,MAAM,kBAAkB,GAAG,CAAC,IAAa,EAAE,EAAE,CAClD,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AADxD,QAAA,kBAAkB,sBACsC;AAI9D,MAAM,cAAc,GAAG,CAAC,GAA6D,EAAE,EAAE;;IAC9F,MAAM,IAAI,GAAG,IAAA,cAAM,EAAC,GAAG,CAAC,CAAC;IACzB,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,CAAA,MAAA,GAAG,CAAC,MAAM,0CAAE,KAAK,KAAI,EAAE,EAAE,CAAC;QAC3C,IAAI,CAAC,IAAA,oBAAY,EAAC,IAAI,EAAE,IAAI,CAAC;YAAE,SAAS;QACxC,IAAI,IAAI,CAAC,SAAS;YAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC/C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,gBAAgB,IAAI,EAAE,EAAE,CAAC;YAChD,IAAI,KAAK,CAAC,SAAS;gBAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YACjD,IAAI,MAAA,KAAK,CAAC,YAAY,0CAAE,SAAS;gBAC/B,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAbW,QAAA,cAAc,kBAazB;AAKK,MAAM,oBAAoB,GAAG,CAAC,IAAa,EAAE,EAAE,CACpD,OAAO,IAAI,KAAK,QAAQ;IACxB,iGAAiG,CAAC,IAAI,CACpG,IAAI,CACL,CAAC;AAJS,QAAA,oBAAoB,wBAI7B;AAKG,MAAM,eAAe,GAAG,CAC7B,IAAmC,EACnC,EAAE,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC;AAF7B,QAAA,eAAe,mBAEc;AAGnC,MAAM,kBAAkB,GAAG,CAChC,IAAmC,EACnC,MAAS,EACT,EAAE;IACF,KAAK,MAAM,IAAI,IAAI,IAAA,uBAAe,EAAC,IAAI,CAAC;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;IAC9D,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AANW,QAAA,kBAAkB,sBAM7B;AAGK,MAAM,mBAAmB,GAAG,CACjC,IAAmC,EACnC,IAAa,EACb,EAAE,CACF,IAAA,uBAAe,EAAC,IAAI,CAAC,CAAC,IAAI,CACxB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CACnE,CAAC;AANS,QAAA,mBAAmB,uBAM5B"}
@@ -0,0 +1,42 @@
1
+ import type { BaseTypeColumn, Config, FileUploadProperties, RichTextColumn, SelectColumn, StandardColumn, Subview, View } from "../types/config";
2
+ import type dbManager from "../utils/dbManager";
3
+ type Identified = {
4
+ __id__?: string;
5
+ };
6
+ type EnrichmentColumn = StandardColumn & Pick<BaseTypeColumn, "required"> & Pick<RichTextColumn, "contentField"> & Pick<FileUploadProperties, "s3Config" | "gcpConfig"> & Partial<Pick<SelectColumn, "options">> & Identified & {
7
+ baseUrl?: string;
8
+ };
9
+ type EnrichmentView = Identified & {
10
+ subviews?: (Subview & Identified)[];
11
+ } & ({
12
+ type: "table";
13
+ tableName: string;
14
+ primaryId?: string;
15
+ columns: EnrichmentColumn[];
16
+ recordViewPanels?: (Identified & {
17
+ type: string;
18
+ createConfig?: {
19
+ columns: EnrichmentColumn[];
20
+ };
21
+ })[];
22
+ } | {
23
+ type: Exclude<View["type"], "table">;
24
+ });
25
+ type EnrichmentConfig = Pick<Config, "mysql" | "postgresql" | "enriched"> & {
26
+ views: EnrichmentView[];
27
+ };
28
+ type SchemaColumn = {
29
+ TABLE_NAME?: string;
30
+ COLUMN_NAME?: string;
31
+ COLUMN_KEY?: string;
32
+ IS_NULLABLE?: string;
33
+ DATA_TYPE?: string;
34
+ COLUMN_TYPE?: string;
35
+ };
36
+ type SchemaDatabase = {
37
+ query(...args: Parameters<dbManager["query"]>): Promise<Partial<Pick<Awaited<ReturnType<dbManager["query"]>>, "err" | "rows">>>;
38
+ };
39
+ export declare function generateUniqueIdForObject(obj: unknown): string;
40
+ export declare function enrichConfig(config: EnrichmentConfig, db: SchemaDatabase): Promise<void>;
41
+ export declare function enrichColumn(column: EnrichmentColumn, tableName: string, info: SchemaColumn[]): void;
42
+ export {};
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateUniqueIdForObject = generateUniqueIdForObject;
4
+ exports.enrichConfig = enrichConfig;
5
+ exports.enrichColumn = enrichColumn;
6
+ const crypto = require("crypto");
7
+ function readSchemaColumns(value) {
8
+ if (!Array.isArray(value) || !value.every((row) => row !== null && typeof row === "object" && !Array.isArray(row)
9
+ && (!("TABLE_NAME" in row) || typeof row.TABLE_NAME === "string")
10
+ && (!("COLUMN_NAME" in row) || typeof row.COLUMN_NAME === "string")
11
+ && (!("COLUMN_KEY" in row) || typeof row.COLUMN_KEY === "string")
12
+ && (!("IS_NULLABLE" in row) || typeof row.IS_NULLABLE === "string")
13
+ && (!("DATA_TYPE" in row) || typeof row.DATA_TYPE === "string")
14
+ && (!("COLUMN_TYPE" in row) || typeof row.COLUMN_TYPE === "string")))
15
+ throw new TypeError("Invalid database schema columns");
16
+ return value;
17
+ }
18
+ function readEnumOptions(columnType) {
19
+ if (columnType === undefined)
20
+ throw new TypeError("Missing enum column type");
21
+ const options = JSON.parse(columnType.replace("enum(", "[").replace(")", "]").replace(/'/g, '"'));
22
+ if (!Array.isArray(options) || !options.every((option) => typeof option === "string")) {
23
+ throw new TypeError("Invalid enum options");
24
+ }
25
+ return options;
26
+ }
27
+ function generateUniqueIdForObject(obj) {
28
+ const str = JSON.stringify(obj);
29
+ const hash = crypto.createHash("sha256").update(str).digest("hex");
30
+ return hash;
31
+ }
32
+ async function enrichConfig(config, db) {
33
+ var _a, _b;
34
+ const { rows, err = null } = await db.query("SELECT * FROM information_schema.columns WHERE table_schema = ?", [((_a = config.mysql) === null || _a === void 0 ? void 0 : _a.database) || ((_b = config.postgresql) === null || _b === void 0 ? void 0 : _b.database)]);
35
+ if (err) {
36
+ console.error(err);
37
+ return;
38
+ }
39
+ const info = readSchemaColumns(rows);
40
+ for (const view of config.views) {
41
+ if (view.type == "table") {
42
+ const tableName = view.tableName;
43
+ for (const infoEl of info) {
44
+ if (infoEl.TABLE_NAME == tableName && infoEl.COLUMN_KEY == "PRI") {
45
+ view.primaryId = infoEl.COLUMN_NAME;
46
+ }
47
+ }
48
+ for (const column of view.columns) {
49
+ enrichColumn(column, tableName, info);
50
+ }
51
+ if (view.recordViewPanels) {
52
+ for (const panel of view.recordViewPanels) {
53
+ if (panel.createConfig) {
54
+ for (const column of panel.createConfig.columns) {
55
+ enrichColumn(column, tableName, info);
56
+ }
57
+ }
58
+ if (!panel.__id__) {
59
+ panel.__id__ = generateUniqueIdForObject(panel);
60
+ }
61
+ }
62
+ }
63
+ }
64
+ if (view.subviews) {
65
+ for (const subview of view.subviews) {
66
+ if (!subview.__id__) {
67
+ subview.__id__ = generateUniqueIdForObject(subview);
68
+ }
69
+ }
70
+ }
71
+ if (!view.__id__) {
72
+ view.__id__ = generateUniqueIdForObject(view);
73
+ }
74
+ }
75
+ config.enriched = true;
76
+ }
77
+ function enrichColumn(column, tableName, info) {
78
+ var _a, _b, _c, _d;
79
+ if (!column.__id__) {
80
+ column.__id__ = generateUniqueIdForObject(column);
81
+ }
82
+ const infoEl = info.find((x) => x.TABLE_NAME == tableName && x.COLUMN_NAME == column.name);
83
+ if ((infoEl === null || infoEl === void 0 ? void 0 : infoEl.IS_NULLABLE) == "NO" &&
84
+ (infoEl === null || infoEl === void 0 ? void 0 : infoEl.DATA_TYPE) != "text" &&
85
+ column.required !== true &&
86
+ column.required !== false) {
87
+ column.required = true;
88
+ }
89
+ if (!column.dataType && infoEl) {
90
+ column.dataType = infoEl.DATA_TYPE;
91
+ if (column.dataType == "enum") {
92
+ column.dataType = "select";
93
+ column.options = readEnumOptions(infoEl.COLUMN_TYPE);
94
+ }
95
+ }
96
+ if (column.dataType == "richText") {
97
+ column.name = column.contentField;
98
+ }
99
+ if (column.dataType == "file" && (column.s3Config || column.gcpConfig)) {
100
+ const host = ((_a = column.s3Config) === null || _a === void 0 ? void 0 : _a.host) || ((_b = column.gcpConfig) === null || _b === void 0 ? void 0 : _b.host);
101
+ const bucket = ((_c = column.s3Config) === null || _c === void 0 ? void 0 : _c.bucket) || ((_d = column.gcpConfig) === null || _d === void 0 ? void 0 : _d.bucket);
102
+ column.baseUrl = `https://${host}/${bucket}/`;
103
+ }
104
+ }
105
+ //# sourceMappingURL=config.js.map