founder-schema 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -16
- package/dist/access/access.d.ts +76 -0
- package/dist/access/access.d.ts.map +1 -0
- package/dist/access/access.js +66 -0
- package/dist/access/access.js.map +1 -0
- package/dist/access/permissions.d.ts +153 -0
- package/dist/access/permissions.d.ts.map +1 -0
- package/dist/access/permissions.js +80 -0
- package/dist/access/permissions.js.map +1 -0
- package/dist/access/roles.d.ts +71 -0
- package/dist/access/roles.d.ts.map +1 -0
- package/dist/access/roles.js +72 -0
- package/dist/access/roles.js.map +1 -0
- package/dist/chat.d.ts +4 -4
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +7 -2
- package/dist/chat.js.map +1 -1
- package/dist/core/common.d.ts +304 -0
- package/dist/core/common.d.ts.map +1 -0
- package/dist/core/common.js +340 -0
- package/dist/core/common.js.map +1 -0
- package/dist/core/errors.d.ts +117 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +139 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/limits.d.ts +122 -0
- package/dist/core/limits.d.ts.map +1 -0
- package/dist/core/limits.js +132 -0
- package/dist/core/limits.js.map +1 -0
- package/dist/core/responses.d.ts +207 -0
- package/dist/core/responses.d.ts.map +1 -0
- package/dist/core/responses.js +73 -0
- package/dist/core/responses.js.map +1 -0
- package/dist/core/validation.d.ts +146 -0
- package/dist/core/validation.d.ts.map +1 -0
- package/dist/core/validation.js +152 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/domains/account/account.d.ts +42 -0
- package/dist/domains/account/account.d.ts.map +1 -0
- package/dist/domains/account/account.js +41 -0
- package/dist/domains/account/account.js.map +1 -0
- package/dist/domains/audit/audit.d.ts +169 -0
- package/dist/domains/audit/audit.d.ts.map +1 -0
- package/dist/domains/audit/audit.js +84 -0
- package/dist/domains/audit/audit.js.map +1 -0
- package/dist/domains/chat/chat.d.ts +187 -0
- package/dist/domains/chat/chat.d.ts.map +1 -0
- package/dist/domains/chat/chat.js +189 -0
- package/dist/domains/chat/chat.js.map +1 -0
- package/dist/domains/dashboard/dashboard.d.ts +48 -0
- package/dist/domains/dashboard/dashboard.d.ts.map +1 -0
- package/dist/domains/dashboard/dashboard.js +58 -0
- package/dist/domains/dashboard/dashboard.js.map +1 -0
- package/dist/domains/discover/discover.d.ts +245 -0
- package/dist/domains/discover/discover.d.ts.map +1 -0
- package/dist/domains/discover/discover.js +68 -0
- package/dist/domains/discover/discover.js.map +1 -0
- package/dist/domains/feed/feed.d.ts +1695 -0
- package/dist/domains/feed/feed.d.ts.map +1 -0
- package/dist/domains/feed/feed.js +626 -0
- package/dist/domains/feed/feed.js.map +1 -0
- package/dist/domains/media/media.d.ts +134 -0
- package/dist/domains/media/media.d.ts.map +1 -0
- package/dist/domains/media/media.js +110 -0
- package/dist/domains/media/media.js.map +1 -0
- package/dist/domains/members/members.d.ts +175 -0
- package/dist/domains/members/members.d.ts.map +1 -0
- package/dist/domains/members/members.js +106 -0
- package/dist/domains/members/members.js.map +1 -0
- package/dist/domains/moderation/moderation.d.ts +184 -0
- package/dist/domains/moderation/moderation.d.ts.map +1 -0
- package/dist/domains/moderation/moderation.js +90 -0
- package/dist/domains/moderation/moderation.js.map +1 -0
- package/dist/domains/onboarding/onboarding.d.ts +1018 -0
- package/dist/domains/onboarding/onboarding.d.ts.map +1 -0
- package/dist/domains/onboarding/onboarding.js +717 -0
- package/dist/domains/onboarding/onboarding.js.map +1 -0
- package/dist/domains/profile/founder-profile.d.ts +411 -0
- package/dist/domains/profile/founder-profile.d.ts.map +1 -0
- package/dist/domains/profile/founder-profile.js +202 -0
- package/dist/domains/profile/founder-profile.js.map +1 -0
- package/dist/domains/support/support.d.ts +305 -0
- package/dist/domains/support/support.d.ts.map +1 -0
- package/dist/domains/support/support.js +129 -0
- package/dist/domains/support/support.js.map +1 -0
- package/dist/domains/team/team.d.ts +79 -0
- package/dist/domains/team/team.d.ts.map +1 -0
- package/dist/domains/team/team.js +54 -0
- package/dist/domains/team/team.js.map +1 -0
- package/dist/domains/uploads/uploads.d.ts +101 -0
- package/dist/domains/uploads/uploads.d.ts.map +1 -0
- package/dist/domains/uploads/uploads.js +89 -0
- package/dist/domains/uploads/uploads.js.map +1 -0
- package/dist/env.d.ts +85 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +150 -0
- package/dist/env.js.map +1 -0
- package/dist/index.d.ts +22 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -19
- package/dist/index.js.map +1 -1
- package/dist/onboarding.d.ts +58 -32
- package/dist/onboarding.d.ts.map +1 -1
- package/dist/onboarding.js +155 -32
- package/dist/onboarding.js.map +1 -1
- package/package.json +14 -2
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* founder-schema — The founder profile
|
|
3
|
+
*
|
|
4
|
+
* One member, one profile. The central auth account owns identity (email,
|
|
5
|
+
* verification, deletion); this owns everything Founder Circle shows.
|
|
6
|
+
*
|
|
7
|
+
* @module founder-schema/domains/profile
|
|
8
|
+
*/
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import { chosenUsernameSchema, GOAL_VALUES, goalSchema, httpUrlSchema, industrySchema, locationSchema, objectIdSchema, optionalTrimmedString, skillSchema, startupStageSchema, usernameSchema, } from '../../core/common.js';
|
|
11
|
+
import { ABOUT_MAX, DISPLAY_NAME_MAX, FOUNDED_YEAR_MIN, HEADLINE_MAX, SKILLS_MAX, TEAM_SIZE_MAX, TEAM_SIZE_MIN, VENTURE_NAME_MAX, VENTURE_TAGLINE_MAX, } from '../../core/limits.js';
|
|
12
|
+
import { VALIDATION_MESSAGE } from '../../core/validation.js';
|
|
13
|
+
import { mediaImageSchema } from '../media/media.js';
|
|
14
|
+
// ─── Venture ─────────────────────────────────────────────────────────────────
|
|
15
|
+
/**
|
|
16
|
+
* ⚠ NUMBERS ARE NUMBERS, never coerced. This is a BODY, which arrives as JSON
|
|
17
|
+
* and already carries types. `z.coerce.number()` here accepted `teamSize: true`
|
|
18
|
+
* as 1 and `" "` as 0, and made the schema's input type `unknown`, so a form
|
|
19
|
+
* typed against it could put anything in the field. Coercion belongs to query
|
|
20
|
+
* strings, where everything is text; a form converts its text input before it
|
|
21
|
+
* builds the body.
|
|
22
|
+
*/
|
|
23
|
+
export const ventureSchema = z.object({
|
|
24
|
+
name: z.string().trim().min(1).max(VENTURE_NAME_MAX),
|
|
25
|
+
tagline: optionalTrimmedString(VENTURE_TAGLINE_MAX),
|
|
26
|
+
website: httpUrlSchema.optional(),
|
|
27
|
+
industry: industrySchema,
|
|
28
|
+
stage: startupStageSchema,
|
|
29
|
+
/** Team size including the founder. Optional: a solo pre-idea founder has none to report. */
|
|
30
|
+
teamSize: z.number().int().min(TEAM_SIZE_MIN).max(TEAM_SIZE_MAX).optional(),
|
|
31
|
+
/**
|
|
32
|
+
* Not in the future. The bound is read per parse, never captured once.
|
|
33
|
+
*
|
|
34
|
+
* `.max(new Date().getUTCFullYear())` looks equivalent and is not: it is
|
|
35
|
+
* evaluated when this module is imported, so a server process still up on
|
|
36
|
+
* 1 January rejects the new year until somebody restarts it. The backend's
|
|
37
|
+
* Mongoose model deliberately carries no upper bound for the same reason, a
|
|
38
|
+
* `max` there being frozen at schema construction. This is where the ceiling
|
|
39
|
+
* lives.
|
|
40
|
+
*/
|
|
41
|
+
foundedYear: z
|
|
42
|
+
.number()
|
|
43
|
+
.int()
|
|
44
|
+
.min(FOUNDED_YEAR_MIN)
|
|
45
|
+
.refine((year) => year <= new Date().getUTCFullYear(), {
|
|
46
|
+
error: VALIDATION_MESSAGE.foundedYearFuture,
|
|
47
|
+
})
|
|
48
|
+
.optional(),
|
|
49
|
+
});
|
|
50
|
+
// ─── Profile ─────────────────────────────────────────────────────────────────
|
|
51
|
+
/**
|
|
52
|
+
* The founder's off-platform links. ONE shape: onboarding's contact step picks
|
|
53
|
+
* its three from this, so a link one side knows can never be missing on the
|
|
54
|
+
* other. (It was written twice until 0.2.0, and the profile side lacked
|
|
55
|
+
* Instagram, which onboarding collected.)
|
|
56
|
+
*
|
|
57
|
+
* Defined without a default, because the read side wants one and the write
|
|
58
|
+
* side must not have one; each adds its own.
|
|
59
|
+
*/
|
|
60
|
+
export const founderLinksSchema = z.object({
|
|
61
|
+
linkedin: httpUrlSchema.optional(),
|
|
62
|
+
x: httpUrlSchema.optional(),
|
|
63
|
+
instagram: httpUrlSchema.optional(),
|
|
64
|
+
github: httpUrlSchema.optional(),
|
|
65
|
+
website: httpUrlSchema.optional(),
|
|
66
|
+
});
|
|
67
|
+
/**
|
|
68
|
+
* The profile as the owner sees it. A peer sees the redacted projection
|
|
69
|
+
* (`founderCardSchema`), which is built on the server and never by dropping
|
|
70
|
+
* fields on the client.
|
|
71
|
+
*/
|
|
72
|
+
export const founderProfileSchema = z.object({
|
|
73
|
+
id: z.string(),
|
|
74
|
+
userId: z.string(),
|
|
75
|
+
username: usernameSchema,
|
|
76
|
+
displayName: z.string().trim().min(1).max(DISPLAY_NAME_MAX),
|
|
77
|
+
headline: optionalTrimmedString(HEADLINE_MAX),
|
|
78
|
+
about: optionalTrimmedString(ABOUT_MAX),
|
|
79
|
+
/**
|
|
80
|
+
* Plain `z.url()`, NOT `httpUrlSchema`, and the difference is deliberate.
|
|
81
|
+
*
|
|
82
|
+
* No member can write this field: it appears in no write body, and
|
|
83
|
+
* `public-surface.test.ts` asserts that and will fail the day somebody adds it
|
|
84
|
+
* to a form. So the scheme restriction would be defending a field that takes
|
|
85
|
+
* no member input, while refusing hosts we legitimately serve avatars from,
|
|
86
|
+
* our own dev host among them. A read schema that rejects our own response is
|
|
87
|
+
* a self-inflicted outage.
|
|
88
|
+
*
|
|
89
|
+
* ⚠️ That argument holds only while nothing member-supplied reaches this
|
|
90
|
+
* field. Two sources write an avatar today and NEITHER is a member typing a
|
|
91
|
+
* URL: our own upload path, and the central-auth `user.created` /
|
|
92
|
+
* `user.updated` webhook, which carries the Google OAuth picture. If a
|
|
93
|
+
* "paste a link to your photo" control is ever added, this goes back to
|
|
94
|
+
* `httpUrlSchema` and the ruling behind it is void.
|
|
95
|
+
*
|
|
96
|
+
* WHICH picture: the member's uploaded photo at its small size (192, see
|
|
97
|
+
* `MEDIA_SIZES.avatar`), else their sign-in provider's picture, else absent.
|
|
98
|
+
* Every surface that shows this member resolves it the same way.
|
|
99
|
+
*/
|
|
100
|
+
avatarUrl: z.url().optional(),
|
|
101
|
+
/**
|
|
102
|
+
* The uploaded photo itself, every size, when the member has one. Absent
|
|
103
|
+
* means `avatarUrl` (if any) is the sign-in provider's picture, so a client
|
|
104
|
+
* offers "Remove photo" only when this is present. Not on `founderCardSchema`:
|
|
105
|
+
* a card needs one small picture, not the set.
|
|
106
|
+
*/
|
|
107
|
+
avatar: mediaImageSchema.optional(),
|
|
108
|
+
location: locationSchema.optional(),
|
|
109
|
+
venture: ventureSchema.optional(),
|
|
110
|
+
skills: z.array(skillSchema).max(SKILLS_MAX).default([]),
|
|
111
|
+
goals: z.array(goalSchema).max(GOAL_VALUES.length).default([]),
|
|
112
|
+
links: founderLinksSchema.default({}),
|
|
113
|
+
/** Onboarding is finished and the member is visible in Discover. */
|
|
114
|
+
isOnboarded: z.boolean().default(false),
|
|
115
|
+
/** Member paused their own profile. Hidden from Discover, keeps their data. */
|
|
116
|
+
isActive: z.boolean().default(true),
|
|
117
|
+
createdAt: z.iso.datetime(),
|
|
118
|
+
updatedAt: z.iso.datetime(),
|
|
119
|
+
});
|
|
120
|
+
/**
|
|
121
|
+
* What one founder sees of another in Discover and on a profile page.
|
|
122
|
+
*
|
|
123
|
+
* Built server-side. Keep it a strict subset of `founderProfileSchema` — if a
|
|
124
|
+
* private field ever needs to show here, that is a product decision, not an
|
|
125
|
+
* accident of spreading the full document.
|
|
126
|
+
*/
|
|
127
|
+
export const founderCardSchema = founderProfileSchema.pick({
|
|
128
|
+
id: true,
|
|
129
|
+
username: true,
|
|
130
|
+
displayName: true,
|
|
131
|
+
headline: true,
|
|
132
|
+
avatarUrl: true,
|
|
133
|
+
location: true,
|
|
134
|
+
venture: true,
|
|
135
|
+
skills: true,
|
|
136
|
+
goals: true,
|
|
137
|
+
});
|
|
138
|
+
// ─── Write bodies ────────────────────────────────────────────────────────────
|
|
139
|
+
/**
|
|
140
|
+
* A text field the member is allowed to empty.
|
|
141
|
+
*
|
|
142
|
+
* Three states, and they are deliberately distinct:
|
|
143
|
+
* absent do not touch this field
|
|
144
|
+
* null clear it (the backend turns this into an `$unset`)
|
|
145
|
+
* "" REJECTED
|
|
146
|
+
*
|
|
147
|
+
* The empty string is refused on purpose. A form that forgets to convert a
|
|
148
|
+
* cleared input to `null` then fails loudly at the boundary, instead of being
|
|
149
|
+
* silently dropped and leaving the member staring at text they just deleted.
|
|
150
|
+
* `optionalTrimmedString` does the dropping, which is right for a create body
|
|
151
|
+
* and wrong for a patch.
|
|
152
|
+
*/
|
|
153
|
+
const clearableText = (maxLength) => z.string().trim().min(1, VALIDATION_MESSAGE.clearWithNull).max(maxLength).nullable().optional();
|
|
154
|
+
/**
|
|
155
|
+
* Patch body for the Me tab. Only the keys the member actually changed.
|
|
156
|
+
*
|
|
157
|
+
* ⚠️ Written as its own object ON PURPOSE. Do NOT rebuild it as
|
|
158
|
+
* `founderProfileSchema.pick({...}).partial()`: `.partial()` does not switch
|
|
159
|
+
* off `.default()`, so `skills`, `goals` and `links` would materialise as
|
|
160
|
+
* `[]`, `[]` and `{}` on every request, including an empty one. Under a `$set`
|
|
161
|
+
* that wipes a member's skills and every one of their links the moment they
|
|
162
|
+
* edit their display name. `tsc` cannot see it and the request looks correct in
|
|
163
|
+
* the network tab.
|
|
164
|
+
*
|
|
165
|
+
* The rule this comes from: defaults belong on the READ side. A write body
|
|
166
|
+
* states what the caller sent and nothing else.
|
|
167
|
+
*/
|
|
168
|
+
export const updateFounderProfileBodySchema = z.object({
|
|
169
|
+
displayName: z.string().trim().min(1).max(DISPLAY_NAME_MAX).optional(),
|
|
170
|
+
headline: clearableText(HEADLINE_MAX),
|
|
171
|
+
about: clearableText(ABOUT_MAX),
|
|
172
|
+
/** Carries the whole location; `locationSchema` has no default to fill a gap. */
|
|
173
|
+
location: locationSchema.optional(),
|
|
174
|
+
venture: ventureSchema.optional(),
|
|
175
|
+
skills: z.array(skillSchema).max(SKILLS_MAX).optional(),
|
|
176
|
+
goals: z.array(goalSchema).max(GOAL_VALUES.length).optional(),
|
|
177
|
+
/** Sent whole. To drop one link, send the object without that key. */
|
|
178
|
+
links: founderLinksSchema.optional(),
|
|
179
|
+
});
|
|
180
|
+
/**
|
|
181
|
+
* Username change. Separate from the profile patch because it needs a
|
|
182
|
+
* uniqueness check, and it refuses a reserved handle (`chosenUsernameSchema`).
|
|
183
|
+
*/
|
|
184
|
+
export const updateUsernameBodySchema = z.object({ username: chosenUsernameSchema });
|
|
185
|
+
/** Availability probe behind the username field. */
|
|
186
|
+
export const usernameAvailabilitySchema = z.object({
|
|
187
|
+
username: usernameSchema,
|
|
188
|
+
available: z.boolean(),
|
|
189
|
+
/** Present when unavailable: `taken` or `reserved`. */
|
|
190
|
+
reason: z.enum(['taken', 'reserved']).optional(),
|
|
191
|
+
});
|
|
192
|
+
// ─── The member's own photo ──────────────────────────────────────────────────
|
|
193
|
+
/**
|
|
194
|
+
* `PUT /api/v1/profiles/me/avatar`: use a completed upload (purpose `avatar`)
|
|
195
|
+
* as the member's photo. Answers with the updated `founderProfileSchema`.
|
|
196
|
+
*
|
|
197
|
+
* The previous photo is deleted from storage, not merely unlinked, so its old
|
|
198
|
+
* address stops working. `DELETE /api/v1/profiles/me/avatar` removes the photo
|
|
199
|
+
* the same way and the member falls back to their sign-in provider's picture.
|
|
200
|
+
*/
|
|
201
|
+
export const setAvatarBodySchema = z.object({ mediaId: objectIdSchema });
|
|
202
|
+
//# sourceMappingURL=founder-profile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"founder-profile.js","sourceRoot":"","sources":["../../../src/domains/profile/founder-profile.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * founder-schema — The founder profile\n *\n * One member, one profile. The central auth account owns identity (email,\n * verification, deletion); this owns everything Founder Circle shows.\n *\n * @module founder-schema/domains/profile\n */\n\nimport { z } from 'zod';\nimport {\n chosenUsernameSchema,\n GOAL_VALUES,\n goalSchema,\n httpUrlSchema,\n industrySchema,\n locationSchema,\n objectIdSchema,\n optionalTrimmedString,\n skillSchema,\n startupStageSchema,\n usernameSchema,\n} from '../../core/common.js';\nimport {\n ABOUT_MAX,\n DISPLAY_NAME_MAX,\n FOUNDED_YEAR_MIN,\n HEADLINE_MAX,\n SKILLS_MAX,\n TEAM_SIZE_MAX,\n TEAM_SIZE_MIN,\n VENTURE_NAME_MAX,\n VENTURE_TAGLINE_MAX,\n} from '../../core/limits.js';\nimport { VALIDATION_MESSAGE } from '../../core/validation.js';\nimport { mediaImageSchema } from '../media/media.js';\n\n// ─── Venture ─────────────────────────────────────────────────────────────────\n\n/**\n * ⚠ NUMBERS ARE NUMBERS, never coerced. This is a BODY, which arrives as JSON\n * and already carries types. `z.coerce.number()` here accepted `teamSize: true`\n * as 1 and `\" \"` as 0, and made the schema's input type `unknown`, so a form\n * typed against it could put anything in the field. Coercion belongs to query\n * strings, where everything is text; a form converts its text input before it\n * builds the body.\n */\nexport const ventureSchema = z.object({\n name: z.string().trim().min(1).max(VENTURE_NAME_MAX),\n tagline: optionalTrimmedString(VENTURE_TAGLINE_MAX),\n website: httpUrlSchema.optional(),\n industry: industrySchema,\n stage: startupStageSchema,\n /** Team size including the founder. Optional: a solo pre-idea founder has none to report. */\n teamSize: z.number().int().min(TEAM_SIZE_MIN).max(TEAM_SIZE_MAX).optional(),\n /**\n * Not in the future. The bound is read per parse, never captured once.\n *\n * `.max(new Date().getUTCFullYear())` looks equivalent and is not: it is\n * evaluated when this module is imported, so a server process still up on\n * 1 January rejects the new year until somebody restarts it. The backend's\n * Mongoose model deliberately carries no upper bound for the same reason, a\n * `max` there being frozen at schema construction. This is where the ceiling\n * lives.\n */\n foundedYear: z\n .number()\n .int()\n .min(FOUNDED_YEAR_MIN)\n .refine((year) => year <= new Date().getUTCFullYear(), {\n error: VALIDATION_MESSAGE.foundedYearFuture,\n })\n .optional(),\n});\nexport type Venture = z.infer<typeof ventureSchema>;\n\n// ─── Profile ─────────────────────────────────────────────────────────────────\n\n/**\n * The founder's off-platform links. ONE shape: onboarding's contact step picks\n * its three from this, so a link one side knows can never be missing on the\n * other. (It was written twice until 0.2.0, and the profile side lacked\n * Instagram, which onboarding collected.)\n *\n * Defined without a default, because the read side wants one and the write\n * side must not have one; each adds its own.\n */\nexport const founderLinksSchema = z.object({\n linkedin: httpUrlSchema.optional(),\n x: httpUrlSchema.optional(),\n instagram: httpUrlSchema.optional(),\n github: httpUrlSchema.optional(),\n website: httpUrlSchema.optional(),\n});\nexport type FounderLinks = z.infer<typeof founderLinksSchema>;\n\n/**\n * The profile as the owner sees it. A peer sees the redacted projection\n * (`founderCardSchema`), which is built on the server and never by dropping\n * fields on the client.\n */\nexport const founderProfileSchema = z.object({\n id: z.string(),\n userId: z.string(),\n username: usernameSchema,\n displayName: z.string().trim().min(1).max(DISPLAY_NAME_MAX),\n headline: optionalTrimmedString(HEADLINE_MAX),\n about: optionalTrimmedString(ABOUT_MAX),\n /**\n * Plain `z.url()`, NOT `httpUrlSchema`, and the difference is deliberate.\n *\n * No member can write this field: it appears in no write body, and\n * `public-surface.test.ts` asserts that and will fail the day somebody adds it\n * to a form. So the scheme restriction would be defending a field that takes\n * no member input, while refusing hosts we legitimately serve avatars from,\n * our own dev host among them. A read schema that rejects our own response is\n * a self-inflicted outage.\n *\n * ⚠️ That argument holds only while nothing member-supplied reaches this\n * field. Two sources write an avatar today and NEITHER is a member typing a\n * URL: our own upload path, and the central-auth `user.created` /\n * `user.updated` webhook, which carries the Google OAuth picture. If a\n * \"paste a link to your photo\" control is ever added, this goes back to\n * `httpUrlSchema` and the ruling behind it is void.\n *\n * WHICH picture: the member's uploaded photo at its small size (192, see\n * `MEDIA_SIZES.avatar`), else their sign-in provider's picture, else absent.\n * Every surface that shows this member resolves it the same way.\n */\n avatarUrl: z.url().optional(),\n /**\n * The uploaded photo itself, every size, when the member has one. Absent\n * means `avatarUrl` (if any) is the sign-in provider's picture, so a client\n * offers \"Remove photo\" only when this is present. Not on `founderCardSchema`:\n * a card needs one small picture, not the set.\n */\n avatar: mediaImageSchema.optional(),\n location: locationSchema.optional(),\n venture: ventureSchema.optional(),\n skills: z.array(skillSchema).max(SKILLS_MAX).default([]),\n goals: z.array(goalSchema).max(GOAL_VALUES.length).default([]),\n links: founderLinksSchema.default({}),\n /** Onboarding is finished and the member is visible in Discover. */\n isOnboarded: z.boolean().default(false),\n /** Member paused their own profile. Hidden from Discover, keeps their data. */\n isActive: z.boolean().default(true),\n createdAt: z.iso.datetime(),\n updatedAt: z.iso.datetime(),\n});\nexport type FounderProfile = z.infer<typeof founderProfileSchema>;\n\n/**\n * What one founder sees of another in Discover and on a profile page.\n *\n * Built server-side. Keep it a strict subset of `founderProfileSchema` — if a\n * private field ever needs to show here, that is a product decision, not an\n * accident of spreading the full document.\n */\nexport const founderCardSchema = founderProfileSchema.pick({\n id: true,\n username: true,\n displayName: true,\n headline: true,\n avatarUrl: true,\n location: true,\n venture: true,\n skills: true,\n goals: true,\n});\nexport type FounderCard = z.infer<typeof founderCardSchema>;\n\n// ─── Write bodies ────────────────────────────────────────────────────────────\n\n/**\n * A text field the member is allowed to empty.\n *\n * Three states, and they are deliberately distinct:\n * absent do not touch this field\n * null clear it (the backend turns this into an `$unset`)\n * \"\" REJECTED\n *\n * The empty string is refused on purpose. A form that forgets to convert a\n * cleared input to `null` then fails loudly at the boundary, instead of being\n * silently dropped and leaving the member staring at text they just deleted.\n * `optionalTrimmedString` does the dropping, which is right for a create body\n * and wrong for a patch.\n */\nconst clearableText = (maxLength: number) =>\n z.string().trim().min(1, VALIDATION_MESSAGE.clearWithNull).max(maxLength).nullable().optional();\n\n/**\n * Patch body for the Me tab. Only the keys the member actually changed.\n *\n * ⚠️ Written as its own object ON PURPOSE. Do NOT rebuild it as\n * `founderProfileSchema.pick({...}).partial()`: `.partial()` does not switch\n * off `.default()`, so `skills`, `goals` and `links` would materialise as\n * `[]`, `[]` and `{}` on every request, including an empty one. Under a `$set`\n * that wipes a member's skills and every one of their links the moment they\n * edit their display name. `tsc` cannot see it and the request looks correct in\n * the network tab.\n *\n * The rule this comes from: defaults belong on the READ side. A write body\n * states what the caller sent and nothing else.\n */\nexport const updateFounderProfileBodySchema = z.object({\n displayName: z.string().trim().min(1).max(DISPLAY_NAME_MAX).optional(),\n headline: clearableText(HEADLINE_MAX),\n about: clearableText(ABOUT_MAX),\n /** Carries the whole location; `locationSchema` has no default to fill a gap. */\n location: locationSchema.optional(),\n venture: ventureSchema.optional(),\n skills: z.array(skillSchema).max(SKILLS_MAX).optional(),\n goals: z.array(goalSchema).max(GOAL_VALUES.length).optional(),\n /** Sent whole. To drop one link, send the object without that key. */\n links: founderLinksSchema.optional(),\n});\nexport type UpdateFounderProfileBody = z.infer<typeof updateFounderProfileBodySchema>;\n\n/**\n * Username change. Separate from the profile patch because it needs a\n * uniqueness check, and it refuses a reserved handle (`chosenUsernameSchema`).\n */\nexport const updateUsernameBodySchema = z.object({ username: chosenUsernameSchema });\nexport type UpdateUsernameBody = z.infer<typeof updateUsernameBodySchema>;\n\n/** Availability probe behind the username field. */\nexport const usernameAvailabilitySchema = z.object({\n username: usernameSchema,\n available: z.boolean(),\n /** Present when unavailable: `taken` or `reserved`. */\n reason: z.enum(['taken', 'reserved']).optional(),\n});\nexport type UsernameAvailability = z.infer<typeof usernameAvailabilitySchema>;\n\n// ─── The member's own photo ──────────────────────────────────────────────────\n\n/**\n * `PUT /api/v1/profiles/me/avatar`: use a completed upload (purpose `avatar`)\n * as the member's photo. Answers with the updated `founderProfileSchema`.\n *\n * The previous photo is deleted from storage, not merely unlinked, so its old\n * address stops working. `DELETE /api/v1/profiles/me/avatar` removes the photo\n * the same way and the member falls back to their sign-in provider's picture.\n */\nexport const setAvatarBodySchema = z.object({ mediaId: objectIdSchema });\nexport type SetAvatarBody = z.infer<typeof setAvatarBodySchema>;\n"]}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* founder-schema: support
|
|
3
|
+
*
|
|
4
|
+
* A member asks for help; a support agent answers. One conversation per
|
|
5
|
+
* problem, a ticket, with its messages in order.
|
|
6
|
+
*
|
|
7
|
+
* POST /api/v1/support/tickets { subject, category, body }
|
|
8
|
+
* GET /api/v1/support/tickets ?scope&status&assignee&limit&cursor
|
|
9
|
+
* GET /api/v1/support/tickets/:ticketId
|
|
10
|
+
* POST /api/v1/support/tickets/:ticketId/messages { body } reply
|
|
11
|
+
* POST /api/v1/support/tickets/:ticketId/claim an agent takes it
|
|
12
|
+
* POST /api/v1/support/tickets/:ticketId/resolve closed as solved
|
|
13
|
+
* POST /api/v1/support/tickets/:ticketId/reopen the member reopens
|
|
14
|
+
*
|
|
15
|
+
* GET /api/v1/support/saved-replies
|
|
16
|
+
* POST /api/v1/support/saved-replies { title, body }
|
|
17
|
+
* PUT /api/v1/support/saved-replies/:replyId { title, body }
|
|
18
|
+
* DELETE /api/v1/support/saved-replies/:replyId
|
|
19
|
+
*
|
|
20
|
+
* THE STATES
|
|
21
|
+
* open waiting on support: new, or the member wrote last
|
|
22
|
+
* waiting waiting on the member: an agent wrote last
|
|
23
|
+
* resolved closed; the member can reopen it, and writing reopens it
|
|
24
|
+
*
|
|
25
|
+
* `scope=mine` is the tickets the asker raised, whatever role they act as;
|
|
26
|
+
* `scope=queue` is every ticket the active role may read (all of them for a
|
|
27
|
+
* support agent or manager, only their own for a member). An agent replying to
|
|
28
|
+
* an unclaimed ticket claims it.
|
|
29
|
+
*
|
|
30
|
+
* Every message is free text from a person: untrusted, shown as text.
|
|
31
|
+
*
|
|
32
|
+
* @module founder-schema/domains/support
|
|
33
|
+
*/
|
|
34
|
+
import { z } from 'zod';
|
|
35
|
+
/** What the problem is about: routes it, and lets the team see patterns. Append only. */
|
|
36
|
+
export declare const TICKET_CATEGORY_VALUES: readonly ["account", "profile", "feed", "chat", "safety", "bug", "other"];
|
|
37
|
+
export declare const ticketCategorySchema: z.ZodEnum<{
|
|
38
|
+
chat: "chat";
|
|
39
|
+
feed: "feed";
|
|
40
|
+
other: "other";
|
|
41
|
+
account: "account";
|
|
42
|
+
profile: "profile";
|
|
43
|
+
safety: "safety";
|
|
44
|
+
bug: "bug";
|
|
45
|
+
}>;
|
|
46
|
+
export type TicketCategory = z.infer<typeof ticketCategorySchema>;
|
|
47
|
+
export declare const TICKET_STATUS_VALUES: readonly ["open", "waiting", "resolved"];
|
|
48
|
+
export declare const ticketStatusSchema: z.ZodEnum<{
|
|
49
|
+
open: "open";
|
|
50
|
+
waiting: "waiting";
|
|
51
|
+
resolved: "resolved";
|
|
52
|
+
}>;
|
|
53
|
+
export type TicketStatus = z.infer<typeof ticketStatusSchema>;
|
|
54
|
+
export declare const TICKET_SCOPE_VALUES: readonly ["mine", "queue"];
|
|
55
|
+
export declare const ticketScopeSchema: z.ZodEnum<{
|
|
56
|
+
mine: "mine";
|
|
57
|
+
queue: "queue";
|
|
58
|
+
}>;
|
|
59
|
+
export type TicketScope = z.infer<typeof ticketScopeSchema>;
|
|
60
|
+
/** The queue's assignee filter: mine, nobody's, or anyone's. */
|
|
61
|
+
export declare const TICKET_ASSIGNEE_FILTER_VALUES: readonly ["me", "unassigned", "any"];
|
|
62
|
+
export declare const ticketAssigneeFilterSchema: z.ZodEnum<{
|
|
63
|
+
any: "any";
|
|
64
|
+
me: "me";
|
|
65
|
+
unassigned: "unassigned";
|
|
66
|
+
}>;
|
|
67
|
+
export type TicketAssigneeFilter = z.infer<typeof ticketAssigneeFilterSchema>;
|
|
68
|
+
/** One message. `from` says which side wrote it; the agent's name is shown to the member. */
|
|
69
|
+
export declare const ticketMessageSchema: z.ZodObject<{
|
|
70
|
+
id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
71
|
+
from: z.ZodEnum<{
|
|
72
|
+
member: "member";
|
|
73
|
+
agent: "agent";
|
|
74
|
+
}>;
|
|
75
|
+
author: z.ZodObject<{
|
|
76
|
+
id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
77
|
+
displayName: z.ZodString;
|
|
78
|
+
}, z.core.$strip>;
|
|
79
|
+
body: z.ZodString;
|
|
80
|
+
at: z.ZodISODateTime;
|
|
81
|
+
}, z.core.$strip>;
|
|
82
|
+
export type TicketMessage = z.infer<typeof ticketMessageSchema>;
|
|
83
|
+
/** A ticket in a list. */
|
|
84
|
+
export declare const ticketSummarySchema: z.ZodObject<{
|
|
85
|
+
id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
86
|
+
subject: z.ZodString;
|
|
87
|
+
category: z.ZodEnum<{
|
|
88
|
+
chat: "chat";
|
|
89
|
+
feed: "feed";
|
|
90
|
+
other: "other";
|
|
91
|
+
account: "account";
|
|
92
|
+
profile: "profile";
|
|
93
|
+
safety: "safety";
|
|
94
|
+
bug: "bug";
|
|
95
|
+
}>;
|
|
96
|
+
status: z.ZodEnum<{
|
|
97
|
+
open: "open";
|
|
98
|
+
waiting: "waiting";
|
|
99
|
+
resolved: "resolved";
|
|
100
|
+
}>;
|
|
101
|
+
member: z.ZodObject<{
|
|
102
|
+
id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
103
|
+
displayName: z.ZodString;
|
|
104
|
+
avatarUrl: z.ZodNullable<z.ZodURL>;
|
|
105
|
+
}, z.core.$strip>;
|
|
106
|
+
assignee: z.ZodNullable<z.ZodObject<{
|
|
107
|
+
id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
108
|
+
displayName: z.ZodString;
|
|
109
|
+
}, z.core.$strip>>;
|
|
110
|
+
messageCount: z.ZodNumber;
|
|
111
|
+
createdAt: z.ZodISODateTime;
|
|
112
|
+
lastMessageAt: z.ZodISODateTime;
|
|
113
|
+
lastFrom: z.ZodEnum<{
|
|
114
|
+
member: "member";
|
|
115
|
+
agent: "agent";
|
|
116
|
+
}>;
|
|
117
|
+
}, z.core.$strip>;
|
|
118
|
+
export type TicketSummary = z.infer<typeof ticketSummarySchema>;
|
|
119
|
+
export declare const ticketPageSchema: z.ZodObject<{
|
|
120
|
+
items: z.ZodArray<z.ZodObject<{
|
|
121
|
+
id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
122
|
+
subject: z.ZodString;
|
|
123
|
+
category: z.ZodEnum<{
|
|
124
|
+
chat: "chat";
|
|
125
|
+
feed: "feed";
|
|
126
|
+
other: "other";
|
|
127
|
+
account: "account";
|
|
128
|
+
profile: "profile";
|
|
129
|
+
safety: "safety";
|
|
130
|
+
bug: "bug";
|
|
131
|
+
}>;
|
|
132
|
+
status: z.ZodEnum<{
|
|
133
|
+
open: "open";
|
|
134
|
+
waiting: "waiting";
|
|
135
|
+
resolved: "resolved";
|
|
136
|
+
}>;
|
|
137
|
+
member: z.ZodObject<{
|
|
138
|
+
id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
139
|
+
displayName: z.ZodString;
|
|
140
|
+
avatarUrl: z.ZodNullable<z.ZodURL>;
|
|
141
|
+
}, z.core.$strip>;
|
|
142
|
+
assignee: z.ZodNullable<z.ZodObject<{
|
|
143
|
+
id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
144
|
+
displayName: z.ZodString;
|
|
145
|
+
}, z.core.$strip>>;
|
|
146
|
+
messageCount: z.ZodNumber;
|
|
147
|
+
createdAt: z.ZodISODateTime;
|
|
148
|
+
lastMessageAt: z.ZodISODateTime;
|
|
149
|
+
lastFrom: z.ZodEnum<{
|
|
150
|
+
member: "member";
|
|
151
|
+
agent: "agent";
|
|
152
|
+
}>;
|
|
153
|
+
}, z.core.$strip>>;
|
|
154
|
+
nextCursor: z.ZodNullable<z.ZodString>;
|
|
155
|
+
}, z.core.$strip>;
|
|
156
|
+
export type TicketPage = z.infer<typeof ticketPageSchema>;
|
|
157
|
+
/** A ticket with its whole conversation and what the asker may do with it. */
|
|
158
|
+
export declare const ticketDetailSchema: z.ZodObject<{
|
|
159
|
+
id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
160
|
+
subject: z.ZodString;
|
|
161
|
+
category: z.ZodEnum<{
|
|
162
|
+
chat: "chat";
|
|
163
|
+
feed: "feed";
|
|
164
|
+
other: "other";
|
|
165
|
+
account: "account";
|
|
166
|
+
profile: "profile";
|
|
167
|
+
safety: "safety";
|
|
168
|
+
bug: "bug";
|
|
169
|
+
}>;
|
|
170
|
+
status: z.ZodEnum<{
|
|
171
|
+
open: "open";
|
|
172
|
+
waiting: "waiting";
|
|
173
|
+
resolved: "resolved";
|
|
174
|
+
}>;
|
|
175
|
+
member: z.ZodObject<{
|
|
176
|
+
id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
177
|
+
displayName: z.ZodString;
|
|
178
|
+
avatarUrl: z.ZodNullable<z.ZodURL>;
|
|
179
|
+
}, z.core.$strip>;
|
|
180
|
+
assignee: z.ZodNullable<z.ZodObject<{
|
|
181
|
+
id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
182
|
+
displayName: z.ZodString;
|
|
183
|
+
}, z.core.$strip>>;
|
|
184
|
+
messageCount: z.ZodNumber;
|
|
185
|
+
createdAt: z.ZodISODateTime;
|
|
186
|
+
lastMessageAt: z.ZodISODateTime;
|
|
187
|
+
lastFrom: z.ZodEnum<{
|
|
188
|
+
member: "member";
|
|
189
|
+
agent: "agent";
|
|
190
|
+
}>;
|
|
191
|
+
messages: z.ZodArray<z.ZodObject<{
|
|
192
|
+
id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
193
|
+
from: z.ZodEnum<{
|
|
194
|
+
member: "member";
|
|
195
|
+
agent: "agent";
|
|
196
|
+
}>;
|
|
197
|
+
author: z.ZodObject<{
|
|
198
|
+
id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
199
|
+
displayName: z.ZodString;
|
|
200
|
+
}, z.core.$strip>;
|
|
201
|
+
body: z.ZodString;
|
|
202
|
+
at: z.ZodISODateTime;
|
|
203
|
+
}, z.core.$strip>>;
|
|
204
|
+
resolvedAt: z.ZodNullable<z.ZodISODateTime>;
|
|
205
|
+
viewer: z.ZodObject<{
|
|
206
|
+
canReply: z.ZodBoolean;
|
|
207
|
+
canClaim: z.ZodBoolean;
|
|
208
|
+
canResolve: z.ZodBoolean;
|
|
209
|
+
canReopen: z.ZodBoolean;
|
|
210
|
+
asAgent: z.ZodBoolean;
|
|
211
|
+
}, z.core.$strip>;
|
|
212
|
+
}, z.core.$strip>;
|
|
213
|
+
export type TicketDetail = z.infer<typeof ticketDetailSchema>;
|
|
214
|
+
/** `:ticketId` in the paths above. */
|
|
215
|
+
export declare const ticketParamsSchema: z.ZodObject<{
|
|
216
|
+
ticketId: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
217
|
+
}, z.core.$strip>;
|
|
218
|
+
export type TicketParams = z.infer<typeof ticketParamsSchema>;
|
|
219
|
+
/** `GET /support/tickets`. Mine by default; status and assignee narrow it. */
|
|
220
|
+
export declare const ticketListQuerySchema: z.ZodObject<{
|
|
221
|
+
limit: z.ZodPreprocess<z.ZodDefault<z.ZodCoercedNumber<unknown>>, unknown>;
|
|
222
|
+
cursor: z.ZodOptional<z.ZodString>;
|
|
223
|
+
scope: z.ZodPreprocess<z.ZodDefault<z.ZodEnum<{
|
|
224
|
+
mine: "mine";
|
|
225
|
+
queue: "queue";
|
|
226
|
+
}>>, unknown>;
|
|
227
|
+
status: z.ZodPreprocess<z.ZodOptional<z.ZodEnum<{
|
|
228
|
+
open: "open";
|
|
229
|
+
waiting: "waiting";
|
|
230
|
+
resolved: "resolved";
|
|
231
|
+
}>>, unknown>;
|
|
232
|
+
assignee: z.ZodPreprocess<z.ZodDefault<z.ZodEnum<{
|
|
233
|
+
any: "any";
|
|
234
|
+
me: "me";
|
|
235
|
+
unassigned: "unassigned";
|
|
236
|
+
}>>, unknown>;
|
|
237
|
+
}, z.core.$strip>;
|
|
238
|
+
export type TicketListQuery = z.infer<typeof ticketListQuerySchema>;
|
|
239
|
+
export type TicketListQueryInput = z.input<typeof ticketListQuerySchema>;
|
|
240
|
+
/** `POST /support/tickets`: what went wrong, in the member's words. */
|
|
241
|
+
export declare const createTicketBodySchema: z.ZodObject<{
|
|
242
|
+
subject: z.ZodString;
|
|
243
|
+
category: z.ZodEnum<{
|
|
244
|
+
chat: "chat";
|
|
245
|
+
feed: "feed";
|
|
246
|
+
other: "other";
|
|
247
|
+
account: "account";
|
|
248
|
+
profile: "profile";
|
|
249
|
+
safety: "safety";
|
|
250
|
+
bug: "bug";
|
|
251
|
+
}>;
|
|
252
|
+
body: z.ZodString;
|
|
253
|
+
}, z.core.$strip>;
|
|
254
|
+
export type CreateTicketBody = z.infer<typeof createTicketBodySchema>;
|
|
255
|
+
/** `POST /support/tickets/:ticketId/messages`. */
|
|
256
|
+
export declare const replyTicketBodySchema: z.ZodObject<{
|
|
257
|
+
body: z.ZodString;
|
|
258
|
+
}, z.core.$strip>;
|
|
259
|
+
export type ReplyTicketBody = z.infer<typeof replyTicketBodySchema>;
|
|
260
|
+
/** An answer the support team reuses. Inserted into a reply, then edited, never sent as is. */
|
|
261
|
+
export declare const savedReplySchema: z.ZodObject<{
|
|
262
|
+
id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
263
|
+
title: z.ZodString;
|
|
264
|
+
body: z.ZodString;
|
|
265
|
+
author: z.ZodObject<{
|
|
266
|
+
id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
267
|
+
displayName: z.ZodString;
|
|
268
|
+
}, z.core.$strip>;
|
|
269
|
+
updatedAt: z.ZodISODateTime;
|
|
270
|
+
viewer: z.ZodObject<{
|
|
271
|
+
canEdit: z.ZodBoolean;
|
|
272
|
+
canDelete: z.ZodBoolean;
|
|
273
|
+
}, z.core.$strip>;
|
|
274
|
+
}, z.core.$strip>;
|
|
275
|
+
export type SavedReply = z.infer<typeof savedReplySchema>;
|
|
276
|
+
/** Every saved reply, by title. Bounded by `SAVED_REPLIES_MAX`, so not paged. */
|
|
277
|
+
export declare const savedReplyListSchema: z.ZodObject<{
|
|
278
|
+
items: z.ZodArray<z.ZodObject<{
|
|
279
|
+
id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
280
|
+
title: z.ZodString;
|
|
281
|
+
body: z.ZodString;
|
|
282
|
+
author: z.ZodObject<{
|
|
283
|
+
id: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
284
|
+
displayName: z.ZodString;
|
|
285
|
+
}, z.core.$strip>;
|
|
286
|
+
updatedAt: z.ZodISODateTime;
|
|
287
|
+
viewer: z.ZodObject<{
|
|
288
|
+
canEdit: z.ZodBoolean;
|
|
289
|
+
canDelete: z.ZodBoolean;
|
|
290
|
+
}, z.core.$strip>;
|
|
291
|
+
}, z.core.$strip>>;
|
|
292
|
+
}, z.core.$strip>;
|
|
293
|
+
export type SavedReplyList = z.infer<typeof savedReplyListSchema>;
|
|
294
|
+
/** `:replyId` in the paths above. */
|
|
295
|
+
export declare const savedReplyParamsSchema: z.ZodObject<{
|
|
296
|
+
replyId: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
297
|
+
}, z.core.$strip>;
|
|
298
|
+
export type SavedReplyParams = z.infer<typeof savedReplyParamsSchema>;
|
|
299
|
+
/** `POST` and `PUT /support/saved-replies`. */
|
|
300
|
+
export declare const savedReplyBodySchema: z.ZodObject<{
|
|
301
|
+
title: z.ZodString;
|
|
302
|
+
body: z.ZodString;
|
|
303
|
+
}, z.core.$strip>;
|
|
304
|
+
export type SavedReplyBody = z.infer<typeof savedReplyBodySchema>;
|
|
305
|
+
//# sourceMappingURL=support.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"support.d.ts","sourceRoot":"","sources":["../../../src/domains/support/support.ts"],"names":[],"mappings":"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"}
|