lazyconvex 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +926 -0
- package/dist/components/index.mjs +937 -0
- package/dist/error-D4GuI0ot.mjs +71 -0
- package/dist/file-field-BqVgy8xY.mjs +205 -0
- package/dist/form-BXJK_j10.d.mts +99 -0
- package/dist/index.d.mts +433 -0
- package/dist/index.mjs +1 -0
- package/dist/index2.d.mts +5 -0
- package/dist/index3.d.mts +35 -0
- package/dist/index4.d.mts +101 -0
- package/dist/index5.d.mts +842 -0
- package/dist/next/index.mjs +151 -0
- package/dist/org-CmJBb8z-.d.mts +56 -0
- package/dist/react/index.mjs +158 -0
- package/dist/retry.d.mts +12 -0
- package/dist/retry.mjs +35 -0
- package/dist/schema.d.mts +23 -0
- package/dist/schema.mjs +15 -0
- package/dist/server/index.mjs +2572 -0
- package/dist/types-DWBVRtit.d.mts +322 -0
- package/dist/use-online-status-CMr73Jlk.mjs +155 -0
- package/dist/use-upload-DtELytQi.mjs +95 -0
- package/dist/zod.d.mts +18 -0
- package/dist/zod.mjs +87 -0
- package/package.json +40 -0
- package/src/components/editors-section.tsx +86 -0
- package/src/components/fields.tsx +884 -0
- package/src/components/file-field.tsx +234 -0
- package/src/components/form.tsx +191 -0
- package/src/components/index.ts +11 -0
- package/src/components/offline-indicator.tsx +15 -0
- package/src/components/org-avatar.tsx +13 -0
- package/src/components/permission-guard.tsx +36 -0
- package/src/components/role-badge.tsx +14 -0
- package/src/components/suspense-wrap.tsx +8 -0
- package/src/index.ts +40 -0
- package/src/next/active-org.ts +33 -0
- package/src/next/auth.ts +9 -0
- package/src/next/image.ts +134 -0
- package/src/next/index.ts +3 -0
- package/src/react/form-meta.ts +53 -0
- package/src/react/form.ts +201 -0
- package/src/react/index.ts +8 -0
- package/src/react/org.tsx +96 -0
- package/src/react/use-active-org.ts +48 -0
- package/src/react/use-bulk-selection.ts +47 -0
- package/src/react/use-online-status.ts +21 -0
- package/src/react/use-optimistic.ts +54 -0
- package/src/react/use-upload.ts +101 -0
- package/src/retry.ts +47 -0
- package/src/schema.ts +30 -0
- package/src/server/cache-crud.ts +175 -0
- package/src/server/check-schema.ts +29 -0
- package/src/server/child.ts +98 -0
- package/src/server/crud.ts +384 -0
- package/src/server/db.ts +7 -0
- package/src/server/error.ts +39 -0
- package/src/server/file.ts +372 -0
- package/src/server/helpers.ts +214 -0
- package/src/server/index.ts +12 -0
- package/src/server/org-crud.ts +307 -0
- package/src/server/org-helpers.ts +54 -0
- package/src/server/org.ts +572 -0
- package/src/server/schema-helpers.ts +107 -0
- package/src/server/setup.ts +138 -0
- package/src/server/test-crud.ts +211 -0
- package/src/server/test.ts +554 -0
- package/src/server/types.ts +392 -0
- package/src/server/unique.ts +28 -0
- package/src/zod.ts +141 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import type { CustomBuilder } from 'convex-helpers/server/zod4'
|
|
2
|
+
import type {
|
|
3
|
+
ActionBuilder,
|
|
4
|
+
FunctionVisibility,
|
|
5
|
+
GenericDataModel,
|
|
6
|
+
MutationBuilder,
|
|
7
|
+
PaginationOptions,
|
|
8
|
+
paginationOptsValidator,
|
|
9
|
+
QueryBuilder,
|
|
10
|
+
RegisteredAction,
|
|
11
|
+
RegisteredMutation,
|
|
12
|
+
RegisteredQuery
|
|
13
|
+
} from 'convex/server'
|
|
14
|
+
import type { GenericId } from 'convex/values'
|
|
15
|
+
import type { z as _, ZodNullable, ZodNumber, ZodObject, ZodOptional, ZodRawShape } from 'zod/v4'
|
|
16
|
+
|
|
17
|
+
interface BaseBuilders {
|
|
18
|
+
m: Mb
|
|
19
|
+
q: Qb
|
|
20
|
+
}
|
|
21
|
+
interface CacheBuilders<DM extends GenericDataModel = GenericDataModel> {
|
|
22
|
+
action: ActionBuilder<DM, 'public'>
|
|
23
|
+
cm: Mb
|
|
24
|
+
cq: Qb
|
|
25
|
+
internalMutation: MutationBuilder<DM, 'internal'>
|
|
26
|
+
internalQuery: QueryBuilder<DM, 'internal'>
|
|
27
|
+
mutation: MutationBuilder<DM, 'public'>
|
|
28
|
+
query: QueryBuilder<DM, 'public'>
|
|
29
|
+
}
|
|
30
|
+
interface CacheOptions<S extends ZodRawShape, K extends keyof _.output<ZodObject<S>> & string> {
|
|
31
|
+
fetcher?: (c: ActionCtxLike, key: _.output<ZodObject<S>>[K]) => Promise<_.output<ZodObject<S>>>
|
|
32
|
+
key: K
|
|
33
|
+
schema: ZodObject<S>
|
|
34
|
+
table: string
|
|
35
|
+
ttl?: number
|
|
36
|
+
}
|
|
37
|
+
interface ChildConfig {
|
|
38
|
+
foreignKey: string
|
|
39
|
+
index?: string
|
|
40
|
+
parent: string
|
|
41
|
+
schema: ZodObject<ZodRawShape>
|
|
42
|
+
}
|
|
43
|
+
interface ComparisonOp<V> {
|
|
44
|
+
$between?: [V, V]
|
|
45
|
+
$gt?: V
|
|
46
|
+
$gte?: V
|
|
47
|
+
$lt?: V
|
|
48
|
+
$lte?: V
|
|
49
|
+
}
|
|
50
|
+
interface CrudBuilders extends BaseBuilders {
|
|
51
|
+
children: Record<string, ChildConfig>
|
|
52
|
+
cm: Mb
|
|
53
|
+
cq: Qb
|
|
54
|
+
pq: Qb
|
|
55
|
+
}
|
|
56
|
+
interface CrudOptions<S extends ZodRawShape> {
|
|
57
|
+
auth?: { where?: WhereOf<S> }
|
|
58
|
+
cascade?: boolean
|
|
59
|
+
pub?: { where?: WhereOf<S> }
|
|
60
|
+
search?: 'index'
|
|
61
|
+
softDelete?: boolean
|
|
62
|
+
}
|
|
63
|
+
interface DbCtx {
|
|
64
|
+
db: DbLike
|
|
65
|
+
}
|
|
66
|
+
interface MutCtx extends UserCtx {
|
|
67
|
+
storage: StorageLike
|
|
68
|
+
}
|
|
69
|
+
type Rec = Record<string, unknown>
|
|
70
|
+
interface UserCtx extends DbCtx {
|
|
71
|
+
user: Rec
|
|
72
|
+
}
|
|
73
|
+
const ERROR_MESSAGES = {
|
|
74
|
+
ALREADY_ORG_MEMBER: 'Already a member of this organization',
|
|
75
|
+
CANNOT_MODIFY_ADMIN: 'Admins cannot modify other admins',
|
|
76
|
+
CANNOT_MODIFY_OWNER: 'Cannot modify the owner',
|
|
77
|
+
CHUNK_ALREADY_UPLOADED: 'Chunk already uploaded',
|
|
78
|
+
CHUNK_NOT_FOUND: 'Chunk not found',
|
|
79
|
+
CONFLICT: 'Conflict detected',
|
|
80
|
+
EDITOR_REQUIRED: 'Editor permission required',
|
|
81
|
+
FILE_NOT_FOUND: 'File not found',
|
|
82
|
+
FILE_TOO_LARGE: 'File too large',
|
|
83
|
+
FORBIDDEN: 'Forbidden',
|
|
84
|
+
INCOMPLETE_UPLOAD: 'Incomplete upload',
|
|
85
|
+
INSUFFICIENT_ORG_ROLE: 'Insufficient permissions',
|
|
86
|
+
INVALID_FILE_TYPE: 'Invalid file type',
|
|
87
|
+
INVALID_INVITE: 'Invalid invite',
|
|
88
|
+
INVALID_MESSAGE: 'Invalid message',
|
|
89
|
+
INVALID_SESSION_STATE: 'Invalid session state',
|
|
90
|
+
INVALID_TOOL_ARGS: 'Invalid tool arguments',
|
|
91
|
+
INVALID_WHERE: 'Invalid filters',
|
|
92
|
+
INVITE_EXPIRED: 'Invite has expired',
|
|
93
|
+
JOIN_REQUEST_EXISTS: 'Join request already exists',
|
|
94
|
+
LIMIT_EXCEEDED: 'Limit exceeded',
|
|
95
|
+
MESSAGE_NOT_SAVED: 'Message not saved',
|
|
96
|
+
MUST_TRANSFER_OWNERSHIP: 'Must transfer ownership before leaving',
|
|
97
|
+
NO_FETCHER: 'No fetcher configured',
|
|
98
|
+
NO_PRECEDING_USER_MESSAGE: 'No preceding user message',
|
|
99
|
+
NOT_AUTHENTICATED: 'Please log in',
|
|
100
|
+
NOT_AUTHORIZED: 'Not authorized',
|
|
101
|
+
NOT_FOUND: 'Not found',
|
|
102
|
+
NOT_ORG_MEMBER: 'Not a member of this organization',
|
|
103
|
+
ORG_SLUG_TAKEN: 'Organization slug already taken',
|
|
104
|
+
RATE_LIMITED: 'Too many requests',
|
|
105
|
+
SESSION_NOT_FOUND: 'Session not found',
|
|
106
|
+
TARGET_MUST_BE_ADMIN: 'Can only transfer ownership to an admin',
|
|
107
|
+
UNAUTHORIZED: 'Unauthorized',
|
|
108
|
+
USER_NOT_FOUND: 'User not found'
|
|
109
|
+
} as const
|
|
110
|
+
type Ab<V extends FunctionVisibility = 'public'> = CustomBuilder<
|
|
111
|
+
'action',
|
|
112
|
+
Record<string, never>,
|
|
113
|
+
Rec,
|
|
114
|
+
Record<string, never>,
|
|
115
|
+
unknown,
|
|
116
|
+
V,
|
|
117
|
+
Rec
|
|
118
|
+
>
|
|
119
|
+
interface ActionCtxLike {
|
|
120
|
+
runMutation: (ref: string, args: Rec) => Promise<unknown>
|
|
121
|
+
runQuery: (ref: string, args: Rec) => Promise<unknown>
|
|
122
|
+
}
|
|
123
|
+
interface AuthorInfo {
|
|
124
|
+
[key: string]: unknown
|
|
125
|
+
email?: string
|
|
126
|
+
image?: string
|
|
127
|
+
name?: string
|
|
128
|
+
}
|
|
129
|
+
interface CacheCrudResult<S extends ZodRawShape> {
|
|
130
|
+
all: RegisteredQuery<'public', Rec, DocBase<S>[]>
|
|
131
|
+
create: RegisteredMutation<'public', Rec, string>
|
|
132
|
+
get: RegisteredQuery<'public', Rec, (DocBase<S> & { cacheHit: true }) | null>
|
|
133
|
+
getInternal: RegisteredQuery<'internal', Rec, DocBase<S> | null>
|
|
134
|
+
invalidate: RegisteredMutation<'public', Rec, DocBase<S> | null>
|
|
135
|
+
list: RegisteredQuery<'public', Rec, PaginatedResult<DocBase<S>>>
|
|
136
|
+
load: RegisteredAction<'public', Rec, _.output<ZodObject<S>> & { cacheHit: boolean }>
|
|
137
|
+
purge: RegisteredMutation<'public', Rec, number>
|
|
138
|
+
read: RegisteredQuery<'public', Rec, DocBase<S> | null>
|
|
139
|
+
refresh: RegisteredAction<'public', Rec, _.output<ZodObject<S>> & { cacheHit: boolean }>
|
|
140
|
+
rm: RegisteredMutation<'public', Rec, DocBase<S> | null>
|
|
141
|
+
set: RegisteredMutation<'internal', Rec, void>
|
|
142
|
+
update: RegisteredMutation<'public', Rec, DocBase<S>>
|
|
143
|
+
}
|
|
144
|
+
interface CanEditOpts {
|
|
145
|
+
acl: boolean
|
|
146
|
+
doc: {
|
|
147
|
+
editors?: string[]
|
|
148
|
+
userId: string
|
|
149
|
+
}
|
|
150
|
+
role: OrgRole
|
|
151
|
+
userId: string
|
|
152
|
+
}
|
|
153
|
+
interface ChildCrudResult<S extends ZodRawShape> {
|
|
154
|
+
create: RegisteredMutation<'public', Rec, string>
|
|
155
|
+
get: RegisteredQuery<'public', Rec, DocBase<S> | null>
|
|
156
|
+
list: RegisteredQuery<'public', Rec, DocBase<S>[]>
|
|
157
|
+
rm: RegisteredMutation<'public', Rec, DocBase<S>>
|
|
158
|
+
update: RegisteredMutation<'public', Rec, DocBase<S> | null>
|
|
159
|
+
}
|
|
160
|
+
interface CrudReadApi<S extends ZodRawShape, V extends FunctionVisibility = 'public'> {
|
|
161
|
+
all: RegisteredQuery<V, { where?: WhereOf<S> }, EnrichedDoc<S>[]>
|
|
162
|
+
count: RegisteredQuery<V, { where?: WhereOf<S> }, number>
|
|
163
|
+
list: RegisteredQuery<V, { paginationOpts: PaginationOptions; where?: WhereOf<S> }, PaginatedResult<EnrichedDoc<S>>>
|
|
164
|
+
read: RegisteredQuery<V, { id: string; own?: boolean; where?: WhereOf<S> }, EnrichedDoc<S> | null>
|
|
165
|
+
search: RegisteredQuery<V, { fields?: string[]; query: string; where?: WhereOf<S> }, EnrichedDoc<S>[]>
|
|
166
|
+
}
|
|
167
|
+
interface CrudResult<S extends ZodRawShape> {
|
|
168
|
+
auth: CrudReadApi<S>
|
|
169
|
+
authIndexed: RegisteredQuery<
|
|
170
|
+
'public',
|
|
171
|
+
{ index: string; key: string; value: string; where?: WhereOf<S> },
|
|
172
|
+
EnrichedDoc<S>[]
|
|
173
|
+
>
|
|
174
|
+
bulkRm: RegisteredMutation<'public', { ids: string[] }, number>
|
|
175
|
+
bulkUpdate: RegisteredMutation<'public', { data: Partial<_.output<ZodObject<S>>>; ids: string[] }, unknown[]>
|
|
176
|
+
create: RegisteredMutation<'public', _.output<ZodObject<S>>, string>
|
|
177
|
+
pub: CrudReadApi<S>
|
|
178
|
+
pubIndexed: RegisteredQuery<
|
|
179
|
+
'public',
|
|
180
|
+
{ index: string; key: string; value: string; where?: WhereOf<S> },
|
|
181
|
+
EnrichedDoc<S>[]
|
|
182
|
+
>
|
|
183
|
+
restore?: RegisteredMutation<'public', { id: string }, DocBase<S>>
|
|
184
|
+
rm: RegisteredMutation<'public', { id: string }, DocBase<S>>
|
|
185
|
+
update: RegisteredMutation<
|
|
186
|
+
'public',
|
|
187
|
+
Partial<_.output<ZodObject<S>>> & { expectedUpdatedAt?: number; id: string },
|
|
188
|
+
DocBase<S>
|
|
189
|
+
>
|
|
190
|
+
}
|
|
191
|
+
interface DbLike extends DbReadLike {
|
|
192
|
+
delete: (id: string) => Promise<void>
|
|
193
|
+
insert: (table: string, data: Rec) => Promise<string>
|
|
194
|
+
patch: (id: string, data: Rec) => Promise<void>
|
|
195
|
+
system: DbReadLike
|
|
196
|
+
}
|
|
197
|
+
interface DbReadLike {
|
|
198
|
+
get: (id: string) => Promise<null | Rec>
|
|
199
|
+
query: (table: string) => QueryLike
|
|
200
|
+
}
|
|
201
|
+
type DocBase<S extends ZodRawShape> = _.output<ZodObject<S>> & {
|
|
202
|
+
_creationTime: number
|
|
203
|
+
_id: string
|
|
204
|
+
updatedAt: number
|
|
205
|
+
}
|
|
206
|
+
type EnrichedDoc<S extends ZodRawShape> = WithUrls<
|
|
207
|
+
DocBase<S> & {
|
|
208
|
+
author: AuthorInfo | null
|
|
209
|
+
own: boolean | null
|
|
210
|
+
userId: string
|
|
211
|
+
}
|
|
212
|
+
>
|
|
213
|
+
type ErrorCode = keyof typeof ERROR_MESSAGES
|
|
214
|
+
type FID = GenericId<'_storage'>
|
|
215
|
+
interface FilterLike {
|
|
216
|
+
and: (a: unknown, b: unknown) => unknown
|
|
217
|
+
eq: (a: unknown, b: unknown) => unknown
|
|
218
|
+
field: (name: string) => unknown
|
|
219
|
+
gt: (a: unknown, b: unknown) => unknown
|
|
220
|
+
gte: (a: unknown, b: unknown) => unknown
|
|
221
|
+
lt: (a: unknown, b: unknown) => unknown
|
|
222
|
+
lte: (a: unknown, b: unknown) => unknown
|
|
223
|
+
or: (a: unknown, b: unknown) => unknown
|
|
224
|
+
}
|
|
225
|
+
interface IndexLike {
|
|
226
|
+
eq: (field: string, value: unknown) => IndexLike
|
|
227
|
+
}
|
|
228
|
+
type Mb<V extends FunctionVisibility = 'public'> = CustomBuilder<
|
|
229
|
+
'mutation',
|
|
230
|
+
Record<string, never>,
|
|
231
|
+
Rec,
|
|
232
|
+
Record<string, never>,
|
|
233
|
+
unknown,
|
|
234
|
+
V,
|
|
235
|
+
Rec
|
|
236
|
+
>
|
|
237
|
+
interface MutationCtxLike {
|
|
238
|
+
auth: { getUserIdentity: () => Promise<unknown> }
|
|
239
|
+
db: DbLike
|
|
240
|
+
storage: StorageLike
|
|
241
|
+
}
|
|
242
|
+
interface OrgCrudResult<S extends ZodRawShape> {
|
|
243
|
+
addEditor: RegisteredMutation<'public', Rec, DocBase<S> | null>
|
|
244
|
+
all: RegisteredQuery<'public', Rec, OrgEnrichedDoc<S>[]>
|
|
245
|
+
bulkRm: RegisteredMutation<'public', Rec, number>
|
|
246
|
+
bulkUpdate: RegisteredMutation<'public', Rec, DocBase<S>[]>
|
|
247
|
+
count: RegisteredQuery<'public', Rec, number>
|
|
248
|
+
create: RegisteredMutation<'public', Rec, string>
|
|
249
|
+
editors: RegisteredQuery<'public', Rec, { email: string; name: string; userId: string }[]>
|
|
250
|
+
list: RegisteredQuery<'public', Rec, PaginatedResult<OrgEnrichedDoc<S>>>
|
|
251
|
+
read: RegisteredQuery<'public', Rec, OrgEnrichedDoc<S>>
|
|
252
|
+
removeEditor: RegisteredMutation<'public', Rec, DocBase<S> | null>
|
|
253
|
+
rm: RegisteredMutation<'public', Rec, DocBase<S>>
|
|
254
|
+
setEditors: RegisteredMutation<'public', Rec, DocBase<S> | null>
|
|
255
|
+
update: RegisteredMutation<'public', Rec, DocBase<S> | null>
|
|
256
|
+
}
|
|
257
|
+
type OrgEnrichedDoc<S extends ZodRawShape> = WithUrls<
|
|
258
|
+
DocBase<S> & {
|
|
259
|
+
author: AuthorInfo | null
|
|
260
|
+
orgId: string
|
|
261
|
+
own: boolean | null
|
|
262
|
+
userId: string
|
|
263
|
+
}
|
|
264
|
+
>
|
|
265
|
+
type OrgRole = 'admin' | 'member' | 'owner'
|
|
266
|
+
interface PaginatedResult<D> {
|
|
267
|
+
continueCursor: string
|
|
268
|
+
isDone: boolean
|
|
269
|
+
page: D[]
|
|
270
|
+
}
|
|
271
|
+
type PaginationOptsShape = Record<keyof typeof paginationOptsValidator.fields, ZodNullable | ZodNumber | ZodOptional>
|
|
272
|
+
type Qb<V extends FunctionVisibility = 'public'> = CustomBuilder<
|
|
273
|
+
'query',
|
|
274
|
+
Record<string, never>,
|
|
275
|
+
Rec,
|
|
276
|
+
Record<string, never>,
|
|
277
|
+
unknown,
|
|
278
|
+
V,
|
|
279
|
+
Rec
|
|
280
|
+
>
|
|
281
|
+
interface QueryCtxLike {
|
|
282
|
+
auth: { getUserIdentity: () => Promise<unknown> }
|
|
283
|
+
db: DbLike
|
|
284
|
+
storage: StorageLike
|
|
285
|
+
}
|
|
286
|
+
interface QueryLike {
|
|
287
|
+
collect: () => Promise<Rec[]>
|
|
288
|
+
filter: (fn: (fb: FilterLike) => unknown) => QueryLike
|
|
289
|
+
first: () => Promise<null | Rec>
|
|
290
|
+
order: (dir: 'asc' | 'desc') => QueryLike
|
|
291
|
+
paginate: (opts: Rec) => Promise<{ continueCursor: string; isDone: boolean; page: Rec[] }>
|
|
292
|
+
take: (n: number) => Promise<Rec[]>
|
|
293
|
+
unique: () => Promise<null | Rec>
|
|
294
|
+
withIndex: (name: string, fn?: (ib: IndexLike) => unknown) => QueryLike
|
|
295
|
+
withSearchIndex: (name: string, fn: (sb: SearchLike) => unknown) => QueryLike
|
|
296
|
+
}
|
|
297
|
+
interface ReadCtx {
|
|
298
|
+
db: DbLike
|
|
299
|
+
storage: StorageLike
|
|
300
|
+
viewerId: null | string
|
|
301
|
+
withAuthor: <T extends { userId: string }>(
|
|
302
|
+
docs: T[]
|
|
303
|
+
) => Promise<
|
|
304
|
+
(T & {
|
|
305
|
+
author: null | Rec
|
|
306
|
+
own: boolean | null
|
|
307
|
+
})[]
|
|
308
|
+
>
|
|
309
|
+
}
|
|
310
|
+
interface SearchLike {
|
|
311
|
+
search: (field: string, query: string) => unknown
|
|
312
|
+
}
|
|
313
|
+
interface SetupConfig<DM extends GenericDataModel = GenericDataModel> {
|
|
314
|
+
action: ActionBuilder<DM, 'public'>
|
|
315
|
+
children?: Record<string, ChildConfig>
|
|
316
|
+
getAuthUserId: (ctx: never) => Promise<null | string>
|
|
317
|
+
internalMutation: MutationBuilder<DM, 'internal'>
|
|
318
|
+
internalQuery: QueryBuilder<DM, 'internal'>
|
|
319
|
+
mutation: MutationBuilder<DM, 'public'>
|
|
320
|
+
orgCascadeTables?: string[]
|
|
321
|
+
orgSchema?: ZodObject<ZodRawShape>
|
|
322
|
+
query: QueryBuilder<DM, 'public'>
|
|
323
|
+
}
|
|
324
|
+
interface StorageLike {
|
|
325
|
+
delete: (id: string) => Promise<void>
|
|
326
|
+
getUrl: (id: string) => Promise<null | string>
|
|
327
|
+
}
|
|
328
|
+
type UrlKey<K, V> =
|
|
329
|
+
NonNullable<V> extends FID | FID[] | readonly FID[] ? `${K & string}Url${NonNullable<V> extends FID ? '' : 's'}` : never
|
|
330
|
+
type UrlVal<V> =
|
|
331
|
+
NonNullable<V> extends FID | FID[] | readonly FID[]
|
|
332
|
+
? NonNullable<V> extends FID
|
|
333
|
+
? null | string
|
|
334
|
+
: (null | string)[]
|
|
335
|
+
: never
|
|
336
|
+
type WhereFieldValue<V> = ComparisonOp<V> | V
|
|
337
|
+
type WhereGroupOf<S extends ZodRawShape> = {
|
|
338
|
+
[K in keyof _.output<ZodObject<S>>]?: WhereFieldValue<_.output<ZodObject<S>>[K]>
|
|
339
|
+
} & {
|
|
340
|
+
own?: boolean
|
|
341
|
+
}
|
|
342
|
+
type WhereOf<S extends ZodRawShape> = WhereGroupOf<S> & {
|
|
343
|
+
or?: WhereGroupOf<S>[]
|
|
344
|
+
}
|
|
345
|
+
type WithUrls<D> = D & { [K in keyof D as UrlKey<K, D[K]>]: UrlVal<D[K]> }
|
|
346
|
+
export type {
|
|
347
|
+
Ab,
|
|
348
|
+
ActionCtxLike,
|
|
349
|
+
AuthorInfo,
|
|
350
|
+
BaseBuilders,
|
|
351
|
+
CacheBuilders,
|
|
352
|
+
CacheCrudResult,
|
|
353
|
+
CacheOptions,
|
|
354
|
+
CanEditOpts,
|
|
355
|
+
ChildConfig,
|
|
356
|
+
ChildCrudResult,
|
|
357
|
+
ComparisonOp,
|
|
358
|
+
CrudBuilders,
|
|
359
|
+
CrudOptions,
|
|
360
|
+
CrudReadApi,
|
|
361
|
+
CrudResult,
|
|
362
|
+
DbCtx,
|
|
363
|
+
DbLike,
|
|
364
|
+
DbReadLike,
|
|
365
|
+
DocBase,
|
|
366
|
+
EnrichedDoc,
|
|
367
|
+
ErrorCode,
|
|
368
|
+
FID,
|
|
369
|
+
FilterLike,
|
|
370
|
+
IndexLike,
|
|
371
|
+
Mb,
|
|
372
|
+
MutationCtxLike,
|
|
373
|
+
MutCtx,
|
|
374
|
+
OrgCrudResult,
|
|
375
|
+
OrgEnrichedDoc,
|
|
376
|
+
OrgRole,
|
|
377
|
+
PaginatedResult,
|
|
378
|
+
PaginationOptsShape,
|
|
379
|
+
Qb,
|
|
380
|
+
QueryCtxLike,
|
|
381
|
+
QueryLike,
|
|
382
|
+
ReadCtx,
|
|
383
|
+
Rec,
|
|
384
|
+
SearchLike,
|
|
385
|
+
SetupConfig,
|
|
386
|
+
StorageLike,
|
|
387
|
+
UserCtx,
|
|
388
|
+
WhereGroupOf,
|
|
389
|
+
WhereOf,
|
|
390
|
+
WithUrls
|
|
391
|
+
}
|
|
392
|
+
export { ERROR_MESSAGES }
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { RegisteredQuery } from 'convex/server'
|
|
2
|
+
|
|
3
|
+
import { zid } from 'convex-helpers/server/zod4'
|
|
4
|
+
import { string } from 'zod/v4'
|
|
5
|
+
|
|
6
|
+
import type { FilterLike, Qb, QueryCtxLike } from './types'
|
|
7
|
+
|
|
8
|
+
const makeUnique = ({
|
|
9
|
+
field,
|
|
10
|
+
pq,
|
|
11
|
+
table
|
|
12
|
+
}: {
|
|
13
|
+
field: string
|
|
14
|
+
pq: Qb
|
|
15
|
+
table: string
|
|
16
|
+
}): RegisteredQuery<'public', { exclude?: string; value: string }, boolean> =>
|
|
17
|
+
pq({
|
|
18
|
+
args: { exclude: zid(table).optional(), value: string() },
|
|
19
|
+
handler: (async (c: QueryCtxLike, { exclude, value }: { exclude?: string; value: string }) => {
|
|
20
|
+
const existing = await c.db
|
|
21
|
+
.query(table)
|
|
22
|
+
.filter((f: FilterLike) => f.eq(f.field(field), value))
|
|
23
|
+
.first()
|
|
24
|
+
return !(existing as null | Record<string, unknown>) || (existing as Record<string, unknown>)._id === exclude
|
|
25
|
+
}) as never
|
|
26
|
+
}) as never
|
|
27
|
+
|
|
28
|
+
export { makeUnique }
|
package/src/zod.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { core, output, ZodObject, ZodRawShape, ZodType } from 'zod/v4'
|
|
2
|
+
|
|
3
|
+
type CvMeta = 'file' | 'files'
|
|
4
|
+
type DefType = core.$ZodTypeDef['type']
|
|
5
|
+
type ZodSchema = ZodType
|
|
6
|
+
|
|
7
|
+
const WRAPPERS: ReadonlySet<DefType> = new Set<DefType>([
|
|
8
|
+
'catch',
|
|
9
|
+
'default',
|
|
10
|
+
'nullable',
|
|
11
|
+
'optional',
|
|
12
|
+
'prefault',
|
|
13
|
+
'readonly'
|
|
14
|
+
]),
|
|
15
|
+
unwrapZod = (
|
|
16
|
+
schema: unknown
|
|
17
|
+
): {
|
|
18
|
+
def: undefined | ZodSchema['def']
|
|
19
|
+
schema: undefined | ZodSchema
|
|
20
|
+
type: '' | DefType
|
|
21
|
+
} => {
|
|
22
|
+
let cur = schema as undefined | ZodSchema
|
|
23
|
+
while (cur && typeof cur === 'object' && 'type' in cur) {
|
|
24
|
+
if (!WRAPPERS.has(cur.type)) return { def: cur.def, schema: cur, type: cur.type }
|
|
25
|
+
cur = (cur.def as { innerType?: ZodSchema }).innerType
|
|
26
|
+
}
|
|
27
|
+
return { def: undefined, schema: undefined, type: '' }
|
|
28
|
+
},
|
|
29
|
+
isOptionalField = (schema: unknown): boolean => {
|
|
30
|
+
let cur = schema as undefined | ZodSchema
|
|
31
|
+
while (cur && typeof cur === 'object' && 'type' in cur) {
|
|
32
|
+
if (cur.type === 'optional') return true
|
|
33
|
+
if (!WRAPPERS.has(cur.type)) return false
|
|
34
|
+
cur = (cur.def as { innerType?: ZodSchema }).innerType
|
|
35
|
+
}
|
|
36
|
+
return false
|
|
37
|
+
},
|
|
38
|
+
elementOf = (s: undefined | ZodSchema): unknown => (s?.def as undefined | { element?: unknown })?.element,
|
|
39
|
+
cvMetaOf = (schema: undefined | ZodSchema): CvMeta | undefined => {
|
|
40
|
+
if (!schema || typeof schema.meta !== 'function') return
|
|
41
|
+
const m = schema.meta() as undefined | { cv?: unknown }
|
|
42
|
+
if (m && typeof m === 'object') {
|
|
43
|
+
const { cv } = m
|
|
44
|
+
if (cv === 'file' || cv === 'files') return cv
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
isArrayType = (t: '' | DefType) => t === 'array',
|
|
48
|
+
isBooleanType = (t: '' | DefType) => t === 'boolean',
|
|
49
|
+
isDateType = (t: '' | DefType) => t === 'date',
|
|
50
|
+
isNumberType = (t: '' | DefType) => t === 'number',
|
|
51
|
+
isStringType = (t: '' | DefType) => t === 'string' || t === 'enum',
|
|
52
|
+
cvFileKindOf = (schema: unknown): CvMeta | undefined => {
|
|
53
|
+
const { schema: s, type } = unwrapZod(schema),
|
|
54
|
+
cv = cvMetaOf(s)
|
|
55
|
+
if (cv) return cv
|
|
56
|
+
if (isArrayType(type) && cvMetaOf(elementOf(s) as undefined | ZodSchema) === 'file') return 'files'
|
|
57
|
+
},
|
|
58
|
+
enumToOptions = <T extends string>(
|
|
59
|
+
schema: { options: readonly T[] },
|
|
60
|
+
transform?: (v: T) => string
|
|
61
|
+
): {
|
|
62
|
+
label: string
|
|
63
|
+
value: T
|
|
64
|
+
}[] =>
|
|
65
|
+
schema.options.map(v => ({
|
|
66
|
+
label: transform?.(v) ?? v.charAt(0).toUpperCase() + v.slice(1),
|
|
67
|
+
value: v
|
|
68
|
+
})),
|
|
69
|
+
requiredPartial = <S extends ZodObject<ZodRawShape>>(
|
|
70
|
+
schema: S,
|
|
71
|
+
requiredKeys: (keyof S['shape'])[]
|
|
72
|
+
): ZodObject<ZodRawShape> => {
|
|
73
|
+
const partial = schema.partial(),
|
|
74
|
+
required = Object.fromEntries(requiredKeys.map(k => [k, true])) as Record<string, true>
|
|
75
|
+
return partial.required(required) as ZodObject<ZodRawShape>
|
|
76
|
+
},
|
|
77
|
+
// eslint-disable-next-line max-statements
|
|
78
|
+
defaultValue = (schema: unknown): unknown => {
|
|
79
|
+
const { schema: base, type } = unwrapZod(schema),
|
|
80
|
+
fk = cvFileKindOf(schema)
|
|
81
|
+
if (fk === 'file') return null
|
|
82
|
+
if (fk === 'files') return []
|
|
83
|
+
if (isArrayType(type)) return []
|
|
84
|
+
if (isBooleanType(type)) return false
|
|
85
|
+
if (isNumberType(type)) return 0
|
|
86
|
+
if (isStringType(type)) {
|
|
87
|
+
if (base && 'options' in base) {
|
|
88
|
+
const opts = (base as { options: readonly string[] }).options
|
|
89
|
+
if (opts.length) return opts[0]
|
|
90
|
+
}
|
|
91
|
+
return ''
|
|
92
|
+
}
|
|
93
|
+
if (isDateType(type)) return null
|
|
94
|
+
const inner = (base?.def as undefined | { innerType?: unknown })?.innerType
|
|
95
|
+
if (inner) return defaultValue(inner)
|
|
96
|
+
},
|
|
97
|
+
defaultValues = <S extends ZodObject<ZodRawShape>>(schema: S): output<S> => {
|
|
98
|
+
const result: Record<string, unknown> = {}
|
|
99
|
+
// biome-ignore lint/nursery/noForIn: iterating shape keys with hasOwn guard
|
|
100
|
+
for (const k in schema.shape) if (Object.hasOwn(schema.shape, k)) result[k] = defaultValue(schema.shape[k])
|
|
101
|
+
return result as output<S>
|
|
102
|
+
},
|
|
103
|
+
pickValues = <S extends ZodObject<ZodRawShape>>(schema: S, doc: object): output<S> => {
|
|
104
|
+
const d = doc as Record<string, unknown>,
|
|
105
|
+
result: Record<string, unknown> = {}
|
|
106
|
+
// biome-ignore lint/nursery/noForIn: iterating shape keys with hasOwn guard
|
|
107
|
+
for (const k in schema.shape) if (Object.hasOwn(schema.shape, k)) result[k] = d[k] ?? defaultValue(schema.shape[k])
|
|
108
|
+
return result as output<S>
|
|
109
|
+
},
|
|
110
|
+
coerceOptionals = <S extends ZodObject<ZodRawShape>>(schema: S, data: output<S>): output<S> => {
|
|
111
|
+
const result: Record<string, unknown> = { ...data }
|
|
112
|
+
for (const k of Object.keys(result))
|
|
113
|
+
if (isOptionalField(schema.shape[k]) && isStringType(unwrapZod(schema.shape[k]).type)) {
|
|
114
|
+
const v = result[k]
|
|
115
|
+
if (typeof v === 'string') {
|
|
116
|
+
const trimmed = v.trim()
|
|
117
|
+
result[k] = trimmed.length > 0 ? trimmed : undefined
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return result as output<S>
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export type { CvMeta, DefType, ZodSchema }
|
|
124
|
+
export {
|
|
125
|
+
coerceOptionals,
|
|
126
|
+
cvFileKindOf,
|
|
127
|
+
cvMetaOf,
|
|
128
|
+
defaultValue,
|
|
129
|
+
defaultValues,
|
|
130
|
+
elementOf,
|
|
131
|
+
enumToOptions,
|
|
132
|
+
isArrayType,
|
|
133
|
+
isBooleanType,
|
|
134
|
+
isDateType,
|
|
135
|
+
isNumberType,
|
|
136
|
+
isOptionalField,
|
|
137
|
+
isStringType,
|
|
138
|
+
pickValues,
|
|
139
|
+
requiredPartial,
|
|
140
|
+
unwrapZod
|
|
141
|
+
}
|