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.
Files changed (70) hide show
  1. package/README.md +926 -0
  2. package/dist/components/index.mjs +937 -0
  3. package/dist/error-D4GuI0ot.mjs +71 -0
  4. package/dist/file-field-BqVgy8xY.mjs +205 -0
  5. package/dist/form-BXJK_j10.d.mts +99 -0
  6. package/dist/index.d.mts +433 -0
  7. package/dist/index.mjs +1 -0
  8. package/dist/index2.d.mts +5 -0
  9. package/dist/index3.d.mts +35 -0
  10. package/dist/index4.d.mts +101 -0
  11. package/dist/index5.d.mts +842 -0
  12. package/dist/next/index.mjs +151 -0
  13. package/dist/org-CmJBb8z-.d.mts +56 -0
  14. package/dist/react/index.mjs +158 -0
  15. package/dist/retry.d.mts +12 -0
  16. package/dist/retry.mjs +35 -0
  17. package/dist/schema.d.mts +23 -0
  18. package/dist/schema.mjs +15 -0
  19. package/dist/server/index.mjs +2572 -0
  20. package/dist/types-DWBVRtit.d.mts +322 -0
  21. package/dist/use-online-status-CMr73Jlk.mjs +155 -0
  22. package/dist/use-upload-DtELytQi.mjs +95 -0
  23. package/dist/zod.d.mts +18 -0
  24. package/dist/zod.mjs +87 -0
  25. package/package.json +40 -0
  26. package/src/components/editors-section.tsx +86 -0
  27. package/src/components/fields.tsx +884 -0
  28. package/src/components/file-field.tsx +234 -0
  29. package/src/components/form.tsx +191 -0
  30. package/src/components/index.ts +11 -0
  31. package/src/components/offline-indicator.tsx +15 -0
  32. package/src/components/org-avatar.tsx +13 -0
  33. package/src/components/permission-guard.tsx +36 -0
  34. package/src/components/role-badge.tsx +14 -0
  35. package/src/components/suspense-wrap.tsx +8 -0
  36. package/src/index.ts +40 -0
  37. package/src/next/active-org.ts +33 -0
  38. package/src/next/auth.ts +9 -0
  39. package/src/next/image.ts +134 -0
  40. package/src/next/index.ts +3 -0
  41. package/src/react/form-meta.ts +53 -0
  42. package/src/react/form.ts +201 -0
  43. package/src/react/index.ts +8 -0
  44. package/src/react/org.tsx +96 -0
  45. package/src/react/use-active-org.ts +48 -0
  46. package/src/react/use-bulk-selection.ts +47 -0
  47. package/src/react/use-online-status.ts +21 -0
  48. package/src/react/use-optimistic.ts +54 -0
  49. package/src/react/use-upload.ts +101 -0
  50. package/src/retry.ts +47 -0
  51. package/src/schema.ts +30 -0
  52. package/src/server/cache-crud.ts +175 -0
  53. package/src/server/check-schema.ts +29 -0
  54. package/src/server/child.ts +98 -0
  55. package/src/server/crud.ts +384 -0
  56. package/src/server/db.ts +7 -0
  57. package/src/server/error.ts +39 -0
  58. package/src/server/file.ts +372 -0
  59. package/src/server/helpers.ts +214 -0
  60. package/src/server/index.ts +12 -0
  61. package/src/server/org-crud.ts +307 -0
  62. package/src/server/org-helpers.ts +54 -0
  63. package/src/server/org.ts +572 -0
  64. package/src/server/schema-helpers.ts +107 -0
  65. package/src/server/setup.ts +138 -0
  66. package/src/server/test-crud.ts +211 -0
  67. package/src/server/test.ts +554 -0
  68. package/src/server/types.ts +392 -0
  69. package/src/server/unique.ts +28 -0
  70. package/src/zod.ts +141 -0
@@ -0,0 +1,151 @@
1
+ import { fetchQuery } from "convex/nextjs";
2
+ import { cookies } from "next/headers";
3
+ import { convexAuthNextjsToken } from "@convex-dev/auth/nextjs/server";
4
+ import { ConvexHttpClient } from "convex/browser";
5
+ import { NextResponse } from "next/server";
6
+ import sharp from "sharp";
7
+
8
+ //#region src/next/active-org.ts
9
+ const setActiveOrgCookie = async ({ orgId, slug }) => {
10
+ const cookieStore = await cookies(), opts = {
11
+ httpOnly: false,
12
+ maxAge: 3600 * 24 * 365,
13
+ path: "/"
14
+ };
15
+ cookieStore.set("activeOrgId", orgId, opts);
16
+ cookieStore.set("activeOrgSlug", slug, opts);
17
+ }, clearActiveOrgCookie = async () => {
18
+ const cookieStore = await cookies();
19
+ cookieStore.delete("activeOrgId");
20
+ cookieStore.delete("activeOrgSlug");
21
+ }, getActiveOrg = async ({ query, token }) => {
22
+ if (!token) return null;
23
+ const cookieStore = await cookies(), orgId = cookieStore.get("activeOrgId")?.value;
24
+ if (!orgId) return null;
25
+ try {
26
+ return await fetchQuery(query, { orgId }, { token });
27
+ } catch {
28
+ cookieStore.delete("activeOrgId");
29
+ cookieStore.delete("activeOrgSlug");
30
+ return null;
31
+ }
32
+ };
33
+
34
+ //#endregion
35
+ //#region src/next/auth.ts
36
+ const isTest = Boolean(process.env.PLAYWRIGHT || process.env.TEST_MODE), getToken = async () => isTest ? void 0 : convexAuthNextjsToken(), isAuthenticated = async () => isTest || Boolean(await convexAuthNextjsToken());
37
+
38
+ //#endregion
39
+ //#region src/next/image.ts
40
+ const IMAGE_TYPES = new Set([
41
+ "image/gif",
42
+ "image/jpeg",
43
+ "image/png",
44
+ "image/svg+xml",
45
+ "image/webp"
46
+ ]), isImageType = (contentType) => IMAGE_TYPES.has(contentType), formatToMime = {
47
+ jpeg: "image/jpeg",
48
+ png: "image/png",
49
+ webp: "image/webp"
50
+ }, applyFormat = ({ contentType, format, pipeline, quality }) => {
51
+ if (format === "jpeg") return pipeline.jpeg({ quality });
52
+ if (format === "png") return pipeline.png({ quality });
53
+ if (format === "webp") return pipeline.webp({ quality });
54
+ const [, ext] = contentType.split("/");
55
+ if (ext === "jpeg" || ext === "jpg") return pipeline.jpeg({ quality });
56
+ if (ext === "png") return pipeline.png({ quality });
57
+ if (ext === "webp") return pipeline.webp({ quality });
58
+ return pipeline;
59
+ }, applyTransforms = ({ contentType, options, pipeline, thumbnail }) => {
60
+ const quality = options?.compress?.quality ?? 80;
61
+ if (thumbnail) return pipeline.resize({
62
+ fit: "cover",
63
+ height: 200,
64
+ width: 200
65
+ }).webp({ quality: 80 });
66
+ let result = pipeline;
67
+ if (options?.resize) result = result.resize({
68
+ fit: options.resize.fit ?? "cover",
69
+ height: options.resize.height,
70
+ width: options.resize.width
71
+ });
72
+ if (options?.format || options?.compress) result = applyFormat({
73
+ contentType,
74
+ format: options.format,
75
+ pipeline: result,
76
+ quality
77
+ });
78
+ return result;
79
+ }, fetchImage = async ({ client, queryRef, storageId }) => {
80
+ const url = (await client.query(queryRef, { id: storageId }))?.url;
81
+ if (!url) return {
82
+ error: "File not found",
83
+ status: 404
84
+ };
85
+ const response = await fetch(url);
86
+ if (!response.ok) return {
87
+ error: "Failed to fetch image",
88
+ status: 500
89
+ };
90
+ const contentType = response.headers.get("content-type") ?? "";
91
+ if (!isImageType(contentType)) return {
92
+ error: "Not an image file",
93
+ status: 400
94
+ };
95
+ return {
96
+ buffer: Buffer.from(await response.arrayBuffer()),
97
+ contentType
98
+ };
99
+ }, makeGet = ({ getClient, queryRef }) => async (req) => {
100
+ try {
101
+ const storageId = req.nextUrl.searchParams.get("id");
102
+ if (!storageId) return NextResponse.json({ error: "id is required" }, { status: 400 });
103
+ const result = await fetchImage({
104
+ client: getClient(),
105
+ queryRef,
106
+ storageId
107
+ });
108
+ if ("error" in result) return NextResponse.json({ error: result.error }, { status: result.status });
109
+ return new NextResponse(new Uint8Array(result.buffer), { headers: {
110
+ "Cache-Control": "public, max-age=31536000, immutable",
111
+ "Content-Type": result.contentType
112
+ } });
113
+ } catch (error) {
114
+ return NextResponse.json({ error: error instanceof Error ? error.message : "Failed to fetch image" }, { status: 500 });
115
+ }
116
+ }, makePost = ({ getClient, queryRef }) => async (req) => {
117
+ try {
118
+ const { options, storageId, thumbnail } = await req.json();
119
+ if (!storageId) return NextResponse.json({ error: "storageId is required" }, { status: 400 });
120
+ const result = await fetchImage({
121
+ client: getClient(),
122
+ queryRef,
123
+ storageId
124
+ });
125
+ if ("error" in result) return NextResponse.json({ error: result.error }, { status: result.status });
126
+ const { buffer, contentType } = result, outputBuffer = await applyTransforms({
127
+ contentType,
128
+ options,
129
+ pipeline: sharp(buffer),
130
+ thumbnail: thumbnail ?? false
131
+ }).toBuffer(), outputMime = thumbnail ? "image/webp" : options?.format ? formatToMime[options.format] : contentType;
132
+ return new NextResponse(new Uint8Array(outputBuffer), { headers: {
133
+ "Cache-Control": "public, max-age=31536000, immutable",
134
+ "Content-Type": outputMime
135
+ } });
136
+ } catch (error) {
137
+ return NextResponse.json({ error: error instanceof Error ? error.message : "Processing failed" }, { status: 500 });
138
+ }
139
+ }, makeImageRoute = async ({ convexUrl, fileInfoQuery = "file:info" }) => {
140
+ const getClient = () => new ConvexHttpClient(convexUrl), opts = {
141
+ getClient,
142
+ queryRef: fileInfoQuery
143
+ };
144
+ return {
145
+ GET: makeGet(opts),
146
+ POST: makePost(opts)
147
+ };
148
+ };
149
+
150
+ //#endregion
151
+ export { clearActiveOrgCookie, getActiveOrg, getToken, isAuthenticated, makeImageRoute, setActiveOrgCookie };
@@ -0,0 +1,56 @@
1
+ import { w as OrgRole } from "./types-DWBVRtit.mjs";
2
+ import { FunctionReference } from "convex/server";
3
+ import { ReactNode } from "react";
4
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
5
+
6
+ //#region src/react/org.d.ts
7
+ interface OrgContextValue<O extends OrgDoc = OrgDoc, M = unknown> {
8
+ canDeleteOrg: boolean;
9
+ canManageAdmins: boolean;
10
+ canManageMembers: boolean;
11
+ isAdmin: boolean;
12
+ isMember: boolean;
13
+ isOwner: boolean;
14
+ membership: M | null;
15
+ org: O;
16
+ orgId: string;
17
+ role: OrgRole;
18
+ }
19
+ interface OrgDoc {
20
+ [key: string]: unknown;
21
+ _id: string;
22
+ }
23
+ interface OrgProviderProps<O extends OrgDoc, M> {
24
+ children: ReactNode;
25
+ membership: M | null;
26
+ org: O;
27
+ role: OrgRole;
28
+ }
29
+ declare const OrgProvider: <O extends OrgDoc, M>({
30
+ children,
31
+ membership,
32
+ org,
33
+ role
34
+ }: OrgProviderProps<O, M>) => react_jsx_runtime0.JSX.Element, useOrg: <O extends OrgDoc = OrgDoc, M = unknown>() => OrgContextValue<O, M>, useOrgQuery: <F extends FunctionReference<"query">>(query: F, args?: "skip" | Omit<F["_args"], "orgId">) => F["_returnType"] | undefined, useOrgMutation: <F extends FunctionReference<"mutation">>(mutation: F) => (args?: Omit<F["_args"], "orgId">) => Promise<F["_returnType"]>, canEditResource: ({
35
+ editorsList,
36
+ isAdmin,
37
+ resource,
38
+ userId
39
+ }: {
40
+ editorsList: {
41
+ userId: string;
42
+ }[];
43
+ isAdmin: boolean;
44
+ resource: {
45
+ userId: string;
46
+ };
47
+ userId: string;
48
+ }) => boolean, useMyOrgs: <O extends OrgDoc>(myOrgsQuery: FunctionReference<"query">) => {
49
+ isLoading: boolean;
50
+ orgs: {
51
+ org: O;
52
+ role: OrgRole;
53
+ }[];
54
+ };
55
+ //#endregion
56
+ export { canEditResource as a, useOrgMutation as c, OrgProviderProps as i, useOrgQuery as l, OrgDoc as n, useMyOrgs as o, OrgProvider as r, useOrg as s, OrgContextValue as t };
@@ -0,0 +1,158 @@
1
+ import "../error-D4GuI0ot.mjs";
2
+ import { a as getMeta, i as buildMeta, n as useForm, r as useFormMutation, t as useOnlineStatus } from "../use-online-status-CMr73Jlk.mjs";
3
+ import { t as useUpload } from "../use-upload-DtELytQi.mjs";
4
+ import { useMutation, useQuery } from "convex/react";
5
+ import { createContext, use, useCallback, useMemo, useRef, useState } from "react";
6
+ import { jsx } from "react/jsx-runtime";
7
+
8
+ //#region src/react/org.tsx
9
+ const OrgContext = createContext(null);
10
+ const OrgProvider = ({ children, membership, org, role }) => {
11
+ return /* @__PURE__ */ jsx(OrgContext, {
12
+ value: useMemo(() => {
13
+ const isOwner = role === "owner", isAdmin = role === "owner" || role === "admin";
14
+ return {
15
+ canDeleteOrg: isOwner,
16
+ canManageAdmins: isOwner,
17
+ canManageMembers: isAdmin,
18
+ isAdmin,
19
+ isMember: true,
20
+ isOwner,
21
+ membership,
22
+ org,
23
+ orgId: org._id,
24
+ role
25
+ };
26
+ }, [
27
+ membership,
28
+ org,
29
+ role
30
+ ]),
31
+ children
32
+ });
33
+ }, useOrg = () => {
34
+ const ctx = use(OrgContext);
35
+ if (!ctx) throw new Error("useOrg must be used inside OrgProvider");
36
+ return ctx;
37
+ }, useOrgQuery = (query, args) => {
38
+ const { orgId } = useOrg();
39
+ return useQuery(query, args === "skip" ? "skip" : {
40
+ ...args,
41
+ orgId
42
+ });
43
+ }, useOrgMutation = (mutation) => {
44
+ const { orgId } = useOrg(), mutate = useMutation(mutation);
45
+ return useCallback(async (args) => mutate({
46
+ ...args,
47
+ orgId
48
+ }), [mutate, orgId]);
49
+ }, canEditResource = ({ editorsList, isAdmin, resource, userId }) => isAdmin || resource.userId === userId || editorsList.some((e) => e.userId === userId), useMyOrgs = (myOrgsQuery) => {
50
+ const data = useQuery(myOrgsQuery);
51
+ return {
52
+ isLoading: data === void 0,
53
+ orgs: data ?? []
54
+ };
55
+ };
56
+
57
+ //#endregion
58
+ //#region src/react/use-active-org.ts
59
+ const ACTIVE_ORG_REGEX = /activeOrgId=(?<id>[^;]+)/u, getActiveOrgIdFromCookie = () => {
60
+ if (typeof document === "undefined") return null;
61
+ return ACTIVE_ORG_REGEX.exec(document.cookie)?.groups?.id ?? null;
62
+ }, setActiveOrgCookieClient = ({ orgId, slug }) => {
63
+ const maxAge = 3600 * 24 * 365;
64
+ document.cookie = `activeOrgId=${orgId}; path=/; max-age=${maxAge}`;
65
+ document.cookie = `activeOrgSlug=${slug}; path=/; max-age=${maxAge}`;
66
+ }, useActiveOrg = (orgGetQuery) => {
67
+ const [activeOrgId, setActiveOrgId] = useState(getActiveOrgIdFromCookie), activeOrg = useQuery(orgGetQuery, activeOrgId ? { orgId: activeOrgId } : "skip"), setActiveOrg = useCallback((org) => {
68
+ setActiveOrgCookieClient({
69
+ orgId: org._id,
70
+ slug: org.slug
71
+ });
72
+ setActiveOrgId(org._id);
73
+ }, []), clearActiveOrg = useCallback(() => {
74
+ document.cookie = "activeOrgId=; path=/; max-age=0";
75
+ document.cookie = "activeOrgSlug=; path=/; max-age=0";
76
+ setActiveOrgId(null);
77
+ }, []);
78
+ return {
79
+ activeOrg: activeOrg ?? null,
80
+ activeOrgId,
81
+ clearActiveOrg,
82
+ isLoading: activeOrgId ? activeOrg === void 0 : false,
83
+ setActiveOrg
84
+ };
85
+ };
86
+
87
+ //#endregion
88
+ //#region src/react/use-bulk-selection.ts
89
+ const useBulkSelection = ({ bulkRm, items, onError, onSuccess, orgId }) => {
90
+ const [selected, setSelected] = useState(/* @__PURE__ */ new Set()), clear = () => {
91
+ setSelected(/* @__PURE__ */ new Set());
92
+ }, toggleSelect = (id) => {
93
+ setSelected((prev) => {
94
+ const next = new Set(prev);
95
+ if (next.has(id)) next.delete(id);
96
+ else next.add(id);
97
+ return next;
98
+ });
99
+ }, toggleSelectAll = () => {
100
+ if (selected.size === items.length) setSelected(/* @__PURE__ */ new Set());
101
+ else setSelected(new Set(items.map((i) => i._id)));
102
+ }, handleBulkDelete = () => {
103
+ if (selected.size === 0) return;
104
+ const count = selected.size;
105
+ bulkRm({
106
+ ids: [...selected],
107
+ orgId
108
+ }).then(() => {
109
+ onSuccess?.(count);
110
+ setSelected(/* @__PURE__ */ new Set());
111
+ return null;
112
+ }).catch((bulkError) => onError?.(bulkError));
113
+ };
114
+ return {
115
+ clear,
116
+ handleBulkDelete,
117
+ selected,
118
+ toggleSelect,
119
+ toggleSelectAll
120
+ };
121
+ };
122
+
123
+ //#endregion
124
+ //#region src/react/use-optimistic.ts
125
+ const useOptimisticMutation = ({ mutation, onOptimistic, onRollback, onSuccess }) => {
126
+ const mutate = useMutation(mutation), [isPending, setIsPending] = useState(false), [mutationError, setMutationError] = useState(null), pendingCount = useRef(0);
127
+ return {
128
+ error: mutationError,
129
+ execute: useCallback(async (args) => {
130
+ pendingCount.current += 1;
131
+ setIsPending(true);
132
+ setMutationError(null);
133
+ onOptimistic?.(args);
134
+ try {
135
+ const result = await mutate(args);
136
+ onSuccess?.(result, args);
137
+ return result;
138
+ } catch (error) {
139
+ const err = error instanceof Error ? error : /* @__PURE__ */ new Error("Mutation failed");
140
+ setMutationError(err);
141
+ onRollback?.(args, err);
142
+ return null;
143
+ } finally {
144
+ pendingCount.current -= 1;
145
+ if (pendingCount.current === 0) setIsPending(false);
146
+ }
147
+ }, [
148
+ mutate,
149
+ onOptimistic,
150
+ onRollback,
151
+ onSuccess
152
+ ]),
153
+ isPending
154
+ };
155
+ };
156
+
157
+ //#endregion
158
+ export { OrgProvider, buildMeta, canEditResource, getMeta, setActiveOrgCookieClient, useActiveOrg, useBulkSelection, useForm, useFormMutation, useMyOrgs, useOnlineStatus, useOptimisticMutation, useOrg, useOrgMutation, useOrgQuery, useUpload };
@@ -0,0 +1,12 @@
1
+ //#region src/retry.d.ts
2
+ interface RetryOptions {
3
+ base?: number;
4
+ initialDelayMs?: number;
5
+ maxAttempts?: number;
6
+ maxDelayMs?: number;
7
+ }
8
+ declare const withRetry: <T>(fn: () => Promise<T>, options?: RetryOptions) => Promise<T>, fetchWithRetry: (url: string, options?: RequestInit & {
9
+ retry?: RetryOptions;
10
+ }) => Promise<Response>;
11
+ //#endregion
12
+ export { fetchWithRetry, withRetry };
package/dist/retry.mjs ADDED
@@ -0,0 +1,35 @@
1
+ //#region src/retry.ts
2
+ const DEFAULT_OPTIONS = {
3
+ base: 2,
4
+ initialDelayMs: 500,
5
+ maxAttempts: 3,
6
+ maxDelayMs: 1e4
7
+ }, sleep = async (ms) => new Promise((resolve) => {
8
+ setTimeout(resolve, ms);
9
+ }), calculateDelay = (attempt, opts) => {
10
+ const jitter = Math.random() * .3 + .85;
11
+ return Math.min(opts.initialDelayMs * opts.base ** attempt * jitter, opts.maxDelayMs);
12
+ }, withRetry = async (fn, options = {}) => {
13
+ const opts = {
14
+ ...DEFAULT_OPTIONS,
15
+ ...options
16
+ };
17
+ let lastError = /* @__PURE__ */ new Error("Retry failed");
18
+ for (let attempt = 0; attempt < opts.maxAttempts; attempt += 1) try {
19
+ return await fn();
20
+ } catch (error) {
21
+ lastError = error instanceof Error ? error : new Error(String(error));
22
+ if (attempt < opts.maxAttempts - 1) await sleep(calculateDelay(attempt, opts));
23
+ }
24
+ throw lastError;
25
+ }, fetchWithRetry = async (url, options) => {
26
+ const { retry, ...fetchOptions } = options ?? {};
27
+ return withRetry(async () => {
28
+ const response = await fetch(url, fetchOptions);
29
+ if (!response.ok && response.status >= 500) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
30
+ return response;
31
+ }, retry);
32
+ };
33
+
34
+ //#endregion
35
+ export { fetchWithRetry, withRetry };
@@ -0,0 +1,23 @@
1
+ import * as zod_v40 from "zod/v4";
2
+ import { ZodObject, ZodRawShape } from "zod/v4";
3
+ import * as convex_helpers_server_zod40 from "convex-helpers/server/zod4";
4
+ import * as zod_v4_core0 from "zod/v4/core";
5
+
6
+ //#region src/schema.d.ts
7
+ declare const cvFile: () => convex_helpers_server_zod40.Zid<"_storage">, cvFiles: () => zod_v40.ZodArray<convex_helpers_server_zod40.Zid<"_storage">>, child: <const P extends string, const S extends ZodRawShape, const FK extends keyof S & string>(config: {
8
+ foreignKey: FK;
9
+ index?: string;
10
+ parent: P;
11
+ schema: ZodObject<S>;
12
+ }) => {
13
+ foreignKey: FK;
14
+ index: string;
15
+ parent: P;
16
+ schema: ZodObject<S>;
17
+ }, orgSchema: ZodObject<{
18
+ avatarId: zod_v40.ZodOptional<zod_v40.ZodNullable<convex_helpers_server_zod40.Zid<"_storage">>>;
19
+ name: zod_v40.ZodString;
20
+ slug: zod_v40.ZodString;
21
+ }, zod_v4_core0.$strip>;
22
+ //#endregion
23
+ export { child, cvFile, cvFiles, orgSchema };
@@ -0,0 +1,15 @@
1
+ import { array, object, string } from "zod/v4";
2
+ import { zid } from "convex-helpers/server/zod4";
3
+
4
+ //#region src/schema.ts
5
+ const cvFile = () => zid("_storage").meta({ cv: "file" }), cvFiles = () => array(cvFile()).meta({ cv: "files" }), child = (config) => ({
6
+ ...config,
7
+ index: config.index ?? `by_${config.parent}`
8
+ }), orgSchema = object({
9
+ avatarId: zid("_storage").nullable().optional(),
10
+ name: string().min(1),
11
+ slug: string().min(1).regex(/^[a-z0-9-]+$/u)
12
+ });
13
+
14
+ //#endregion
15
+ export { child, cvFile, cvFiles, orgSchema };