@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,160 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Message type schema
4
+ */
5
+ export declare const MessageTypeSchema: z.ZodEnum<{
6
+ announcement: "announcement";
7
+ conversation: "conversation";
8
+ }>;
9
+ /**
10
+ * Message priority schema
11
+ */
12
+ export declare const MessagePrioritySchema: z.ZodEnum<{
13
+ high: "high";
14
+ normal: "normal";
15
+ urgent: "urgent";
16
+ }>;
17
+ /**
18
+ * Channel/inbox-bucket schema. Open-ended string up to 50 chars so we
19
+ * can add new channels (support, help, billing, …) without a schema
20
+ * migration. Constrained to a-z/0-9/dash/underscore for URL safety and
21
+ * to keep filter conditions trivially indexable.
22
+ */
23
+ export declare const MessageChannelSchema: z.ZodString;
24
+ /**
25
+ * Message filter schema for query parameters.
26
+ *
27
+ * Convention for `threadId`:
28
+ * - Omitted (undefined): no filter applied — returns messages regardless of thread.
29
+ * - `'root'` or `null`: filter for root messages only (translated to `threadId IS NULL`
30
+ * by the route layer / query builder). The string sentinel `'root'` is the
31
+ * wire form, since URL query params can't carry a true `null`.
32
+ * - A UUID: filter for messages in that specific thread.
33
+ *
34
+ * `isRead` is honored by `buildMessageConditions` in pipeline-data via a
35
+ * `jsonb_exists(read_by, $orgId)` check on `messages.read_by` for the requesting
36
+ * org (the function form, not the `?` operator, to avoid param-binding clashes).
37
+ */
38
+ export declare const MessageFilterSchema: z.ZodObject<{
39
+ id: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>, z.ZodString]>>;
40
+ visibility: z.ZodOptional<z.ZodEnum<{
41
+ org: "org";
42
+ private: "private";
43
+ public: "public";
44
+ }>>;
45
+ isActive: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodPipe<z.ZodEnum<{
46
+ false: "false";
47
+ true: "true";
48
+ }>, z.ZodTransform<boolean, "false" | "true">>, z.ZodPipe<z.ZodString, z.ZodTransform<boolean, string>>]>>;
49
+ isDefault: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodPipe<z.ZodEnum<{
50
+ false: "false";
51
+ true: "true";
52
+ }>, z.ZodTransform<boolean, "false" | "true">>, z.ZodPipe<z.ZodString, z.ZodTransform<boolean, string>>]>>;
53
+ ownerId: z.ZodOptional<z.ZodString>;
54
+ lifecycle: z.ZodOptional<z.ZodEnum<{
55
+ deprecated: "deprecated";
56
+ experimental: "experimental";
57
+ production: "production";
58
+ }>>;
59
+ threadId: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<"root">, z.ZodNull]>>;
60
+ recipientOrgId: z.ZodOptional<z.ZodString>;
61
+ messageType: z.ZodOptional<z.ZodEnum<{
62
+ announcement: "announcement";
63
+ conversation: "conversation";
64
+ }>>;
65
+ isRead: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodPipe<z.ZodEnum<{
66
+ false: "false";
67
+ true: "true";
68
+ }>, z.ZodTransform<boolean, "false" | "true">>, z.ZodPipe<z.ZodString, z.ZodTransform<boolean, string>>]>>;
69
+ priority: z.ZodOptional<z.ZodEnum<{
70
+ high: "high";
71
+ normal: "normal";
72
+ urgent: "urgent";
73
+ }>>;
74
+ channel: z.ZodOptional<z.ZodString>;
75
+ search: z.ZodOptional<z.ZodString>;
76
+ }, z.core.$strip>;
77
+ /**
78
+ * Upper bounds on free-text message fields. Without these an unbounded
79
+ * subject/content (e.g. a 1MB announcement) is persisted AND SSE-broadcast to
80
+ * every org. `subject` matches the `varchar(500)` DB column so we reject rather
81
+ * than silently truncate; `content` is a `text` column with no DB bound, so we
82
+ * cap it at a sane 32 KiB at the app layer.
83
+ */
84
+ export declare const MESSAGE_SUBJECT_MAX = 500;
85
+ export declare const MESSAGE_CONTENT_MAX = 32768;
86
+ /** Max attachments linkable to a single message/reply. */
87
+ export declare const MESSAGE_MAX_ATTACHMENTS = 5;
88
+ /**
89
+ * Max attachment size (MiB) — env-overridable. The upload route enforces this
90
+ * (multer `fileSize`); kept here so the bound lives with the other message
91
+ * limits rather than only inline in the route.
92
+ */
93
+ export declare const MESSAGE_ATTACHMENT_MAX_MB: number;
94
+ export declare const MESSAGE_ATTACHMENT_MAX_BYTES: number;
95
+ /**
96
+ * Allow-list of attachment MIME types — common images + documents + zip/yaml/json.
97
+ * Deliberately excludes executables/scripts/HTML/SVG (stored-XSS / drive-by).
98
+ * zip is a download container (doesn't render in-browser); yaml/json are plain
99
+ * text — none carry the drive-by risk of the excluded types. Extend here.
100
+ */
101
+ export declare const MESSAGE_ATTACHMENT_ALLOWED_MIME: ReadonlySet<string>;
102
+ /**
103
+ * Safe filename extensions. Browsers frequently send `application/octet-stream`
104
+ * for archives + config files (.tar/.gz/.tgz/.yaml/…) because they have no
105
+ * reliable registered MIME type, so the MIME allow-list alone rejects them. These
106
+ * extensions are accepted as a fallback ONLY when the reported MIME is that
107
+ * generic type (see isAllowedAttachmentType) — so a known render/execute MIME
108
+ * (image/svg+xml, text/html, …) is never rescued by a spoofed extension. Still
109
+ * excludes .html/.svg/.js/.exe/… (never listed here).
110
+ */
111
+ export declare const MESSAGE_ATTACHMENT_ALLOWED_EXT: ReadonlySet<string>;
112
+ /**
113
+ * True if an attachment is accepted. Accepts by MIME allow-list, OR — when the
114
+ * browser reports the generic `application/octet-stream` (unknown binary) — by a
115
+ * safe filename extension. octet-stream is served as a download (never rendered),
116
+ * so the extension fallback can't turn into a stored-XSS vector.
117
+ */
118
+ export declare function isAllowedAttachmentType(mimetype: string, filename: string): boolean;
119
+ /** Attachment metadata DTO returned to clients (never exposes the storage key). */
120
+ export interface MessageAttachmentDTO {
121
+ id: string;
122
+ filename: string;
123
+ contentType: string;
124
+ sizeBytes: number;
125
+ }
126
+ /**
127
+ * Message creation schema
128
+ */
129
+ export declare const MessageCreateSchema: z.ZodObject<{
130
+ recipientOrgId: z.ZodString;
131
+ recipientUserId: z.ZodOptional<z.ZodString>;
132
+ messageType: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
133
+ announcement: "announcement";
134
+ conversation: "conversation";
135
+ }>>>;
136
+ channel: z.ZodOptional<z.ZodString>;
137
+ subject: z.ZodString;
138
+ content: z.ZodString;
139
+ priority: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
140
+ high: "high";
141
+ normal: "normal";
142
+ urgent: "urgent";
143
+ }>>>;
144
+ attachmentIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
145
+ }, z.core.$strip>;
146
+ /**
147
+ * Message reply schema
148
+ */
149
+ export declare const MessageReplySchema: z.ZodObject<{
150
+ content: z.ZodString;
151
+ attachmentIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
152
+ }, z.core.$strip>;
153
+ /**
154
+ * Edit an already-sent message's CONTENT (author-only, enforced server-side).
155
+ * Only the body is editable — subject/recipient/type/attachments are immutable
156
+ * after send (changing them would rewrite the conversation's routing/meaning).
157
+ */
158
+ export declare const MessageEditSchema: z.ZodObject<{
159
+ content: z.ZodString;
160
+ }, z.core.$strip>;
@@ -0,0 +1,156 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { z } from 'zod';
4
+ import { BaseFilterSchema, BooleanQuerySchema } from './common-schemas.js';
5
+ /**
6
+ * Message type schema
7
+ */
8
+ export const MessageTypeSchema = z.enum(['announcement', 'conversation']);
9
+ /**
10
+ * Message priority schema
11
+ */
12
+ export const MessagePrioritySchema = z.enum(['normal', 'high', 'urgent']);
13
+ /**
14
+ * Channel/inbox-bucket schema. Open-ended string up to 50 chars so we
15
+ * can add new channels (support, help, billing, …) without a schema
16
+ * migration. Constrained to a-z/0-9/dash/underscore for URL safety and
17
+ * to keep filter conditions trivially indexable.
18
+ */
19
+ export const MessageChannelSchema = z.string().min(1).max(50).regex(/^[a-z0-9_-]+$/);
20
+ /**
21
+ * Message filter schema for query parameters.
22
+ *
23
+ * Convention for `threadId`:
24
+ * - Omitted (undefined): no filter applied — returns messages regardless of thread.
25
+ * - `'root'` or `null`: filter for root messages only (translated to `threadId IS NULL`
26
+ * by the route layer / query builder). The string sentinel `'root'` is the
27
+ * wire form, since URL query params can't carry a true `null`.
28
+ * - A UUID: filter for messages in that specific thread.
29
+ *
30
+ * `isRead` is honored by `buildMessageConditions` in pipeline-data via a
31
+ * `jsonb_exists(read_by, $orgId)` check on `messages.read_by` for the requesting
32
+ * org (the function form, not the `?` operator, to avoid param-binding clashes).
33
+ */
34
+ export const MessageFilterSchema = BaseFilterSchema.extend({
35
+ threadId: z.union([z.string().uuid(), z.literal('root'), z.null()]).optional(),
36
+ recipientOrgId: z.string().min(1).optional(),
37
+ messageType: MessageTypeSchema.optional(),
38
+ isRead: BooleanQuerySchema.optional(),
39
+ priority: MessagePrioritySchema.optional(),
40
+ channel: MessageChannelSchema.optional(),
41
+ // Free-text inbox search over subject/content. Bounded so a pathological term
42
+ // can't blow up the LIKE; the query builder escapes wildcards + ignores blanks.
43
+ search: z.string().trim().min(1).max(200).optional(),
44
+ });
45
+ /**
46
+ * Upper bounds on free-text message fields. Without these an unbounded
47
+ * subject/content (e.g. a 1MB announcement) is persisted AND SSE-broadcast to
48
+ * every org. `subject` matches the `varchar(500)` DB column so we reject rather
49
+ * than silently truncate; `content` is a `text` column with no DB bound, so we
50
+ * cap it at a sane 32 KiB at the app layer.
51
+ */
52
+ export const MESSAGE_SUBJECT_MAX = 500;
53
+ export const MESSAGE_CONTENT_MAX = 32768;
54
+ /** Max attachments linkable to a single message/reply. */
55
+ export const MESSAGE_MAX_ATTACHMENTS = 5;
56
+ /**
57
+ * Max attachment size (MiB) — env-overridable. The upload route enforces this
58
+ * (multer `fileSize`); kept here so the bound lives with the other message
59
+ * limits rather than only inline in the route.
60
+ */
61
+ export const MESSAGE_ATTACHMENT_MAX_MB = Math.max(1, Number.parseInt(process.env.MESSAGE_ATTACHMENT_MAX_MB ?? '10', 10) || 10);
62
+ export const MESSAGE_ATTACHMENT_MAX_BYTES = MESSAGE_ATTACHMENT_MAX_MB * 1024 * 1024;
63
+ /**
64
+ * Allow-list of attachment MIME types — common images + documents + zip/yaml/json.
65
+ * Deliberately excludes executables/scripts/HTML/SVG (stored-XSS / drive-by).
66
+ * zip is a download container (doesn't render in-browser); yaml/json are plain
67
+ * text — none carry the drive-by risk of the excluded types. Extend here.
68
+ */
69
+ export const MESSAGE_ATTACHMENT_ALLOWED_MIME = new Set([
70
+ 'image/png', 'image/jpeg', 'image/gif', 'image/webp',
71
+ 'application/pdf', 'text/plain', 'text/csv',
72
+ 'application/msword',
73
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
74
+ 'application/vnd.ms-excel',
75
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
76
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
77
+ // Archives (zip / gz / tar / tar.gz) — browsers report these variously by OS;
78
+ // cover the common variants. They download rather than render in-browser.
79
+ 'application/zip', 'application/x-zip-compressed',
80
+ 'application/gzip', 'application/x-gzip',
81
+ 'application/x-tar', 'application/tar',
82
+ 'application/x-compressed-tar',
83
+ // json (browsers may send application/json or text/json).
84
+ 'application/json', 'text/json',
85
+ // yaml/yml — no single registered type; cover the common variants.
86
+ 'application/x-yaml', 'application/yaml', 'text/yaml', 'text/x-yaml',
87
+ ]);
88
+ /**
89
+ * Safe filename extensions. Browsers frequently send `application/octet-stream`
90
+ * for archives + config files (.tar/.gz/.tgz/.yaml/…) because they have no
91
+ * reliable registered MIME type, so the MIME allow-list alone rejects them. These
92
+ * extensions are accepted as a fallback ONLY when the reported MIME is that
93
+ * generic type (see isAllowedAttachmentType) — so a known render/execute MIME
94
+ * (image/svg+xml, text/html, …) is never rescued by a spoofed extension. Still
95
+ * excludes .html/.svg/.js/.exe/… (never listed here).
96
+ */
97
+ export const MESSAGE_ATTACHMENT_ALLOWED_EXT = new Set([
98
+ '.png', '.jpg', '.jpeg', '.gif', '.webp',
99
+ '.pdf', '.txt', '.csv', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
100
+ '.zip', '.gz', '.tgz', '.tar', '.json', '.yaml', '.yml',
101
+ ]);
102
+ /**
103
+ * True if an attachment is accepted. Accepts by MIME allow-list, OR — when the
104
+ * browser reports the generic `application/octet-stream` (unknown binary) — by a
105
+ * safe filename extension. octet-stream is served as a download (never rendered),
106
+ * so the extension fallback can't turn into a stored-XSS vector.
107
+ */
108
+ export function isAllowedAttachmentType(mimetype, filename) {
109
+ if (MESSAGE_ATTACHMENT_ALLOWED_MIME.has(mimetype))
110
+ return true;
111
+ if (mimetype !== 'application/octet-stream')
112
+ return false;
113
+ const dot = (filename || '').lastIndexOf('.');
114
+ if (dot < 0)
115
+ return false;
116
+ return MESSAGE_ATTACHMENT_ALLOWED_EXT.has(filename.slice(dot).toLowerCase());
117
+ }
118
+ /**
119
+ * Attachment ids to link to a message. Each is the id returned by a prior
120
+ * POST /messages/attachments upload; the create route links only ids that belong
121
+ * to the caller (own org + uploader) and are still unattached.
122
+ */
123
+ const AttachmentIdsSchema = z.array(z.string().uuid()).max(MESSAGE_MAX_ATTACHMENTS).optional();
124
+ /**
125
+ * Message creation schema
126
+ */
127
+ export const MessageCreateSchema = z.object({
128
+ recipientOrgId: z.string().min(1, 'Recipient organization ID is required'),
129
+ // Optional per-user targeting WITHIN recipientOrgId. When set, only this user
130
+ // sees the conversation (enforced in buildMessageConditions). Conversation-only
131
+ // and requires a concrete recipient org — the route rejects it on announcements
132
+ // and '*' broadcasts. Bounded to the recipient_user_id column width.
133
+ recipientUserId: z.string().min(1).max(255).optional(),
134
+ messageType: MessageTypeSchema.optional().default('conversation'),
135
+ channel: MessageChannelSchema.optional(),
136
+ subject: z.string().min(1, 'Subject is required').max(MESSAGE_SUBJECT_MAX, `Subject must be at most ${MESSAGE_SUBJECT_MAX} characters`),
137
+ content: z.string().min(1, 'Content is required').max(MESSAGE_CONTENT_MAX, `Content must be at most ${MESSAGE_CONTENT_MAX} characters`),
138
+ priority: MessagePrioritySchema.optional().default('normal'),
139
+ attachmentIds: AttachmentIdsSchema,
140
+ });
141
+ /**
142
+ * Message reply schema
143
+ */
144
+ export const MessageReplySchema = z.object({
145
+ content: z.string().min(1, 'Content is required').max(MESSAGE_CONTENT_MAX, `Content must be at most ${MESSAGE_CONTENT_MAX} characters`),
146
+ attachmentIds: AttachmentIdsSchema,
147
+ });
148
+ /**
149
+ * Edit an already-sent message's CONTENT (author-only, enforced server-side).
150
+ * Only the body is editable — subject/recipient/type/attachments are immutable
151
+ * after send (changing them would rewrite the conversation's routing/meaning).
152
+ */
153
+ export const MessageEditSchema = z.object({
154
+ content: z.string().min(1, 'Content is required').max(MESSAGE_CONTENT_MAX, `Content must be at most ${MESSAGE_CONTENT_MAX} characters`),
155
+ });
156
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWVzc2FnZS1zY2hlbWFzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3ZhbGlkYXRpb24vbWVzc2FnZS1zY2hlbWFzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEMsT0FBTyxFQUFFLENBQUMsRUFBRSxNQUFNLEtBQUssQ0FBQztBQUN4QixPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUUzRTs7R0FFRztBQUNILE1BQU0sQ0FBQyxNQUFNLGlCQUFpQixHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxjQUFjLEVBQUUsY0FBYyxDQUFDLENBQUMsQ0FBQztBQUUxRTs7R0FFRztBQUNILE1BQU0sQ0FBQyxNQUFNLHFCQUFxQixHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxRQUFRLEVBQUUsTUFBTSxFQUFFLFFBQVEsQ0FBQyxDQUFDLENBQUM7QUFFMUU7Ozs7O0dBS0c7QUFDSCxNQUFNLENBQUMsTUFBTSxvQkFBb0IsR0FBRyxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQyxLQUFLLENBQUMsZUFBZSxDQUFDLENBQUM7QUFFckY7Ozs7Ozs7Ozs7Ozs7R0FhRztBQUNILE1BQU0sQ0FBQyxNQUFNLG1CQUFtQixHQUFHLGdCQUFnQixDQUFDLE1BQU0sQ0FBQztJQUN6RCxRQUFRLEVBQUUsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsUUFBUSxFQUFFO0lBQzlFLGNBQWMsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsR0FBRyxDQUFD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QjtDQUNuQyxDQUFDLENBQUM7QUFFSDs7R0FFRztBQUNILE1BQU0sQ0FBQyxNQUFNLGtCQUFrQixHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDekMsT0FBTyxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxFQUFFLHFCQUFxQixDQUFDLENBQUMsR0FBRyxDQUFDLG1CQUFtQixFQUFFLDJCQUEyQixtQkFBbUIsYUFBYSxDQUFDO0lBQ3ZJLGFBQWEsRUFBRSxtQkFBbUI7Q0FDbkMsQ0FBQyxDQUFDO0FBRUg7Ozs7R0FJRztBQUNILE1BQU0sQ0FBQyxNQUFNLGlCQUFpQixHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDeEMsT0FBTyxFQUFFLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxFQUFFLHFCQUFxQixDQUFDLENBQUMsR0FBRyxDQUFDLG1CQUFtQixFQUFFLDJCQUEyQixtQkFBbUIsYUFBYSxDQUFDO0NBQ3hJLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgeyB6IH0gZnJvbSAnem9kJztcbmltcG9ydCB7IEJhc2VGaWx0ZXJTY2hlbWEsIEJvb2xlYW5RdWVyeVNjaGVtYSB9IGZyb20gJy4vY29tbW9uLXNjaGVtYXMuanMnO1xuXG4vKipcbiAqIE1lc3NhZ2UgdHlwZSBzY2hlbWFcbiAqL1xuZXhwb3J0IGNvbnN0IE1lc3NhZ2VUeXBlU2NoZW1hID0gei5lbnVtKFsnYW5ub3VuY2VtZW50JywgJ2NvbnZlcnNhdGlvbiddKTtcblxuLyoqXG4gKiBNZXNzYWdlIHByaW9yaXR5IHNjaGVtYVxuICovXG5leHBvcnQgY29uc3QgTWVzc2FnZVByaW9yaXR5U2NoZW1hID0gei5lbnVtKFsnbm9ybWFsJywgJ2hpZ2gnLCAndXJnZW50J10pO1xuXG4vKipcbiAqIENoYW5uZWwvaW5ib3gtYnVja2V0IHNjaGVtYS4gT3Blbi1lbmRlZCBzdHJpbmcgdXAgdG8gNTAgY2hhcnMgc28gd2VcbiAqIGNhbiBhZGQgbmV3IGNoYW5uZWxzIChzdXBwb3J0LCBoZWxwLCBiaWxsaW5nLCDigKYpIHdpdGhvdXQgYSBzY2hlbWFcbiAqIG1pZ3JhdGlvbi4gQ29uc3RyYWluZWQgdG8gYS16LzAtOS9kYXNoL3VuZGVyc2NvcmUgZm9yIFVSTCBzYWZldHkgYW5kXG4gKiB0byBrZWVwIGZpbHRlciBjb25kaXRpb25zIHRyaXZpYWxseSBpbmRleGFibGUuXG4gKi9cbmV4cG9ydCBjb25zdCBNZXNzYWdlQ2hhbm5lbFNjaGVtYSA9IHouc3RyaW5nKCkubWluKDEpLm1heCg1MCkucmVnZXgoL15bYS16MC05Xy1dKyQvKTtcblxuLyoqXG4gKiBNZXNzYWdlIGZpbHRlciBzY2hlbWEgZm9yIHF1ZXJ5IHBhcmFtZXRlcnMuXG4gKlxuICogQ29udmVudGlvbiBmb3IgYHRocmVhZElkYDpcbiAqIC0gT21pdHRlZCAodW5kZWZpbmVkKTogbm8gZmlsdGVyIGFwcGxpZWQg4oCUIHJldHVybnMgbWVzc2FnZXMgcmVnYXJkbGVzcyBvZiB0aHJlYWQuXG4gKiAtIGAncm9vdCdgIG9yIGBudWxsYDogZmlsdGVyIGZvciByb290IG1lc3NhZ2VzIG9ubHkgKHRyYW5zbGF0ZWQgdG8gYHRocmVhZElkIElTIE5VTExgXG4gKiAgIGJ5IHRoZSByb3V0ZSBsYXllciAvIHF1ZXJ5IGJ1aWxkZXIpLiBUaGUgc3RyaW5nIHNlbnRpbmVsIGAncm9vdCdgIGlzIHRoZVxuICogICB3aXJlIGZvcm0sIHNpbmNlIFVSTCBxdWVyeSBwYXJhbXMgY2FuJ3QgY2FycnkgYSB0cnVlIGBudWxsYC5cbiAqIC0gQSBVVUlEOiBmaWx0ZXIgZm9yIG1lc3NhZ2VzIGluIHRoYXQgc3BlY2lmaWMgdGhyZWFkLlxuICpcbiAqIGBpc1JlYWRgIGlzIGhvbm9yZWQgYnkgYGJ1aWxkTWVzc2FnZUNvbmRpdGlvbnNgIGluIHBpcGVsaW5lLWRhdGEgdmlhIGFcbiAqIGBqc29uYl9leGlzdHMocmVhZF9ieSwgJG9yZ0lkKWAgY2hlY2sgb24gYG1lc3NhZ2VzLnJlYWRfYnlgIGZvciB0aGUgcmVxdWVzdGluZ1xuICogb3JnICh0aGUgZnVuY3Rpb24gZm9ybSwgbm90IHRoZSBgP2Agb3BlcmF0b3IsIHRvIGF2b2lkIHBhcmFtLWJpbmRpbmcgY2xhc2hlcykuXG4gKi9cbmV4cG9ydCBjb25zdCBNZXNzYWdlRmlsdGVyU2NoZW1hID0gQmFzZUZpbHRlclNjaGVtYS5leHRlbmQoe1xuICB0aHJlYWRJZDogei51bmlvbihbei5zdHJpbmcoKS51dWlkKCksIHoubGl0ZXJhbCgncm9vdCcpLCB6Lm51bGwoKV0pLm9wdGlvbmFsKCksXG4gIHJlY2lwaWVudE9yZ0lkOiB6LnN0cmluZygpLm1pbigxKS5vcHRpb25hbCgpLFxuICBtZXNzYWdlVHlwZTogTWVzc2FnZVR5cGVTY2hlbWEub3B0aW9uYWwoKSxcbiAgaXNSZWFkOiBCb29sZWFuUXVlcnlTY2hlbWEub3B0aW9uYWwoKSxcbiAgcHJpb3JpdHk6IE1lc3NhZ2VQcmlvcml0eVNjaGVtYS5vcHRpb25hbCgpLFxuICBjaGFubmVsOiBNZXNzYWdlQ2hhbm5lbFNjaGVtYS5vcHRpb25hbCgpLFxuICAvLyBGcmVlLXRleHQgaW5ib3ggc2VhcmNoIG92ZXIgc3ViamVjdC9jb250ZW50LiBCb3VuZGVkIHNvIGEgcGF0aG9sb2dpY2FsIHRlcm1cbiAgLy8gY2FuJ3QgYmxvdyB1cCB0aGUgTElLRTsgdGhlIHF1ZXJ5IGJ1aWxkZXIgZXNjYXBlcyB3aWxkY2FyZHMgKyBpZ25vcmVzIGJsYW5rcy5cbiAgc2VhcmNoOiB6LnN0cmluZygpLnRyaW0oKS5taW4oMSkubWF4KDIwMCkub3B0aW9uYWwoKSxcbn0pO1xuXG4vKipcbiAqIFVwcGVyIGJvdW5kcyBvbiBmcmVlLXRleHQgbWVzc2FnZSBmaWVsZHMuIFdpdGhvdXQgdGhlc2UgYW4gdW5ib3VuZGVkXG4gKiBzdWJqZWN0L2NvbnRlbnQgKGUuZy4gYSAxTUIgYW5ub3VuY2VtZW50KSBpcyBwZXJzaXN0ZWQgQU5EIFNTRS1icm9hZGNhc3QgdG9cbiAqIGV2ZXJ5IG9yZy4gYHN1YmplY3RgIG1hdGNoZXMgdGhlIGB2YXJjaGFyKDUwMClgIERCIGNvbHVtbiBzbyB3ZSByZWplY3QgcmF0aGVyXG4gKiB0aGFuIHNpbGVudGx5IHRydW5jYXRlOyBgY29udGVudGAgaXMgYSBgdGV4dGAgY29sdW1uIHdpdGggbm8gREIgYm91bmQsIHNvIHdlXG4gKiBjYXAgaXQgYXQgYSBzYW5lIDMyIEtpQiBhdCB0aGUgYXBwIGxheWVyLlxuICovXG5leHBvcnQgY29uc3QgTUVTU0FHRV9TVUJKRUNUX01BWCA9IDUwMDtcbmV4cG9ydCBjb25zdCBNRVNTQUdFX0NPTlRFTlRfTUFYID0gMzI3Njg7XG5cbi8qKiBNYXggYXR0YWNobWVudHMgbGlua2FibGUgdG8gYSBzaW5nbGUgbWVzc2FnZS9yZXBseS4gKi9cbmV4cG9ydCBjb25zdCBNRVNTQUdFX01BWF9BVFRBQ0hNRU5UUyA9IDU7XG5cbi8qKlxuICogTWF4IGF0dGFjaG1lbnQgc2l6ZSAoTWlCKSDigJQgZW52LW92ZXJyaWRhYmxlLiBUaGUgdXBsb2FkIHJvdXRlIGVuZm9yY2VzIHRoaXNcbiAqIChtdWx0ZXIgYGZpbGVTaXplYCk7IGtlcHQgaGVyZSBzbyB0aGUgYm91bmQgbGl2ZXMgd2l0aCB0aGUgb3RoZXIgbWVzc2FnZVxuICogbGltaXRzIHJhdGhlciB0aGFuIG9ubHkgaW5saW5lIGluIHRoZSByb3V0ZS5cbiAqL1xuZXhwb3J0IGNvbnN0IE1FU1NBR0VfQVRUQUNITUVOVF9NQVhfTUIgPSBNYXRoLm1heCgxLCBOdW1iZXIucGFyc2VJbnQocHJvY2Vzcy5lbnYuTUVTU0FHRV9BVFRBQ0hNRU5UX01BWF9NQiA/PyAnMTAnLCAxMCkgfHwgMTApO1xuZXhwb3J0IGNvbnN0IE1FU1NBR0VfQVRUQUNITUVOVF9NQVhfQllURVMgPSBNRVNTQUdFX0FUVEFDSE1FTlRfTUFYX01CICogMTAyNCAqIDEwMjQ7XG5cbi8qKlxuICogQWxsb3ctbGlzdCBvZiBhdHRhY2htZW50IE1JTUUgdHlwZXMg4oCUIGNvbW1vbiBpbWFnZXMgKyBkb2N1bWVudHMgKyB6aXAveWFtbC9qc29uLlxuICogRGVsaWJlcmF0ZWx5IGV4Y2x1ZGVzIGV4ZWN1dGFibGVzL3NjcmlwdHMvSFRNTC9TVkcgKHN0b3JlZC1YU1MgLyBkcml2ZS1ieSkuXG4gKiB6aXAgaXMgYSBkb3dubG9hZCBjb250YWluZXIgKGRvZXNuJ3QgcmVuZGVyIGluLWJyb3dzZXIpOyB5YW1sL2pzb24gYXJlIHBsYWluXG4gKiB0ZXh0IOKAlCBub25lIGNhcnJ5IHRoZSBkcml2ZS1ieSByaXNrIG9mIHRoZSBleGNsdWRlZCB0eXBlcy4gRXh0ZW5kIGhlcmUuXG4gKi9cbmV4cG9ydCBjb25zdCBNRVNTQUdFX0FUVEFDSE1FTlRfQUxMT1dFRF9NSU1FOiBSZWFkb25seVNldDxzdHJpbmc+ID0gbmV3IFNldChbXG4gICdpbWFnZS9wbmcnLCAnaW1hZ2UvanBlZycsICdpbWFnZS9naWYnLCAnaW1hZ2Uvd2VicCcsXG4gICdhcHBsaWNhdGlvbi9wZGYnLCAndGV4dC9wbGFpbicsICd0ZXh0L2NzdicsXG4gICdhcHBsaWNhdGlvbi9tc3dvcmQnLFxuICAnYXBwbGljYXRpb24vdm5kLm9wZW54bWxmb3JtYXRzLW9mZmljZWRvY3VtZW50LndvcmRwcm9jZXNzaW5nbWwuZG9jdW1lbnQnLFxuICAnYXBwbGljYXRpb24vdm5kLm1zLWV4Y2VsJyxcbiAgJ2FwcGxpY2F0aW9uL3ZuZC5vcGVueG1sZm9ybWF0cy1vZmZpY2Vkb2N1bWVudC5zcHJlYWRzaGVldG1sLnNoZWV0JyxcbiAgJ2FwcGxpY2F0aW9uL3ZuZC5vcGVueG1sZm9ybWF0cy1vZmZpY2Vkb2N1bWVudC5wcmVzZW50YXRpb25tbC5wcmVzZW50YXRpb24nLFxuICAvLyBBcmNoaXZlcyAoemlwIC8gZ3ogLyB0YXIgLyB0YXIuZ3opIOKAlCBicm93c2VycyByZXBvcnQgdGhlc2UgdmFyaW91c2x5IGJ5IE9TO1xuICAvLyBjb3ZlciB0aGUgY29tbW9uIHZhcmlhbnRzLiBUaGV5IGRvd25sb2FkIHJhdGhlciB0aGFuIHJlbmRlciBpbi1icm93c2VyLlxuICAnYXBwbGljYXRpb24vemlwJywgJ2FwcGxpY2F0aW9uL3gtemlwLWNvbXByZXNzZWQnLFxuICAnYXBwbGljYXRpb24vZ3ppcCcsICdhcHBsaWNhdGlvbi94LWd6aXAnLFxuICAnYXBwbGljYXRpb24veC10YXInLCAnYXBwbGljYXRpb24vdGFyJyxcbiAgJ2FwcGxpY2F0aW9uL3gtY29tcHJlc3NlZC10YXInLFxuICAvLyBqc29uIChicm93c2VycyBtYXkgc2VuZCBhcHBsaWNhdGlvbi9qc29uIG9yIHRleHQvanNvbikuXG4gICdhcHBsaWNhdGlvbi9qc29uJywgJ3RleHQvanNvbicsXG4gIC8vIHlhbWwveW1sIOKAlCBubyBzaW5nbGUgcmVnaXN0ZXJlZCB0eXBlOyBjb3ZlciB0aGUgY29tbW9uIHZhcmlhbnRzLlxuICAnYXBwbGljYXRpb24veC15YW1sJywgJ2FwcGxpY2F0aW9uL3lhbWwnLCAndGV4dC95YW1sJywgJ3RleHQveC15YW1sJyxcbl0pO1xuXG4vKipcbiAqIFNhZmUgZmlsZW5hbWUgZXh0ZW5zaW9ucy4gQnJvd3NlcnMgZnJlcXVlbnRseSBzZW5kIGBhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW1gXG4gKiBmb3IgYXJjaGl2ZXMgKyBjb25maWcgZmlsZXMgKC50YXIvLmd6Ly50Z3ovLnlhbWwv4oCmKSBiZWNhdXNlIHRoZXkgaGF2ZSBub1xuICogcmVsaWFibGUgcmVnaXN0ZXJlZCBNSU1FIHR5cGUsIHNvIHRoZSBNSU1FIGFsbG93LWxpc3QgYWxvbmUgcmVqZWN0cyB0aGVtLiBUaGVzZVxuICogZXh0ZW5zaW9ucyBhcmUgYWNjZXB0ZWQgYXMgYSBmYWxsYmFjayBPTkxZIHdoZW4gdGhlIHJlcG9ydGVkIE1JTUUgaXMgdGhhdFxuICogZ2VuZXJpYyB0eXBlIChzZWUgaXNBbGxvd2VkQXR0YWNobWVudFR5cGUpIOKAlCBzbyBhIGtub3duIHJlbmRlci9leGVjdXRlIE1JTUVcbiAqIChpbWFnZS9zdmcreG1sLCB0ZXh0L2h0bWwsIOKApikgaXMgbmV2ZXIgcmVzY3VlZCBieSBhIHNwb29mZWQgZXh0ZW5zaW9uLiBTdGlsbFxuICogZXhjbHVkZXMgLmh0bWwvLnN2Zy8uanMvLmV4ZS/igKYgKG5ldmVyIGxpc3RlZCBoZXJlKS5cbiAqL1xuZXhwb3J0IGNvbnN0IE1FU1NBR0VfQVRUQUNITUVOVF9BTExPV0VEX0VYVDogUmVhZG9ubHlTZXQ8c3RyaW5nPiA9IG5ldyBTZXQoW1xuICAnLnBuZycsICcuanBnJywgJy5qcGVnJywgJy5naWYnLCAnLndlYnAnLFxuICAnLnBkZicsICcudHh0JywgJy5jc3YnLCAnLmRvYycsICcuZG9jeCcsICcueGxzJywgJy54bHN4JywgJy5wcHQnLCAnLnBwdHgnLFxuICAnLnppcCcsICcuZ3onLCAnLnRneicsICcudGFyJywgJy5qc29uJywgJy55YW1sJywgJy55bWwnLFxuXSk7XG5cbi8qKlxuICogVHJ1ZSBpZiBhbiBhdHRhY2htZW50IGlzIGFjY2VwdGVkLiBBY2NlcHRzIGJ5IE1JTUUgYWxsb3ctbGlzdCwgT1Ig4oCUIHdoZW4gdGhlXG4gKiBicm93c2VyIHJlcG9ydHMgdGhlIGdlbmVyaWMgYGFwcGxpY2F0aW9uL29jdGV0LXN0cmVhbWAgKHVua25vd24gYmluYXJ5KSDigJQgYnkgYVxuICogc2FmZSBmaWxlbmFtZSBleHRlbnNpb24uIG9jdGV0LXN0cmVhbSBpcyBzZXJ2ZWQgYXMgYSBkb3dubG9hZCAobmV2ZXIgcmVuZGVyZWQpLFxuICogc28gdGhlIGV4dGVuc2lvbiBmYWxsYmFjayBjYW4ndCB0dXJuIGludG8gYSBzdG9yZWQtWFNTIHZlY3Rvci5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGlzQWxsb3dlZEF0dGFjaG1lbnRUeXBlKG1pbWV0eXBlOiBzdHJpbmcsIGZpbGVuYW1lOiBzdHJpbmcpOiBib29sZWFuIHtcbiAgaWYgKE1FU1NBR0VfQVRUQUNITUVOVF9BTExPV0VEX01JTUUuaGFzKG1pbWV0eXBlKSkgcmV0dXJuIHRydWU7XG4gIGlmIChtaW1ldHlwZSAhPT0gJ2FwcGxpY2F0aW9uL29jdGV0LXN0cmVhbScpIHJldHVybiBmYWxzZTtcbiAgY29uc3QgZG90ID0gKGZpbGVuYW1lIHx8ICcnKS5sYXN0SW5kZXhPZignLicpO1xuICBpZiAoZG90IDwgMCkgcmV0dXJuIGZhbHNlO1xuICByZXR1cm4gTUVTU0FHRV9BVFRBQ0hNRU5UX0FMTE9XRURfRVhULmhhcyhmaWxlbmFtZS5zbGljZShkb3QpLnRvTG93ZXJDYXNlKCkpO1xufVxuXG4vKiogQXR0YWNobWVudCBtZXRhZGF0YSBEVE8gcmV0dXJuZWQgdG8gY2xpZW50cyAobmV2ZXIgZXhwb3NlcyB0aGUgc3RvcmFnZSBrZXkpLiAqL1xuZXhwb3J0IGludGVyZmFjZSBNZXNzYWdlQXR0YWNobWVudERUTyB7XG4gIGlkOiBzdHJpbmc7XG4gIGZpbGVuYW1lOiBzdHJpbmc7XG4gIGNvbnRlbnRUeXBlOiBzdHJpbmc7XG4gIHNpemVCeXRlczogbnVtYmVyO1xufVxuXG4vKipcbiAqIEF0dGFjaG1lbnQgaWRzIHRvIGxpbmsgdG8gYSBtZXNzYWdlLiBFYWNoIGlzIHRoZSBpZCByZXR1cm5lZCBieSBhIHByaW9yXG4gKiBQT1NUIC9tZXNzYWdlcy9hdHRhY2htZW50cyB1cGxvYWQ7IHRoZSBjcmVhdGUgcm91dGUgbGlua3Mgb25seSBpZHMgdGhhdCBiZWxvbmdcbiAqIHRvIHRoZSBjYWxsZXIgKG93biBvcmcgKyB1cGxvYWRlcikgYW5kIGFyZSBzdGlsbCB1bmF0dGFjaGVkLlxuICovXG5jb25zdCBBdHRhY2htZW50SWRzU2NoZW1hID0gei5hcnJheSh6LnN0cmluZygpLnV1aWQoKSkubWF4KE1FU1NBR0VfTUFYX0FUVEFDSE1FTlRTKS5vcHRpb25hbCgpO1xuXG4vKipcbiAqIE1lc3NhZ2UgY3JlYXRpb24gc2NoZW1hXG4gKi9cbmV4cG9ydCBjb25zdCBNZXNzYWdlQ3JlYXRlU2NoZW1hID0gei5vYmplY3Qoe1xuICByZWNpcGllbnRPcmdJZDogei5zdHJpbmcoKS5taW4oMSwgJ1JlY2lwaWVudCBvcmdhbml6YXRpb24gSUQgaXMgcmVxdWlyZWQnKSxcbiAgLy8gT3B0aW9uYWwgcGVyLXVzZXIgdGFyZ2V0aW5nIFdJVEhJTiByZWNpcGllbnRPcmdJZC4gV2hlbiBzZXQsIG9ubHkgdGhpcyB1c2VyXG4gIC8vIHNlZXMgdGhlIGNvbnZlcnNhdGlvbiAoZW5mb3JjZWQgaW4gYnVpbGRNZXNzYWdlQ29uZGl0aW9ucykuIENvbnZlcnNhdGlvbi1vbmx5XG4gIC8vIGFuZCByZXF1aXJlcyBhIGNvbmNyZXRlIHJlY2lwaWVudCBvcmcg4oCUIHRoZSByb3V0ZSByZWplY3RzIGl0IG9uIGFubm91bmNlbWVudHNcbiAgLy8gYW5kICcqJyBicm9hZGNhc3RzLiBCb3VuZGVkIHRvIHRoZSByZWNpcGllbnRfdXNlcl9pZCBjb2x1bW4gd2lkdGguXG4gIHJlY2lwaWVudFVzZXJJZDogei5zdHJpbmcoKS5taW4oMSkubWF4KDI1NSkub3B0aW9uYWwoKSxcbiAgbWVzc2FnZVR5cGU6IE1lc3NhZ2VUeXBlU2NoZW1hLm9wdGlvbmFsKCkuZGVmYXVsdCgnY29udmVyc2F0aW9uJyksXG4gIGNoYW5uZWw6IE1lc3NhZ2VDaGFubmVsU2NoZW1hLm9wdGlvbmFsKCksXG4gIHN1YmplY3Q6IHouc3RyaW5nKCkubWluKDEsICdTdWJqZWN0IGlzIHJlcXVpcmVkJykubWF4KE1FU1NBR0VfU1VCSkVDVF9NQVgsIGBTdWJqZWN0IG11c3QgYmUgYXQgbW9zdCAke01FU1NBR0VfU1VCSkVDVF9NQVh9IGNoYXJhY3RlcnNgKSxcbiAgY29udGVudDogei5zdHJpbmcoKS5taW4oMSwgJ0NvbnRlbnQgaXMgcmVxdWlyZWQnKS5tYXgoTUVTU0FHRV9DT05URU5UX01BWCwgYENvbnRlbnQgbXVzdCBiZSBhdCBtb3N0ICR7TUVTU0FHRV9DT05URU5UX01BWH0gY2hhcmFjdGVyc2ApLFxuICBwcmlvcml0eTogTWVzc2FnZVByaW9yaXR5U2NoZW1hLm9wdGlvbmFsKCkuZGVmYXVsdCgnbm9ybWFsJyksXG4gIGF0dGFjaG1lbnRJZHM6IEF0dGFjaG1lbnRJZHNTY2hlbWEsXG59KTtcblxuLyoqXG4gKiBNZXNzYWdlIHJlcGx5IHNjaGVtYVxuICovXG5leHBvcnQgY29uc3QgTWVzc2FnZVJlcGx5U2NoZW1hID0gei5vYmplY3Qoe1xuICBjb250ZW50OiB6LnN0cmluZygpLm1pbigxLCAnQ29udGVudCBpcyByZXF1aXJlZCcpLm1heChNRVNTQUdFX0NPTlRFTlRfTUFYLCBgQ29udGVudCBtdXN0IGJlIGF0IG1vc3QgJHtNRVNTQUdFX0NPTlRFTlRfTUFYfSBjaGFyYWN0ZXJzYCksXG4gIGF0dGFjaG1lbnRJZHM6IEF0dGFjaG1lbnRJZHNTY2hlbWEsXG59KTtcblxuLyoqXG4gKiBFZGl0IGFuIGFscmVhZHktc2VudCBtZXNzYWdlJ3MgQ09OVEVOVCAoYXV0aG9yLW9ubHksIGVuZm9yY2VkIHNlcnZlci1zaWRlKS5cbiAqIE9ubHkgdGhlIGJvZHkgaXMgZWRpdGFibGUg4oCUIHN1YmplY3QvcmVjaXBpZW50L3R5cGUvYXR0YWNobWVudHMgYXJlIGltbXV0YWJsZVxuICogYWZ0ZXIgc2VuZCAoY2hhbmdpbmcgdGhlbSB3b3VsZCByZXdyaXRlIHRoZSBjb252ZXJzYXRpb24ncyByb3V0aW5nL21lYW5pbmcpLlxuICovXG5leHBvcnQgY29uc3QgTWVzc2FnZUVkaXRTY2hlbWEgPSB6Lm9iamVjdCh7XG4gIGNvbnRlbnQ6IHouc3RyaW5nKCkubWluKDEsICdDb250ZW50IGlzIHJlcXVpcmVkJykubWF4KE1FU1NBR0VfQ09OVEVOVF9NQVgsIGBDb250ZW50IG11c3QgYmUgYXQgbW9zdCAke01FU1NBR0VfQ09OVEVOVF9NQVh9IGNoYXJhY3RlcnNgKSxcbn0pO1xuIl19
@@ -0,0 +1,72 @@
1
+ import type { Request } from 'express';
2
+ import { ZodError, type ZodSchema } from 'zod';
3
+ /**
4
+ * Result type for validation operations.
5
+ *
6
+ * CANONICAL validation-result shape for the api-core public surface: a boolean-
7
+ * discriminated union (`if (!result.ok) …`). Use THIS for anything that parses
8
+ * against a Zod schema — `validate` / `validateBody` / `validateQuery`. The
9
+ * `ok` discriminant also carries the optional `zodError` for callers that want
10
+ * field-level detail.
11
+ *
12
+ * Note the deliberately-lighter sibling shape in `utils/params.ts`
13
+ * (`{ value } | { error }`, narrowed with `'error' in result`). That one is for
14
+ * simple inline request-parsing guards (`validateBulkArray`, `parseDateRange`,
15
+ * `parseReportInterval`) — NOT schema validation. The two are kept distinct on
16
+ * purpose: unifying `utils/params.ts` onto `{ ok }` would touch every
17
+ * `'error' in result` call site across the downstream services (pipeline,
18
+ * plugin, reporting, compliance, …) for no behavioural gain, so the convention
19
+ * is documented rather than force-migrated. Rule of thumb: Zod schema →
20
+ * `ValidationResult` (`ok`); ad-hoc parse guard → params.ts (`'error' in`).
21
+ */
22
+ export type ValidationResult<T> = {
23
+ ok: true;
24
+ value: T;
25
+ } | {
26
+ ok: false;
27
+ error: string;
28
+ zodError?: ZodError;
29
+ };
30
+ /**
31
+ * Validate arbitrary data against a Zod schema.
32
+ *
33
+ * This is the core validation function used by the Express-specific helpers
34
+ * (`validateBody`, `validateQuery`). It can also be called directly when
35
+ * validating data that doesn't come from a request object.
36
+ *
37
+ * @param data - Data to validate
38
+ * @param schema - Zod schema for validation
39
+ * @returns Validation result with parsed data or error
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * const result = validate(someData, MySchema);
44
+ * if (!result.ok) {
45
+ * return sendBadRequest(res, result.error);
46
+ * }
47
+ * const parsed = result.value;
48
+ * ```
49
+ */
50
+ export declare function validate<T>(data: unknown, schema: ZodSchema<T>): ValidationResult<T>;
51
+ /**
52
+ * Validate request query parameters with a Zod schema.
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * const result = validateQuery(req, PipelineFilterSchema);
57
+ * if (!result.ok) return sendBadRequest(res, result.error);
58
+ * const filter = result.value;
59
+ * ```
60
+ */
61
+ export declare function validateQuery<T>(req: Request, schema: ZodSchema<T>): ValidationResult<T>;
62
+ /**
63
+ * Validate request body with a Zod schema.
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * const result = validateBody(req, PipelineCreateSchema);
68
+ * if (!result.ok) return sendBadRequest(res, result.error);
69
+ * const data = result.value;
70
+ * ```
71
+ */
72
+ export declare function validateBody<T>(req: Request, schema: ZodSchema<T>): ValidationResult<T>;
@@ -0,0 +1,65 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { ZodError } from 'zod';
4
+ /**
5
+ * Validate arbitrary data against a Zod schema.
6
+ *
7
+ * This is the core validation function used by the Express-specific helpers
8
+ * (`validateBody`, `validateQuery`). It can also be called directly when
9
+ * validating data that doesn't come from a request object.
10
+ *
11
+ * @param data - Data to validate
12
+ * @param schema - Zod schema for validation
13
+ * @returns Validation result with parsed data or error
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const result = validate(someData, MySchema);
18
+ * if (!result.ok) {
19
+ * return sendBadRequest(res, result.error);
20
+ * }
21
+ * const parsed = result.value;
22
+ * ```
23
+ */
24
+ export function validate(data, schema) {
25
+ try {
26
+ return { ok: true, value: schema.parse(data) };
27
+ }
28
+ catch (error) {
29
+ if (error instanceof ZodError) {
30
+ const firstIssue = error.issues[0];
31
+ const message = firstIssue
32
+ ? `${firstIssue.path.join('.')}: ${firstIssue.message}`
33
+ : 'Validation failed';
34
+ return { ok: false, error: message, zodError: error };
35
+ }
36
+ return { ok: false, error: 'Validation failed' };
37
+ }
38
+ }
39
+ /**
40
+ * Validate request query parameters with a Zod schema.
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * const result = validateQuery(req, PipelineFilterSchema);
45
+ * if (!result.ok) return sendBadRequest(res, result.error);
46
+ * const filter = result.value;
47
+ * ```
48
+ */
49
+ export function validateQuery(req, schema) {
50
+ return validate(req.query, schema);
51
+ }
52
+ /**
53
+ * Validate request body with a Zod schema.
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const result = validateBody(req, PipelineCreateSchema);
58
+ * if (!result.ok) return sendBadRequest(res, result.error);
59
+ * const data = result.value;
60
+ * ```
61
+ */
62
+ export function validateBody(req, schema) {
63
+ return validate(req.body, schema);
64
+ }
65
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWlkZGxld2FyZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy92YWxpZGF0aW9uL21pZGRsZXdhcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUd0QyxPQUFPLEVBQUUsUUFBUSxFQUFrQixNQUFNLEtBQUssQ0FBQztBQXlCL0M7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FtQkc7QUFDSCxNQUFNLFVBQVUsUUFBUSxDQUN0QixJQUFhLEVBQ2IsTUFBb0I7SUFFcEIsSUFBSSxDQUFDO1FBQ0gsT0FBTyxFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLE1BQU0sQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztJQUNqRCxDQUFDO0lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztRQUNmLElBQUksS0FBSyxZQUFZLFFBQVEsRUFBRSxDQUFDO1lBQzlCLE1BQU0sVUFBVSxHQUFHLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDbkMsTUFBTSxPQUFPLEdBQUcsVUFBVTtnQkFDeEIsQ0FBQyxDQUFDLEdBQUcsVUFBVSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLEtBQUssVUFBVSxDQUFDLE9BQU8sRUFBRTtnQkFDdkQsQ0FBQyxDQUFDLG1CQUFtQixDQUFDO1lBQ3hCLE9BQU8sRUFBRSxFQUFFLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRSxDQUFDO1FBQ3hELENBQUM7UUFDRCxPQUFPLEVBQUUsRUFBRSxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsbUJBQW1CLEVBQUUsQ0FBQztJQUNuRCxDQUFDO0FBQ0gsQ0FBQztBQUVEOzs7Ozs7Ozs7R0FTRztBQUNILE1BQU0sVUFBVSxhQUFhLENBQUksR0FBWSxFQUFFLE1BQW9CO0lBQ2pFLE9BQU8sUUFBUSxDQUFDLEdBQUcsQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLENBQUM7QUFDckMsQ0FBQztBQUVEOzs7Ozs7Ozs7R0FTRztBQUNILE1BQU0sVUFBVSxZQUFZLENBQUksR0FBWSxFQUFFLE1BQW9CO0lBQ2hFLE9BQU8sUUFBUSxDQUFDLEdBQUcsQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDLENBQUM7QUFDcEMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgdHlwZSB7IFJlcXVlc3QgfSBmcm9tICdleHByZXNzJztcbmltcG9ydCB7IFpvZEVycm9yLCB0eXBlIFpvZFNjaGVtYSB9IGZyb20gJ3pvZCc7XG5cbi8qKlxuICogUmVzdWx0IHR5cGUgZm9yIHZhbGlkYXRpb24gb3BlcmF0aW9ucy5cbiAqXG4gKiBDQU5PTklDQUwgdmFsaWRhdGlvbi1yZXN1bHQgc2hhcGUgZm9yIHRoZSBhcGktY29yZSBwdWJsaWMgc3VyZmFjZTogYSBib29sZWFuLVxuICogZGlzY3JpbWluYXRlZCB1bmlvbiAoYGlmICghcmVzdWx0Lm9rKSDigKZgKS4gVXNlIFRISVMgZm9yIGFueXRoaW5nIHRoYXQgcGFyc2VzXG4gKiBhZ2FpbnN0IGEgWm9kIHNjaGVtYSDigJQgYHZhbGlkYXRlYCAvIGB2YWxpZGF0ZUJvZHlgIC8gYHZhbGlkYXRlUXVlcnlgLiBUaGVcbiAqIGBva2AgZGlzY3JpbWluYW50IGFsc28gY2FycmllcyB0aGUgb3B0aW9uYWwgYHpvZEVycm9yYCBmb3IgY2FsbGVycyB0aGF0IHdhbnRcbiAqIGZpZWxkLWxldmVsIGRldGFpbC5cbiAqXG4gKiBOb3RlIHRoZSBkZWxpYmVyYXRlbHktbGlnaHRlciBzaWJsaW5nIHNoYXBlIGluIGB1dGlscy9wYXJhbXMudHNgXG4gKiAoYHsgdmFsdWUgfSB8IHsgZXJyb3IgfWAsIG5hcnJvd2VkIHdpdGggYCdlcnJvcicgaW4gcmVzdWx0YCkuIFRoYXQgb25lIGlzIGZvclxuICogc2ltcGxlIGlubGluZSByZXF1ZXN0LXBhcnNpbmcgZ3VhcmRzIChgdmFsaWRhdGVCdWxrQXJyYXlgLCBgcGFyc2VEYXRlUmFuZ2VgLFxuICogYHBhcnNlUmVwb3J0SW50ZXJ2YWxgKSDigJQgTk9UIHNjaGVtYSB2YWxpZGF0aW9uLiBUaGUgdHdvIGFyZSBrZXB0IGRpc3RpbmN0IG9uXG4gKiBwdXJwb3NlOiB1bmlmeWluZyBgdXRpbHMvcGFyYW1zLnRzYCBvbnRvIGB7IG9rIH1gIHdvdWxkIHRvdWNoIGV2ZXJ5XG4gKiBgJ2Vycm9yJyBpbiByZXN1bHRgIGNhbGwgc2l0ZSBhY3Jvc3MgdGhlIGRvd25zdHJlYW0gc2VydmljZXMgKHBpcGVsaW5lLFxuICogcGx1Z2luLCByZXBvcnRpbmcsIGNvbXBsaWFuY2UsIOKApikgZm9yIG5vIGJlaGF2aW91cmFsIGdhaW4sIHNvIHRoZSBjb252ZW50aW9uXG4gKiBpcyBkb2N1bWVudGVkIHJhdGhlciB0aGFuIGZvcmNlLW1pZ3JhdGVkLiBSdWxlIG9mIHRodW1iOiBab2Qgc2NoZW1hIOKGklxuICogYFZhbGlkYXRpb25SZXN1bHRgIChgb2tgKTsgYWQtaG9jIHBhcnNlIGd1YXJkIOKGkiBwYXJhbXMudHMgKGAnZXJyb3InIGluYCkuXG4gKi9cbmV4cG9ydCB0eXBlIFZhbGlkYXRpb25SZXN1bHQ8VD4gPVxuICB8IHsgb2s6IHRydWU7IHZhbHVlOiBUIH1cbiAgfCB7IG9rOiBmYWxzZTsgZXJyb3I6IHN0cmluZzsgem9kRXJyb3I/OiBab2RFcnJvciB9O1xuXG4vKipcbiAqIFZhbGlkYXRlIGFyYml0cmFyeSBkYXRhIGFnYWluc3QgYSBab2Qgc2NoZW1hLlxuICpcbiAqIFRoaXMgaXMgdGhlIGNvcmUgdmFsaWRhdGlvbiBmdW5jdGlvbiB1c2VkIGJ5IHRoZSBFeHByZXNzLXNwZWNpZmljIGhlbHBlcnNcbiAqIChgdmFsaWRhdGVCb2R5YCwgYHZhbGlkYXRlUXVlcnlgKS4gSXQgY2FuIGFsc28gYmUgY2FsbGVkIGRpcmVjdGx5IHdoZW5cbiAqIHZhbGlkYXRpbmcgZGF0YSB0aGF0IGRvZXNuJ3QgY29tZSBmcm9tIGEgcmVxdWVzdCBvYmplY3QuXG4gKlxuICogQHBhcmFtIGRhdGEgLSBEYXRhIHRvIHZhbGlkYXRlXG4gKiBAcGFyYW0gc2NoZW1hIC0gWm9kIHNjaGVtYSBmb3IgdmFsaWRhdGlvblxuICogQHJldHVybnMgVmFsaWRhdGlvbiByZXN1bHQgd2l0aCBwYXJzZWQgZGF0YSBvciBlcnJvclxuICpcbiAqIEBleGFtcGxlXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiBjb25zdCByZXN1bHQgPSB2YWxpZGF0ZShzb21lRGF0YSwgTXlTY2hlbWEpO1xuICogaWYgKCFyZXN1bHQub2spIHtcbiAqICAgcmV0dXJuIHNlbmRCYWRSZXF1ZXN0KHJlcywgcmVzdWx0LmVycm9yKTtcbiAqIH1cbiAqIGNvbnN0IHBhcnNlZCA9IHJlc3VsdC52YWx1ZTtcbiAqIGBgYFxuICovXG5leHBvcnQgZnVuY3Rpb24gdmFsaWRhdGU8VD4oXG4gIGRhdGE6IHVua25vd24sXG4gIHNjaGVtYTogWm9kU2NoZW1hPFQ+LFxuKTogVmFsaWRhdGlvblJlc3VsdDxUPiB7XG4gIHRyeSB7XG4gICAgcmV0dXJuIHsgb2s6IHRydWUsIHZhbHVlOiBzY2hlbWEucGFyc2UoZGF0YSkgfTtcbiAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICBpZiAoZXJyb3IgaW5zdGFuY2VvZiBab2RFcnJvcikge1xuICAgICAgY29uc3QgZmlyc3RJc3N1ZSA9IGVycm9yLmlzc3Vlc1swXTtcbiAgICAgIGNvbnN0IG1lc3NhZ2UgPSBmaXJzdElzc3VlXG4gICAgICAgID8gYCR7Zmlyc3RJc3N1ZS5wYXRoLmpvaW4oJy4nKX06ICR7Zmlyc3RJc3N1ZS5tZXNzYWdlfWBcbiAgICAgICAgOiAnVmFsaWRhdGlvbiBmYWlsZWQnO1xuICAgICAgcmV0dXJuIHsgb2s6IGZhbHNlLCBlcnJvcjogbWVzc2FnZSwgem9kRXJyb3I6IGVycm9yIH07XG4gICAgfVxuICAgIHJldHVybiB7IG9rOiBmYWxzZSwgZXJyb3I6ICdWYWxpZGF0aW9uIGZhaWxlZCcgfTtcbiAgfVxufVxuXG4vKipcbiAqIFZhbGlkYXRlIHJlcXVlc3QgcXVlcnkgcGFyYW1ldGVycyB3aXRoIGEgWm9kIHNjaGVtYS5cbiAqXG4gKiBAZXhhbXBsZVxuICogYGBgdHlwZXNjcmlwdFxuICogY29uc3QgcmVzdWx0ID0gdmFsaWRhdGVRdWVyeShyZXEsIFBpcGVsaW5lRmlsdGVyU2NoZW1hKTtcbiAqIGlmICghcmVzdWx0Lm9rKSByZXR1cm4gc2VuZEJhZFJlcXVlc3QocmVzLCByZXN1bHQuZXJyb3IpO1xuICogY29uc3QgZmlsdGVyID0gcmVzdWx0LnZhbHVlO1xuICogYGBgXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB2YWxpZGF0ZVF1ZXJ5PFQ+KHJlcTogUmVxdWVzdCwgc2NoZW1hOiBab2RTY2hlbWE8VD4pOiBWYWxpZGF0aW9uUmVzdWx0PFQ+IHtcbiAgcmV0dXJuIHZhbGlkYXRlKHJlcS5xdWVyeSwgc2NoZW1hKTtcbn1cblxuLyoqXG4gKiBWYWxpZGF0ZSByZXF1ZXN0IGJvZHkgd2l0aCBhIFpvZCBzY2hlbWEuXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIGNvbnN0IHJlc3VsdCA9IHZhbGlkYXRlQm9keShyZXEsIFBpcGVsaW5lQ3JlYXRlU2NoZW1hKTtcbiAqIGlmICghcmVzdWx0Lm9rKSByZXR1cm4gc2VuZEJhZFJlcXVlc3QocmVzLCByZXN1bHQuZXJyb3IpO1xuICogY29uc3QgZGF0YSA9IHJlc3VsdC52YWx1ZTtcbiAqIGBgYFxuICovXG5leHBvcnQgZnVuY3Rpb24gdmFsaWRhdGVCb2R5PFQ+KHJlcTogUmVxdWVzdCwgc2NoZW1hOiBab2RTY2hlbWE8VD4pOiBWYWxpZGF0aW9uUmVzdWx0PFQ+IHtcbiAgcmV0dXJuIHZhbGlkYXRlKHJlcS5ib2R5LCBzY2hlbWEpO1xufVxuIl19