diff --git a/core/actions/datacite/run.test.ts b/core/actions/datacite/run.test.ts index 0e78c70cc..aa98ed24b 100644 --- a/core/actions/datacite/run.test.ts +++ b/core/actions/datacite/run.test.ts @@ -75,6 +75,7 @@ const pub = { updatedAt: new Date(), schemaName: CoreSchemaType.String, relatedPubId: null, + rank: "a", }, { id: "" as PubValuesId, @@ -86,6 +87,7 @@ const pub = { updatedAt: new Date(), schemaName: CoreSchemaType.String, relatedPubId: null, + rank: "b", }, { id: "" as PubValuesId, @@ -97,6 +99,7 @@ const pub = { updatedAt: new Date(), schemaName: CoreSchemaType.URL, relatedPubId: null, + rank: "c", }, { id: "" as PubValuesId, @@ -108,6 +111,7 @@ const pub = { updatedAt: new Date(), schemaName: CoreSchemaType.DateTime, relatedPubId: null, + rank: "d", }, ], communityId: "" as CommunitiesId, diff --git a/core/actions/googleDriveImport/run.ts b/core/actions/googleDriveImport/run.ts index 4a6f05f3f..bb54aac70 100644 --- a/core/actions/googleDriveImport/run.ts +++ b/core/actions/googleDriveImport/run.ts @@ -84,7 +84,7 @@ export const run = defineRun( (value) => value.fieldSlug === `${communitySlug}:publication-date` )[0]; const publicationDate: Date = publicationDateField - ? (publicationDateField.value as Date) + ? (publicationDateField.value as unknown as Date) : new Date(values.relatedPub!.createdAt); return { [`${publicationDate.toISOString()}`]: values.relatedPubId }; }); @@ -102,7 +102,7 @@ export const run = defineRun( (value) => value.fieldSlug === `${communitySlug}:publication-date` )[0]; const publicationDate: Date = publicationDateField - ? (publicationDateField.value as Date) + ? (publicationDateField.value as unknown as Date) : new Date(values.relatedPub!.createdAt); return publicationDate.toISOString(); }); diff --git a/core/actions/move/run.ts b/core/actions/move/run.ts index e469e4fb1..591a151d8 100644 --- a/core/actions/move/run.ts +++ b/core/actions/move/run.ts @@ -4,6 +4,7 @@ import type { StagesId } from "db/public"; import { logger } from "logger"; import type { action } from "./action"; +import { isUniqueConstraintError } from "~/kysely/errors"; import { movePub } from "~/lib/server/stages"; import { defineRun } from "../types"; @@ -11,6 +12,13 @@ export const run = defineRun(async ({ pub, config }) => { try { await movePub(pub.id, config.stage as StagesId).execute(); } catch (error) { + if (isUniqueConstraintError(error)) { + return { + success: true, + report: `Pub was already in stage ${config.stage}`, + data: {}, + }; + } logger.error({ msg: "move", error }); return { title: "Failed to move pub", diff --git a/core/app/c/[communitySlug]/stages/components/lib/actions.ts b/core/app/c/[communitySlug]/stages/components/lib/actions.ts index 3b3ee86ff..ff3c8fd1e 100644 --- a/core/app/c/[communitySlug]/stages/components/lib/actions.ts +++ b/core/app/c/[communitySlug]/stages/components/lib/actions.ts @@ -34,7 +34,7 @@ export const move = defineServerAction(async function move( } try { - await movePub(pubId, destinationStageId).executeTakeFirstOrThrow(); + await movePub(pubId, destinationStageId).execute(); } catch { return { error: "The Pub was not successully moved" }; } diff --git a/core/app/components/ContextEditor/ContextEditorClient.tsx b/core/app/components/ContextEditor/ContextEditorClient.tsx index ca610ce51..ba4bf5e10 100644 --- a/core/app/components/ContextEditor/ContextEditorClient.tsx +++ b/core/app/components/ContextEditor/ContextEditorClient.tsx @@ -6,12 +6,14 @@ import dynamic from "next/dynamic"; import type { PubsId, PubTypesId } from "db/public"; import { Skeleton } from "ui/skeleton"; -import type { GetPubsResult, GetPubTypesResult } from "~/lib/server"; +import type { GetPubTypesResult } from "~/lib/server"; import { upload } from "../forms/actions"; import { ContextAtom } from "./AtomRenderer"; import "context-editor/style.css"; +import type { ContextEditorPub } from "./ContextEditorContext"; + const ContextEditor = dynamic(() => import("context-editor").then((mod) => mod.ContextEditor), { ssr: false, loading: () => , @@ -28,7 +30,7 @@ export const ContextEditorClient = ({ disabled, hideMenu, }: { - pubs: GetPubsResult; + pubs: ContextEditorPub[]; pubTypes: GetPubTypesResult; pubId: PubsId; pubTypeId: PubTypesId; diff --git a/core/app/components/ContextEditor/ContextEditorContext.tsx b/core/app/components/ContextEditor/ContextEditorContext.tsx index c4c1894fa..35e24f78e 100644 --- a/core/app/components/ContextEditor/ContextEditorContext.tsx +++ b/core/app/components/ContextEditor/ContextEditorContext.tsx @@ -7,11 +7,10 @@ import { createContext, useContext, useState } from "react"; import type { ProcessedPub } from "contracts"; import type { PubsId, PubTypesId } from "db/public"; -import type { GetPubsResult, GetPubTypesResult } from "~/lib/server"; -import { processedPubsToPubsResult } from "~/lib/pubs"; +import type { GetPubTypesResult } from "~/lib/server"; export type ContextEditorContext = { - pubs: GetPubsResult; + pubs: ContextEditorPub[]; pubTypes: GetPubTypesResult; pubId?: PubsId; pubTypeId?: PubTypesId; @@ -22,21 +21,23 @@ const ContextEditorContext = createContext({ pubTypes: [], }); -type InputPub = ProcessedPub<{ withStage: true; withLegacyAssignee: true; withPubType: true }>; +export type ContextEditorPub = ProcessedPub<{ + withStage: true; + withLegacyAssignee: true; + withPubType: true; +}>; type Props = PropsWithChildren< Omit & { - pubs: InputPub[]; + pubs: ContextEditorPub[]; } >; export const ContextEditorContextProvider = (props: Props) => { const [cachedPubId] = useState(props.pubId); - const { children, pubId, ...value } = props; - - const contextPubs = processedPubsToPubsResult(value.pubs); + const { children, pubId, pubs, ...value } = props; return ( - + {children} ); diff --git a/core/app/components/FormBuilder/ElementPanel/ButtonConfigurationForm.tsx b/core/app/components/FormBuilder/ElementPanel/ButtonConfigurationForm.tsx index ab24b55f7..005d0bbdf 100644 --- a/core/app/components/FormBuilder/ElementPanel/ButtonConfigurationForm.tsx +++ b/core/app/components/FormBuilder/ElementPanel/ButtonConfigurationForm.tsx @@ -1,6 +1,5 @@ import { useMemo } from "react"; import { zodResolver } from "@hookform/resolvers/zod"; -import mudder from "mudder"; import { useForm, useFormContext } from "react-hook-form"; import { z } from "zod"; @@ -29,6 +28,7 @@ import { Input } from "ui/input"; import { cn } from "utils"; import type { FormBuilderSchema } from "../types"; +import { findRanksBetween } from "~/lib/rank"; import { useCommunity } from "../../providers/CommunityProvider"; import { useFormBuilder } from "../FormBuilderContext"; import { ButtonOption } from "../SubmissionSettings"; @@ -103,11 +103,10 @@ export const ButtonConfigurationForm = ({ const onSubmit = (values: z.infer) => { const index = buttonIndex === -1 ? numElements : buttonIndex; update(index, { - rank: mudder.base62.mudder( - elements[index - 1]?.rank ?? "", - elements[index + 1]?.rank ?? "", - 1 - )[0], + rank: findRanksBetween({ + start: elements[index - 1]?.rank ?? "", + end: elements[index + 1]?.rank ?? "", + })[0], type: ElementType.button, elementId: button?.elementId, label: values.label, diff --git a/core/app/components/FormBuilder/ElementPanel/SelectElement.tsx b/core/app/components/FormBuilder/ElementPanel/SelectElement.tsx index 11a4b0582..6534273ba 100644 --- a/core/app/components/FormBuilder/ElementPanel/SelectElement.tsx +++ b/core/app/components/FormBuilder/ElementPanel/SelectElement.tsx @@ -1,4 +1,3 @@ -import mudder from "mudder"; import { useFormContext } from "react-hook-form"; import { defaultComponent } from "schemas"; @@ -9,6 +8,7 @@ import { usePubFieldContext } from "ui/pubFields"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "ui/tabs"; import type { FormElementData, PanelState } from "../types"; +import { findRanksBetween } from "~/lib/rank"; import { FieldIcon } from "../FieldIcon"; import { useFormBuilder } from "../FormBuilderContext"; import { structuralElements } from "../StructuralElements"; @@ -49,7 +49,7 @@ export const SelectElement = ({ panelState }: { panelState: PanelState }) => { fieldId: field.id, required: true, type: ElementType.pubfield, - rank: mudder.base62.mudder(elements[elementsCount - 1]?.rank, "", 1)[0], + rank: findRanksBetween({ start: elements[elementsCount - 1]?.rank })[0], configured: false, config: field.isRelation ? { @@ -137,11 +137,9 @@ export const SelectElement = ({ panelState }: { panelState: PanelState }) => { addElement({ element: elementType, type: ElementType.structural, - rank: mudder.base62.mudder( - elements[elementsCount - 1]?.rank, - "", - 1 - )[0], + rank: findRanksBetween({ + start: elements[elementsCount - 1]?.rank, + })[0], configured: false, }); dispatch({ diff --git a/core/app/components/FormBuilder/FormBuilder.tsx b/core/app/components/FormBuilder/FormBuilder.tsx index a420cdf66..15bbab2e3 100644 --- a/core/app/components/FormBuilder/FormBuilder.tsx +++ b/core/app/components/FormBuilder/FormBuilder.tsx @@ -13,7 +13,6 @@ import { verticalListSortingStrategy, } from "@dnd-kit/sortable"; import { zodResolver } from "@hookform/resolvers/zod"; -import mudder from "mudder"; import { useFieldArray, useForm } from "react-hook-form"; import type { Stages } from "db/public"; @@ -27,6 +26,7 @@ import { toast } from "ui/use-toast"; import type { FormBuilderSchema, FormElementData, PanelEvent, PanelState } from "./types"; import type { Form as PubForm } from "~/lib/server/form"; +import { getRankAndIndexChanges } from "~/lib/rank"; import { renderWithPubTokens } from "~/lib/server/render/pub/renderWithPubTokens"; import { didSucceed, useServerAction } from "~/lib/serverActions"; import { PanelHeader, PanelWrapper, SidePanel } from "../SidePanel"; @@ -206,33 +206,15 @@ export function FormBuilder({ pubForm, id, stages }: Props) { // Update ranks and rhf field array position when elements are dragged const handleDragEnd = useCallback( (event: DragEndEvent) => { - const { active, over } = event; - if (over && active.id !== over?.id) { - // activeIndex is the position the element started at and over is where it was dropped - const activeIndex = active.data.current?.sortable?.index; - const overIndex = over.data.current?.sortable?.index; - if (activeIndex !== undefined && overIndex !== undefined) { - // "earlier" means towards the beginning of the list, or towards the top of the page - const isMovedEarlier = activeIndex > overIndex; - const activeElem = elements[activeIndex]; - - // When moving an element earlier in the array, find a rank between the rank of the - // element at the dropped position and the element before it. When moving an element - // later, instead find a rank between that element and the element after it - const aboveRank = - elements[isMovedEarlier ? overIndex : overIndex + 1]?.rank ?? ""; - const belowRank = - elements[isMovedEarlier ? overIndex - 1 : overIndex]?.rank ?? ""; - const [rank] = mudder.base62.mudder(belowRank, aboveRank, 1); - - // move doesn't trigger a rerender, so it's safe to chain these calls - move(activeIndex, overIndex); - update(overIndex, { - ...activeElem, - rank, - updated: true, - }); - } + const changes = getRankAndIndexChanges(event, elements); + if (changes) { + // move doesn't trigger a rerender, so it's safe to chain these calls + move(changes.activeIndex, changes.overIndex); + update(changes.overIndex, { + ...elements[changes.activeIndex], + rank: changes.rank, + updated: true, + }); } }, [elements] diff --git a/core/app/components/FormBuilder/FormElement.tsx b/core/app/components/FormBuilder/FormElement.tsx index cbc1c0ee7..248ce1a6b 100644 --- a/core/app/components/FormBuilder/FormElement.tsx +++ b/core/app/components/FormBuilder/FormElement.tsx @@ -149,7 +149,9 @@ export const FieldInputElement = ({ element, isEditing, labelId }: FieldInputEle id={labelId} className={cn("font-semibold", element.deleted ? "text-gray-500" : "")} > - {(element.config as any)?.label ?? field.name} + {(element.config as any)?.relationshipConfig?.label ?? + (element.config as any)?.label ?? + field.name} diff --git a/core/app/components/forms/AddRelatedPubsPanel.tsx b/core/app/components/forms/AddRelatedPubsPanel.tsx index 8f6bb257e..86219482f 100644 --- a/core/app/components/forms/AddRelatedPubsPanel.tsx +++ b/core/app/components/forms/AddRelatedPubsPanel.tsx @@ -5,12 +5,11 @@ import type { ColumnDef } from "@tanstack/react-table"; import { useRef, useState } from "react"; import type { PubsId } from "db/public"; -import { pubFieldsIdSchema, pubsIdSchema } from "db/public"; import { Button } from "ui/button"; import { Checkbox } from "ui/checkbox"; import { DataTableColumnHeader } from "ui/data-table"; -import type { GetPubsResult } from "~/lib/server"; +import type { ContextEditorPub } from "../ContextEditor/ContextEditorContext"; import { PanelHeader, SidePanel } from "~/app/components/SidePanel"; import { getPubTitle } from "~/lib/pubs"; import { DataTable } from "../DataTable/v2/DataTable"; @@ -51,7 +50,7 @@ const getColumns = () => ); }, }, - ] as const satisfies ColumnDef[]; + ] as const satisfies ColumnDef[]; export const AddRelatedPubsPanel = ({ title, @@ -61,8 +60,8 @@ export const AddRelatedPubsPanel = ({ }: { title: string; onCancel: () => void; - onAdd: (pubs: GetPubsResult) => void; - pubs: GetPubsResult; + onAdd: (pubs: ContextEditorPub[]) => void; + pubs: ContextEditorPub[]; }) => { const sidebarRef = useRef(null); const [selected, setSelected] = useState>({}); @@ -77,7 +76,7 @@ export const AddRelatedPubsPanel = ({ }; return ( - +
d.id} />
-
+
diff --git a/core/app/components/forms/elements/RelatedPubsElement.tsx b/core/app/components/forms/elements/RelatedPubsElement.tsx index 8678acc0f..b8b710d96 100644 --- a/core/app/components/forms/elements/RelatedPubsElement.tsx +++ b/core/app/components/forms/elements/RelatedPubsElement.tsx @@ -1,51 +1,75 @@ "use client"; +import type { DragEndEvent } from "@dnd-kit/core"; import type { FieldErrors } from "react-hook-form"; -import { useMemo, useState } from "react"; +import { useCallback, useMemo, useState } from "react"; +import { DndContext, KeyboardSensor, PointerSensor, useSensor, useSensors } from "@dnd-kit/core"; +import { restrictToParentElement, restrictToVerticalAxis } from "@dnd-kit/modifiers"; +import { + SortableContext, + sortableKeyboardCoordinates, + useSortable, + verticalListSortingStrategy, +} from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; import { Value } from "@sinclair/typebox/value"; import { useFieldArray, useFormContext } from "react-hook-form"; import { relationBlockConfigSchema } from "schemas"; -import type { JsonValue } from "contracts"; import type { InputComponent, PubsId } from "db/public"; import { Button } from "ui/button"; import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "ui/form"; -import { Pencil, Plus, Trash, TriangleAlert } from "ui/icon"; +import { GripVertical, Pencil, Plus, Trash, TriangleAlert } from "ui/icon"; import { MultiBlock } from "ui/multiblock"; import { Popover, PopoverContent, PopoverTrigger } from "ui/popover"; import { cn } from "utils"; +import type { ContextEditorPub } from "../../ContextEditor/ContextEditorContext"; import type { PubFieldFormElementProps } from "../PubFieldFormElement"; -import type { ElementProps } from "../types"; -import type { GetPubsResult } from "~/lib/server"; +import type { ElementProps, RelatedFormValues, SingleFormValues } from "../types"; import { AddRelatedPubsPanel } from "~/app/components/forms/AddRelatedPubsPanel"; import { getPubTitle } from "~/lib/pubs"; +import { findRanksBetween, getRankAndIndexChanges } from "~/lib/rank"; import { useContextEditorContext } from "../../ContextEditor/ContextEditorContext"; import { useFormElementToggleContext } from "../FormElementToggleContext"; import { PubFieldFormElement } from "../PubFieldFormElement"; const RelatedPubBlock = ({ + id, pub, onRemove, valueComponentProps, slug, onBlur, }: { - pub: GetPubsResult[number]; + id: string; + pub: ContextEditorPub; onRemove: () => void; valueComponentProps: PubFieldFormElementProps; slug: string; onBlur?: () => void; }) => { + const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ + id, + }); + + const style = { + transform: CSS.Translate.toString(transform), + transition, + }; return ( -
+
{/* Max width to keep long 'value's truncated. 90% to leave room for the trash button */}
{getPubTitle(pub)}
-
+
+
+ +
); }; -type FieldValue = { value: JsonValue; relatedPubId: PubsId }; -type FormValue = { - [slug: string]: FieldValue[]; -}; -type FormValueSingle = { - [slug: string]: JsonValue; -}; - const parseRelatedPubValuesSlugError = ( slug: string, - formStateErrors: FieldErrors | FieldErrors + formStateErrors: FieldErrors | FieldErrors ) => { const [baseSlug, index] = slug.split("."); const indexNumber = index ? parseInt(index) : undefined; if (!indexNumber || isNaN(indexNumber)) { - const baseError = (formStateErrors as FieldErrors)[baseSlug]; + const baseError = (formStateErrors as FieldErrors)[baseSlug]; return baseError; } - const valueError = (formStateErrors as FieldErrors)[baseSlug]?.[indexNumber]?.value; + const valueError = (formStateErrors as FieldErrors)[baseSlug]?.[indexNumber] + ?.value; return valueError; }; @@ -100,7 +129,7 @@ export const ConfigureRelatedValue = ({ : element.config.label; const label = configLabel || element.label || element.slug; - const { watch, formState } = useFormContext(); + const { watch, formState } = useFormContext(); const [isPopoverOpen, setPopoverIsOpen] = useState(false); const value = watch(slug); const showValue = value != null && value !== ""; @@ -161,11 +190,37 @@ export const RelatedPubsElement = ({ }) => { const { pubs, pubId } = useContextEditorContext(); const [showPanel, setShowPanel] = useState(false); - const { control } = useFormContext(); + const { control, getValues, setValue } = useFormContext< + RelatedFormValues & { deleted: { slug: string; relatedPubId: PubsId }[] } + >(); const formElementToggle = useFormElementToggleContext(); const isEnabled = formElementToggle.isEnabled(slug); - const { fields, append, remove } = useFieldArray({ control, name: slug }); + const { fields, append, move, update, remove } = useFieldArray({ control, name: slug }); + + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }) + ); + + // Update ranks and rhf field array position when elements are dragged + const handleDragEnd = useCallback( + (event: DragEndEvent) => { + const changes = getRankAndIndexChanges(event, fields); + if (changes) { + // move doesn't trigger a rerender, so it's safe to chain these calls + move(changes.activeIndex, changes.overIndex); + const { id, ...movedField } = fields[changes.activeIndex]; + update(changes.overIndex, { + ...movedField, + rank: changes.rank, + }); + } + }, + [fields] + ); Value.Default(relationBlockConfigSchema, config); if (!Value.Check(relationBlockConfigSchema, config)) { @@ -178,7 +233,7 @@ export const RelatedPubsElement = ({ acc[pub.id] = pub; return acc; }, - {} as Record + {} as Record ); }, [pubs]); @@ -193,12 +248,21 @@ export const RelatedPubsElement = ({ control={control} name={slug} render={({ field }) => { - const handleAddPubs = (newPubs: GetPubsResult) => { - const values = newPubs.map((p) => ({ relatedPubId: p.id, value: null })); + const handleAddPubs = (newPubs: ContextEditorPub[]) => { + const ranks = findRanksBetween({ + start: field.value[field.value.length - 1]?.rank, + numberOfRanks: newPubs.length, + }); + const values = newPubs.map((p, i) => ({ + relatedPubId: p.id, + value: null, + rank: ranks[i], + })); for (const value of values) { append(value); } }; + return ( {showPanel && ( @@ -219,25 +283,52 @@ export const RelatedPubsElement = ({ > {fields.length ? (
- {fields.map((item, index) => { - const handleRemovePub = () => { - remove(index); - }; - const innerSlug = - `${slug}.${index}.value` as const; - return ( - - ); - })} + + + {fields.map(({ id, ...item }, index) => { + const handleRemovePub = () => { + remove(index); + if (item.valueId) { + setValue("deleted", [ + ...getValues("deleted"), + { + relatedPubId: + item.relatedPubId, + slug, + }, + ]); + } + }; + const innerSlug = + `${slug}.${index}.value` as const; + return ( + + ); + })} + +
) : null} diff --git a/core/app/components/forms/types.ts b/core/app/components/forms/types.ts index ade66e15c..b79d6ab05 100644 --- a/core/app/components/forms/types.ts +++ b/core/app/components/forms/types.ts @@ -1,11 +1,14 @@ import { type InputComponentConfigSchema, type SchemaTypeByInputComponent } from "schemas"; +import type { JsonValue, ProcessedPub } from "contracts"; import type { CoreSchemaType, ElementType, FormElementsId, InputComponent, PubFieldsId, + PubsId, + PubValuesId, StructuralFormElement, } from "db/public"; @@ -86,3 +89,26 @@ export type StructuralElement = { export type FormElements = PubFieldElement | StructuralElement | ButtonElement; export type BasicFormElements = ButtonElement | StructuralElement | BasicPubFieldElement; + +export type RelatedFieldValue = { + value: JsonValue; + relatedPubId: PubsId; + rank: string; + valueId?: PubValuesId; +}; + +export type HydratedRelatedFieldValue = Omit & { + value: JsonValue | Date; +}; + +export type RelatedFormValues = { + [slug: string]: RelatedFieldValue[]; +}; + +export type SingleFormValues = { + [slug: string]: JsonValue; +}; + +export const isRelatedValue = ( + value: ProcessedPub["values"][number] +): value is ProcessedPub["values"][number] & RelatedFieldValue => Boolean(value.relatedPubId); diff --git a/core/app/components/pubs/CreatePubButton.tsx b/core/app/components/pubs/CreatePubButton.tsx index 6da91ae8f..40f795812 100644 --- a/core/app/components/pubs/CreatePubButton.tsx +++ b/core/app/components/pubs/CreatePubButton.tsx @@ -97,7 +97,7 @@ export const CreatePubButton = async (props: Props) => { return null; } - const pubTypes = await getPubTypesForCommunity(community.id); + const pubTypes = await getPubTypesForCommunity(community.id, { limit: 0 }); const stageId = "stageId" in props ? props.stageId : undefined; return ( diff --git a/core/app/components/pubs/PubEditor/PubEditorClient.tsx b/core/app/components/pubs/PubEditor/PubEditorClient.tsx index 10c38b156..35f18c540 100644 --- a/core/app/components/pubs/PubEditor/PubEditorClient.tsx +++ b/core/app/components/pubs/PubEditor/PubEditorClient.tsx @@ -8,18 +8,25 @@ import React, { useCallback, useEffect, useMemo, useState } from "react"; import { usePathname, useRouter } from "next/navigation"; import { typeboxResolver } from "@hookform/resolvers/typebox"; import { Type } from "@sinclair/typebox"; +import isEqualWith from "lodash.isequalwith"; import partition from "lodash.partition"; import { useForm } from "react-hook-form"; import { getDefaultValueByCoreSchemaType, getJsonSchemaByCoreSchemaType } from "schemas"; import type { JsonValue, ProcessedPub } from "contracts"; -import type { PubsId, PubTypesId, StagesId } from "db/public"; +import type { PubsId, StagesId } from "db/public"; import { CoreSchemaType, ElementType } from "db/public"; import { Form } from "ui/form"; import { useUnsavedChangesWarning } from "ui/hooks"; import { cn } from "utils"; -import type { BasicFormElements, FormElements } from "../../forms/types"; +import type { + BasicFormElements, + FormElements, + HydratedRelatedFieldValue, + RelatedFieldValue, + SingleFormValues, +} from "../../forms/types"; import type { FormElementToggleContext } from "~/app/components/forms/FormElementToggleContext"; import type { DefinitelyHas } from "~/lib/types"; import { useFormElementToggleContext } from "~/app/components/forms/FormElementToggleContext"; @@ -27,6 +34,7 @@ import { useCommunity } from "~/app/components/providers/CommunityProvider"; import * as actions from "~/app/components/pubs/PubEditor/actions"; import { SubmitButtons } from "~/app/components/pubs/PubEditor/SubmitButtons"; import { didSucceed, useServerAction } from "~/lib/serverActions"; +import { isRelatedValue } from "../../forms/types"; import { RELATED_PUB_SLUG } from "./constants"; const SAVE_WAIT_MS = 5000; @@ -36,26 +44,42 @@ const preparePayload = ({ formValues, formState, toggleContext, + defaultValues, + arrayDefaults, }: { formElements: BasicFormElements[]; formValues: FieldValues; formState: FormState; toggleContext: FormElementToggleContext; + defaultValues: Record< + string, + HydratedRelatedFieldValue | SingleFormValues | undefined | StagesId + >; + arrayDefaults: Record; }) => { - const payload: Record = {}; + const valuesPayload: Record = {}; for (const { slug } of formElements) { if ( slug && toggleContext.isEnabled(slug) && - // Only send fields that were changed. - // TODO: this check doesn't quite work for related pub field arrays. - // perhaps they are initialized differently so always show up as dirty? - formState.dirtyFields[slug] + // Only send fields that were changed. RHF erroneously reports fields as changed (dirty) + // sometimes even when the default value is the same, so we do the check ourselves + !isEqualWith(formValues[slug], defaultValues[slug]) ) { - payload[slug] = formValues[slug]; + const val = formValues[slug]; + if (Array.isArray(val)) { + const filteredVal = val.filter((v: RelatedFieldValue) => { + const isNew = !v.valueId; + const isChanged = v.valueId && !isEqualWith(arrayDefaults[v.valueId], v); + return isNew || isChanged; + }); + valuesPayload[slug] = filteredVal; + } else { + valuesPayload[slug] = val; + } } } - return payload; + return valuesPayload; }; /** @@ -63,7 +87,10 @@ const preparePayload = ({ * Special case: date pubValues need to be transformed to a Date type to pass validation */ const buildDefaultValues = (elements: BasicFormElements[], pubValues: ProcessedPub["values"]) => { - const defaultValues: FieldValues = {}; + const defaultValues: FieldValues = { deleted: [] }; + // Build a record of the default values for array elements (related pubs) keyed by pub_values.id + // for dirty checking in preparePayload + const arrayDefaults: Record = {}; for (const element of elements) { if (element.slug && element.schemaName) { const pubValue = pubValues.find((v) => v.fieldSlug === element.slug)?.value; @@ -76,25 +103,52 @@ const buildDefaultValues = (elements: BasicFormElements[], pubValues: ProcessedP // There can be multiple relations for a single slug if (element.isRelation) { const relatedPubValues = pubValues.filter((v) => v.fieldSlug === element.slug); - defaultValues[element.slug] = relatedPubValues.map((pv) => ({ - value: - pv.schemaName === CoreSchemaType.DateTime - ? new Date(pv.value as string) - : pv.value, - relatedPubId: pv.relatedPubId, - })); + defaultValues[element.slug] = []; + relatedPubValues.forEach((pv) => { + if (!isRelatedValue(pv)) { + return; + } + const relatedVal = { + value: + element.schemaName === CoreSchemaType.DateTime && + typeof pv.value === "string" + ? new Date(pv.value) + : pv.value, + relatedPubId: pv.relatedPubId, + rank: pv.rank, + valueId: pv.id, + }; + defaultValues[element.slug].push(relatedVal); + arrayDefaults[pv.id] = relatedVal; + }); } } } - return defaultValues; + return { defaultValues, arrayDefaults }; +}; + +const deletedValuesSchema = Type.Array( + Type.Object({ + slug: Type.String(), + relatedPubId: Type.Unsafe(Type.String()), + }) +); + +const staticSchema = { + deleted: deletedValuesSchema, + stageId: Type.Optional(Type.Union([Type.Unsafe(Type.String()), Type.Null()])), +}; + +type StaticSchema = { + [Property in keyof typeof staticSchema]: Static<(typeof staticSchema)[Property]>; }; const createSchemaFromElements = ( elements: BasicFormElements[], toggleContext: FormElementToggleContext ) => { - return Type.Object( - Object.fromEntries( + return Type.Object({ + ...Object.fromEntries( elements // only add enabled pubfields to the schema .filter( @@ -138,8 +192,9 @@ const createSchemaFromElements = ( } return [slug, schemaAllowEmpty]; }) - ) - ); + ), + ...staticSchema, + }); }; const isSubmitEvent = ( @@ -223,17 +278,21 @@ export const PubEditorClient = ({ ); const toggleContext = useFormElementToggleContext(); - const defaultValues = useMemo(() => { - const defaultPubValues = buildDefaultValues(formElements, pub.values); - return { ...defaultPubValues, stageId }; - }, [formElements, pub]); - - const resolver = useMemo( - () => typeboxResolver(createSchemaFromElements(formElements, toggleContext)), + const schema = useMemo( + () => createSchemaFromElements(formElements, toggleContext), [formElements, toggleContext] ); - const formInstance = useForm>>({ + const [defaultValues, arrayDefaults] = useMemo(() => { + const { defaultValues, arrayDefaults } = buildDefaultValues(formElements, pub.values); + return [{ ...defaultValues, stageId }, arrayDefaults]; + }, [formElements, stageId, schema]); + + const resolver = useMemo(() => typeboxResolver(schema), [formElements, toggleContext]); + + const formInstance = useForm< + Static> & StaticSchema + >({ resolver, defaultValues, shouldFocusError: false, @@ -242,13 +301,14 @@ export const PubEditorClient = ({ const handleSubmit = useCallback( async ( - formValues: FieldValues, + formValues: FieldValues & StaticSchema, evt: React.BaseSyntheticEvent | undefined, autoSave = false ) => { const { stageId: stageIdFromForm, [RELATED_PUB_SLUG]: relatedPubValue, + deleted, ...newValues } = formValues; @@ -257,6 +317,8 @@ export const PubEditorClient = ({ formValues: newValues, formState: formInstance.formState, toggleContext, + defaultValues, + arrayDefaults, }); const { stageId: stageIdFromButtonConfig, submitButtonId } = getButtonConfig({ @@ -265,24 +327,26 @@ export const PubEditorClient = ({ buttonElements, }); - const stageId = stageIdFromForm ?? stageIdFromButtonConfig; + const newStageId = stageIdFromForm ?? stageIdFromButtonConfig; + const stageIdChanged = newStageId !== stageId; let result; if (isUpdating) { result = await runUpdatePub({ pubId: pubId, pubValues, - stageId, + stageId: stageIdChanged ? newStageId : undefined, formSlug, continueOnValidationError: autoSave, + deleted, }); } else { result = await runCreatePub({ formSlug, body: { id: pubId, - pubTypeId: pub.pubTypeId as PubTypesId, - values: pubValues as Record, - stageId: stageId, + pubTypeId: pub.pubTypeId, + values: pubValues, + stageId: newStageId, }, communityId: community.id, addUserToForm: isExternalForm, @@ -295,6 +359,7 @@ export const PubEditorClient = ({ [relatedPub.slug]: [{ value: relatedPubValue, relatedPubId: pubId }], }, continueOnValidationError: true, + deleted: [], }); } } @@ -308,6 +373,7 @@ export const PubEditorClient = ({ keepValues: true, } ); + onSuccess({ isAutoSave: autoSave, submitButtonId, values: pubValues }); } }, @@ -320,6 +386,14 @@ export const PubEditorClient = ({ pub, community.id, toggleContext, + withButtonElements, + buttonElements, + pubId, + isExternalForm, + relatedPub, + defaultValues, + arrayDefaults, + stageId, ] ); @@ -333,7 +407,7 @@ export const PubEditorClient = ({ const isSubmitting = formInstance.formState.isSubmitting; const handleAutoSave = useCallback( - (values: FieldValues, evt: React.BaseSyntheticEvent | undefined) => { + (values: FieldValues & StaticSchema, evt: React.BaseSyntheticEvent | undefined) => { if (saveTimer) { clearTimeout(saveTimer); } @@ -347,10 +421,10 @@ export const PubEditorClient = ({ [formElements, saveTimer, handleSubmit] ); - const handleAutoSaveOnError: SubmitErrorHandler = (errors) => { + const handleAutoSaveOnError: SubmitErrorHandler = (errors) => { const validFields = Object.fromEntries( Object.entries(formInstance.getValues()).filter(([name, value]) => !(name in errors)) - ); + ) as FieldValues & StaticSchema; handleAutoSave(validFields, undefined); }; diff --git a/core/app/components/pubs/PubEditor/actions.db.test.ts b/core/app/components/pubs/PubEditor/actions.db.test.ts index 5ca7e8694..83a52eaad 100644 --- a/core/app/components/pubs/PubEditor/actions.db.test.ts +++ b/core/app/components/pubs/PubEditor/actions.db.test.ts @@ -110,11 +110,17 @@ describe("updatePub", () => { name: "Can update if all permissions are satisfied", loginUser: { id: crypto.randomUUID() as UsersId }, userRole: MemberRole.admin, - expected: [ - { - value: "new title", - }, - ], + expected: { + values: [ + { + fieldName: "Title", + rank: null, + relatedPubId: null, + schemaName: "String", + value: "new title", + }, + ], + }, }, ])("$name", async ({ loginUser, userRole, expected }) => { const { seedCommunity } = await import("~/prisma/seed/seedCommunity"); @@ -169,6 +175,7 @@ describe("updatePub", () => { [`${community.slug}:title`]: "new title", }, continueOnValidationError: false, + deleted: [], }); expect(result).toMatchObject(expected); }); diff --git a/core/app/components/pubs/PubEditor/actions.ts b/core/app/components/pubs/PubEditor/actions.ts index 69b1f68ab..da2f31f0b 100644 --- a/core/app/components/pubs/PubEditor/actions.ts +++ b/core/app/components/pubs/PubEditor/actions.ts @@ -1,21 +1,22 @@ "use server"; -import type { Json } from "contracts"; +import type { JsonValue } from "contracts"; import type { PubsId, StagesId, UsersId } from "db/public"; import { Capabilities, MembershipType } from "db/public"; import { logger } from "logger"; -import type { PubValues } from "~/lib/server"; import { db } from "~/kysely/database"; import { getLoginData } from "~/lib/authentication/loginData"; import { isCommunityAdmin } from "~/lib/authentication/roles"; import { userCan } from "~/lib/authorization/capabilities"; +import { parseRichTextForPubFieldsAndRelatedPubs } from "~/lib/fields/richText"; import { createLastModifiedBy } from "~/lib/lastModifiedBy"; import { ApiError, createPubRecursiveNew } from "~/lib/server"; import { findCommunityBySlug } from "~/lib/server/community"; import { defineServerAction } from "~/lib/server/defineServerAction"; import { addMemberToForm, userHasPermissionToForm } from "~/lib/server/form"; -import { updatePub as _updatePub, deletePub } from "~/lib/server/pub"; +import { deletePub, normalizePubValues } from "~/lib/server/pub"; +import { PubOp } from "~/lib/server/pub-op"; type CreatePubRecursiveProps = Omit[0], "lastModifiedBy">; @@ -77,12 +78,17 @@ export const updatePub = defineServerAction(async function updatePub({ stageId, formSlug, continueOnValidationError, + deleted, }: { pubId: PubsId; - pubValues: Record; + pubValues: Record< + string, + JsonValue | Date | { value: JsonValue | Date; relatedPubId: PubsId }[] + >; stageId?: StagesId; formSlug?: string; continueOnValidationError: boolean; + deleted: { slug: string; relatedPubId: PubsId }[]; }) { const loginData = await getLoginData(); @@ -96,14 +102,14 @@ export const updatePub = defineServerAction(async function updatePub({ return ApiError.COMMUNITY_NOT_FOUND; } - const canUpdateFromForm = formSlug - ? await userHasPermissionToForm({ formSlug, userId: loginData.user.id, pubId }) - : false; - const canUpdatePubValues = await userCan( - Capabilities.updatePubValues, - { type: MembershipType.pub, pubId }, - loginData.user.id - ); + const [canUpdateFromForm, canUpdatePubValues] = await Promise.all([ + formSlug ? userHasPermissionToForm({ formSlug, userId: loginData.user.id, pubId }) : false, + userCan( + Capabilities.updatePubValues, + { type: MembershipType.pub, pubId }, + loginData.user.id + ), + ]); if (!canUpdatePubValues && !canUpdateFromForm) { return ApiError.UNAUTHORIZED; @@ -114,16 +120,38 @@ export const updatePub = defineServerAction(async function updatePub({ }); try { - const result = await _updatePub({ - pubId, + const updateQuery = PubOp.update(pubId, { communityId: community.id, - pubValues, - stageId, - continueOnValidationError, lastModifiedBy, + continueOnValidationError, }); - return result; + if (stageId) { + updateQuery.setStage(stageId); + } + + const { values: processedVals }: { values: typeof pubValues } = + parseRichTextForPubFieldsAndRelatedPubs({ + pubId: pubId, + values: pubValues as Record, + }); + + const normalizedValues = normalizePubValues(processedVals); + for (const { slug, value, relatedPubId } of normalizedValues) { + if (relatedPubId) { + updateQuery.relate(slug, value, relatedPubId, { + replaceExisting: false, + }); + } else { + updateQuery.set(slug, value); + } + } + + for (const { slug, relatedPubId } of deleted) { + updateQuery.unrelate(slug, relatedPubId); + } + + return await updateQuery.execute(); } catch (error) { logger.error(error); return { diff --git a/core/lib/pubs.ts b/core/lib/pubs.ts index 4b1913610..3054b533c 100644 --- a/core/lib/pubs.ts +++ b/core/lib/pubs.ts @@ -1,6 +1,4 @@ -import type { JsonValue, ProcessedPub } from "contracts"; - -import type { GetPubsResult } from "./server"; +import type { ProcessedPub } from "contracts"; export type PubTitleProps = { title?: string | null; @@ -47,48 +45,6 @@ export const getPubTitle = (pub: PubTitleProps): string => { type InputPub = ProcessedPub<{ withStage: true; withLegacyAssignee: true; withPubType: true }>; -/** - * this is a bridge function for places where we still use the `{ slug: value }` pubvalues shape, rather than an array - * this is eg the case in the contexteditor at the time of writing (2024-12-18) - */ -export const processedPubToPubResult = (pub: T): GetPubsResult[number] => { - return { - ...pub, - values: pub.values.reduce( - (acc, value) => { - const existingValue = acc[value.fieldSlug] as JsonValue | JsonValue[] | undefined; - - if (!value?.relatedPubId) { - acc[value.fieldSlug] = value.value as JsonValue; - return acc; - } - - const existingVal = existingValue - ? Array.isArray(existingValue) - ? existingValue - : [existingValue] - : []; - - acc[value.fieldSlug] = [ - ...existingVal, - { relatedPubId: value.relatedPubId, value: value.value as JsonValue }, - ]; - - return acc; - }, - {} as GetPubsResult[number]["values"] - ), - stages: pub.stage ? [pub.stage] : [], - assigneeId: pub.assignee?.id ?? null, - assignee: (pub.assignee ?? null) as GetPubsResult[number]["assignee"], - pubType: pub.pubType as GetPubsResult[number]["pubType"], - }; -}; - -export const processedPubsToPubsResult = (pubs: InputPub[]): GetPubsResult => { - return pubs.map(processedPubToPubResult); -}; - export const getTitleField = ( pub: T ): T["pubType"]["fields"][number] | undefined => pub.pubType.fields.find((field) => field.isTitle); diff --git a/core/lib/rank.ts b/core/lib/rank.ts new file mode 100644 index 000000000..dd02403da --- /dev/null +++ b/core/lib/rank.ts @@ -0,0 +1,36 @@ +import type { DragEndEvent } from "@dnd-kit/core"; + +import mudder from "mudder"; + +export const findRanksBetween = ({ start = "", end = "", numberOfRanks = 1 }) => + mudder.base62.mudder(start, end, numberOfRanks); + +export const getRankAndIndexChanges = ( + event: DragEndEvent, + elements: T +) => { + const { active, over } = event; + if (over && active.id !== over?.id) { + // activeIndex is the position the element started at and over is where it was + // dropped + const activeIndex = active.data.current?.sortable?.index; + const overIndex = over.data.current?.sortable?.index; + if (activeIndex !== undefined && overIndex !== undefined) { + // "earlier" means towards the beginning of the list, or towards the top of the page + const isMovedEarlier = activeIndex > overIndex; + + // When moving an element earlier in the array, find a rank between the rank of the + // element at the dropped position and the element before it. When moving an element + // later, instead find a rank between that element and the element after it + const aboveRank = elements[isMovedEarlier ? overIndex : overIndex + 1]?.rank ?? ""; + const belowRank = elements[isMovedEarlier ? overIndex - 1 : overIndex]?.rank ?? ""; + const [rank] = findRanksBetween({ start: belowRank, end: aboveRank }); + + return { + activeIndex, + overIndex, + rank, + }; + } + } +}; diff --git a/core/lib/server/form.ts b/core/lib/server/form.ts index 20f03aa42..517ca9f81 100644 --- a/core/lib/server/form.ts +++ b/core/lib/server/form.ts @@ -2,7 +2,6 @@ import type { QueryCreator } from "kysely"; import { sql } from "kysely"; import { jsonArrayFrom } from "kysely/helpers/postgres"; -import mudder from "mudder"; import { defaultComponent } from "schemas"; import type { CommunitiesId, FormsId, PublicSchema, PubsId, PubTypesId, UsersId } from "db/public"; @@ -13,6 +12,7 @@ import type { GetPubTypesResult } from "./pubtype"; import type { FormElements } from "~/app/components/forms/types"; import { db } from "~/kysely/database"; import { createMagicLink } from "../authentication/createMagicLink"; +import { findRanksBetween } from "../rank"; import { autoCache } from "./cache/autoCache"; import { autoRevalidate } from "./cache/autoRevalidate"; import { getCommunitySlug } from "./cache/getCommunitySlug"; @@ -215,7 +215,7 @@ export const insertForm = ( isDefault: boolean, trx = db ) => { - const ranks = mudder.base62.mudder(pubType.fields.length + 1); + const ranks = findRanksBetween({ numberOfRanks: pubType.fields.length + 1 }); return trx .with("form", (db) => diff --git a/core/lib/server/pub-op.ts b/core/lib/server/pub-op.ts index ab7a66df2..2b87f8c53 100644 --- a/core/lib/server/pub-op.ts +++ b/core/lib/server/pub-op.ts @@ -21,7 +21,7 @@ import { validatePubValues, } from "./pub"; -type PubValue = string | number | boolean | JsonValue; +type PubValue = string | number | boolean | JsonValue | Date; type PubOpOptionsBase = { communityId: CommunitiesId; @@ -31,9 +31,11 @@ type PubOpOptionsBase = { type PubOpOptionsCreateUpsert = PubOpOptionsBase & { pubTypeId: PubTypesId; + continueOnValidationError?: boolean; }; type PubOpOptionsUpdate = PubOpOptionsBase & { + continueOnValidationError?: boolean; pubTypeId?: never; }; @@ -979,7 +981,7 @@ abstract class BasePubOp { const validated = await validatePubValues({ pubValues: toUpsert, communityId: this.options.communityId, - continueOnValidationError: false, + continueOnValidationError: this.options.continueOnValidationError, trx, }); diff --git a/core/lib/server/pub.ts b/core/lib/server/pub.ts index 4f0b235c1..d5edac2e2 100644 --- a/core/lib/server/pub.ts +++ b/core/lib/server/pub.ts @@ -10,12 +10,10 @@ import type { import { sql, Transaction } from "kysely"; import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/postgres"; import partition from "lodash.partition"; -import mudder from "mudder"; import type { CreatePubRequestBodyWithNullsNew, FTSReturn, - GetPubResponseBody, Json, JsonValue, MaybePubOptions, @@ -42,13 +40,15 @@ import { Capabilities, CoreSchemaType, MemberRole, MembershipType, OperationType import { logger } from "logger"; import { assert, expect } from "utils"; -import type { DefinitelyHas, MaybeHas, Prettify, XOR } from "../types"; +import type { DefinitelyHas, MaybeHas, XOR } from "../types"; import type { SafeUser } from "./user"; import { db } from "~/kysely/database"; +import { isUniqueConstraintError } from "~/kysely/errors"; import { env } from "../env/env.mjs"; import { parseRichTextForPubFieldsAndRelatedPubs } from "../fields/richText"; import { hydratePubValues, mergeSlugsWithFields } from "../fields/utils"; import { parseLastModifiedBy } from "../lastModifiedBy"; +import { findRanksBetween } from "../rank"; import { autoCache } from "./cache/autoCache"; import { autoRevalidate } from "./cache/autoRevalidate"; import { BadRequestError, NotFoundError } from "./errors"; @@ -140,23 +140,6 @@ export const pubType = < .$notNull() .as("pubType"); -const pubAssignee = (eb: ExpressionBuilder) => - jsonObjectFrom( - eb - .selectFrom("users") - .whereRef("users.id", "=", "pubs.assigneeId") - .select([ - "users.id", - "slug", - "firstName", - "lastName", - "avatar", - "createdAt", - "email", - "communityId", - ]) - ).as("assignee"); - const pubColumns = [ "id", "communityId", @@ -167,57 +150,6 @@ const pubColumns = [ "title", ] as const satisfies SelectExpression[]; -export const getPubBase = ( - props: - | { pubId: PubsId; communityId?: never; stageId?: never } - | { pubId?: never; communityId: CommunitiesId; stageId?: never } - | { - pubId?: never; - communityId?: never; - stageId: StagesId; - } -) => - db - .selectFrom("pubs") - .select((eb) => [ - ...pubColumns, - pubType({ eb, pubTypeIdRef: "pubs.pubTypeId" }), - pubAssignee(eb), - jsonArrayFrom( - eb - .selectFrom("PubsInStages") - .select(["PubsInStages.stageId as id"]) - .whereRef("PubsInStages.pubId", "=", "pubs.id") - ).as("stages"), - ]) - .$if(!!props.pubId, (eb) => eb.select(pubValuesByVal(props.pubId!))) - .$if(!props.pubId, (eb) => eb.select(pubValuesByRef("pubs.id"))) - .$narrowType<{ values: PubValues }>(); - -export const _deprecated_getPub = async (pubId: PubsId): Promise => { - const pub = await getPubBase({ pubId }).where("pubs.id", "=", pubId).executeTakeFirst(); - - if (!pub) { - throw PubNotFoundError; - } - - return pub; -}; - -export const _deprecated_getPubCached = async (pubId: PubsId) => { - const pub = await autoCache( - getPubBase({ pubId }).where("pubs.id", "=", pubId) - ).executeTakeFirst(); - - if (!pub) { - throw PubNotFoundError; - } - - return pub; -}; - -export type GetPubResult = Prettify>>; - export type GetManyParams = { limit?: number; offset?: number; @@ -238,42 +170,6 @@ export const GET_MANY_DEFAULT = { orderDirection: "desc", } as const; -const GET_PUBS_DEFAULT = { - ...GET_MANY_DEFAULT, - select: pubColumns, -} as const; - -/** - * Get a nested array of pubs - * - * Either per community, or per stage - */ -export const _deprecated_getPubs = async ( - props: XOR<{ communityId: CommunitiesId }, { stageId: StagesId }>, - params: GetManyParams = GET_PUBS_DEFAULT -) => { - const { limit, offset, orderBy, orderDirection } = { ...GET_PUBS_DEFAULT, ...params }; - - const pubs = await autoCache( - getPubBase(props) - .$if(Boolean(props.communityId), (eb) => - eb.where("pubs.communityId", "=", props.communityId!) - ) - .$if(Boolean(props.stageId), (eb) => - eb - .innerJoin("PubsInStages", "pubs.id", "PubsInStages.pubId") - .where("PubsInStages.stageId", "=", props.stageId!) - ) - .limit(limit) - .offset(offset) - .orderBy(orderBy, orderDirection) - ).execute(); - - return pubs; -}; - -export type GetPubsResult = Prettify>>; - const PubNotFoundError = new NotFoundError("Pub not found"); /** @@ -326,7 +222,7 @@ export const maybeWithTrx = async ( const isRelatedPubInit = (value: unknown): value is { value: unknown; relatedPubId: PubsId }[] => Array.isArray(value) && - value.every((v) => typeof v === "object" && "value" in v && "relatedPubId" in v); + value.every((v) => typeof v === "object" && v && "value" in v && "relatedPubId" in v); /** * Transform pub values which can either be @@ -340,15 +236,15 @@ const isRelatedPubInit = (value: unknown): value is { value: unknown; relatedPub * to a more standardized * [ { slug, value, relatedPubId } ] */ -const normalizePubValues = ( - pubValues: Record +export const normalizePubValues = ( + pubValues: Record ) => { return Object.entries(pubValues).flatMap(([slug, value]) => isRelatedPubInit(value) ? value.map((v) => ({ slug, value: v.value, relatedPubId: v.relatedPubId })) : ([{ slug, value, relatedPubId: undefined }] as { slug: string; - value: unknown; + value: T; relatedPubId: PubsId | undefined; }[]) ); @@ -389,7 +285,7 @@ export const createPubRecursiveNew = async , }); values = processedVals; } @@ -461,6 +357,7 @@ export const createPubRecursiveNew = async () ).execute() : []; @@ -693,11 +590,11 @@ export const validatePubValues = async error).join(" ")); }; -type AddPubRelationsInput = { value: unknown; slug: string } & XOR< +type AddPubRelationsInput = { value: JsonValue | Date; slug: string } & XOR< { relatedPubId: PubsId }, { relatedPub: CreatePubRequestBodyWithNullsNew } >; -type UpdatePubRelationsInput = { value: unknown; slug: string; relatedPubId: PubsId }; +type UpdatePubRelationsInput = { value: JsonValue | Date; slug: string; relatedPubId: PubsId }; type RemovePubRelationsInput = { value?: never; slug: string; relatedPubId: PubsId }; @@ -790,12 +687,7 @@ export const upsertPubRelations = async ( relatedPubId: expect(newlyCreatedPubs[index].id), })); - const allRelationsToCreate = [...newPubsWithRelatedPubId, ...existingPubs] as { - relatedPubId: PubsId; - value: unknown; - slug: string; - fieldId: PubFieldsId; - }[]; + const allRelationsToCreate = [...newPubsWithRelatedPubId, ...existingPubs]; const pubRelations = await upsertPubRelationValues({ pubId, @@ -999,7 +891,13 @@ export const updatePub = async ({ const result = await maybeWithTrx(db, async (trx) => { // Update the stage if a target stage was provided. if (stageId !== undefined) { - await movePub(pubId, stageId, trx).execute(); + try { + await movePub(pubId, stageId, trx).execute(); + } catch (err) { + if (!isUniqueConstraintError(err)) { + throw err; + } + } } // Allow rich text fields to overwrite other fields @@ -1145,7 +1043,10 @@ const getRankedValues = async ({ valuesForField[0].pubId === pubId && valuesForField[0].fieldId === fieldId )?.rank ?? ""; - const ranks = mudder.base62.mudder(highestRank, "", valuesForField.length); + const ranks = findRanksBetween({ + start: highestRank, + numberOfRanks: valuesForField.length, + }); return valuesForField.map((value, i) => ({ ...value, rank: ranks[i] })); }) ); @@ -1220,12 +1121,12 @@ export const upsertPubRelationValues = async ({ allRelationsToCreate: { pubId?: PubsId; relatedPubId: PubsId; - value: unknown; + value: JsonValue | Date; fieldId: PubFieldsId; }[]; lastModifiedBy: LastModifiedBy; trx: typeof db; -}): Promise => { +}) => { if (!allRelationsToCreate.length) { return []; } @@ -1253,9 +1154,11 @@ export const upsertPubRelationValues = async ({ .doUpdateSet((eb) => ({ value: eb.ref("excluded.value"), lastModifiedBy: eb.ref("excluded.lastModifiedBy"), + rank: eb.ref("excluded.rank"), })) ) .returningAll() + .$narrowType<{ value: JsonValue }>() ).execute(); }; @@ -1749,6 +1652,7 @@ export async function getPubsWithRelatedValues() .whereRef("pub_values.pubId", "=", "pubs.id") .where( diff --git a/core/lib/server/pubtype.ts b/core/lib/server/pubtype.ts index 1afffa87b..75cd55135 100644 --- a/core/lib/server/pubtype.ts +++ b/core/lib/server/pubtype.ts @@ -76,8 +76,7 @@ export const getPubTypesForCommunity = async ( getPubTypeBase() .where("pub_types.communityId", "=", communityId) .orderBy(orderBy, orderDirection) - .limit(limit) - .offset(offset) + .$if(limit !== 0, (qb) => qb.limit(limit).offset(offset)) ).execute(); export type GetPubTypesResult = Prettify>>; diff --git a/core/lib/server/stages.ts b/core/lib/server/stages.ts index 310fe3d48..88dfc93e4 100644 --- a/core/lib/server/stages.ts +++ b/core/lib/server/stages.ts @@ -172,8 +172,5 @@ export const movePub = (pubId: PubsId, stageId: StagesId, trx = db) => { .with("leave_stage", (db) => db.deleteFrom("PubsInStages").where("pubId", "=", pubId)) .insertInto("PubsInStages") .values([{ pubId, stageId }]) - // Without this on conflict clause, the db errors if this function is called with the - // stageId the pub already belongs to - .onConflict((oc) => oc.doNothing()) ); }; diff --git a/core/package.json b/core/package.json index a202df3a3..b9472dda5 100644 --- a/core/package.json +++ b/core/package.json @@ -103,6 +103,7 @@ "jsonwebtoken": "^9.0.0", "katex": "catalog:", "kysely": "^0.27.5", + "lodash.isequalwith": "^4.4.0", "lodash.partition": "^4.6.0", "logger": "workspace:*", "lucia": "^3.2.2", @@ -160,6 +161,7 @@ "@testing-library/user-event": "^14.5.2", "@types/diacritics": "^1.3.1", "@types/jsonwebtoken": "^9.0.2", + "@types/lodash.isequalwith": "^4.4.9", "@types/lodash.partition": "^4.6.9", "@types/mdast": "^4.0.4", "@types/mudder": "^2.1.3", diff --git a/core/prisma/migrations/20250319022702_remove_order/migration.sql b/core/prisma/migrations/20250319022702_remove_order/migration.sql new file mode 100644 index 000000000..3c29fd5de --- /dev/null +++ b/core/prisma/migrations/20250319022702_remove_order/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - You are about to drop the column `order` on the `form_elements` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "form_elements" DROP COLUMN "order"; diff --git a/core/prisma/schema/schema.dbml b/core/prisma/schema/schema.dbml index af27c373a..ae94d7a4b 100644 --- a/core/prisma/schema/schema.dbml +++ b/core/prisma/schema/schema.dbml @@ -383,7 +383,6 @@ Table form_elements { fieldId String field pub_fields formId String [not null] - order Int rank String [not null] label String element StructuralFormElement diff --git a/core/prisma/schema/schema.prisma b/core/prisma/schema/schema.prisma index dea08c43e..7af736c63 100644 --- a/core/prisma/schema/schema.prisma +++ b/core/prisma/schema/schema.prisma @@ -543,7 +543,6 @@ model FormElement { fieldId String? field PubField? @relation(fields: [fieldId], references: [id], onDelete: Cascade) formId String - order Int? rank String // Uses "C" collation // label is only used by elements with type: ElementType.button. Pubfield inputs put everything in config label String? diff --git a/core/prisma/seed/seedCommunity.ts b/core/prisma/seed/seedCommunity.ts index 6becf3160..59820e674 100644 --- a/core/prisma/seed/seedCommunity.ts +++ b/core/prisma/seed/seedCommunity.ts @@ -7,7 +7,6 @@ import type { import { faker } from "@faker-js/faker"; import { jsonArrayFrom } from "kysely/helpers/postgres"; -import mudder from "mudder"; import type { ProcessedPub } from "contracts"; import type { @@ -46,6 +45,7 @@ import type { actions } from "~/actions/api"; import { db } from "~/kysely/database"; import { createPasswordHash } from "~/lib/authentication/password"; import { createLastModifiedBy } from "~/lib/lastModifiedBy"; +import { findRanksBetween } from "~/lib/rank"; import { createPubRecursiveNew } from "~/lib/server"; import { allPermissions, createApiAccessToken } from "~/lib/server/apiAccessTokens"; import { insertForm } from "~/lib/server/form"; @@ -1069,11 +1069,9 @@ export async function seedCommunity< .insertInto("form_elements") .values((eb) => formList.flatMap(([formTitle, formInput], idx) => { - const ranks = mudder.base62.mudder( - "", - "", - formInput.elements.length - ); + const ranks = findRanksBetween({ + numberOfRanks: formInput.elements.length, + }); return formInput.elements.map((elementInput, elementIndex) => ({ formId: eb .selectFrom("form") diff --git a/packages/contracts/src/resources/site.ts b/packages/contracts/src/resources/site.ts index dad22d2f9..67308002b 100644 --- a/packages/contracts/src/resources/site.ts +++ b/packages/contracts/src/resources/site.ts @@ -33,12 +33,12 @@ import { usersSchema, } from "db/public"; -import type { Json } from "./types"; +import type { Json, JsonValue } from "./types"; import { CreatePubRequestBodyWithNulls, jsonSchema } from "./types"; export type CreatePubRequestBodyWithNullsNew = z.infer & { stageId?: StagesId; - relatedPubs?: Record; + relatedPubs?: Record; members?: Record; }; @@ -47,12 +47,16 @@ export const safeUserSchema = usersSchema.omit({ passwordHash: true }).strict(); const CreatePubRequestBodyWithNullsWithStageId = CreatePubRequestBodyWithNulls.extend({ stageId: stagesIdSchema.optional(), values: z.record( - jsonSchema.or( - z.object({ - value: jsonSchema, - relatedPubId: pubsIdSchema, - }) - ) + jsonSchema + .or( + z.array( + z.object({ + value: jsonSchema.or(z.date()), + relatedPubId: pubsIdSchema, + }) + ) + ) + .or(z.date()) ), members: ( z.record(usersIdSchema, memberRoleSchema) as z.ZodType> @@ -64,7 +68,12 @@ export const CreatePubRequestBodyWithNullsNew: z.ZodType z.record( - z.array(z.object({ value: jsonSchema, pub: CreatePubRequestBodyWithNullsNew })) + z.array( + z.object({ + value: jsonSchema.or(z.date()), + pub: CreatePubRequestBodyWithNullsNew, + }) + ) ) ) .optional(), @@ -76,10 +85,10 @@ const upsertPubRelationsSchema = z.record( z.array( z.union([ z.object({ - value: jsonSchema, + value: jsonSchema.or(z.date()), relatedPub: CreatePubRequestBodyWithNullsNew, }), - z.object({ value: jsonSchema, relatedPubId: pubsIdSchema }), + z.object({ value: jsonSchema.or(z.date()), relatedPubId: pubsIdSchema }), ]) ) ); @@ -188,7 +197,7 @@ export type MaybePubOptions = { type ValueBase = { id: PubValuesId; fieldId: PubFieldsId; - value: unknown; + value: JsonValue; createdAt: Date; updatedAt: Date; /** @@ -197,6 +206,7 @@ type ValueBase = { schemaName: CoreSchemaType; fieldSlug: string; fieldName: string; + rank: string | null; }; type ProcessedPubBase = { @@ -257,6 +267,7 @@ const processedPubSchema: z.ZodType = z.object({ schemaName: coreSchemaTypeSchema, relatedPubId: pubsIdSchema.nullable(), relatedPub: z.lazy(() => processedPubSchema.nullish()), + rank: z.string().nullable(), }) ), createdAt: z.date(), diff --git a/packages/contracts/src/resources/types.ts b/packages/contracts/src/resources/types.ts index 2431f39d2..15112cb17 100644 --- a/packages/contracts/src/resources/types.ts +++ b/packages/contracts/src/resources/types.ts @@ -82,7 +82,10 @@ export type CreatePubRequestBody = z.infer; export const CreatePubRequestBodyWithNulls = commonPubFields.extend({ id: z.string().optional(), values: z.record( - z.union([jsonSchema, z.object({ value: jsonSchema, relatedPubId: pubsIdSchema }).array()]) + z.union([ + jsonSchema.or(z.date()), + z.object({ value: jsonSchema.or(z.date()), relatedPubId: pubsIdSchema }).array(), + ]) ), assigneeId: z.string().optional(), }); diff --git a/packages/db/src/public/FormElements.ts b/packages/db/src/public/FormElements.ts index af7a3d8e1..10225f0db 100644 --- a/packages/db/src/public/FormElements.ts +++ b/packages/db/src/public/FormElements.ts @@ -31,8 +31,6 @@ export interface FormElementsTable { formId: ColumnType; - order: ColumnType; - label: ColumnType; element: ColumnType< @@ -71,7 +69,6 @@ export const formElementsSchema = z.object({ type: elementTypeSchema, fieldId: pubFieldsIdSchema.nullable(), formId: formsIdSchema, - order: z.number().nullable(), label: z.string().nullable(), element: structuralFormElementSchema.nullable(), content: z.string().nullable(), @@ -89,7 +86,6 @@ export const formElementsInitializerSchema = z.object({ type: elementTypeSchema, fieldId: pubFieldsIdSchema.optional().nullable(), formId: formsIdSchema, - order: z.number().optional().nullable(), label: z.string().optional().nullable(), element: structuralFormElementSchema.optional().nullable(), content: z.string().optional().nullable(), @@ -107,7 +103,6 @@ export const formElementsMutatorSchema = z.object({ type: elementTypeSchema.optional(), fieldId: pubFieldsIdSchema.optional().nullable(), formId: formsIdSchema.optional(), - order: z.number().optional().nullable(), label: z.string().optional().nullable(), element: structuralFormElementSchema.optional().nullable(), content: z.string().optional().nullable(), diff --git a/packages/db/src/public/PublicSchema.ts b/packages/db/src/public/PublicSchema.ts index 62eb7d16d..164fb0f46 100644 --- a/packages/db/src/public/PublicSchema.ts +++ b/packages/db/src/public/PublicSchema.ts @@ -33,6 +33,34 @@ import type { StagesTable } from "./Stages"; import type { UsersTable } from "./Users"; export interface PublicSchema { + rules: RulesTable; + + action_runs: ActionRunsTable; + + forms: FormsTable; + + api_access_tokens: ApiAccessTokensTable; + + api_access_logs: ApiAccessLogsTable; + + api_access_permissions: ApiAccessPermissionsTable; + + form_elements: FormElementsTable; + + sessions: SessionsTable; + + community_memberships: CommunityMembershipsTable; + + pub_memberships: PubMembershipsTable; + + stage_memberships: StageMembershipsTable; + + form_memberships: FormMembershipsTable; + + membership_capabilities: MembershipCapabilitiesTable; + + pub_values_history: PubValuesHistoryTable; + _prisma_migrations: PrismaMigrationsTable; users: UsersTable; @@ -64,32 +92,4 @@ export interface PublicSchema { action_instances: ActionInstancesTable; PubsInStages: PubsInStagesTable; - - rules: RulesTable; - - action_runs: ActionRunsTable; - - forms: FormsTable; - - api_access_tokens: ApiAccessTokensTable; - - api_access_logs: ApiAccessLogsTable; - - api_access_permissions: ApiAccessPermissionsTable; - - form_elements: FormElementsTable; - - sessions: SessionsTable; - - community_memberships: CommunityMembershipsTable; - - pub_memberships: PubMembershipsTable; - - stage_memberships: StageMembershipsTable; - - form_memberships: FormMembershipsTable; - - membership_capabilities: MembershipCapabilitiesTable; - - pub_values_history: PubValuesHistoryTable; } diff --git a/packages/db/src/table-names.ts b/packages/db/src/table-names.ts index a98446c5b..ea4b4bd05 100644 --- a/packages/db/src/table-names.ts +++ b/packages/db/src/table-names.ts @@ -845,14 +845,6 @@ export const databaseTables = [ isAutoIncrementing: false, hasDefaultValue: false, }, - { - name: "order", - dataType: "int4", - dataTypeSchema: "pg_catalog", - isNullable: true, - isAutoIncrementing: false, - hasDefaultValue: false, - }, { name: "label", dataType: "text", diff --git a/packages/schemas/src/schemas.ts b/packages/schemas/src/schemas.ts index 88e745017..ada3ca0ae 100644 --- a/packages/schemas/src/schemas.ts +++ b/packages/schemas/src/schemas.ts @@ -117,11 +117,16 @@ export const getStringArrayWithMinMax = (config: unknown) => { }); }; -export const DateTime = Type.Date({ - description: "A moment in time", - examples: ["2021-01-01T00:00:00Z"], - error: "Invalid date", -}); +export const DateTime = Type.Union([ + Type.Date({ + description: "A moment in time", + examples: ["2021-01-01T00:00:00Z"], + error: "Invalid date", + }), + Type.Transform(Type.String()) + .Decode((value) => new Date(value)) + .Encode((value) => value.toISOString()), +]); export const Email = Type.String({ format: "email", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2f568fb2d..269e55631 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,8 +22,8 @@ catalogs: specifier: ^8.48.0 version: 8.48.0 '@sinclair/typebox': - specifier: ^0.32.34 - version: 0.32.35 + specifier: ^0.34.30 + version: 0.34.30 '@ts-rest/core': specifier: 3.51.0 version: 3.51.0 @@ -134,7 +134,7 @@ importers: version: link:config/prettier '@turbo/gen': specifier: ^2.1.1 - version: 2.1.1(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.5)(typescript@5.7.2) + version: 2.1.1(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.10)(typescript@5.7.2) concurrently: specifier: ^9.0.1 version: 9.0.1 @@ -300,7 +300,7 @@ importers: version: 8.48.0(@opentelemetry/core@1.30.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.0(@opentelemetry/api@1.9.0))(next@15.1.4(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(webpack@5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.15))) '@sinclair/typebox': specifier: 'catalog:' - version: 0.32.35 + version: 0.34.30 '@t3-oss/env-nextjs': specifier: ^0.11.1 version: 0.11.1(typescript@5.7.2)(zod@3.23.8) @@ -388,6 +388,9 @@ importers: kysely: specifier: ^0.27.5 version: 0.27.5 + lodash.isequalwith: + specifier: ^4.4.0 + version: 4.4.0 lodash.partition: specifier: ^4.6.0 version: 4.6.0 @@ -554,6 +557,9 @@ importers: '@types/jsonwebtoken': specifier: ^9.0.2 version: 9.0.6 + '@types/lodash.isequalwith': + specifier: ^4.4.9 + version: 4.4.9 '@types/lodash.partition': specifier: ^4.6.9 version: 4.6.9 @@ -655,10 +661,10 @@ importers: version: 15.1.4(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) nextra: specifier: ^4.2.13 - version: 4.2.16(acorn@8.14.0)(next@15.1.4(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + version: 4.2.16(acorn@8.14.1)(next@15.1.4(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) nextra-theme-docs: specifier: ^4.2.13 - version: 4.2.16(@types/react@19.0.6)(next@15.1.4(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(nextra@4.2.16(acorn@8.14.0)(next@15.1.4(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(use-sync-external-store@1.2.2(react@19.0.0)) + version: 4.2.16(@types/react@19.0.6)(next@15.1.4(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(nextra@4.2.16(acorn@8.14.1)(next@15.1.4(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(use-sync-external-store@1.2.2(react@19.0.0)) pagefind: specifier: ^1.3.0 version: 1.3.0 @@ -1074,7 +1080,7 @@ importers: version: link:../../config/prettier '@ts-rest/core': specifier: 'catalog:' - version: 3.51.0(@types/node@22.13.5)(zod@3.23.8) + version: 3.51.0(@types/node@22.13.10)(zod@3.23.8) '@types/pg': specifier: ^8.11.6 version: 8.11.8 @@ -1178,7 +1184,7 @@ importers: dependencies: '@sinclair/typebox': specifier: 'catalog:' - version: 0.32.35 + version: 0.34.30 db: specifier: workspace:* version: link:../db @@ -1203,7 +1209,7 @@ importers: version: 5.7.2 vitest: specifier: 'catalog:' - version: 3.0.5(@types/debug@4.1.12)(@types/node@22.13.5)(jsdom@25.0.1)(lightningcss@1.29.2)(terser@5.39.0) + version: 3.0.5(@types/debug@4.1.12)(@types/node@22.13.10)(jsdom@25.0.1)(lightningcss@1.29.2)(terser@5.39.0) packages/ui: dependencies: @@ -1317,7 +1323,7 @@ importers: version: 1.1.1(@types/react-dom@19.0.3(@types/react@19.0.6))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@tailwindcss/typography': specifier: ^0.5.10 - version: 0.5.15(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.5)(typescript@5.7.2))) + version: 0.5.15(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.10)(typescript@5.7.2))) '@tanstack/react-table': specifier: ^8.20.6 version: 8.20.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -1380,7 +1386,7 @@ importers: version: 2.5.2 tailwindcss-animate: specifier: ^1.0.6 - version: 1.0.7(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.5)(typescript@5.7.2))) + version: 1.0.7(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.10)(typescript@5.7.2))) utils: specifier: workspace:* version: link:../utils @@ -1405,7 +1411,7 @@ importers: version: 19.0.0 tailwindcss: specifier: 'catalog:' - version: 3.4.13(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.5)(typescript@5.7.2)) + version: 3.4.13(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.10)(typescript@5.7.2)) tsconfig: specifier: workspace:* version: link:../../config/tsconfig @@ -1470,7 +1476,7 @@ importers: version: 8.4.7(@storybook/test@8.4.7(storybook@8.4.7(prettier@3.4.2)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.4.7(prettier@3.4.2))(typescript@5.7.2) '@storybook/react-vite': specifier: ^8.4.7 - version: 8.4.7(@storybook/test@8.4.7(storybook@8.4.7(prettier@3.4.2)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(rollup@4.21.2)(storybook@8.4.7(prettier@3.4.2))(typescript@5.7.2)(vite@5.4.3(@types/node@22.13.5)(lightningcss@1.29.2)(terser@5.39.0))(webpack-sources@3.2.3) + version: 8.4.7(@storybook/test@8.4.7(storybook@8.4.7(prettier@3.4.2)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(rollup@4.21.2)(storybook@8.4.7(prettier@3.4.2))(typescript@5.7.2)(vite@5.4.3(@types/node@22.13.10)(lightningcss@1.29.2)(terser@5.39.0))(webpack-sources@3.2.3) '@storybook/test': specifier: ^8.4.7 version: 8.4.7(storybook@8.4.7(prettier@3.4.2)) @@ -1494,7 +1500,7 @@ importers: version: 8.4.7(prettier@3.4.2) tailwindcss: specifier: 'catalog:' - version: 3.4.13(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.5)(typescript@5.7.2)) + version: 3.4.13(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.10)(typescript@5.7.2)) tsconfig: specifier: workspace:* version: link:../config/tsconfig @@ -5919,8 +5925,8 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} - '@sinclair/typebox@0.32.35': - resolution: {integrity: sha512-Ul3YyOTU++to8cgNkttakC0dWvpERr6RYoHO2W47DLbFvrwBDJUY31B1sImH6JZSYc4Kt4PyHtoPNu+vL2r2dA==} + '@sinclair/typebox@0.34.30': + resolution: {integrity: sha512-gFB3BiqjDxEoadW0zn+xyMVb7cLxPCoblVn2C/BKpI41WPYi2d6fwHAlynPNZ5O/Q4WEiujdnJzVtvG/Jc2CBQ==} '@smithy/abort-controller@4.0.1': resolution: {integrity: sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g==} @@ -6389,8 +6395,8 @@ packages: '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} - '@swc/types@0.1.17': - resolution: {integrity: sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==} + '@swc/types@0.1.19': + resolution: {integrity: sha512-WkAZaAfj44kh/UFdAQcrMP1I0nwRqpt27u+08LMBYMqmQfwwMofYoMh/48NGkMMRfC4ynpfwRbJuu8ErfNloeA==} '@t3-oss/env-core@0.11.1': resolution: {integrity: sha512-MaxOwEoG1ntCFoKJsS7nqwgcxLW1SJw238AJwfJeaz3P/8GtkxXZsPPolsz1AdYvUTbe3XvqZ/VCdfjt+3zmKw==} @@ -6854,6 +6860,9 @@ packages: '@types/katex@0.16.7': resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==} + '@types/lodash.isequalwith@4.4.9': + resolution: {integrity: sha512-uLXidHwVTfYeaHRipKT/pjl10PFTGM0rHLyfANSSfEJvwtFmRACmQb2y6i8eZbcOCtX+BSxOKa+JzzCTxmQ32g==} + '@types/lodash.partition@4.6.9': resolution: {integrity: sha512-ANgnHyTw/C07oHr/8/jzoc1BlZZFRafAyDvc04Z8qR1IvWZpAGB8aHPUkd0UCgJWOauqoCsILhvPLXKsTc4rXQ==} @@ -6887,8 +6896,8 @@ packages: '@types/node@20.17.12': resolution: {integrity: sha512-vo/wmBgMIiEA23A/knMfn/cf37VnuF52nZh5ZoW0GWt4e4sxNquibrMRJ7UQsA06+MBx9r/H1jsI9grYjQCQlw==} - '@types/node@22.13.5': - resolution: {integrity: sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==} + '@types/node@22.13.10': + resolution: {integrity: sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==} '@types/nodemailer@6.4.15': resolution: {integrity: sha512-0EBJxawVNjPkng1zm2vopRctuWVCxk34JcIlRuXSf54habUWdz1FB7wHDqOqvDa8Mtpt0Q3LTXQkAs2LNyK5jQ==} @@ -7256,6 +7265,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.14.1: + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -10079,6 +10093,9 @@ packages: lodash.isboolean@3.0.3: resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + lodash.isequalwith@4.4.0: + resolution: {integrity: sha512-dcZON0IalGBpRmJBmMkaoV7d3I80R2O+FrzsZyHdNSFrANq/cgDqKQNmAHE8UEj4+QYWwwhkQOVdLHiAopzlsQ==} + lodash.isinteger@4.0.4: resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} @@ -12391,8 +12408,8 @@ packages: resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} engines: {node: '>=8'} - terser-webpack-plugin@5.3.11: - resolution: {integrity: sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==} + terser-webpack-plugin@5.3.14: + resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==} engines: {node: '>= 10.13.0'} peerDependencies: '@swc/core': '*' @@ -15524,11 +15541,11 @@ snapshots: optionalDependencies: typescript: 5.7.2 - '@joshwooding/vite-plugin-react-docgen-typescript@0.4.2(typescript@5.7.2)(vite@5.4.3(@types/node@22.13.5)(lightningcss@1.29.2)(terser@5.39.0))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.4.2(typescript@5.7.2)(vite@5.4.3(@types/node@22.13.10)(lightningcss@1.29.2)(terser@5.39.0))': dependencies: magic-string: 0.27.0 react-docgen-typescript: 2.2.2(typescript@5.7.2) - vite: 5.4.3(@types/node@22.13.5)(lightningcss@1.29.2)(terser@5.39.0) + vite: 5.4.3(@types/node@22.13.10)(lightningcss@1.29.2)(terser@5.39.0) optionalDependencies: typescript: 5.7.2 @@ -15802,7 +15819,7 @@ snapshots: '@marijn/find-cluster-break@1.0.2': {} - '@mdx-js/mdx@3.1.0(acorn@8.14.0)': + '@mdx-js/mdx@3.1.0(acorn@8.14.1)': dependencies: '@types/estree': 1.0.6 '@types/estree-jsx': 1.0.5 @@ -15816,7 +15833,7 @@ snapshots: hast-util-to-jsx-runtime: 2.3.0 markdown-extensions: 2.0.0 recma-build-jsx: 1.0.0 - recma-jsx: 1.0.0(acorn@8.14.0) + recma-jsx: 1.0.0(acorn@8.14.1) recma-stringify: 1.0.0 rehype-recma: 1.0.0 remark-mdx: 3.1.0 @@ -18808,7 +18825,7 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} - '@sinclair/typebox@0.32.35': {} + '@sinclair/typebox@0.34.30': {} '@smithy/abort-controller@4.0.1': dependencies: @@ -19278,13 +19295,13 @@ snapshots: transitivePeerDependencies: - webpack-sources - '@storybook/builder-vite@8.4.7(storybook@8.4.7(prettier@3.4.2))(vite@5.4.3(@types/node@22.13.5)(lightningcss@1.29.2)(terser@5.39.0))(webpack-sources@3.2.3)': + '@storybook/builder-vite@8.4.7(storybook@8.4.7(prettier@3.4.2))(vite@5.4.3(@types/node@22.13.10)(lightningcss@1.29.2)(terser@5.39.0))(webpack-sources@3.2.3)': dependencies: '@storybook/csf-plugin': 8.4.7(storybook@8.4.7(prettier@3.4.2))(webpack-sources@3.2.3) browser-assert: 1.2.1 storybook: 8.4.7(prettier@3.4.2) ts-dedent: 2.2.0 - vite: 5.4.3(@types/node@22.13.5)(lightningcss@1.29.2)(terser@5.39.0) + vite: 5.4.3(@types/node@22.13.10)(lightningcss@1.29.2)(terser@5.39.0) transitivePeerDependencies: - webpack-sources @@ -19383,11 +19400,11 @@ snapshots: - typescript - webpack-sources - '@storybook/react-vite@8.4.7(@storybook/test@8.4.7(storybook@8.4.7(prettier@3.4.2)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(rollup@4.21.2)(storybook@8.4.7(prettier@3.4.2))(typescript@5.7.2)(vite@5.4.3(@types/node@22.13.5)(lightningcss@1.29.2)(terser@5.39.0))(webpack-sources@3.2.3)': + '@storybook/react-vite@8.4.7(@storybook/test@8.4.7(storybook@8.4.7(prettier@3.4.2)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(rollup@4.21.2)(storybook@8.4.7(prettier@3.4.2))(typescript@5.7.2)(vite@5.4.3(@types/node@22.13.10)(lightningcss@1.29.2)(terser@5.39.0))(webpack-sources@3.2.3)': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.4.2(typescript@5.7.2)(vite@5.4.3(@types/node@22.13.5)(lightningcss@1.29.2)(terser@5.39.0)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.4.2(typescript@5.7.2)(vite@5.4.3(@types/node@22.13.10)(lightningcss@1.29.2)(terser@5.39.0)) '@rollup/pluginutils': 5.1.0(rollup@4.21.2) - '@storybook/builder-vite': 8.4.7(storybook@8.4.7(prettier@3.4.2))(vite@5.4.3(@types/node@22.13.5)(lightningcss@1.29.2)(terser@5.39.0))(webpack-sources@3.2.3) + '@storybook/builder-vite': 8.4.7(storybook@8.4.7(prettier@3.4.2))(vite@5.4.3(@types/node@22.13.10)(lightningcss@1.29.2)(terser@5.39.0))(webpack-sources@3.2.3) '@storybook/react': 8.4.7(@storybook/test@8.4.7(storybook@8.4.7(prettier@3.4.2)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.4.7(prettier@3.4.2))(typescript@5.7.2) find-up: 5.0.0 magic-string: 0.30.17 @@ -19397,7 +19414,7 @@ snapshots: resolve: 1.22.8 storybook: 8.4.7(prettier@3.4.2) tsconfig-paths: 4.2.0 - vite: 5.4.3(@types/node@22.13.5)(lightningcss@1.29.2)(terser@5.39.0) + vite: 5.4.3(@types/node@22.13.10)(lightningcss@1.29.2)(terser@5.39.0) transitivePeerDependencies: - '@storybook/test' - rollup @@ -19469,7 +19486,7 @@ snapshots: '@swc/core@1.7.24(@swc/helpers@0.5.15)': dependencies: '@swc/counter': 0.1.3 - '@swc/types': 0.1.17 + '@swc/types': 0.1.19 optionalDependencies: '@swc/core-darwin-arm64': 1.7.24 '@swc/core-darwin-x64': 1.7.24 @@ -19494,7 +19511,7 @@ snapshots: dependencies: tslib: 2.8.1 - '@swc/types@0.1.17': + '@swc/types@0.1.19': dependencies: '@swc/counter': 0.1.3 optional: true @@ -19587,13 +19604,13 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 3.4.13(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@20.17.12)(typescript@5.7.2)) - '@tailwindcss/typography@0.5.15(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.5)(typescript@5.7.2)))': + '@tailwindcss/typography@0.5.15(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.10)(typescript@5.7.2)))': dependencies: lodash.castarray: 4.4.0 lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 postcss-selector-parser: 6.0.10 - tailwindcss: 3.4.13(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.5)(typescript@5.7.2)) + tailwindcss: 3.4.13(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.10)(typescript@5.7.2)) '@tanstack/query-core@5.60.6': {} @@ -19693,9 +19710,9 @@ snapshots: '@types/node': 20.17.12 zod: 3.23.8 - '@ts-rest/core@3.51.0(@types/node@22.13.5)(zod@3.23.8)': + '@ts-rest/core@3.51.0(@types/node@22.13.10)(zod@3.23.8)': optionalDependencies: - '@types/node': 22.13.5 + '@types/node': 22.13.10 zod: 3.23.8 '@ts-rest/next@3.51.0(@ts-rest/core@3.51.0(@types/node@20.17.12)(zod@3.23.8))(next@15.1.4(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(zod@3.23.8)': @@ -19737,7 +19754,7 @@ snapshots: '@tsconfig/node16@1.0.4': {} - '@turbo/gen@2.1.1(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.5)(typescript@5.7.2)': + '@turbo/gen@2.1.1(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.10)(typescript@5.7.2)': dependencies: '@turbo/workspaces': 2.1.1 commander: 10.0.1 @@ -19747,7 +19764,7 @@ snapshots: node-plop: 0.26.3 picocolors: 1.0.1 proxy-agent: 6.4.0 - ts-node: 10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.5)(typescript@5.7.2) + ts-node: 10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.10)(typescript@5.7.2) update-check: 1.5.4 validate-npm-package-name: 5.0.1 transitivePeerDependencies: @@ -20012,6 +20029,10 @@ snapshots: '@types/katex@0.16.7': {} + '@types/lodash.isequalwith@4.4.9': + dependencies: + '@types/lodash': 4.17.7 + '@types/lodash.partition@4.6.9': dependencies: '@types/lodash': 4.17.7 @@ -20046,7 +20067,7 @@ snapshots: dependencies: undici-types: 6.19.8 - '@types/node@22.13.5': + '@types/node@22.13.10': dependencies: undici-types: 6.20.0 @@ -20361,13 +20382,13 @@ snapshots: optionalDependencies: vite: 5.4.3(@types/node@20.17.12)(lightningcss@1.29.2)(terser@5.39.0) - '@vitest/mocker@3.0.5(vite@5.4.3(@types/node@22.13.5)(lightningcss@1.29.2)(terser@5.39.0))': + '@vitest/mocker@3.0.5(vite@5.4.3(@types/node@22.13.10)(lightningcss@1.29.2)(terser@5.39.0))': dependencies: '@vitest/spy': 3.0.5 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 5.4.3(@types/node@22.13.5)(lightningcss@1.29.2)(terser@5.39.0) + vite: 5.4.3(@types/node@22.13.10)(lightningcss@1.29.2)(terser@5.39.0) '@vitest/pretty-format@2.0.5': dependencies: @@ -20510,17 +20531,21 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 - acorn-import-assertions@1.9.0(acorn@8.14.0): + acorn-import-assertions@1.9.0(acorn@8.14.1): dependencies: - acorn: 8.14.0 + acorn: 8.14.1 acorn-import-attributes@1.9.5(acorn@8.14.0): dependencies: acorn: 8.14.0 - acorn-jsx@5.3.2(acorn@8.14.0): + acorn-import-attributes@1.9.5(acorn@8.14.1): dependencies: - acorn: 8.14.0 + acorn: 8.14.1 + + acorn-jsx@5.3.2(acorn@8.14.1): + dependencies: + acorn: 8.14.1 acorn-walk@8.3.4: dependencies: @@ -20528,6 +20553,8 @@ snapshots: acorn@8.14.0: {} + acorn@8.14.1: {} + agent-base@6.0.2: dependencies: debug: 4.4.0 @@ -21899,7 +21926,7 @@ snapshots: esast-util-from-js@2.0.1: dependencies: '@types/estree-jsx': 1.0.5 - acorn: 8.14.0 + acorn: 8.14.1 esast-util-from-estree: 2.0.0 vfile-message: 4.0.2 @@ -22192,8 +22219,8 @@ snapshots: espree@10.1.0: dependencies: - acorn: 8.14.0 - acorn-jsx: 5.3.2(acorn@8.14.0) + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) eslint-visitor-keys: 4.2.0 esprima@1.2.2: {} @@ -23126,8 +23153,8 @@ snapshots: import-in-the-middle@1.7.1: dependencies: - acorn: 8.14.0 - acorn-import-assertions: 1.9.0(acorn@8.14.0) + acorn: 8.14.1 + acorn-import-assertions: 1.9.0(acorn@8.14.1) cjs-module-lexer: 1.4.1 module-details-from-path: 1.0.3 @@ -23833,6 +23860,8 @@ snapshots: lodash.isboolean@3.0.3: {} + lodash.isequalwith@4.4.0: {} + lodash.isinteger@4.0.4: {} lodash.isnumber@3.0.3: {} @@ -24354,8 +24383,8 @@ snapshots: micromark-extension-mdxjs@3.0.0: dependencies: - acorn: 8.14.0 - acorn-jsx: 5.3.2(acorn@8.14.0) + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) micromark-extension-mdx-expression: 3.0.0 micromark-extension-mdx-jsx: 3.0.1 micromark-extension-mdx-md: 2.0.0 @@ -24568,7 +24597,7 @@ snapshots: mlly@1.7.4: dependencies: - acorn: 8.14.0 + acorn: 8.14.1 pathe: 2.0.3 pkg-types: 1.3.1 ufo: 1.5.4 @@ -24677,13 +24706,13 @@ snapshots: - '@babel/core' - babel-plugin-macros - nextra-theme-docs@4.2.16(@types/react@19.0.6)(next@15.1.4(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(nextra@4.2.16(acorn@8.14.0)(next@15.1.4(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(use-sync-external-store@1.2.2(react@19.0.0)): + nextra-theme-docs@4.2.16(@types/react@19.0.6)(next@15.1.4(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(nextra@4.2.16(acorn@8.14.1)(next@15.1.4(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(use-sync-external-store@1.2.2(react@19.0.0)): dependencies: '@headlessui/react': 2.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) clsx: 2.1.1 next: 15.1.4(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) next-themes: 0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - nextra: 4.2.16(acorn@8.14.0)(next@15.1.4(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + nextra: 4.2.16(acorn@8.14.1)(next@15.1.4(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) react: 19.0.0 react-compiler-runtime: 0.0.0-experimental-22c6e49-20241219(react@19.0.0) react-dom: 19.0.0(react@19.0.0) @@ -24696,11 +24725,11 @@ snapshots: - immer - use-sync-external-store - nextra@4.2.16(acorn@8.14.0)(next@15.1.4(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2): + nextra@4.2.16(acorn@8.14.1)(next@15.1.4(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.51.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2): dependencies: '@formatjs/intl-localematcher': 0.6.0 '@headlessui/react': 2.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@mdx-js/mdx': 3.1.0(acorn@8.14.0) + '@mdx-js/mdx': 3.1.0(acorn@8.14.1) '@napi-rs/simple-git': 0.1.19 '@shikijs/twoslash': 2.5.0(typescript@5.7.2) '@theguild/remark-mermaid': 0.2.0(react@19.0.0) @@ -25328,13 +25357,13 @@ snapshots: postcss: 8.4.49 ts-node: 10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@20.17.12)(typescript@5.7.2) - postcss-load-config@4.0.2(postcss@8.4.49)(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.5)(typescript@5.7.2)): + postcss-load-config@4.0.2(postcss@8.4.49)(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.10)(typescript@5.7.2)): dependencies: lilconfig: 3.1.3 yaml: 2.5.1 optionalDependencies: postcss: 8.4.49 - ts-node: 10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.5)(typescript@5.7.2) + ts-node: 10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.10)(typescript@5.7.2) postcss-nested@6.2.0(postcss@8.4.49): dependencies: @@ -25935,9 +25964,9 @@ snapshots: estree-util-build-jsx: 3.0.1 vfile: 6.0.3 - recma-jsx@1.0.0(acorn@8.14.0): + recma-jsx@1.0.0(acorn@8.14.1): dependencies: - acorn-jsx: 5.3.2(acorn@8.14.0) + acorn-jsx: 5.3.2(acorn@8.14.1) estree-util-to-js: 2.0.0 recma-parse: 1.0.0 recma-stringify: 1.0.0 @@ -26882,9 +26911,9 @@ snapshots: tailwind-merge@2.5.2: {} - tailwindcss-animate@1.0.7(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.5)(typescript@5.7.2))): + tailwindcss-animate@1.0.7(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.10)(typescript@5.7.2))): dependencies: - tailwindcss: 3.4.13(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.5)(typescript@5.7.2)) + tailwindcss: 3.4.13(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.10)(typescript@5.7.2)) tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@20.17.12)(typescript@5.7.2)): dependencies: @@ -26913,7 +26942,7 @@ snapshots: transitivePeerDependencies: - ts-node - tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.5)(typescript@5.7.2)): + tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.10)(typescript@5.7.2)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -26932,7 +26961,7 @@ snapshots: postcss: 8.4.49 postcss-import: 15.1.0(postcss@8.4.49) postcss-js: 4.0.1(postcss@8.4.49) - postcss-load-config: 4.0.2(postcss@8.4.49)(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.5)(typescript@5.7.2)) + postcss-load-config: 4.0.2(postcss@8.4.49)(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.10)(typescript@5.7.2)) postcss-nested: 6.2.0(postcss@8.4.49) postcss-selector-parser: 6.1.2 resolve: 1.22.8 @@ -26979,7 +27008,7 @@ snapshots: ansi-escapes: 4.3.2 supports-hyperlinks: 2.3.0 - terser-webpack-plugin@5.3.11(@swc/core@1.7.24(@swc/helpers@0.5.15))(webpack@5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.15))): + terser-webpack-plugin@5.3.14(@swc/core@1.7.24(@swc/helpers@0.5.15))(webpack@5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.15))): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 @@ -27000,7 +27029,7 @@ snapshots: terser@5.39.0: dependencies: '@jridgewell/source-map': 0.3.6 - acorn: 8.14.0 + acorn: 8.14.1 commander: 2.20.3 source-map-support: 0.5.21 @@ -27132,14 +27161,14 @@ snapshots: optionalDependencies: '@swc/core': 1.7.24(@swc/helpers@0.5.15) - ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.5)(typescript@5.7.2): + ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.15))(@types/node@22.13.10)(typescript@5.7.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 22.13.5 + '@types/node': 22.13.10 acorn: 8.14.0 acorn-walk: 8.3.4 arg: 4.1.3 @@ -27423,14 +27452,14 @@ snapshots: unplugin@1.0.1: dependencies: - acorn: 8.14.0 + acorn: 8.14.1 chokidar: 3.6.0 webpack-sources: 3.2.3 webpack-virtual-modules: 0.5.0 unplugin@1.14.1(webpack-sources@3.2.3): dependencies: - acorn: 8.14.0 + acorn: 8.14.1 webpack-virtual-modules: 0.6.2 optionalDependencies: webpack-sources: 3.2.3 @@ -27555,13 +27584,13 @@ snapshots: - supports-color - terser - vite-node@3.0.5(@types/node@22.13.5)(lightningcss@1.29.2)(terser@5.39.0): + vite-node@3.0.5(@types/node@22.13.10)(lightningcss@1.29.2)(terser@5.39.0): dependencies: cac: 6.7.14 debug: 4.4.0 es-module-lexer: 1.6.0 pathe: 2.0.3 - vite: 5.4.3(@types/node@22.13.5)(lightningcss@1.29.2)(terser@5.39.0) + vite: 5.4.3(@types/node@22.13.10)(lightningcss@1.29.2)(terser@5.39.0) transitivePeerDependencies: - '@types/node' - less @@ -27595,13 +27624,13 @@ snapshots: lightningcss: 1.29.2 terser: 5.39.0 - vite@5.4.3(@types/node@22.13.5)(lightningcss@1.29.2)(terser@5.39.0): + vite@5.4.3(@types/node@22.13.10)(lightningcss@1.29.2)(terser@5.39.0): dependencies: esbuild: 0.21.5 postcss: 8.4.49 rollup: 4.21.2 optionalDependencies: - '@types/node': 22.13.5 + '@types/node': 22.13.10 fsevents: 2.3.3 lightningcss: 1.29.2 terser: 5.39.0 @@ -27643,10 +27672,10 @@ snapshots: - supports-color - terser - vitest@3.0.5(@types/debug@4.1.12)(@types/node@22.13.5)(jsdom@25.0.1)(lightningcss@1.29.2)(terser@5.39.0): + vitest@3.0.5(@types/debug@4.1.12)(@types/node@22.13.10)(jsdom@25.0.1)(lightningcss@1.29.2)(terser@5.39.0): dependencies: '@vitest/expect': 3.0.5 - '@vitest/mocker': 3.0.5(vite@5.4.3(@types/node@22.13.5)(lightningcss@1.29.2)(terser@5.39.0)) + '@vitest/mocker': 3.0.5(vite@5.4.3(@types/node@22.13.10)(lightningcss@1.29.2)(terser@5.39.0)) '@vitest/pretty-format': 3.0.5 '@vitest/runner': 3.0.5 '@vitest/snapshot': 3.0.5 @@ -27662,12 +27691,12 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.0.2 tinyrainbow: 2.0.0 - vite: 5.4.3(@types/node@22.13.5)(lightningcss@1.29.2)(terser@5.39.0) - vite-node: 3.0.5(@types/node@22.13.5)(lightningcss@1.29.2)(terser@5.39.0) + vite: 5.4.3(@types/node@22.13.10)(lightningcss@1.29.2)(terser@5.39.0) + vite-node: 3.0.5(@types/node@22.13.10)(lightningcss@1.29.2)(terser@5.39.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 - '@types/node': 22.13.5 + '@types/node': 22.13.10 jsdom: 25.0.1 transitivePeerDependencies: - less @@ -27730,8 +27759,8 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.14.0 - acorn-import-attributes: 1.9.5(acorn@8.14.0) + acorn: 8.14.1 + acorn-import-attributes: 1.9.5(acorn@8.14.1) browserslist: 4.24.4 chrome-trace-event: 1.0.4 enhanced-resolve: 5.18.1 @@ -27746,7 +27775,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.11(@swc/core@1.7.24(@swc/helpers@0.5.15))(webpack@5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.15))) + terser-webpack-plugin: 5.3.14(@swc/core@1.7.24(@swc/helpers@0.5.15))(webpack@5.94.0(@swc/core@1.7.24(@swc/helpers@0.5.15))) watchpack: 2.4.2 webpack-sources: 3.2.3 transitivePeerDependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f6b7a291b..ec57466fd 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -25,7 +25,7 @@ catalog: react-hook-form: ^7.54.2 "@hookform/resolvers": ^3.9.1 "@playwright/test": ^1.49.0 - "@sinclair/typebox": ^0.32.34 + "@sinclair/typebox": ^0.34.30 "@types/node": ^20 "@honeycombio/opentelemetry-node": ^0.6.1 "@opentelemetry/auto-instrumentations-node": 0.53.0