form0-core 0.1.0 → 0.1.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/LICENSE +1 -1
- package/README.md +22 -9
- package/package.json +2 -2
- package/src/utilities/record-transformer.js +32 -11
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -2,28 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/form0-core)
|
|
4
4
|
[](https://www.npmjs.com/package/form0-core)
|
|
5
|
-
|
|
5
|
+

|
|
6
6
|
[](https://docs.form0.dev)
|
|
7
7
|
[](https://form0.dev)
|
|
8
|
+

|
|
8
9
|
|
|
9
10
|
> [!WARNING]
|
|
10
11
|
> form0 is in active, very early development. Do not use in production. Expect breaking
|
|
11
12
|
> changes and unstable behavior.
|
|
12
13
|
|
|
13
|
-
form0-core is the schema-driven engine that powers form0 ecosystem. It is framework-agnostic and runs in any JavaScript runtime (Node.js, browsers, React Native, etc.)
|
|
14
|
+
form0-core is the schema-driven engine that powers [form0 open-source ecosystem](https://form0.dev). It is framework-agnostic and runs in any JavaScript runtime (Node.js, browsers, React Native, etc.)
|
|
14
15
|
|
|
15
16
|
## 🚀 Start with the CLI (recommended)
|
|
16
17
|
|
|
17
|
-
The entry point for most users is form0-cli. Follow the quickstart to create a project and preview
|
|
18
|
-
your schema
|
|
19
|
-
- https://docs.form0.dev/getting-started/quickstart
|
|
18
|
+
The entry point for most users is [form0-cli](https://github.com/paqu-io/form0-cli). Follow the [quickstart](https://docs.form0.dev/getting-started/quickstart) to create a project and preview
|
|
19
|
+
your schema.
|
|
20
20
|
|
|
21
21
|
## 🗂️ Documentation
|
|
22
22
|
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
- Main site: https://form0.dev
|
|
23
|
+
- Overview: https://docs.form0.dev/core/overview
|
|
24
|
+
- Concepts: https://docs.form0.dev/core/concepts
|
|
25
|
+
- Full docs: https://docs.form0.dev
|
|
27
26
|
|
|
28
27
|
## Direct usage (advanced)
|
|
29
28
|
|
|
@@ -36,3 +35,17 @@ npm install form0-core
|
|
|
36
35
|
## Security
|
|
37
36
|
|
|
38
37
|
See `SECURITY.md` for security modes and configuration.
|
|
38
|
+
|
|
39
|
+
## Requirements
|
|
40
|
+
|
|
41
|
+
- Node.js 18+
|
|
42
|
+
|
|
43
|
+
## Related repositories
|
|
44
|
+
|
|
45
|
+
- [form0-cli](https://github.com/paqu-io/form0-cli) - Command-line interface
|
|
46
|
+
- [form0-react](https://github.com/paqu-io/form0-react) - React components
|
|
47
|
+
- [form0-react-native](https://github.com/paqu-io/form0-react-native) - React Native components
|
|
48
|
+
|
|
49
|
+
## Contributing
|
|
50
|
+
|
|
51
|
+
Contributions are welcome! Please feel free to submit [issues](https://github.com/paqu-io/form0-core/issues) and [pull requests](https://github.com/paqu-io/form0-core/pulls).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "form0-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"registry": "https://registry.npmjs.org/"
|
|
6
6
|
},
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"bugs": {
|
|
44
44
|
"url": "https://github.com/paqu-io/form0-core/issues"
|
|
45
45
|
},
|
|
46
|
-
"homepage": "https://form0.dev
|
|
46
|
+
"homepage": "https://form0.dev",
|
|
47
47
|
"engines": {
|
|
48
48
|
"node": ">=18.0.0"
|
|
49
49
|
},
|
|
@@ -199,6 +199,12 @@ export function createStructuredRecord(state, fields = null, options = {}, id =
|
|
|
199
199
|
sectionFields,
|
|
200
200
|
} = buildRepeatableMetadata(elementsSource);
|
|
201
201
|
|
|
202
|
+
const hasStructuredRepeatableState =
|
|
203
|
+
state &&
|
|
204
|
+
Object.prototype.hasOwnProperty.call(state, 'repeatable') &&
|
|
205
|
+
state.repeatable &&
|
|
206
|
+
typeof state.repeatable === 'object';
|
|
207
|
+
|
|
202
208
|
// Transform the values object using the preferred keys
|
|
203
209
|
for (const [dataName, value] of Object.entries(state.values)) {
|
|
204
210
|
// Skip flattened choice fields (created by form renderer for allow_other fields)
|
|
@@ -292,7 +298,9 @@ export function createStructuredRecord(state, fields = null, options = {}, id =
|
|
|
292
298
|
const childIds = getNestedChildIds([...currentPath, pathKey]);
|
|
293
299
|
|
|
294
300
|
// Structured RepeatableSection state supplied (new format)
|
|
295
|
-
|
|
301
|
+
// Empty arrays are treated as explicit "no instances" and must not fall back
|
|
302
|
+
// to legacy flattened behavior, which can emit phantom records.
|
|
303
|
+
if (Array.isArray(structuredInstances)) {
|
|
296
304
|
const recordCount = Math.max(structuredInstances.length, childIds.length);
|
|
297
305
|
const childRecords = [];
|
|
298
306
|
|
|
@@ -360,16 +368,20 @@ export function createStructuredRecord(state, fields = null, options = {}, id =
|
|
|
360
368
|
}
|
|
361
369
|
|
|
362
370
|
for (const [, childRepInfo] of repInfo.children) {
|
|
363
|
-
const
|
|
371
|
+
const rawNestedInstances = Object.prototype.hasOwnProperty.call(
|
|
372
|
+
instanceRepeatable,
|
|
373
|
+
childRepInfo.preferredKey
|
|
374
|
+
)
|
|
375
|
+
? instanceRepeatable[childRepInfo.preferredKey]
|
|
376
|
+
: [];
|
|
377
|
+
const nestedInstances = Array.isArray(rawNestedInstances) ? rawNestedInstances : [];
|
|
364
378
|
const childRepeatableArray = processRepeatableSection(
|
|
365
379
|
childRepInfo,
|
|
366
380
|
[...currentPath, pathKey],
|
|
367
381
|
nestedInstances
|
|
368
382
|
);
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
childRecord.form_values[childOutputKey] = childRepeatableArray;
|
|
372
|
-
}
|
|
383
|
+
const childOutputKey = resolveRepeatableOutputKey(childRepInfo);
|
|
384
|
+
childRecord.form_values[childOutputKey] = childRepeatableArray;
|
|
373
385
|
}
|
|
374
386
|
|
|
375
387
|
if (hasMeaningfulRecord(childRecord)) {
|
|
@@ -501,14 +513,23 @@ export function createStructuredRecord(state, fields = null, options = {}, id =
|
|
|
501
513
|
for (const [, repInfo] of repeatableSectionTree) {
|
|
502
514
|
if (repInfo.parentPath.length === 0) {
|
|
503
515
|
// This is a top-level RepeatableSection
|
|
504
|
-
const
|
|
505
|
-
state.repeatable
|
|
516
|
+
const rawStructuredInstances = hasStructuredRepeatableState
|
|
517
|
+
? Object.prototype.hasOwnProperty.call(state.repeatable, repInfo.preferredKey)
|
|
506
518
|
? state.repeatable[repInfo.preferredKey]
|
|
507
|
-
:
|
|
519
|
+
: []
|
|
520
|
+
: null;
|
|
521
|
+
const structuredInstances = hasStructuredRepeatableState
|
|
522
|
+
? Array.isArray(rawStructuredInstances)
|
|
523
|
+
? rawStructuredInstances
|
|
524
|
+
: []
|
|
525
|
+
: null;
|
|
508
526
|
|
|
509
527
|
const childRecords = processRepeatableSection(repInfo, [], structuredInstances);
|
|
510
|
-
|
|
511
|
-
|
|
528
|
+
const repeatableOutputKey = resolveRepeatableOutputKey(repInfo);
|
|
529
|
+
if (hasStructuredRepeatableState) {
|
|
530
|
+
// In structured mode keep explicit repeatable keys, even when empty.
|
|
531
|
+
form_values[repeatableOutputKey] = childRecords;
|
|
532
|
+
} else if (childRecords.length > 0) {
|
|
512
533
|
form_values[repeatableOutputKey] = childRecords;
|
|
513
534
|
}
|
|
514
535
|
}
|