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,322 @@
|
|
|
1
|
+
import { GenericId } from "convex/values";
|
|
2
|
+
import { ActionBuilder, FunctionVisibility, GenericDataModel, MutationBuilder, PaginationOptions, QueryBuilder, RegisteredAction, RegisteredMutation, RegisteredQuery, paginationOptsValidator } from "convex/server";
|
|
3
|
+
import { ZodNullable, ZodNumber, ZodObject, ZodOptional, ZodRawShape, z } from "zod/v4";
|
|
4
|
+
import { CustomBuilder } from "convex-helpers/server/zod4";
|
|
5
|
+
|
|
6
|
+
//#region src/server/types.d.ts
|
|
7
|
+
interface BaseBuilders {
|
|
8
|
+
m: Mb;
|
|
9
|
+
q: Qb;
|
|
10
|
+
}
|
|
11
|
+
interface CacheOptions<S extends ZodRawShape, K extends keyof z.output<ZodObject<S>> & string> {
|
|
12
|
+
fetcher?: (c: ActionCtxLike, key: z.output<ZodObject<S>>[K]) => Promise<z.output<ZodObject<S>>>;
|
|
13
|
+
key: K;
|
|
14
|
+
schema: ZodObject<S>;
|
|
15
|
+
table: string;
|
|
16
|
+
ttl?: number;
|
|
17
|
+
}
|
|
18
|
+
interface ChildConfig {
|
|
19
|
+
foreignKey: string;
|
|
20
|
+
index?: string;
|
|
21
|
+
parent: string;
|
|
22
|
+
schema: ZodObject<ZodRawShape>;
|
|
23
|
+
}
|
|
24
|
+
interface ComparisonOp<V> {
|
|
25
|
+
$between?: [V, V];
|
|
26
|
+
$gt?: V;
|
|
27
|
+
$gte?: V;
|
|
28
|
+
$lt?: V;
|
|
29
|
+
$lte?: V;
|
|
30
|
+
}
|
|
31
|
+
interface CrudOptions<S extends ZodRawShape> {
|
|
32
|
+
auth?: {
|
|
33
|
+
where?: WhereOf<S>;
|
|
34
|
+
};
|
|
35
|
+
cascade?: boolean;
|
|
36
|
+
pub?: {
|
|
37
|
+
where?: WhereOf<S>;
|
|
38
|
+
};
|
|
39
|
+
search?: 'index';
|
|
40
|
+
softDelete?: boolean;
|
|
41
|
+
}
|
|
42
|
+
type Rec = Record<string, unknown>;
|
|
43
|
+
declare const ERROR_MESSAGES: {
|
|
44
|
+
readonly ALREADY_ORG_MEMBER: "Already a member of this organization";
|
|
45
|
+
readonly CANNOT_MODIFY_ADMIN: "Admins cannot modify other admins";
|
|
46
|
+
readonly CANNOT_MODIFY_OWNER: "Cannot modify the owner";
|
|
47
|
+
readonly CHUNK_ALREADY_UPLOADED: "Chunk already uploaded";
|
|
48
|
+
readonly CHUNK_NOT_FOUND: "Chunk not found";
|
|
49
|
+
readonly CONFLICT: "Conflict detected";
|
|
50
|
+
readonly EDITOR_REQUIRED: "Editor permission required";
|
|
51
|
+
readonly FILE_NOT_FOUND: "File not found";
|
|
52
|
+
readonly FILE_TOO_LARGE: "File too large";
|
|
53
|
+
readonly FORBIDDEN: "Forbidden";
|
|
54
|
+
readonly INCOMPLETE_UPLOAD: "Incomplete upload";
|
|
55
|
+
readonly INSUFFICIENT_ORG_ROLE: "Insufficient permissions";
|
|
56
|
+
readonly INVALID_FILE_TYPE: "Invalid file type";
|
|
57
|
+
readonly INVALID_INVITE: "Invalid invite";
|
|
58
|
+
readonly INVALID_MESSAGE: "Invalid message";
|
|
59
|
+
readonly INVALID_SESSION_STATE: "Invalid session state";
|
|
60
|
+
readonly INVALID_TOOL_ARGS: "Invalid tool arguments";
|
|
61
|
+
readonly INVALID_WHERE: "Invalid filters";
|
|
62
|
+
readonly INVITE_EXPIRED: "Invite has expired";
|
|
63
|
+
readonly JOIN_REQUEST_EXISTS: "Join request already exists";
|
|
64
|
+
readonly LIMIT_EXCEEDED: "Limit exceeded";
|
|
65
|
+
readonly MESSAGE_NOT_SAVED: "Message not saved";
|
|
66
|
+
readonly MUST_TRANSFER_OWNERSHIP: "Must transfer ownership before leaving";
|
|
67
|
+
readonly NO_FETCHER: "No fetcher configured";
|
|
68
|
+
readonly NO_PRECEDING_USER_MESSAGE: "No preceding user message";
|
|
69
|
+
readonly NOT_AUTHENTICATED: "Please log in";
|
|
70
|
+
readonly NOT_AUTHORIZED: "Not authorized";
|
|
71
|
+
readonly NOT_FOUND: "Not found";
|
|
72
|
+
readonly NOT_ORG_MEMBER: "Not a member of this organization";
|
|
73
|
+
readonly ORG_SLUG_TAKEN: "Organization slug already taken";
|
|
74
|
+
readonly RATE_LIMITED: "Too many requests";
|
|
75
|
+
readonly SESSION_NOT_FOUND: "Session not found";
|
|
76
|
+
readonly TARGET_MUST_BE_ADMIN: "Can only transfer ownership to an admin";
|
|
77
|
+
readonly UNAUTHORIZED: "Unauthorized";
|
|
78
|
+
readonly USER_NOT_FOUND: "User not found";
|
|
79
|
+
};
|
|
80
|
+
type Ab<V extends FunctionVisibility = 'public'> = CustomBuilder<'action', Record<string, never>, Rec, Record<string, never>, unknown, V, Rec>;
|
|
81
|
+
interface ActionCtxLike {
|
|
82
|
+
runMutation: (ref: string, args: Rec) => Promise<unknown>;
|
|
83
|
+
runQuery: (ref: string, args: Rec) => Promise<unknown>;
|
|
84
|
+
}
|
|
85
|
+
interface AuthorInfo {
|
|
86
|
+
[key: string]: unknown;
|
|
87
|
+
email?: string;
|
|
88
|
+
image?: string;
|
|
89
|
+
name?: string;
|
|
90
|
+
}
|
|
91
|
+
interface CacheCrudResult<S extends ZodRawShape> {
|
|
92
|
+
all: RegisteredQuery<'public', Rec, DocBase<S>[]>;
|
|
93
|
+
create: RegisteredMutation<'public', Rec, string>;
|
|
94
|
+
get: RegisteredQuery<'public', Rec, (DocBase<S> & {
|
|
95
|
+
cacheHit: true;
|
|
96
|
+
}) | null>;
|
|
97
|
+
getInternal: RegisteredQuery<'internal', Rec, DocBase<S> | null>;
|
|
98
|
+
invalidate: RegisteredMutation<'public', Rec, DocBase<S> | null>;
|
|
99
|
+
list: RegisteredQuery<'public', Rec, PaginatedResult<DocBase<S>>>;
|
|
100
|
+
load: RegisteredAction<'public', Rec, z.output<ZodObject<S>> & {
|
|
101
|
+
cacheHit: boolean;
|
|
102
|
+
}>;
|
|
103
|
+
purge: RegisteredMutation<'public', Rec, number>;
|
|
104
|
+
read: RegisteredQuery<'public', Rec, DocBase<S> | null>;
|
|
105
|
+
refresh: RegisteredAction<'public', Rec, z.output<ZodObject<S>> & {
|
|
106
|
+
cacheHit: boolean;
|
|
107
|
+
}>;
|
|
108
|
+
rm: RegisteredMutation<'public', Rec, DocBase<S> | null>;
|
|
109
|
+
set: RegisteredMutation<'internal', Rec, void>;
|
|
110
|
+
update: RegisteredMutation<'public', Rec, DocBase<S>>;
|
|
111
|
+
}
|
|
112
|
+
interface CanEditOpts {
|
|
113
|
+
acl: boolean;
|
|
114
|
+
doc: {
|
|
115
|
+
editors?: string[];
|
|
116
|
+
userId: string;
|
|
117
|
+
};
|
|
118
|
+
role: OrgRole;
|
|
119
|
+
userId: string;
|
|
120
|
+
}
|
|
121
|
+
interface ChildCrudResult<S extends ZodRawShape> {
|
|
122
|
+
create: RegisteredMutation<'public', Rec, string>;
|
|
123
|
+
get: RegisteredQuery<'public', Rec, DocBase<S> | null>;
|
|
124
|
+
list: RegisteredQuery<'public', Rec, DocBase<S>[]>;
|
|
125
|
+
rm: RegisteredMutation<'public', Rec, DocBase<S>>;
|
|
126
|
+
update: RegisteredMutation<'public', Rec, DocBase<S> | null>;
|
|
127
|
+
}
|
|
128
|
+
interface CrudReadApi<S extends ZodRawShape, V extends FunctionVisibility = 'public'> {
|
|
129
|
+
all: RegisteredQuery<V, {
|
|
130
|
+
where?: WhereOf<S>;
|
|
131
|
+
}, EnrichedDoc<S>[]>;
|
|
132
|
+
count: RegisteredQuery<V, {
|
|
133
|
+
where?: WhereOf<S>;
|
|
134
|
+
}, number>;
|
|
135
|
+
list: RegisteredQuery<V, {
|
|
136
|
+
paginationOpts: PaginationOptions;
|
|
137
|
+
where?: WhereOf<S>;
|
|
138
|
+
}, PaginatedResult<EnrichedDoc<S>>>;
|
|
139
|
+
read: RegisteredQuery<V, {
|
|
140
|
+
id: string;
|
|
141
|
+
own?: boolean;
|
|
142
|
+
where?: WhereOf<S>;
|
|
143
|
+
}, EnrichedDoc<S> | null>;
|
|
144
|
+
search: RegisteredQuery<V, {
|
|
145
|
+
fields?: string[];
|
|
146
|
+
query: string;
|
|
147
|
+
where?: WhereOf<S>;
|
|
148
|
+
}, EnrichedDoc<S>[]>;
|
|
149
|
+
}
|
|
150
|
+
interface CrudResult<S extends ZodRawShape> {
|
|
151
|
+
auth: CrudReadApi<S>;
|
|
152
|
+
authIndexed: RegisteredQuery<'public', {
|
|
153
|
+
index: string;
|
|
154
|
+
key: string;
|
|
155
|
+
value: string;
|
|
156
|
+
where?: WhereOf<S>;
|
|
157
|
+
}, EnrichedDoc<S>[]>;
|
|
158
|
+
bulkRm: RegisteredMutation<'public', {
|
|
159
|
+
ids: string[];
|
|
160
|
+
}, number>;
|
|
161
|
+
bulkUpdate: RegisteredMutation<'public', {
|
|
162
|
+
data: Partial<z.output<ZodObject<S>>>;
|
|
163
|
+
ids: string[];
|
|
164
|
+
}, unknown[]>;
|
|
165
|
+
create: RegisteredMutation<'public', z.output<ZodObject<S>>, string>;
|
|
166
|
+
pub: CrudReadApi<S>;
|
|
167
|
+
pubIndexed: RegisteredQuery<'public', {
|
|
168
|
+
index: string;
|
|
169
|
+
key: string;
|
|
170
|
+
value: string;
|
|
171
|
+
where?: WhereOf<S>;
|
|
172
|
+
}, EnrichedDoc<S>[]>;
|
|
173
|
+
restore?: RegisteredMutation<'public', {
|
|
174
|
+
id: string;
|
|
175
|
+
}, DocBase<S>>;
|
|
176
|
+
rm: RegisteredMutation<'public', {
|
|
177
|
+
id: string;
|
|
178
|
+
}, DocBase<S>>;
|
|
179
|
+
update: RegisteredMutation<'public', Partial<z.output<ZodObject<S>>> & {
|
|
180
|
+
expectedUpdatedAt?: number;
|
|
181
|
+
id: string;
|
|
182
|
+
}, DocBase<S>>;
|
|
183
|
+
}
|
|
184
|
+
interface DbLike extends DbReadLike {
|
|
185
|
+
delete: (id: string) => Promise<void>;
|
|
186
|
+
insert: (table: string, data: Rec) => Promise<string>;
|
|
187
|
+
patch: (id: string, data: Rec) => Promise<void>;
|
|
188
|
+
system: DbReadLike;
|
|
189
|
+
}
|
|
190
|
+
interface DbReadLike {
|
|
191
|
+
get: (id: string) => Promise<null | Rec>;
|
|
192
|
+
query: (table: string) => QueryLike;
|
|
193
|
+
}
|
|
194
|
+
type DocBase<S extends ZodRawShape> = z.output<ZodObject<S>> & {
|
|
195
|
+
_creationTime: number;
|
|
196
|
+
_id: string;
|
|
197
|
+
updatedAt: number;
|
|
198
|
+
};
|
|
199
|
+
type EnrichedDoc<S extends ZodRawShape> = WithUrls<DocBase<S> & {
|
|
200
|
+
author: AuthorInfo | null;
|
|
201
|
+
own: boolean | null;
|
|
202
|
+
userId: string;
|
|
203
|
+
}>;
|
|
204
|
+
type ErrorCode = keyof typeof ERROR_MESSAGES;
|
|
205
|
+
type FID = GenericId<'_storage'>;
|
|
206
|
+
interface FilterLike {
|
|
207
|
+
and: (a: unknown, b: unknown) => unknown;
|
|
208
|
+
eq: (a: unknown, b: unknown) => unknown;
|
|
209
|
+
field: (name: string) => unknown;
|
|
210
|
+
gt: (a: unknown, b: unknown) => unknown;
|
|
211
|
+
gte: (a: unknown, b: unknown) => unknown;
|
|
212
|
+
lt: (a: unknown, b: unknown) => unknown;
|
|
213
|
+
lte: (a: unknown, b: unknown) => unknown;
|
|
214
|
+
or: (a: unknown, b: unknown) => unknown;
|
|
215
|
+
}
|
|
216
|
+
interface IndexLike {
|
|
217
|
+
eq: (field: string, value: unknown) => IndexLike;
|
|
218
|
+
}
|
|
219
|
+
type Mb<V extends FunctionVisibility = 'public'> = CustomBuilder<'mutation', Record<string, never>, Rec, Record<string, never>, unknown, V, Rec>;
|
|
220
|
+
interface MutationCtxLike {
|
|
221
|
+
auth: {
|
|
222
|
+
getUserIdentity: () => Promise<unknown>;
|
|
223
|
+
};
|
|
224
|
+
db: DbLike;
|
|
225
|
+
storage: StorageLike;
|
|
226
|
+
}
|
|
227
|
+
interface OrgCrudResult<S extends ZodRawShape> {
|
|
228
|
+
addEditor: RegisteredMutation<'public', Rec, DocBase<S> | null>;
|
|
229
|
+
all: RegisteredQuery<'public', Rec, OrgEnrichedDoc<S>[]>;
|
|
230
|
+
bulkRm: RegisteredMutation<'public', Rec, number>;
|
|
231
|
+
bulkUpdate: RegisteredMutation<'public', Rec, DocBase<S>[]>;
|
|
232
|
+
count: RegisteredQuery<'public', Rec, number>;
|
|
233
|
+
create: RegisteredMutation<'public', Rec, string>;
|
|
234
|
+
editors: RegisteredQuery<'public', Rec, {
|
|
235
|
+
email: string;
|
|
236
|
+
name: string;
|
|
237
|
+
userId: string;
|
|
238
|
+
}[]>;
|
|
239
|
+
list: RegisteredQuery<'public', Rec, PaginatedResult<OrgEnrichedDoc<S>>>;
|
|
240
|
+
read: RegisteredQuery<'public', Rec, OrgEnrichedDoc<S>>;
|
|
241
|
+
removeEditor: RegisteredMutation<'public', Rec, DocBase<S> | null>;
|
|
242
|
+
rm: RegisteredMutation<'public', Rec, DocBase<S>>;
|
|
243
|
+
setEditors: RegisteredMutation<'public', Rec, DocBase<S> | null>;
|
|
244
|
+
update: RegisteredMutation<'public', Rec, DocBase<S> | null>;
|
|
245
|
+
}
|
|
246
|
+
type OrgEnrichedDoc<S extends ZodRawShape> = WithUrls<DocBase<S> & {
|
|
247
|
+
author: AuthorInfo | null;
|
|
248
|
+
orgId: string;
|
|
249
|
+
own: boolean | null;
|
|
250
|
+
userId: string;
|
|
251
|
+
}>;
|
|
252
|
+
type OrgRole = 'admin' | 'member' | 'owner';
|
|
253
|
+
interface PaginatedResult<D> {
|
|
254
|
+
continueCursor: string;
|
|
255
|
+
isDone: boolean;
|
|
256
|
+
page: D[];
|
|
257
|
+
}
|
|
258
|
+
type PaginationOptsShape = Record<keyof typeof paginationOptsValidator.fields, ZodNullable | ZodNumber | ZodOptional>;
|
|
259
|
+
type Qb<V extends FunctionVisibility = 'public'> = CustomBuilder<'query', Record<string, never>, Rec, Record<string, never>, unknown, V, Rec>;
|
|
260
|
+
interface QueryCtxLike {
|
|
261
|
+
auth: {
|
|
262
|
+
getUserIdentity: () => Promise<unknown>;
|
|
263
|
+
};
|
|
264
|
+
db: DbLike;
|
|
265
|
+
storage: StorageLike;
|
|
266
|
+
}
|
|
267
|
+
interface QueryLike {
|
|
268
|
+
collect: () => Promise<Rec[]>;
|
|
269
|
+
filter: (fn: (fb: FilterLike) => unknown) => QueryLike;
|
|
270
|
+
first: () => Promise<null | Rec>;
|
|
271
|
+
order: (dir: 'asc' | 'desc') => QueryLike;
|
|
272
|
+
paginate: (opts: Rec) => Promise<{
|
|
273
|
+
continueCursor: string;
|
|
274
|
+
isDone: boolean;
|
|
275
|
+
page: Rec[];
|
|
276
|
+
}>;
|
|
277
|
+
take: (n: number) => Promise<Rec[]>;
|
|
278
|
+
unique: () => Promise<null | Rec>;
|
|
279
|
+
withIndex: (name: string, fn?: (ib: IndexLike) => unknown) => QueryLike;
|
|
280
|
+
withSearchIndex: (name: string, fn: (sb: SearchLike) => unknown) => QueryLike;
|
|
281
|
+
}
|
|
282
|
+
interface ReadCtx {
|
|
283
|
+
db: DbLike;
|
|
284
|
+
storage: StorageLike;
|
|
285
|
+
viewerId: null | string;
|
|
286
|
+
withAuthor: <T extends {
|
|
287
|
+
userId: string;
|
|
288
|
+
}>(docs: T[]) => Promise<(T & {
|
|
289
|
+
author: null | Rec;
|
|
290
|
+
own: boolean | null;
|
|
291
|
+
})[]>;
|
|
292
|
+
}
|
|
293
|
+
interface SearchLike {
|
|
294
|
+
search: (field: string, query: string) => unknown;
|
|
295
|
+
}
|
|
296
|
+
interface SetupConfig<DM extends GenericDataModel = GenericDataModel> {
|
|
297
|
+
action: ActionBuilder<DM, 'public'>;
|
|
298
|
+
children?: Record<string, ChildConfig>;
|
|
299
|
+
getAuthUserId: (ctx: never) => Promise<null | string>;
|
|
300
|
+
internalMutation: MutationBuilder<DM, 'internal'>;
|
|
301
|
+
internalQuery: QueryBuilder<DM, 'internal'>;
|
|
302
|
+
mutation: MutationBuilder<DM, 'public'>;
|
|
303
|
+
orgCascadeTables?: string[];
|
|
304
|
+
orgSchema?: ZodObject<ZodRawShape>;
|
|
305
|
+
query: QueryBuilder<DM, 'public'>;
|
|
306
|
+
}
|
|
307
|
+
interface StorageLike {
|
|
308
|
+
delete: (id: string) => Promise<void>;
|
|
309
|
+
getUrl: (id: string) => Promise<null | string>;
|
|
310
|
+
}
|
|
311
|
+
type UrlKey<K, V> = NonNullable<V> extends FID | FID[] | readonly FID[] ? `${K & string}Url${NonNullable<V> extends FID ? '' : 's'}` : never;
|
|
312
|
+
type UrlVal<V> = NonNullable<V> extends FID | FID[] | readonly FID[] ? NonNullable<V> extends FID ? null | string : (null | string)[] : never;
|
|
313
|
+
type WhereFieldValue<V> = ComparisonOp<V> | V;
|
|
314
|
+
type WhereGroupOf<S extends ZodRawShape> = { [K in keyof z.output<ZodObject<S>>]?: WhereFieldValue<z.output<ZodObject<S>>[K]> } & {
|
|
315
|
+
own?: boolean;
|
|
316
|
+
};
|
|
317
|
+
type WhereOf<S extends ZodRawShape> = WhereGroupOf<S> & {
|
|
318
|
+
or?: WhereGroupOf<S>[];
|
|
319
|
+
};
|
|
320
|
+
type WithUrls<D> = D & { [K in keyof D as UrlKey<K, D[K]>]: UrlVal<D[K]> };
|
|
321
|
+
//#endregion
|
|
322
|
+
export { ReadCtx as A, OrgEnrichedDoc as C, Qb as D, PaginationOptsShape as E, WhereOf as F, WithUrls as I, SetupConfig as M, StorageLike as N, QueryCtxLike as O, WhereGroupOf as P, OrgCrudResult as S, PaginatedResult as T, EnrichedDoc as _, CacheCrudResult as a, Mb as b, ChildConfig as c, CrudOptions as d, CrudReadApi as f, DocBase as g, DbReadLike as h, BaseBuilders as i, Rec as j, QueryLike as k, ChildCrudResult as l, DbLike as m, ActionCtxLike as n, CacheOptions as o, CrudResult as p, AuthorInfo as r, CanEditOpts as s, Ab as t, ComparisonOp as u, ErrorCode as v, OrgRole as w, MutationCtxLike as x, FID as y };
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { coerceOptionals, cvFileKindOf, defaultValues, elementOf, isArrayType, isBooleanType, isDateType, isNumberType, isStringType, unwrapZod } from "./zod.mjs";
|
|
2
|
+
import { n as getErrorMessage, r as isRecord, t as getErrorCode } from "./error-D4GuI0ot.mjs";
|
|
3
|
+
import { useForm } from "@tanstack/react-form";
|
|
4
|
+
import { useStore } from "@tanstack/react-store";
|
|
5
|
+
import { useMutation } from "convex/react";
|
|
6
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
7
|
+
|
|
8
|
+
//#region src/react/form-meta.ts
|
|
9
|
+
const getMax = (schema) => {
|
|
10
|
+
const checks = schema?.def.checks;
|
|
11
|
+
if (checks) {
|
|
12
|
+
for (const c of checks) if (c?._zod.def.check === "max_length" && c._zod.def.maximum !== void 0) return c._zod.def.maximum;
|
|
13
|
+
}
|
|
14
|
+
}, getMeta = (s) => {
|
|
15
|
+
const { schema: base, type } = unwrapZod(s), fk = cvFileKindOf(s);
|
|
16
|
+
if (fk === "file") return { kind: "file" };
|
|
17
|
+
if (fk === "files") return {
|
|
18
|
+
kind: "files",
|
|
19
|
+
max: getMax(base)
|
|
20
|
+
};
|
|
21
|
+
if (isArrayType(type)) return {
|
|
22
|
+
kind: isStringType(unwrapZod(elementOf(base)).type) ? "stringArray" : "unknown",
|
|
23
|
+
max: getMax(base)
|
|
24
|
+
};
|
|
25
|
+
if (isStringType(type)) return { kind: "string" };
|
|
26
|
+
if (isNumberType(type)) return { kind: "number" };
|
|
27
|
+
if (isBooleanType(type)) return { kind: "boolean" };
|
|
28
|
+
if (isDateType(type)) return { kind: "date" };
|
|
29
|
+
return { kind: "unknown" };
|
|
30
|
+
}, buildMeta = (s) => {
|
|
31
|
+
const m = {};
|
|
32
|
+
for (const k in s.shape) if (Object.hasOwn(s.shape, k)) m[k] = getMeta(s.shape[k]);
|
|
33
|
+
return m;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/react/form.ts
|
|
38
|
+
const submitError = (e) => new Error(getErrorMessage(e), { cause: e }), handleConflict = (error) => {
|
|
39
|
+
if (getErrorCode(error) !== "CONFLICT") return null;
|
|
40
|
+
const { data } = error;
|
|
41
|
+
return {
|
|
42
|
+
code: "CONFLICT",
|
|
43
|
+
current: data?.current,
|
|
44
|
+
incoming: data?.incoming
|
|
45
|
+
};
|
|
46
|
+
}, useForm$1 = ({ autoSave, onConflict, onError, onSubmit, onSuccess, resetOnSuccess, schema, values }) => {
|
|
47
|
+
const resolved = values ?? defaultValues(schema), [conflict, setConflict] = useState(null), [er, setEr] = useState(null), [forceSubmit, setForceSubmit] = useState(false), [lastSaved, setLastSaved] = useState(null), vRef = useRef(resolved), autoSaveTimerRef = useRef(null);
|
|
48
|
+
vRef.current = resolved;
|
|
49
|
+
if (Object.keys(resolved).some((k) => !(k in schema.shape))) throw new Error("Form values include keys not in schema");
|
|
50
|
+
const meta = useMemo(() => buildMeta(schema), [schema]), instance = useForm({
|
|
51
|
+
defaultValues: resolved,
|
|
52
|
+
onSubmit: async ({ value }) => {
|
|
53
|
+
setEr(null);
|
|
54
|
+
try {
|
|
55
|
+
const result = await onSubmit(coerceOptionals(schema, value), forceSubmit), newValues = resetOnSuccess && isRecord(result) ? result : value;
|
|
56
|
+
instance.reset(newValues);
|
|
57
|
+
if (resetOnSuccess && isRecord(result)) vRef.current = result;
|
|
58
|
+
setForceSubmit(false);
|
|
59
|
+
setLastSaved(Date.now());
|
|
60
|
+
onSuccess?.();
|
|
61
|
+
} catch (error) {
|
|
62
|
+
const conflictData = handleConflict(error);
|
|
63
|
+
if (conflictData) {
|
|
64
|
+
setConflict(conflictData);
|
|
65
|
+
onConflict?.(conflictData);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const err = submitError(error);
|
|
69
|
+
setEr(err);
|
|
70
|
+
onError?.(err);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
validators: { onSubmit: schema }
|
|
74
|
+
}), { isDirty, isSubmitting } = useStore(instance.store, (s) => ({
|
|
75
|
+
isDirty: s.isDirty,
|
|
76
|
+
isSubmitting: s.isSubmitting
|
|
77
|
+
}));
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!(autoSave?.enabled && isDirty)) return;
|
|
80
|
+
if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
|
|
81
|
+
autoSaveTimerRef.current = setTimeout(() => {
|
|
82
|
+
instance.handleSubmit();
|
|
83
|
+
}, autoSave.debounceMs);
|
|
84
|
+
return () => {
|
|
85
|
+
if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
|
|
86
|
+
};
|
|
87
|
+
}, [
|
|
88
|
+
autoSave?.enabled,
|
|
89
|
+
autoSave?.debounceMs,
|
|
90
|
+
isDirty,
|
|
91
|
+
instance
|
|
92
|
+
]);
|
|
93
|
+
return {
|
|
94
|
+
conflict,
|
|
95
|
+
error: er,
|
|
96
|
+
instance,
|
|
97
|
+
isDirty,
|
|
98
|
+
isPending: isSubmitting,
|
|
99
|
+
lastSaved,
|
|
100
|
+
meta,
|
|
101
|
+
reset: (vals) => {
|
|
102
|
+
const resetVals = vals ?? vRef.current;
|
|
103
|
+
instance.reset(resetVals);
|
|
104
|
+
if (vals) vRef.current = vals;
|
|
105
|
+
setEr(null);
|
|
106
|
+
setLastSaved(null);
|
|
107
|
+
},
|
|
108
|
+
resolveConflict: (action) => {
|
|
109
|
+
if (action === "overwrite") {
|
|
110
|
+
setConflict(null);
|
|
111
|
+
setForceSubmit(true);
|
|
112
|
+
instance.handleSubmit();
|
|
113
|
+
} else if (action === "reload") {
|
|
114
|
+
setConflict(null);
|
|
115
|
+
instance.reset(vRef.current);
|
|
116
|
+
} else setConflict(null);
|
|
117
|
+
},
|
|
118
|
+
schema,
|
|
119
|
+
watch: (name) => useStore(instance.store, (s) => s.values[name])
|
|
120
|
+
};
|
|
121
|
+
}, useFormMutation = ({ mutation: mutationRef, onConflict, onError, onSuccess, resetOnSuccess = true, schema, transform, values }) => {
|
|
122
|
+
const mutate = useMutation(mutationRef);
|
|
123
|
+
return useForm$1({
|
|
124
|
+
onConflict,
|
|
125
|
+
onError,
|
|
126
|
+
onSubmit: async (d) => {
|
|
127
|
+
await mutate(transform ? transform(d) : d);
|
|
128
|
+
return d;
|
|
129
|
+
},
|
|
130
|
+
onSuccess,
|
|
131
|
+
resetOnSuccess,
|
|
132
|
+
schema,
|
|
133
|
+
values
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/react/use-online-status.ts
|
|
139
|
+
const useOnlineStatus = () => {
|
|
140
|
+
const [online, setOnline] = useState(true);
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
setOnline(navigator.onLine);
|
|
143
|
+
const handleOnline = () => setOnline(true), handleOffline = () => setOnline(false);
|
|
144
|
+
window.addEventListener("online", handleOnline);
|
|
145
|
+
window.addEventListener("offline", handleOffline);
|
|
146
|
+
return () => {
|
|
147
|
+
window.removeEventListener("online", handleOnline);
|
|
148
|
+
window.removeEventListener("offline", handleOffline);
|
|
149
|
+
};
|
|
150
|
+
}, []);
|
|
151
|
+
return online;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
//#endregion
|
|
155
|
+
export { getMeta as a, buildMeta as i, useForm$1 as n, useFormMutation as r, useOnlineStatus as t };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { useMutation } from "convex/react";
|
|
2
|
+
import { useRef, useState } from "react";
|
|
3
|
+
|
|
4
|
+
//#region src/react/use-upload.ts
|
|
5
|
+
/** biome-ignore-all lint/performance/noAwaitInLoops: retry logic */
|
|
6
|
+
const sleep = async (ms) => new Promise((resolve) => {
|
|
7
|
+
setTimeout(resolve, ms);
|
|
8
|
+
}), useUpload = (uploadMutation, options) => {
|
|
9
|
+
const { retries = 3, retryDelay = 1e3 } = options ?? {}, getUrl = useMutation(uploadMutation), [progress, setProgress] = useState(0), [uploading, setUploading] = useState(false), [attempt, setAttempt] = useState(0), xhr = useRef(null), reset = () => {
|
|
10
|
+
setUploading(false);
|
|
11
|
+
setProgress(0);
|
|
12
|
+
setAttempt(0);
|
|
13
|
+
}, uploadOnce = async (file) => {
|
|
14
|
+
try {
|
|
15
|
+
const url = await getUrl();
|
|
16
|
+
return await new Promise((res) => {
|
|
17
|
+
const x = new XMLHttpRequest();
|
|
18
|
+
xhr.current = x;
|
|
19
|
+
x.upload.onprogress = (e) => e.lengthComputable && setProgress(Math.round(e.loaded / e.total * 100));
|
|
20
|
+
x.onload = () => {
|
|
21
|
+
if (x.status < 200 || x.status >= 300) return res({
|
|
22
|
+
code: "HTTP",
|
|
23
|
+
ok: false,
|
|
24
|
+
status: x.status
|
|
25
|
+
});
|
|
26
|
+
try {
|
|
27
|
+
const parsed = JSON.parse(x.responseText), storageId = typeof parsed === "object" && parsed !== null && "storageId" in parsed ? parsed.storageId : void 0;
|
|
28
|
+
if (typeof storageId !== "string") return res({
|
|
29
|
+
code: "INVALID_RESPONSE",
|
|
30
|
+
ok: false
|
|
31
|
+
});
|
|
32
|
+
setProgress(100);
|
|
33
|
+
res({
|
|
34
|
+
ok: true,
|
|
35
|
+
storageId
|
|
36
|
+
});
|
|
37
|
+
} catch {
|
|
38
|
+
res({
|
|
39
|
+
code: "INVALID_RESPONSE",
|
|
40
|
+
ok: false
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
x.onerror = () => res({
|
|
45
|
+
code: "NETWORK",
|
|
46
|
+
ok: false
|
|
47
|
+
});
|
|
48
|
+
x.onabort = () => res({
|
|
49
|
+
code: "ABORTED",
|
|
50
|
+
ok: false
|
|
51
|
+
});
|
|
52
|
+
x.open("POST", url);
|
|
53
|
+
x.setRequestHeader("Content-Type", file.type || "application/octet-stream");
|
|
54
|
+
x.send(file);
|
|
55
|
+
});
|
|
56
|
+
} catch {
|
|
57
|
+
return {
|
|
58
|
+
code: "URL",
|
|
59
|
+
ok: false
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}, upload = async (file) => {
|
|
63
|
+
setUploading(true);
|
|
64
|
+
setProgress(0);
|
|
65
|
+
setAttempt(0);
|
|
66
|
+
try {
|
|
67
|
+
for (let i = 0; i < retries; i += 1) {
|
|
68
|
+
setAttempt(i + 1);
|
|
69
|
+
const result = await uploadOnce(file);
|
|
70
|
+
if (result.ok || result.code === "ABORTED") return result;
|
|
71
|
+
if (i < retries - 1) await sleep(retryDelay * (i + 1));
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
code: "NETWORK",
|
|
75
|
+
ok: false
|
|
76
|
+
};
|
|
77
|
+
} finally {
|
|
78
|
+
setUploading(false);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
attempt,
|
|
83
|
+
cancel: () => {
|
|
84
|
+
xhr.current?.abort();
|
|
85
|
+
reset();
|
|
86
|
+
},
|
|
87
|
+
isUploading: uploading,
|
|
88
|
+
progress,
|
|
89
|
+
reset,
|
|
90
|
+
upload
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
//#endregion
|
|
95
|
+
export { useUpload as t };
|
package/dist/zod.d.mts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ZodObject, ZodRawShape, ZodType, core, output } from "zod/v4";
|
|
2
|
+
|
|
3
|
+
//#region src/zod.d.ts
|
|
4
|
+
type CvMeta = 'file' | 'files';
|
|
5
|
+
type DefType = core.$ZodTypeDef['type'];
|
|
6
|
+
type ZodSchema = ZodType;
|
|
7
|
+
declare const unwrapZod: (schema: unknown) => {
|
|
8
|
+
def: undefined | ZodSchema["def"];
|
|
9
|
+
schema: undefined | ZodSchema;
|
|
10
|
+
type: "" | DefType;
|
|
11
|
+
}, isOptionalField: (schema: unknown) => boolean, elementOf: (s: undefined | ZodSchema) => unknown, cvMetaOf: (schema: undefined | ZodSchema) => CvMeta | undefined, isArrayType: (t: "" | DefType) => t is "array", isBooleanType: (t: "" | DefType) => t is "boolean", isDateType: (t: "" | DefType) => t is "date", isNumberType: (t: "" | DefType) => t is "number", isStringType: (t: "" | DefType) => t is "string" | "enum", cvFileKindOf: (schema: unknown) => CvMeta | undefined, enumToOptions: <T extends string>(schema: {
|
|
12
|
+
options: readonly T[];
|
|
13
|
+
}, transform?: (v: T) => string) => {
|
|
14
|
+
label: string;
|
|
15
|
+
value: T;
|
|
16
|
+
}[], requiredPartial: <S extends ZodObject<ZodRawShape>>(schema: S, requiredKeys: (keyof S["shape"])[]) => ZodObject<ZodRawShape>, defaultValue: (schema: unknown) => unknown, defaultValues: <S extends ZodObject<ZodRawShape>>(schema: S) => output<S>, pickValues: <S extends ZodObject<ZodRawShape>>(schema: S, doc: object) => output<S>, coerceOptionals: <S extends ZodObject<ZodRawShape>>(schema: S, data: output<S>) => output<S>;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { type CvMeta, type DefType, type ZodSchema, coerceOptionals, cvFileKindOf, cvMetaOf, defaultValue, defaultValues, elementOf, enumToOptions, isArrayType, isBooleanType, isDateType, isNumberType, isOptionalField, isStringType, pickValues, requiredPartial, unwrapZod };
|
package/dist/zod.mjs
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
//#region src/zod.ts
|
|
2
|
+
const WRAPPERS = new Set([
|
|
3
|
+
"catch",
|
|
4
|
+
"default",
|
|
5
|
+
"nullable",
|
|
6
|
+
"optional",
|
|
7
|
+
"prefault",
|
|
8
|
+
"readonly"
|
|
9
|
+
]), unwrapZod = (schema) => {
|
|
10
|
+
let cur = schema;
|
|
11
|
+
while (cur && typeof cur === "object" && "type" in cur) {
|
|
12
|
+
if (!WRAPPERS.has(cur.type)) return {
|
|
13
|
+
def: cur.def,
|
|
14
|
+
schema: cur,
|
|
15
|
+
type: cur.type
|
|
16
|
+
};
|
|
17
|
+
cur = cur.def.innerType;
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
def: void 0,
|
|
21
|
+
schema: void 0,
|
|
22
|
+
type: ""
|
|
23
|
+
};
|
|
24
|
+
}, isOptionalField = (schema) => {
|
|
25
|
+
let cur = schema;
|
|
26
|
+
while (cur && typeof cur === "object" && "type" in cur) {
|
|
27
|
+
if (cur.type === "optional") return true;
|
|
28
|
+
if (!WRAPPERS.has(cur.type)) return false;
|
|
29
|
+
cur = cur.def.innerType;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}, elementOf = (s) => (s?.def)?.element, cvMetaOf = (schema) => {
|
|
33
|
+
if (!schema || typeof schema.meta !== "function") return;
|
|
34
|
+
const m = schema.meta();
|
|
35
|
+
if (m && typeof m === "object") {
|
|
36
|
+
const { cv } = m;
|
|
37
|
+
if (cv === "file" || cv === "files") return cv;
|
|
38
|
+
}
|
|
39
|
+
}, isArrayType = (t) => t === "array", isBooleanType = (t) => t === "boolean", isDateType = (t) => t === "date", isNumberType = (t) => t === "number", isStringType = (t) => t === "string" || t === "enum", cvFileKindOf = (schema) => {
|
|
40
|
+
const { schema: s, type } = unwrapZod(schema), cv = cvMetaOf(s);
|
|
41
|
+
if (cv) return cv;
|
|
42
|
+
if (isArrayType(type) && cvMetaOf(elementOf(s)) === "file") return "files";
|
|
43
|
+
}, enumToOptions = (schema, transform) => schema.options.map((v) => ({
|
|
44
|
+
label: transform?.(v) ?? v.charAt(0).toUpperCase() + v.slice(1),
|
|
45
|
+
value: v
|
|
46
|
+
})), requiredPartial = (schema, requiredKeys) => {
|
|
47
|
+
const partial = schema.partial(), required = Object.fromEntries(requiredKeys.map((k) => [k, true]));
|
|
48
|
+
return partial.required(required);
|
|
49
|
+
}, defaultValue = (schema) => {
|
|
50
|
+
const { schema: base, type } = unwrapZod(schema), fk = cvFileKindOf(schema);
|
|
51
|
+
if (fk === "file") return null;
|
|
52
|
+
if (fk === "files") return [];
|
|
53
|
+
if (isArrayType(type)) return [];
|
|
54
|
+
if (isBooleanType(type)) return false;
|
|
55
|
+
if (isNumberType(type)) return 0;
|
|
56
|
+
if (isStringType(type)) {
|
|
57
|
+
if (base && "options" in base) {
|
|
58
|
+
const opts = base.options;
|
|
59
|
+
if (opts.length) return opts[0];
|
|
60
|
+
}
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
if (isDateType(type)) return null;
|
|
64
|
+
const inner = (base?.def)?.innerType;
|
|
65
|
+
if (inner) return defaultValue(inner);
|
|
66
|
+
}, defaultValues = (schema) => {
|
|
67
|
+
const result = {};
|
|
68
|
+
for (const k in schema.shape) if (Object.hasOwn(schema.shape, k)) result[k] = defaultValue(schema.shape[k]);
|
|
69
|
+
return result;
|
|
70
|
+
}, pickValues = (schema, doc) => {
|
|
71
|
+
const d = doc, result = {};
|
|
72
|
+
for (const k in schema.shape) if (Object.hasOwn(schema.shape, k)) result[k] = d[k] ?? defaultValue(schema.shape[k]);
|
|
73
|
+
return result;
|
|
74
|
+
}, coerceOptionals = (schema, data) => {
|
|
75
|
+
const result = { ...data };
|
|
76
|
+
for (const k of Object.keys(result)) if (isOptionalField(schema.shape[k]) && isStringType(unwrapZod(schema.shape[k]).type)) {
|
|
77
|
+
const v = result[k];
|
|
78
|
+
if (typeof v === "string") {
|
|
79
|
+
const trimmed = v.trim();
|
|
80
|
+
result[k] = trimmed.length > 0 ? trimmed : void 0;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
//#endregion
|
|
87
|
+
export { coerceOptionals, cvFileKindOf, cvMetaOf, defaultValue, defaultValues, elementOf, enumToOptions, isArrayType, isBooleanType, isDateType, isNumberType, isOptionalField, isStringType, pickValues, requiredPartial, unwrapZod };
|