@arcote.tech/arc-react 0.1.9 → 0.3.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/dist/index.js +311 -221
- package/dist/src/factories/index.d.ts +4 -0
- package/dist/src/factories/model-provider-factory.d.ts +14 -0
- package/dist/src/factories/use-commands-factory.d.ts +3 -0
- package/dist/src/factories/use-query-factory.d.ts +3 -0
- package/dist/{form → src/form}/field.d.ts +6 -4
- package/dist/{form → src/form}/form.d.ts +7 -4
- package/dist/{form → src/form}/index.d.ts +0 -1
- package/dist/src/index.d.ts +4 -0
- package/dist/src/options.d.ts +6 -0
- package/dist/src/react-model.d.ts +17 -0
- package/package.json +5 -7
- package/dist/form/legacy_form_resolver.d.ts +0 -6
- package/dist/index.d.ts +0 -3
- package/dist/reactModel.d.ts +0 -21
- package/dist/sqliteWasmAdapter.d.ts +0 -4
- /package/dist/{form → src/form}/form-part.d.ts +0 -0
- /package/dist/{form → src/form}/message.d.ts +0 -0
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
// form/field.tsx
|
|
2
|
+
// src/form/field.tsx
|
|
3
3
|
import { createContext as createContext3, useCallback as useCallback3, useContext as useContext2, useMemo as useMemo2 } from "react";
|
|
4
4
|
|
|
5
|
-
// form/form.tsx
|
|
5
|
+
// src/form/form.tsx
|
|
6
6
|
import {
|
|
7
7
|
deepMerge
|
|
8
8
|
} from "@arcote.tech/arc";
|
|
9
|
-
import {
|
|
9
|
+
import React, {
|
|
10
10
|
createContext,
|
|
11
11
|
forwardRef,
|
|
12
12
|
useCallback,
|
|
@@ -17,6 +17,32 @@ import {
|
|
|
17
17
|
} from "react";
|
|
18
18
|
import { jsx } from "react/jsx-runtime";
|
|
19
19
|
var FormContext = createContext(null);
|
|
20
|
+
function useForm() {
|
|
21
|
+
const context = React.useContext(FormContext);
|
|
22
|
+
if (!context) {
|
|
23
|
+
throw new Error("useForm must be used within a Form component");
|
|
24
|
+
}
|
|
25
|
+
return context;
|
|
26
|
+
}
|
|
27
|
+
function setNestedValue(obj, path, value) {
|
|
28
|
+
const keys = path.split(".");
|
|
29
|
+
const result = { ...obj };
|
|
30
|
+
let current = result;
|
|
31
|
+
for (let i = 0;i < keys.length - 1; i++) {
|
|
32
|
+
const key = keys[i];
|
|
33
|
+
if (!(key in current) || typeof current[key] !== "object" || current[key] === null) {
|
|
34
|
+
current[key] = {};
|
|
35
|
+
} else {
|
|
36
|
+
current[key] = { ...current[key] };
|
|
37
|
+
}
|
|
38
|
+
current = current[key];
|
|
39
|
+
}
|
|
40
|
+
current[keys[keys.length - 1]] = value;
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
function getNestedValue(obj, path) {
|
|
44
|
+
return path.split(".").reduce((current, key) => current?.[key], obj);
|
|
45
|
+
}
|
|
20
46
|
var Form = forwardRef(function Form2(props, ref) {
|
|
21
47
|
const { render, schema, onSubmit, defaults, onUnvalidatedSubmit } = props;
|
|
22
48
|
const [values, setValues] = useState({});
|
|
@@ -38,7 +64,13 @@ var Form = forwardRef(function Form2(props, ref) {
|
|
|
38
64
|
return Object.values(errors2).some((result) => result);
|
|
39
65
|
}, [schema, values]);
|
|
40
66
|
const validatePartial = useCallback((keys) => {
|
|
41
|
-
const partialValues = keys.reduce((acc, key) =>
|
|
67
|
+
const partialValues = keys.reduce((acc, key) => {
|
|
68
|
+
const value = getNestedValue(values, key);
|
|
69
|
+
if (value !== undefined) {
|
|
70
|
+
return setNestedValue(acc, key, value);
|
|
71
|
+
}
|
|
72
|
+
return acc;
|
|
73
|
+
}, {});
|
|
42
74
|
const errors2 = schema.validatePartial(partialValues);
|
|
43
75
|
if (errors2)
|
|
44
76
|
setErrors((prev) => deepMerge(prev, errors2));
|
|
@@ -52,13 +84,19 @@ var Form = forwardRef(function Form2(props, ref) {
|
|
|
52
84
|
return Object.values(errors2).some((result) => result);
|
|
53
85
|
}, [schema, values, dirty]);
|
|
54
86
|
const setFieldValue = useCallback((field, value) => {
|
|
55
|
-
setValues((prev) => (
|
|
87
|
+
setValues((prev) => setNestedValue(prev, field, value));
|
|
56
88
|
}, []);
|
|
57
89
|
const setFieldDirty = useCallback((field) => {
|
|
58
90
|
setDirty((prev) => new Set([...prev, field]));
|
|
59
91
|
}, []);
|
|
60
92
|
useEffect(() => {
|
|
61
|
-
const partialValues = Array.from(dirty).reduce((acc, key) =>
|
|
93
|
+
const partialValues = Array.from(dirty).reduce((acc, key) => {
|
|
94
|
+
const value = getNestedValue(values, key);
|
|
95
|
+
if (value !== undefined) {
|
|
96
|
+
return setNestedValue(acc, key, value);
|
|
97
|
+
}
|
|
98
|
+
return acc;
|
|
99
|
+
}, {});
|
|
62
100
|
const errors2 = schema.validatePartial(partialValues);
|
|
63
101
|
setErrors(errors2);
|
|
64
102
|
}, [values, dirty]);
|
|
@@ -77,14 +115,30 @@ var Form = forwardRef(function Form2(props, ref) {
|
|
|
77
115
|
useImperativeHandle(ref, () => ({
|
|
78
116
|
submit: handleSubmit,
|
|
79
117
|
getValues: () => values,
|
|
80
|
-
validate
|
|
118
|
+
validate,
|
|
119
|
+
setFieldValue
|
|
81
120
|
}), [handleSubmit, values, validate]);
|
|
121
|
+
const buildFieldsStructure = useCallback((element, fieldName, defaultValue) => {
|
|
122
|
+
const isOptional = element?.toJsonSchema && typeof element.toJsonSchema === "function" && element.parent !== undefined;
|
|
123
|
+
if (isOptional) {
|
|
124
|
+
return buildFieldsStructure(element.parent, fieldName, defaultValue);
|
|
125
|
+
}
|
|
126
|
+
const isObject = element?.entries && typeof element.entries === "function";
|
|
127
|
+
if (isObject) {
|
|
128
|
+
const subFields = Object.fromEntries(element.entries().map(([key, value]) => [
|
|
129
|
+
key.charAt(0).toUpperCase() + key.slice(1),
|
|
130
|
+
buildFieldsStructure(value, fieldName ? `${fieldName}.${key}` : key, defaultValue?.[key])
|
|
131
|
+
]));
|
|
132
|
+
return FormField(fieldName, defaultValue, subFields);
|
|
133
|
+
}
|
|
134
|
+
return FormField(fieldName, defaultValue);
|
|
135
|
+
}, []);
|
|
82
136
|
const Fields = useMemo(() => {
|
|
83
137
|
return Object.fromEntries(schema.entries().map(([key, value]) => [
|
|
84
138
|
key.charAt(0).toUpperCase() + key.slice(1),
|
|
85
|
-
|
|
139
|
+
buildFieldsStructure(value, key, defaults?.[key])
|
|
86
140
|
]));
|
|
87
|
-
}, [schema, defaults]);
|
|
141
|
+
}, [schema, defaults, buildFieldsStructure]);
|
|
88
142
|
const contextValue = useMemo(() => ({
|
|
89
143
|
values,
|
|
90
144
|
errors,
|
|
@@ -111,7 +165,7 @@ var Form = forwardRef(function Form2(props, ref) {
|
|
|
111
165
|
}, undefined, false, undefined, this);
|
|
112
166
|
});
|
|
113
167
|
|
|
114
|
-
// form/form-part.tsx
|
|
168
|
+
// src/form/form-part.tsx
|
|
115
169
|
import React2, { createContext as createContext2, useCallback as useCallback2, useContext, useState as useState2 } from "react";
|
|
116
170
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
117
171
|
var FormPartContext = createContext2(null);
|
|
@@ -165,8 +219,11 @@ function useFormPartField(fieldName) {
|
|
|
165
219
|
return context;
|
|
166
220
|
}
|
|
167
221
|
|
|
168
|
-
// form/field.tsx
|
|
222
|
+
// src/form/field.tsx
|
|
169
223
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
224
|
+
function getNestedValue2(obj, path) {
|
|
225
|
+
return path.split(".").reduce((current, key) => current?.[key], obj);
|
|
226
|
+
}
|
|
170
227
|
var FormFieldContext = createContext3(null);
|
|
171
228
|
function useFormField() {
|
|
172
229
|
const context = useContext2(FormFieldContext);
|
|
@@ -174,7 +231,7 @@ function useFormField() {
|
|
|
174
231
|
throw new Error("useFormField must be used within a FormFieldProvider");
|
|
175
232
|
return context;
|
|
176
233
|
}
|
|
177
|
-
function FormField(name, defaultValue) {
|
|
234
|
+
function FormField(name, defaultValue, subFields) {
|
|
178
235
|
return ({ translations, render }) => {
|
|
179
236
|
const form = useContext2(FormContext);
|
|
180
237
|
if (!form)
|
|
@@ -183,7 +240,7 @@ function FormField(name, defaultValue) {
|
|
|
183
240
|
const { values, errors, dirty, isSubmitted, setFieldValue, setFieldDirty } = form;
|
|
184
241
|
const schemaErrors = errors?.["schema"] || {};
|
|
185
242
|
const fieldErrors = schemaErrors[name] || false;
|
|
186
|
-
const value = values
|
|
243
|
+
const value = getNestedValue2(values, name) ?? defaultValue ?? "";
|
|
187
244
|
const isDirty = dirty.has(name);
|
|
188
245
|
const handleChange = useCallback3((value2) => {
|
|
189
246
|
if (value2?.target?.value !== undefined) {
|
|
@@ -218,21 +275,14 @@ function FormField(name, defaultValue) {
|
|
|
218
275
|
onChange: handleChange,
|
|
219
276
|
name: name.toString(),
|
|
220
277
|
value,
|
|
221
|
-
defaultValue
|
|
278
|
+
defaultValue,
|
|
279
|
+
subFields,
|
|
280
|
+
setFieldValue
|
|
222
281
|
})
|
|
223
282
|
}, undefined, false, undefined, this);
|
|
224
283
|
};
|
|
225
284
|
}
|
|
226
|
-
// form/
|
|
227
|
-
function formResolver(schema) {
|
|
228
|
-
return async (data) => {
|
|
229
|
-
return {
|
|
230
|
-
values: data,
|
|
231
|
-
errors: {}
|
|
232
|
-
};
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
// form/message.tsx
|
|
285
|
+
// src/form/message.tsx
|
|
236
286
|
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
237
287
|
function FormMessage({ ...props }) {
|
|
238
288
|
const { messages } = useFormField();
|
|
@@ -244,220 +294,260 @@ function FormMessage({ ...props }) {
|
|
|
244
294
|
}, undefined, false, undefined, this);
|
|
245
295
|
}
|
|
246
296
|
FormMessage.displayName = "FormMessage";
|
|
247
|
-
//
|
|
297
|
+
// src/factories/model-provider-factory.tsx
|
|
248
298
|
import {
|
|
299
|
+
AuthAdapter,
|
|
300
|
+
CommandWire,
|
|
301
|
+
EventWire,
|
|
302
|
+
LocalEventPublisher,
|
|
249
303
|
MasterDataStorage,
|
|
250
|
-
Model
|
|
251
|
-
RemoteModelClient,
|
|
252
|
-
rtcClientFactory
|
|
304
|
+
Model
|
|
253
305
|
} from "@arcote.tech/arc";
|
|
254
|
-
import {
|
|
255
|
-
createContext as createContext4,
|
|
256
|
-
useCallback as useCallback4,
|
|
257
|
-
useContext as useContext3,
|
|
258
|
-
useEffect as useEffect2,
|
|
259
|
-
useMemo as useMemo3,
|
|
260
|
-
useRef,
|
|
261
|
-
useState as useState3
|
|
262
|
-
} from "react";
|
|
263
|
-
|
|
264
|
-
// sqliteWasmAdapter.ts
|
|
265
|
-
import {
|
|
266
|
-
createSQLiteAdapterFactory
|
|
267
|
-
} from "@arcote.tech/arc";
|
|
268
|
-
var sqliteWasmAdapterFactory = (db) => {
|
|
269
|
-
return async (context) => {
|
|
270
|
-
return createSQLiteAdapterFactory(db)(context);
|
|
271
|
-
};
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
// reactModel.tsx
|
|
306
|
+
import { createContext as createContext4, useContext as useContext3, useEffect as useEffect2, useState as useState3 } from "react";
|
|
275
307
|
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
276
|
-
var
|
|
277
|
-
const
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const dbAdapterPromise = sqliteWasmAdapterFactory(options.sqliteAdapter)(context);
|
|
302
|
-
const dataStorage = new MasterDataStorage(dbAdapterPromise, rtcClientFactory(props.token), context);
|
|
303
|
-
const model2 = new Model(context, dataStorage, props.catchErrorCallback);
|
|
304
|
-
masterModel = model2;
|
|
305
|
-
return model2;
|
|
306
|
-
}
|
|
307
|
-
}, [context, options, props.client]);
|
|
308
|
-
const [syncProgress, setSyncProgress] = useState3([]);
|
|
309
|
-
const [syncDone, setSyncDone] = useState3(false);
|
|
310
|
-
useEffect2(() => {
|
|
311
|
-
if (typeof window === "undefined" || !model)
|
|
312
|
-
return;
|
|
313
|
-
const sync = async () => {
|
|
314
|
-
if (!("dataStorage" in model))
|
|
315
|
-
return setSyncDone(true);
|
|
316
|
-
await model.dataStorage.sync(({ store, size }) => {
|
|
317
|
-
setSyncProgress((prev) => [...prev, { store, size }]);
|
|
318
|
-
});
|
|
319
|
-
setSyncDone(true);
|
|
320
|
-
};
|
|
321
|
-
sync();
|
|
322
|
-
}, [model]);
|
|
323
|
-
if (!model || !syncDone)
|
|
324
|
-
return props.syncView ? /* @__PURE__ */ jsx5(props.syncView, {
|
|
325
|
-
progress: syncProgress
|
|
326
|
-
}, undefined, false, undefined, this) : null;
|
|
327
|
-
return /* @__PURE__ */ jsx5(LiveModelContext.Provider, {
|
|
328
|
-
value: model,
|
|
329
|
-
children: props.children
|
|
330
|
-
}, undefined, false, undefined, this);
|
|
331
|
-
},
|
|
332
|
-
function LocalModelProvider({ children }) {
|
|
333
|
-
const parentModel = useContext3(LiveModelContext);
|
|
334
|
-
if (!parentModel || !(parentModel instanceof Model)) {
|
|
335
|
-
throw new Error("LocalModelProvider must be used within a LiveModelProvider");
|
|
336
|
-
}
|
|
337
|
-
const [localModel] = useState3(() => parentModel);
|
|
338
|
-
return /* @__PURE__ */ jsx5(LocalModelContext.Provider, {
|
|
339
|
-
value: localModel,
|
|
340
|
-
children
|
|
341
|
-
}, undefined, false, undefined, this);
|
|
342
|
-
},
|
|
343
|
-
function useQuery(queryBuilderFn, dependencies = [], cacheKey) {
|
|
344
|
-
const model = useContext3(LocalModelContext) || useContext3(LiveModelContext);
|
|
345
|
-
if (!model) {
|
|
346
|
-
throw new Error("useQuery must be used within a ModelProvider");
|
|
308
|
+
var modelProviderFactory = (context, options) => {
|
|
309
|
+
const ModelContext = createContext4(null);
|
|
310
|
+
const commandWire = options.remoteUrl ? new CommandWire(options.remoteUrl) : undefined;
|
|
311
|
+
const eventWire = options.remoteUrl ? new EventWire(options.remoteUrl) : undefined;
|
|
312
|
+
const authAdapter = new AuthAdapter;
|
|
313
|
+
let initialized = false;
|
|
314
|
+
let cachedModel = null;
|
|
315
|
+
let cachedDataStorage = null;
|
|
316
|
+
let onResetCallback = null;
|
|
317
|
+
function ModelProvider(props) {
|
|
318
|
+
const [model, setModel] = useState3(cachedModel);
|
|
319
|
+
const [resetTrigger, setResetTrigger] = useState3(0);
|
|
320
|
+
useEffect2(() => {
|
|
321
|
+
onResetCallback = () => {
|
|
322
|
+
setModel(null);
|
|
323
|
+
setResetTrigger((prev) => prev + 1);
|
|
324
|
+
};
|
|
325
|
+
return () => {
|
|
326
|
+
onResetCallback = null;
|
|
327
|
+
};
|
|
328
|
+
}, []);
|
|
329
|
+
useEffect2(() => {
|
|
330
|
+
if (initialized && cachedModel) {
|
|
331
|
+
setModel(cachedModel);
|
|
332
|
+
return;
|
|
347
333
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
334
|
+
initialized = true;
|
|
335
|
+
let cancelled = false;
|
|
336
|
+
async function initializeModel() {
|
|
337
|
+
let dataStorage;
|
|
338
|
+
let eventPublisher;
|
|
339
|
+
if (options.dbAdapterFactory) {
|
|
340
|
+
const databaseAdapter = await options.dbAdapterFactory(context);
|
|
341
|
+
if (cancelled)
|
|
342
|
+
return;
|
|
343
|
+
if (databaseAdapter) {
|
|
344
|
+
dataStorage = new MasterDataStorage(databaseAdapter);
|
|
345
|
+
cachedDataStorage = dataStorage;
|
|
346
|
+
eventPublisher = new LocalEventPublisher(dataStorage);
|
|
347
|
+
const views = context.elements.filter((element) => ("getHandlers" in element) && ("databaseStoreSchema" in element) && ("schema" in element));
|
|
348
|
+
eventPublisher.registerViews(views);
|
|
349
|
+
if (eventWire && eventPublisher) {
|
|
350
|
+
const publisher = eventPublisher;
|
|
351
|
+
const eventsStore = dataStorage.getStore("events");
|
|
352
|
+
const allEvents = await eventsStore.find({});
|
|
353
|
+
const hostEvents = allEvents.filter((e) => typeof e._id === "string" && e._id.startsWith("host_"));
|
|
354
|
+
if (hostEvents.length > 0) {
|
|
355
|
+
hostEvents.sort((a, b) => {
|
|
356
|
+
const aTimestamp = parseInt(a._id.split("_")[2] || "0", 10);
|
|
357
|
+
const bTimestamp = parseInt(b._id.split("_")[2] || "0", 10);
|
|
358
|
+
return bTimestamp - aTimestamp;
|
|
359
|
+
});
|
|
360
|
+
const lastHostEventId = hostEvents[0]._id;
|
|
361
|
+
eventWire.setLastHostEventId(lastHostEventId);
|
|
362
|
+
}
|
|
363
|
+
const eventsFromHost = new Set;
|
|
364
|
+
publisher.onPublish((event) => {
|
|
365
|
+
if (eventsFromHost.has(event.id)) {
|
|
366
|
+
eventsFromHost.delete(event.id);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
eventWire.syncEvents([
|
|
370
|
+
{
|
|
371
|
+
localId: event.id,
|
|
372
|
+
type: event.type,
|
|
373
|
+
payload: event.payload,
|
|
374
|
+
createdAt: event.createdAt.toISOString()
|
|
375
|
+
}
|
|
376
|
+
]);
|
|
377
|
+
});
|
|
378
|
+
const processedEvents = new Set;
|
|
379
|
+
let eventQueue = Promise.resolve();
|
|
380
|
+
eventWire.onEvent((event) => {
|
|
381
|
+
eventQueue = eventQueue.then(async () => {
|
|
382
|
+
try {
|
|
383
|
+
if (processedEvents.has(event.hostId)) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
processedEvents.add(event.hostId);
|
|
387
|
+
eventsFromHost.add(event.hostId);
|
|
388
|
+
const eventsStore2 = dataStorage?.getStore("events");
|
|
389
|
+
if (eventsStore2) {
|
|
390
|
+
const existing = await eventsStore2.find({
|
|
391
|
+
where: { _id: event.hostId }
|
|
392
|
+
});
|
|
393
|
+
if (existing.length > 0) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
await publisher.publish({
|
|
398
|
+
id: event.hostId,
|
|
399
|
+
type: event.type,
|
|
400
|
+
payload: event.payload,
|
|
401
|
+
createdAt: new Date(event.createdAt)
|
|
402
|
+
});
|
|
403
|
+
} catch (error) {
|
|
404
|
+
console.error(`[Arc] Failed to process event ${event.hostId} (${event.type}):`, error, `
|
|
405
|
+
Event payload:`, event.payload);
|
|
406
|
+
processedEvents.delete(event.hostId);
|
|
407
|
+
eventsFromHost.delete(event.hostId);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
if (authAdapter.isAuthenticated()) {
|
|
412
|
+
eventWire.connect();
|
|
377
413
|
}
|
|
378
414
|
}
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
}, [model, cacheKey]);
|
|
382
|
-
useEffect2(() => {
|
|
383
|
-
if (unsubscribeRef.current) {
|
|
384
|
-
unsubscribeRef.current();
|
|
385
|
-
}
|
|
386
|
-
const defaultAuthContext = { userId: "react-user" };
|
|
387
|
-
const { unsubscribe, result: result2 } = model.subscribe(queryBuilderFn, (newResult) => {
|
|
388
|
-
setResult(newResult);
|
|
389
|
-
setLoading(false);
|
|
390
|
-
if (resolvePromiseRef.current) {
|
|
391
|
-
resolvePromiseRef.current();
|
|
392
|
-
resolvePromiseRef.current = null;
|
|
393
|
-
}
|
|
394
|
-
}, defaultAuthContext);
|
|
395
|
-
unsubscribeRef.current = unsubscribe;
|
|
396
|
-
return () => {
|
|
397
|
-
if (unsubscribeRef.current) {
|
|
398
|
-
unsubscribeRef.current();
|
|
399
|
-
unsubscribeRef.current = null;
|
|
400
|
-
}
|
|
401
|
-
};
|
|
402
|
-
}, [model, revalidationTrigger, ...dependencies]);
|
|
403
|
-
return [result, loading];
|
|
404
|
-
},
|
|
405
|
-
function useRevalidate() {
|
|
406
|
-
const model = useContext3(LocalModelContext) || useContext3(LiveModelContext);
|
|
407
|
-
if (!model) {
|
|
408
|
-
throw new Error("useRevalidate must be used within a ModelProvider");
|
|
409
|
-
}
|
|
410
|
-
return useCallback4(async (cacheKey) => {
|
|
411
|
-
const registry = model.__cacheRegistry;
|
|
412
|
-
if (registry && registry.has(cacheKey)) {
|
|
413
|
-
const revalidators = registry.get(cacheKey);
|
|
414
|
-
if (revalidators) {
|
|
415
|
-
const promises = [];
|
|
416
|
-
revalidators.forEach((revalidateFn) => {
|
|
417
|
-
promises.push(revalidateFn());
|
|
418
|
-
});
|
|
419
|
-
await Promise.all(promises);
|
|
420
415
|
}
|
|
421
416
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
417
|
+
if (cancelled)
|
|
418
|
+
return;
|
|
419
|
+
const newModel = new Model(context, {
|
|
420
|
+
adapters: {
|
|
421
|
+
dataStorage,
|
|
422
|
+
commandWire,
|
|
423
|
+
eventPublisher,
|
|
424
|
+
eventWire,
|
|
425
|
+
authAdapter
|
|
426
|
+
},
|
|
427
|
+
environment: "client"
|
|
428
|
+
});
|
|
429
|
+
await newModel.init();
|
|
430
|
+
if (cancelled)
|
|
431
|
+
return;
|
|
432
|
+
cachedModel = newModel;
|
|
433
|
+
setModel(newModel);
|
|
434
|
+
if (typeof window !== "undefined") {
|
|
435
|
+
window.arcModel = newModel;
|
|
436
|
+
window.arcDataStorage = dataStorage;
|
|
437
|
+
}
|
|
428
438
|
}
|
|
429
|
-
|
|
430
|
-
return
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
439
|
+
initializeModel();
|
|
440
|
+
return () => {
|
|
441
|
+
cancelled = true;
|
|
442
|
+
eventWire?.disconnect();
|
|
443
|
+
};
|
|
444
|
+
}, [resetTrigger]);
|
|
445
|
+
if (!model)
|
|
446
|
+
return;
|
|
447
|
+
return /* @__PURE__ */ jsx5(ModelContext.Provider, {
|
|
448
|
+
value: model,
|
|
449
|
+
children: props.children
|
|
450
|
+
}, undefined, false, undefined, this);
|
|
451
|
+
}
|
|
452
|
+
function useModel() {
|
|
453
|
+
const model = useContext3(ModelContext);
|
|
454
|
+
if (!model) {
|
|
455
|
+
throw new Error("useModel must be used within a ModelProvider");
|
|
456
|
+
}
|
|
457
|
+
return model;
|
|
458
|
+
}
|
|
459
|
+
function setAuthToken(token) {
|
|
460
|
+
authAdapter.setToken(token);
|
|
461
|
+
commandWire?.setAuthToken(token);
|
|
462
|
+
if (eventWire) {
|
|
463
|
+
eventWire.setAuthToken(token);
|
|
464
|
+
if (token && eventWire.getState() === "disconnected") {
|
|
465
|
+
eventWire.connect();
|
|
444
466
|
}
|
|
445
|
-
return model;
|
|
446
|
-
},
|
|
447
|
-
function useModel(token) {
|
|
448
|
-
const parentModel = useContext3(LiveModelContext);
|
|
449
|
-
if (!parentModel)
|
|
450
|
-
throw new Error("useModel have to be used in Provider");
|
|
451
|
-
return parentModel;
|
|
452
467
|
}
|
|
453
|
-
|
|
468
|
+
}
|
|
469
|
+
async function resetModel() {
|
|
470
|
+
eventWire?.disconnect();
|
|
471
|
+
if (cachedDataStorage) {
|
|
472
|
+
await cachedDataStorage.destroy();
|
|
473
|
+
cachedDataStorage = null;
|
|
474
|
+
}
|
|
475
|
+
cachedModel = null;
|
|
476
|
+
initialized = false;
|
|
477
|
+
authAdapter.setToken(null);
|
|
478
|
+
commandWire?.setAuthToken(null);
|
|
479
|
+
eventWire?.setAuthToken(null);
|
|
480
|
+
if (onResetCallback) {
|
|
481
|
+
onResetCallback();
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function onReset(callback) {
|
|
485
|
+
onResetCallback = callback;
|
|
486
|
+
}
|
|
487
|
+
return {
|
|
488
|
+
ModelProvider,
|
|
489
|
+
useModel,
|
|
490
|
+
commandWire,
|
|
491
|
+
eventWire,
|
|
492
|
+
setAuthToken,
|
|
493
|
+
resetModel,
|
|
494
|
+
onReset
|
|
495
|
+
};
|
|
496
|
+
};
|
|
497
|
+
// src/factories/use-commands-factory.tsx
|
|
498
|
+
import {
|
|
499
|
+
mutationExecutor
|
|
500
|
+
} from "@arcote.tech/arc";
|
|
501
|
+
import { useMemo as useMemo3 } from "react";
|
|
502
|
+
var useCommandsFactory = (useModel) => {
|
|
503
|
+
return function useCommands() {
|
|
504
|
+
const model = useModel();
|
|
505
|
+
return useMemo3(() => mutationExecutor(model), [model]);
|
|
506
|
+
};
|
|
507
|
+
};
|
|
508
|
+
// src/factories/use-query-factory.tsx
|
|
509
|
+
import {
|
|
510
|
+
liveQuery
|
|
511
|
+
} from "@arcote.tech/arc";
|
|
512
|
+
import { useEffect as useEffect3, useState as useState4 } from "react";
|
|
513
|
+
var useQueryFactory = (useModel) => {
|
|
514
|
+
return function useQuery(queryFn, dependencies = []) {
|
|
515
|
+
const model = useModel();
|
|
516
|
+
const [loading, setLoading] = useState4(true);
|
|
517
|
+
const [result, setResult] = useState4(undefined);
|
|
518
|
+
useEffect3(() => {
|
|
519
|
+
const { unsubscribe } = liveQuery(model, queryFn, (newResult) => {
|
|
520
|
+
setResult(newResult);
|
|
521
|
+
setLoading(false);
|
|
522
|
+
});
|
|
523
|
+
return () => {
|
|
524
|
+
unsubscribe();
|
|
525
|
+
};
|
|
526
|
+
}, [model, ...dependencies]);
|
|
527
|
+
return [result, loading];
|
|
528
|
+
};
|
|
529
|
+
};
|
|
530
|
+
// src/react-model.tsx
|
|
531
|
+
var reactModel = (context, options = {}) => {
|
|
532
|
+
const { ModelProvider, useModel, commandWire, setAuthToken, resetModel } = modelProviderFactory(context, options);
|
|
533
|
+
const useQuery = useQueryFactory(useModel);
|
|
534
|
+
const useCommands = useCommandsFactory(useModel);
|
|
535
|
+
return {
|
|
536
|
+
ModelProvider,
|
|
537
|
+
useModel,
|
|
538
|
+
useQuery,
|
|
539
|
+
useCommands,
|
|
540
|
+
commandWire,
|
|
541
|
+
setAuthToken,
|
|
542
|
+
resetModel
|
|
543
|
+
};
|
|
454
544
|
};
|
|
455
545
|
export {
|
|
456
546
|
useFormPartField,
|
|
457
547
|
useFormPart,
|
|
458
548
|
useFormField,
|
|
549
|
+
useForm,
|
|
459
550
|
reactModel,
|
|
460
|
-
formResolver,
|
|
461
551
|
FormPartContext,
|
|
462
552
|
FormPart,
|
|
463
553
|
FormMessage,
|
|
@@ -467,4 +557,4 @@ export {
|
|
|
467
557
|
Form
|
|
468
558
|
};
|
|
469
559
|
|
|
470
|
-
//# debugId=
|
|
560
|
+
//# debugId=7543AFFDBE5BB66264756E2164756E21
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { CommandWire, EventWire, Model, type ArcContextAny } from "@arcote.tech/arc";
|
|
2
|
+
import type { ReactModelOptions } from "../options";
|
|
3
|
+
export declare const modelProviderFactory: <C extends ArcContextAny>(context: C, options: ReactModelOptions) => {
|
|
4
|
+
ModelProvider: (props: {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
}) => import("react/jsx-dev-runtime").JSX.Element | undefined;
|
|
7
|
+
useModel: () => Model<C>;
|
|
8
|
+
commandWire: CommandWire | undefined;
|
|
9
|
+
eventWire: EventWire | undefined;
|
|
10
|
+
setAuthToken: (token: string | null) => void;
|
|
11
|
+
resetModel: () => Promise<void>;
|
|
12
|
+
onReset: (callback: () => void) => void;
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=model-provider-factory.d.ts.map
|
|
@@ -0,0 +1,3 @@
|
|
|
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
|
|
@@ -0,0 +1,3 @@
|
|
|
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
|
|
@@ -9,16 +9,18 @@ export declare function useFormField(): FormFieldContext;
|
|
|
9
9
|
type Translations<E extends ArcElement> = {
|
|
10
10
|
[K in keyof Exclude<ReturnType<E["validate"]>, false>]: (data: Exclude<Exclude<ReturnType<E["validate"]>, false>[K], undefined | false>) => string;
|
|
11
11
|
} | ((data: any) => string) | string;
|
|
12
|
-
type FormFieldProps<E extends ArcElement> = {
|
|
12
|
+
type FormFieldProps<E extends ArcElement, S = undefined> = {
|
|
13
13
|
translations: Translations<E>;
|
|
14
|
-
render: (field: FormFieldData<E>) => React.ReactNode;
|
|
14
|
+
render: (field: FormFieldData<E, S>) => React.ReactNode;
|
|
15
15
|
};
|
|
16
|
-
export type FormFieldData<T> = {
|
|
16
|
+
export type FormFieldData<T, S = undefined> = {
|
|
17
17
|
onChange: (value: any) => void;
|
|
18
18
|
value: any;
|
|
19
19
|
defaultValue?: any;
|
|
20
20
|
name: string;
|
|
21
|
+
subFields?: S;
|
|
22
|
+
setFieldValue: (field: string, value: any) => void;
|
|
21
23
|
};
|
|
22
|
-
export declare function FormField<E extends ArcElement>(name: string, defaultValue?: any): ({ translations, render }: FormFieldProps<E>) => import("react/jsx-dev-runtime").JSX.Element;
|
|
24
|
+
export declare function FormField<E extends ArcElement, S = undefined>(name: string, defaultValue?: any, subFields?: S): ({ translations, render }: FormFieldProps<E, S>) => import("react/jsx-dev-runtime").JSX.Element;
|
|
23
25
|
export {};
|
|
24
26
|
//# sourceMappingURL=field.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type $type, type ArcObjectAny, type ArcObjectKeys, type ArcObjectValueByKey } from "@arcote.tech/arc";
|
|
1
|
+
import { type $type, type ArcObjectAny, type ArcObjectKeys, type ArcObjectValueByKey, ArcOptional } from "@arcote.tech/arc";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { FormField } from "./field";
|
|
4
4
|
export type FormContextValue<T extends ArcObjectAny> = {
|
|
@@ -14,16 +14,19 @@ export type FormRef<T extends ArcObjectAny> = {
|
|
|
14
14
|
submit: () => Promise<void>;
|
|
15
15
|
getValues: () => Partial<$type<T>>;
|
|
16
16
|
validate: () => boolean;
|
|
17
|
+
setFieldValue: (field: ArcObjectKeys<T>, value: any) => void;
|
|
18
|
+
};
|
|
19
|
+
export type FormFields<T extends ArcObjectAny> = {
|
|
20
|
+
[K in ArcObjectKeys<T> as Capitalize<`${K}`>]: ArcObjectValueByKey<T, K> extends ArcObjectAny ? ReturnType<typeof FormField<ArcObjectValueByKey<T, K>, FormFields<ArcObjectValueByKey<T, K>>>> : ArcObjectValueByKey<T, K> extends ArcOptional<infer U extends ArcObjectAny> ? ReturnType<typeof FormField<ArcObjectValueByKey<T, K>, FormFields<U>>> : ReturnType<typeof FormField<ArcObjectValueByKey<T, K>>>;
|
|
17
21
|
};
|
|
18
22
|
export type FormProps<T extends ArcObjectAny> = {
|
|
19
|
-
render: (props:
|
|
20
|
-
[K in ArcObjectKeys<T> as Capitalize<`${K}`>]: ReturnType<typeof FormField<ArcObjectValueByKey<T, K>>>;
|
|
21
|
-
}, values: Partial<$type<T>>) => React.ReactNode;
|
|
23
|
+
render: (props: FormFields<T>, values: Partial<$type<T>>) => React.ReactNode;
|
|
22
24
|
schema: T;
|
|
23
25
|
onSubmit: (values: $type<T>) => void | Promise<void>;
|
|
24
26
|
onUnvalidatedSubmit?: (values: Partial<$type<T>>, errors: any) => void;
|
|
25
27
|
defaults?: Partial<$type<T>> | null;
|
|
26
28
|
};
|
|
27
29
|
export declare const FormContext: React.Context<FormContextValue<any> | null>;
|
|
30
|
+
export declare function useForm<T extends ArcObjectAny>(): FormContextValue<T>;
|
|
28
31
|
export declare const Form: <T extends ArcObjectAny>(props: FormProps<T> & React.RefAttributes<FormRef<T>>) => JSX.Element;
|
|
29
32
|
//# sourceMappingURL=form.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ArcContextAny } from "@arcote.tech/arc";
|
|
2
|
+
import type { ReactModelOptions } from "./options";
|
|
3
|
+
export declare const reactModel: <C extends ArcContextAny>(context: C, options?: ReactModelOptions) => {
|
|
4
|
+
readonly ModelProvider: (props: {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
}) => import("react/jsx-dev-runtime").JSX.Element | undefined;
|
|
7
|
+
readonly useModel: () => import("@arcote.tech/arc").Model<C>;
|
|
8
|
+
readonly useQuery: <TResult>(queryFn: (q: { [Element in C["elements"][number] as Element["queryContext"] extends (...args: any[]) => infer Return ? Element["name"] : never]: Element["queryContext"] extends (...args: any[]) => infer Return ? Return : never; } extends infer T ? { [K in keyof T]: T[K]; } : never) => Promise<TResult>, dependencies?: any[]) => [TResult | undefined, boolean];
|
|
9
|
+
readonly useCommands: () => { [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;
|
|
10
|
+
/** CommandWire instance for remote execution (if remoteUrl provided) */
|
|
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
|
+
/** Reset model - destroys local database and disconnects from host */
|
|
15
|
+
readonly resetModel: () => Promise<void>;
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=react-model.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcote.tech/arc-react",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
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.",
|
|
8
8
|
"main": "./dist/index.js",
|
|
9
9
|
"module": "./dist/index.js",
|
|
10
|
-
"types": "./dist/index.d.ts",
|
|
10
|
+
"types": "./dist/src/index.d.ts",
|
|
11
11
|
"exports": {
|
|
12
12
|
".": {
|
|
13
|
-
"types": "./dist/index.d.ts",
|
|
13
|
+
"types": "./dist/src/index.d.ts",
|
|
14
14
|
"import": "./dist/index.js",
|
|
15
15
|
"default": "./dist/index.js"
|
|
16
16
|
}
|
|
@@ -22,10 +22,6 @@
|
|
|
22
22
|
"type-check": "tsc",
|
|
23
23
|
"dev": "nodemon --ignore dist -e ts,tsx --exec 'bun run build'"
|
|
24
24
|
},
|
|
25
|
-
"dependencies": {
|
|
26
|
-
"react": "^18.3.1",
|
|
27
|
-
"react-dom": "^18.3.1"
|
|
28
|
-
},
|
|
29
25
|
"devDependencies": {
|
|
30
26
|
"@types/bun": "latest",
|
|
31
27
|
"@types/react": "^18.3.5",
|
|
@@ -37,6 +33,8 @@
|
|
|
37
33
|
},
|
|
38
34
|
"peerDependencies": {
|
|
39
35
|
"@arcote.tech/arc": "latest",
|
|
36
|
+
"react": "^18.0.0",
|
|
37
|
+
"react-dom": "^18.0.0",
|
|
40
38
|
"typescript": "^5.0.0"
|
|
41
39
|
},
|
|
42
40
|
"files": [
|
package/dist/index.d.ts
DELETED
package/dist/reactModel.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { Model, type ArcContextAny, type IArcQueryBuilder, type ModelBase, type QueryBuilderFunctionResult, type QueryFactoryFunction, type UnaryFunction } from "@arcote.tech/arc";
|
|
2
|
-
import type { ArcContextElementMethodReturnType, objectUtil, SQLiteDatabase } from "@arcote.tech/arc";
|
|
3
|
-
export declare const reactModel: <C extends ArcContextAny>(arcContext: C, options: {
|
|
4
|
-
sqliteAdapter: SQLiteDatabase;
|
|
5
|
-
} | {
|
|
6
|
-
remoteUrl: string;
|
|
7
|
-
}) => readonly [(props: {
|
|
8
|
-
children: React.ReactNode;
|
|
9
|
-
client: string;
|
|
10
|
-
token: string;
|
|
11
|
-
syncView?: React.ComponentType<{
|
|
12
|
-
progress: {
|
|
13
|
-
store: string;
|
|
14
|
-
size: number;
|
|
15
|
-
}[];
|
|
16
|
-
}>;
|
|
17
|
-
catchErrorCallback: (error: any) => void;
|
|
18
|
-
}) => import("react/jsx-dev-runtime").JSX.Element | null, ({ children }: {
|
|
19
|
-
children: React.ReactNode;
|
|
20
|
-
}) => import("react/jsx-dev-runtime").JSX.Element, <Q extends IArcQueryBuilder>(queryBuilderFn: UnaryFunction<ReturnType<C["queryBuilder"]>, Q>, dependencies?: any[], cacheKey?: string) => [objectUtil.simplify<ReturnType<Q["toQuery"]>["lastResult"]>, false] | [undefined, true], () => (cacheKey: string) => Promise<void>, () => ArcContextElementMethodReturnType<C["elements"], "commandClient">, <QueryBuilderFn extends QueryFactoryFunction<C>>(queryBuilderFn: QueryBuilderFn, model?: ModelBase<C> | null) => Promise<QueryBuilderFunctionResult<QueryBuilderFn>>, () => Model<C> | null, (token: string) => ModelBase<C>];
|
|
21
|
-
//# sourceMappingURL=reactModel.d.ts.map
|
|
File without changes
|
|
File without changes
|