@evolu/common 6.0.1-preview.1 → 6.0.1-preview.11
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/src/Assert.d.ts +6 -3
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +6 -3
- package/dist/src/Crypto.d.ts +11 -0
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Evolu/Config.d.ts +17 -4
- package/dist/src/Evolu/Config.d.ts.map +1 -1
- package/dist/src/Evolu/Config.js +1 -1
- package/dist/src/Evolu/Db.d.ts +21 -13
- package/dist/src/Evolu/Db.d.ts.map +1 -1
- package/dist/src/Evolu/Db.js +81 -64
- package/dist/src/Evolu/Evolu.d.ts +6 -6
- package/dist/src/Evolu/Evolu.d.ts.map +1 -1
- package/dist/src/Evolu/Evolu.js +19 -24
- package/dist/src/Evolu/Protocol.d.ts +79 -37
- package/dist/src/Evolu/Protocol.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.js +170 -58
- package/dist/src/Evolu/Relay.d.ts +3 -1
- package/dist/src/Evolu/Relay.d.ts.map +1 -1
- package/dist/src/Evolu/Relay.js +39 -3
- package/dist/src/Evolu/Schema.d.ts +24 -40
- package/dist/src/Evolu/Schema.d.ts.map +1 -1
- package/dist/src/Evolu/Schema.js +13 -72
- package/dist/src/Evolu/Storage.d.ts +1 -0
- package/dist/src/Evolu/Storage.d.ts.map +1 -1
- package/dist/src/Evolu/Storage.js +10 -0
- package/dist/src/Type.d.ts +42 -2
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +58 -1
- package/package.json +3 -3
- package/src/Assert.ts +6 -3
- package/src/Crypto.ts +13 -0
- package/src/Evolu/Config.ts +19 -5
- package/src/Evolu/Db.ts +92 -76
- package/src/Evolu/Evolu.ts +38 -35
- package/src/Evolu/Protocol.ts +209 -89
- package/src/Evolu/Relay.ts +45 -4
- package/src/Evolu/Schema.ts +102 -130
- package/src/Evolu/Storage.ts +12 -0
- package/src/Type.ts +71 -3
package/src/Evolu/Schema.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Kysely, SelectQueryBuilder } from "kysely";
|
|
2
2
|
import { pack } from "msgpackr";
|
|
3
|
+
import { assert } from "../Assert.js";
|
|
3
4
|
import { mapObject, objectToEntries, ReadonlyRecord } from "../Object.js";
|
|
4
5
|
import { err, ok, Result } from "../Result.js";
|
|
5
6
|
import { SqliteBoolean, SqliteQueryOptions, SqliteValue } from "../Sqlite.js";
|
|
@@ -9,8 +10,7 @@ import {
|
|
|
9
10
|
BrandType,
|
|
10
11
|
createTypeErrorFormatter,
|
|
11
12
|
DateIsoString,
|
|
12
|
-
|
|
13
|
-
Id,
|
|
13
|
+
IdType,
|
|
14
14
|
InferErrors,
|
|
15
15
|
InferInput,
|
|
16
16
|
InferType,
|
|
@@ -23,32 +23,20 @@ import {
|
|
|
23
23
|
omit,
|
|
24
24
|
optional,
|
|
25
25
|
OptionalType,
|
|
26
|
-
record,
|
|
27
26
|
Type,
|
|
28
27
|
TypeError,
|
|
29
|
-
Unknown,
|
|
30
28
|
} from "../Type.js";
|
|
31
29
|
import { Simplify } from "../Types.js";
|
|
32
|
-
import { DbSchema
|
|
30
|
+
import { DbSchema } from "./Db.js";
|
|
33
31
|
import { createIndexes, DbIndexesBuilder } from "./Kysely.js";
|
|
34
32
|
import { AppOwner, ShardOwner, SharedOwner } from "./Owner.js";
|
|
35
|
-
import {
|
|
36
|
-
Base64Url256,
|
|
37
|
-
BinaryId,
|
|
38
|
-
maxProtocolMessageRangesSize,
|
|
39
|
-
} from "./Protocol.js";
|
|
33
|
+
import { BinaryId, maxProtocolMessageRangesSize } from "./Protocol.js";
|
|
40
34
|
import { Query, Row } from "./Query.js";
|
|
41
35
|
import { BinaryTimestamp } from "./Timestamp.js";
|
|
42
36
|
|
|
43
37
|
/**
|
|
44
38
|
* Defines the schema of an Evolu database.
|
|
45
39
|
*
|
|
46
|
-
* - Each top-level key represents a table name.
|
|
47
|
-
* - The value for each table name is a record of column names mapped to their
|
|
48
|
-
* respective data types, defined by {@link Type}.
|
|
49
|
-
* - Each table must include a mandatory `id` column of type {@link Id}.
|
|
50
|
-
* - No table may contain {@link DefaultColumns}.
|
|
51
|
-
*
|
|
52
40
|
* Table schema defines columns that are required for table rows. For not
|
|
53
41
|
* required columns, use {@link nullOr}.
|
|
54
42
|
*
|
|
@@ -82,11 +70,105 @@ import { BinaryTimestamp } from "./Timestamp.js";
|
|
|
82
70
|
*/
|
|
83
71
|
export type EvoluSchema = ReadonlyRecord<
|
|
84
72
|
string,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
73
|
+
// TypeScript errors are cryptic so we use ValidateSchema.
|
|
74
|
+
ReadonlyRecord<string, Type<any, any, any, any, any>>
|
|
88
75
|
>;
|
|
89
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Validates an {@link EvoluSchema} at compile time, returning the first error
|
|
79
|
+
* found as a readable string literal type. This approach provides much clearer
|
|
80
|
+
* and more actionable TypeScript errors than the default, which are often hard
|
|
81
|
+
* to read.
|
|
82
|
+
*
|
|
83
|
+
* Validates the following schema requirements:
|
|
84
|
+
*
|
|
85
|
+
* 1. All tables must have an 'id' column
|
|
86
|
+
* 2. The 'id' column must be a branded ID type (created with id() function)
|
|
87
|
+
* 3. Tables cannot use default column names (createdAt, updatedAt, isDeleted)
|
|
88
|
+
* 4. All column types must be compatible with SQLite (extend SqliteValue)
|
|
89
|
+
*/
|
|
90
|
+
export type ValidateSchema<S extends EvoluSchema> =
|
|
91
|
+
ValidateSchemaHasId<S> extends never
|
|
92
|
+
? ValidateIdColumnType<S> extends never
|
|
93
|
+
? ValidateNoDefaultColumns<S> extends never
|
|
94
|
+
? ValidateColumnTypes<S> extends never
|
|
95
|
+
? S
|
|
96
|
+
: ValidateColumnTypes<S>
|
|
97
|
+
: ValidateNoDefaultColumns<S>
|
|
98
|
+
: ValidateIdColumnType<S>
|
|
99
|
+
: ValidateSchemaHasId<S>;
|
|
100
|
+
|
|
101
|
+
export type ValidateSchemaHasId<S extends EvoluSchema> =
|
|
102
|
+
keyof S extends infer TableName
|
|
103
|
+
? TableName extends keyof S
|
|
104
|
+
? "id" extends keyof S[TableName]
|
|
105
|
+
? never
|
|
106
|
+
: SchemaValidationError<`Table "${TableName & string}" is missing required id column.`>
|
|
107
|
+
: never
|
|
108
|
+
: never;
|
|
109
|
+
|
|
110
|
+
export type ValidateIdColumnType<S extends EvoluSchema> =
|
|
111
|
+
keyof S extends infer TableName
|
|
112
|
+
? TableName extends keyof S
|
|
113
|
+
? "id" extends keyof S[TableName]
|
|
114
|
+
? S[TableName]["id"] extends IdType<any>
|
|
115
|
+
? never
|
|
116
|
+
: SchemaValidationError<`Table "${TableName & string}" id column must be a branded ID type (created with id("${TableName & string}")).`>
|
|
117
|
+
: never
|
|
118
|
+
: never
|
|
119
|
+
: never;
|
|
120
|
+
|
|
121
|
+
export type ValidateNoDefaultColumns<S extends EvoluSchema> =
|
|
122
|
+
keyof S extends infer TableName
|
|
123
|
+
? TableName extends keyof S
|
|
124
|
+
? keyof S[TableName] extends infer ColumnName
|
|
125
|
+
? ColumnName extends keyof S[TableName]
|
|
126
|
+
? ColumnName extends "createdAt" | "updatedAt" | "isDeleted"
|
|
127
|
+
? SchemaValidationError<`Table "${TableName & string}" uses default column name "${ColumnName & string}". Default columns (createdAt, updatedAt, isDeleted) are added automatically.`>
|
|
128
|
+
: never
|
|
129
|
+
: never
|
|
130
|
+
: never
|
|
131
|
+
: never
|
|
132
|
+
: never;
|
|
133
|
+
|
|
134
|
+
export type ValidateColumnTypes<S extends EvoluSchema> =
|
|
135
|
+
keyof S extends infer TableName
|
|
136
|
+
? TableName extends keyof S
|
|
137
|
+
? keyof S[TableName] extends infer ColumnName
|
|
138
|
+
? ColumnName extends keyof S[TableName]
|
|
139
|
+
? InferType<S[TableName][ColumnName]> extends SqliteValue
|
|
140
|
+
? never
|
|
141
|
+
: SchemaValidationError<`Table "${TableName & string}" column "${ColumnName & string}" type is not compatible with SQLite. Column types must extend SqliteValue (string, number, Uint8Array, or null).`>
|
|
142
|
+
: never
|
|
143
|
+
: never
|
|
144
|
+
: never
|
|
145
|
+
: never;
|
|
146
|
+
|
|
147
|
+
/** Schema validation error that shows clear, readable messages */
|
|
148
|
+
export type SchemaValidationError<Message extends string> =
|
|
149
|
+
`❌ Schema Error: ${Message}`;
|
|
150
|
+
|
|
151
|
+
export const evoluSchemaToDbSchema = (
|
|
152
|
+
schema: EvoluSchema,
|
|
153
|
+
indexes?: DbIndexesBuilder,
|
|
154
|
+
): DbSchema => {
|
|
155
|
+
const tables = objectToEntries(schema).map(([tableName, table]) => ({
|
|
156
|
+
name: tableName,
|
|
157
|
+
columns: objectToEntries(table)
|
|
158
|
+
.filter(([k]) => k !== "id")
|
|
159
|
+
.map(([k]) => k),
|
|
160
|
+
}));
|
|
161
|
+
|
|
162
|
+
const dbSchema = { tables, indexes: createIndexes(indexes) };
|
|
163
|
+
|
|
164
|
+
assert(
|
|
165
|
+
DbSchema.is(dbSchema),
|
|
166
|
+
"Invalid EvoluSchema: Table and column names must use only characters A-Za-z0-9_- and be at most 256 characters long.",
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
return dbSchema;
|
|
170
|
+
};
|
|
171
|
+
|
|
90
172
|
export type CreateQuery<S extends EvoluSchema> = <R extends Row>(
|
|
91
173
|
queryCallback: (
|
|
92
174
|
db: Pick<
|
|
@@ -104,7 +186,7 @@ export type CreateQuery<S extends EvoluSchema> = <R extends Row>(
|
|
|
104
186
|
readonly evolu_history: {
|
|
105
187
|
readonly timestamp: BinaryTimestamp;
|
|
106
188
|
readonly table: keyof S;
|
|
107
|
-
readonly
|
|
189
|
+
readonly id: BinaryId;
|
|
108
190
|
readonly column: string;
|
|
109
191
|
readonly value: SqliteValue;
|
|
110
192
|
};
|
|
@@ -123,116 +205,6 @@ export const DefaultColumns = object({
|
|
|
123
205
|
});
|
|
124
206
|
export type DefaultColumns = typeof DefaultColumns.Type;
|
|
125
207
|
|
|
126
|
-
const isDefaultColumnName = (value: string): boolean =>
|
|
127
|
-
value === "createdAt" || value === "updatedAt" || value === "isDeleted";
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Valid {@link EvoluSchema}.
|
|
131
|
-
*
|
|
132
|
-
* - Table and column names must be Base64Url strings.
|
|
133
|
-
* - Each table must include an `id` column of type {@link Id}.
|
|
134
|
-
* - Default column names (`createdAt`, `updatedAt`, `isDeleted`) are not allowed.
|
|
135
|
-
*/
|
|
136
|
-
export const ValidEvoluSchema = brand(
|
|
137
|
-
"ValidEvoluSchema",
|
|
138
|
-
record(
|
|
139
|
-
Base64Url256,
|
|
140
|
-
object({ id: EvoluType }, record(Base64Url256, Unknown)),
|
|
141
|
-
),
|
|
142
|
-
(value) => {
|
|
143
|
-
for (const tableName in value) {
|
|
144
|
-
for (const columnName in value[tableName as never]) {
|
|
145
|
-
if (isDefaultColumnName(columnName)) {
|
|
146
|
-
return err<ValidEvoluSchemaError>({
|
|
147
|
-
type: "ValidEvoluSchema",
|
|
148
|
-
value,
|
|
149
|
-
reason: {
|
|
150
|
-
kind: "DefaultColumnError",
|
|
151
|
-
tableName,
|
|
152
|
-
columnName,
|
|
153
|
-
},
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return ok(value);
|
|
160
|
-
},
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
export type ValidEvoluSchema = typeof ValidEvoluSchema.Type;
|
|
164
|
-
|
|
165
|
-
export interface ValidEvoluSchemaError extends TypeError<"ValidEvoluSchema"> {
|
|
166
|
-
readonly reason: {
|
|
167
|
-
kind: "DefaultColumnError";
|
|
168
|
-
tableName: string;
|
|
169
|
-
columnName: string;
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Asserts that the given value is {@link ValidEvoluSchema}.
|
|
175
|
-
*
|
|
176
|
-
* Throws an error if the value is not a valid Evolu schema.
|
|
177
|
-
*/
|
|
178
|
-
export const assertValidEvoluSchema = (value: unknown): ValidEvoluSchema => {
|
|
179
|
-
const validEvoluSchema = ValidEvoluSchema.fromUnknown(value);
|
|
180
|
-
if (!validEvoluSchema.ok) {
|
|
181
|
-
const message = formatValidEvoluSchemaError(validEvoluSchema.error);
|
|
182
|
-
throw new Error(`Invalid Evolu schema: ${message}`);
|
|
183
|
-
}
|
|
184
|
-
return validEvoluSchema.value;
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
const formatValidEvoluSchemaError = (
|
|
188
|
-
error: typeof ValidEvoluSchema.Error | typeof ValidEvoluSchema.ParentError,
|
|
189
|
-
): string => {
|
|
190
|
-
if (error.type === "Record") {
|
|
191
|
-
if (error.reason.kind === "Key") {
|
|
192
|
-
return `The table "${error.reason.key}" has invalid name. A table name must be Base64Url256 string (A-Z, a-z, 0-9, -, _).`;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (
|
|
196
|
-
error.reason.kind === "Value" &&
|
|
197
|
-
error.reason.error.reason.kind === "Props" &&
|
|
198
|
-
error.reason.error.reason.errors.id?.type === "EvoluType"
|
|
199
|
-
) {
|
|
200
|
-
return `The table "${error.reason.key}" has invalid ID column. Check examples.`;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (
|
|
204
|
-
error.reason.kind === "Value" &&
|
|
205
|
-
error.reason.error.reason.kind === "IndexKey"
|
|
206
|
-
) {
|
|
207
|
-
return `The table "${error.reason.key}" has invalid column name "${error.reason.error.reason.key}". A column name must be Base64Url256 string (A-Z, a-z, 0-9, -, _).`;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (error.type === "ValidEvoluSchema") {
|
|
212
|
-
return `The table "${error.reason.tableName}" uses reserved column name "${error.reason.columnName}". Reserved column names are: createdAt, updatedAt, isDeleted.`;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return JSON.stringify(error, null, 2);
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
export const validEvoluSchemaToDbSchema = (
|
|
219
|
-
validEvoluSchema: ValidEvoluSchema,
|
|
220
|
-
indexes?: DbIndexesBuilder,
|
|
221
|
-
): DbSchema => {
|
|
222
|
-
const tables = objectToEntries(validEvoluSchema).map(
|
|
223
|
-
([tableName, table]): DbTable => ({
|
|
224
|
-
name: tableName,
|
|
225
|
-
columns: objectToEntries(table)
|
|
226
|
-
.filter(([k]) => k !== "id")
|
|
227
|
-
.map(([k]) => k as Base64Url256),
|
|
228
|
-
}),
|
|
229
|
-
);
|
|
230
|
-
return {
|
|
231
|
-
tables,
|
|
232
|
-
indexes: createIndexes(indexes),
|
|
233
|
-
};
|
|
234
|
-
};
|
|
235
|
-
|
|
236
208
|
export type MutationKind = "insert" | "update" | "upsert";
|
|
237
209
|
|
|
238
210
|
export type Mutation<S extends EvoluSchema, Kind extends MutationKind> = <
|
package/src/Evolu/Storage.ts
CHANGED
|
@@ -57,6 +57,7 @@ export interface SqliteStorageBase {
|
|
|
57
57
|
readonly fingerprintRanges: Storage["fingerprintRanges"];
|
|
58
58
|
readonly findLowerBound: Storage["findLowerBound"];
|
|
59
59
|
readonly iterate: Storage["iterate"];
|
|
60
|
+
readonly deleteOwner: Storage["deleteOwner"];
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
export interface SqliteStorageBaseDep {
|
|
@@ -209,6 +210,17 @@ export const createSqliteStorageBase =
|
|
|
209
210
|
if (!callback(result.value.rows[i].t, index)) return;
|
|
210
211
|
}
|
|
211
212
|
},
|
|
213
|
+
|
|
214
|
+
deleteOwner: (ownerId) => {
|
|
215
|
+
const result = deps.sqlite.exec(sql`
|
|
216
|
+
delete from evolu_timestamp where ownerId = ${ownerId};
|
|
217
|
+
`);
|
|
218
|
+
if (!result.ok) {
|
|
219
|
+
options.onStorageError(result.error);
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
return true;
|
|
223
|
+
},
|
|
212
224
|
});
|
|
213
225
|
};
|
|
214
226
|
|
package/src/Type.ts
CHANGED
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
* simple.
|
|
27
27
|
*
|
|
28
28
|
* - Evolu `Type` is:
|
|
29
|
-
*
|
|
30
29
|
* - A TypeScript type with a {@link Brand} whenever it's possible.
|
|
31
30
|
* - A function to create a value of that type, which may fail.
|
|
32
31
|
* - A function to transform value back to its original representation, which
|
|
@@ -70,6 +69,8 @@
|
|
|
70
69
|
* @module
|
|
71
70
|
*/
|
|
72
71
|
|
|
72
|
+
import { utf8ToBytes } from "@noble/ciphers/utils";
|
|
73
|
+
import { sha256 } from "@noble/hashes/sha2";
|
|
73
74
|
import * as bip39 from "@scure/bip39";
|
|
74
75
|
import { wordlist } from "@scure/bip39/wordlists/english";
|
|
75
76
|
import { assert } from "./Assert.js";
|
|
@@ -1243,6 +1244,13 @@ export const Base64Url = regex(
|
|
|
1243
1244
|
export type Base64Url = typeof Base64Url.Type;
|
|
1244
1245
|
export type Base64UrlError = typeof Base64Url.Error;
|
|
1245
1246
|
|
|
1247
|
+
/**
|
|
1248
|
+
* Alphabet used for Base64Url encoding. This is copied from the `nanoid`
|
|
1249
|
+
* library to avoid dependency on a specific version of `nanoid`.
|
|
1250
|
+
*/
|
|
1251
|
+
export const base64UrlAlphabet =
|
|
1252
|
+
"useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
|
|
1253
|
+
|
|
1246
1254
|
/**
|
|
1247
1255
|
* Simple alphanumeric string for naming.
|
|
1248
1256
|
*
|
|
@@ -1324,10 +1332,70 @@ export const idTypeValueLength = 21;
|
|
|
1324
1332
|
* ```ts
|
|
1325
1333
|
* // string & Brand<"Id">
|
|
1326
1334
|
* const id = createId(deps);
|
|
1335
|
+
*
|
|
1336
|
+
* // string & Brand<"Id"> & Brand<"Todo">
|
|
1337
|
+
* const todoId = createId<"Todo">(deps);
|
|
1327
1338
|
* ```
|
|
1328
1339
|
*/
|
|
1329
|
-
export const createId =
|
|
1330
|
-
deps
|
|
1340
|
+
export const createId = <B extends string = never>(
|
|
1341
|
+
deps: NanoIdLibDep,
|
|
1342
|
+
): [B] extends [never] ? Id : Id & Brand<B> =>
|
|
1343
|
+
deps.nanoIdLib.nanoid() as [B] extends [never] ? Id : Id & Brand<B>;
|
|
1344
|
+
|
|
1345
|
+
/**
|
|
1346
|
+
* Creates an {@link Id} from a string using SHA-256.
|
|
1347
|
+
*
|
|
1348
|
+
* Evolu table IDs must follow a fixed 21-character NanoID format. When
|
|
1349
|
+
* integrating with external systems that use different ID formats, use this
|
|
1350
|
+
* function to convert external IDs into valid Evolu IDs.
|
|
1351
|
+
*
|
|
1352
|
+
* In Evolu's CRDT, the ID serves as the unique identifier for conflict
|
|
1353
|
+
* resolution across distributed clients. When multiple clients create records
|
|
1354
|
+
* with the same external identifier, they must resolve to the same Evolu ID to
|
|
1355
|
+
* ensure data consistency.
|
|
1356
|
+
*
|
|
1357
|
+
* ### Example
|
|
1358
|
+
*
|
|
1359
|
+
* ```ts
|
|
1360
|
+
* // Both clients will generate the same ID
|
|
1361
|
+
* const id1 = createIdFromString("user-api-123");
|
|
1362
|
+
* const id2 = createIdFromString("user-api-123");
|
|
1363
|
+
* console.log(id1 === id2); // true
|
|
1364
|
+
*
|
|
1365
|
+
* upsert("todo", {
|
|
1366
|
+
* id: createIdFromString("external-todo-456"),
|
|
1367
|
+
* title: "Synced from external system",
|
|
1368
|
+
* });
|
|
1369
|
+
* ```
|
|
1370
|
+
*
|
|
1371
|
+
* **Important**: This transformation is one-way. You cannot recover the
|
|
1372
|
+
* original external string from the generated {@link Id}. If you need to
|
|
1373
|
+
* preserve the original external ID, store it in a separate column.
|
|
1374
|
+
*
|
|
1375
|
+
* @category String
|
|
1376
|
+
*/
|
|
1377
|
+
export const createIdFromString = <B extends string = never>(
|
|
1378
|
+
value: string,
|
|
1379
|
+
): [B] extends [never] ? Id : Id & Brand<B> => {
|
|
1380
|
+
const hash = sha256(utf8ToBytes(value)); // 32 bytes = 256 bits
|
|
1381
|
+
|
|
1382
|
+
let output = "";
|
|
1383
|
+
let buffer = 0;
|
|
1384
|
+
let bits = 0;
|
|
1385
|
+
|
|
1386
|
+
for (let i = 0; i < hash.length && output.length < 21; i++) {
|
|
1387
|
+
buffer = (buffer << 8) | hash[i]; // push 8 bits
|
|
1388
|
+
bits += 8;
|
|
1389
|
+
|
|
1390
|
+
while (bits >= 6 && output.length < 21) {
|
|
1391
|
+
bits -= 6;
|
|
1392
|
+
const index = (buffer >> bits) & 0b111111; // extract top 6 bits
|
|
1393
|
+
output += base64UrlAlphabet[index];
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
return output as [B] extends [never] ? Id : Id & Brand<B>;
|
|
1398
|
+
};
|
|
1331
1399
|
|
|
1332
1400
|
/**
|
|
1333
1401
|
* Type Factory to create branded {@link Id} Type for a specific table.
|