[Snapping 1/5] Validation & strict types for fractional indexes (#2827)

Currently, we type our fractional index keys as `string` and don't have
any validation for them. I'm touching some of this code for my work on
line handles and wanted to change that:
- fractional indexes are now `IndexKey`s, not `string`s. `IndexKey`s
have a brand property so can't be used interchangeably with strings
(like our IDs)
- There's a new `T.indexKey` validator which we can use in our
validations to make sure we don't end up with nonsense keys.

This PR is part of a series - please don't merge it until the things
before it have landed!
1. #2827 (you are here)
2. #2831
3. #2793
4. #2841
5. #2845

### Change Type

- [x] `patch` — Bug fix

### Test Plan

1. Mostly relying on unit & end to end tests here - no user facing
changes.

- [x] Unit Tests
This commit is contained in:
alex 2024-02-14 17:53:30 +00:00 committed by GitHub
parent fb00358a53
commit 93c2ed615c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 989 additions and 681 deletions

View file

@ -790,6 +790,483 @@
],
"name": "getHashForString"
},
{
"kind": "Function",
"canonicalReference": "@tldraw/utils!getIndexAbove:function(1)",
"docComment": "/**\n * Get the index above a given index.\n *\n * @param below - The index below.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "export declare function getIndexAbove(below: "
},
{
"kind": "Reference",
"text": "IndexKey",
"canonicalReference": "@tldraw/utils!IndexKey:type"
},
{
"kind": "Content",
"text": "): "
},
{
"kind": "Reference",
"text": "IndexKey",
"canonicalReference": "@tldraw/utils!IndexKey:type"
},
{
"kind": "Content",
"text": ";"
}
],
"fileUrlPath": "packages/utils/src/lib/reordering/reordering.ts",
"returnTypeTokenRange": {
"startIndex": 3,
"endIndex": 4
},
"releaseTag": "Public",
"overloadIndex": 1,
"parameters": [
{
"parameterName": "below",
"parameterTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"isOptional": false
}
],
"name": "getIndexAbove"
},
{
"kind": "Function",
"canonicalReference": "@tldraw/utils!getIndexBelow:function(1)",
"docComment": "/**\n * Get the index below a given index.\n *\n * @param above - The index above.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "export declare function getIndexBelow(above: "
},
{
"kind": "Reference",
"text": "IndexKey",
"canonicalReference": "@tldraw/utils!IndexKey:type"
},
{
"kind": "Content",
"text": "): "
},
{
"kind": "Reference",
"text": "IndexKey",
"canonicalReference": "@tldraw/utils!IndexKey:type"
},
{
"kind": "Content",
"text": ";"
}
],
"fileUrlPath": "packages/utils/src/lib/reordering/reordering.ts",
"returnTypeTokenRange": {
"startIndex": 3,
"endIndex": 4
},
"releaseTag": "Public",
"overloadIndex": 1,
"parameters": [
{
"parameterName": "above",
"parameterTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"isOptional": false
}
],
"name": "getIndexBelow"
},
{
"kind": "Function",
"canonicalReference": "@tldraw/utils!getIndexBetween:function(1)",
"docComment": "/**\n * Get the index between two indices.\n *\n * @param below - The index below.\n *\n * @param above - The index above.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "export declare function getIndexBetween(below: "
},
{
"kind": "Reference",
"text": "IndexKey",
"canonicalReference": "@tldraw/utils!IndexKey:type"
},
{
"kind": "Content",
"text": ", above?: "
},
{
"kind": "Reference",
"text": "IndexKey",
"canonicalReference": "@tldraw/utils!IndexKey:type"
},
{
"kind": "Content",
"text": "): "
},
{
"kind": "Reference",
"text": "IndexKey",
"canonicalReference": "@tldraw/utils!IndexKey:type"
},
{
"kind": "Content",
"text": ";"
}
],
"fileUrlPath": "packages/utils/src/lib/reordering/reordering.ts",
"returnTypeTokenRange": {
"startIndex": 5,
"endIndex": 6
},
"releaseTag": "Public",
"overloadIndex": 1,
"parameters": [
{
"parameterName": "below",
"parameterTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"isOptional": false
},
{
"parameterName": "above",
"parameterTypeTokenRange": {
"startIndex": 3,
"endIndex": 4
},
"isOptional": true
}
],
"name": "getIndexBetween"
},
{
"kind": "Function",
"canonicalReference": "@tldraw/utils!getIndices:function(1)",
"docComment": "/**\n * Get n number of indices, starting at an index.\n *\n * @param n - The number of indices to get.\n *\n * @param start - The index to start at.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "export declare function getIndices(n: "
},
{
"kind": "Content",
"text": "number"
},
{
"kind": "Content",
"text": ", start?: "
},
{
"kind": "Reference",
"text": "IndexKey",
"canonicalReference": "@tldraw/utils!IndexKey:type"
},
{
"kind": "Content",
"text": "): "
},
{
"kind": "Reference",
"text": "IndexKey",
"canonicalReference": "@tldraw/utils!IndexKey:type"
},
{
"kind": "Content",
"text": "[]"
},
{
"kind": "Content",
"text": ";"
}
],
"fileUrlPath": "packages/utils/src/lib/reordering/reordering.ts",
"returnTypeTokenRange": {
"startIndex": 5,
"endIndex": 7
},
"releaseTag": "Public",
"overloadIndex": 1,
"parameters": [
{
"parameterName": "n",
"parameterTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"isOptional": false
},
{
"parameterName": "start",
"parameterTypeTokenRange": {
"startIndex": 3,
"endIndex": 4
},
"isOptional": true
}
],
"name": "getIndices"
},
{
"kind": "Function",
"canonicalReference": "@tldraw/utils!getIndicesAbove:function(1)",
"docComment": "/**\n * Get a number of indices above an index.\n *\n * @param below - The index below.\n *\n * @param n - The number of indices to get.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "export declare function getIndicesAbove(below: "
},
{
"kind": "Reference",
"text": "IndexKey",
"canonicalReference": "@tldraw/utils!IndexKey:type"
},
{
"kind": "Content",
"text": ", n: "
},
{
"kind": "Content",
"text": "number"
},
{
"kind": "Content",
"text": "): "
},
{
"kind": "Reference",
"text": "IndexKey",
"canonicalReference": "@tldraw/utils!IndexKey:type"
},
{
"kind": "Content",
"text": "[]"
},
{
"kind": "Content",
"text": ";"
}
],
"fileUrlPath": "packages/utils/src/lib/reordering/reordering.ts",
"returnTypeTokenRange": {
"startIndex": 5,
"endIndex": 7
},
"releaseTag": "Public",
"overloadIndex": 1,
"parameters": [
{
"parameterName": "below",
"parameterTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"isOptional": false
},
{
"parameterName": "n",
"parameterTypeTokenRange": {
"startIndex": 3,
"endIndex": 4
},
"isOptional": false
}
],
"name": "getIndicesAbove"
},
{
"kind": "Function",
"canonicalReference": "@tldraw/utils!getIndicesBelow:function(1)",
"docComment": "/**\n * Get a number of indices below an index.\n *\n * @param above - The index above.\n *\n * @param n - The number of indices to get.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "export declare function getIndicesBelow(above: "
},
{
"kind": "Reference",
"text": "IndexKey",
"canonicalReference": "@tldraw/utils!IndexKey:type"
},
{
"kind": "Content",
"text": ", n: "
},
{
"kind": "Content",
"text": "number"
},
{
"kind": "Content",
"text": "): "
},
{
"kind": "Reference",
"text": "IndexKey",
"canonicalReference": "@tldraw/utils!IndexKey:type"
},
{
"kind": "Content",
"text": "[]"
},
{
"kind": "Content",
"text": ";"
}
],
"fileUrlPath": "packages/utils/src/lib/reordering/reordering.ts",
"returnTypeTokenRange": {
"startIndex": 5,
"endIndex": 7
},
"releaseTag": "Public",
"overloadIndex": 1,
"parameters": [
{
"parameterName": "above",
"parameterTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"isOptional": false
},
{
"parameterName": "n",
"parameterTypeTokenRange": {
"startIndex": 3,
"endIndex": 4
},
"isOptional": false
}
],
"name": "getIndicesBelow"
},
{
"kind": "Function",
"canonicalReference": "@tldraw/utils!getIndicesBetween:function(1)",
"docComment": "/**\n * Get a number of indices between two indices.\n *\n * @param below - The index below.\n *\n * @param above - The index above.\n *\n * @param n - The number of indices to get.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "export declare function getIndicesBetween(below: "
},
{
"kind": "Reference",
"text": "IndexKey",
"canonicalReference": "@tldraw/utils!IndexKey:type"
},
{
"kind": "Content",
"text": " | undefined"
},
{
"kind": "Content",
"text": ", above: "
},
{
"kind": "Reference",
"text": "IndexKey",
"canonicalReference": "@tldraw/utils!IndexKey:type"
},
{
"kind": "Content",
"text": " | undefined"
},
{
"kind": "Content",
"text": ", n: "
},
{
"kind": "Content",
"text": "number"
},
{
"kind": "Content",
"text": "): "
},
{
"kind": "Reference",
"text": "IndexKey",
"canonicalReference": "@tldraw/utils!IndexKey:type"
},
{
"kind": "Content",
"text": "[]"
},
{
"kind": "Content",
"text": ";"
}
],
"fileUrlPath": "packages/utils/src/lib/reordering/reordering.ts",
"returnTypeTokenRange": {
"startIndex": 9,
"endIndex": 11
},
"releaseTag": "Public",
"overloadIndex": 1,
"parameters": [
{
"parameterName": "below",
"parameterTypeTokenRange": {
"startIndex": 1,
"endIndex": 3
},
"isOptional": false
},
{
"parameterName": "above",
"parameterTypeTokenRange": {
"startIndex": 4,
"endIndex": 6
},
"isOptional": false
},
{
"parameterName": "n",
"parameterTypeTokenRange": {
"startIndex": 7,
"endIndex": 8
},
"isOptional": false
}
],
"name": "getIndicesBetween"
},
{
"kind": "TypeAlias",
"canonicalReference": "@tldraw/utils!IndexKey:type",
"docComment": "/**\n * A string made up of an integer part followed by a fraction part. The fraction point consists of zero or more digits with no trailing zeros. Based on {@link https://observablehq.com/@dgreensp/implementing-fractional-indexing}.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "export type IndexKey = "
},
{
"kind": "Content",
"text": "string & {\n __orderKey: true;\n}"
},
{
"kind": "Content",
"text": ";"
}
],
"fileUrlPath": "packages/utils/src/lib/reordering/IndexKey.ts",
"releaseTag": "Public",
"name": "IndexKey",
"typeTokenRange": {
"startIndex": 1,
"endIndex": 2
}
},
{
"kind": "Function",
"canonicalReference": "@tldraw/utils!invLerp:function(1)",
@ -2666,6 +3143,97 @@
],
"name": "sortById"
},
{
"kind": "Function",
"canonicalReference": "@tldraw/utils!sortByIndex:function(1)",
"docComment": "/**\n * Sort by index.\n *\n * @param a - An object with an index property.\n *\n * @param b - An object with an index property.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "export declare function sortByIndex<T extends "
},
{
"kind": "Content",
"text": "{\n index: "
},
{
"kind": "Reference",
"text": "IndexKey",
"canonicalReference": "@tldraw/utils!IndexKey:type"
},
{
"kind": "Content",
"text": ";\n}"
},
{
"kind": "Content",
"text": ">(a: "
},
{
"kind": "Content",
"text": "T"
},
{
"kind": "Content",
"text": ", b: "
},
{
"kind": "Content",
"text": "T"
},
{
"kind": "Content",
"text": "): "
},
{
"kind": "Content",
"text": "-1 | 0 | 1"
},
{
"kind": "Content",
"text": ";"
}
],
"fileUrlPath": "packages/utils/src/lib/reordering/reordering.ts",
"returnTypeTokenRange": {
"startIndex": 9,
"endIndex": 10
},
"releaseTag": "Public",
"overloadIndex": 1,
"parameters": [
{
"parameterName": "a",
"parameterTypeTokenRange": {
"startIndex": 5,
"endIndex": 6
},
"isOptional": false
},
{
"parameterName": "b",
"parameterTypeTokenRange": {
"startIndex": 7,
"endIndex": 8
},
"isOptional": false
}
],
"typeParameters": [
{
"typeParameterName": "T",
"constraintTokenRange": {
"startIndex": 1,
"endIndex": 4
},
"defaultTypeTokenRange": {
"startIndex": 0,
"endIndex": 0
}
}
],
"name": "sortByIndex"
},
{
"kind": "Variable",
"canonicalReference": "@tldraw/utils!structuredClone_2:var",
@ -2788,6 +3356,30 @@
}
],
"name": "throttle"
},
{
"kind": "Variable",
"canonicalReference": "@tldraw/utils!ZERO_INDEX_KEY:var",
"docComment": "/**\n * The index key for the first index - 'a0'.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "ZERO_INDEX_KEY: "
},
{
"kind": "Reference",
"text": "IndexKey",
"canonicalReference": "@tldraw/utils!IndexKey:type"
}
],
"fileUrlPath": "packages/utils/src/lib/reordering/reordering.ts",
"isReadonly": true,
"releaseTag": "Public",
"name": "ZERO_INDEX_KEY",
"variableTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
}
}
]
}