@multiplatform.one/frappe 6.6.0 → 7.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 (224) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/auth.native.js.map +1 -1
  3. package/dist/cjs/call.native.js.map +1 -1
  4. package/dist/cjs/collection.native.js.map +1 -1
  5. package/dist/cjs/common/types.native.js.map +1 -1
  6. package/dist/cjs/core/http.cjs +45 -1
  7. package/dist/cjs/core/http.native.js +72 -1
  8. package/dist/cjs/core/http.native.js.map +1 -1
  9. package/dist/cjs/core/types.native.js.map +1 -1
  10. package/dist/cjs/core/wire.native.js.map +1 -1
  11. package/dist/cjs/db.native.js.map +1 -1
  12. package/dist/cjs/devtools/FrappeDevtoolsPanel.native.js.map +1 -1
  13. package/dist/cjs/devtools/index.native.js.map +1 -1
  14. package/dist/cjs/devtools/plugin.native.js.map +1 -1
  15. package/dist/cjs/devtools/theme.native.js.map +1 -1
  16. package/dist/cjs/doctype.cjs +15 -2
  17. package/dist/cjs/doctype.native.js +22 -5
  18. package/dist/cjs/doctype.native.js.map +1 -1
  19. package/dist/cjs/file.native.js.map +1 -1
  20. package/dist/cjs/fixtureDb.native.js.map +1 -1
  21. package/dist/cjs/fixtures.native.js.map +1 -1
  22. package/dist/cjs/frappeApp.native.js.map +1 -1
  23. package/dist/cjs/frappeReact.cjs +55 -1
  24. package/dist/cjs/frappeReact.native.js +67 -2
  25. package/dist/cjs/frappeReact.native.js.map +1 -1
  26. package/dist/cjs/index.cjs +8 -0
  27. package/dist/cjs/index.native.js +10 -2
  28. package/dist/cjs/index.native.js.map +1 -1
  29. package/dist/cjs/optimistic.cjs +32 -2
  30. package/dist/cjs/optimistic.native.js +34 -1
  31. package/dist/cjs/optimistic.native.js.map +1 -1
  32. package/dist/cjs/pacer.native.js.map +1 -1
  33. package/dist/cjs/plugin/index.native.js.map +1 -1
  34. package/dist/cjs/realtime.cjs +234 -11
  35. package/dist/cjs/realtime.native.js +368 -18
  36. package/dist/cjs/realtime.native.js.map +1 -1
  37. package/dist/cjs/sync/backfill.native.js.map +1 -1
  38. package/dist/cjs/sync/filters.native.js.map +1 -1
  39. package/dist/cjs/sync/index.cjs +45 -1
  40. package/dist/cjs/sync/index.native.js +71 -1
  41. package/dist/cjs/sync/index.native.js.map +1 -1
  42. package/dist/cjs/sync/merge.native.js.map +1 -1
  43. package/dist/cjs/sync/mutationEngine.cjs +51 -15
  44. package/dist/cjs/sync/mutationEngine.native.js +69 -16
  45. package/dist/cjs/sync/mutationEngine.native.js.map +1 -1
  46. package/dist/cjs/sync/mutationJournal.cjs +8 -0
  47. package/dist/cjs/sync/mutationJournal.native.js +10 -0
  48. package/dist/cjs/sync/mutationJournal.native.js.map +1 -1
  49. package/dist/cjs/sync/normalizedCache.native.js.map +1 -1
  50. package/dist/cjs/sync/persistence.native.js.map +1 -1
  51. package/dist/cjs/sync/realtime.native.js.map +1 -1
  52. package/dist/cjs/sync/rehydration.native.js.map +1 -1
  53. package/dist/cjs/sync/storage.native.js.map +1 -1
  54. package/dist/cjs/sync/subscriptionManager.native.js.map +1 -1
  55. package/dist/cjs/sync/types.native.js.map +1 -1
  56. package/dist/cjs/sync/virtualPageManager.native.js.map +1 -1
  57. package/dist/cjs/useLiveQuery.native.js.map +1 -1
  58. package/dist/cjs/util.native.js.map +1 -1
  59. package/dist/esm/auth.native.js.map +1 -1
  60. package/dist/esm/call.native.js.map +1 -1
  61. package/dist/esm/collection.native.js +1 -1
  62. package/dist/esm/collection.native.js.map +1 -1
  63. package/dist/esm/common/types.native.js.map +1 -1
  64. package/dist/esm/core/http.mjs +45 -2
  65. package/dist/esm/core/http.mjs.map +1 -1
  66. package/dist/esm/core/http.native.js +74 -4
  67. package/dist/esm/core/http.native.js.map +1 -1
  68. package/dist/esm/core/types.native.js.map +1 -1
  69. package/dist/esm/core/wire.native.js.map +1 -1
  70. package/dist/esm/db.native.js +1 -1
  71. package/dist/esm/db.native.js.map +1 -1
  72. package/dist/esm/devtools/FrappeDevtoolsPanel.native.js +2 -2
  73. package/dist/esm/devtools/FrappeDevtoolsPanel.native.js.map +1 -1
  74. package/dist/esm/devtools/index.native.js +2 -2
  75. package/dist/esm/devtools/index.native.js.map +1 -1
  76. package/dist/esm/devtools/plugin.native.js +1 -1
  77. package/dist/esm/devtools/plugin.native.js.map +1 -1
  78. package/dist/esm/devtools/theme.native.js.map +1 -1
  79. package/dist/esm/doctype.mjs +15 -2
  80. package/dist/esm/doctype.mjs.map +1 -1
  81. package/dist/esm/doctype.native.js +23 -6
  82. package/dist/esm/doctype.native.js.map +1 -1
  83. package/dist/esm/file.native.js.map +1 -1
  84. package/dist/esm/fixtureDb.native.js +1 -1
  85. package/dist/esm/fixtureDb.native.js.map +1 -1
  86. package/dist/esm/fixtures.native.js.map +1 -1
  87. package/dist/esm/frappeApp.native.js +7 -7
  88. package/dist/esm/frappeApp.native.js.map +1 -1
  89. package/dist/esm/frappeReact.mjs +55 -3
  90. package/dist/esm/frappeReact.mjs.map +1 -1
  91. package/dist/esm/frappeReact.native.js +73 -10
  92. package/dist/esm/frappeReact.native.js.map +1 -1
  93. package/dist/esm/index.mjs +5 -5
  94. package/dist/esm/index.mjs.map +1 -1
  95. package/dist/esm/index.native.js +22 -22
  96. package/dist/esm/index.native.js.map +1 -1
  97. package/dist/esm/optimistic.mjs +27 -1
  98. package/dist/esm/optimistic.mjs.map +1 -1
  99. package/dist/esm/optimistic.native.js +30 -1
  100. package/dist/esm/optimistic.native.js.map +1 -1
  101. package/dist/esm/pacer.mjs.map +1 -1
  102. package/dist/esm/pacer.native.js +4 -4
  103. package/dist/esm/pacer.native.js.map +1 -1
  104. package/dist/esm/plugin/index.native.js.map +1 -1
  105. package/dist/esm/realtime.mjs +234 -11
  106. package/dist/esm/realtime.mjs.map +1 -1
  107. package/dist/esm/realtime.native.js +369 -19
  108. package/dist/esm/realtime.native.js.map +1 -1
  109. package/dist/esm/sync/backfill.native.js +1 -1
  110. package/dist/esm/sync/backfill.native.js.map +1 -1
  111. package/dist/esm/sync/filters.native.js.map +1 -1
  112. package/dist/esm/sync/index.mjs +45 -1
  113. package/dist/esm/sync/index.mjs.map +1 -1
  114. package/dist/esm/sync/index.native.js +79 -9
  115. package/dist/esm/sync/index.native.js.map +1 -1
  116. package/dist/esm/sync/merge.native.js.map +1 -1
  117. package/dist/esm/sync/mutationEngine.mjs +51 -15
  118. package/dist/esm/sync/mutationEngine.mjs.map +1 -1
  119. package/dist/esm/sync/mutationEngine.native.js +73 -20
  120. package/dist/esm/sync/mutationEngine.native.js.map +1 -1
  121. package/dist/esm/sync/mutationJournal.mjs +8 -0
  122. package/dist/esm/sync/mutationJournal.mjs.map +1 -1
  123. package/dist/esm/sync/mutationJournal.native.js +11 -1
  124. package/dist/esm/sync/mutationJournal.native.js.map +1 -1
  125. package/dist/esm/sync/normalizedCache.native.js.map +1 -1
  126. package/dist/esm/sync/persistence.native.js +2 -2
  127. package/dist/esm/sync/persistence.native.js.map +1 -1
  128. package/dist/esm/sync/realtime.native.js +1 -1
  129. package/dist/esm/sync/realtime.native.js.map +1 -1
  130. package/dist/esm/sync/rehydration.native.js +1 -1
  131. package/dist/esm/sync/rehydration.native.js.map +1 -1
  132. package/dist/esm/sync/storage.native.js.map +1 -1
  133. package/dist/esm/sync/subscriptionManager.native.js +4 -4
  134. package/dist/esm/sync/subscriptionManager.native.js.map +1 -1
  135. package/dist/esm/sync/types.mjs.map +1 -1
  136. package/dist/esm/sync/types.native.js.map +1 -1
  137. package/dist/esm/sync/virtualPageManager.native.js +1 -1
  138. package/dist/esm/sync/virtualPageManager.native.js.map +1 -1
  139. package/dist/esm/useLiveQuery.native.js.map +1 -1
  140. package/dist/esm/util.native.js.map +1 -1
  141. package/dist/jsx/core/http.mjs +45 -2
  142. package/dist/jsx/core/http.mjs.map +1 -1
  143. package/dist/jsx/core/http.native.js +72 -1
  144. package/dist/jsx/core/http.native.js.map +1 -1
  145. package/dist/jsx/doctype.mjs +15 -2
  146. package/dist/jsx/doctype.mjs.map +1 -1
  147. package/dist/jsx/doctype.native.js +22 -5
  148. package/dist/jsx/doctype.native.js.map +1 -1
  149. package/dist/jsx/frappeReact.mjs +55 -3
  150. package/dist/jsx/frappeReact.mjs.map +1 -1
  151. package/dist/jsx/frappeReact.native.js +66 -1
  152. package/dist/jsx/frappeReact.native.js.map +1 -1
  153. package/dist/jsx/index.js +5 -5
  154. package/dist/jsx/index.js.map +1 -1
  155. package/dist/jsx/index.mjs +5 -5
  156. package/dist/jsx/index.mjs.map +1 -1
  157. package/dist/jsx/index.native.js +8 -0
  158. package/dist/jsx/index.native.js.map +1 -1
  159. package/dist/jsx/optimistic.mjs +27 -1
  160. package/dist/jsx/optimistic.mjs.map +1 -1
  161. package/dist/jsx/optimistic.native.js +34 -1
  162. package/dist/jsx/optimistic.native.js.map +1 -1
  163. package/dist/jsx/pacer.mjs.map +1 -1
  164. package/dist/jsx/pacer.native.js.map +1 -1
  165. package/dist/jsx/realtime.mjs +234 -11
  166. package/dist/jsx/realtime.mjs.map +1 -1
  167. package/dist/jsx/realtime.native.js +368 -18
  168. package/dist/jsx/realtime.native.js.map +1 -1
  169. package/dist/jsx/sync/index.mjs +45 -1
  170. package/dist/jsx/sync/index.mjs.map +1 -1
  171. package/dist/jsx/sync/index.native.js +71 -1
  172. package/dist/jsx/sync/index.native.js.map +1 -1
  173. package/dist/jsx/sync/mutationEngine.mjs +51 -15
  174. package/dist/jsx/sync/mutationEngine.mjs.map +1 -1
  175. package/dist/jsx/sync/mutationEngine.native.js +69 -16
  176. package/dist/jsx/sync/mutationEngine.native.js.map +1 -1
  177. package/dist/jsx/sync/mutationJournal.mjs +8 -0
  178. package/dist/jsx/sync/mutationJournal.mjs.map +1 -1
  179. package/dist/jsx/sync/mutationJournal.native.js +10 -0
  180. package/dist/jsx/sync/mutationJournal.native.js.map +1 -1
  181. package/dist/jsx/sync/types.mjs.map +1 -1
  182. package/dist/jsx/sync/types.native.js.map +1 -1
  183. package/package.json +8 -8
  184. package/src/core/http.spec.ts +81 -0
  185. package/src/core/http.ts +80 -3
  186. package/src/doctype.spec.ts +82 -3
  187. package/src/doctype.ts +51 -7
  188. package/src/frappeReact.ts +126 -4
  189. package/src/index.ts +28 -3
  190. package/src/optimistic.spec.ts +113 -2
  191. package/src/optimistic.ts +95 -0
  192. package/src/pacer.ts +8 -0
  193. package/src/realtime.spec.ts +62 -0
  194. package/src/realtime.ts +440 -14
  195. package/src/realtimeSse.spec.ts +547 -0
  196. package/src/sync/index.ts +44 -1
  197. package/src/sync/mutationEngine.spec.ts +7 -2
  198. package/src/sync/mutationEngine.ts +83 -27
  199. package/src/sync/mutationJournal.ts +9 -0
  200. package/src/sync/mutations.spec.ts +1 -1
  201. package/src/sync/syncModule.spec.ts +255 -1
  202. package/src/sync/types.ts +20 -0
  203. package/types/core/http.d.ts +11 -0
  204. package/types/core/http.d.ts.map +1 -1
  205. package/types/doctype.d.ts +7 -0
  206. package/types/doctype.d.ts.map +1 -1
  207. package/types/frappeReact.d.ts +50 -2
  208. package/types/frappeReact.d.ts.map +1 -1
  209. package/types/index.d.ts +8 -6
  210. package/types/index.d.ts.map +1 -1
  211. package/types/optimistic.d.ts +38 -0
  212. package/types/optimistic.d.ts.map +1 -1
  213. package/types/pacer.d.ts +8 -0
  214. package/types/pacer.d.ts.map +1 -1
  215. package/types/realtime.d.ts +183 -1
  216. package/types/realtime.d.ts.map +1 -1
  217. package/types/sync/index.d.ts +16 -1
  218. package/types/sync/index.d.ts.map +1 -1
  219. package/types/sync/mutationEngine.d.ts +7 -0
  220. package/types/sync/mutationEngine.d.ts.map +1 -1
  221. package/types/sync/mutationJournal.d.ts +6 -0
  222. package/types/sync/mutationJournal.d.ts.map +1 -1
  223. package/types/sync/types.d.ts +20 -0
  224. package/types/sync/types.d.ts.map +1 -1
package/src/optimistic.ts CHANGED
@@ -37,3 +37,98 @@ export { tanstackCreateOptimisticAction as createOptimisticAction };
37
37
  * await tx.isPersisted.promise;
38
38
  * ```
39
39
  */
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // Optimistic RPC lifecycle — the ONE pattern for mutations that ride
43
+ // whitelisted desk RPCs instead of the document CRUD path.
44
+ // ---------------------------------------------------------------------------
45
+ //
46
+ // Document CRUD (collection insert/update/delete) is already optimistic via
47
+ // the MutationEngine: local cache apply → journal Pending → server ack →
48
+ // temp-id → real-name remap, snapshot rollback on failure. Desk RPC
49
+ // mutations (add_comment, assign_to, tags, likes, shares…) bypass that
50
+ // engine, and every callsite used to hand-roll "await, then refetch" — the
51
+ // user saw nothing until the server round-trip completed.
52
+ //
53
+ // `runOptimisticRpc` gives those callsites the same lifecycle the engine
54
+ // gives documents, without per-callsite hacks:
55
+ //
56
+ // 1. APPLY — synchronous local apply BEFORE the request leaves;
57
+ // returns the undo used for rollback. The applied state
58
+ // should carry a pending marker so surfaces can render it
59
+ // visible-but-distinct (Axiom 6 HONEST STATE).
60
+ // 2. SEND — the RPC itself.
61
+ // 3. RECONCILE — on ack, converge the optimistic identity (the issued
62
+ // `tmp_*` id) to the server identity so the optimistic
63
+ // record and the server record become ONE record, never
64
+ // duplicates.
65
+ // 4. ROLLBACK — on failure, the apply-phase undo runs, then `onError`
66
+ // surfaces a visible error (house `notify()`), and the
67
+ // error rethrows for the caller.
68
+
69
+ /** Prefix shared with the MutationEngine's synthesized create identities. */
70
+ export const OPTIMISTIC_TEMP_PREFIX = "tmp_";
71
+
72
+ /** Issues a client-only identity for an optimistic record (`tmp_<uuid>`). */
73
+ export function newOptimisticTempId(): string {
74
+ const id =
75
+ typeof crypto !== "undefined" && "randomUUID" in crypto
76
+ ? crypto.randomUUID()
77
+ : Math.random().toString(36).slice(2);
78
+ return `${OPTIMISTIC_TEMP_PREFIX}${id}`;
79
+ }
80
+
81
+ /** True when a document name is a client-issued optimistic identity. */
82
+ export function isOptimisticTempId(name: unknown): name is string {
83
+ return typeof name === "string" && name.startsWith(OPTIMISTIC_TEMP_PREFIX);
84
+ }
85
+
86
+ export interface OptimisticRpcContext {
87
+ /** Client-only identity for the optimistic record (`tmp_<uuid>`). */
88
+ tempId: string;
89
+ }
90
+
91
+ export interface OptimisticRpcOptions<TAck> {
92
+ /**
93
+ * Apply the mutation locally, synchronously, before the request is sent.
94
+ * Returns the undo that restores the exact pre-apply state (rollback).
95
+ * The applied record should use `ctx.tempId` as its identity and carry a
96
+ * pending marker for the UI.
97
+ */
98
+ apply: (ctx: OptimisticRpcContext) => (() => void) | undefined | void;
99
+ /** The server RPC. */
100
+ send: (ctx: OptimisticRpcContext) => Promise<TAck>;
101
+ /**
102
+ * Converge on ack: replace the `ctx.tempId` record with the server's
103
+ * authoritative record (dedupe by server identity — a racing refetch may
104
+ * already have delivered it).
105
+ */
106
+ reconcile?: (ack: TAck, ctx: OptimisticRpcContext) => void;
107
+ /**
108
+ * Visible-error surface for the rollback path (house `notify()` or an
109
+ * inline error slot). Runs after the apply-phase undo.
110
+ */
111
+ onError?: (error: unknown, ctx: OptimisticRpcContext) => void;
112
+ }
113
+
114
+ /**
115
+ * Runs one mutation through the optimistic lifecycle:
116
+ * apply (local, immediate) → send → reconcile on ack, or undo + onError on
117
+ * failure (the error rethrows).
118
+ */
119
+ export async function runOptimisticRpc<TAck>(options: OptimisticRpcOptions<TAck>): Promise<TAck> {
120
+ const ctx: OptimisticRpcContext = { tempId: newOptimisticTempId() };
121
+ const undo = options.apply(ctx) ?? undefined;
122
+ try {
123
+ const ack = await options.send(ctx);
124
+ options.reconcile?.(ack, ctx);
125
+ return ack;
126
+ } catch (error) {
127
+ try {
128
+ undo?.();
129
+ } finally {
130
+ options.onError?.(error, ctx);
131
+ }
132
+ throw error;
133
+ }
134
+ }
package/src/pacer.ts CHANGED
@@ -6,6 +6,7 @@ import { useCallback, useMemo, useRef, useState } from "react";
6
6
  import type { FrappeDoc, FrappeDocData } from "./common/types";
7
7
  import type { AuthConfig } from "./core/types";
8
8
  import { DbModule } from "./db";
9
+ import type { FixtureProvider } from "./fixtures";
9
10
  import { HttpClient } from "./core/http";
10
11
  import type { SyncModule } from "./sync";
11
12
  import { Operation } from "./sync/types";
@@ -62,6 +63,13 @@ export interface FrappeConnectionConfig {
62
63
  token?: string | (() => string | Promise<string>);
63
64
  type?: string;
64
65
  };
66
+ /**
67
+ * Fixture provider for story / test hosts. Consumers already forward it
68
+ * (frappe-ui's FrappeProvider builds this config from its own context), and
69
+ * `useDocTypeMeta` reads doctype metadata from it before reaching for the
70
+ * network.
71
+ */
72
+ fixtures?: FixtureProvider;
65
73
  }
66
74
 
67
75
  function createDbModule(config: FrappeConnectionConfig): DbModule {
@@ -783,6 +783,68 @@ describe("RealtimeModule", () => {
783
783
  rt.disconnect();
784
784
  });
785
785
 
786
+ it("uses an explicit siteName as the Socket.IO namespace (127.0.0.1 → localhost)", async () => {
787
+ // Frappe socketio maps Host localhost/127.0.0.1 to conf.default_site
788
+ // and rejects any other namespace. Connecting via the IPv4 literal
789
+ // (the serve_dev.py workaround for shcd on [::1]:8000) must still
790
+ // join /localhost, not /127.0.0.1.
791
+ const rt = new RealtimeModule({
792
+ baseURL: "http://127.0.0.1:8000",
793
+ socketPort: 9000,
794
+ siteName: "localhost",
795
+ });
796
+ await (rt as any).connect();
797
+
798
+ const calls = (io as unknown as ReturnType<typeof vi.fn>).mock.calls;
799
+ expect(calls[calls.length - 1]?.[0]).toBe("http://127.0.0.1:9000/localhost");
800
+ rt.disconnect();
801
+ });
802
+
803
+ it("dials socketBaseURL when set, joining the siteName namespace (same-origin dev)", async () => {
804
+ // Web dev: the dev server proxies /socket.io on the PAGE origin (e.g.
805
+ // :3998) to the bench socketio — the client must dial that origin, not
806
+ // baseURL+socketPort, so the polling handshake is same-origin.
807
+ const rt = new RealtimeModule({
808
+ baseURL: "http://127.0.0.1:8000",
809
+ socketBaseURL: "http://127.0.0.1:3998",
810
+ siteName: "localhost",
811
+ });
812
+ await (rt as any).connect();
813
+
814
+ const calls = (io as unknown as ReturnType<typeof vi.fn>).mock.calls;
815
+ expect(calls[calls.length - 1]?.[0]).toBe("http://127.0.0.1:3998/localhost");
816
+ rt.disconnect();
817
+ });
818
+
819
+ it("socketBaseURL wins over socketPort and trims a trailing slash", async () => {
820
+ const rt = new RealtimeModule({
821
+ baseURL: "http://127.0.0.1:8000",
822
+ socketPort: 9000,
823
+ socketBaseURL: "http://localhost:3998/",
824
+ siteName: "localhost",
825
+ });
826
+ await (rt as any).connect();
827
+
828
+ const calls = (io as unknown as ReturnType<typeof vi.fn>).mock.calls;
829
+ expect(calls[calls.length - 1]?.[0]).toBe("http://localhost:3998/localhost");
830
+ rt.disconnect();
831
+ });
832
+
833
+ it("falls back to the baseURL hostname namespace under socketBaseURL without siteName", async () => {
834
+ // Same fallback as the derived path: the namespace comes from the
835
+ // BENCH URL's hostname — never from socketBaseURL, whose hostname is
836
+ // the dev server, not a Frappe site.
837
+ const rt = new RealtimeModule({
838
+ baseURL: "http://bench.example.com:8000",
839
+ socketBaseURL: "http://localhost:3998",
840
+ });
841
+ await (rt as any).connect();
842
+
843
+ const calls = (io as unknown as ReturnType<typeof vi.fn>).mock.calls;
844
+ expect(calls[calls.length - 1]?.[0]).toBe("http://localhost:3998/bench.example.com");
845
+ rt.disconnect();
846
+ });
847
+
786
848
  it("uses the token scheme when auth type is token", async () => {
787
849
  const rt = new RealtimeModule({
788
850
  baseURL: "http://localhost:3000",