@lasterp/shared 1.0.0-alpha.0 → 1.0.0-alpha.10

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.
@@ -0,0 +1,498 @@
1
+ "use client";
2
+ // src/frappe/provider.tsx
3
+ import { useMemo, createContext } from "react";
4
+ import { FrappeApp } from "frappe-js-sdk";
5
+
6
+ // src/frappe/socket.ts
7
+ import io from "socket.io-client";
8
+
9
+ class SocketIO {
10
+ socket_port;
11
+ host;
12
+ port;
13
+ protocol;
14
+ url;
15
+ site_name;
16
+ socket;
17
+ constructor(url, site_name, socket_port, tokenParams) {
18
+ this.socket_port = socket_port ?? "9000";
19
+ this.host = window.location?.hostname;
20
+ this.port = window.location?.port ? `:${this.socket_port}` : "";
21
+ this.protocol = window.location?.protocol === "https:" ? "https" : "http";
22
+ if (url) {
23
+ let urlObject = new URL(url);
24
+ urlObject.port = "";
25
+ if (socket_port) {
26
+ urlObject.port = socket_port;
27
+ this.url = urlObject.toString();
28
+ } else {
29
+ this.url = urlObject.toString();
30
+ }
31
+ } else {
32
+ this.url = `${this.protocol}://${this.host}${this.port}/`;
33
+ }
34
+ if (site_name) {
35
+ this.url = `${this.url}${site_name}`;
36
+ }
37
+ this.site_name = site_name;
38
+ this.socket = io(`${this.url}`, {
39
+ withCredentials: true,
40
+ secure: this.protocol === "https",
41
+ extraHeaders: tokenParams && tokenParams.useToken === true ? {
42
+ Authorization: `${tokenParams.type} ${tokenParams.token?.()}`
43
+ } : {}
44
+ });
45
+ }
46
+ }
47
+
48
+ // src/frappe/provider.tsx
49
+ import { jsxDEV } from "react/jsx-dev-runtime";
50
+
51
+ var FrappeContext = createContext(null);
52
+ var FrappeProvider = ({
53
+ url = "",
54
+ tokenParams,
55
+ enableSocket = true,
56
+ socketPort,
57
+ siteName,
58
+ children,
59
+ customHeaders
60
+ }) => {
61
+ const frappeConfig = useMemo(() => {
62
+ const frappe = new FrappeApp(url, tokenParams, undefined, customHeaders);
63
+ return {
64
+ url,
65
+ tokenParams,
66
+ app: frappe,
67
+ auth: frappe.auth(),
68
+ db: frappe.db(),
69
+ call: frappe.call(),
70
+ file: frappe.file(),
71
+ socket: enableSocket ? new SocketIO(url, siteName, socketPort, tokenParams).socket : undefined
72
+ };
73
+ }, [url, tokenParams, enableSocket, socketPort, siteName, customHeaders]);
74
+ return /* @__PURE__ */ jsxDEV(FrappeContext.Provider, {
75
+ value: frappeConfig,
76
+ children
77
+ }, undefined, false, undefined, this);
78
+ };
79
+ // src/frappe/hooks/auth.ts
80
+ import { useCallback, useContext, useState, useEffect } from "react";
81
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
82
+ var useFrappeAuth = () => {
83
+ const queryClient = useQueryClient();
84
+ const { auth, tokenParams } = useContext(FrappeContext);
85
+ const [userID, setUserID] = useState();
86
+ const getUserCookie = useCallback(() => {
87
+ const userCookie = document.cookie.split(";").find((c) => c.trim().startsWith("user_id="));
88
+ if (userCookie) {
89
+ const userName = userCookie.split("=")[1];
90
+ if (userName && userName !== "Guest") {
91
+ setUserID(userName);
92
+ } else {
93
+ setUserID(null);
94
+ }
95
+ } else {
96
+ setUserID(null);
97
+ }
98
+ }, []);
99
+ useEffect(() => {
100
+ if (tokenParams && tokenParams.useToken) {
101
+ setUserID(null);
102
+ } else {
103
+ getUserCookie();
104
+ }
105
+ }, [tokenParams, getUserCookie]);
106
+ const {
107
+ data: currentUser,
108
+ error,
109
+ isLoading,
110
+ isFetching: isValidating
111
+ } = useQuery({
112
+ queryKey: ["currentUser", { tokenParams, userID }],
113
+ queryFn: () => auth.getLoggedInUser(),
114
+ enabled: !!(tokenParams?.useToken || userID),
115
+ retry: false,
116
+ refetchOnWindowFocus: false,
117
+ staleTime: Infinity
118
+ });
119
+ const loginMutation = useMutation({
120
+ mutationFn: (credentials) => auth.loginWithUsernamePassword(credentials),
121
+ onSuccess: () => {
122
+ getUserCookie();
123
+ queryClient.invalidateQueries({ queryKey: ["currentUser"] });
124
+ }
125
+ });
126
+ const logoutMutation = useMutation({
127
+ mutationFn: () => auth.logout(),
128
+ onSuccess: () => {
129
+ setUserID(null);
130
+ queryClient.setQueryData(["currentUser"], null);
131
+ }
132
+ });
133
+ return {
134
+ isLoading: userID === undefined || isLoading,
135
+ currentUser,
136
+ isValidating,
137
+ error,
138
+ login: loginMutation.mutateAsync,
139
+ logout: logoutMutation.mutateAsync,
140
+ updateCurrentUser: () => queryClient.invalidateQueries({ queryKey: ["currentUser"] }),
141
+ getUserCookie
142
+ };
143
+ };
144
+ // src/frappe/hooks/call.ts
145
+ import { useContext as useContext2 } from "react";
146
+ import { useMutation as useMutation2, useQuery as useQuery2 } from "@tanstack/react-query";
147
+
148
+ // src/frappe/utils.ts
149
+ function encodeQueryData(data) {
150
+ const ret = [];
151
+ for (let d in data)
152
+ ret.push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d]));
153
+ return ret.join("&");
154
+ }
155
+
156
+ // src/frappe/hooks/call.ts
157
+ var useFrappeGetCall = (method, params, options, type = "GET") => {
158
+ const { call } = useContext2(FrappeContext);
159
+ const urlParams = encodeQueryData(params ?? {});
160
+ const url = `${method}?${urlParams}`;
161
+ const queryKey = ["frappeCall", type, method, url];
162
+ const { data, error, isLoading, isFetching, refetch } = useQuery2({
163
+ queryKey,
164
+ queryFn: type === "GET" ? () => call.get(method, params) : () => call.post(method, params),
165
+ enabled: (options?.enabled ?? true) && !!method,
166
+ staleTime: options?.staleTime,
167
+ retry: options?.retry ?? false
168
+ });
169
+ return {
170
+ data,
171
+ error,
172
+ isLoading,
173
+ isFetching,
174
+ refetch
175
+ };
176
+ };
177
+ var useFrappeMutation = (method, httpMethod) => {
178
+ const { call: frappeCall } = useContext2(FrappeContext);
179
+ const queryKey = ["frappeCall", httpMethod, method];
180
+ const { mutateAsync, data, isPending, error, isSuccess, reset } = useMutation2({
181
+ mutationKey: queryKey,
182
+ mutationFn: (params) => {
183
+ if (httpMethod === "POST")
184
+ return frappeCall.post(method, params);
185
+ if (httpMethod === "PUT")
186
+ return frappeCall.put(method, params);
187
+ return frappeCall.delete(method, params);
188
+ }
189
+ });
190
+ return {
191
+ call: (params) => mutateAsync(params),
192
+ result: data ?? null,
193
+ loading: isPending,
194
+ error,
195
+ isCompleted: isSuccess,
196
+ reset
197
+ };
198
+ };
199
+ var useFrappePostCall = (method) => useFrappeMutation(method, "POST");
200
+ var useFrappePutCall = (method) => useFrappeMutation(method, "PUT");
201
+ var useFrappeDeleteCall = (method) => useFrappeMutation(method, "DELETE");
202
+ // src/frappe/hooks/count.ts
203
+ import { useContext as useContext3 } from "react";
204
+ import { useQuery as useQuery3 } from "@tanstack/react-query";
205
+ var useFrappeGetDocCount = (doctype, filters, debug = false, options) => {
206
+ const { url, db } = useContext3(FrappeContext);
207
+ const queryKey = ["docCount", url, doctype, filters ?? [], debug];
208
+ const { data, error, isLoading, isFetching, refetch } = useQuery3({
209
+ queryKey,
210
+ queryFn: () => db.getCount(doctype, filters, debug),
211
+ enabled: !!doctype,
212
+ ...options
213
+ });
214
+ return {
215
+ data,
216
+ error,
217
+ isLoading,
218
+ isFetching,
219
+ refetch
220
+ };
221
+ };
222
+ // src/frappe/hooks/create.ts
223
+ import { useContext as useContext4 } from "react";
224
+ import { useMutation as useMutation3 } from "@tanstack/react-query";
225
+ var useFrappeCreateDoc = () => {
226
+ const { db } = useContext4(FrappeContext);
227
+ const { mutateAsync, isPending, error, isSuccess, reset } = useMutation3({
228
+ mutationFn: ({ doctype, doc }) => db.createDoc(doctype, doc)
229
+ });
230
+ const createDoc = (doctype, doc) => mutateAsync({ doctype, doc });
231
+ return {
232
+ createDoc,
233
+ loading: isPending,
234
+ error,
235
+ isCompleted: isSuccess,
236
+ reset
237
+ };
238
+ };
239
+ // src/frappe/hooks/delete.ts
240
+ import { useContext as useContext5 } from "react";
241
+ import { useMutation as useMutation4 } from "@tanstack/react-query";
242
+ var useFrappeDeleteDoc = () => {
243
+ const { db } = useContext5(FrappeContext);
244
+ const { mutateAsync, isPending, error, isSuccess, reset } = useMutation4({
245
+ mutationFn: ({ doctype, docname }) => db.deleteDoc(doctype, docname)
246
+ });
247
+ const deleteDoc = (doctype, docname) => mutateAsync({ doctype, docname });
248
+ return {
249
+ deleteDoc,
250
+ loading: isPending,
251
+ error,
252
+ isCompleted: isSuccess,
253
+ reset
254
+ };
255
+ };
256
+ // src/frappe/hooks/event.ts
257
+ import { useCallback as useCallback2, useContext as useContext6, useState as useState2, useEffect as useEffect2 } from "react";
258
+ var useFrappeEventListener = (eventName, callback) => {
259
+ const { socket } = useContext6(FrappeContext);
260
+ useEffect2(() => {
261
+ if (socket === undefined) {
262
+ console.warn("Socket is not enabled. Please enable socket in FrappeProvider.");
263
+ }
264
+ let listener = socket?.on(eventName, callback);
265
+ return () => {
266
+ listener?.off(eventName);
267
+ };
268
+ }, [eventName, callback, socket]);
269
+ };
270
+ var useFrappeDocumentEventListener = (doctype, docname, onUpdateCallback, emitOpenCloseEventsOnMount = true) => {
271
+ const { socket } = useContext6(FrappeContext);
272
+ const [viewers, setViewers] = useState2([]);
273
+ useEffect2(() => {
274
+ if (socket === undefined) {
275
+ console.warn("Socket is not enabled. Please enable socket in FrappeProvider.");
276
+ }
277
+ socket?.emit("doc_subscribe", doctype, docname);
278
+ const onReconnect = () => {
279
+ socket?.emit("doc_subscribe", doctype, docname);
280
+ };
281
+ socket?.io.on("reconnect", onReconnect);
282
+ if (emitOpenCloseEventsOnMount) {
283
+ socket?.emit("doc_open", doctype, docname);
284
+ }
285
+ return () => {
286
+ socket?.emit("doc_unsubscribe", doctype, docname);
287
+ if (emitOpenCloseEventsOnMount) {
288
+ socket?.emit("doc_close", doctype, docname);
289
+ }
290
+ socket?.io.off("reconnect", onReconnect);
291
+ };
292
+ }, [doctype, docname, emitOpenCloseEventsOnMount, socket]);
293
+ useFrappeEventListener("doc_update", onUpdateCallback);
294
+ const emitDocOpen = useCallback2(() => {
295
+ socket?.emit("doc_open", doctype, docname);
296
+ }, [doctype, docname, socket]);
297
+ const emitDocClose = useCallback2(() => {
298
+ socket?.emit("doc_close", doctype, docname);
299
+ }, [doctype, docname, socket]);
300
+ const onViewerEvent = useCallback2((data) => {
301
+ if (data.doctype === doctype && data.docname === docname) {
302
+ setViewers(data.users);
303
+ }
304
+ }, [doctype, docname]);
305
+ useFrappeEventListener("doc_viewers", onViewerEvent);
306
+ return {
307
+ viewers,
308
+ emitDocOpen,
309
+ emitDocClose
310
+ };
311
+ };
312
+ var useFrappeDocTypeEventListener = (doctype, onListUpdateCallback) => {
313
+ const { socket } = useContext6(FrappeContext);
314
+ useEffect2(() => {
315
+ if (socket === undefined) {
316
+ console.warn("Socket is not enabled. Please enable socket in FrappeProvider.");
317
+ }
318
+ socket?.emit("doctype_subscribe", doctype);
319
+ const onReconnect = () => {
320
+ socket?.emit("doctype_subscribe", doctype);
321
+ };
322
+ socket?.io.on("reconnect", onReconnect);
323
+ return () => {
324
+ socket?.emit("doctype_unsubscribe", doctype);
325
+ socket?.io.off("reconnect", onReconnect);
326
+ };
327
+ }, [doctype, socket]);
328
+ useFrappeEventListener("list_update", onListUpdateCallback);
329
+ };
330
+ // src/frappe/hooks/file.ts
331
+ import { useContext as useContext7, useState as useState3 } from "react";
332
+ import { useMutation as useMutation5 } from "@tanstack/react-query";
333
+ var useFrappeFileUpload = () => {
334
+ const { file } = useContext7(FrappeContext);
335
+ const [progress, setProgress] = useState3(0);
336
+ const {
337
+ mutateAsync,
338
+ isPending,
339
+ error,
340
+ isSuccess,
341
+ reset: resetMutation
342
+ } = useMutation5({
343
+ mutationFn: ({ f, args, apiPath }) => {
344
+ setProgress(0);
345
+ return file.uploadFile(f, args, (completed, total) => {
346
+ if (total)
347
+ setProgress(Math.round(completed / total * 100));
348
+ }, apiPath).then((r) => {
349
+ setProgress(100);
350
+ return r.data.message;
351
+ });
352
+ }
353
+ });
354
+ const upload = (f, args, apiPath) => mutateAsync({ f, args, apiPath });
355
+ const reset = () => {
356
+ setProgress(0);
357
+ resetMutation();
358
+ };
359
+ return {
360
+ upload,
361
+ progress,
362
+ loading: isPending,
363
+ error: error ?? null,
364
+ isCompleted: isSuccess,
365
+ reset
366
+ };
367
+ };
368
+ // src/frappe/hooks/get.ts
369
+ import { useContext as useContext8 } from "react";
370
+ import { useQuery as useQuery4 } from "@tanstack/react-query";
371
+ var useFrappeGetDoc = (doctype, name, options) => {
372
+ const { url, db } = useContext8(FrappeContext);
373
+ const queryKey = ["doc", url, doctype, name];
374
+ const { data, error, isLoading, isFetching, refetch } = useQuery4({
375
+ queryKey,
376
+ queryFn: () => db.getDoc(doctype, name),
377
+ enabled: !!name,
378
+ staleTime: options?.staleTime ?? 5 * 60 * 1000,
379
+ retry: options?.retry ?? false
380
+ });
381
+ return {
382
+ data,
383
+ error,
384
+ isLoading,
385
+ isFetching,
386
+ refetch
387
+ };
388
+ };
389
+ // src/frappe/hooks/list.ts
390
+ import { useContext as useContext9 } from "react";
391
+ import {
392
+ useQuery as useQuery5
393
+ } from "@tanstack/react-query";
394
+ var useFrappeGetDocList = (doctype, args, options) => {
395
+ const { url, db } = useContext9(FrappeContext);
396
+ const queryKey = ["docList", url, doctype, args ?? null];
397
+ const { data, error, isLoading, isFetching, refetch } = useQuery5({
398
+ queryKey,
399
+ queryFn: () => db.getDocList(doctype, args),
400
+ ...options
401
+ });
402
+ return {
403
+ data,
404
+ error: error ?? null,
405
+ isLoading,
406
+ isFetching,
407
+ refetch
408
+ };
409
+ };
410
+ // src/frappe/hooks/prefetch.ts
411
+ import { useCallback as useCallback3, useContext as useContext10 } from "react";
412
+ import { useQueryClient as useQueryClient2 } from "@tanstack/react-query";
413
+ var useFrappePrefetchDoc = (doctype, name, options) => {
414
+ const queryClient = useQueryClient2();
415
+ const { url, db } = useContext10(FrappeContext);
416
+ const preloadDoc = useCallback3(() => {
417
+ if (!name)
418
+ return;
419
+ const queryKey = ["doc", url, doctype, name];
420
+ queryClient.prefetchQuery({
421
+ queryKey,
422
+ queryFn: () => db.getDoc(doctype, name),
423
+ staleTime: options?.staleTime ?? 5 * 60 * 1000
424
+ });
425
+ }, [doctype, name, queryClient, options?.staleTime]);
426
+ return preloadDoc;
427
+ };
428
+ // src/frappe/hooks/search.ts
429
+ import { useEffect as useEffect3, useState as useState4 } from "react";
430
+ var useSearch = (doctype, text, filters = [], limit = 20, debounce = 250) => {
431
+ const debouncedText = useDebounce(text, debounce);
432
+ return useFrappeGetCall("frappe.desk.search.search_link", {
433
+ doctype,
434
+ page_length: limit,
435
+ txt: debouncedText,
436
+ filters: JSON.stringify(filters ?? [])
437
+ });
438
+ };
439
+ var useDebounce = (value, delay) => {
440
+ const [debouncedValue, setDebouncedValue] = useState4(value);
441
+ useEffect3(() => {
442
+ const handler = setTimeout(() => {
443
+ setDebouncedValue(value);
444
+ }, delay);
445
+ return () => {
446
+ clearTimeout(handler);
447
+ };
448
+ }, [value, delay]);
449
+ return debouncedValue;
450
+ };
451
+ // src/frappe/hooks/update.ts
452
+ import { useContext as useContext11 } from "react";
453
+ import { useMutation as useMutation6 } from "@tanstack/react-query";
454
+ var useFrappeUpdateDoc = () => {
455
+ const { db } = useContext11(FrappeContext);
456
+ const { mutateAsync, isPending, error, isSuccess, reset } = useMutation6({
457
+ mutationFn: ({ doctype, docname, doc }) => db.updateDoc(doctype, docname, doc)
458
+ });
459
+ const updateDoc = (doctype, docname, doc) => mutateAsync({ doctype, docname, doc });
460
+ return {
461
+ updateDoc,
462
+ loading: isPending,
463
+ error,
464
+ isCompleted: isSuccess,
465
+ reset
466
+ };
467
+ };
468
+ // src/utils/char.ts
469
+ import { camelize, decamelize, camelizeKeys, decamelizeKeys } from "humps";
470
+ function equalsIgnoreCase(str1, str2) {
471
+ return str1.localeCompare(str2, undefined, { sensitivity: "accent" }) === 0;
472
+ }
473
+ export {
474
+ useSearch,
475
+ useFrappeUpdateDoc,
476
+ useFrappePutCall,
477
+ useFrappePrefetchDoc,
478
+ useFrappePostCall,
479
+ useFrappeGetDocList,
480
+ useFrappeGetDocCount,
481
+ useFrappeGetDoc,
482
+ useFrappeGetCall,
483
+ useFrappeFileUpload,
484
+ useFrappeEventListener,
485
+ useFrappeDocumentEventListener,
486
+ useFrappeDocTypeEventListener,
487
+ useFrappeDeleteDoc,
488
+ useFrappeDeleteCall,
489
+ useFrappeCreateDoc,
490
+ useFrappeAuth,
491
+ equalsIgnoreCase,
492
+ decamelizeKeys,
493
+ decamelize,
494
+ camelizeKeys,
495
+ camelize,
496
+ FrappeProvider,
497
+ FrappeContext
498
+ };