@pipeline-builder/api-core 0.0.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 (184) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +98 -0
  3. package/lib/constants/ai-providers.d.ts +52 -0
  4. package/lib/constants/ai-providers.js +129 -0
  5. package/lib/constants/http-status.d.ts +20 -0
  6. package/lib/constants/http-status.js +26 -0
  7. package/lib/constants/index.d.ts +3 -0
  8. package/lib/constants/index.js +6 -0
  9. package/lib/constants/time.d.ts +10 -0
  10. package/lib/constants/time.js +13 -0
  11. package/lib/errors/app-errors.d.ts +30 -0
  12. package/lib/errors/app-errors.js +53 -0
  13. package/lib/errors/index.d.ts +1 -0
  14. package/lib/errors/index.js +4 -0
  15. package/lib/helpers/access-helpers.d.ts +63 -0
  16. package/lib/helpers/access-helpers.js +82 -0
  17. package/lib/helpers/crud-helpers.d.ts +9 -0
  18. package/lib/helpers/crud-helpers.js +23 -0
  19. package/lib/helpers/index.d.ts +6 -0
  20. package/lib/helpers/index.js +9 -0
  21. package/lib/helpers/org-hierarchy-http.d.ts +74 -0
  22. package/lib/helpers/org-hierarchy-http.js +144 -0
  23. package/lib/helpers/org-hierarchy.d.ts +57 -0
  24. package/lib/helpers/org-hierarchy.js +110 -0
  25. package/lib/helpers/restore-helpers.d.ts +49 -0
  26. package/lib/helpers/restore-helpers.js +78 -0
  27. package/lib/helpers/sse-helpers.d.ts +20 -0
  28. package/lib/helpers/sse-helpers.js +45 -0
  29. package/lib/index.d.ts +58 -0
  30. package/lib/index.js +71 -0
  31. package/lib/middleware/auth.d.ts +208 -0
  32. package/lib/middleware/auth.js +588 -0
  33. package/lib/middleware/index.d.ts +3 -0
  34. package/lib/middleware/index.js +6 -0
  35. package/lib/middleware/mongo-sanitize.d.ts +32 -0
  36. package/lib/middleware/mongo-sanitize.js +100 -0
  37. package/lib/middleware/step-up.d.ts +45 -0
  38. package/lib/middleware/step-up.js +138 -0
  39. package/lib/openapi/extend-zod.d.ts +1 -0
  40. package/lib/openapi/extend-zod.js +6 -0
  41. package/lib/openapi/index.d.ts +2 -0
  42. package/lib/openapi/index.js +5 -0
  43. package/lib/openapi/registry.d.ts +17 -0
  44. package/lib/openapi/registry.js +38 -0
  45. package/lib/openapi/routes/ask-routes.d.ts +7 -0
  46. package/lib/openapi/routes/ask-routes.js +62 -0
  47. package/lib/openapi/routes/billing-routes.d.ts +1 -0
  48. package/lib/openapi/routes/billing-routes.js +66 -0
  49. package/lib/openapi/routes/image-registry-routes.d.ts +7 -0
  50. package/lib/openapi/routes/image-registry-routes.js +98 -0
  51. package/lib/openapi/routes/index.d.ts +5 -0
  52. package/lib/openapi/routes/index.js +28 -0
  53. package/lib/openapi/routes/message-routes.d.ts +1 -0
  54. package/lib/openapi/routes/message-routes.js +105 -0
  55. package/lib/openapi/routes/pipeline-routes.d.ts +1 -0
  56. package/lib/openapi/routes/pipeline-routes.js +105 -0
  57. package/lib/openapi/routes/pipeline-template-routes.d.ts +1 -0
  58. package/lib/openapi/routes/pipeline-template-routes.js +61 -0
  59. package/lib/openapi/routes/plugin-routes.d.ts +1 -0
  60. package/lib/openapi/routes/plugin-routes.js +96 -0
  61. package/lib/openapi/routes/quota-routes.d.ts +1 -0
  62. package/lib/openapi/routes/quota-routes.js +62 -0
  63. package/lib/openapi/schema-registry.d.ts +25 -0
  64. package/lib/openapi/schema-registry.js +91 -0
  65. package/lib/routes/health.d.ts +41 -0
  66. package/lib/routes/health.js +82 -0
  67. package/lib/routes/index.d.ts +1 -0
  68. package/lib/routes/index.js +4 -0
  69. package/lib/services/audit-spool.d.ts +80 -0
  70. package/lib/services/audit-spool.js +172 -0
  71. package/lib/services/cache-service.d.ts +124 -0
  72. package/lib/services/cache-service.js +276 -0
  73. package/lib/services/circuit-breaker.d.ts +45 -0
  74. package/lib/services/circuit-breaker.js +138 -0
  75. package/lib/services/compliance-client.d.ts +46 -0
  76. package/lib/services/compliance-client.js +128 -0
  77. package/lib/services/compliance-event-subscriber.d.ts +16 -0
  78. package/lib/services/compliance-event-subscriber.js +72 -0
  79. package/lib/services/durable-event-bus.d.ts +96 -0
  80. package/lib/services/durable-event-bus.js +204 -0
  81. package/lib/services/entity-events.d.ts +48 -0
  82. package/lib/services/entity-events.js +79 -0
  83. package/lib/services/env-redis.d.ts +44 -0
  84. package/lib/services/env-redis.js +94 -0
  85. package/lib/services/http-client.d.ts +143 -0
  86. package/lib/services/http-client.js +335 -0
  87. package/lib/services/index.d.ts +18 -0
  88. package/lib/services/index.js +20 -0
  89. package/lib/services/leader-lock.d.ts +45 -0
  90. package/lib/services/leader-lock.js +85 -0
  91. package/lib/services/quota.d.ts +130 -0
  92. package/lib/services/quota.js +264 -0
  93. package/lib/services/remote-audit-client.d.ts +160 -0
  94. package/lib/services/remote-audit-client.js +390 -0
  95. package/lib/services/retry-strategy.d.ts +74 -0
  96. package/lib/services/retry-strategy.js +118 -0
  97. package/lib/services/scheduler.d.ts +41 -0
  98. package/lib/services/scheduler.js +86 -0
  99. package/lib/services/service-boot.d.ts +20 -0
  100. package/lib/services/service-boot.js +28 -0
  101. package/lib/services/sse-ticket-store.d.ts +51 -0
  102. package/lib/services/sse-ticket-store.js +130 -0
  103. package/lib/services/token-revocation.d.ts +52 -0
  104. package/lib/services/token-revocation.js +120 -0
  105. package/lib/testing/tier-mock.d.ts +44 -0
  106. package/lib/testing/tier-mock.js +65 -0
  107. package/lib/types/audit-events.d.ts +69 -0
  108. package/lib/types/audit-events.js +4 -0
  109. package/lib/types/catalog-metadata.d.ts +36 -0
  110. package/lib/types/catalog-metadata.js +21 -0
  111. package/lib/types/common.d.ts +255 -0
  112. package/lib/types/common.js +41 -0
  113. package/lib/types/error-codes.d.ts +54 -0
  114. package/lib/types/error-codes.js +107 -0
  115. package/lib/types/feature-flags.d.ts +36 -0
  116. package/lib/types/feature-flags.js +128 -0
  117. package/lib/types/http.d.ts +27 -0
  118. package/lib/types/http.js +4 -0
  119. package/lib/types/index.d.ts +11 -0
  120. package/lib/types/index.js +14 -0
  121. package/lib/types/permissions.d.ts +65 -0
  122. package/lib/types/permissions.js +131 -0
  123. package/lib/types/pipeline-template.d.ts +30 -0
  124. package/lib/types/pipeline-template.js +13 -0
  125. package/lib/types/pipeline.d.ts +59 -0
  126. package/lib/types/pipeline.js +31 -0
  127. package/lib/types/quota-tiers.d.ts +79 -0
  128. package/lib/types/quota-tiers.js +221 -0
  129. package/lib/types/visibility.d.ts +20 -0
  130. package/lib/types/visibility.js +22 -0
  131. package/lib/utils/alias-resolver.d.ts +37 -0
  132. package/lib/utils/alias-resolver.js +68 -0
  133. package/lib/utils/audit.d.ts +15 -0
  134. package/lib/utils/audit.js +39 -0
  135. package/lib/utils/aws-scrub.d.ts +11 -0
  136. package/lib/utils/aws-scrub.js +67 -0
  137. package/lib/utils/compliance-attributes.d.ts +6 -0
  138. package/lib/utils/compliance-attributes.js +69 -0
  139. package/lib/utils/concurrency.d.ts +9 -0
  140. package/lib/utils/concurrency.js +29 -0
  141. package/lib/utils/crash-handlers.d.ts +18 -0
  142. package/lib/utils/crash-handlers.js +36 -0
  143. package/lib/utils/env.d.ts +28 -0
  144. package/lib/utils/env.js +54 -0
  145. package/lib/utils/headers.d.ts +18 -0
  146. package/lib/utils/headers.js +21 -0
  147. package/lib/utils/identity.d.ts +40 -0
  148. package/lib/utils/identity.js +51 -0
  149. package/lib/utils/index.d.ts +17 -0
  150. package/lib/utils/index.js +20 -0
  151. package/lib/utils/logger.d.ts +38 -0
  152. package/lib/utils/logger.js +174 -0
  153. package/lib/utils/metric-emitter.d.ts +36 -0
  154. package/lib/utils/metric-emitter.js +32 -0
  155. package/lib/utils/object.d.ts +13 -0
  156. package/lib/utils/object.js +18 -0
  157. package/lib/utils/params.d.ts +150 -0
  158. package/lib/utils/params.js +206 -0
  159. package/lib/utils/response.d.ts +117 -0
  160. package/lib/utils/response.js +223 -0
  161. package/lib/utils/safe-require.d.ts +13 -0
  162. package/lib/utils/safe-require.js +19 -0
  163. package/lib/utils/secret-encryption.d.ts +176 -0
  164. package/lib/utils/secret-encryption.js +374 -0
  165. package/lib/utils/ssrf.d.ts +30 -0
  166. package/lib/utils/ssrf.js +106 -0
  167. package/lib/validation/ai-schemas.d.ts +56 -0
  168. package/lib/validation/ai-schemas.js +78 -0
  169. package/lib/validation/common-schemas.d.ts +133 -0
  170. package/lib/validation/common-schemas.js +94 -0
  171. package/lib/validation/index.d.ts +7 -0
  172. package/lib/validation/index.js +10 -0
  173. package/lib/validation/message-schemas.d.ts +160 -0
  174. package/lib/validation/message-schemas.js +156 -0
  175. package/lib/validation/middleware.d.ts +72 -0
  176. package/lib/validation/middleware.js +65 -0
  177. package/lib/validation/pipeline-schemas.d.ts +181 -0
  178. package/lib/validation/pipeline-schemas.js +101 -0
  179. package/lib/validation/pipeline-template-schemas.d.ts +155 -0
  180. package/lib/validation/pipeline-template-schemas.js +66 -0
  181. package/lib/validation/plugin-schemas.d.ts +171 -0
  182. package/lib/validation/plugin-schemas.js +81 -0
  183. package/openapi.yaml +292 -0
  184. package/package.json +167 -0
@@ -0,0 +1,110 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Org → team hierarchy traversal (org-team-hierarchy proposal).
5
+ *
6
+ * A "team" is an organization whose `parentOrgId` points at another org; a root
7
+ * org has `parentOrgId = null`. These helpers walk that single self-referential
8
+ * column, but hold **no database dependency** of their own: each takes a query
9
+ * callback so every service (platform, quota, …) can share the cycle-safe,
10
+ * depth-capped traversal logic while reading its own Mongoose model with its own
11
+ * `_id` casting rules.
12
+ *
13
+ * - {@link resolveOrgLineageWith} — UP: direct parent + root of the chain.
14
+ * - {@link resolveRootOrgIdWith} — UP: just the root id.
15
+ * - {@link isAncestorOrgWith} — UP: is A an ancestor of B?
16
+ * - {@link expandOrgScopeWith} — DOWN: self + all descendant org ids.
17
+ *
18
+ * Every org is flat today (`parentOrgId` null on all rows), so lineage resolves
19
+ * to `{ rootOrgId: self }` and scope to `[self]` — i.e. these are no-ops until
20
+ * orgs get parents.
21
+ */
22
+ /** Hard ceiling on ancestry/descendant traversal — cycle + abuse guard. */
23
+ export const MAX_ORG_DEPTH = 16;
24
+ /** Normalize a Mixed/ObjectId/string org id to a non-empty string, or undefined. */
25
+ export function toOrgIdString(v) {
26
+ if (v === null || v === undefined)
27
+ return undefined;
28
+ const s = String(v);
29
+ return s && s !== 'null' && s !== 'undefined' ? s : undefined;
30
+ }
31
+ /**
32
+ * Walk `parentOrgId` from `orgId` up to the root. Returns the direct parent (if
33
+ * any) and the root org id (the input itself when the org has no parent). On a
34
+ * detected cycle, a missing org, or the depth cap, traversal stops and the last
35
+ * reached id is treated as the root.
36
+ */
37
+ export async function resolveOrgLineageWith(orgId, getParent) {
38
+ const seen = new Set([orgId]);
39
+ let currentId = orgId;
40
+ let parentOrgId;
41
+ for (let depth = 0; depth < MAX_ORG_DEPTH; depth++) {
42
+ const parent = toOrgIdString(await getParent(currentId));
43
+ if (!parent)
44
+ break; // currentId is the root
45
+ // Record the direct parent only when it isn't a self-loop, so a malformed
46
+ // A→A org isn't reported as its own parent.
47
+ if (depth === 0 && parent !== orgId)
48
+ parentOrgId = parent;
49
+ if (seen.has(parent))
50
+ break; // cycle — treat currentId as the root
51
+ seen.add(parent);
52
+ currentId = parent;
53
+ }
54
+ return { rootOrgId: currentId, ...(parentOrgId && { parentOrgId }) };
55
+ }
56
+ /** Walk `parentOrgId` up to the root and return just the root id. */
57
+ export async function resolveRootOrgIdWith(orgId, getParent) {
58
+ return (await resolveOrgLineageWith(orgId, getParent)).rootOrgId;
59
+ }
60
+ /**
61
+ * True when `ancestorOrgId` is an ancestor of `candidateOrgId` — i.e.
62
+ * `candidateOrgId` lives somewhere in the subtree rooted at `ancestorOrgId`.
63
+ * Walks `candidateOrgId`'s parent chain upward, depth-capped and cycle-safe.
64
+ * Returns `false` when the two are equal (an org is not its own ancestor) or
65
+ * unrelated.
66
+ */
67
+ export async function isAncestorOrgWith(ancestorOrgId, candidateOrgId, getParent) {
68
+ if (ancestorOrgId === candidateOrgId)
69
+ return false;
70
+ const seen = new Set([candidateOrgId]);
71
+ let currentId = candidateOrgId;
72
+ for (let depth = 0; depth < MAX_ORG_DEPTH; depth++) {
73
+ const parent = toOrgIdString(await getParent(currentId));
74
+ if (!parent)
75
+ return false; // reached a root without matching
76
+ if (parent === ancestorOrgId)
77
+ return true;
78
+ if (seen.has(parent))
79
+ return false; // cycle
80
+ seen.add(parent);
81
+ currentId = parent;
82
+ }
83
+ return false;
84
+ }
85
+ /**
86
+ * Expand `orgId` to itself plus every descendant org id (breadth-first over
87
+ * `parentOrgId`). This is the scope a parent org "sees" for visibility and
88
+ * analytics rollups: `[self, ...descendants]`. Returns `[orgId]` when the org
89
+ * has no children. Cycle-safe and depth-capped.
90
+ */
91
+ export async function expandOrgScopeWith(orgId, getChildren) {
92
+ const result = [orgId];
93
+ const seen = new Set([orgId]);
94
+ let frontier = [orgId];
95
+ for (let depth = 0; depth < MAX_ORG_DEPTH && frontier.length > 0; depth++) {
96
+ const children = await getChildren(frontier);
97
+ const next = [];
98
+ for (const raw of children) {
99
+ const id = toOrgIdString(raw);
100
+ if (!id || seen.has(id))
101
+ continue;
102
+ seen.add(id);
103
+ result.push(id);
104
+ next.push(id);
105
+ }
106
+ frontier = next;
107
+ }
108
+ return result;
109
+ }
110
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3JnLWhpZXJhcmNoeS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9oZWxwZXJzL29yZy1oaWVyYXJjaHkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0Qzs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBa0JHO0FBRUgsMkVBQTJFO0FBQzNFLE1BQU0sQ0FBQyxNQUFNLGFBQWEsR0FBRyxFQUFFLENBQUM7QUFRaEMsb0ZBQW9GO0FBQ3BGLE1BQU0sVUFBVSxhQUFhLENBQUMsQ0FBVTtJQUN0QyxJQUFJLENBQUMsS0FBSyxJQUFJLElBQUksQ0FBQyxLQUFLLFNBQVM7UUFBRSxPQUFPLFNBQVMsQ0FBQztJQUNwRCxNQUFNLENBQUMsR0FBRyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDcEIsT0FBTyxDQUFDLElBQUksQ0FBQyxLQUFLLE1BQU0sSUFBSSxDQUFDLEtBQUssV0FBVyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQztBQUNoRSxDQUFDO0FBU0Q7Ozs7O0dBS0c7QUFDSCxNQUFNLENBQUMsS0FBSyxVQUFVLHFCQUFxQixDQUFDLEtBQWEsRUFBRSxTQUF5QjtJQUNsRixNQUFNLElBQUksR0FBRyxJQUFJLEdBQUcsQ0FBUyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7SUFDdEMsSUFBSSxTQUFTLEdBQUcsS0FBSyxDQUFDO0lBQ3RCLElBQUksV0FBK0IsQ0FBQztJQUVwQyxLQUFLLElBQUksS0FBSyxHQUFHLENBQUMsRUFBRSxLQUFLLEdBQUcsYUFBYSxFQUFFLEtBQUssRUFBRSxFQUFFLENBQUM7UUFDbkQsTUFBTSxNQUFNLEdBQUcsYUFBYSxDQUFDLE1BQU0sU0FBUyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUM7UUFDekQsSUFBSSxDQUFDLE1BQU07WUFBRSxNQUFNLENBQUMsd0JBQXdCO1FBQzVDLDBFQUEwRTtRQUMxRSw0Q0FBNEM7UUFDNUMs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QUM7SUFDbEIsQ0FBQztJQUVELE9BQU8sTUFBTSxDQUFDO0FBQ2hCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBPcmcg4oaSIHRlYW0gaGllcmFyY2h5IHRyYXZlcnNhbCAob3JnLXRlYW0taGllcmFyY2h5IHByb3Bvc2FsKS5cbiAqXG4gKiBBIFwidGVhbVwiIGlzIGFuIG9yZ2FuaXphdGlvbiB3aG9zZSBgcGFyZW50T3JnSWRgIHBvaW50cyBhdCBhbm90aGVyIG9yZzsgYSByb290XG4gKiBvcmcgaGFzIGBwYXJlbnRPcmdJZCA9IG51bGxgLiBUaGVzZSBoZWxwZXJzIHdhbGsgdGhhdCBzaW5nbGUgc2VsZi1yZWZlcmVudGlhbFxuICogY29sdW1uLCBidXQgaG9sZCAqKm5vIGRhdGFiYXNlIGRlcGVuZGVuY3kqKiBvZiB0aGVpciBvd246IGVhY2ggdGFrZXMgYSBxdWVyeVxuICogY2FsbGJhY2sgc28gZXZlcnkgc2VydmljZSAocGxhdGZvcm0sIHF1b3RhLCDigKYpIGNhbiBzaGFyZSB0aGUgY3ljbGUtc2FmZSxcbiAqIGRlcHRoLWNhcHBlZCB0cmF2ZXJzYWwgbG9naWMgd2hpbGUgcmVhZGluZyBpdHMgb3duIE1vbmdvb3NlIG1vZGVsIHdpdGggaXRzIG93blxuICogYF9pZGAgY2FzdGluZyBydWxlcy5cbiAqXG4gKiAgIC0ge0BsaW5rIHJlc29sdmVPcmdMaW5lYWdlV2l0aH0g4oCUIFVQOiBkaXJlY3QgcGFyZW50ICsgcm9vdCBvZiB0aGUgY2hhaW4uXG4gKiAgIC0ge0BsaW5rIHJlc29sdmVSb290T3JnSWRXaXRofSAgIOKAlCBVUDoganVzdCB0aGUgcm9vdCBpZC5cbiAqICAgLSB7QGxpbmsgaXNBbmNlc3Rvck9yZ1dpdGh9ICAgICAg4oCUIFVQOiBpcyBBIGFuIGFuY2VzdG9yIG9mIEI/XG4gKiAgIC0ge0BsaW5rIGV4cGFuZE9yZ1Njb3BlV2l0aH0gICAgIOKAlCBET1dOOiBzZWxmICsgYWxsIGRlc2NlbmRhbnQgb3JnIGlkcy5cbiAqXG4gKiBFdmVyeSBvcmcgaXMgZmxhdCB0b2RheSAoYHBhcmVudE9yZ0lkYCBudWxsIG9uIGFsbCByb3dzKSwgc28gbGluZWFnZSByZXNvbHZlc1xuICogdG8gYHsgcm9vdE9yZ0lkOiBzZWxmIH1gIGFuZCBzY29wZSB0byBgW3NlbGZdYCDigJQgaS5lLiB0aGVzZSBhcmUgbm8tb3BzIHVudGlsXG4gKiBvcmdzIGdldCBwYXJlbnRzLlxuICovXG5cbi8qKiBIYXJkIGNlaWxpbmcgb24gYW5jZXN0cnkvZGVzY2VuZGFudCB0cmF2ZXJzYWwg4oCUIGN5Y2xlICsgYWJ1c2UgZ3VhcmQuICovXG5leHBvcnQgY29uc3QgTUFYX09SR19ERVBUSCA9IDE2O1xuXG4vKiogRmV0Y2ggYW4gb3JnJ3MgZGlyZWN0IGBwYXJlbnRPcmdJZGAsIG9yIHVuZGVmaW5lZCBmb3IgYSByb290L21pc3Npbmcgb3JnLiAqL1xuZXhwb3J0IHR5cGUgR2V0UGFyZW50T3JnSWQgPSAob3JnSWQ6IHN0cmluZykgPT4gUHJvbWlzZTxzdHJpbmcgfCB1bmRlZmluZWQ+O1xuXG4vKiogRmV0Y2ggdGhlIGRpcmVjdCBjaGlsZCBvcmcgaWRzIG9mIGV2ZXJ5IG9yZyBpbiBgZnJvbnRpZXJgIChvbmUgcm91bmQgb2YgQkZTKS4gKi9cbmV4cG9ydCB0eXBlIEdldENoaWxkT3JnSWRzID0gKGZyb250aWVyOiBzdHJpbmdbXSkgPT4gUHJvbWlzZTxzdHJpbmdbXT47XG5cbi8qKiBOb3JtYWxpemUgYSBNaXhlZC9PYmplY3RJZC9zdHJpbmcgb3JnIGlkIHRvIGEgbm9uLWVtcHR5IHN0cmluZywgb3IgdW5kZWZpbmVkLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHRvT3JnSWRTdHJpbmcodjogdW5rbm93bik6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG4gIGlmICh2ID09PSBudWxsIHx8IHYgPT09IHVuZGVmaW5lZCkgcmV0dXJuIHVuZGVmaW5lZDtcbiAgY29uc3QgcyA9IFN0cmluZyh2KTtcbiAgcmV0dXJuIHMgJiYgcyAhPT0gJ251bGwnICYmIHMgIT09ICd1bmRlZmluZWQnID8gcyA6IHVuZGVmaW5lZDtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBPcmdMaW5lYWdlIHtcbiAgLyoqIFRoZSBvcmcncyBkaXJlY3QgcGFyZW50IGlkLCBvciBgdW5kZWZpbmVkYCB3aGVuIGl0J3MgYSByb290IG9yZy4gKi9cbiAgcGFyZW50T3JnSWQ/OiBzdHJpbmc7XG4gIC8qKiBUb3Agb2YgdGhlIGFuY2VzdHJ5IGNoYWluLiBFcXVhbHMgdGhlIGlucHV0IGBvcmdJZGAgZm9yIGEgcm9vdCBvcmcuICovXG4gIHJvb3RPcmdJZDogc3RyaW5nO1xufVxuXG4vKipcbiAqIFdhbGsgYHBhcmVudE9yZ0lkYCBmcm9tIGBvcmdJZGAgdXAgdG8gdGhlIHJvb3QuIFJldHVybnMgdGhlIGRpcmVjdCBwYXJlbnQgKGlmXG4gKiBhbnkpIGFuZCB0aGUgcm9vdCBvcmcgaWQgKHRoZSBpbnB1dCBpdHNlbGYgd2hlbiB0aGUgb3JnIGhhcyBubyBwYXJlbnQpLiBPbiBhXG4gKiBkZXRlY3RlZCBjeWNsZSwgYSBtaXNzaW5nIG9yZywgb3IgdGhlIGRlcHRoIGNhcCwgdHJhdmVyc2FsIHN0b3BzIGFuZCB0aGUgbGFzdFxuICogcmVhY2hlZCBpZCBpcyB0cmVhdGVkIGFzIHRoZSByb290LlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gcmVzb2x2ZU9yZ0xpbmVhZ2VXaXRoKG9yZ0lkOiBzdHJpbmcsIGdldFBhcmVudDogR2V0UGFyZW50T3JnSWQpOiBQcm9taXNlPE9yZ0xpbmVhZ2U+IHtcbiAgY29uc3Qgc2VlbiA9IG5ldyBTZXQ8c3RyaW5nPihbb3JnSWRdKTtcbiAgbGV0IGN1cnJlbnRJZCA9IG9yZ0lkO1xuICBsZXQgcGFyZW50T3JnSWQ6IHN0cmluZyB8IHVuZGVmaW5lZDtcblxuICBmb3IgKGxldCBkZXB0aCA9IDA7IGRlcHRoIDwgTUFYX09SR19ERVBUSDsgZGVwdGgrKykge1xuICAgIGNvbnN0IHBhcmVudCA9IHRvT3JnSWRTdHJpbmcoYXdhaXQgZ2V0UGFyZW50KGN1cnJlbnRJZCkpO1xuICAgIGlmICghcGFyZW50KSBicmVhazsgLy8gY3VycmVudElkIGlzIHRoZSByb290XG4gICAgLy8gUmVjb3JkIHRoZSBkaXJlY3QgcGFyZW50IG9ubHkgd2hlbiBpdCBpc24ndCBhIHNlbGYtbG9vcCwgc28gYSBtYWxmb3JtZWRcbiAgICAvLyBB4oaSQSBvcmcgaXNuJ3QgcmVwb3J0ZWQgYXMgaXRzIG93biBwYXJlbnQuXG4gICAgaWYgKGRlcHRoID09PSAwICYmIHBhcmVudCAhPT0gb3JnSWQpIHBhcmVudE9yZ0lkID0gcGFyZW50O1xuICAgIGlmIChzZWVuLmhhcyhwYXJlbnQpKSBicmVhazsgLy8gY3ljbGUg4oCUIHRyZWF0IGN1cnJlbnRJZCBhcyB0aGUgcm9vdFxuICAgIHNlZW4uYWRkKHBhcmVudCk7XG4gICAgY3VycmVudElkID0gcGFyZW50O1xuICB9XG5cbiAgcmV0dXJuIHsgcm9vdE9yZ0lkOiBjdXJyZW50SWQsIC4uLihwYXJlbnRPcmdJZCAmJiB7IHBhcmVudE9yZ0lkIH0pIH07XG59XG5cbi8qKiBXYWxrIGBwYXJlbnRPcmdJZGAgdXAgdG8gdGhlIHJvb3QgYW5kIHJldHVybiBqdXN0IHRoZSByb290IGlkLiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHJlc29sdmVSb290T3JnSWRXaXRoKG9yZ0lkOiBzdHJpbmcsIGdldFBhcmVudDogR2V0UGFyZW50T3JnSWQpOiBQcm9taXNlPHN0cmluZz4ge1xuICByZXR1cm4gKGF3YWl0IHJlc29sdmVPcmdMaW5lYWdlV2l0aChvcmdJZCwgZ2V0UGFyZW50KSkucm9vdE9yZ0lkO1xufVxuXG4vKipcbiAqIFRydWUgd2hlbiBgYW5jZXN0b3JPcmdJZGAgaXMgYW4gYW5jZXN0b3Igb2YgYGNhbmRpZGF0ZU9yZ0lkYCDigJQgaS5lLlxuICogYGNhbmRpZGF0ZU9yZ0lkYCBsaXZlcyBzb21ld2hlcmUgaW4gdGhlIHN1YnRyZWUgcm9vdGVkIGF0IGBhbmNlc3Rvck9yZ0lkYC5cbiAqIFdhbGtzIGBjYW5kaWRhdGVPcmdJZGAncyBwYXJlbnQgY2hhaW4gdXB3YXJkLCBkZXB0aC1jYXBwZWQgYW5kIGN5Y2xlLXNhZmUuXG4gKiBSZXR1cm5zIGBmYWxzZWAgd2hlbiB0aGUgdHdvIGFyZSBlcXVhbCAoYW4gb3JnIGlzIG5vdCBpdHMgb3duIGFuY2VzdG9yKSBvclxuICogdW5yZWxhdGVkLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gaXNBbmNlc3Rvck9yZ1dpdGgoXG4gIGFuY2VzdG9yT3JnSWQ6IHN0cmluZyxcbiAgY2FuZGlkYXRlT3JnSWQ6IHN0cmluZyxcbiAgZ2V0UGFyZW50OiBHZXRQYXJlbnRPcmdJZCxcbik6IFByb21pc2U8Ym9vbGVhbj4ge1xuICBpZiAoYW5jZXN0b3JPcmdJZCA9PT0gY2FuZGlkYXRlT3JnSWQpIHJldHVybiBmYWxzZTtcblxuICBjb25zdCBzZWVuID0gbmV3IFNldDxzdHJpbmc+KFtjYW5kaWRhdGVPcmdJZF0pO1xuICBsZXQgY3VycmVudElkID0gY2FuZGlkYXRlT3JnSWQ7XG5cbiAgZm9yIChsZXQgZGVwdGggPSAwOyBkZXB0aCA8IE1BWF9PUkdfREVQVEg7IGRlcHRoKyspIHtcbiAgICBjb25zdCBwYXJlbnQgPSB0b09yZ0lkU3RyaW5nKGF3YWl0IGdldFBhcmVudChjdXJyZW50SWQpKTtcbiAgICBpZiAoIXBhcmVudCkgcmV0dXJuIGZhbHNlOyAvLyByZWFjaGVkIGEgcm9vdCB3aXRob3V0IG1hdGNoaW5nXG4gICAgaWYgKHBhcmVudCA9PT0gYW5jZXN0b3JPcmdJZCkgcmV0dXJuIHRydWU7XG4gICAgaWYgKHNlZW4uaGFzKHBhcmVudCkpIHJldHVybiBmYWxzZTsgLy8gY3ljbGVcbiAgICBzZWVuLmFkZChwYXJlbnQpO1xuICAgIGN1cnJlbnRJZCA9IHBhcmVudDtcbiAgfVxuXG4gIHJldHVybiBmYWxzZTtcbn1cblxuLyoqXG4gKiBFeHBhbmQgYG9yZ0lkYCB0byBpdHNlbGYgcGx1cyBldmVyeSBkZXNjZW5kYW50IG9yZyBpZCAoYnJlYWR0aC1maXJzdCBvdmVyXG4gKiBgcGFyZW50T3JnSWRgKS4gVGhpcyBpcyB0aGUgc2NvcGUgYSBwYXJlbnQgb3JnIFwic2Vlc1wiIGZvciB2aXNpYmlsaXR5IGFuZFxuICogYW5hbHl0aWNzIHJvbGx1cHM6IGBbc2VsZiwgLi4uZGVzY2VuZGFudHNdYC4gUmV0dXJucyBgW29yZ0lkXWAgd2hlbiB0aGUgb3JnXG4gKiBoYXMgbm8gY2hpbGRyZW4uIEN5Y2xlLXNhZmUgYW5kIGRlcHRoLWNhcHBlZC5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGV4cGFuZE9yZ1Njb3BlV2l0aChvcmdJZDogc3RyaW5nLCBnZXRDaGlsZHJlbjogR2V0Q2hpbGRPcmdJZHMpOiBQcm9taXNlPHN0cmluZ1tdPiB7XG4gIGNvbnN0IHJlc3VsdDogc3RyaW5nW10gPSBbb3JnSWRdO1xuICBjb25zdCBzZWVuID0gbmV3IFNldDxzdHJpbmc+KFtvcmdJZF0pO1xuICBsZXQgZnJvbnRpZXI6IHN0cmluZ1tdID0gW29yZ0lkXTtcblxuICBmb3IgKGxldCBkZXB0aCA9IDA7IGRlcHRoIDwgTUFYX09SR19ERVBUSCAmJiBmcm9udGllci5sZW5ndGggPiAwOyBkZXB0aCsrKSB7XG4gICAgY29uc3QgY2hpbGRyZW4gPSBhd2FpdCBnZXRDaGlsZHJlbihmcm9udGllcik7XG4gICAgY29uc3QgbmV4dDogc3RyaW5nW10gPSBbXTtcbiAgICBmb3IgKGNvbnN0IHJhdyBvZiBjaGlsZHJlbikge1xuICAgICAgY29uc3QgaWQgPSB0b09yZ0lkU3RyaW5nKHJhdyk7XG4gICAgICBpZiAoIWlkIHx8IHNlZW4uaGFzKGlkKSkgY29udGludWU7XG4gICAgICBzZWVuLmFkZChpZCk7XG4gICAgICByZXN1bHQucHVzaChpZCk7XG4gICAgICBuZXh0LnB1c2goaWQpO1xuICAgIH1cbiAgICBmcm9udGllciA9IG5leHQ7XG4gIH1cblxuICByZXR1cm4gcmVzdWx0O1xufVxuIl19
@@ -0,0 +1,49 @@
1
+ import type { Request, Response } from 'express';
2
+ import type { Permission } from '../types/permissions.js';
3
+ /** Minimal shape a restore route needs from the entity it restores. */
4
+ export interface RestorableEntity {
5
+ orgId: string;
6
+ /** Rung the tombstone sat at — drives the restore/purge authority check. */
7
+ visibility?: string;
8
+ /** Author — the only non-admin who may restore/purge a `private` row. */
9
+ createdBy?: string;
10
+ }
11
+ /** The slice of a service a restore route uses (satisfied by any CrudService). */
12
+ export interface RestorableService<T extends RestorableEntity> {
13
+ findDeletedById(id: string, orgId?: string): Promise<T | null>;
14
+ restore(id: string, orgId: string, userId: string): Promise<T | null>;
15
+ }
16
+ /**
17
+ * Shared skeleton for a step-up-gated entity restore route: extract `:id`, load
18
+ * the own-org tombstone, gate it on the visibility ladder (parity with delete),
19
+ * restore, and 404 on miss. Returns `{ existing, restored }` on success,
20
+ * or `null` when it has ALREADY sent a response (400/404/403) — the caller then
21
+ * does the entity-specific audit + response. DRYs the near-identical
22
+ * pipeline / plugin / pipeline_template restore routes (which differ only in
23
+ * service, audit action, and response shaping).
24
+ */
25
+ export declare function loadAndRestore<T extends RestorableEntity>(req: Request, res: Response, orgId: string, userId: string, service: RestorableService<T>, label: string, publishPermission: Permission): Promise<{
26
+ existing: T;
27
+ restored: T;
28
+ } | null>;
29
+ /** The slice of a service a purge route uses (satisfied by any CrudService). */
30
+ export interface PurgeableService<T extends RestorableEntity> {
31
+ findDeletedById(id: string, orgId?: string): Promise<T | null>;
32
+ purgeById(id: string, orgId?: string): Promise<string | null>;
33
+ }
34
+ /**
35
+ * Shared skeleton for an entity PURGE route: extract `:id`, load the own-org
36
+ * tombstone, gate it on the visibility ladder (parity with
37
+ * delete/restore), then permanently hard-delete it — but ONLY when it is a
38
+ * genuine tombstone (a live/active row loads as `null` here and 404s, so purge
39
+ * can never hard-delete a live row). Returns `{ existing, purgedId }` on success
40
+ * (the caller emits the entity-specific audit off `existing`), or `null` when it
41
+ * has ALREADY sent a response (400/404/403). Purge is intentionally NOT step-up
42
+ * gated: it only finalizes what the retention sweep would remove anyway, and the
43
+ * frontend gates it with an explicit confirm dialog. DRYs the near-identical
44
+ * pipeline / pipeline_template purge routes.
45
+ */
46
+ export declare function loadAndPurge<T extends RestorableEntity>(req: Request, res: Response, orgId: string, service: PurgeableService<T>, label: string, publishPermission: Permission, userId: string): Promise<{
47
+ existing: T;
48
+ purgedId: string;
49
+ } | null>;
@@ -0,0 +1,78 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { requireVisibilityWriteAccess } from './access-helpers.js';
4
+ import { sendEntityNotFound } from './crud-helpers.js';
5
+ import { ErrorCode } from '../types/error-codes.js';
6
+ import { getParam } from '../utils/params.js';
7
+ import { sendBadRequest } from '../utils/response.js';
8
+ /**
9
+ * Shared skeleton for a step-up-gated entity restore route: extract `:id`, load
10
+ * the own-org tombstone, gate it on the visibility ladder (parity with delete),
11
+ * restore, and 404 on miss. Returns `{ existing, restored }` on success,
12
+ * or `null` when it has ALREADY sent a response (400/404/403) — the caller then
13
+ * does the entity-specific audit + response. DRYs the near-identical
14
+ * pipeline / plugin / pipeline_template restore routes (which differ only in
15
+ * service, audit action, and response shaping).
16
+ */
17
+ export async function loadAndRestore(req, res, orgId, userId, service, label, publishPermission) {
18
+ const id = getParam(req.params, 'id');
19
+ if (!id) {
20
+ sendBadRequest(res, `${label} ID is required.`, ErrorCode.MISSING_REQUIRED_FIELD);
21
+ return null;
22
+ }
23
+ // Load the TOMBSTONE (own-org). A live row / unknown id / already-purged → 404.
24
+ const existing = await service.findDeletedById(id, orgId);
25
+ if (!existing) {
26
+ sendEntityNotFound(res, label);
27
+ return null;
28
+ }
29
+ // Same ladder authority as delete: `:publish` for a public row, authorship for
30
+ // a private one. Load-bearing here — the tombstone load is org-scoped, not
31
+ // visibility-scoped, so this is what stops a colleague resurrecting a draft.
32
+ if (!requireVisibilityWriteAccess(req, res, existing, userId || '', publishPermission))
33
+ return null;
34
+ const restored = await service.restore(id, orgId, userId || 'system');
35
+ if (!restored) {
36
+ sendEntityNotFound(res, label);
37
+ return null;
38
+ }
39
+ return { existing, restored };
40
+ }
41
+ /**
42
+ * Shared skeleton for an entity PURGE route: extract `:id`, load the own-org
43
+ * tombstone, gate it on the visibility ladder (parity with
44
+ * delete/restore), then permanently hard-delete it — but ONLY when it is a
45
+ * genuine tombstone (a live/active row loads as `null` here and 404s, so purge
46
+ * can never hard-delete a live row). Returns `{ existing, purgedId }` on success
47
+ * (the caller emits the entity-specific audit off `existing`), or `null` when it
48
+ * has ALREADY sent a response (400/404/403). Purge is intentionally NOT step-up
49
+ * gated: it only finalizes what the retention sweep would remove anyway, and the
50
+ * frontend gates it with an explicit confirm dialog. DRYs the near-identical
51
+ * pipeline / pipeline_template purge routes.
52
+ */
53
+ export async function loadAndPurge(req, res, orgId, service, label, publishPermission, userId) {
54
+ const id = getParam(req.params, 'id');
55
+ if (!id) {
56
+ sendBadRequest(res, `${label} ID is required.`, ErrorCode.MISSING_REQUIRED_FIELD);
57
+ return null;
58
+ }
59
+ // Load the TOMBSTONE (own-org). A live row / unknown id / already-purged → 404,
60
+ // so a currently-active entity is never hard-deleted through this path.
61
+ const existing = await service.findDeletedById(id, orgId);
62
+ if (!existing) {
63
+ sendEntityNotFound(res, label);
64
+ return null;
65
+ }
66
+ // Same ladder authority as delete/restore (see loadAndRestore).
67
+ if (!requireVisibilityWriteAccess(req, res, existing, userId || '', publishPermission))
68
+ return null;
69
+ // Reuse the retention sweep's single-id hard-delete (same tombstone match +
70
+ // dependent-teardown hooks). Returns null on a race (already purged) → 404.
71
+ const purgedId = await service.purgeById(id, orgId);
72
+ if (!purgedId) {
73
+ sendEntityNotFound(res, label);
74
+ return null;
75
+ }
76
+ return { existing, purgedId };
77
+ }
78
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzdG9yZS1oZWxwZXJzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2hlbHBlcnMvcmVzdG9yZS1oZWxwZXJzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFHdEMsT0FBTyxFQUFFLDRCQUE0QixFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDbkUsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDdkQsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBRXBELE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUM5QyxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFpQnREOzs7Ozs7OztHQVFHO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSxjQUFjLENBQ2xDLEdBQVksRUFDWixHQUFhLEVBQ2IsS0FBYSxFQUNiLE1BQWMsRUFDZCxPQUE2QixFQUM3QixLQUFhLEVBQ2IsaUJBQTZCO0lBRTdCLE1BQU0sRUFBRSxHQUFHLFFBQVEsQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxDQUFDO0lBQ3RDLElBQUksQ0FBQyxFQUFFLEVBQUUsQ0FBQztRQUNSLGNBQWMsQ0FBQyxHQUFHLEVBQUUsR0FBRyxLQUFLLGtCQUFrQixFQUFFLFNBQVMsQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDO1FBQ2xGLE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVELGdGQUFnRjtJQUNoRixNQUFNLFFBQVEsR0FBRyxNQUFNLE9BQU8sQ0FBQyxlQUFlLENBQUMsRUFBRSxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQzFELElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUNkLGtCQUFrQixDQUFDLEdBQUcsRUFBRSxLQUFLLENBQUMsQ0FBQztRQUMvQixPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7SUFFRCwrRUFBK0U7SUFDL0UsMkVBQTJFO0lBQzNFLDZFQUE2RTtJQUM3RSxJQUFJLENBQUMsNEJBQTRCLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxRQUFRLEVBQUUsTUFBTSxJQUFJLEVBQUUsRUFBRSxpQkFBaUIsQ0FBQztRQUFFLE9BQU8sSUFBSSxDQUFDO0lBRXBHLE1BQU0sUUFBUSxHQUFHLE1BQU0sT0FBTyxDQUFDLE9BQU8sQ0FBQyxFQUFFLEVBQUUsS0FBSyxFQUFFLE1BQU0sSUFBSSxRQUFRLENBQUMsQ0FBQztJQUN0RSxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7UUFDZCxrQkFBa0IsQ0FBQyxHQUFHLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDL0IsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBRUQsT0FBTyxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUUsQ0FBQztBQUNoQyxDQUFDO0FBUUQ7Ozs7Ozs7Ozs7O0dBV0c7QUFDSCxNQUFNLENBQUMsS0FBSyxVQUFVLFlBQVksQ0FDaEMsR0FBWSxFQUNaLEdBQWEsRUFDYixLQUFhLEVBQ2IsT0FBNEIsRUFDNUIsS0FBYSxFQUNiLGlCQUE2QixFQUM3QixNQUFjO0lBRWQsTUFBTSxFQUFFLEdBQUcsUUFBUSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDdEMsSUFBSSxDQUFDLEVBQUUsRUFBRSxDQUFDO1FBQ1IsY0FBYyxDQUFDLEdBQUcsRUFBRSxHQUFHLEtBQUssa0JBQWtCLEVBQUUsU0FBUyxDQUFDLHNCQUFzQixDQUFDLENBQUM7UUFDbEYsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBRUQsZ0ZBQWdGO0lBQ2hGLHdFQUF3RTtJQUN4RSxNQUFNLFFBQVEsR0FBRyxNQUFNLE9BQU8sQ0FBQyxlQUFlLENBQUMsRUFBRSxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQzFELElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUNkLGtCQUFrQixDQUFDLEdBQUcsRUFBRSxLQUFLLENBQUMsQ0FBQztRQUMvQixPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7SUFFRCxnRUFBZ0U7SUFDaEUsSUFBSSxDQUFDLDRCQUE0QixDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsUUFBUSxFQUFFLE1BQU0sSUFBSSxFQUFFLEVBQUUsaUJBQWlCLENBQUM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUVwRyw0RUFBNEU7SUFDNUUsNEVBQTRFO0lBQzVFLE1BQU0sUUFBUSxHQUFHLE1BQU0sT0FBTyxDQUFDLFNBQVMsQ0FBQyxFQUFFLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFDcEQsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBQ2Qsa0JBQWtCLENBQUMsR0FBRyxFQUFFLEtBQUssQ0FBQyxDQUFDO1FBQy9CLE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVELE9BQU8sRUFBRSxRQUFRLEVBQUUsUUFBUSxFQUFFLENBQUM7QUFDaEMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgdHlwZSB7IFJlcXVlc3QsIFJlc3BvbnNlIH0gZnJvbSAnZXhwcmVzcyc7XG5pbXBvcnQgeyByZXF1aXJlVmlzaWJpbGl0eVdyaXRlQWNjZXNzIH0gZnJvbSAnLi9hY2Nlc3MtaGVscGVycy5qcyc7XG5pbXBvcnQgeyBzZW5kRW50aXR5Tm90Rm91bmQgfSBmcm9tICcuL2NydWQtaGVscGVycy5qcyc7XG5pbXBvcnQgeyBFcnJvckNvZGUgfSBmcm9tICcuLi90eXBlcy9lcnJvci1jb2Rlcy5qcyc7XG5pbXBvcnQgdHlwZSB7IFBlcm1pc3Npb24gfSBmcm9tICcuLi90eXBlcy9wZXJtaXNzaW9ucy5qcyc7XG5pbXBvcnQgeyBnZXRQYXJhbSB9IGZyb20gJy4uL3V0aWxzL3BhcmFtcy5qcyc7XG5pbXBvcnQgeyBzZW5kQmFkUmVxdWVzdCB9IGZyb20gJy4uL3V0aWxzL3Jlc3BvbnNlLmpzJztcblxuLyoqIE1pbmltYWwgc2hhcGUgYSByZXN0b3JlIHJvdXRlIG5lZWRzIGZyb20gdGhlIGVudGl0eSBpdCByZXN0b3Jlcy4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUmVzdG9yYWJsZUVudGl0eSB7XG4gIG9yZ0lkOiBzdHJpbmc7XG4gIC8qKiBSdW5nIHRoZSB0b21ic3RvbmUgc2F0IGF0IOKAlCBkcml2ZXMgdGhlIHJlc3RvcmUvcHVyZ2UgYXV0aG9yaXR5IGNoZWNrLiAqL1xuICB2aXNpYmlsaXR5Pzogc3RyaW5nO1xuICAvKiogQXV0aG9yIOKAlCB0aGUgb25seSBub24tYWRtaW4gd2hvIG1heSByZXN0b3JlL3B1cmdlIGEgYHByaXZhdGVgIHJvdy4gKi9cbiAgY3JlYXRlZEJ5Pzogc3RyaW5nO1xufVxuXG4vKiogVGhlIHNsaWNlIG9mIGEgc2VydmljZSBhIHJlc3RvcmUgcm91dGUgdXNlcyAoc2F0aXNmaWVkIGJ5IGFueSBDcnVkU2VydmljZSkuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlc3RvcmFibGVTZXJ2aWNlPFQgZXh0ZW5kcyBSZXN0b3JhYmxlRW50aXR5PiB7XG4gIGZpbmREZWxldGVkQnlJZChpZDogc3RyaW5nLCBvcmdJZD86IHN0cmluZyk6IFByb21pc2U8VCB8IG51bGw+O1xuICByZXN0b3JlKGlkOiBzdHJpbmcsIG9yZ0lkOiBzdHJpbmcsIHVzZXJJZDogc3RyaW5nKTogUHJvbWlzZTxUIHwgbnVsbD47XG59XG5cbi8qKlxuICogU2hhcmVkIHNrZWxldG9uIGZvciBhIHN0ZXAtdXAtZ2F0ZWQgZW50aXR5IHJlc3RvcmUgcm91dGU6IGV4dHJhY3QgYDppZGAsIGxvYWRcbiAqIHRoZSBvd24tb3JnIHRvbWJzdG9uZSwgZ2F0ZSBpdCBvbiB0aGUgdmlzaWJpbGl0eSBsYWRkZXIgKHBhcml0eSB3aXRoIGRlbGV0ZSksXG4gKiByZXN0b3JlLCBhbmQgNDA0IG9uIG1pc3MuIFJldHVybnMgYHsgZXhpc3RpbmcsIHJlc3RvcmVkIH1gIG9uIHN1Y2Nlc3MsXG4gKiBvciBgbnVsbGAgd2hlbiBpdCBoYXMgQUxSRUFEWSBzZW50IGEgcmVzcG9uc2UgKDQwMC80MDQvNDAzKSDigJQgdGhlIGNhbGxlciB0aGVuXG4gKiBkb2VzIHRoZSBlbnRpdHktc3BlY2lmaWMgYXVkaXQgKyByZXNwb25zZS4gRFJZcyB0aGUgbmVhci1pZGVudGljYWxcbiAqIHBpcGVsaW5lIC8gcGx1Z2luIC8gcGlwZWxpbmVfdGVtcGxhdGUgcmVzdG9yZSByb3V0ZXMgKHdoaWNoIGRpZmZlciBvbmx5IGluXG4gKiBzZXJ2aWNlLCBhdWRpdCBhY3Rpb24sIGFuZCByZXNwb25zZSBzaGFwaW5nKS5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGxvYWRBbmRSZXN0b3JlPFQgZXh0ZW5kcyBSZXN0b3JhYmxlRW50aXR5PihcbiAgcmVxOiBSZXF1ZXN0LFxuICByZXM6IFJlc3BvbnNlLFxuICBvcmdJZDogc3RyaW5nLFxuICB1c2VySWQ6IHN0cmluZyxcbiAgc2VydmljZTogUmVzdG9yYWJsZVNlcnZpY2U8VD4sXG4gIGxhYmVsOiBzdHJpbmcsXG4gIHB1Ymxpc2hQZXJtaXNzaW9uOiBQZXJtaXNzaW9uLFxuKTogUHJvbWlzZTx7IGV4aXN0aW5nOiBUOyByZXN0b3JlZDogVCB9IHwgbnVsbD4ge1xuICBjb25zdCBpZCA9IGdldFBhcmFtKHJlcS5wYXJhbXMsICdpZCcpO1xuICBpZiAoIWlkKSB7XG4gICAgc2VuZEJhZFJlcXVlc3QocmVzLCBgJHtsYWJlbH0gSUQgaXMgcmVxdWlyZWQuYCwgRXJyb3JDb2RlLk1JU1NJTkdfUkVRVUlSRURfRklFTEQpO1xuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgLy8gTG9hZCB0aGUgVE9NQlNUT05FIChvd24tb3JnKS4gQSBsaXZlIHJvdyAvIHVua25vd24gaWQgLyBhbHJlYWR5LXB1cmdlZCDihpIgNDA0LlxuICBjb25zdCBleGlzdGluZyA9IGF3YWl0IHNlcnZpY2UuZmluZERlbGV0ZWRCeUlkKGlkLCBvcmdJZCk7XG4gIGlmICghZXhpc3RpbmcpIHtcbiAgICBzZW5kRW50aXR5Tm90Rm91bmQocmVzLCBsYWJlbCk7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICAvLyBTYW1lIGxhZGRlciBhdXRob3JpdHkgYXMgZGVsZXRlOiBgOnB1Ymxpc2hgIGZvciBhIHB1YmxpYyByb3csIGF1dGhvcnNoaXAgZm9yXG4gIC8vIGEgcHJpdmF0ZSBvbmUuIExvYWQtYmVhcmluZyBoZXJlIOKAlCB0aGUgdG9tYnN0b25lIGxvYWQgaXMgb3JnLXNjb3BlZCwgbm90XG4gIC8vIHZpc2liaWxpdHktc2NvcGVkLCBzbyB0aGlzIGlzIHdoYXQgc3RvcHMgYSBjb2xsZWFndWUgcmVzdXJyZWN0aW5nIGEgZHJhZnQuXG4gIGlmICghcmVxdWlyZVZpc2liaWxpdHlXcml0ZUFjY2VzcyhyZXEsIHJlcywgZXhpc3RpbmcsIHVzZXJJZCB8fCAnJywgcHVibGlzaFBlcm1pc3Npb24pKSByZXR1cm4gbnVsbDtcblxuICBjb25zdCByZXN0b3JlZCA9IGF3YWl0IHNlcnZpY2UucmVzdG9yZShpZCwgb3JnSWQsIHVzZXJJZCB8fCAnc3lzdGVtJyk7XG4gIGlmICghcmVzdG9yZWQpIHtcbiAgICBzZW5kRW50aXR5Tm90Rm91bmQocmVzLCBsYWJlbCk7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICByZXR1cm4geyBleGlzdGluZywgcmVzdG9yZWQgfTtcbn1cblxuLyoqIFRoZSBzbGljZSBvZiBhIHNlcnZpY2UgYSBwdXJnZSByb3V0ZSB1c2VzIChzYXRpc2ZpZWQgYnkgYW55IENydWRTZXJ2aWNlKS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUHVyZ2VhYmxlU2VydmljZTxUIGV4dGVuZHMgUmVzdG9yYWJsZUVudGl0eT4ge1xuICBmaW5kRGVsZXRlZEJ5SWQoaWQ6IHN0cmluZywgb3JnSWQ/OiBzdHJpbmcpOiBQcm9taXNlPFQgfCBudWxsPjtcbiAgcHVyZ2VCeUlkKGlkOiBzdHJpbmcsIG9yZ0lkPzogc3RyaW5nKTogUHJvbWlzZTxzdHJpbmcgfCBudWxsPjtcbn1cblxuLyoqXG4gKiBTaGFyZWQgc2tlbGV0b24gZm9yIGFuIGVudGl0eSBQVVJHRSByb3V0ZTogZXh0cmFjdCBgOmlkYCwgbG9hZCB0aGUgb3duLW9yZ1xuICogdG9tYnN0b25lLCBnYXRlIGl0IG9uIHRoZSB2aXNpYmlsaXR5IGxhZGRlciAocGFyaXR5IHdpdGhcbiAqIGRlbGV0ZS9yZXN0b3JlKSwgdGhlbiBwZXJtYW5lbnRseSBoYXJkLWRlbGV0ZSBpdCDigJQgYnV0IE9OTFkgd2hlbiBpdCBpcyBhXG4gKiBnZW51aW5lIHRvbWJzdG9uZSAoYSBsaXZlL2FjdGl2ZSByb3cgbG9hZHMgYXMgYG51bGxgIGhlcmUgYW5kIDQwNHMsIHNvIHB1cmdlXG4gKiBjYW4gbmV2ZXIgaGFyZC1kZWxldGUgYSBsaXZlIHJvdykuIFJldHVybnMgYHsgZXhpc3RpbmcsIHB1cmdlZElkIH1gIG9uIHN1Y2Nlc3NcbiAqICh0aGUgY2FsbGVyIGVtaXRzIHRoZSBlbnRpdHktc3BlY2lmaWMgYXVkaXQgb2ZmIGBleGlzdGluZ2ApLCBvciBgbnVsbGAgd2hlbiBpdFxuICogaGFzIEFMUkVBRFkgc2VudCBhIHJlc3BvbnNlICg0MDAvNDA0LzQwMykuIFB1cmdlIGlzIGludGVudGlvbmFsbHkgTk9UIHN0ZXAtdXBcbiAqIGdhdGVkOiBpdCBvbmx5IGZpbmFsaXplcyB3aGF0IHRoZSByZXRlbnRpb24gc3dlZXAgd291bGQgcmVtb3ZlIGFueXdheSwgYW5kIHRoZVxuICogZnJvbnRlbmQgZ2F0ZXMgaXQgd2l0aCBhbiBleHBsaWNpdCBjb25maXJtIGRpYWxvZy4gRFJZcyB0aGUgbmVhci1pZGVudGljYWxcbiAqIHBpcGVsaW5lIC8gcGlwZWxpbmVfdGVtcGxhdGUgcHVyZ2Ugcm91dGVzLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gbG9hZEFuZFB1cmdlPFQgZXh0ZW5kcyBSZXN0b3JhYmxlRW50aXR5PihcbiAgcmVxOiBSZXF1ZXN0LFxuICByZXM6IFJlc3BvbnNlLFxuICBvcmdJZDogc3RyaW5nLFxuICBzZXJ2aWNlOiBQdXJnZWFibGVTZXJ2aWNlPFQ+LFxuICBsYWJlbDogc3RyaW5nLFxuICBwdWJsaXNoUGVybWlzc2lvbjogUGVybWlzc2lvbixcbiAgdXNlcklkOiBzdHJpbmcsXG4pOiBQcm9taXNlPHsgZXhpc3Rpbmc6IFQ7IHB1cmdlZElkOiBzdHJpbmcgfSB8IG51bGw+IHtcbiAgY29uc3QgaWQgPSBnZXRQYXJhbShyZXEucGFyYW1zLCAnaWQnKTtcbiAgaWYgKCFpZCkge1xuICAgIHNlbmRCYWRSZXF1ZXN0KHJlcywgYCR7bGFiZWx9IElEIGlzIHJlcXVpcmVkLmAsIEVycm9yQ29kZS5NSVNTSU5HX1JFUVVJUkVEX0ZJRUxEKTtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuXG4gIC8vIExvYWQgdGhlIFRPTUJTVE9ORSAob3duLW9yZykuIEEgbGl2ZSByb3cgLyB1bmtub3duIGlkIC8gYWxyZWFkeS1wdXJnZWQg4oaSIDQwNCxcbiAgLy8gc28gYSBjdXJyZW50bHktYWN0aXZlIGVudGl0eSBpcyBuZXZlciBoYXJkLWRlbGV0ZWQgdGhyb3VnaCB0aGlzIHBhdGguXG4gIGNvbnN0IGV4aXN0aW5nID0gYXdhaXQgc2VydmljZS5maW5kRGVsZXRlZEJ5SWQoaWQsIG9yZ0lkKTtcbiAgaWYgKCFleGlzdGluZykge1xuICAgIHNlbmRFbnRpdHlOb3RGb3VuZChyZXMsIGxhYmVsKTtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuXG4gIC8vIFNhbWUgbGFkZGVyIGF1dGhvcml0eSBhcyBkZWxldGUvcmVzdG9yZSAoc2VlIGxvYWRBbmRSZXN0b3JlKS5cbiAgaWYgKCFyZXF1aXJlVmlzaWJpbGl0eVdyaXRlQWNjZXNzKHJlcSwgcmVzLCBleGlzdGluZywgdXNlcklkIHx8ICcnLCBwdWJsaXNoUGVybWlzc2lvbikpIHJldHVybiBudWxsO1xuXG4gIC8vIFJldXNlIHRoZSByZXRlbnRpb24gc3dlZXAncyBzaW5nbGUtaWQgaGFyZC1kZWxldGUgKHNhbWUgdG9tYnN0b25lIG1hdGNoICtcbiAgLy8gZGVwZW5kZW50LXRlYXJkb3duIGhvb2tzKS4gUmV0dXJucyBudWxsIG9uIGEgcmFjZSAoYWxyZWFkeSBwdXJnZWQpIOKGkiA0MDQuXG4gIGNvbnN0IHB1cmdlZElkID0gYXdhaXQgc2VydmljZS5wdXJnZUJ5SWQoaWQsIG9yZ0lkKTtcbiAgaWYgKCFwdXJnZWRJZCkge1xuICAgIHNlbmRFbnRpdHlOb3RGb3VuZChyZXMsIGxhYmVsKTtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuXG4gIHJldHVybiB7IGV4aXN0aW5nLCBwdXJnZWRJZCB9O1xufVxuIl19
@@ -0,0 +1,20 @@
1
+ import type { Request, Response } from 'express';
2
+ /**
3
+ * Set the four standard SSE response headers (does NOT flush — the caller flushes
4
+ * after any additional per-endpoint setup like `res.setTimeout`). The single
5
+ * source of truth for the `text/event-stream` + no-cache + keep-alive +
6
+ * `X-Accel-Buffering: no` (disable nginx buffering) block.
7
+ */
8
+ export declare function writeSseHeaders(res: Response): void;
9
+ /**
10
+ * Set SSE response headers and flush.
11
+ * Returns an `aborted()` function to check if the client disconnected.
12
+ */
13
+ export declare function initSSEStream(req: Request, res: Response, timeoutMs: number): {
14
+ aborted: () => boolean;
15
+ };
16
+ /**
17
+ * Classify AI generation errors and send the appropriate HTTP response or SSE event.
18
+ * If headers are already sent (streaming), writes an SSE error event and ends the response.
19
+ */
20
+ export declare function handleAIError(res: Response, message: string, fallbackMessage: string): void;
@@ -0,0 +1,45 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { sendBadRequest, sendInternalError } from '../utils/response.js';
4
+ /**
5
+ * Set the four standard SSE response headers (does NOT flush — the caller flushes
6
+ * after any additional per-endpoint setup like `res.setTimeout`). The single
7
+ * source of truth for the `text/event-stream` + no-cache + keep-alive +
8
+ * `X-Accel-Buffering: no` (disable nginx buffering) block.
9
+ */
10
+ export function writeSseHeaders(res) {
11
+ res.setHeader('Content-Type', 'text/event-stream');
12
+ res.setHeader('Cache-Control', 'no-cache');
13
+ res.setHeader('Connection', 'keep-alive');
14
+ res.setHeader('X-Accel-Buffering', 'no');
15
+ }
16
+ /**
17
+ * Set SSE response headers and flush.
18
+ * Returns an `aborted()` function to check if the client disconnected.
19
+ */
20
+ export function initSSEStream(req, res, timeoutMs) {
21
+ writeSseHeaders(res);
22
+ res.setTimeout(timeoutMs);
23
+ res.flushHeaders();
24
+ let _aborted = false;
25
+ req.on('close', () => { _aborted = true; });
26
+ return { aborted: () => _aborted };
27
+ }
28
+ /**
29
+ * Classify AI generation errors and send the appropriate HTTP response or SSE event.
30
+ * If headers are already sent (streaming), writes an SSE error event and ends the response.
31
+ */
32
+ export function handleAIError(res, message, fallbackMessage) {
33
+ if (!res.headersSent) {
34
+ if (message.includes('not configured') || message.includes('API key')) {
35
+ return sendInternalError(res, 'AI generation is not configured for the requested provider');
36
+ }
37
+ if (message.includes('not available for provider')) {
38
+ return sendBadRequest(res, message);
39
+ }
40
+ return sendInternalError(res, fallbackMessage, { details: message });
41
+ }
42
+ res.write(`data: ${JSON.stringify({ type: 'error', message })}\n\n`);
43
+ res.end();
44
+ }
45
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3NlLWhlbHBlcnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaGVscGVycy9zc2UtaGVscGVycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBR3RDLE9BQU8sRUFBRSxjQUFjLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUV6RTs7Ozs7R0FLRztBQUNILE1BQU0sVUFBVSxlQUFlLENBQUMsR0FBYTtJQUMzQyxHQUFHLENBQUMsU0FBUyxDQUFDLGNBQWMsRUFBRSxtQkFBbUIsQ0FBQyxDQUFDO0lBQ25ELEdBQUcsQ0FBQyxTQUFTLENBQUMsZUFBZSxFQUFFLFVBQVUsQ0FBQyxDQUFDO0lBQzNDLEdBQUcsQ0FBQyxTQUFTLENBQUMsWUFBWSxFQUFFLFlBQVksQ0FBQyxDQUFDO0lBQzFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsbUJBQW1CLEVBQUUsSUFBSSxDQUFDLENBQUM7QUFDM0MsQ0FBQztBQUVEOzs7R0FHRztBQUNILE1BQU0sVUFBVSxhQUFhLENBQUMsR0FBWSxFQUFFLEdBQWEsRUFBRSxTQUFpQjtJQUMxRSxlQUFlLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDckIsR0FBRyxDQUFDLFVBQVUsQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUMxQixHQUFHLENBQUMsWUFBWSxFQUFFLENBQUM7SUFDbkIsSUFBSSxRQUFRLEdBQUcsS0FBSyxDQUFDO0lBQ3JCLEdBQUcsQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLEdBQUcsRUFBRSxHQUFHLFFBQVEsR0FBRyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUM1QyxPQUFPLEVBQUUsT0FBTyxFQUFFLEdBQUcsRUFBRSxDQUFDLFFBQVEsRUFBRSxDQUFDO0FBQ3JDLENBQUM7QUFFRDs7O0dBR0c7QUFDSCxNQUFNLFVBQVUsYUFBYSxDQUFDLEdBQWEsRUFBRSxPQUFlLEVBQUUsZUFBdUI7SUFDbkYsSUFBSSxDQUFDLEdBQUcsQ0FBQyxXQUFXLEVBQUUsQ0FBQztRQUNyQixJQUFJLE9BQU8sQ0FBQyxRQUFRLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxPQUFPLENBQUMsUUFBUSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUM7WUFDdEUsT0FBTyxpQkFBaUIsQ0FBQyxHQUFHLEVBQUUsNERBQTRELENBQUMsQ0FBQztRQUM5RixDQUFDO1FBQ0QsSUFBSSxPQUFPLENBQUMsUUFBUSxDQUFDLDRCQUE0QixDQUFDLEVBQUUsQ0FBQztZQUNuRCxPQUFPLGNBQWMsQ0FBQyxHQUFHLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDdEMsQ0FBQztRQUNELE9BQU8saUJBQWlCLENBQUMsR0FBRyxFQUFFLGVBQWUsRUFBRSxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsQ0FBQyxDQUFDO0lBQ3ZFLENBQUM7SUFDRCxHQUFHLENBQUMsS0FBSyxDQUFDLFNBQVMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLElBQUksRUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDckUsR0FBRyxDQUFDLEdBQUcsRUFBRSxDQUFDO0FBQ1osQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgdHlwZSB7IFJlcXVlc3QsIFJlc3BvbnNlIH0gZnJvbSAnZXhwcmVzcyc7XG5pbXBvcnQgeyBzZW5kQmFkUmVxdWVzdCwgc2VuZEludGVybmFsRXJyb3IgfSBmcm9tICcuLi91dGlscy9yZXNwb25zZS5qcyc7XG5cbi8qKlxuICogU2V0IHRoZSBmb3VyIHN0YW5kYXJkIFNTRSByZXNwb25zZSBoZWFkZXJzIChkb2VzIE5PVCBmbHVzaCDigJQgdGhlIGNhbGxlciBmbHVzaGVzXG4gKiBhZnRlciBhbnkgYWRkaXRpb25hbCBwZXItZW5kcG9pbnQgc2V0dXAgbGlrZSBgcmVzLnNldFRpbWVvdXRgKS4gVGhlIHNpbmdsZVxuICogc291cmNlIG9mIHRydXRoIGZvciB0aGUgYHRleHQvZXZlbnQtc3RyZWFtYCArIG5vLWNhY2hlICsga2VlcC1hbGl2ZSArXG4gKiBgWC1BY2NlbC1CdWZmZXJpbmc6IG5vYCAoZGlzYWJsZSBuZ2lueCBidWZmZXJpbmcpIGJsb2NrLlxuICovXG5leHBvcnQgZnVuY3Rpb24gd3JpdGVTc2VIZWFkZXJzKHJlczogUmVzcG9uc2UpOiB2b2lkIHtcbiAgcmVzLnNldEhlYWRlcignQ29udGVudC1UeXBlJywgJ3RleHQvZXZlbnQtc3RyZWFtJyk7XG4gIHJlcy5zZXRIZWFkZXIoJ0NhY2hlLUNvbnRyb2wnLCAnbm8tY2FjaGUnKTtcbiAgcmVzLnNldEhlYWRlcignQ29ubmVjdGlvbicsICdrZWVwLWFsaXZlJyk7XG4gIHJlcy5zZXRIZWFkZXIoJ1gtQWNjZWwtQnVmZmVyaW5nJywgJ25vJyk7XG59XG5cbi8qKlxuICogU2V0IFNTRSByZXNwb25zZSBoZWFkZXJzIGFuZCBmbHVzaC5cbiAqIFJldHVybnMgYW4gYGFib3J0ZWQoKWAgZnVuY3Rpb24gdG8gY2hlY2sgaWYgdGhlIGNsaWVudCBkaXNjb25uZWN0ZWQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpbml0U1NFU3RyZWFtKHJlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSwgdGltZW91dE1zOiBudW1iZXIpOiB7IGFib3J0ZWQ6ICgpID0+IGJvb2xlYW4gfSB7XG4gIHdyaXRlU3NlSGVhZGVycyhyZXMpO1xuICByZXMuc2V0VGltZW91dCh0aW1lb3V0TXMpO1xuICByZXMuZmx1c2hIZWFkZXJzKCk7XG4gIGxldCBfYWJvcnRlZCA9IGZhbHNlO1xuICByZXEub24oJ2Nsb3NlJywgKCkgPT4geyBfYWJvcnRlZCA9IHRydWU7IH0pO1xuICByZXR1cm4geyBhYm9ydGVkOiAoKSA9PiBfYWJvcnRlZCB9O1xufVxuXG4vKipcbiAqIENsYXNzaWZ5IEFJIGdlbmVyYXRpb24gZXJyb3JzIGFuZCBzZW5kIHRoZSBhcHByb3ByaWF0ZSBIVFRQIHJlc3BvbnNlIG9yIFNTRSBldmVudC5cbiAqIElmIGhlYWRlcnMgYXJlIGFscmVhZHkgc2VudCAoc3RyZWFtaW5nKSwgd3JpdGVzIGFuIFNTRSBlcnJvciBldmVudCBhbmQgZW5kcyB0aGUgcmVzcG9uc2UuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBoYW5kbGVBSUVycm9yKHJlczogUmVzcG9uc2UsIG1lc3NhZ2U6IHN0cmluZywgZmFsbGJhY2tNZXNzYWdlOiBzdHJpbmcpOiB2b2lkIHtcbiAgaWYgKCFyZXMuaGVhZGVyc1NlbnQpIHtcbiAgICBpZiAobWVzc2FnZS5pbmNsdWRlcygnbm90IGNvbmZpZ3VyZWQnKSB8fCBtZXNzYWdlLmluY2x1ZGVzKCdBUEkga2V5JykpIHtcbiAgICAgIHJldHVybiBzZW5kSW50ZXJuYWxFcnJvcihyZXMsICdBSSBnZW5lcmF0aW9uIGlzIG5vdCBjb25maWd1cmVkIGZvciB0aGUgcmVxdWVzdGVkIHByb3ZpZGVyJyk7XG4gICAgfVxuICAgIGlmIChtZXNzYWdlLmluY2x1ZGVzKCdub3QgYXZhaWxhYmxlIGZvciBwcm92aWRlcicpKSB7XG4gICAgICByZXR1cm4gc2VuZEJhZFJlcXVlc3QocmVzLCBtZXNzYWdlKTtcbiAgICB9XG4gICAgcmV0dXJuIHNlbmRJbnRlcm5hbEVycm9yKHJlcywgZmFsbGJhY2tNZXNzYWdlLCB7IGRldGFpbHM6IG1lc3NhZ2UgfSk7XG4gIH1cbiAgcmVzLndyaXRlKGBkYXRhOiAke0pTT04uc3RyaW5naWZ5KHsgdHlwZTogJ2Vycm9yJywgbWVzc2FnZSB9KX1cXG5cXG5gKTtcbiAgcmVzLmVuZCgpO1xufVxuIl19
package/lib/index.d.ts ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * @module @pipeline-builder/api-core
3
+ *
4
+ * Core API utilities shared across all services.
5
+ *
6
+ * **Middleware**
7
+ * - requireAuth, requireSystemAdmin, requireFeature — JWT authentication
8
+ * - isSystemAdmin, isServicePrincipal — authorization helpers (isSystemOrgId remains as a content-owner check, not a privilege gate)
9
+ * - signServiceToken, getServiceAuthHeader — inter-service JWT minting
10
+ *
11
+ * **Types**
12
+ * - ErrorCode, ErrorCodeStatus — standardized error code enum and status mapping
13
+ * - RequestIdentity — parsed JWT identity
14
+ * - ServiceConfig, RequestOptions — HTTP client types
15
+ * - QuotaType, QuotaCheckResult — quota service types
16
+ * - PipelineType, ComputeType, Visibility — pipeline domain types
17
+ * - FeatureFlags — feature flag types
18
+ *
19
+ * **Utilities**
20
+ * - createLogger — Winston-based structured logger factory
21
+ * - sendSuccess, sendError, sendPaginatedNested, sendBadRequest, sendInternalError — HTTP response helpers
22
+ * - getParam, getOrgId — request parameter extraction
23
+ * - parseQueryInt, parseQueryString — query string parsing
24
+ * - getIdentity — identity extraction from requests
25
+ * - errorMessage — safe error-to-string conversion
26
+ *
27
+ * **Constants**
28
+ * - HTTP status codes, AI provider identifiers, time constants
29
+ *
30
+ * **Services**
31
+ * - InternalHttpClient, createSafeClient — internal service-to-service HTTP client
32
+ * - createQuotaService — quota enforcement client factory
33
+ * - CacheService — in-memory TTL cache
34
+ * - ComplianceClient — compliance service client
35
+ * - EntityEventEmitter — domain event pub/sub
36
+ *
37
+ * **Errors**
38
+ * - AppError, NotFoundError, ForbiddenError — typed HTTP error classes
39
+ *
40
+ * **Validation**
41
+ * - Zod-based request validation schemas and middleware
42
+ *
43
+ * **Routes**
44
+ * - Health check route factory
45
+ *
46
+ * **OpenAPI**
47
+ * - Schema registry and spec generation
48
+ */
49
+ export * from './types/index.js';
50
+ export * from './constants/index.js';
51
+ export * from './utils/index.js';
52
+ export * from './helpers/index.js';
53
+ export * from './services/index.js';
54
+ export * from './middleware/index.js';
55
+ export * from './routes/index.js';
56
+ export * from './errors/index.js';
57
+ export * from './validation/index.js';
58
+ export * from './openapi/index.js';
package/lib/index.js ADDED
@@ -0,0 +1,71 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * @module @pipeline-builder/api-core
5
+ *
6
+ * Core API utilities shared across all services.
7
+ *
8
+ * **Middleware**
9
+ * - requireAuth, requireSystemAdmin, requireFeature — JWT authentication
10
+ * - isSystemAdmin, isServicePrincipal — authorization helpers (isSystemOrgId remains as a content-owner check, not a privilege gate)
11
+ * - signServiceToken, getServiceAuthHeader — inter-service JWT minting
12
+ *
13
+ * **Types**
14
+ * - ErrorCode, ErrorCodeStatus — standardized error code enum and status mapping
15
+ * - RequestIdentity — parsed JWT identity
16
+ * - ServiceConfig, RequestOptions — HTTP client types
17
+ * - QuotaType, QuotaCheckResult — quota service types
18
+ * - PipelineType, ComputeType, Visibility — pipeline domain types
19
+ * - FeatureFlags — feature flag types
20
+ *
21
+ * **Utilities**
22
+ * - createLogger — Winston-based structured logger factory
23
+ * - sendSuccess, sendError, sendPaginatedNested, sendBadRequest, sendInternalError — HTTP response helpers
24
+ * - getParam, getOrgId — request parameter extraction
25
+ * - parseQueryInt, parseQueryString — query string parsing
26
+ * - getIdentity — identity extraction from requests
27
+ * - errorMessage — safe error-to-string conversion
28
+ *
29
+ * **Constants**
30
+ * - HTTP status codes, AI provider identifiers, time constants
31
+ *
32
+ * **Services**
33
+ * - InternalHttpClient, createSafeClient — internal service-to-service HTTP client
34
+ * - createQuotaService — quota enforcement client factory
35
+ * - CacheService — in-memory TTL cache
36
+ * - ComplianceClient — compliance service client
37
+ * - EntityEventEmitter — domain event pub/sub
38
+ *
39
+ * **Errors**
40
+ * - AppError, NotFoundError, ForbiddenError — typed HTTP error classes
41
+ *
42
+ * **Validation**
43
+ * - Zod-based request validation schemas and middleware
44
+ *
45
+ * **Routes**
46
+ * - Health check route factory
47
+ *
48
+ * **OpenAPI**
49
+ * - Schema registry and spec generation
50
+ */
51
+ // Types
52
+ export * from './types/index.js';
53
+ // Constants
54
+ export * from './constants/index.js';
55
+ // Utils
56
+ export * from './utils/index.js';
57
+ // Helpers
58
+ export * from './helpers/index.js';
59
+ // Services
60
+ export * from './services/index.js';
61
+ // Middleware
62
+ export * from './middleware/index.js';
63
+ // Routes
64
+ export * from './routes/index.js';
65
+ // Errors
66
+ export * from './errors/index.js';
67
+ // Validation
68
+ export * from './validation/index.js';
69
+ // OpenAPI
70
+ export * from './openapi/index.js';
71
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0Qzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0ErQ0c7QUFFSCxRQUFRO0FBQ1IsY0FBYyxrQkFBa0IsQ0FBQztBQUVqQyxZQUFZO0FBQ1osY0FBYyxzQkFBc0IsQ0FBQztBQUVyQyxRQUFRO0FBQ1IsY0FBYyxrQkFBa0IsQ0FBQztBQUVqQyxVQUFVO0FBQ1YsY0FBYyxvQkFBb0IsQ0FBQztBQUVuQyxXQUFXO0FBQ1gsY0FBYyxxQkFBcUIsQ0FBQztBQUVwQyxhQUFhO0FBQ2IsY0FBYyx1QkFBdUIsQ0FBQztBQUV0QyxTQUFTO0FBQ1QsY0FBYyxtQkFBbUIsQ0FBQztBQUVsQyxTQUFTO0FBQ1QsY0FBYyxtQkFBbUIsQ0FBQztBQUVsQyxhQUFhO0FBQ2IsY0FBYyx1QkFBdUIsQ0FBQztBQUV0QyxVQUFVO0FBQ1YsY0FBYyxvQkFBb0IsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG4vKipcbiAqIEBtb2R1bGUgQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmVcbiAqXG4gKiBDb3JlIEFQSSB1dGlsaXRpZXMgc2hhcmVkIGFjcm9zcyBhbGwgc2VydmljZXMuXG4gKlxuICogKipNaWRkbGV3YXJlKipcbiAqIC0gcmVxdWlyZUF1dGgsIHJlcXVpcmVTeXN0ZW1BZG1pbiwgcmVxdWlyZUZlYXR1cmUg4oCUIEpXVCBhdXRoZW50aWNhdGlvblxuICogLSBpc1N5c3RlbUFkbWluLCBpc1NlcnZpY2VQcmluY2lwYWwg4oCUIGF1dGhvcml6YXRpb24gaGVscGVycyAoaXNTeXN0ZW1PcmdJZCByZW1haW5zIGFzIGEgY29udGVudC1vd25lciBjaGVjaywgbm90IGEgcHJpdmlsZWdlIGdhdGUpXG4gKiAtIHNpZ25TZXJ2aWNlVG9rZW4sIGdldFNlcnZpY2VBdXRoSGVhZGVyIOKAlCBpbnRlci1zZXJ2aWNlIEpXVCBtaW50aW5nXG4gKlxuICogKipUeXBlcyoqXG4gKiAtIEVycm9yQ29kZSwgRXJyb3JDb2RlU3RhdHVzIOKAlCBzdGFuZGFyZGl6ZWQgZXJyb3IgY29kZSBlbnVtIGFuZCBzdGF0dXMgbWFwcGluZ1xuICogLSBSZXF1ZXN0SWRlbnRpdHkg4oCUIHBhcnNlZCBKV1QgaWRlbnRpdHlcbiAqIC0gU2VydmljZUNvbmZpZywgUmVxdWVzdE9wdGlvbnMg4oCUIEhUVFAgY2xpZW50IHR5cGVzXG4gKiAtIFF1b3RhVHlwZSwgUXVvdGFDaGVja1Jlc3VsdCDigJQgcXVvdGEgc2VydmljZSB0eXBlc1xuICogLSBQaXBlbGluZVR5cGUsIENvbXB1dGVUeXBlLCBWaXNpYmlsaXR5IOKAlCBwaXBlbGluZSBkb21haW4gdHlwZXNcbiAqIC0gRmVhdHVyZUZsYWdzIOKAlCBmZWF0dXJlIGZsYWcgdHlwZXNcbiAqXG4gKiAqKlV0aWxpdGllcyoqXG4gKiAtIGNyZWF0ZUxvZ2dlciDigJQgV2luc3Rvbi1iYXNlZCBzdHJ1Y3R1cmVkIGxvZ2dlciBmYWN0b3J5XG4gKiAtIHNlbmRTdWNjZXNzLCBzZW5kRXJyb3IsIHNlbmRQYWdpbmF0ZWROZXN0ZWQsIHNlbmRCYWRSZXF1ZXN0LCBzZW5kSW50ZXJuYWxFcnJvciDigJQgSFRUUCByZXNwb25zZSBoZWxwZXJzXG4gKiAtIGdldFBhcmFtLCBnZXRPcmdJZCDigJQgcmVxdWVzdCBwYXJhbWV0ZXIgZXh0cmFjdGlvblxuICogLSBwYXJzZVF1ZXJ5SW50LCBwYXJzZVF1ZXJ5U3RyaW5nIOKAlCBxdWVyeSBzdHJpbmcgcGFyc2luZ1xuICogLSBnZXRJZGVudGl0eSDigJQgaWRlbnRpdHkgZXh0cmFjdGlvbiBmcm9tIHJlcXVlc3RzXG4gKiAtIGVycm9yTWVzc2FnZSDigJQgc2FmZSBlcnJvci10by1zdHJpbmcgY29udmVyc2lvblxuICpcbiAqICoqQ29uc3RhbnRzKipcbiAqIC0gSFRUUCBzdGF0dXMgY29kZXMsIEFJIHByb3ZpZGVyIGlkZW50aWZpZXJzLCB0aW1lIGNvbnN0YW50c1xuICpcbiAqICoqU2VydmljZXMqKlxuICogLSBJbnRlcm5hbEh0dHBDbGllbnQsIGNyZWF0ZVNhZmVDbGllbnQg4oCUIGludGVybmFsIHNlcnZpY2UtdG8tc2VydmljZSBIVFRQIGNsaWVudFxuICogLSBjcmVhdGVRdW90YVNlcnZpY2Ug4oCUIHF1b3RhIGVuZm9yY2VtZW50IGNsaWVudCBmYWN0b3J5XG4gKiAtIENhY2hlU2VydmljZSDigJQgaW4tbWVtb3J5IFRUTCBjYWNoZVxuICogLSBDb21wbGlhbmNlQ2xpZW50IOKAlCBjb21wbGlhbmNlIHNlcnZpY2UgY2xpZW50XG4gKiAtIEVudGl0eUV2ZW50RW1pdHRlciDigJQgZG9tYWluIGV2ZW50IHB1Yi9zdWJcbiAqXG4gKiAqKkVycm9ycyoqXG4gKiAtIEFwcEVycm9yLCBOb3RGb3VuZEVycm9yLCBGb3JiaWRkZW5FcnJvciDigJQgdHlwZWQgSFRUUCBlcnJvciBjbGFzc2VzXG4gKlxuICogKipWYWxpZGF0aW9uKipcbiAqIC0gWm9kLWJhc2VkIHJlcXVlc3QgdmFsaWRhdGlvbiBzY2hlbWFzIGFuZCBtaWRkbGV3YXJlXG4gKlxuICogKipSb3V0ZXMqKlxuICogLSBIZWFsdGggY2hlY2sgcm91dGUgZmFjdG9yeVxuICpcbiAqICoqT3BlbkFQSSoqXG4gKiAtIFNjaGVtYSByZWdpc3RyeSBhbmQgc3BlYyBnZW5lcmF0aW9uXG4gKi9cblxuLy8gVHlwZXNcbmV4cG9ydCAqIGZyb20gJy4vdHlwZXMvaW5kZXguanMnO1xuXG4vLyBDb25zdGFudHNcbmV4cG9ydCAqIGZyb20gJy4vY29uc3RhbnRzL2luZGV4LmpzJztcblxuLy8gVXRpbHNcbmV4cG9ydCAqIGZyb20gJy4vdXRpbHMvaW5kZXguanMnO1xuXG4vLyBIZWxwZXJzXG5leHBvcnQgKiBmcm9tICcuL2hlbHBlcnMvaW5kZXguanMnO1xuXG4vLyBTZXJ2aWNlc1xuZXhwb3J0ICogZnJvbSAnLi9zZXJ2aWNlcy9pbmRleC5qcyc7XG5cbi8vIE1pZGRsZXdhcmVcbmV4cG9ydCAqIGZyb20gJy4vbWlkZGxld2FyZS9pbmRleC5qcyc7XG5cbi8vIFJvdXRlc1xuZXhwb3J0ICogZnJvbSAnLi9yb3V0ZXMvaW5kZXguanMnO1xuXG4vLyBFcnJvcnNcbmV4cG9ydCAqIGZyb20gJy4vZXJyb3JzL2luZGV4LmpzJztcblxuLy8gVmFsaWRhdGlvblxuZXhwb3J0ICogZnJvbSAnLi92YWxpZGF0aW9uL2luZGV4LmpzJztcblxuLy8gT3BlbkFQSVxuZXhwb3J0ICogZnJvbSAnLi9vcGVuYXBpL2luZGV4LmpzJztcbiJdfQ==