Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
sandulat committed Oct 26, 2023
1 parent 51317f4 commit 0de571e
Show file tree
Hide file tree
Showing 10 changed files with 329 additions and 72 deletions.
92 changes: 91 additions & 1 deletion apps/docs/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,93 @@
"use client";

import { useEffect } from "react";
import { createBuilder, createEntity } from "basebuilder";
import { useFormState, useFormStatus } from "react-dom";
import { z } from "zod";

import {
createEntityComponent,
Interpreter,
useInterpreterStore,
} from "@basebuilder/react";

import { submit } from "./submit";

const builder = createBuilder({
entities: [
createEntity({
name: "text",
validate(value) {
const res = z.string().safeParse(value);

if (!res.success) {
throw res.error.issues[0]?.message;
}

return res.data;
},
}),
],
});

const textComponent = createEntityComponent(
builder.entities[0],
({ entity, setValue }) => {
return (
<div>
<input
value={entity.value ?? ""}
onChange={(e) => setValue(e.target.value)}
name={entity.id}
/>
{JSON.stringify(entity.error)}
</div>
);
},
);

function SubmitButton() {
const { pending } = useFormStatus();

return (
<button type="submit" aria-disabled={pending}>
Add
</button>
);
}

const initialState = {
errors: {},
values: {},
};

export default function Page() {
return <div>Page</div>;
const [state, formAction] = useFormState(submit, initialState);

const interpreterStore = useInterpreterStore(builder, {
entities: {
"68b1a0dd-f3f8-4dde-a294-d61680d11223": {
type: "text",
attributes: {},
},
},
root: ["68b1a0dd-f3f8-4dde-a294-d61680d11223"],
});

useEffect(() => {
interpreterStore.setEntitiesErrors(state.errors);
}, [interpreterStore, state]);

return (
<form action={formAction}>
<Interpreter
interpreterStore={interpreterStore}
components={{
text: textComponent,
}}
/>

<SubmitButton />
</form>
);
}
51 changes: 51 additions & 0 deletions apps/docs/app/submit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"use server";

import {
createBuilder,
createEntity,
validateEntitiesValues,
} from "basebuilder";
import { z } from "zod";

const builder = createBuilder({
entities: [
createEntity({
name: "text",
validate(value) {
const res = z.string().min(5).safeParse(value);

if (!res.success) {
throw res.error.issues[0]?.message;
}

return res.data;
},
}),
],
});

export async function submit(prevState: unknown, formData: FormData) {
const values = Object.fromEntries(formData);

const res = await validateEntitiesValues(values, builder, {
entities: {
"68b1a0dd-f3f8-4dde-a294-d61680d11223": {
type: "text",
attributes: {},
},
},
root: ["68b1a0dd-f3f8-4dde-a294-d61680d11223"],
});

if (!res.success) {
return {
errors: res.entitiesErrors,
values,
};
}

return {
errors: {},
values: {},
};
}
2 changes: 1 addition & 1 deletion apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"basebuilder": "^0.1.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"next": "^13.5.6",
"next": "13.5.7-canary.27",
"next-themes": "^0.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
24 changes: 13 additions & 11 deletions packages/basebuilder-react/src/interpreter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,17 +225,19 @@ export function Interpreter<TBuilder extends Builder>(props: {
export function useEntitiesValues(entitiesIds?: Array<string>): EntitiesValues {
const { interpreterStore } = useContext(InterpreterContext);

const entitiesValues = useInterpreterStoreData(interpreterStore, (events) =>
events.some(
(event) =>
event.name === interpreterStoreEventsNames.DataSet ||
(event.name === interpreterStoreEventsNames.EntityValueUpdated &&
entitiesIds &&
entitiesIds.includes(event.payload.entityId)) ||
(event.name === interpreterStoreEventsNames.EntityValueUpdated &&
!entitiesIds),
),
).entitiesValues;
const { entitiesValues } = useInterpreterStoreData(
interpreterStore,
(events) =>
events.some(
(event) =>
event.name === interpreterStoreEventsNames.DataSet ||
(event.name === interpreterStoreEventsNames.EntityValueUpdated &&
entitiesIds &&
entitiesIds.includes(event.payload.entityId)) ||
(event.name === interpreterStoreEventsNames.EntityValueUpdated &&
!entitiesIds),
),
);

if (!entitiesIds) {
return entitiesValues;
Expand Down
21 changes: 15 additions & 6 deletions packages/basebuilder/src/entities-values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,27 +102,36 @@ export function getEligibleEntitiesIdsForValidation<TBuilder extends Builder>(
}

export async function validateEntitiesValues<TBuilder extends Builder>(
entitiesValues: EntitiesValues<TBuilder>,
entitiesValues: unknown,
builder: TBuilder,
schema: Schema<TBuilder>,
): Promise<EntitiesValuesValidationResult<TBuilder>> {
const computedEntitiesValues: EntitiesValues<TBuilder> =
typeof entitiesValues !== "object" ||
Array.isArray(entitiesValues) ||
entitiesValues === null
? {}
: (entitiesValues as EntitiesValues<TBuilder>);

const eligibleEntitiesIdsForValidation = getEligibleEntitiesIdsForValidation(
entitiesValues,
computedEntitiesValues,
builder,
schema,
);

const entitiesErrors: EntitiesErrors = {};

const newEntitiesValues: EntitiesValues<TBuilder> = { ...entitiesValues };
const newEntitiesValues: EntitiesValues<TBuilder> = {
...computedEntitiesValues,
};

for (const entityId in newEntitiesValues) {
for (const entityId in schema.entities) {
if (!eligibleEntitiesIdsForValidation.includes(entityId)) {
delete newEntitiesValues[entityId];

continue;
}
}

for (const entityId of eligibleEntitiesIdsForValidation) {
const validationResult = await validateEntityValue(
entityId,
newEntitiesValues,
Expand Down
16 changes: 9 additions & 7 deletions packages/basebuilder/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -644,25 +644,27 @@ type SchemValidationResult<TBuilder extends Builder> =
| { reason: SchemaValidationErrorReason; success: false };

export function validateSchemaIntegrity<TBuilder extends Builder>(
schema: Schema<TBuilder>,
schema: unknown,
builder: TBuilder,
): SchemValidationResult<TBuilder> {
const castedSchema = schema as Schema<TBuilder>;

if (typeof schema === "undefined") {
return { data: getEmptySchema(), success: true };
}

try {
ensureEntitiesHaveValidFormat(schema.entities);
ensureEntitiesHaveValidFormat(castedSchema.entities);

ensureRootHasValidFormat(schema.root);
ensureRootHasValidFormat(castedSchema.root);

ensureRootNotEmptyWhenThereAreEntities(schema);
ensureRootNotEmptyWhenThereAreEntities(castedSchema);

const validatedEntities = validateEntitiesSchema(schema, builder);
const validatedEntities = validateEntitiesSchema(castedSchema, builder);

const computedSchema = {
entities: validatedEntities,
root: schema.root,
root: castedSchema.root,
};

ensureRootIdsAreValid(computedSchema, builder);
Expand Down Expand Up @@ -726,7 +728,7 @@ async function validateEntitiesAttributes<TBuilder extends Builder>(
}

export async function validateSchema<TBuilder extends Builder>(
schema: Schema<TBuilder>,
schema: unknown,
builder: TBuilder,
): Promise<SchemValidationResult<TBuilder>> {
const validatedSchema = validateSchemaIntegrity(schema, builder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,91 @@ exports[`entities values validation > can validate entities 1`] = `
"success": false,
}
`;

exports[`entities values validation > can validate entities 2`] = `
{
"data": {
"51324b32-adc3-4d17-a90e-66b5453935bd": "value",
"6e0035c3-0d4c-445f-a42b-2d971225447c": "second value",
},
"success": true,
}
`;

exports[`entities values validation > can validate entities 3`] = `
{
"entitiesErrors": {
"51324b32-adc3-4d17-a90e-66b5453935bd": [ZodError: [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": [],
"message": "Required"
}
]],
"6e0035c3-0d4c-445f-a42b-2d971225447c": [ZodError: [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": [],
"message": "Required"
}
]],
},
"success": false,
}
`;

exports[`entities values validation > can validate entities 4`] = `
{
"entitiesErrors": {
"51324b32-adc3-4d17-a90e-66b5453935bd": [ZodError: [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": [],
"message": "Required"
}
]],
"6e0035c3-0d4c-445f-a42b-2d971225447c": [ZodError: [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": [],
"message": "Required"
}
]],
},
"success": false,
}
`;

exports[`entities values validation > can validate entities 5`] = `
{
"entitiesErrors": {
"51324b32-adc3-4d17-a90e-66b5453935bd": [ZodError: [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": [],
"message": "Required"
}
]],
"6e0035c3-0d4c-445f-a42b-2d971225447c": [ZodError: [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": [],
"message": "Required"
}
]],
},
"success": false,
}
`;
21 changes: 21 additions & 0 deletions packages/basebuilder/tests/entities-values.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,26 @@ describe("entities values validation", () => {
schema,
),
).toMatchSnapshot();

expect(
await validateEntitiesValues(
{
"51324b32-adc3-4d17-a90e-66b5453935bd": "value",
"6e0035c3-0d4c-445f-a42b-2d971225447c": "second value",
},
builder,
schema,
),
).toMatchSnapshot();

expect(
await validateEntitiesValues(null, builder, schema),
).toMatchSnapshot();

expect(await validateEntitiesValues([], builder, schema)).toMatchSnapshot();

expect(
await validateEntitiesValues(new Map(), builder, schema),
).toMatchSnapshot();
});
});
Loading

0 comments on commit 0de571e

Please sign in to comment.