@shipfox/annotations 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-type$colon$emit.log +1 -0
  3. package/.turbo/turbo-type.log +1 -0
  4. package/CHANGELOG.md +67 -0
  5. package/LICENSE +21 -0
  6. package/dist/config.d.ts +6 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +26 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/core/entities/annotation.d.ts +21 -0
  11. package/dist/core/entities/annotation.d.ts.map +1 -0
  12. package/dist/core/entities/annotation.js +3 -0
  13. package/dist/core/entities/annotation.js.map +1 -0
  14. package/dist/core/entities/index.d.ts +2 -0
  15. package/dist/core/entities/index.d.ts.map +1 -0
  16. package/dist/core/entities/index.js +3 -0
  17. package/dist/core/entities/index.js.map +1 -0
  18. package/dist/core/errors.d.ts +13 -0
  19. package/dist/core/errors.d.ts.map +1 -0
  20. package/dist/core/errors.js +20 -0
  21. package/dist/core/errors.js.map +1 -0
  22. package/dist/core/write-annotations.d.ts +25 -0
  23. package/dist/core/write-annotations.d.ts.map +1 -0
  24. package/dist/core/write-annotations.js +105 -0
  25. package/dist/core/write-annotations.js.map +1 -0
  26. package/dist/db/annotations.d.ts +63 -0
  27. package/dist/db/annotations.d.ts.map +1 -0
  28. package/dist/db/annotations.js +96 -0
  29. package/dist/db/annotations.js.map +1 -0
  30. package/dist/db/db.d.ts +304 -0
  31. package/dist/db/db.d.ts.map +1 -0
  32. package/dist/db/db.js +18 -0
  33. package/dist/db/db.js.map +1 -0
  34. package/dist/db/index.d.ts +4 -0
  35. package/dist/db/index.d.ts.map +1 -0
  36. package/dist/db/index.js +6 -0
  37. package/dist/db/index.js.map +1 -0
  38. package/dist/db/schema/annotations.d.ts +302 -0
  39. package/dist/db/schema/annotations.d.ts.map +1 -0
  40. package/dist/db/schema/annotations.js +67 -0
  41. package/dist/db/schema/annotations.js.map +1 -0
  42. package/dist/db/schema/common.d.ts +2 -0
  43. package/dist/db/schema/common.d.ts.map +1 -0
  44. package/dist/db/schema/common.js +4 -0
  45. package/dist/db/schema/common.js.map +1 -0
  46. package/dist/index.d.ts +5 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +15 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/presentation/dto/annotation.d.ts +4 -0
  51. package/dist/presentation/dto/annotation.d.ts.map +1 -0
  52. package/dist/presentation/dto/annotation.js +15 -0
  53. package/dist/presentation/dto/annotation.js.map +1 -0
  54. package/dist/presentation/dto/index.d.ts +2 -0
  55. package/dist/presentation/dto/index.d.ts.map +1 -0
  56. package/dist/presentation/dto/index.js +3 -0
  57. package/dist/presentation/dto/index.js.map +1 -0
  58. package/dist/presentation/routes/index.d.ts +3 -0
  59. package/dist/presentation/routes/index.d.ts.map +1 -0
  60. package/dist/presentation/routes/index.js +21 -0
  61. package/dist/presentation/routes/index.js.map +1 -0
  62. package/dist/presentation/routes/read-annotations.d.ts +2 -0
  63. package/dist/presentation/routes/read-annotations.d.ts.map +1 -0
  64. package/dist/presentation/routes/read-annotations.js +49 -0
  65. package/dist/presentation/routes/read-annotations.js.map +1 -0
  66. package/dist/presentation/routes/write-annotations.d.ts +2 -0
  67. package/dist/presentation/routes/write-annotations.d.ts.map +1 -0
  68. package/dist/presentation/routes/write-annotations.js +73 -0
  69. package/dist/presentation/routes/write-annotations.js.map +1 -0
  70. package/dist/tsconfig.test.tsbuildinfo +1 -0
  71. package/drizzle/0000_initial.sql +32 -0
  72. package/drizzle/meta/0000_snapshot.json +247 -0
  73. package/drizzle/meta/_journal.json +13 -0
  74. package/drizzle.config.ts +7 -0
  75. package/package.json +69 -0
  76. package/src/config.test.ts +45 -0
  77. package/src/config.ts +26 -0
  78. package/src/core/entities/annotation.ts +21 -0
  79. package/src/core/entities/index.ts +1 -0
  80. package/src/core/errors.ts +20 -0
  81. package/src/core/write-annotations.test.ts +261 -0
  82. package/src/core/write-annotations.ts +142 -0
  83. package/src/db/annotations.test.ts +261 -0
  84. package/src/db/annotations.ts +180 -0
  85. package/src/db/db.ts +19 -0
  86. package/src/db/index.ts +7 -0
  87. package/src/db/schema/annotations.test.ts +50 -0
  88. package/src/db/schema/annotations.ts +96 -0
  89. package/src/db/schema/common.ts +3 -0
  90. package/src/index.ts +17 -0
  91. package/src/presentation/dto/annotation.test.ts +42 -0
  92. package/src/presentation/dto/annotation.ts +16 -0
  93. package/src/presentation/dto/index.ts +1 -0
  94. package/src/presentation/routes/index.ts +17 -0
  95. package/src/presentation/routes/read-annotations.test.ts +279 -0
  96. package/src/presentation/routes/read-annotations.ts +50 -0
  97. package/src/presentation/routes/write-annotations.test.ts +188 -0
  98. package/src/presentation/routes/write-annotations.ts +76 -0
  99. package/test/env.ts +5 -0
  100. package/test/factories/annotation.ts +57 -0
  101. package/test/fixtures/lease-token.ts +43 -0
  102. package/test/globalSetup.ts +20 -0
  103. package/test/index.ts +2 -0
  104. package/test/setup.ts +17 -0
  105. package/tsconfig.build.json +9 -0
  106. package/tsconfig.build.tsbuildinfo +1 -0
  107. package/tsconfig.json +3 -0
  108. package/tsconfig.test.json +8 -0
  109. package/vitest.config.ts +13 -0
@@ -0,0 +1,2 @@
1
+ $ shipfox-swc
2
+ Successfully compiled: 16 files with swc (50.57ms)
@@ -0,0 +1 @@
1
+ $ shipfox-tsc-emit
@@ -0,0 +1 @@
1
+ $ shipfox-tsc-check
package/CHANGELOG.md ADDED
@@ -0,0 +1,67 @@
1
+ # @shipfox/annotations
2
+
3
+ ## 2.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 1b0d344: Publishes the complete API runtime closure with packed-consumer-safe internal imports and records its exact package set in application releases.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [0cd6dd4]
12
+ - Updated dependencies [1b0d344]
13
+ - @shipfox/node-module@0.2.0
14
+ - @shipfox/annotations-dto@2.0.0
15
+ - @shipfox/api-auth-context@2.0.0
16
+ - @shipfox/config@1.2.1
17
+ - @shipfox/node-drizzle@0.2.1
18
+ - @shipfox/node-fastify@0.2.1
19
+ - @shipfox/node-postgres@0.4.1
20
+
21
+ ## 0.0.3
22
+
23
+ ### Patch Changes
24
+
25
+ - @shipfox/node-module@0.1.2
26
+
27
+ ## 0.0.2
28
+
29
+ ### Patch Changes
30
+
31
+ - Updated dependencies [ec75cd5]
32
+ - Updated dependencies [6a1fb54]
33
+ - @shipfox/node-drizzle@0.2.0
34
+ - @shipfox/node-postgres@0.4.0
35
+ - @shipfox/node-module@0.1.1
36
+
37
+ ## 0.0.1
38
+
39
+ ### Patch Changes
40
+
41
+ - 5707d6d: Adds the user-authenticated annotations read route with workspace-scoped run-attempt filtering, DTO conversion, and a continuation cursor.
42
+ - 0dd23a7: Warns on agent tool capability mismatches during dispatch without blocking label-matched runners.
43
+ - Updated dependencies [34ba284]
44
+ - Updated dependencies [5707d6d]
45
+ - Updated dependencies [b9c3f32]
46
+ - Updated dependencies [a81b68c]
47
+ - Updated dependencies [c0a883c]
48
+ - Updated dependencies [72ce351]
49
+ - Updated dependencies [e47f8da]
50
+ - Updated dependencies [ae7a63c]
51
+ - Updated dependencies [f92122b]
52
+ - Updated dependencies [857fd73]
53
+ - Updated dependencies [75520ff]
54
+ - Updated dependencies [d6d4862]
55
+ - Updated dependencies [c0a883c]
56
+ - Updated dependencies [6077301]
57
+ - Updated dependencies [3bea87f]
58
+ - Updated dependencies [82d22e4]
59
+ - Updated dependencies [6181819]
60
+ - Updated dependencies [9c149d1]
61
+ - @shipfox/node-fastify@0.2.0
62
+ - @shipfox/annotations-dto@0.0.1
63
+ - @shipfox/node-drizzle@0.1.0
64
+ - @shipfox/api-auth-context@0.1.0
65
+ - @shipfox/node-postgres@0.3.2
66
+ - @shipfox/node-module@0.1.0
67
+ - @shipfox/config@1.2.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shipfox
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ export declare const config: Readonly<{
2
+ ANNOTATIONS_MAX_BODY_BYTES: number;
3
+ ANNOTATIONS_MAX_PER_EXECUTION: number;
4
+ ANNOTATIONS_MAX_TOTAL_BYTES: number;
5
+ } & import("@shipfox/config").CleanedEnvAccessors>;
6
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,MAAM;;;;kDAajB,CAAC"}
package/dist/config.js ADDED
@@ -0,0 +1,26 @@
1
+ import { createConfig, num } from '@shipfox/config';
2
+ export const config = createConfig({
3
+ ANNOTATIONS_MAX_BODY_BYTES: num({
4
+ desc: 'Maximum UTF-8 byte size of one annotation body. Writes that would make an annotation body larger than this are rejected.',
5
+ default: 1048576
6
+ }),
7
+ ANNOTATIONS_MAX_PER_EXECUTION: num({
8
+ desc: 'Maximum number of distinct annotation contexts one job execution may store.',
9
+ default: 50
10
+ }),
11
+ ANNOTATIONS_MAX_TOTAL_BYTES: num({
12
+ desc: 'Maximum total UTF-8 bytes of annotation bodies one job execution may store.',
13
+ default: 4194304
14
+ })
15
+ });
16
+ for (const name of [
17
+ 'ANNOTATIONS_MAX_BODY_BYTES',
18
+ 'ANNOTATIONS_MAX_PER_EXECUTION',
19
+ 'ANNOTATIONS_MAX_TOTAL_BYTES'
20
+ ]){
21
+ if (!Number.isInteger(config[name]) || config[name] < 1) {
22
+ throw new Error(`${name} (${config[name]}) must be a whole number greater than 0.`);
23
+ }
24
+ }
25
+
26
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts"],"sourcesContent":["import {createConfig, num} from '@shipfox/config';\n\nexport const config = createConfig({\n ANNOTATIONS_MAX_BODY_BYTES: num({\n desc: 'Maximum UTF-8 byte size of one annotation body. Writes that would make an annotation body larger than this are rejected.',\n default: 1048576,\n }),\n ANNOTATIONS_MAX_PER_EXECUTION: num({\n desc: 'Maximum number of distinct annotation contexts one job execution may store.',\n default: 50,\n }),\n ANNOTATIONS_MAX_TOTAL_BYTES: num({\n desc: 'Maximum total UTF-8 bytes of annotation bodies one job execution may store.',\n default: 4194304,\n }),\n});\n\nfor (const name of [\n 'ANNOTATIONS_MAX_BODY_BYTES',\n 'ANNOTATIONS_MAX_PER_EXECUTION',\n 'ANNOTATIONS_MAX_TOTAL_BYTES',\n] as const) {\n if (!Number.isInteger(config[name]) || config[name] < 1) {\n throw new Error(`${name} (${config[name]}) must be a whole number greater than 0.`);\n }\n}\n"],"names":["createConfig","num","config","ANNOTATIONS_MAX_BODY_BYTES","desc","default","ANNOTATIONS_MAX_PER_EXECUTION","ANNOTATIONS_MAX_TOTAL_BYTES","name","Number","isInteger","Error"],"mappings":"AAAA,SAAQA,YAAY,EAAEC,GAAG,QAAO,kBAAkB;AAElD,OAAO,MAAMC,SAASF,aAAa;IACjCG,4BAA4BF,IAAI;QAC9BG,MAAM;QACNC,SAAS;IACX;IACAC,+BAA+BL,IAAI;QACjCG,MAAM;QACNC,SAAS;IACX;IACAE,6BAA6BN,IAAI;QAC/BG,MAAM;QACNC,SAAS;IACX;AACF,GAAG;AAEH,KAAK,MAAMG,QAAQ;IACjB;IACA;IACA;CACD,CAAW;IACV,IAAI,CAACC,OAAOC,SAAS,CAACR,MAAM,CAACM,KAAK,KAAKN,MAAM,CAACM,KAAK,GAAG,GAAG;QACvD,MAAM,IAAIG,MAAM,GAAGH,KAAK,EAAE,EAAEN,MAAM,CAACM,KAAK,CAAC,wCAAwC,CAAC;IACpF;AACF"}
@@ -0,0 +1,21 @@
1
+ import type { AnnotationStyleDto } from '@shipfox/annotations-dto';
2
+ export interface Annotation {
3
+ id: string;
4
+ workspaceId: string;
5
+ projectId: string;
6
+ workflowRunId: string;
7
+ workflowRunAttempt: number;
8
+ workflowRunAttemptId: string;
9
+ jobId: string;
10
+ jobExecutionId: string;
11
+ originStepId: string;
12
+ originStepAttempt: number;
13
+ context: string;
14
+ style: AnnotationStyleDto;
15
+ body: string;
16
+ bodyBytes: number;
17
+ sequence: number;
18
+ createdAt: Date;
19
+ updatedAt: Date;
20
+ }
21
+ //# sourceMappingURL=annotation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"annotation.d.ts","sourceRoot":"","sources":["../../../src/core/entities/annotation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,0BAA0B,CAAC;AAEjE,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB"}
@@ -0,0 +1,3 @@
1
+ export { };
2
+
3
+ //# sourceMappingURL=annotation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/core/entities/annotation.ts"],"sourcesContent":["import type {AnnotationStyleDto} from '@shipfox/annotations-dto';\n\nexport interface Annotation {\n id: string;\n workspaceId: string;\n projectId: string;\n workflowRunId: string;\n workflowRunAttempt: number;\n workflowRunAttemptId: string;\n jobId: string;\n jobExecutionId: string;\n originStepId: string;\n originStepAttempt: number;\n context: string;\n style: AnnotationStyleDto;\n body: string;\n bodyBytes: number;\n sequence: number;\n createdAt: Date;\n updatedAt: Date;\n}\n"],"names":[],"mappings":"AAEA,WAkBC"}
@@ -0,0 +1,2 @@
1
+ export type { Annotation } from './annotation.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/entities/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAC,UAAU,EAAC,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { };
2
+
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/core/entities/index.ts"],"sourcesContent":["export type {Annotation} from './annotation.js';\n"],"names":[],"mappings":"AAAA,WAAgD"}
@@ -0,0 +1,13 @@
1
+ export declare class AnnotationBodyTooLargeError extends Error {
2
+ readonly maxBytes: number;
3
+ constructor(maxBytes: number);
4
+ }
5
+ export declare class AnnotationCountLimitExceededError extends Error {
6
+ readonly maxAnnotations: number;
7
+ constructor(maxAnnotations: number);
8
+ }
9
+ export declare class AnnotationTotalBytesLimitExceededError extends Error {
10
+ readonly maxBytes: number;
11
+ constructor(maxBytes: number);
12
+ }
13
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,2BAA4B,SAAQ,KAAK;aACxB,QAAQ,EAAE,MAAM;gBAAhB,QAAQ,EAAE,MAAM;CAI7C;AAED,qBAAa,iCAAkC,SAAQ,KAAK;aAC9B,cAAc,EAAE,MAAM;gBAAtB,cAAc,EAAE,MAAM;CAInD;AAED,qBAAa,sCAAuC,SAAQ,KAAK;aACnC,QAAQ,EAAE,MAAM;gBAAhB,QAAQ,EAAE,MAAM;CAI7C"}
@@ -0,0 +1,20 @@
1
+ export class AnnotationBodyTooLargeError extends Error {
2
+ constructor(maxBytes){
3
+ super(`Annotation body exceeds ${maxBytes} bytes`), this.maxBytes = maxBytes;
4
+ this.name = 'AnnotationBodyTooLargeError';
5
+ }
6
+ }
7
+ export class AnnotationCountLimitExceededError extends Error {
8
+ constructor(maxAnnotations){
9
+ super(`Annotation count exceeds ${maxAnnotations}`), this.maxAnnotations = maxAnnotations;
10
+ this.name = 'AnnotationCountLimitExceededError';
11
+ }
12
+ }
13
+ export class AnnotationTotalBytesLimitExceededError extends Error {
14
+ constructor(maxBytes){
15
+ super(`Annotation total body bytes exceed ${maxBytes}`), this.maxBytes = maxBytes;
16
+ this.name = 'AnnotationTotalBytesLimitExceededError';
17
+ }
18
+ }
19
+
20
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/errors.ts"],"sourcesContent":["export class AnnotationBodyTooLargeError extends Error {\n constructor(public readonly maxBytes: number) {\n super(`Annotation body exceeds ${maxBytes} bytes`);\n this.name = 'AnnotationBodyTooLargeError';\n }\n}\n\nexport class AnnotationCountLimitExceededError extends Error {\n constructor(public readonly maxAnnotations: number) {\n super(`Annotation count exceeds ${maxAnnotations}`);\n this.name = 'AnnotationCountLimitExceededError';\n }\n}\n\nexport class AnnotationTotalBytesLimitExceededError extends Error {\n constructor(public readonly maxBytes: number) {\n super(`Annotation total body bytes exceed ${maxBytes}`);\n this.name = 'AnnotationTotalBytesLimitExceededError';\n }\n}\n"],"names":["AnnotationBodyTooLargeError","Error","maxBytes","name","AnnotationCountLimitExceededError","maxAnnotations","AnnotationTotalBytesLimitExceededError"],"mappings":"AAAA,OAAO,MAAMA,oCAAoCC;IAC/C,YAAY,AAAgBC,QAAgB,CAAE;QAC5C,KAAK,CAAC,CAAC,wBAAwB,EAAEA,SAAS,MAAM,CAAC,QADvBA,WAAAA;QAE1B,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMC,0CAA0CH;IACrD,YAAY,AAAgBI,cAAsB,CAAE;QAClD,KAAK,CAAC,CAAC,yBAAyB,EAAEA,gBAAgB,QADxBA,iBAAAA;QAE1B,IAAI,CAACF,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMG,+CAA+CL;IAC1D,YAAY,AAAgBC,QAAgB,CAAE;QAC5C,KAAK,CAAC,CAAC,mCAAmC,EAAEA,UAAU,QAD5BA,WAAAA;QAE1B,IAAI,CAACC,IAAI,GAAG;IACd;AACF"}
@@ -0,0 +1,25 @@
1
+ import type { LeasedWriteAnnotationOperationDto } from '@shipfox/annotations-dto';
2
+ export interface WriteAnnotationsParams {
3
+ workspaceId: string;
4
+ projectId: string;
5
+ workflowRunId: string;
6
+ workflowRunAttempt: number;
7
+ workflowRunAttemptId: string;
8
+ jobId: string;
9
+ jobExecutionId: string;
10
+ originStepId: string;
11
+ originStepAttempt: number;
12
+ operations: readonly LeasedWriteAnnotationOperationDto[];
13
+ }
14
+ export interface WriteAnnotationsResult {
15
+ annotations: Array<{
16
+ context: string;
17
+ id: string | null;
18
+ }>;
19
+ accounting: {
20
+ annotationCount: number;
21
+ totalBodyBytes: number;
22
+ };
23
+ }
24
+ export declare function writeAnnotations(params: WriteAnnotationsParams): Promise<WriteAnnotationsResult>;
25
+ //# sourceMappingURL=write-annotations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-annotations.d.ts","sourceRoot":"","sources":["../../src/core/write-annotations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,iCAAiC,EAAC,MAAM,0BAA0B,CAAC;AAShF,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,SAAS,iCAAiC,EAAE,CAAC;CAC1D;AAED,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,KAAK,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;KAAC,CAAC,CAAC;IACzD,UAAU,EAAE;QACV,eAAe,EAAE,MAAM,CAAC;QACxB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;CACH;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAqFhG"}
@@ -0,0 +1,105 @@
1
+ import { config } from '#config.js';
2
+ import { withAnnotationLock } from '#db/annotations.js';
3
+ import { AnnotationBodyTooLargeError, AnnotationCountLimitExceededError, AnnotationTotalBytesLimitExceededError } from './errors.js';
4
+ export function writeAnnotations(params) {
5
+ return withAnnotationLock(params.jobExecutionId, async (repo)=>{
6
+ const current = await repo.loadCurrentAnnotations(params.jobExecutionId);
7
+ let nextSequence = Math.max(0, ...Array.from(current.values()).map((annotation)=>annotation.sequence)) + 1;
8
+ const results = [];
9
+ for (const operation of params.operations){
10
+ if (operation.op === 'remove') {
11
+ await repo.removeAnnotation(params.jobExecutionId, operation.context);
12
+ current.delete(operation.context);
13
+ results.push({
14
+ context: operation.context,
15
+ id: null
16
+ });
17
+ continue;
18
+ }
19
+ const existing = current.get(operation.context);
20
+ const body = operation.op === 'append' ? `${existing?.body ?? ''}${operation.body}` : operation.body;
21
+ const bodyBytes = Buffer.byteLength(body);
22
+ ensureBodyBudget(bodyBytes);
23
+ const isUnchangedReplace = operation.op === 'replace' && existing !== undefined && existing.body === body && existing.style === operation.style;
24
+ if (isUnchangedReplace) {
25
+ results.push({
26
+ context: operation.context,
27
+ id: existing.id
28
+ });
29
+ continue;
30
+ }
31
+ const draft = new Map(current);
32
+ draft.set(operation.context, {
33
+ id: existing?.id ?? '',
34
+ context: operation.context,
35
+ style: operation.style,
36
+ body,
37
+ bodyBytes,
38
+ sequence: existing?.sequence ?? nextSequence
39
+ });
40
+ ensureExecutionBudgets(draft);
41
+ const row = existing ? await repo.updateAnnotation({
42
+ id: existing.id,
43
+ originStepId: params.originStepId,
44
+ originStepAttempt: params.originStepAttempt,
45
+ style: operation.style,
46
+ body,
47
+ bodyBytes
48
+ }) : await repo.createAnnotation({
49
+ workspaceId: params.workspaceId,
50
+ projectId: params.projectId,
51
+ workflowRunId: params.workflowRunId,
52
+ workflowRunAttempt: params.workflowRunAttempt,
53
+ workflowRunAttemptId: params.workflowRunAttemptId,
54
+ jobId: params.jobId,
55
+ jobExecutionId: params.jobExecutionId,
56
+ originStepId: params.originStepId,
57
+ originStepAttempt: params.originStepAttempt,
58
+ context: operation.context,
59
+ style: operation.style,
60
+ body,
61
+ bodyBytes,
62
+ sequence: nextSequence
63
+ });
64
+ current.set(operation.context, {
65
+ id: row.id,
66
+ context: row.context,
67
+ style: row.style,
68
+ body: row.body,
69
+ bodyBytes: row.bodyBytes,
70
+ sequence: row.sequence
71
+ });
72
+ if (!existing) nextSequence += 1;
73
+ results.push({
74
+ context: operation.context,
75
+ id: row.id
76
+ });
77
+ }
78
+ return {
79
+ annotations: results,
80
+ accounting: currentAccounting(current)
81
+ };
82
+ });
83
+ }
84
+ function ensureBodyBudget(bodyBytes) {
85
+ if (bodyBytes > config.ANNOTATIONS_MAX_BODY_BYTES) {
86
+ throw new AnnotationBodyTooLargeError(config.ANNOTATIONS_MAX_BODY_BYTES);
87
+ }
88
+ }
89
+ function ensureExecutionBudgets(annotationsByContext) {
90
+ const accounting = currentAccounting(annotationsByContext);
91
+ if (accounting.annotationCount > config.ANNOTATIONS_MAX_PER_EXECUTION) {
92
+ throw new AnnotationCountLimitExceededError(config.ANNOTATIONS_MAX_PER_EXECUTION);
93
+ }
94
+ if (accounting.totalBodyBytes > config.ANNOTATIONS_MAX_TOTAL_BYTES) {
95
+ throw new AnnotationTotalBytesLimitExceededError(config.ANNOTATIONS_MAX_TOTAL_BYTES);
96
+ }
97
+ }
98
+ function currentAccounting(annotationsByContext) {
99
+ return {
100
+ annotationCount: annotationsByContext.size,
101
+ totalBodyBytes: Array.from(annotationsByContext.values()).reduce((total, annotation)=>total + annotation.bodyBytes, 0)
102
+ };
103
+ }
104
+
105
+ //# sourceMappingURL=write-annotations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/write-annotations.ts"],"sourcesContent":["import type {LeasedWriteAnnotationOperationDto} from '@shipfox/annotations-dto';\nimport {config} from '#config.js';\nimport {type StoredAnnotation, withAnnotationLock} from '#db/annotations.js';\nimport {\n AnnotationBodyTooLargeError,\n AnnotationCountLimitExceededError,\n AnnotationTotalBytesLimitExceededError,\n} from './errors.js';\n\nexport interface WriteAnnotationsParams {\n workspaceId: string;\n projectId: string;\n workflowRunId: string;\n workflowRunAttempt: number;\n workflowRunAttemptId: string;\n jobId: string;\n jobExecutionId: string;\n originStepId: string;\n originStepAttempt: number;\n operations: readonly LeasedWriteAnnotationOperationDto[];\n}\n\nexport interface WriteAnnotationsResult {\n annotations: Array<{context: string; id: string | null}>;\n accounting: {\n annotationCount: number;\n totalBodyBytes: number;\n };\n}\n\nexport function writeAnnotations(params: WriteAnnotationsParams): Promise<WriteAnnotationsResult> {\n return withAnnotationLock(params.jobExecutionId, async (repo) => {\n const current = await repo.loadCurrentAnnotations(params.jobExecutionId);\n let nextSequence =\n Math.max(0, ...Array.from(current.values()).map((annotation) => annotation.sequence)) + 1;\n const results: WriteAnnotationsResult['annotations'] = [];\n\n for (const operation of params.operations) {\n if (operation.op === 'remove') {\n await repo.removeAnnotation(params.jobExecutionId, operation.context);\n current.delete(operation.context);\n results.push({context: operation.context, id: null});\n continue;\n }\n\n const existing = current.get(operation.context);\n const body =\n operation.op === 'append' ? `${existing?.body ?? ''}${operation.body}` : operation.body;\n const bodyBytes = Buffer.byteLength(body);\n ensureBodyBudget(bodyBytes);\n\n const isUnchangedReplace =\n operation.op === 'replace' &&\n existing !== undefined &&\n existing.body === body &&\n existing.style === operation.style;\n if (isUnchangedReplace) {\n results.push({context: operation.context, id: existing.id});\n continue;\n }\n\n const draft = new Map(current);\n draft.set(operation.context, {\n id: existing?.id ?? '',\n context: operation.context,\n style: operation.style,\n body,\n bodyBytes,\n sequence: existing?.sequence ?? nextSequence,\n });\n ensureExecutionBudgets(draft);\n\n const row = existing\n ? await repo.updateAnnotation({\n id: existing.id,\n originStepId: params.originStepId,\n originStepAttempt: params.originStepAttempt,\n style: operation.style,\n body,\n bodyBytes,\n })\n : await repo.createAnnotation({\n workspaceId: params.workspaceId,\n projectId: params.projectId,\n workflowRunId: params.workflowRunId,\n workflowRunAttempt: params.workflowRunAttempt,\n workflowRunAttemptId: params.workflowRunAttemptId,\n jobId: params.jobId,\n jobExecutionId: params.jobExecutionId,\n originStepId: params.originStepId,\n originStepAttempt: params.originStepAttempt,\n context: operation.context,\n style: operation.style,\n body,\n bodyBytes,\n sequence: nextSequence,\n });\n\n current.set(operation.context, {\n id: row.id,\n context: row.context,\n style: row.style,\n body: row.body,\n bodyBytes: row.bodyBytes,\n sequence: row.sequence,\n });\n if (!existing) nextSequence += 1;\n results.push({context: operation.context, id: row.id});\n }\n\n return {\n annotations: results,\n accounting: currentAccounting(current),\n };\n });\n}\n\nfunction ensureBodyBudget(bodyBytes: number): void {\n if (bodyBytes > config.ANNOTATIONS_MAX_BODY_BYTES) {\n throw new AnnotationBodyTooLargeError(config.ANNOTATIONS_MAX_BODY_BYTES);\n }\n}\n\nfunction ensureExecutionBudgets(annotationsByContext: ReadonlyMap<string, StoredAnnotation>): void {\n const accounting = currentAccounting(annotationsByContext);\n if (accounting.annotationCount > config.ANNOTATIONS_MAX_PER_EXECUTION) {\n throw new AnnotationCountLimitExceededError(config.ANNOTATIONS_MAX_PER_EXECUTION);\n }\n if (accounting.totalBodyBytes > config.ANNOTATIONS_MAX_TOTAL_BYTES) {\n throw new AnnotationTotalBytesLimitExceededError(config.ANNOTATIONS_MAX_TOTAL_BYTES);\n }\n}\n\nfunction currentAccounting(annotationsByContext: ReadonlyMap<string, StoredAnnotation>) {\n return {\n annotationCount: annotationsByContext.size,\n totalBodyBytes: Array.from(annotationsByContext.values()).reduce(\n (total, annotation) => total + annotation.bodyBytes,\n 0,\n ),\n };\n}\n"],"names":["config","withAnnotationLock","AnnotationBodyTooLargeError","AnnotationCountLimitExceededError","AnnotationTotalBytesLimitExceededError","writeAnnotations","params","jobExecutionId","repo","current","loadCurrentAnnotations","nextSequence","Math","max","Array","from","values","map","annotation","sequence","results","operation","operations","op","removeAnnotation","context","delete","push","id","existing","get","body","bodyBytes","Buffer","byteLength","ensureBodyBudget","isUnchangedReplace","undefined","style","draft","Map","set","ensureExecutionBudgets","row","updateAnnotation","originStepId","originStepAttempt","createAnnotation","workspaceId","projectId","workflowRunId","workflowRunAttempt","workflowRunAttemptId","jobId","annotations","accounting","currentAccounting","ANNOTATIONS_MAX_BODY_BYTES","annotationsByContext","annotationCount","ANNOTATIONS_MAX_PER_EXECUTION","totalBodyBytes","ANNOTATIONS_MAX_TOTAL_BYTES","size","reduce","total"],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import { type AnnotationStyleDto } from '@shipfox/annotations-dto';
2
+ import type { Annotation } from '#core/entities/annotation.js';
3
+ export declare const DEFAULT_ANNOTATIONS_READ_LIMIT = 500;
4
+ export interface ListAnnotationsForRunAttemptParams {
5
+ workflowRunId: string;
6
+ workflowRunAttempt: number;
7
+ workspaceIds: readonly string[];
8
+ jobExecutionId?: string | undefined;
9
+ after?: {
10
+ sequence: number;
11
+ id: string;
12
+ } | undefined;
13
+ limit?: number | undefined;
14
+ }
15
+ export interface ListAnnotationsForRunAttemptResult {
16
+ annotations: Annotation[];
17
+ hasMore: boolean;
18
+ nextCursor: {
19
+ sequence: number;
20
+ id: string;
21
+ } | null;
22
+ }
23
+ export declare function listAnnotationsForRunAttempt(params: ListAnnotationsForRunAttemptParams): Promise<ListAnnotationsForRunAttemptResult>;
24
+ export interface StoredAnnotation {
25
+ id: string;
26
+ context: string;
27
+ style: AnnotationStyleDto;
28
+ body: string;
29
+ bodyBytes: number;
30
+ sequence: number;
31
+ }
32
+ export interface CreateAnnotationParams {
33
+ workspaceId: string;
34
+ projectId: string;
35
+ workflowRunId: string;
36
+ workflowRunAttempt: number;
37
+ workflowRunAttemptId: string;
38
+ jobId: string;
39
+ jobExecutionId: string;
40
+ originStepId: string;
41
+ originStepAttempt: number;
42
+ context: string;
43
+ style: AnnotationStyleDto;
44
+ body: string;
45
+ bodyBytes: number;
46
+ sequence: number;
47
+ }
48
+ export interface UpdateAnnotationParams {
49
+ id: string;
50
+ originStepId: string;
51
+ originStepAttempt: number;
52
+ style: AnnotationStyleDto;
53
+ body: string;
54
+ bodyBytes: number;
55
+ }
56
+ export interface AnnotationWriteRepository {
57
+ loadCurrentAnnotations(jobExecutionId: string): Promise<Map<string, StoredAnnotation>>;
58
+ removeAnnotation(jobExecutionId: string, context: string): Promise<void>;
59
+ createAnnotation(params: CreateAnnotationParams): Promise<StoredAnnotation>;
60
+ updateAnnotation(params: UpdateAnnotationParams): Promise<StoredAnnotation>;
61
+ }
62
+ export declare function withAnnotationLock<T>(jobExecutionId: string, work: (repo: AnnotationWriteRepository) => Promise<T>): Promise<T>;
63
+ //# sourceMappingURL=annotations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"annotations.d.ts","sourceRoot":"","sources":["../../src/db/annotations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,kBAAkB,EAA6B,MAAM,0BAA0B,CAAC;AAE7F,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,8BAA8B,CAAC;AAI7D,eAAO,MAAM,8BAA8B,MAA6B,CAAC;AAEzE,MAAM,WAAW,kCAAkC;IACjD,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,KAAK,CAAC,EAAE;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAC,GAAG,SAAS,CAAC;IACnD,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAED,MAAM,WAAW,kCAAkC;IACjD,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAC,GAAG,IAAI,CAAC;CACnD;AAED,wBAAsB,4BAA4B,CAChD,MAAM,EAAE,kCAAkC,GACzC,OAAO,CAAC,kCAAkC,CAAC,CAoC7C;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE,kBAAkB,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,yBAAyB;IACxC,sBAAsB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC;IACvF,gBAAgB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzE,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC5E,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;CAC7E;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAClC,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,CAAC,IAAI,EAAE,yBAAyB,KAAK,OAAO,CAAC,CAAC,CAAC,GACpD,OAAO,CAAC,CAAC,CAAC,CAuEZ"}
@@ -0,0 +1,96 @@
1
+ import { READ_ANNOTATIONS_MAX_LIMIT } from '@shipfox/annotations-dto';
2
+ import { and, asc, eq, gt, inArray, or, sql } from 'drizzle-orm';
3
+ import { db } from './db.js';
4
+ import { annotations, toAnnotation } from './schema/annotations.js';
5
+ export const DEFAULT_ANNOTATIONS_READ_LIMIT = READ_ANNOTATIONS_MAX_LIMIT;
6
+ export async function listAnnotationsForRunAttempt(params) {
7
+ if (params.workspaceIds.length === 0) return {
8
+ annotations: [],
9
+ hasMore: false,
10
+ nextCursor: null
11
+ };
12
+ const limit = params.limit ?? DEFAULT_ANNOTATIONS_READ_LIMIT;
13
+ const conditions = [
14
+ eq(annotations.workflowRunId, params.workflowRunId),
15
+ eq(annotations.workflowRunAttempt, params.workflowRunAttempt),
16
+ inArray(annotations.workspaceId, [
17
+ ...params.workspaceIds
18
+ ])
19
+ ];
20
+ if (params.jobExecutionId) {
21
+ conditions.push(eq(annotations.jobExecutionId, params.jobExecutionId));
22
+ }
23
+ if (params.after) {
24
+ const cursorCondition = or(gt(annotations.sequence, params.after.sequence), and(eq(annotations.sequence, params.after.sequence), gt(annotations.id, params.after.id)));
25
+ if (cursorCondition) conditions.push(cursorCondition);
26
+ }
27
+ const rows = await db().select().from(annotations).where(and(...conditions)).orderBy(asc(annotations.sequence), asc(annotations.id)).limit(limit + 1);
28
+ const pageRows = rows.slice(0, limit);
29
+ const last = pageRows.at(-1);
30
+ return {
31
+ annotations: pageRows.map(toAnnotation),
32
+ hasMore: rows.length > limit,
33
+ nextCursor: rows.length > limit && last ? {
34
+ sequence: last.sequence,
35
+ id: last.id
36
+ } : null
37
+ };
38
+ }
39
+ export function withAnnotationLock(jobExecutionId, work) {
40
+ return db().transaction(async (tx)=>{
41
+ await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${jobExecutionId}))`);
42
+ const repo = {
43
+ loadCurrentAnnotations: async (lockedJobExecutionId)=>{
44
+ const rows = await tx.select({
45
+ id: annotations.id,
46
+ context: annotations.context,
47
+ style: annotations.style,
48
+ body: annotations.body,
49
+ bodyBytes: annotations.bodyBytes,
50
+ sequence: annotations.sequence
51
+ }).from(annotations).where(eq(annotations.jobExecutionId, lockedJobExecutionId));
52
+ return new Map(rows.map((row)=>[
53
+ row.context,
54
+ row
55
+ ]));
56
+ },
57
+ removeAnnotation: async (lockedJobExecutionId, context)=>{
58
+ await tx.delete(annotations).where(and(eq(annotations.jobExecutionId, lockedJobExecutionId), eq(annotations.context, context)));
59
+ },
60
+ createAnnotation: async (params)=>{
61
+ const [row] = await tx.insert(annotations).values(params).returning({
62
+ id: annotations.id,
63
+ context: annotations.context,
64
+ style: annotations.style,
65
+ body: annotations.body,
66
+ bodyBytes: annotations.bodyBytes,
67
+ sequence: annotations.sequence
68
+ });
69
+ if (!row) throw new Error('createAnnotation: insert returned no row');
70
+ return row;
71
+ },
72
+ updateAnnotation: async (params)=>{
73
+ const [row] = await tx.update(annotations).set({
74
+ originStepId: params.originStepId,
75
+ originStepAttempt: params.originStepAttempt,
76
+ style: params.style,
77
+ body: params.body,
78
+ bodyBytes: params.bodyBytes,
79
+ updatedAt: new Date()
80
+ }).where(eq(annotations.id, params.id)).returning({
81
+ id: annotations.id,
82
+ context: annotations.context,
83
+ style: annotations.style,
84
+ body: annotations.body,
85
+ bodyBytes: annotations.bodyBytes,
86
+ sequence: annotations.sequence
87
+ });
88
+ if (!row) throw new Error('updateAnnotation: update returned no row');
89
+ return row;
90
+ }
91
+ };
92
+ return await work(repo);
93
+ });
94
+ }
95
+
96
+ //# sourceMappingURL=annotations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/db/annotations.ts"],"sourcesContent":["import {type AnnotationStyleDto, READ_ANNOTATIONS_MAX_LIMIT} from '@shipfox/annotations-dto';\nimport {and, asc, eq, gt, inArray, or, type SQL, sql} from 'drizzle-orm';\nimport type {Annotation} from '#core/entities/annotation.js';\nimport {db} from './db.js';\nimport {annotations, toAnnotation} from './schema/annotations.js';\n\nexport const DEFAULT_ANNOTATIONS_READ_LIMIT = READ_ANNOTATIONS_MAX_LIMIT;\n\nexport interface ListAnnotationsForRunAttemptParams {\n workflowRunId: string;\n workflowRunAttempt: number;\n workspaceIds: readonly string[];\n jobExecutionId?: string | undefined;\n after?: {sequence: number; id: string} | undefined;\n limit?: number | undefined;\n}\n\nexport interface ListAnnotationsForRunAttemptResult {\n annotations: Annotation[];\n hasMore: boolean;\n nextCursor: {sequence: number; id: string} | null;\n}\n\nexport async function listAnnotationsForRunAttempt(\n params: ListAnnotationsForRunAttemptParams,\n): Promise<ListAnnotationsForRunAttemptResult> {\n if (params.workspaceIds.length === 0) return {annotations: [], hasMore: false, nextCursor: null};\n\n const limit = params.limit ?? DEFAULT_ANNOTATIONS_READ_LIMIT;\n\n const conditions: SQL[] = [\n eq(annotations.workflowRunId, params.workflowRunId),\n eq(annotations.workflowRunAttempt, params.workflowRunAttempt),\n inArray(annotations.workspaceId, [...params.workspaceIds]),\n ];\n if (params.jobExecutionId) {\n conditions.push(eq(annotations.jobExecutionId, params.jobExecutionId));\n }\n if (params.after) {\n const cursorCondition = or(\n gt(annotations.sequence, params.after.sequence),\n and(eq(annotations.sequence, params.after.sequence), gt(annotations.id, params.after.id)),\n );\n if (cursorCondition) conditions.push(cursorCondition);\n }\n\n const rows = await db()\n .select()\n .from(annotations)\n .where(and(...conditions))\n .orderBy(asc(annotations.sequence), asc(annotations.id))\n .limit(limit + 1);\n\n const pageRows = rows.slice(0, limit);\n const last = pageRows.at(-1);\n\n return {\n annotations: pageRows.map(toAnnotation),\n hasMore: rows.length > limit,\n nextCursor: rows.length > limit && last ? {sequence: last.sequence, id: last.id} : null,\n };\n}\n\nexport interface StoredAnnotation {\n id: string;\n context: string;\n style: AnnotationStyleDto;\n body: string;\n bodyBytes: number;\n sequence: number;\n}\n\nexport interface CreateAnnotationParams {\n workspaceId: string;\n projectId: string;\n workflowRunId: string;\n workflowRunAttempt: number;\n workflowRunAttemptId: string;\n jobId: string;\n jobExecutionId: string;\n originStepId: string;\n originStepAttempt: number;\n context: string;\n style: AnnotationStyleDto;\n body: string;\n bodyBytes: number;\n sequence: number;\n}\n\nexport interface UpdateAnnotationParams {\n id: string;\n originStepId: string;\n originStepAttempt: number;\n style: AnnotationStyleDto;\n body: string;\n bodyBytes: number;\n}\n\nexport interface AnnotationWriteRepository {\n loadCurrentAnnotations(jobExecutionId: string): Promise<Map<string, StoredAnnotation>>;\n removeAnnotation(jobExecutionId: string, context: string): Promise<void>;\n createAnnotation(params: CreateAnnotationParams): Promise<StoredAnnotation>;\n updateAnnotation(params: UpdateAnnotationParams): Promise<StoredAnnotation>;\n}\n\nexport function withAnnotationLock<T>(\n jobExecutionId: string,\n work: (repo: AnnotationWriteRepository) => Promise<T>,\n): Promise<T> {\n return db().transaction(async (tx) => {\n await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${jobExecutionId}))`);\n\n const repo: AnnotationWriteRepository = {\n loadCurrentAnnotations: async (lockedJobExecutionId) => {\n const rows = await tx\n .select({\n id: annotations.id,\n context: annotations.context,\n style: annotations.style,\n body: annotations.body,\n bodyBytes: annotations.bodyBytes,\n sequence: annotations.sequence,\n })\n .from(annotations)\n .where(eq(annotations.jobExecutionId, lockedJobExecutionId));\n\n return new Map(rows.map((row) => [row.context, row]));\n },\n removeAnnotation: async (lockedJobExecutionId, context) => {\n await tx\n .delete(annotations)\n .where(\n and(\n eq(annotations.jobExecutionId, lockedJobExecutionId),\n eq(annotations.context, context),\n ),\n );\n },\n createAnnotation: async (params) => {\n const [row] = await tx.insert(annotations).values(params).returning({\n id: annotations.id,\n context: annotations.context,\n style: annotations.style,\n body: annotations.body,\n bodyBytes: annotations.bodyBytes,\n sequence: annotations.sequence,\n });\n\n if (!row) throw new Error('createAnnotation: insert returned no row');\n return row;\n },\n updateAnnotation: async (params) => {\n const [row] = await tx\n .update(annotations)\n .set({\n originStepId: params.originStepId,\n originStepAttempt: params.originStepAttempt,\n style: params.style,\n body: params.body,\n bodyBytes: params.bodyBytes,\n updatedAt: new Date(),\n })\n .where(eq(annotations.id, params.id))\n .returning({\n id: annotations.id,\n context: annotations.context,\n style: annotations.style,\n body: annotations.body,\n bodyBytes: annotations.bodyBytes,\n sequence: annotations.sequence,\n });\n\n if (!row) throw new Error('updateAnnotation: update returned no row');\n return row;\n },\n };\n\n return await work(repo);\n });\n}\n"],"names":["READ_ANNOTATIONS_MAX_LIMIT","and","asc","eq","gt","inArray","or","sql","db","annotations","toAnnotation","DEFAULT_ANNOTATIONS_READ_LIMIT","listAnnotationsForRunAttempt","params","workspaceIds","length","hasMore","nextCursor","limit","conditions","workflowRunId","workflowRunAttempt","workspaceId","jobExecutionId","push","after","cursorCondition","sequence","id","rows","select","from","where","orderBy","pageRows","slice","last","at","map","withAnnotationLock","work","transaction","tx","execute","repo","loadCurrentAnnotations","lockedJobExecutionId","context","style","body","bodyBytes","Map","row","removeAnnotation","delete","createAnnotation","insert","values","returning","Error","updateAnnotation","update","set","originStepId","originStepAttempt","updatedAt","Date"],"mappings":"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"}