@cyberismo/data-handler 0.0.21 → 0.0.22

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 (166) hide show
  1. package/dist/command-handler.js +13 -24
  2. package/dist/command-handler.js.map +1 -1
  3. package/dist/command-manager.d.ts +21 -6
  4. package/dist/command-manager.js +34 -32
  5. package/dist/command-manager.js.map +1 -1
  6. package/dist/commands/calculate.js +101 -46
  7. package/dist/commands/calculate.js.map +1 -1
  8. package/dist/commands/create.js +417 -328
  9. package/dist/commands/create.js.map +1 -1
  10. package/dist/commands/edit.js +117 -68
  11. package/dist/commands/edit.js.map +1 -1
  12. package/dist/commands/export.js +301 -252
  13. package/dist/commands/export.js.map +1 -1
  14. package/dist/commands/fetch.js +205 -156
  15. package/dist/commands/fetch.js.map +1 -1
  16. package/dist/commands/import.js +189 -134
  17. package/dist/commands/import.js.map +1 -1
  18. package/dist/commands/migrate.js +91 -45
  19. package/dist/commands/migrate.js.map +1 -1
  20. package/dist/commands/move.js +347 -267
  21. package/dist/commands/move.js.map +1 -1
  22. package/dist/commands/remove.d.ts +1 -0
  23. package/dist/commands/remove.js +202 -135
  24. package/dist/commands/remove.js.map +1 -1
  25. package/dist/commands/rename.js +233 -187
  26. package/dist/commands/rename.js.map +1 -1
  27. package/dist/commands/show.d.ts +8 -8
  28. package/dist/commands/show.js +477 -372
  29. package/dist/commands/show.js.map +1 -1
  30. package/dist/commands/transition.js +119 -73
  31. package/dist/commands/transition.js.map +1 -1
  32. package/dist/commands/update.js +8 -3
  33. package/dist/commands/update.js.map +1 -1
  34. package/dist/commands/validate.js +1 -1
  35. package/dist/commands/validate.js.map +1 -1
  36. package/dist/containers/project/calculation-engine.js +0 -1
  37. package/dist/containers/project/calculation-engine.js.map +1 -1
  38. package/dist/containers/project/card-cache.js +1 -1
  39. package/dist/containers/project/card-cache.js.map +1 -1
  40. package/dist/containers/project.d.ts +16 -0
  41. package/dist/containers/project.js +59 -1
  42. package/dist/containers/project.js.map +1 -1
  43. package/dist/containers/template.js +1 -1
  44. package/dist/containers/template.js.map +1 -1
  45. package/dist/interfaces/command-options.d.ts +1 -0
  46. package/dist/interfaces/resource-interfaces.d.ts +5 -12
  47. package/dist/interfaces/resource-interfaces.js.map +1 -1
  48. package/dist/macros/base-macro.js +1 -1
  49. package/dist/macros/base-macro.js.map +1 -1
  50. package/dist/macros/graph/index.js +3 -1
  51. package/dist/macros/graph/index.js.map +1 -1
  52. package/dist/macros/index.js +3 -1
  53. package/dist/macros/index.js.map +1 -1
  54. package/dist/macros/report/index.js +1 -1
  55. package/dist/macros/report/index.js.map +1 -1
  56. package/dist/module-manager.js +5 -3
  57. package/dist/module-manager.js.map +1 -1
  58. package/dist/project-settings.js +2 -2
  59. package/dist/project-settings.js.map +1 -1
  60. package/dist/resources/card-type-resource.js +1 -1
  61. package/dist/resources/card-type-resource.js.map +1 -1
  62. package/dist/resources/create-defaults.js +0 -1
  63. package/dist/resources/create-defaults.js.map +1 -1
  64. package/dist/resources/field-type-resource.js +2 -5
  65. package/dist/resources/field-type-resource.js.map +1 -1
  66. package/dist/resources/file-resource.js +4 -1
  67. package/dist/resources/file-resource.js.map +1 -1
  68. package/dist/resources/folder-resource.d.ts +1 -1
  69. package/dist/resources/folder-resource.js +4 -1
  70. package/dist/resources/folder-resource.js.map +1 -1
  71. package/dist/resources/graph-model-resource.d.ts +1 -8
  72. package/dist/resources/graph-model-resource.js +0 -14
  73. package/dist/resources/graph-model-resource.js.map +1 -1
  74. package/dist/resources/graph-view-resource.d.ts +1 -8
  75. package/dist/resources/graph-view-resource.js +0 -14
  76. package/dist/resources/graph-view-resource.js.map +1 -1
  77. package/dist/resources/link-type-resource.js +1 -1
  78. package/dist/resources/link-type-resource.js.map +1 -1
  79. package/dist/resources/report-resource.d.ts +1 -8
  80. package/dist/resources/report-resource.js +0 -14
  81. package/dist/resources/report-resource.js.map +1 -1
  82. package/dist/resources/resource-object.d.ts +11 -1
  83. package/dist/resources/resource-object.js +19 -2
  84. package/dist/resources/resource-object.js.map +1 -1
  85. package/dist/resources/template-resource.d.ts +1 -9
  86. package/dist/resources/template-resource.js +0 -15
  87. package/dist/resources/template-resource.js.map +1 -1
  88. package/dist/resources/workflow-resource.js +1 -1
  89. package/dist/resources/workflow-resource.js.map +1 -1
  90. package/dist/utils/card-utils.js +1 -1
  91. package/dist/utils/card-utils.js.map +1 -1
  92. package/dist/utils/commit-context.d.ts +23 -0
  93. package/dist/utils/commit-context.js +30 -0
  94. package/dist/utils/commit-context.js.map +1 -0
  95. package/dist/utils/file-utils.js +3 -1
  96. package/dist/utils/file-utils.js.map +1 -1
  97. package/dist/utils/git-manager.d.ts +29 -0
  98. package/dist/utils/git-manager.js +76 -0
  99. package/dist/utils/git-manager.js.map +1 -0
  100. package/dist/utils/handlebars-helpers.d.ts +22 -0
  101. package/dist/utils/handlebars-helpers.js +78 -0
  102. package/dist/utils/handlebars-helpers.js.map +1 -0
  103. package/dist/utils/json.js +6 -2
  104. package/dist/utils/json.js.map +1 -1
  105. package/dist/utils/log-utils.d.ts +7 -2
  106. package/dist/utils/log-utils.js +28 -3
  107. package/dist/utils/log-utils.js.map +1 -1
  108. package/dist/utils/report.d.ts +0 -19
  109. package/dist/utils/report.js +4 -67
  110. package/dist/utils/report.js.map +1 -1
  111. package/dist/utils/rw-lock.d.ts +71 -0
  112. package/dist/utils/rw-lock.js +220 -0
  113. package/dist/utils/rw-lock.js.map +1 -0
  114. package/dist/utils/user-preferences.js +3 -3
  115. package/dist/utils/user-preferences.js.map +1 -1
  116. package/package.json +5 -5
  117. package/src/command-handler.ts +14 -22
  118. package/src/command-manager.ts +43 -37
  119. package/src/commands/calculate.ts +8 -1
  120. package/src/commands/create.ts +24 -1
  121. package/src/commands/edit.ts +3 -0
  122. package/src/commands/export.ts +8 -2
  123. package/src/commands/fetch.ts +3 -0
  124. package/src/commands/import.ts +5 -0
  125. package/src/commands/migrate.ts +2 -0
  126. package/src/commands/move.ts +34 -0
  127. package/src/commands/remove.ts +24 -2
  128. package/src/commands/rename.ts +2 -0
  129. package/src/commands/show.ts +63 -34
  130. package/src/commands/transition.ts +2 -0
  131. package/src/commands/update.ts +9 -3
  132. package/src/commands/validate.ts +1 -1
  133. package/src/containers/project/calculation-engine.ts +0 -1
  134. package/src/containers/project/card-cache.ts +1 -0
  135. package/src/containers/project.ts +75 -1
  136. package/src/containers/template.ts +1 -1
  137. package/src/interfaces/command-options.ts +1 -0
  138. package/src/interfaces/resource-interfaces.ts +5 -12
  139. package/src/macros/base-macro.ts +1 -1
  140. package/src/macros/graph/index.ts +3 -0
  141. package/src/macros/index.ts +3 -1
  142. package/src/macros/report/index.ts +1 -0
  143. package/src/module-manager.ts +5 -2
  144. package/src/project-settings.ts +2 -1
  145. package/src/resources/card-type-resource.ts +1 -1
  146. package/src/resources/create-defaults.ts +0 -1
  147. package/src/resources/field-type-resource.ts +2 -4
  148. package/src/resources/file-resource.ts +3 -1
  149. package/src/resources/folder-resource.ts +7 -2
  150. package/src/resources/graph-model-resource.ts +1 -25
  151. package/src/resources/graph-view-resource.ts +1 -25
  152. package/src/resources/link-type-resource.ts +1 -1
  153. package/src/resources/report-resource.ts +1 -25
  154. package/src/resources/resource-object.ts +22 -1
  155. package/src/resources/template-resource.ts +0 -23
  156. package/src/resources/workflow-resource.ts +1 -1
  157. package/src/utils/card-utils.ts +1 -1
  158. package/src/utils/commit-context.ts +45 -0
  159. package/src/utils/file-utils.ts +3 -1
  160. package/src/utils/git-manager.ts +87 -0
  161. package/src/utils/handlebars-helpers.ts +95 -0
  162. package/src/utils/json.ts +6 -2
  163. package/src/utils/log-utils.ts +33 -4
  164. package/src/utils/report.ts +8 -78
  165. package/src/utils/rw-lock.ts +279 -0
  166. package/src/utils/user-preferences.ts +3 -0
@@ -0,0 +1,78 @@
1
+ import { escapeJsonString } from './json.js';
2
+ import { escapeCsvField } from './csv.js';
3
+ import { resourceName } from './resource-utils.js';
4
+ /**
5
+ * Formats a value from a logic program for use in graphviz
6
+ * @param value - The value to format
7
+ * @returns The formatted value
8
+ */
9
+ function formatAttributeValue(value) {
10
+ if (!value) {
11
+ return '';
12
+ }
13
+ // value is an html-like string
14
+ if (value.length > 1 && value.startsWith('<') && value.endsWith('>')) {
15
+ return value;
16
+ }
17
+ // value is a normal string and needs to be wrapped in quotes
18
+ return `"${value}"`;
19
+ }
20
+ /**
21
+ * Checks if a field is a custom field
22
+ * @param field - The field to check
23
+ * @returns True if the field is a custom field, false otherwise
24
+ */
25
+ function isCustomField(field) {
26
+ try {
27
+ const { type } = resourceName(field, true);
28
+ return type === 'fieldTypes';
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
34
+ /**
35
+ * Formats a value for display in a report
36
+ * @param value - The value to format
37
+ * @returns The formatted value
38
+ */
39
+ function formatValue(value) {
40
+ if (typeof value === 'object') {
41
+ if (Array.isArray(value)) {
42
+ return value.map((v) => formatValue(v)).join(', ');
43
+ }
44
+ if (value != null &&
45
+ 'displayValue' in value &&
46
+ typeof value.displayValue === 'string') {
47
+ return value.displayValue;
48
+ }
49
+ if (value != null && 'value' in value && typeof value.value === 'string') {
50
+ return formatValue(value.value);
51
+ }
52
+ return JSON.stringify(value, null, 2);
53
+ }
54
+ if (typeof value === 'boolean') {
55
+ return value ? 'Yes' : 'No';
56
+ }
57
+ return value?.toString() ?? '';
58
+ }
59
+ /**
60
+ * Registers comparison helpers (eq and ne) with a Handlebars instance
61
+ * @param instance handlebars instance
62
+ */
63
+ export function registerComparisonHelpers(instance) {
64
+ instance.registerHelper('eq', (a, b) => a === b);
65
+ instance.registerHelper('ne', (a, b) => a !== b);
66
+ }
67
+ /**
68
+ * Registers report-specific helpers with a Handlebars instance
69
+ * @param instance handlebars instance
70
+ */
71
+ export function registerReportHelpers(instance) {
72
+ instance.registerHelper('formatAttributeValue', formatAttributeValue);
73
+ instance.registerHelper('isCustomField', isCustomField);
74
+ instance.registerHelper('formatValue', formatValue);
75
+ instance.registerHelper('jsonEscape', escapeJsonString);
76
+ instance.registerHelper('csvEscape', escapeCsvField);
77
+ }
78
+ //# sourceMappingURL=handlebars-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlebars-helpers.js","sourceRoot":"","sources":["../../src/utils/handlebars-helpers.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD;;;;GAIG;AACH,SAAS,oBAAoB,CAAC,KAAc;IAC1C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,+BAA+B;IAC/B,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACrE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,6DAA6D;IAC7D,OAAO,IAAI,KAAK,GAAG,CAAC;AACtB,CAAC;AAED;;;;GAIG;AACH,SAAS,aAAa,CAAC,KAAa;IAClC,IAAI,CAAC;QACH,MAAM,EAAE,IAAI,EAAE,GAAG,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC3C,OAAO,IAAI,KAAK,YAAY,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,WAAW,CAAC,KAAc;IACjC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrD,CAAC;QACD,IACE,KAAK,IAAI,IAAI;YACb,cAAc,IAAI,KAAK;YACvB,OAAO,KAAK,CAAC,YAAY,KAAK,QAAQ,EACtC,CAAC;YACD,OAAO,KAAK,CAAC,YAAY,CAAC;QAC5B,CAAC;QACD,IAAI,KAAK,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YACzE,OAAO,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAClC,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACxC,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9B,CAAC;IACD,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AACjC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,QAA2B;IACnE,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAU,EAAE,CAAU,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IACnE,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAU,EAAE,CAAU,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;AACrE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAA2B;IAC/D,QAAQ,CAAC,cAAc,CAAC,sBAAsB,EAAE,oBAAoB,CAAC,CAAC;IACtE,QAAQ,CAAC,cAAc,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;IACxD,QAAQ,CAAC,cAAc,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;IACpD,QAAQ,CAAC,cAAc,CAAC,YAAY,EAAE,gBAAgB,CAAC,CAAC;IACxD,QAAQ,CAAC,cAAc,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;AACvD,CAAC"}
@@ -48,7 +48,9 @@ export function readJsonFileSync(file) {
48
48
  }
49
49
  catch (error) {
50
50
  if (error instanceof Error) {
51
- throw new Error(`Invalid JSON in file '${file}': ${error.message}`);
51
+ throw new Error(`Invalid JSON in file '${file}': ${error.message}`, {
52
+ cause: error,
53
+ });
52
54
  }
53
55
  }
54
56
  }
@@ -65,7 +67,9 @@ export async function readJsonFile(file) {
65
67
  }
66
68
  catch (error) {
67
69
  if (error instanceof Error) {
68
- throw new Error(`Invalid JSON in file '${file}': ${error.message}`);
70
+ throw new Error(`Invalid JSON in file '${file}': ${error.message}`, {
71
+ cause: error,
72
+ });
69
73
  }
70
74
  }
71
75
  }
@@ -1 +1 @@
1
- {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/utils/json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;EAYE;AAEF,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAmB,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAExE;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;QAClE,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACpC,OAAO,WAAW,CAAC;IACrB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,yBAAyB,IAAI,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAY;IAC7C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;QACxD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,yBAAyB,IAAI,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;QACtD,OAAO,GAAG,CAAC;IACb,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,aAAa,CAAC,CAAC;IACnD,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,CAAS,EAAE,KAAc;IACpD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;IACtB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAA6B,EAC7B,IAAY,EACZ,OAAgB;IAEhB,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;QACrD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,4DAA4D;IAC9D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/utils/json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;EAYE;AAEF,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAmB,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAExE;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;QAClE,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACpC,OAAO,WAAW,CAAC;IACrB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,yBAAyB,IAAI,MAAM,KAAK,CAAC,OAAO,EAAE,EAAE;gBAClE,KAAK,EAAE,KAAK;aACb,CAAC,CAAC;QACL,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAY;IAC7C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;QACxD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,yBAAyB,IAAI,MAAM,KAAK,CAAC,OAAO,EAAE,EAAE;gBAClE,KAAK,EAAE,KAAK;aACb,CAAC,CAAC;QACL,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;QACtD,OAAO,GAAG,CAAC;IACb,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,aAAa,CAAC,CAAC;IACnD,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,CAAS,EAAE,KAAc;IACpD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;IACtB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAA6B,EAC7B,IAAY,EACZ,OAAgB;IAEhB,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;QACrD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,4DAA4D;IAC9D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -9,8 +9,13 @@
9
9
  You should have received a copy of the GNU Affero General Public
10
10
  License along with this program. If not, see <https://www.gnu.org/licenses/>.
11
11
  */
12
- import { type ChildLoggerOptions, type Logger } from 'pino';
13
- export declare function setLogger(logger: Logger): void;
12
+ import { type Level, type ChildLoggerOptions, type Logger } from 'pino';
13
+ /**
14
+ * Initialize the logger
15
+ * @param level Log level for stdout output.
16
+ * @param logPath Optional file path for full trace logging.
17
+ */
18
+ export declare function initLogger(level: Level, logPath?: string): void;
14
19
  /**
15
20
  * Returns the logger instance.
16
21
  */
@@ -10,11 +10,36 @@
10
10
  License along with this program. If not, see <https://www.gnu.org/licenses/>.
11
11
  */
12
12
  import pino from 'pino';
13
+ import { mkdirSync } from 'node:fs';
14
+ import { dirname } from 'node:path';
13
15
  // This could be also a more generic interface, but since we use pino and this is an internal package, let's keep it simple
14
- // silent logger as default
16
+ // Silent logger as default.
15
17
  let _logger = pino({ level: 'silent' });
16
- export function setLogger(logger) {
17
- _logger = logger;
18
+ let initialized = false;
19
+ /**
20
+ * Initialize the logger
21
+ * @param level Log level for stdout output.
22
+ * @param logPath Optional file path for full trace logging.
23
+ */
24
+ export function initLogger(level, logPath) {
25
+ if (initialized)
26
+ return;
27
+ initialized = true;
28
+ if (logPath) {
29
+ try {
30
+ mkdirSync(dirname(logPath), { recursive: true });
31
+ }
32
+ catch (error) {
33
+ throw new Error(`Failed to create log directory '${dirname(logPath)}': ${error instanceof Error ? error.message : String(error)}`, { cause: error });
34
+ }
35
+ _logger = pino({ level: 'trace' }, pino.multistream([
36
+ { stream: pino.destination(logPath), level: 'trace' },
37
+ { stream: pino.destination(1), level },
38
+ ]));
39
+ }
40
+ else {
41
+ _logger = pino({ level }, pino.destination(1));
42
+ }
18
43
  }
19
44
  /**
20
45
  * Returns the logger instance.
@@ -1 +1 @@
1
- {"version":3,"file":"log-utils.js","sourceRoot":"","sources":["../../src/utils/log-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;EAUE;AACF,OAAO,IAA8C,MAAM,MAAM,CAAC;AAElE,2HAA2H;AAC3H,2BAA2B;AAC3B,IAAI,OAAO,GAAW,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;AAEhD,MAAM,UAAU,SAAS,CAAC,MAAc;IACtC,OAAO,GAAG,MAAM,CAAC;AACnB,CAAC;AACD;;GAEG;AACH,MAAM,UAAU,SAAS;IACvB,OAAO,OAAO,CAAC;AACjB,CAAC;AACD;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAC5B,OAAqD,EACrD,OAA4B;IAE5B,OAAO,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AACzC,CAAC"}
1
+ {"version":3,"file":"log-utils.js","sourceRoot":"","sources":["../../src/utils/log-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;EAUE;AACF,OAAO,IAA0D,MAAM,MAAM,CAAC;AAC9E,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,2HAA2H;AAC3H,4BAA4B;AAC5B,IAAI,OAAO,GAAW,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;AAChD,IAAI,WAAW,GAAG,KAAK,CAAC;AAExB;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,KAAY,EAAE,OAAgB;IACvD,IAAI,WAAW;QAAE,OAAO;IACxB,WAAW,GAAG,IAAI,CAAC;IACnB,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,CAAC;YACH,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACb,mCAAmC,OAAO,CAAC,OAAO,CAAC,MAAM,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EACjH,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,IAAI,CACZ,EAAE,KAAK,EAAE,OAAO,EAAE,EAClB,IAAI,CAAC,WAAW,CAAC;YACf,EAAE,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE;YACrD,EAAE,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE;SACvC,CAAC,CACH,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS;IACvB,OAAO,OAAO,CAAC;AACjB,CAAC;AACD;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAC5B,OAAqD,EACrD,OAA4B;IAE5B,OAAO,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AACzC,CAAC"}
@@ -1,23 +1,5 @@
1
1
  import type { CalculationEngine } from '../containers/project/calculation-engine.js';
2
2
  import type { Context } from '../interfaces/project-interfaces.js';
3
- /**
4
- * Formats a value from a logic program for use to graphviz
5
- * @param value - The value to format
6
- * @returns The formatted value
7
- */
8
- export declare function formatAttributeValue(value?: string): string;
9
- /**
10
- * Checks if a field is a custom field
11
- * @param field - The field to check
12
- * @returns True if the field is a custom field, false otherwise
13
- */
14
- export declare function isCustomField(field: string): boolean;
15
- /**
16
- * Formats a value for display in a report
17
- * @param value - The value to format
18
- * @returns The formatted value
19
- */
20
- export declare function formatValue(value: unknown): string;
21
3
  /**
22
4
  * Parameters for the core generation function
23
5
  */
@@ -26,7 +8,6 @@ interface GenerateReportContentParams {
26
8
  contentTemplate: string;
27
9
  queryTemplate: string;
28
10
  options: Record<string, string | undefined | boolean>;
29
- graph?: boolean;
30
11
  context: Context;
31
12
  }
32
13
  /**
@@ -11,64 +11,7 @@
11
11
  */
12
12
  import Handlebars from 'handlebars';
13
13
  import { registerEmptyMacros } from '../macros/index.js';
14
- import { resourceName } from './resource-utils.js';
15
- import { escapeJsonString } from './json.js';
16
- import { escapeCsvField } from './csv.js';
17
- /**
18
- * Formats a value from a logic program for use to graphviz
19
- * @param value - The value to format
20
- * @returns The formatted value
21
- */
22
- export function formatAttributeValue(value) {
23
- if (!value) {
24
- return '';
25
- }
26
- // value is an html-like string
27
- if (value.length > 1 && value.startsWith('<') && value.endsWith('>')) {
28
- return value;
29
- }
30
- // value is a normal string and needs to be wrapped in quotes
31
- return `"${value}"`;
32
- }
33
- /**
34
- * Checks if a field is a custom field
35
- * @param field - The field to check
36
- * @returns True if the field is a custom field, false otherwise
37
- */
38
- export function isCustomField(field) {
39
- try {
40
- const { type } = resourceName(field, true);
41
- return type === 'fieldTypes';
42
- }
43
- catch {
44
- return false;
45
- }
46
- }
47
- /**
48
- * Formats a value for display in a report
49
- * @param value - The value to format
50
- * @returns The formatted value
51
- */
52
- export function formatValue(value) {
53
- if (typeof value === 'object') {
54
- if (Array.isArray(value)) {
55
- return value.map((v) => formatValue(v)).join(', ');
56
- }
57
- if (value != null &&
58
- 'displayValue' in value &&
59
- typeof value.displayValue === 'string') {
60
- return value.displayValue;
61
- }
62
- if (value != null && 'value' in value && typeof value.value === 'string') {
63
- return formatValue(value.value);
64
- }
65
- return JSON.stringify(value, null, 2);
66
- }
67
- if (typeof value === 'boolean') {
68
- return value ? 'Yes' : 'No';
69
- }
70
- return value?.toString() ?? '';
71
- }
14
+ import { registerComparisonHelpers, registerReportHelpers, } from './handlebars-helpers.js';
72
15
  /**
73
16
  * Generates report content based on a report definition, project context, and options,
74
17
  * by utilizing the ReportRunner class.
@@ -78,9 +21,9 @@ export function formatValue(value) {
78
21
  * @throws Error if the report definition is invalid, schema validation fails, or logic program execution fails.
79
22
  */
80
23
  export async function generateReportContent(params) {
81
- const { calculate, contentTemplate, queryTemplate, graph, options, // Destructure options
82
- context, } = params;
24
+ const { calculate, contentTemplate, queryTemplate, options, context } = params;
83
25
  const handlebars = Handlebars.create();
26
+ registerComparisonHelpers(handlebars);
84
27
  // Compile and execute the query template
85
28
  const template = handlebars.compile(queryTemplate, {
86
29
  strict: true,
@@ -91,13 +34,7 @@ export async function generateReportContent(params) {
91
34
  }
92
35
  // register empty macros so that other macros aren't touched yet
93
36
  registerEmptyMacros(handlebars);
94
- if (graph) {
95
- handlebars.registerHelper('formatAttributeValue', formatAttributeValue);
96
- }
97
- handlebars.registerHelper('isCustomField', isCustomField);
98
- handlebars.registerHelper('formatValue', formatValue);
99
- handlebars.registerHelper('jsonEscape', escapeJsonString);
100
- handlebars.registerHelper('csvEscape', escapeCsvField);
37
+ registerReportHelpers(handlebars);
101
38
  return handlebars.compile(contentTemplate)({
102
39
  ...options,
103
40
  ...result,
@@ -1 +1 @@
1
- {"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/utils/report.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/utils/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;EAUE;AACF,OAAO,UAAU,MAAM,YAAY,CAAC;AAEpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,OAAO,EACL,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,yBAAyB,CAAC;AAajC;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,MAAmC;IAEnC,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,GACnE,MAAM,CAAC;IAET,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC;IACvC,yBAAyB,CAAC,UAAU,CAAC,CAAC;IAEtC,yCAAyC;IACzC,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,aAAa,EAAE;QACjD,MAAM,EAAE,IAAI;KACb,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,eAAe,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;IAE3E,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IACD,gEAAgE;IAChE,mBAAmB,CAAC,UAAU,CAAC,CAAC;IAChC,qBAAqB,CAAC,UAAU,CAAC,CAAC;IAElC,OAAO,UAAU,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QACzC,GAAG,OAAO;QACV,GAAG,MAAM;KACV,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ Cyberismo
3
+ Copyright © Cyberismo Ltd and contributors 2026
4
+ This program is free software: you can redistribute it and/or modify it under
5
+ the terms of the GNU Affero General Public License version 3 as published by
6
+ the Free Software Foundation. This program is distributed in the hope that it
7
+ will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
8
+ of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
9
+ See the GNU Affero General Public License for more details.
10
+ You should have received a copy of the GNU Affero General Public
11
+ License along with this program. If not, see <https://www.gnu.org/licenses/>.
12
+ */
13
+ /**
14
+ * Promise-based read-write lock with writer priority and reentrancy.
15
+ *
16
+ * - Multiple concurrent readers are allowed.
17
+ * - Writers get exclusive access.
18
+ * - Writer-priority: new readers block when a writer is waiting.
19
+ * - Reentrancy via AsyncLocalStorage: nested lock calls within the same
20
+ * async context are no-ops.
21
+ * - After-write hooks fire after the outermost write completes successfully.
22
+ */
23
+ export declare class RWLock {
24
+ private readonly name;
25
+ private readers;
26
+ private writer;
27
+ private readerQueue;
28
+ private writerQueue;
29
+ private context;
30
+ private afterWriteHooks;
31
+ private writeErrorHooks;
32
+ private readonly logger;
33
+ constructor(name?: string);
34
+ /**
35
+ * Register a callback that fires after the outermost write completes
36
+ * successfully. Hooks run while still holding the write lock.
37
+ */
38
+ onAfterWrite(hook: () => Promise<void>): void;
39
+ /**
40
+ * Register a callback that fires when the outermost write fails.
41
+ * Hooks run while still holding the write lock.
42
+ */
43
+ onWriteError(hook: (error: unknown) => Promise<void>): void;
44
+ /**
45
+ * Execute `fn` under a read lock. Concurrent readers are allowed.
46
+ * If already inside a read or write context, just runs fn directly.
47
+ */
48
+ read<T>(fn: () => Promise<T>): Promise<T>;
49
+ /**
50
+ * Execute `fn` under an exclusive write lock.
51
+ * If already inside a write context, just runs fn directly (no hooks).
52
+ */
53
+ write<T>(fn: () => Promise<T>): Promise<T>;
54
+ private acquireRead;
55
+ private releaseRead;
56
+ private acquireWrite;
57
+ private releaseWrite;
58
+ }
59
+ /**
60
+ * A Helper decorator built for commands that automatically handles using a read lock
61
+ */
62
+ export declare function read<This extends object, Args extends unknown[], Return>(target: (this: This, ...args: Args) => Promise<Return>): (this: This, ...args: Args) => Promise<Return>;
63
+ /**
64
+ * A Helper decorator built for commands that automatically handles using a write lock.
65
+ *
66
+ * Two forms:
67
+ * - `@write()`: just acquires the write lock, no commit message (for wrapper methods)
68
+ * - `@write((param) => \`Do ${param}\`)`: acquires the write lock and sets a default commit message
69
+ */
70
+ export declare function write<This extends object, Args extends unknown[], Return>(): (target: (this: This, ...args: Args) => Promise<Return>) => (this: This, ...args: Args) => Promise<Return>;
71
+ export declare function write<This extends object, Args extends unknown[], Return>(message: (...args: any[]) => string): (target: (this: This, ...args: Args) => Promise<Return>) => (this: This, ...args: Args) => Promise<Return>;
@@ -0,0 +1,220 @@
1
+ /**
2
+ Cyberismo
3
+ Copyright © Cyberismo Ltd and contributors 2026
4
+ This program is free software: you can redistribute it and/or modify it under
5
+ the terms of the GNU Affero General Public License version 3 as published by
6
+ the Free Software Foundation. This program is distributed in the hope that it
7
+ will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
8
+ of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
9
+ See the GNU Affero General Public License for more details.
10
+ You should have received a copy of the GNU Affero General Public
11
+ License along with this program. If not, see <https://www.gnu.org/licenses/>.
12
+ */
13
+ import { AsyncLocalStorage } from 'node:async_hooks';
14
+ import { runWithDefaultCommitMessage } from './commit-context.js';
15
+ import { getChildLogger } from './log-utils.js';
16
+ /**
17
+ * Promise-based read-write lock with writer priority and reentrancy.
18
+ *
19
+ * - Multiple concurrent readers are allowed.
20
+ * - Writers get exclusive access.
21
+ * - Writer-priority: new readers block when a writer is waiting.
22
+ * - Reentrancy via AsyncLocalStorage: nested lock calls within the same
23
+ * async context are no-ops.
24
+ * - After-write hooks fire after the outermost write completes successfully.
25
+ */
26
+ export class RWLock {
27
+ name;
28
+ readers = 0;
29
+ writer = false;
30
+ readerQueue = [];
31
+ writerQueue = [];
32
+ context = new AsyncLocalStorage();
33
+ afterWriteHooks = [];
34
+ writeErrorHooks = [];
35
+ logger;
36
+ constructor(name = 'RWLock') {
37
+ this.name = name;
38
+ this.logger = getChildLogger({ module: 'RWLock', name });
39
+ }
40
+ /**
41
+ * Register a callback that fires after the outermost write completes
42
+ * successfully. Hooks run while still holding the write lock.
43
+ */
44
+ onAfterWrite(hook) {
45
+ this.afterWriteHooks.push(hook);
46
+ }
47
+ /**
48
+ * Register a callback that fires when the outermost write fails.
49
+ * Hooks run while still holding the write lock.
50
+ */
51
+ onWriteError(hook) {
52
+ this.writeErrorHooks.push(hook);
53
+ }
54
+ /**
55
+ * Execute `fn` under a read lock. Concurrent readers are allowed.
56
+ * If already inside a read or write context, just runs fn directly.
57
+ */
58
+ async read(fn) {
59
+ const current = this.context.getStore();
60
+ if (current?.active) {
61
+ return fn();
62
+ }
63
+ await this.acquireRead();
64
+ const ctx = { mode: 'read', active: true };
65
+ try {
66
+ return await this.context.run(ctx, fn);
67
+ }
68
+ finally {
69
+ ctx.active = false;
70
+ this.releaseRead();
71
+ }
72
+ }
73
+ /**
74
+ * Execute `fn` under an exclusive write lock.
75
+ * If already inside a write context, just runs fn directly (no hooks).
76
+ */
77
+ async write(fn) {
78
+ const current = this.context.getStore();
79
+ if (current?.active && current.mode === 'write') {
80
+ return fn();
81
+ }
82
+ if (current?.active && current.mode === 'read') {
83
+ throw new Error('Cannot acquire write lock while holding read lock');
84
+ }
85
+ await this.acquireWrite();
86
+ const ctx = { mode: 'write', active: true };
87
+ try {
88
+ const result = await this.context.run(ctx, fn);
89
+ // Fire after-write hooks while still holding the lock
90
+ for (const hook of this.afterWriteHooks) {
91
+ await this.context.run(ctx, hook);
92
+ }
93
+ return result;
94
+ }
95
+ catch (error) {
96
+ // Run rollback hooks on error (outermost write only)
97
+ for (const hook of this.writeErrorHooks) {
98
+ await this.context.run(ctx, () => hook(error));
99
+ }
100
+ throw error;
101
+ }
102
+ finally {
103
+ ctx.active = false;
104
+ this.releaseWrite();
105
+ }
106
+ }
107
+ acquireRead() {
108
+ if (!this.writer && this.writerQueue.length === 0) {
109
+ this.readers++;
110
+ this.logger.trace({ readers: this.readers }, 'read lock acquired');
111
+ return Promise.resolve();
112
+ }
113
+ this.logger.debug({
114
+ readers: this.readers,
115
+ writer: this.writer,
116
+ writerQueueDepth: this.writerQueue.length,
117
+ readerQueueDepth: this.readerQueue.length,
118
+ }, 'read lock queued (writer active or pending)');
119
+ return new Promise((resolve) => {
120
+ this.readerQueue.push(() => {
121
+ this.readers++;
122
+ this.logger.debug({
123
+ readers: this.readers,
124
+ writerQueueDepth: this.writerQueue.length,
125
+ readerQueueDepth: this.readerQueue.length,
126
+ }, 'read lock acquired after wait');
127
+ resolve();
128
+ });
129
+ });
130
+ }
131
+ releaseRead() {
132
+ this.readers--;
133
+ this.logger.trace({
134
+ readers: this.readers,
135
+ writerQueueDepth: this.writerQueue.length,
136
+ readerQueueDepth: this.readerQueue.length,
137
+ }, 'read lock released');
138
+ if (this.readers === 0 && this.writerQueue.length > 0) {
139
+ const next = this.writerQueue.shift();
140
+ next();
141
+ }
142
+ }
143
+ acquireWrite() {
144
+ if (!this.writer && this.readers === 0) {
145
+ this.writer = true;
146
+ this.logger.trace({
147
+ readers: this.readers,
148
+ writerQueueDepth: this.writerQueue.length,
149
+ readerQueueDepth: this.readerQueue.length,
150
+ }, 'write lock acquired');
151
+ return Promise.resolve();
152
+ }
153
+ this.logger.debug({
154
+ readers: this.readers,
155
+ writer: this.writer,
156
+ writerQueueDepth: this.writerQueue.length,
157
+ readerQueueDepth: this.readerQueue.length,
158
+ }, `write lock queued (readers: ${this.readers}, writer active: ${this.writer})`);
159
+ return new Promise((resolve) => {
160
+ this.writerQueue.push(() => {
161
+ this.writer = true;
162
+ this.logger.debug({
163
+ readers: this.readers,
164
+ writerQueueDepth: this.writerQueue.length,
165
+ readerQueueDepth: this.readerQueue.length,
166
+ }, 'write lock acquired after wait');
167
+ resolve();
168
+ });
169
+ });
170
+ }
171
+ releaseWrite() {
172
+ this.writer = false;
173
+ this.logger.trace({
174
+ readers: this.readers,
175
+ writerQueueDepth: this.writerQueue.length,
176
+ readerQueueDepth: this.readerQueue.length,
177
+ }, 'write lock released');
178
+ if (this.writerQueue.length > 0) {
179
+ const next = this.writerQueue.shift();
180
+ next();
181
+ }
182
+ else {
183
+ // Wake ALL waiting readers
184
+ const readers = this.readerQueue.splice(0);
185
+ for (const wake of readers) {
186
+ wake();
187
+ }
188
+ }
189
+ }
190
+ }
191
+ // Helper to access the lock from a command instance via its `project` property.
192
+ function getLock(instance) {
193
+ const lock = instance.project?.lock;
194
+ if (!lock) {
195
+ throw new Error('@read/@write decorator: instance.project.lock is not defined. ' +
196
+ 'Ensure the class has a `project` property with `lock: RWLock`.');
197
+ }
198
+ return lock;
199
+ }
200
+ /**
201
+ * A Helper decorator built for commands that automatically handles using a read lock
202
+ */
203
+ export function read(target) {
204
+ return function (...args) {
205
+ return getLock(this).read(() => target.call(this, ...args));
206
+ };
207
+ }
208
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
209
+ export function write(message) {
210
+ return function (target) {
211
+ return function (...args) {
212
+ if (!message) {
213
+ return getLock(this).write(() => target.call(this, ...args));
214
+ }
215
+ const label = message(...args);
216
+ return runWithDefaultCommitMessage(label, () => getLock(this).write(() => target.call(this, ...args)));
217
+ };
218
+ };
219
+ }
220
+ //# sourceMappingURL=rw-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rw-lock.js","sourceRoot":"","sources":["../../src/utils/rw-lock.ts"],"names":[],"mappings":"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"}
@@ -97,14 +97,14 @@ export class UserPreferences {
97
97
  if (hasCode(error)) {
98
98
  // If file already exists (EEXIST), that's fine - we'll use the existing file
99
99
  if (error.code !== 'EEXIST') {
100
- throw new Error(`Error creating preferences file '${this.prefsFilePath}': ${error}`);
100
+ throw new Error(`Error creating preferences file '${this.prefsFilePath}': ${error}`, { cause: error });
101
101
  }
102
102
  else {
103
103
  this.logger.warn('Preferences file already exists');
104
104
  }
105
105
  }
106
106
  else {
107
- throw new Error(`Error creating preferences file '${this.prefsFilePath}': ${error}`);
107
+ throw new Error(`Error creating preferences file '${this.prefsFilePath}': ${error}`, { cause: error });
108
108
  }
109
109
  }
110
110
  }
@@ -119,7 +119,7 @@ export class UserPreferences {
119
119
  return JSON.parse(readFileSync(this.prefsFilePath, 'utf8'));
120
120
  }
121
121
  catch (error) {
122
- throw new Error(`Error reading preferences file '${this.prefsFilePath}': ${error}`);
122
+ throw new Error(`Error reading preferences file '${this.prefsFilePath}': ${error}`, { cause: error });
123
123
  }
124
124
  }
125
125
  }