Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 33 additions & 18 deletions client/packages/cli/src/renderSchemaPlan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,25 +51,40 @@ const renderLinkUpdate = (

reverseLabel = ` <-> ${oldAttr['reverse-identity']![1]}.${oldAttr['reverse-identity']![2]}`;

if (!oldAttr['on-delete'] && tx.partialAttr['on-delete']) {
details.push(
`(SET ON DELETE ${oldAttr['reverse-identity']![1]} CASCADE DELETE ${oldAttr['forward-identity'][1]})`,
);
}
if (!oldAttr['on-delete-reverse'] && tx.partialAttr['on-delete-reverse']) {
details.push(
`(SET ON DELETE ${oldAttr['forward-identity']![1]} CASCADE DELETE ${oldAttr['reverse-identity']![1]})`,
);
}
if (oldAttr['on-delete'] && !oldAttr['on-delete-reverse']) {
details.push(
`(REMOVE CASCADE DELETE ON ${oldAttr['reverse-identity']![1]})`,
);
const prevOnDelete = oldAttr['on-delete'];
const newOnDelete = tx.partialAttr['on-delete'];

if (prevOnDelete != newOnDelete) {
if (prevOnDelete) {
const action = prevOnDelete.toUpperCase();
details.push(
`(REMOVE ${action} DELETE ON ${oldAttr['reverse-identity']![1]})`,
);
}
if (newOnDelete) {
const action = newOnDelete.toUpperCase();
details.push(
`(SET ON DELETE ${oldAttr['reverse-identity']![1]} ${action} DELETE ${oldAttr['forward-identity'][1]})`,
);
}
}
if (oldAttr['on-delete-reverse'] && !oldAttr['on-delete-reverse']) {
details.push(
`(REMOVE REVERSE CASCADE DELETE ON ${oldAttr['forward-identity'][1]})`,
);

const prevOnDeleteReverse = oldAttr['on-delete-reverse'];
const newOnDeleteReverse = tx.partialAttr['on-delete-reverse'];

if (prevOnDeleteReverse != newOnDeleteReverse) {
if (prevOnDeleteReverse) {
const action = prevOnDeleteReverse.toUpperCase();
details.push(
`(REMOVE REVERSE ${action} DELETE ON ${oldAttr['reverse-identity']![1]})`,
);
}
if (newOnDeleteReverse) {
const action = newOnDeleteReverse.toUpperCase();
details.push(
`(SET ON DELETE ${oldAttr['forward-identity']![1]} ${action} DELETE ${oldAttr['reverse-identity']![1]})`,
);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
ToggleGroup,
} from '@lib/components/ui';
import {
OnDelete,
RelationshipKinds,
relationshipConstraints,
relationshipConstraintsInverse,
Expand Down Expand Up @@ -360,8 +361,8 @@ function AddAttrForm({
const [isRequired, setIsRequired] = useState(false);
const [isIndex, setIsIndex] = useState(false);
const [isUniq, setIsUniq] = useState(false);
const [isCascade, setIsCascade] = useState(false);
const [isCascadeReverse, setIsCascadeReverse] = useState(false);
const [onDelete, setOnDelete] = useState<OnDelete>(null);
const [onDeleteReverse, setOnDeleteReverse] = useState<OnDelete>(null);
const [checkedDataType, setCheckedDataType] =
useState<CheckedDataType | null>(null);
const [attrType, setAttrType] = useState<'blob' | 'ref'>('blob');
Expand All @@ -374,9 +375,9 @@ function AddAttrForm({
const [attrName, setAttrName] = useState('');
const [reverseAttrName, setReverseAttrName] = useState(namespace.name);

const isCascadeAllowed =
const isOnDeleteAllowed =
relationship === 'one-one' || relationship === 'one-many';
const isCascadeReverseAllowed =
const isOnDeleteReverseAllowed =
relationship === 'one-one' || relationship === 'many-one';

const linkValidation = validateLink({
Expand Down Expand Up @@ -428,9 +429,10 @@ function AddAttrForm({
'value-type': 'ref',
'index?': false,
'required?': isRequired,
'on-delete': isCascadeAllowed && isCascade ? 'cascade' : undefined,
'on-delete-reverse':
isCascadeReverseAllowed && isCascadeReverse ? 'cascade' : undefined,
'on-delete': isOnDeleteAllowed ? onDelete : undefined,
'on-delete-reverse': isOnDeleteReverseAllowed
? onDeleteReverse
: undefined,
};

const ops = [['add-attr', attr]];
Expand Down Expand Up @@ -578,12 +580,12 @@ function AddAttrForm({
setAttrName={setAttrName}
setReverseAttrName={setReverseAttrName}
setRelationship={setRelationship}
isCascadeAllowed={isCascadeAllowed}
isCascade={isCascade}
setIsCascade={setIsCascade}
isCascadeReverseAllowed={isCascadeReverseAllowed}
isCascadeReverse={isCascadeReverse}
setIsCascadeReverse={setIsCascadeReverse}
isOnDeleteAllowed={isOnDeleteAllowed}
onDelete={onDelete}
setOnDelete={setOnDelete}
isOnDeleteReverseAllowed={isOnDeleteReverseAllowed}
onDeleteReverse={onDeleteReverse}
setOnDeleteReverse={setOnDeleteReverse}
isRequired={isRequired}
setIsRequired={setIsRequired}
constraints={constraints}
Expand Down Expand Up @@ -834,12 +836,12 @@ function RelationshipConfigurator({
setAttrName,
setReverseAttrName,
setRelationship,
isCascade,
setIsCascade,
isCascadeAllowed,
isCascadeReverse,
setIsCascadeReverse,
isCascadeReverseAllowed,
onDelete,
setOnDelete,
isOnDeleteAllowed,
onDeleteReverse,
setOnDeleteReverse,
isOnDeleteReverseAllowed,
isRequired,
setIsRequired,
constraints,
Expand All @@ -854,13 +856,13 @@ function RelationshipConfigurator({
setReverseAttrName: (n: string) => void;
setRelationship: (n: RelationshipKinds) => void;

isCascadeAllowed: boolean;
isCascade: boolean;
setIsCascade: (n: boolean) => void;
isOnDeleteAllowed: boolean;
onDelete: OnDelete;
setOnDelete: (n: OnDelete) => void;

isCascadeReverseAllowed: boolean;
isCascadeReverse: boolean;
setIsCascadeReverse: (n: boolean) => void;
isOnDeleteReverseAllowed: boolean;
onDeleteReverse: OnDelete;
setOnDeleteReverse: (n: OnDelete) => void;

isRequired: boolean;
setIsRequired: (n: boolean) => void;
Expand Down Expand Up @@ -952,9 +954,11 @@ function RelationshipConfigurator({

<div className="flex gap-2">
<Checkbox
checked={isCascadeAllowed && isCascade}
disabled={!isCascadeAllowed || constraints.attr.disabled}
onChange={setIsCascade}
checked={isOnDeleteAllowed && onDelete === 'cascade'}
disabled={!isOnDeleteAllowed || constraints.attr.disabled}
onChange={() =>
setOnDelete(onDelete === 'cascade' ? null : 'cascade')
}
title={constraints.attr.message}
label={
<span className="dark:text-neutral-200">
Expand All @@ -970,12 +974,36 @@ function RelationshipConfigurator({
}
/>
</div>
<div className="flex gap-2">
<Checkbox
checked={isOnDeleteAllowed && onDelete === 'restrict'}
disabled={!isOnDeleteAllowed || constraints.attr.disabled}
onChange={() =>
setOnDelete(onDelete === 'restrict' ? null : 'restrict')
}
title={constraints.attr.message}
label={
<span className="dark:text-neutral-200">
<div>
<strong>
Restrict Delete {reverseNamespaceName} → {namespaceName}
</strong>
</div>
When a <strong>{reverseNamespaceName}</strong> entity is deleted,
the transaction will be blocked if all linked{' '}
<strong>{namespaceName}</strong> are not deleted or unlinked
</span>
}
/>
</div>

<div className="flex gap-2">
<Checkbox
checked={isCascadeReverseAllowed && isCascadeReverse}
disabled={!isCascadeReverseAllowed || constraints.attr.disabled}
onChange={setIsCascadeReverse}
checked={isOnDeleteReverseAllowed && onDeleteReverse === 'cascade'}
disabled={!isOnDeleteReverseAllowed || constraints.attr.disabled}
onChange={() =>
setOnDeleteReverse(onDeleteReverse === 'cascade' ? null : 'cascade')
}
title={constraints.attr.message}
label={
<span className="dark:text-neutral-200">
Expand All @@ -992,6 +1020,32 @@ function RelationshipConfigurator({
/>
</div>

<div className="flex gap-2">
<Checkbox
checked={isOnDeleteReverseAllowed && onDeleteReverse === 'restrict'}
disabled={!isOnDeleteReverseAllowed || constraints.attr.disabled}
onChange={() =>
setOnDeleteReverse(
onDeleteReverse === 'restrict' ? null : 'restrict',
)
}
title={constraints.attr.message}
label={
<span className="dark:text-neutral-200">
<div>
<strong>
Restrict Delete {namespaceName} → {reverseNamespaceName}
</strong>
</div>
When a <strong>{namespaceName}</strong> entity is deleted, the
transaction will be blocked if all linked{' '}
<strong>{reverseNamespaceName}</strong> are not deleted or
unlinked
</span>
}
/>
</div>

<div className="flex flex-col gap-1">
<h6 className="text-md font-bold">Constraints</h6>
<div className="flex gap-2">
Expand Down Expand Up @@ -1578,10 +1632,10 @@ function EditAttrForm({

const explorerProps = useExplorerProps();

const [isCascade, setIsCascade] = useState(() => attr.onDelete === 'cascade');
const [onDelete, setOnDelete] = useState<OnDelete>(() => attr.onDelete);

const [isCascadeReverse, setIsCascadeReverse] = useState(
() => attr.onDeleteReverse === 'cascade',
const [onDeleteReverse, setOnDeleteReverse] = useState<OnDelete>(
() => attr.onDeleteReverse,
);

const [isRequired, setIsRequired] = useState(attr.isRequired || false);
Expand All @@ -1598,9 +1652,9 @@ function EditAttrForm({
return () => stopFetchLoop.current?.();
}, [stopFetchLoop]);

const isCascadeAllowed =
const isOnDeleteAllowed =
relationship === 'one-one' || relationship === 'one-many';
const isCascadeReverseAllowed =
const isOnDeleteReverseAllowed =
relationship === 'one-one' || relationship === 'many-one';

const linkValidation = validateLink({
Expand Down Expand Up @@ -1645,9 +1699,10 @@ function EditAttrForm({
attr.linkConfig.reverse.namespace,
reverseAttrName,
],
'on-delete': isCascadeAllowed && isCascade ? 'cascade' : null,
'on-delete-reverse':
isCascadeReverseAllowed && isCascadeReverse ? 'cascade' : null,
'on-delete': isOnDeleteAllowed ? onDelete : null,
'on-delete-reverse': isOnDeleteReverseAllowed
? onDeleteReverse
: null,
},
],
];
Expand Down Expand Up @@ -1772,12 +1827,12 @@ function EditAttrForm({
setAttrName={setAttrName}
setReverseAttrName={setReverseAttrName}
setRelationship={setRelationship}
isCascadeAllowed={isCascadeAllowed}
isCascade={isCascade}
setIsCascade={setIsCascade}
isCascadeReverseAllowed={isCascadeReverseAllowed}
isCascadeReverse={isCascadeReverse}
setIsCascadeReverse={setIsCascadeReverse}
isOnDeleteAllowed={isOnDeleteAllowed}
onDelete={onDelete}
setOnDelete={setOnDelete}
isOnDeleteReverseAllowed={isOnDeleteReverseAllowed}
onDeleteReverse={onDeleteReverse}
setOnDeleteReverse={setOnDeleteReverse}
isRequired={isRequired}
setIsRequired={setIsRequired}
constraints={constraints}
Expand Down
6 changes: 4 additions & 2 deletions client/packages/components/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ export type DBIdent =

export type CheckedDataType = 'string' | 'number' | 'boolean' | 'date';

export type OnDelete = 'cascade' | 'restrict' | null | undefined;

export interface DBAttr {
id: string;
'forward-identity': DBIdent;
Expand All @@ -235,8 +237,8 @@ export interface DBAttr {
'inferred-types'?: Array<'string' | 'number' | 'boolean' | 'json'>;
catalog?: 'user' | 'system';
'checked-data-type'?: CheckedDataType;
'on-delete'?: 'cascade';
'on-delete-reverse'?: 'cascade';
'on-delete'?: OnDelete;
'on-delete-reverse'?: OnDelete;
metadata?: any;
}

Expand Down
2 changes: 1 addition & 1 deletion client/packages/core/src/attrTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export type InstantDBInferredType = 'number' | 'string' | 'boolean' | 'json';

export type InstantDBCheckedDataType = 'number' | 'string' | 'boolean' | 'date';

export type InstantDBAttrOnDelete = 'cascade';
export type InstantDBAttrOnDelete = 'cascade' | 'restrict';

export type InstantDBAttr = {
id: string;
Expand Down
4 changes: 2 additions & 2 deletions client/packages/core/src/schemaTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,13 @@ export type LinkDef<
label: FwdAttr;
has: FwdCardinality;
required?: RequirementKind;
onDelete?: 'cascade';
onDelete?: 'cascade' | 'restrict';
};
reverse: {
on: RevEntity;
label: RevAttr;
has: RevCardinality;
onDelete?: 'cascade';
onDelete?: 'cascade' | 'restrict';
};
};

Expand Down
Loading
Loading