@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 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) => ({ ...acc, [key]: values[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) => ({ ...prev, [field]: value }));
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) => ({ ...acc, [key]: values[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
- FormField(key, defaults?.[key])
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[name] ?? defaultValue ?? "";
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/legacy_form_resolver.ts
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
- // reactModel.tsx
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 reactModel = (arcContext, options) => {
277
- const LiveModelContext = createContext4(null);
278
- const LocalModelContext = createContext4(null);
279
- let masterModel = null;
280
- return [
281
- function LiveModelProvider(props) {
282
- const [context, setContext] = useState3(null);
283
- useEffect2(() => {
284
- setContext(arcContext);
285
- }, [arcContext]);
286
- useEffect2(() => {
287
- model?.setAuthToken(props.token);
288
- }, [props.token]);
289
- const model = useMemo3(() => {
290
- if (typeof window === "undefined")
291
- return null;
292
- if (masterModel)
293
- return masterModel;
294
- if (!context)
295
- return null;
296
- if ("remoteUrl" in options) {
297
- const model2 = new RemoteModelClient(context, options.remoteUrl, props.client, props.catchErrorCallback);
298
- masterModel = model2;
299
- return model2;
300
- } else {
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
- const [loading, setLoading] = useState3(true);
349
- const [result, setResult] = useState3(null);
350
- const [revalidationTrigger, setRevalidationTrigger] = useState3(0);
351
- const unsubscribeRef = useRef(null);
352
- const queryPromiseRef = useRef(null);
353
- const resolvePromiseRef = useRef(null);
354
- useEffect2(() => {
355
- if (cacheKey) {
356
- if (!model.__cacheRegistry) {
357
- model.__cacheRegistry = new Map;
358
- }
359
- if (!model.__cacheRegistry.has(cacheKey)) {
360
- model.__cacheRegistry.set(cacheKey, new Set);
361
- }
362
- const revalidateFn = async () => {
363
- const promise = new Promise((resolve) => {
364
- resolvePromiseRef.current = resolve;
365
- });
366
- queryPromiseRef.current = promise;
367
- setRevalidationTrigger((prev) => prev + 1);
368
- return promise;
369
- };
370
- model.__cacheRegistry.get(cacheKey).add(revalidateFn);
371
- return () => {
372
- const registry = model.__cacheRegistry?.get(cacheKey);
373
- if (registry) {
374
- registry.delete(revalidateFn);
375
- if (registry.size === 0) {
376
- model.__cacheRegistry.delete(cacheKey);
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
- }, [model]);
423
- },
424
- function useCommands() {
425
- const model = useContext3(LocalModelContext) || useContext3(LiveModelContext);
426
- if (!model) {
427
- throw new Error("useCommands must be used within a ModelProvider");
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
- const defaultAuthContext = { userId: "react-user" };
430
- return model.commands(defaultAuthContext);
431
- },
432
- async function query(queryBuilderFn, model) {
433
- if (!model)
434
- model = masterModel;
435
- if (!model)
436
- throw new Error("Model not found");
437
- const defaultAuthContext = { userId: "react-user" };
438
- return model.query(queryBuilderFn, defaultAuthContext);
439
- },
440
- function useLocalModel() {
441
- const model = useContext3(LocalModelContext);
442
- if (!model) {
443
- return null;
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=64B909B4D47850D764756E2164756E21
560
+ //# debugId=7543AFFDBE5BB66264756E2164756E21
@@ -0,0 +1,4 @@
1
+ export * from "./model-provider-factory";
2
+ export * from "./use-commands-factory";
3
+ export * from "./use-query-factory";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -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
@@ -1,6 +1,5 @@
1
1
  export * from "./field";
2
2
  export * from "./form";
3
3
  export * from "./form-part";
4
- export * from "./legacy_form_resolver";
5
4
  export * from "./message";
6
5
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,4 @@
1
+ export * from "./form/";
2
+ export * from "./options";
3
+ export * from "./react-model";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,6 @@
1
+ import type { DBAdapterFactory } from "@arcote.tech/arc";
2
+ export type ReactModelOptions = {
3
+ dbAdapterFactory?: DBAdapterFactory;
4
+ remoteUrl?: string;
5
+ };
6
+ //# sourceMappingURL=options.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.1.9",
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": [
@@ -1,6 +0,0 @@
1
- import type { ArcObjectAny } from "@arcote.tech/arc";
2
- export declare function formResolver(schema: ArcObjectAny): (data: any) => Promise<{
3
- values: any;
4
- errors: {};
5
- }>;
6
- //# sourceMappingURL=legacy_form_resolver.d.ts.map
package/dist/index.d.ts DELETED
@@ -1,3 +0,0 @@
1
- export * from "./form/";
2
- export * from "./reactModel.tsx";
3
- //# sourceMappingURL=index.d.ts.map
@@ -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
@@ -1,4 +0,0 @@
1
- import type { SQLiteDatabase } from "@arcote.tech/arc";
2
- import { type DBAdapterFactory } from "@arcote.tech/arc";
3
- export declare const sqliteWasmAdapterFactory: (db: SQLiteDatabase) => DBAdapterFactory;
4
- //# sourceMappingURL=sqliteWasmAdapter.d.ts.map
File without changes
File without changes