@pcg/dynamic-components 1.0.0-alpha.2 → 1.0.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +6 -6
- package/dist/index.js +52 -52
- package/dist/index.js.map +1 -1
- package/package.json +15 -3
- package/.turbo/turbo-build.log +0 -16
- package/CHANGELOG.md +0 -24
- package/eslint.config.cjs +0 -14
- package/src/assertions/basic.ts +0 -7
- package/src/assertions/containers.ts +0 -8
- package/src/assertions/index.ts +0 -6
- package/src/assertions/paths.ts +0 -12
- package/src/assertions/rich-text.ts +0 -16
- package/src/assertions/yjs.ts +0 -25
- package/src/data-objects/data-object.ts +0 -34
- package/src/data-objects/index.ts +0 -3
- package/src/data-objects/rich-text.ts +0 -38
- package/src/dynamic-components/fractional-indexing.ts +0 -321
- package/src/dynamic-components/index.ts +0 -6
- package/src/dynamic-components/paths.ts +0 -194
- package/src/dynamic-components/registry/chats.ts +0 -24
- package/src/dynamic-components/registry/content.ts +0 -118
- package/src/dynamic-components/registry/forms.ts +0 -525
- package/src/dynamic-components/registry/index.ts +0 -6
- package/src/dynamic-components/registry/layout.ts +0 -86
- package/src/dynamic-components/registry/uikit-dynamic-component.ts +0 -84
- package/src/dynamic-components/tools.ts +0 -195
- package/src/dynamic-components/types.ts +0 -237
- package/src/index.ts +0 -7
- package/src/paths/array-keys.ts +0 -164
- package/src/paths/array-ops.ts +0 -124
- package/src/paths/basic-ops.ts +0 -181
- package/src/paths/constants.ts +0 -1
- package/src/paths/index.ts +0 -7
- package/src/paths/tools.ts +0 -42
- package/src/paths/types.ts +0 -133
- package/src/y-components/index.ts +0 -3
- package/src/y-components/tools.ts +0 -234
- package/src/y-components/types.ts +0 -19
- package/src/y-tools/array-path-ops.ts +0 -240
- package/src/y-tools/basic-path-ops.ts +0 -189
- package/src/y-tools/index.ts +0 -6
- package/src/y-tools/tools.ts +0 -122
- package/src/y-tools/types.ts +0 -32
- package/src/y-tools/y-array-keys.ts +0 -47
- package/tests/assertions/basic-types.test.ts +0 -78
- package/tests/assertions/containers.test.ts +0 -72
- package/tests/assertions/paths.test.ts +0 -23
- package/tests/assertions/yjs.test.ts +0 -33
- package/tests/dynamic-components/paths.test.ts +0 -171
- package/tests/dynamic-components/tools.test.ts +0 -121
- package/tests/paths/array-keys.test.ts +0 -182
- package/tests/paths/array-ops.test.ts +0 -164
- package/tests/paths/basic-ops.test.ts +0 -263
- package/tests/paths/tools.test.ts +0 -55
- package/tests/y-components/tools.test.ts +0 -198
- package/tests/y-tools/array-base-ops.test.ts +0 -55
- package/tests/y-tools/array-path-ops.test.ts +0 -95
- package/tsconfig.json +0 -13
- package/tsconfig.lib.json +0 -13
- package/tsdown.config.ts +0 -18
- package/vitest.config.ts +0 -19
package/package.json
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pcg/dynamic-components",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.4",
|
|
4
4
|
"description": "Dynamic components with Yjs integration for collaborative editing",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": {
|
|
7
|
+
"email": "code@deepvision.team",
|
|
8
|
+
"name": "DeepVision Code"
|
|
9
|
+
},
|
|
10
|
+
"contributors": [
|
|
11
|
+
"Vitaliy Angolenko <v.angolenko@deepvision.software>",
|
|
12
|
+
"Sergii Sadovyi <s.sadovyi@deepvision.software>"
|
|
13
|
+
],
|
|
5
14
|
"type": "module",
|
|
6
15
|
"main": "dist/index.js",
|
|
7
16
|
"types": "dist/index.d.ts",
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
8
20
|
"dependencies": {
|
|
9
21
|
"@tiptap/core": "^2.12.0",
|
|
10
22
|
"y-prosemirror": "^1.3.4",
|
|
11
23
|
"yjs": "^13.6.27",
|
|
12
|
-
"@pcg/predicates": "1.0.0-alpha.
|
|
24
|
+
"@pcg/predicates": "1.0.0-alpha.2"
|
|
13
25
|
},
|
|
14
26
|
"devDependencies": {
|
|
15
27
|
"@tiptap/pm": "^2.12.0",
|
|
@@ -25,6 +37,6 @@
|
|
|
25
37
|
"build": "tsdown",
|
|
26
38
|
"test": "vitest run",
|
|
27
39
|
"test:watch": "vitest",
|
|
28
|
-
"lint": "eslint \"src/**/*.ts\""
|
|
40
|
+
"lint": "eslint \"src/**/*.ts\" --fix"
|
|
29
41
|
}
|
|
30
42
|
}
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
> @pcg/dynamic-components@1.0.0-alpha.0 build /Users/vangolenko/node/pcg/packages/dynamic-components
|
|
4
|
-
> tsdown
|
|
5
|
-
|
|
6
|
-
[34mℹ[39m tsdown [2mv0.15.12[22m powered by rolldown [2mv1.0.0-beta.45[22m
|
|
7
|
-
[34mℹ[39m Using tsdown config: [4m/Users/vangolenko/node/pcg/packages/dynamic-components/tsdown.config.ts[24m
|
|
8
|
-
[34mℹ[39m entry: [34msrc/index.ts[39m
|
|
9
|
-
[34mℹ[39m tsconfig: [34mtsconfig.lib.json[39m
|
|
10
|
-
[34mℹ[39m Build start
|
|
11
|
-
[34mℹ[39m Cleaning 3 files
|
|
12
|
-
[34mℹ[39m [2mdist/[22m[1mindex.js[22m [2m 57.84 kB[22m [2m│ gzip: 12.05 kB[22m
|
|
13
|
-
[34mℹ[39m [2mdist/[22mindex.js.map [2m120.49 kB[22m [2m│ gzip: 25.12 kB[22m
|
|
14
|
-
[34mℹ[39m [2mdist/[22m[32m[1mindex.d.ts[22m[39m [2m 60.94 kB[22m [2m│ gzip: 11.28 kB[22m
|
|
15
|
-
[34mℹ[39m 3 files, total: 239.28 kB
|
|
16
|
-
[32m✔[39m Build complete in [32m1141ms[39m
|
package/CHANGELOG.md
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
# @pcg/dynamic-components
|
|
2
|
-
|
|
3
|
-
## 1.0.0-alpha.2
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- Updated dependencies
|
|
8
|
-
- @pcg/predicates@1.0.0-alpha.1
|
|
9
|
-
|
|
10
|
-
## 1.0.0-alpha.1
|
|
11
|
-
|
|
12
|
-
### Patch Changes
|
|
13
|
-
|
|
14
|
-
- 72f7749: feat(ui): 🔄 make repeatable collection heading optional and add draggability
|
|
15
|
-
|
|
16
|
-
- Makes the `heading` prop optional in `DRepeatableCollection` interface
|
|
17
|
-
- Adds new `draggable` boolean prop to support drag-and-drop functionality
|
|
18
|
-
- Improves component flexibility for different use cases
|
|
19
|
-
|
|
20
|
-
## 1.0.0
|
|
21
|
-
|
|
22
|
-
### Minor Changes
|
|
23
|
-
|
|
24
|
-
- Initial release of dynamic-components package with Yjs integration for collaborative editing
|
package/eslint.config.cjs
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// @pkg 📦 DeepVision ESLint Config [NestJS]
|
|
3
|
-
// @version 📍 1.0.0
|
|
4
|
-
// @author 🐳 DeepVision Team <code@deepvision.team>
|
|
5
|
-
//
|
|
6
|
-
// Documentation reference: https://eslint.org/docs/user-guide/configuring/
|
|
7
|
-
// ESLint versions: https://eslint.org/blog/
|
|
8
|
-
//
|
|
9
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports, node/no-unpublished-require
|
|
10
|
-
const deep = require('@deepvision/eslint-plugin');
|
|
11
|
-
|
|
12
|
-
module.exports = [
|
|
13
|
-
...deep.default.configs.node,
|
|
14
|
-
];
|
package/src/assertions/basic.ts
DELETED
package/src/assertions/index.ts
DELETED
package/src/assertions/paths.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Checks if two paths are equal.
|
|
3
|
-
* @param {(string | number)[]} path - The first path to compare.
|
|
4
|
-
* @param {(string | number)[]} toPath - The second path to compare.
|
|
5
|
-
* @returns {boolean} Returns true if both paths are of the same length and all corresponding elements are equal, false otherwise.
|
|
6
|
-
* @example
|
|
7
|
-
* isEqualPath(['items', 'id:xxx', 'name'], ['items', 'id:xxx', 'name']); // Returns true
|
|
8
|
-
* isEqualPath(['items', 'id:yyy', 'name'], ['items', 'id:xxx', 'name']); // Returns false
|
|
9
|
-
*/
|
|
10
|
-
export const isEqualPath = (path: (string | number)[], toPath: (string | number)[]) => {
|
|
11
|
-
return path.length === toPath.length && path.every((v, index) => v === toPath[index]);
|
|
12
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { RichText } from '@/data-objects/rich-text.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Checks if the given value is a RichText object.
|
|
5
|
-
* @param {unknown} value - The value to check.
|
|
6
|
-
* @returns {boolean} Returns true if `value` is a RichText object, false otherwise.
|
|
7
|
-
* @example
|
|
8
|
-
* isRichText({ id: 'xx', ... type: 'RichText' }); // Returns true
|
|
9
|
-
*/
|
|
10
|
-
export const isRichText = (value: unknown): value is RichText => {
|
|
11
|
-
if (typeof value === 'object' && value !== null) {
|
|
12
|
-
return 'type' in value && value.type === 'RichText';
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return false;
|
|
16
|
-
};
|
package/src/assertions/yjs.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import * as Y from 'yjs';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Checks if the given value is a Yjs Map.
|
|
5
|
-
* @param {unknown} yMap - The value to check.
|
|
6
|
-
* @returns {boolean} Returns true if `yMap` is a Yjs Map, false otherwise.
|
|
7
|
-
* @example
|
|
8
|
-
* const yMap = new Y.Map();
|
|
9
|
-
* isYMap(yMap); // Returns true
|
|
10
|
-
*/
|
|
11
|
-
export const isYMap = <T = unknown>(yMap: unknown): yMap is Y.Map<T> => {
|
|
12
|
-
return yMap instanceof Y.Map;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Checks if the given value is a Yjs Array.
|
|
17
|
-
* @param {unknown} yArray - The value to check.
|
|
18
|
-
* @returns {boolean} Returns true if `yArray` is a Yjs Array, false otherwise.
|
|
19
|
-
* @example
|
|
20
|
-
* const yArray = new Y.Array();
|
|
21
|
-
* isYArray(yArray); // Returns true
|
|
22
|
-
*/
|
|
23
|
-
export const isYArray = <T = unknown>(yArray: unknown): yArray is Y.Array<T> => {
|
|
24
|
-
return yArray instanceof Y.Array;
|
|
25
|
-
};
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The BaseDataObject interface represents a basic structure of a data object.
|
|
3
|
-
* It includes an `id` and a `type` string.
|
|
4
|
-
*
|
|
5
|
-
* @interface
|
|
6
|
-
* @example
|
|
7
|
-
* {
|
|
8
|
-
* id: 'xxx',
|
|
9
|
-
* tiptap: { ... },
|
|
10
|
-
* type: 'RichText'
|
|
11
|
-
* }
|
|
12
|
-
*/
|
|
13
|
-
export interface BaseDataObject {
|
|
14
|
-
/**
|
|
15
|
-
* The unique identifier for the data object.
|
|
16
|
-
*/
|
|
17
|
-
id: string;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* The type of the data object. It's used for differentiating different types of data objects.
|
|
21
|
-
*/
|
|
22
|
-
type: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Gets the key of the data object. The key is a string combining `type` and `id` of the data object, separated by a colon.
|
|
27
|
-
* @param {BaseDataObject} dataObject - The data object to get the key from.
|
|
28
|
-
* @returns {string} Returns the key of the data object.
|
|
29
|
-
* @example
|
|
30
|
-
* getDataObjectKey({ id: 'xxx', type: 'RichText' }); // Returns 'RichText:xxx'
|
|
31
|
-
*/
|
|
32
|
-
export const getDataObjectKey = (dataObject: BaseDataObject): string => {
|
|
33
|
-
return `${dataObject.type}:${dataObject.id}`;
|
|
34
|
-
};
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { JSONContent } from '@tiptap/core';
|
|
2
|
-
|
|
3
|
-
import { BaseDataObject } from './data-object.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* The RichText interface represents a rich text data object.
|
|
7
|
-
*
|
|
8
|
-
* @interface
|
|
9
|
-
* @example
|
|
10
|
-
* {
|
|
11
|
-
* id: 'xx',
|
|
12
|
-
* tiptap: { ... },
|
|
13
|
-
* html: '<p>...</p>',
|
|
14
|
-
* plaintext: '...',
|
|
15
|
-
* type: 'RichText'
|
|
16
|
-
* }
|
|
17
|
-
*/
|
|
18
|
-
export interface RichText extends BaseDataObject {
|
|
19
|
-
/**
|
|
20
|
-
* The TipTap JSON content of the rich text data object.
|
|
21
|
-
*/
|
|
22
|
-
tiptap?: JSONContent;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* The HTML representation of the rich text data object.
|
|
26
|
-
*/
|
|
27
|
-
html?: string;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* The plaintext representation of the rich text data object.
|
|
31
|
-
*/
|
|
32
|
-
plaintext?: string;
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* The type of the rich text data object, set to 'RichText'.
|
|
36
|
-
*/
|
|
37
|
-
type: 'RichText';
|
|
38
|
-
}
|
|
@@ -1,321 +0,0 @@
|
|
|
1
|
-
// License: CC0 (no rights reserved).
|
|
2
|
-
|
|
3
|
-
// This is based on https://observablehq.com/@dgreensp/implementing-fractional-indexing
|
|
4
|
-
|
|
5
|
-
export const BASE_62_DIGITS =
|
|
6
|
-
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
|
7
|
-
|
|
8
|
-
const SMALLEST_INTEGER = 'A00000000000000000000000000';
|
|
9
|
-
|
|
10
|
-
const INTEGER_ZERO = 'a0';
|
|
11
|
-
|
|
12
|
-
// `a` may be empty string, `b` is null or non-empty string.
|
|
13
|
-
// `a < b` lexicographically if `b` is non-null.
|
|
14
|
-
// no trailing zeros allowed.
|
|
15
|
-
// digits is a string such as '0123456789' for base 10. Digits must be in
|
|
16
|
-
// ascending character code order!
|
|
17
|
-
/**
|
|
18
|
-
* @param {string} a
|
|
19
|
-
* @param {string | null} b
|
|
20
|
-
* @param {string} digits
|
|
21
|
-
* @returns {string}
|
|
22
|
-
*/
|
|
23
|
-
const midpoint = (a: string, b: string | null, digits: string): string => {
|
|
24
|
-
if (b !== null && a >= b) {
|
|
25
|
-
throw new Error(a + ' >= ' + b);
|
|
26
|
-
}
|
|
27
|
-
if (a.slice(-1) === '0' || (b && b.slice(-1) === '0')) {
|
|
28
|
-
throw new Error('trailing zero');
|
|
29
|
-
}
|
|
30
|
-
if (b) {
|
|
31
|
-
// remove longest common prefix. pad `a` with 0s as we
|
|
32
|
-
// go. note that we don't need to pad `b`, because it can't
|
|
33
|
-
// end before `a` while traversing the common prefix.
|
|
34
|
-
let n = 0;
|
|
35
|
-
while ((a[n] || '0') === b[n]) {
|
|
36
|
-
n++;
|
|
37
|
-
}
|
|
38
|
-
if (n > 0) {
|
|
39
|
-
return b.slice(0, n) + midpoint(a.slice(n), b.slice(n), digits);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
// first digits (or lack of digit) are different
|
|
43
|
-
const digitA = a ? digits.indexOf(a[0]) : 0;
|
|
44
|
-
const digitB = b !== null ? digits.indexOf(b[0]) : digits.length;
|
|
45
|
-
if (digitB - digitA > 1) {
|
|
46
|
-
const midDigit = Math.round(0.5 * (digitA + digitB));
|
|
47
|
-
|
|
48
|
-
return digits[midDigit];
|
|
49
|
-
} else {
|
|
50
|
-
// first digits are consecutive
|
|
51
|
-
if (b && b.length > 1) {
|
|
52
|
-
return b.slice(0, 1);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// `b` is null or has length 1 (a single digit).
|
|
56
|
-
// the first digit of `a` is the previous digit to `b`,
|
|
57
|
-
// or 9 if `b` is null.
|
|
58
|
-
// given, for example, midpoint('49', '5'), return
|
|
59
|
-
// '4' + midpoint('9', null), which will become
|
|
60
|
-
// '4' + '9' + midpoint('', null), which is '495'
|
|
61
|
-
return digits[digitA] + midpoint(a.slice(1), null, digits);
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* @param {string} int
|
|
67
|
-
* @return {void}
|
|
68
|
-
*/
|
|
69
|
-
|
|
70
|
-
const validateInteger = (int: string) => {
|
|
71
|
-
if (int.length !== getIntegerLength(int[0])) {
|
|
72
|
-
throw new Error('invalid integer part of order key: ' + int);
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* @param {string} head
|
|
78
|
-
* @return {number}
|
|
79
|
-
*/
|
|
80
|
-
|
|
81
|
-
const getIntegerLength = (head: string): number => {
|
|
82
|
-
if (head >= 'a' && head <= 'z') {
|
|
83
|
-
return head.charCodeAt(0) - 'a'.charCodeAt(0) + 2;
|
|
84
|
-
} else if (head >= 'A' && head <= 'Z') {
|
|
85
|
-
return 'Z'.charCodeAt(0) - head.charCodeAt(0) + 2;
|
|
86
|
-
} else {
|
|
87
|
-
throw new Error('invalid order key head: ' + head);
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* @param {string} key
|
|
93
|
-
* @return {string}
|
|
94
|
-
*/
|
|
95
|
-
|
|
96
|
-
const getIntegerPart = (key: string) => {
|
|
97
|
-
const integerPartLength = getIntegerLength(key[0]);
|
|
98
|
-
if (integerPartLength > key.length) {
|
|
99
|
-
throw new Error('invalid order key: ' + key);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return key.slice(0, integerPartLength);
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* @param {string} key
|
|
107
|
-
* @return {void}
|
|
108
|
-
*/
|
|
109
|
-
|
|
110
|
-
const validateOrderKey = (key: string) => {
|
|
111
|
-
if (key === SMALLEST_INTEGER) {
|
|
112
|
-
throw new Error('invalid order key: ' + key);
|
|
113
|
-
}
|
|
114
|
-
// getIntegerPart will throw if the first character is bad,
|
|
115
|
-
// or the key is too short. we'd call it to check these things
|
|
116
|
-
// even if we didn't need the result
|
|
117
|
-
const i = getIntegerPart(key);
|
|
118
|
-
const f = key.slice(i.length);
|
|
119
|
-
if (f.slice(-1) === '0') {
|
|
120
|
-
throw new Error('invalid order key: ' + key);
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
// note that this may return null, as there is a largest integer
|
|
125
|
-
/**
|
|
126
|
-
* @param {string} x
|
|
127
|
-
* @param {string} digits
|
|
128
|
-
* @return {string | null}
|
|
129
|
-
*/
|
|
130
|
-
const incrementInteger = (x: string, digits: string) => {
|
|
131
|
-
validateInteger(x);
|
|
132
|
-
const [head, ...digs] = x.split('');
|
|
133
|
-
let carry = true;
|
|
134
|
-
for (let i = digs.length - 1; carry && i >= 0; i--) {
|
|
135
|
-
const d = digits.indexOf(digs[i]) + 1;
|
|
136
|
-
if (d === digits.length) {
|
|
137
|
-
digs[i] = '0';
|
|
138
|
-
} else {
|
|
139
|
-
digs[i] = digits[d];
|
|
140
|
-
carry = false;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
if (carry) {
|
|
144
|
-
if (head === 'Z') {
|
|
145
|
-
return 'a0';
|
|
146
|
-
}
|
|
147
|
-
if (head === 'z') {
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
const h = String.fromCharCode(head.charCodeAt(0) + 1);
|
|
151
|
-
if (h > 'a') {
|
|
152
|
-
digs.push('0');
|
|
153
|
-
} else {
|
|
154
|
-
digs.pop();
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return h + digs.join('');
|
|
158
|
-
} else {
|
|
159
|
-
return head + digs.join('');
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
// note that this may return null, as there is a smallest integer
|
|
164
|
-
/**
|
|
165
|
-
* @param {string} x
|
|
166
|
-
* @param {string} digits
|
|
167
|
-
* @return {string | null}
|
|
168
|
-
*/
|
|
169
|
-
|
|
170
|
-
const decrementInteger = (x: string, digits: string) => {
|
|
171
|
-
validateInteger(x);
|
|
172
|
-
const [head, ...digs] = x.split('');
|
|
173
|
-
let borrow = true;
|
|
174
|
-
for (let i = digs.length - 1; borrow && i >= 0; i--) {
|
|
175
|
-
const d = digits.indexOf(digs[i]) - 1;
|
|
176
|
-
if (d === -1) {
|
|
177
|
-
digs[i] = digits.slice(-1);
|
|
178
|
-
} else {
|
|
179
|
-
digs[i] = digits[d];
|
|
180
|
-
borrow = false;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
if (borrow) {
|
|
184
|
-
if (head === 'a') {
|
|
185
|
-
return 'Z' + digits.slice(-1);
|
|
186
|
-
}
|
|
187
|
-
if (head === 'A') {
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
const h = String.fromCharCode(head.charCodeAt(0) - 1);
|
|
191
|
-
if (h < 'Z') {
|
|
192
|
-
digs.push(digits.slice(-1));
|
|
193
|
-
} else {
|
|
194
|
-
digs.pop();
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return h + digs.join('');
|
|
198
|
-
} else {
|
|
199
|
-
return head + digs.join('');
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
// `a` is an order key or null (START).
|
|
204
|
-
// `b` is an order key or null (END).
|
|
205
|
-
// `a < b` lexicographically if both are non-null.
|
|
206
|
-
// digits is a string such as '0123456789' for base 10. Digits must be in
|
|
207
|
-
// ascending character code order!
|
|
208
|
-
/**
|
|
209
|
-
* @param {string | null} a
|
|
210
|
-
* @param {string | null} b
|
|
211
|
-
* @param {string=} digits
|
|
212
|
-
* @return {string}
|
|
213
|
-
*/
|
|
214
|
-
export const generateKeyBetween = (a: string | null, b: string | null, digits = BASE_62_DIGITS) => {
|
|
215
|
-
if (a !== null) {
|
|
216
|
-
validateOrderKey(a);
|
|
217
|
-
}
|
|
218
|
-
if (b !== null) {
|
|
219
|
-
validateOrderKey(b);
|
|
220
|
-
}
|
|
221
|
-
if (a !== null && b !== null && a >= b) {
|
|
222
|
-
throw new Error(a + ' >= ' + b);
|
|
223
|
-
}
|
|
224
|
-
if (a === null) {
|
|
225
|
-
if (b === null) {
|
|
226
|
-
return INTEGER_ZERO;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const ib = getIntegerPart(b);
|
|
230
|
-
const fb = b.slice(ib.length);
|
|
231
|
-
if (ib === SMALLEST_INTEGER) {
|
|
232
|
-
return ib + midpoint('', fb, digits);
|
|
233
|
-
}
|
|
234
|
-
if (ib < b) {
|
|
235
|
-
return ib;
|
|
236
|
-
}
|
|
237
|
-
const res = decrementInteger(ib, digits);
|
|
238
|
-
if (res === null) {
|
|
239
|
-
throw new Error('cannot decrement any more');
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return res;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (b === null) {
|
|
246
|
-
const ia = getIntegerPart(a);
|
|
247
|
-
const fa = a.slice(ia.length);
|
|
248
|
-
const i = incrementInteger(ia, digits);
|
|
249
|
-
|
|
250
|
-
return i === null ? ia + midpoint(fa, null, digits) : i;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const ia = getIntegerPart(a);
|
|
254
|
-
const fa = a.slice(ia.length);
|
|
255
|
-
const ib = getIntegerPart(b);
|
|
256
|
-
const fb = b.slice(ib.length);
|
|
257
|
-
if (ia === ib) {
|
|
258
|
-
return ia + midpoint(fa, fb, digits);
|
|
259
|
-
}
|
|
260
|
-
const i = incrementInteger(ia, digits);
|
|
261
|
-
if (i === null) {
|
|
262
|
-
throw new Error('cannot increment any more');
|
|
263
|
-
}
|
|
264
|
-
if (i < b) {
|
|
265
|
-
return i;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
return ia + midpoint(fa, null, digits);
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* same preconditions as generateKeysBetween.
|
|
273
|
-
* n >= 0.
|
|
274
|
-
* Returns an array of n distinct keys in sorted order.
|
|
275
|
-
* If a and b are both null, returns [a0, a1, ...]
|
|
276
|
-
* If one or the other is null, returns consecutive "integer"
|
|
277
|
-
* keys. Otherwise, returns relatively short keys between
|
|
278
|
-
* a and b.
|
|
279
|
-
* @param {string | null} a
|
|
280
|
-
* @param {string | null} b
|
|
281
|
-
* @param {number} n
|
|
282
|
-
* @param {string} digits
|
|
283
|
-
* @return {string[]}
|
|
284
|
-
*/
|
|
285
|
-
export const generateNKeysBetween = (a: string | null, b: string | null, n: number, digits = BASE_62_DIGITS): string[] => {
|
|
286
|
-
if (n === 0) {
|
|
287
|
-
return [];
|
|
288
|
-
}
|
|
289
|
-
if (n === 1) {
|
|
290
|
-
return [generateKeyBetween(a, b, digits)];
|
|
291
|
-
}
|
|
292
|
-
if (b === null) {
|
|
293
|
-
let c = generateKeyBetween(a, b, digits);
|
|
294
|
-
const result = [c];
|
|
295
|
-
for (let i = 0; i < n - 1; i++) {
|
|
296
|
-
c = generateKeyBetween(c, b, digits);
|
|
297
|
-
result.push(c);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
return result;
|
|
301
|
-
}
|
|
302
|
-
if (a === null) {
|
|
303
|
-
let c = generateKeyBetween(a, b, digits);
|
|
304
|
-
const result = [c];
|
|
305
|
-
for (let i = 0; i < n - 1; i++) {
|
|
306
|
-
c = generateKeyBetween(a, c, digits);
|
|
307
|
-
result.push(c);
|
|
308
|
-
}
|
|
309
|
-
result.reverse();
|
|
310
|
-
|
|
311
|
-
return result;
|
|
312
|
-
}
|
|
313
|
-
const mid = Math.floor(n / 2);
|
|
314
|
-
const c = generateKeyBetween(a, b, digits);
|
|
315
|
-
|
|
316
|
-
return [
|
|
317
|
-
...generateNKeysBetween(a, c, mid, digits),
|
|
318
|
-
c,
|
|
319
|
-
...generateNKeysBetween(c, b, n - mid - 1, digits),
|
|
320
|
-
];
|
|
321
|
-
};
|