@arcote.tech/arc-react 0.3.4 → 0.4.2
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/dist/index.js +174 -62
- package/dist/src/factories/index.d.ts +0 -2
- package/dist/src/factories/model-provider-factory.d.ts +25 -2
- package/dist/src/form/form.d.ts +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/react-model.d.ts +2 -4
- package/package.json +6 -6
- package/dist/src/factories/use-commands-factory.d.ts +0 -3
- package/dist/src/factories/use-query-factory.d.ts +0 -3
package/dist/index.js
CHANGED
|
@@ -304,9 +304,19 @@ import {
|
|
|
304
304
|
Model,
|
|
305
305
|
QueryWire,
|
|
306
306
|
StreamingEventPublisher,
|
|
307
|
-
StreamingQueryCache
|
|
307
|
+
StreamingQueryCache,
|
|
308
|
+
buildContextAccessor,
|
|
309
|
+
resolveQueryChange
|
|
308
310
|
} from "@arcote.tech/arc";
|
|
309
|
-
import {
|
|
311
|
+
import {
|
|
312
|
+
createContext as createContext4,
|
|
313
|
+
useContext as useContext3,
|
|
314
|
+
useEffect as useEffect2,
|
|
315
|
+
useLayoutEffect,
|
|
316
|
+
useMemo as useMemo3,
|
|
317
|
+
useRef,
|
|
318
|
+
useState as useState3
|
|
319
|
+
} from "react";
|
|
310
320
|
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
311
321
|
var modelProviderFactory = (context, options) => {
|
|
312
322
|
const ModelContext = createContext4(null);
|
|
@@ -314,11 +324,21 @@ var modelProviderFactory = (context, options) => {
|
|
|
314
324
|
const eventWire = options.remoteUrl ? new EventWire(options.remoteUrl) : undefined;
|
|
315
325
|
const queryWire = options.remoteUrl ? new QueryWire(options.remoteUrl) : undefined;
|
|
316
326
|
const authAdapter = new AuthAdapter;
|
|
327
|
+
authAdapter.loadPersisted();
|
|
317
328
|
let initialized = false;
|
|
318
329
|
let cachedModel = null;
|
|
319
330
|
let cachedDataStorage = null;
|
|
320
331
|
let onResetCallback = null;
|
|
321
332
|
let needsReconnect = false;
|
|
333
|
+
const scopeTokenListeners = new Map;
|
|
334
|
+
function notifyScopeTokenListeners(scope) {
|
|
335
|
+
const listeners = scopeTokenListeners.get(scope);
|
|
336
|
+
if (listeners) {
|
|
337
|
+
for (const listener of listeners) {
|
|
338
|
+
listener();
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
322
342
|
function ModelProvider(props) {
|
|
323
343
|
const [model, setModel] = useState3(cachedModel);
|
|
324
344
|
const [resetTrigger, setResetTrigger] = useState3(0);
|
|
@@ -455,13 +475,12 @@ Event payload:`, event.payload);
|
|
|
455
475
|
}
|
|
456
476
|
if (cancelled)
|
|
457
477
|
return;
|
|
458
|
-
const modelEventWire = options.dbAdapterFactory ? eventWire : undefined;
|
|
459
478
|
const newModel = new Model(context, {
|
|
460
479
|
adapters: {
|
|
461
480
|
dataStorage,
|
|
462
481
|
commandWire,
|
|
463
482
|
eventPublisher,
|
|
464
|
-
eventWire
|
|
483
|
+
eventWire,
|
|
465
484
|
authAdapter,
|
|
466
485
|
queryWire,
|
|
467
486
|
streamingCache
|
|
@@ -501,16 +520,153 @@ Event payload:`, event.payload);
|
|
|
501
520
|
}
|
|
502
521
|
return model;
|
|
503
522
|
}
|
|
504
|
-
function
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
523
|
+
function syncPersistedToken(model, scopeName) {
|
|
524
|
+
const scoped = model.scope(scopeName);
|
|
525
|
+
const persisted = authAdapter.getToken(scopeName);
|
|
526
|
+
if (persisted && scoped.getToken() !== persisted) {
|
|
527
|
+
scoped.setToken(persisted);
|
|
528
|
+
}
|
|
529
|
+
return scoped;
|
|
530
|
+
}
|
|
531
|
+
function createScope(name) {
|
|
532
|
+
function setToken(token) {
|
|
533
|
+
authAdapter.setToken(token, name);
|
|
534
|
+
if (cachedModel) {
|
|
535
|
+
cachedModel.scope(name).setToken(token);
|
|
536
|
+
} else {
|
|
537
|
+
eventWire?.setScopeToken(name, token);
|
|
538
|
+
if (eventWire && token && eventWire.getState() === "disconnected") {
|
|
539
|
+
eventWire.connect();
|
|
540
|
+
}
|
|
512
541
|
}
|
|
542
|
+
notifyScopeTokenListeners(name);
|
|
543
|
+
}
|
|
544
|
+
function useToken() {
|
|
545
|
+
const [token, setTokenState] = useState3(() => authAdapter.getToken(name));
|
|
546
|
+
useEffect2(() => {
|
|
547
|
+
if (!scopeTokenListeners.has(name)) {
|
|
548
|
+
scopeTokenListeners.set(name, new Set);
|
|
549
|
+
}
|
|
550
|
+
const listeners = scopeTokenListeners.get(name);
|
|
551
|
+
const listener = () => setTokenState(authAdapter.getToken(name));
|
|
552
|
+
listeners.add(listener);
|
|
553
|
+
return () => {
|
|
554
|
+
listeners.delete(listener);
|
|
555
|
+
};
|
|
556
|
+
}, []);
|
|
557
|
+
return token;
|
|
558
|
+
}
|
|
559
|
+
const ScopeContext = createContext4(null);
|
|
560
|
+
function Provider(props) {
|
|
561
|
+
return /* @__PURE__ */ jsx5(ScopeContext.Provider, {
|
|
562
|
+
value: name,
|
|
563
|
+
children: props.children
|
|
564
|
+
}, undefined, false, undefined, this);
|
|
513
565
|
}
|
|
566
|
+
function useQuery() {
|
|
567
|
+
const model = useModel();
|
|
568
|
+
const token = useToken();
|
|
569
|
+
const [data, setData] = useState3(undefined);
|
|
570
|
+
const [loading, setLoading] = useState3(true);
|
|
571
|
+
const descriptorRef = useRef(null);
|
|
572
|
+
const [subKey, setSubKey] = useState3("");
|
|
573
|
+
const scoped = syncPersistedToken(model, name);
|
|
574
|
+
const adapters = scoped.getAdapters();
|
|
575
|
+
useLayoutEffect(() => {
|
|
576
|
+
const desc = descriptorRef.current;
|
|
577
|
+
if (!desc)
|
|
578
|
+
return;
|
|
579
|
+
const key = JSON.stringify({ ...desc, token });
|
|
580
|
+
if (key !== subKey)
|
|
581
|
+
setSubKey(key);
|
|
582
|
+
});
|
|
583
|
+
useEffect2(() => {
|
|
584
|
+
const desc = descriptorRef.current;
|
|
585
|
+
if (!desc || !subKey)
|
|
586
|
+
return;
|
|
587
|
+
const unsubs = [];
|
|
588
|
+
const element = model.context.get(desc.element);
|
|
589
|
+
const qCtx = element?.queryContext?.(adapters);
|
|
590
|
+
const method = qCtx?.[desc.method];
|
|
591
|
+
const reExecute = async () => {
|
|
592
|
+
if (!method)
|
|
593
|
+
return;
|
|
594
|
+
try {
|
|
595
|
+
const result = await method(...desc.args);
|
|
596
|
+
setData(result);
|
|
597
|
+
setLoading(false);
|
|
598
|
+
} catch (err) {
|
|
599
|
+
console.error(`[Arc] Query error:`, err);
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
if (adapters.streamingCache) {
|
|
603
|
+
const store = adapters.streamingCache.getStore(desc.element);
|
|
604
|
+
let cachedResult;
|
|
605
|
+
unsubs.push(store.subscribe((events) => {
|
|
606
|
+
if (!events) {
|
|
607
|
+
cachedResult = undefined;
|
|
608
|
+
reExecute();
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
if (cachedResult !== undefined) {
|
|
612
|
+
let current = cachedResult;
|
|
613
|
+
let changed = false;
|
|
614
|
+
for (const event of events) {
|
|
615
|
+
const newResult = resolveQueryChange(current, event, desc.args[0] ?? {});
|
|
616
|
+
if (newResult !== false) {
|
|
617
|
+
current = newResult;
|
|
618
|
+
changed = true;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (!changed)
|
|
622
|
+
return;
|
|
623
|
+
cachedResult = current;
|
|
624
|
+
reExecute();
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
reExecute();
|
|
628
|
+
}));
|
|
629
|
+
unsubs.push(adapters.streamingCache.subscribeQuery(desc, adapters.eventWire, name));
|
|
630
|
+
if (store.hasData()) {
|
|
631
|
+
reExecute();
|
|
632
|
+
}
|
|
633
|
+
} else {
|
|
634
|
+
if (adapters.eventPublisher) {
|
|
635
|
+
const eventTypes = element?.getElements?.()?.map((e) => e.name) ?? [];
|
|
636
|
+
if (eventTypes.length > 0) {
|
|
637
|
+
for (const eventType of eventTypes) {
|
|
638
|
+
unsubs.push(adapters.eventPublisher.subscribe(eventType, () => reExecute()));
|
|
639
|
+
}
|
|
640
|
+
} else {
|
|
641
|
+
unsubs.push(adapters.eventPublisher.subscribe("*", () => reExecute()));
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
reExecute();
|
|
645
|
+
}
|
|
646
|
+
return () => {
|
|
647
|
+
for (const unsub of unsubs)
|
|
648
|
+
unsub();
|
|
649
|
+
};
|
|
650
|
+
}, [subKey]);
|
|
651
|
+
return useMemo3(() => buildContextAccessor(model.context, adapters, "queryContext", (desc, _execute) => {
|
|
652
|
+
descriptorRef.current = desc;
|
|
653
|
+
return [data, loading];
|
|
654
|
+
}), [model, token, data, loading]);
|
|
655
|
+
}
|
|
656
|
+
function useMutation() {
|
|
657
|
+
const model = useModel();
|
|
658
|
+
return useMemo3(() => {
|
|
659
|
+
const scoped = syncPersistedToken(model, name);
|
|
660
|
+
return buildContextAccessor(model.context, scoped.getAdapters(), "mutateContext", (_descriptor, execute) => execute());
|
|
661
|
+
}, [model]);
|
|
662
|
+
}
|
|
663
|
+
return {
|
|
664
|
+
Provider,
|
|
665
|
+
useQuery,
|
|
666
|
+
useMutation,
|
|
667
|
+
setToken,
|
|
668
|
+
useToken
|
|
669
|
+
};
|
|
514
670
|
}
|
|
515
671
|
async function resetModel() {
|
|
516
672
|
eventWire?.disconnect();
|
|
@@ -520,72 +676,28 @@ Event payload:`, event.payload);
|
|
|
520
676
|
}
|
|
521
677
|
cachedModel = null;
|
|
522
678
|
initialized = false;
|
|
523
|
-
authAdapter.
|
|
524
|
-
commandWire?.setAuthToken(null);
|
|
525
|
-
queryWire?.setAuthToken(null);
|
|
526
|
-
eventWire?.setAuthToken(null);
|
|
679
|
+
authAdapter.clear();
|
|
527
680
|
if (onResetCallback) {
|
|
528
681
|
onResetCallback();
|
|
529
682
|
}
|
|
530
683
|
}
|
|
531
|
-
function onReset(callback) {
|
|
532
|
-
onResetCallback = callback;
|
|
533
|
-
}
|
|
534
684
|
return {
|
|
535
685
|
ModelProvider,
|
|
536
686
|
useModel,
|
|
537
687
|
commandWire,
|
|
538
688
|
eventWire,
|
|
539
|
-
|
|
540
|
-
resetModel
|
|
541
|
-
onReset
|
|
542
|
-
};
|
|
543
|
-
};
|
|
544
|
-
// src/factories/use-commands-factory.tsx
|
|
545
|
-
import {
|
|
546
|
-
mutationExecutor
|
|
547
|
-
} from "@arcote.tech/arc";
|
|
548
|
-
import { useMemo as useMemo3 } from "react";
|
|
549
|
-
var useCommandsFactory = (useModel) => {
|
|
550
|
-
return function useCommands() {
|
|
551
|
-
const model = useModel();
|
|
552
|
-
return useMemo3(() => mutationExecutor(model), [model]);
|
|
553
|
-
};
|
|
554
|
-
};
|
|
555
|
-
// src/factories/use-query-factory.tsx
|
|
556
|
-
import {
|
|
557
|
-
liveQuery
|
|
558
|
-
} from "@arcote.tech/arc";
|
|
559
|
-
import { useEffect as useEffect3, useState as useState4 } from "react";
|
|
560
|
-
var useQueryFactory = (useModel) => {
|
|
561
|
-
return function useQuery(queryFn, dependencies = []) {
|
|
562
|
-
const model = useModel();
|
|
563
|
-
const [loading, setLoading] = useState4(true);
|
|
564
|
-
const [result, setResult] = useState4(undefined);
|
|
565
|
-
useEffect3(() => {
|
|
566
|
-
const { unsubscribe } = liveQuery(model, queryFn, (newResult) => {
|
|
567
|
-
setResult(newResult);
|
|
568
|
-
setLoading(false);
|
|
569
|
-
});
|
|
570
|
-
return () => {
|
|
571
|
-
unsubscribe();
|
|
572
|
-
};
|
|
573
|
-
}, [model, ...dependencies]);
|
|
574
|
-
return [result, loading];
|
|
689
|
+
createScope,
|
|
690
|
+
resetModel
|
|
575
691
|
};
|
|
576
692
|
};
|
|
577
693
|
// src/react-model.tsx
|
|
578
694
|
var reactModel = (context, options = {}) => {
|
|
579
|
-
const { ModelProvider, useModel, commandWire,
|
|
580
|
-
const useQuery = useQueryFactory(useModel);
|
|
581
|
-
const useCommands = useCommandsFactory(useModel);
|
|
695
|
+
const { ModelProvider, useModel, commandWire, createScope, resetModel } = modelProviderFactory(context, options);
|
|
582
696
|
return {
|
|
583
697
|
ModelProvider,
|
|
584
698
|
useModel,
|
|
585
|
-
|
|
586
|
-
useCommands,
|
|
699
|
+
createScope,
|
|
587
700
|
commandWire,
|
|
588
|
-
setAuthToken,
|
|
589
701
|
resetModel
|
|
590
702
|
};
|
|
591
703
|
};
|
|
@@ -604,4 +716,4 @@ export {
|
|
|
604
716
|
Form
|
|
605
717
|
};
|
|
606
718
|
|
|
607
|
-
//# debugId=
|
|
719
|
+
//# debugId=075760FF00A2465564756E2164756E21
|
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
import { CommandWire, EventWire, Model, type ArcContextAny } from "@arcote.tech/arc";
|
|
2
2
|
import type { ReactModelOptions } from "../options";
|
|
3
|
+
/**
|
|
4
|
+
* Accessor type for useQuery() — maps element names to their query methods,
|
|
5
|
+
* where each method returns [data, loading] tuple.
|
|
6
|
+
*/
|
|
7
|
+
export type QueryAccessor<C extends ArcContextAny> = {
|
|
8
|
+
[E in C["elements"][number] as E["queryContext"] extends (...args: any[]) => any ? E["name"] : never]: E["queryContext"] extends (...args: any[]) => infer R ? {
|
|
9
|
+
[K in keyof R]: R[K] extends (...args: infer A) => Promise<infer V> ? (...args: A) => readonly [V | undefined, boolean] : never;
|
|
10
|
+
} : never;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Accessor type for useMutation() — maps element names to their mutation methods.
|
|
14
|
+
*/
|
|
15
|
+
export type MutationAccessor<C extends ArcContextAny> = {
|
|
16
|
+
[E in C["elements"][number] as E["mutateContext"] extends (...args: any[]) => any ? E["name"] : never]: E["mutateContext"] extends (...args: any[]) => infer R ? R : never;
|
|
17
|
+
};
|
|
18
|
+
export interface ScopeAPI<C extends ArcContextAny = ArcContextAny> {
|
|
19
|
+
Provider: React.FC<{
|
|
20
|
+
children: React.ReactNode;
|
|
21
|
+
}>;
|
|
22
|
+
useQuery: () => QueryAccessor<C>;
|
|
23
|
+
useMutation: () => MutationAccessor<C>;
|
|
24
|
+
setToken: (token: string | null) => void;
|
|
25
|
+
useToken: () => string | null;
|
|
26
|
+
}
|
|
3
27
|
export declare const modelProviderFactory: <C extends ArcContextAny>(context: C, options: ReactModelOptions) => {
|
|
4
28
|
ModelProvider: (props: {
|
|
5
29
|
children: React.ReactNode;
|
|
@@ -7,8 +31,7 @@ export declare const modelProviderFactory: <C extends ArcContextAny>(context: C,
|
|
|
7
31
|
useModel: () => Model<C>;
|
|
8
32
|
commandWire: CommandWire | undefined;
|
|
9
33
|
eventWire: EventWire | undefined;
|
|
10
|
-
|
|
34
|
+
createScope: (name: string) => ScopeAPI<C>;
|
|
11
35
|
resetModel: () => Promise<void>;
|
|
12
|
-
onReset: (callback: () => void) => void;
|
|
13
36
|
};
|
|
14
37
|
//# sourceMappingURL=model-provider-factory.d.ts.map
|
package/dist/src/form/form.d.ts
CHANGED
|
@@ -28,5 +28,5 @@ export type FormProps<T extends ArcObjectAny> = {
|
|
|
28
28
|
};
|
|
29
29
|
export declare const FormContext: React.Context<FormContextValue<any> | null>;
|
|
30
30
|
export declare function useForm<T extends ArcObjectAny>(): FormContextValue<T>;
|
|
31
|
-
export declare const Form: <T extends ArcObjectAny>(props: FormProps<T> & React.RefAttributes<FormRef<T>>) => JSX.Element;
|
|
31
|
+
export declare const Form: <T extends ArcObjectAny>(props: FormProps<T> & React.RefAttributes<FormRef<T>>) => React.JSX.Element;
|
|
32
32
|
//# sourceMappingURL=form.d.ts.map
|
package/dist/src/index.d.ts
CHANGED
|
@@ -5,12 +5,10 @@ export declare const reactModel: <C extends ArcContextAny>(context: C, options?:
|
|
|
5
5
|
children: React.ReactNode;
|
|
6
6
|
}) => import("react/jsx-dev-runtime").JSX.Element | undefined;
|
|
7
7
|
readonly useModel: () => import("@arcote.tech/arc").Model<C>;
|
|
8
|
-
|
|
9
|
-
readonly
|
|
8
|
+
/** Create a named scope with its own token, Provider, and hooks */
|
|
9
|
+
readonly createScope: (name: string) => import("./factories").ScopeAPI<C>;
|
|
10
10
|
/** CommandWire instance for remote execution (if remoteUrl provided) */
|
|
11
11
|
readonly commandWire: import("@arcote.tech/arc").CommandWire | undefined;
|
|
12
|
-
/** Set auth token for remote command execution */
|
|
13
|
-
readonly setAuthToken: (token: string | null) => void;
|
|
14
12
|
/** Reset model - destroys local database and disconnects from host */
|
|
15
13
|
readonly resetModel: () => Promise<void>;
|
|
16
14
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcote.tech/arc-react",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.2",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Przemysław Krasiński [arcote.tech]",
|
|
7
7
|
"description": "React client for the Arc framework, providing utilities for querying data and executing commands, enhancing the development of reactive and efficient user interfaces.",
|
|
@@ -24,17 +24,17 @@
|
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/bun": "latest",
|
|
27
|
-
"@types/react": "^
|
|
28
|
-
"@types/react-dom": "^
|
|
27
|
+
"@types/react": "^19.2.7",
|
|
28
|
+
"@types/react-dom": "^19.2.3",
|
|
29
29
|
"nodemon": "^2.0.22",
|
|
30
30
|
"prettier": "^3.0.3",
|
|
31
31
|
"rimraf": "^5.0.5",
|
|
32
32
|
"typescript": "^5.2.2"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@arcote.tech/arc": "
|
|
36
|
-
"react": "^18.0.0",
|
|
37
|
-
"react-dom": "^18.0.0",
|
|
35
|
+
"@arcote.tech/arc": "^0.4.2",
|
|
36
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
37
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
38
38
|
"typescript": "^5.0.0"
|
|
39
39
|
},
|
|
40
40
|
"files": [
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import { type ArcContextAny, type Model } from "@arcote.tech/arc";
|
|
2
|
-
export declare const useCommandsFactory: <C extends ArcContextAny>(useModel: () => Model<C>) => () => { [Element in C["elements"][number] as Element["mutateContext"] extends (...args: any[]) => infer Return ? Element["name"] : never]: Element["mutateContext"] extends (...args: any[]) => infer Return ? Return : never; } extends infer T ? { [K in keyof T]: T[K]; } : never;
|
|
3
|
-
//# sourceMappingURL=use-commands-factory.d.ts.map
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import { type ArcContextAny, type Model, type QueryContext } from "@arcote.tech/arc";
|
|
2
|
-
export declare const useQueryFactory: <C extends ArcContextAny>(useModel: () => Model<C>) => <TResult>(queryFn: (q: QueryContext<C>) => Promise<TResult>, dependencies?: any[]) => [TResult | undefined, boolean];
|
|
3
|
-
//# sourceMappingURL=use-query-factory.d.ts.map
|