@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.
- package/LICENSE +202 -0
- package/README.md +98 -0
- package/lib/constants/ai-providers.d.ts +52 -0
- package/lib/constants/ai-providers.js +129 -0
- package/lib/constants/http-status.d.ts +20 -0
- package/lib/constants/http-status.js +26 -0
- package/lib/constants/index.d.ts +3 -0
- package/lib/constants/index.js +6 -0
- package/lib/constants/time.d.ts +10 -0
- package/lib/constants/time.js +13 -0
- package/lib/errors/app-errors.d.ts +30 -0
- package/lib/errors/app-errors.js +53 -0
- package/lib/errors/index.d.ts +1 -0
- package/lib/errors/index.js +4 -0
- package/lib/helpers/access-helpers.d.ts +63 -0
- package/lib/helpers/access-helpers.js +82 -0
- package/lib/helpers/crud-helpers.d.ts +9 -0
- package/lib/helpers/crud-helpers.js +23 -0
- package/lib/helpers/index.d.ts +6 -0
- package/lib/helpers/index.js +9 -0
- package/lib/helpers/org-hierarchy-http.d.ts +74 -0
- package/lib/helpers/org-hierarchy-http.js +144 -0
- package/lib/helpers/org-hierarchy.d.ts +57 -0
- package/lib/helpers/org-hierarchy.js +110 -0
- package/lib/helpers/restore-helpers.d.ts +49 -0
- package/lib/helpers/restore-helpers.js +78 -0
- package/lib/helpers/sse-helpers.d.ts +20 -0
- package/lib/helpers/sse-helpers.js +45 -0
- package/lib/index.d.ts +58 -0
- package/lib/index.js +71 -0
- package/lib/middleware/auth.d.ts +208 -0
- package/lib/middleware/auth.js +588 -0
- package/lib/middleware/index.d.ts +3 -0
- package/lib/middleware/index.js +6 -0
- package/lib/middleware/mongo-sanitize.d.ts +32 -0
- package/lib/middleware/mongo-sanitize.js +100 -0
- package/lib/middleware/step-up.d.ts +45 -0
- package/lib/middleware/step-up.js +138 -0
- package/lib/openapi/extend-zod.d.ts +1 -0
- package/lib/openapi/extend-zod.js +6 -0
- package/lib/openapi/index.d.ts +2 -0
- package/lib/openapi/index.js +5 -0
- package/lib/openapi/registry.d.ts +17 -0
- package/lib/openapi/registry.js +38 -0
- package/lib/openapi/routes/ask-routes.d.ts +7 -0
- package/lib/openapi/routes/ask-routes.js +62 -0
- package/lib/openapi/routes/billing-routes.d.ts +1 -0
- package/lib/openapi/routes/billing-routes.js +66 -0
- package/lib/openapi/routes/image-registry-routes.d.ts +7 -0
- package/lib/openapi/routes/image-registry-routes.js +98 -0
- package/lib/openapi/routes/index.d.ts +5 -0
- package/lib/openapi/routes/index.js +28 -0
- package/lib/openapi/routes/message-routes.d.ts +1 -0
- package/lib/openapi/routes/message-routes.js +105 -0
- package/lib/openapi/routes/pipeline-routes.d.ts +1 -0
- package/lib/openapi/routes/pipeline-routes.js +105 -0
- package/lib/openapi/routes/pipeline-template-routes.d.ts +1 -0
- package/lib/openapi/routes/pipeline-template-routes.js +61 -0
- package/lib/openapi/routes/plugin-routes.d.ts +1 -0
- package/lib/openapi/routes/plugin-routes.js +96 -0
- package/lib/openapi/routes/quota-routes.d.ts +1 -0
- package/lib/openapi/routes/quota-routes.js +62 -0
- package/lib/openapi/schema-registry.d.ts +25 -0
- package/lib/openapi/schema-registry.js +91 -0
- package/lib/routes/health.d.ts +41 -0
- package/lib/routes/health.js +82 -0
- package/lib/routes/index.d.ts +1 -0
- package/lib/routes/index.js +4 -0
- package/lib/services/audit-spool.d.ts +80 -0
- package/lib/services/audit-spool.js +172 -0
- package/lib/services/cache-service.d.ts +124 -0
- package/lib/services/cache-service.js +276 -0
- package/lib/services/circuit-breaker.d.ts +45 -0
- package/lib/services/circuit-breaker.js +138 -0
- package/lib/services/compliance-client.d.ts +46 -0
- package/lib/services/compliance-client.js +128 -0
- package/lib/services/compliance-event-subscriber.d.ts +16 -0
- package/lib/services/compliance-event-subscriber.js +72 -0
- package/lib/services/durable-event-bus.d.ts +96 -0
- package/lib/services/durable-event-bus.js +204 -0
- package/lib/services/entity-events.d.ts +48 -0
- package/lib/services/entity-events.js +79 -0
- package/lib/services/env-redis.d.ts +44 -0
- package/lib/services/env-redis.js +94 -0
- package/lib/services/http-client.d.ts +143 -0
- package/lib/services/http-client.js +335 -0
- package/lib/services/index.d.ts +18 -0
- package/lib/services/index.js +20 -0
- package/lib/services/leader-lock.d.ts +45 -0
- package/lib/services/leader-lock.js +85 -0
- package/lib/services/quota.d.ts +130 -0
- package/lib/services/quota.js +264 -0
- package/lib/services/remote-audit-client.d.ts +160 -0
- package/lib/services/remote-audit-client.js +390 -0
- package/lib/services/retry-strategy.d.ts +74 -0
- package/lib/services/retry-strategy.js +118 -0
- package/lib/services/scheduler.d.ts +41 -0
- package/lib/services/scheduler.js +86 -0
- package/lib/services/service-boot.d.ts +20 -0
- package/lib/services/service-boot.js +28 -0
- package/lib/services/sse-ticket-store.d.ts +51 -0
- package/lib/services/sse-ticket-store.js +130 -0
- package/lib/services/token-revocation.d.ts +52 -0
- package/lib/services/token-revocation.js +120 -0
- package/lib/testing/tier-mock.d.ts +44 -0
- package/lib/testing/tier-mock.js +65 -0
- package/lib/types/audit-events.d.ts +69 -0
- package/lib/types/audit-events.js +4 -0
- package/lib/types/catalog-metadata.d.ts +36 -0
- package/lib/types/catalog-metadata.js +21 -0
- package/lib/types/common.d.ts +255 -0
- package/lib/types/common.js +41 -0
- package/lib/types/error-codes.d.ts +54 -0
- package/lib/types/error-codes.js +107 -0
- package/lib/types/feature-flags.d.ts +36 -0
- package/lib/types/feature-flags.js +128 -0
- package/lib/types/http.d.ts +27 -0
- package/lib/types/http.js +4 -0
- package/lib/types/index.d.ts +11 -0
- package/lib/types/index.js +14 -0
- package/lib/types/permissions.d.ts +65 -0
- package/lib/types/permissions.js +131 -0
- package/lib/types/pipeline-template.d.ts +30 -0
- package/lib/types/pipeline-template.js +13 -0
- package/lib/types/pipeline.d.ts +59 -0
- package/lib/types/pipeline.js +31 -0
- package/lib/types/quota-tiers.d.ts +79 -0
- package/lib/types/quota-tiers.js +221 -0
- package/lib/types/visibility.d.ts +20 -0
- package/lib/types/visibility.js +22 -0
- package/lib/utils/alias-resolver.d.ts +37 -0
- package/lib/utils/alias-resolver.js +68 -0
- package/lib/utils/audit.d.ts +15 -0
- package/lib/utils/audit.js +39 -0
- package/lib/utils/aws-scrub.d.ts +11 -0
- package/lib/utils/aws-scrub.js +67 -0
- package/lib/utils/compliance-attributes.d.ts +6 -0
- package/lib/utils/compliance-attributes.js +69 -0
- package/lib/utils/concurrency.d.ts +9 -0
- package/lib/utils/concurrency.js +29 -0
- package/lib/utils/crash-handlers.d.ts +18 -0
- package/lib/utils/crash-handlers.js +36 -0
- package/lib/utils/env.d.ts +28 -0
- package/lib/utils/env.js +54 -0
- package/lib/utils/headers.d.ts +18 -0
- package/lib/utils/headers.js +21 -0
- package/lib/utils/identity.d.ts +40 -0
- package/lib/utils/identity.js +51 -0
- package/lib/utils/index.d.ts +17 -0
- package/lib/utils/index.js +20 -0
- package/lib/utils/logger.d.ts +38 -0
- package/lib/utils/logger.js +174 -0
- package/lib/utils/metric-emitter.d.ts +36 -0
- package/lib/utils/metric-emitter.js +32 -0
- package/lib/utils/object.d.ts +13 -0
- package/lib/utils/object.js +18 -0
- package/lib/utils/params.d.ts +150 -0
- package/lib/utils/params.js +206 -0
- package/lib/utils/response.d.ts +117 -0
- package/lib/utils/response.js +223 -0
- package/lib/utils/safe-require.d.ts +13 -0
- package/lib/utils/safe-require.js +19 -0
- package/lib/utils/secret-encryption.d.ts +176 -0
- package/lib/utils/secret-encryption.js +374 -0
- package/lib/utils/ssrf.d.ts +30 -0
- package/lib/utils/ssrf.js +106 -0
- package/lib/validation/ai-schemas.d.ts +56 -0
- package/lib/validation/ai-schemas.js +78 -0
- package/lib/validation/common-schemas.d.ts +133 -0
- package/lib/validation/common-schemas.js +94 -0
- package/lib/validation/index.d.ts +7 -0
- package/lib/validation/index.js +10 -0
- package/lib/validation/message-schemas.d.ts +160 -0
- package/lib/validation/message-schemas.js +156 -0
- package/lib/validation/middleware.d.ts +72 -0
- package/lib/validation/middleware.js +65 -0
- package/lib/validation/pipeline-schemas.d.ts +181 -0
- package/lib/validation/pipeline-schemas.js +101 -0
- package/lib/validation/pipeline-template-schemas.d.ts +155 -0
- package/lib/validation/pipeline-template-schemas.js +66 -0
- package/lib/validation/plugin-schemas.d.ts +171 -0
- package/lib/validation/plugin-schemas.js +81 -0
- package/openapi.yaml +292 -0
- 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
|