@evolu/common 8.4.0 → 8.5.0
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/Array.d.ts +24 -0
- package/dist/src/Array.d.ts.map +1 -1
- package/dist/src/Array.js +27 -1
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +1 -0
- package/dist/src/LeakDetector.d.ts.map +1 -1
- package/dist/src/LeakDetector.js +4 -1
- package/dist/src/Lookup.js +1 -0
- package/dist/src/Platform.js +1 -0
- package/dist/src/Relation.js +3 -3
- package/dist/src/Resource.d.ts.map +1 -1
- package/dist/src/Resource.js +9 -3
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +3 -5
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +9 -7
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Store.js +3 -1
- package/dist/src/String.d.ts.map +1 -1
- package/dist/src/String.js +1 -1
- package/dist/src/Task.d.ts +122 -60
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +40 -53
- package/dist/src/Test.d.ts.map +1 -1
- package/dist/src/Test.js +1 -0
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +9 -2
- package/dist/src/Type.d.ts +146 -9
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +10 -7
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +8 -0
- package/dist/src/Worker.js +1 -1
- package/dist/src/local-first/Db.js +3 -2
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +1 -0
- package/dist/src/local-first/Owner.d.ts +8 -7
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +4 -4
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +30 -24
- package/dist/src/local-first/Query.d.ts.map +1 -1
- package/dist/src/local-first/Query.js +2 -1
- package/dist/src/local-first/Relay.js +1 -1
- package/dist/src/local-first/Shared.d.ts.map +1 -1
- package/dist/src/local-first/Shared.js +4 -2
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +1 -1
- package/package.json +1 -1
- package/src/Array.ts +28 -1
- package/src/Assert.ts +1 -0
- package/src/LeakDetector.ts +4 -3
- package/src/Lookup.ts +1 -0
- package/src/Platform.ts +1 -0
- package/src/Relation.ts +3 -3
- package/src/Resource.ts +9 -3
- package/src/Result.ts +5 -5
- package/src/Sqlite.ts +9 -7
- package/src/Store.ts +3 -1
- package/src/String.ts +1 -1
- package/src/Task.ts +133 -82
- package/src/Test.ts +1 -0
- package/src/Time.ts +9 -2
- package/src/Type.ts +173 -29
- package/src/WebSocket.ts +8 -0
- package/src/Worker.ts +1 -1
- package/src/local-first/Db.ts +2 -1
- package/src/local-first/Evolu.ts +1 -0
- package/src/local-first/Owner.ts +8 -7
- package/src/local-first/Protocol.ts +40 -30
- package/src/local-first/Query.ts +6 -3
- package/src/local-first/Relay.ts +1 -1
- package/src/local-first/Shared.ts +4 -2
- package/src/local-first/Timestamp.ts +1 -1
package/src/Time.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { assert } from "./Assert.ts";
|
|
8
8
|
import type { Brand } from "./Brand.ts";
|
|
9
|
+
import { exhaustiveCheck } from "./Function.ts";
|
|
9
10
|
import type { yieldNow } from "./Task.ts";
|
|
10
11
|
import {
|
|
11
12
|
brand,
|
|
@@ -239,6 +240,10 @@ export const testCreateTime = (options?: {
|
|
|
239
240
|
case "microtask":
|
|
240
241
|
queueMicrotask(incrementNow);
|
|
241
242
|
break;
|
|
243
|
+
case undefined:
|
|
244
|
+
break;
|
|
245
|
+
default:
|
|
246
|
+
exhaustiveCheck(autoIncrement);
|
|
242
247
|
}
|
|
243
248
|
return result;
|
|
244
249
|
};
|
|
@@ -564,8 +569,10 @@ const durationUnits = {
|
|
|
564
569
|
m: 60000,
|
|
565
570
|
h: 3600000,
|
|
566
571
|
d: 86400000,
|
|
567
|
-
|
|
568
|
-
|
|
572
|
+
// 7 days
|
|
573
|
+
w: 604800000,
|
|
574
|
+
// 365 days
|
|
575
|
+
y: 31536000000,
|
|
569
576
|
} as const;
|
|
570
577
|
|
|
571
578
|
/**
|
package/src/Type.ts
CHANGED
|
@@ -432,9 +432,10 @@ import { sha256 } from "@noble/hashes/sha2.js";
|
|
|
432
432
|
import * as bip39 from "@scure/bip39";
|
|
433
433
|
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
434
434
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
435
|
-
import
|
|
436
|
-
|
|
437
|
-
|
|
435
|
+
import {
|
|
436
|
+
createMutableArray,
|
|
437
|
+
type AtLeastTwoReadonlyArray,
|
|
438
|
+
type NonEmptyReadonlyArray,
|
|
438
439
|
} from "./Array.ts";
|
|
439
440
|
import { assert, assertNonNullable } from "./Assert.ts";
|
|
440
441
|
import type { Brand } from "./Brand.ts";
|
|
@@ -1349,9 +1350,8 @@ type ValidateFormatErrorByTypeByLocale<
|
|
|
1349
1350
|
? unknown
|
|
1350
1351
|
: never;
|
|
1351
1352
|
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
measured by `pnpm bench:type`. */
|
|
1353
|
+
// This mapped validation type is cheaper than the equivalent Record. Changes
|
|
1354
|
+
// are measured by `pnpm bench:type`.
|
|
1355
1355
|
type NoNonStringKeys<Value> = {
|
|
1356
1356
|
readonly [Key in Exclude<keyof Value, string>]: never;
|
|
1357
1357
|
};
|
|
@@ -1891,6 +1891,7 @@ export function createType<
|
|
|
1891
1891
|
parent: ValidateParent<ParentType>,
|
|
1892
1892
|
fromParent: (
|
|
1893
1893
|
value: ParentType["Output"],
|
|
1894
|
+
// oxlint-disable-next-line typescript/no-unnecessary-type-arguments -- Explicit defaults in recursive Type declarations are benchmarked to avoid extra compiler work.
|
|
1894
1895
|
) => Result<ParentType["Output"], never>,
|
|
1895
1896
|
): Type<
|
|
1896
1897
|
Name,
|
|
@@ -2179,12 +2180,58 @@ export function transform<
|
|
|
2179
2180
|
operations: {
|
|
2180
2181
|
readonly from: (
|
|
2181
2182
|
value: ParentType["Output"],
|
|
2183
|
+
// oxlint-disable-next-line typescript/no-unnecessary-type-arguments -- Explicit defaults in recursive Type declarations are benchmarked to avoid extra compiler work.
|
|
2182
2184
|
) => Result<OutputType["Input"], never>;
|
|
2183
2185
|
readonly to: (value: CanonicalInputOf<OutputType>) => ToOutput;
|
|
2184
2186
|
},
|
|
2185
2187
|
): TransformType<ParentType, OutputType, Name, never, ToOutput>;
|
|
2186
2188
|
|
|
2187
|
-
/**
|
|
2189
|
+
/**
|
|
2190
|
+
* Creates a fallible transformed Type with its own error formatter.
|
|
2191
|
+
*
|
|
2192
|
+
* ### Example
|
|
2193
|
+
*
|
|
2194
|
+
* ```ts
|
|
2195
|
+
* import {
|
|
2196
|
+
* Boolean,
|
|
2197
|
+
* String,
|
|
2198
|
+
* err,
|
|
2199
|
+
* ok,
|
|
2200
|
+
* transform,
|
|
2201
|
+
* type Result,
|
|
2202
|
+
* type TypeError,
|
|
2203
|
+
* } from "@evolu/common";
|
|
2204
|
+
*
|
|
2205
|
+
* interface BooleanFromStringError extends TypeError<"BooleanFromString"> {
|
|
2206
|
+
* readonly value: string;
|
|
2207
|
+
* }
|
|
2208
|
+
*
|
|
2209
|
+
* const BooleanFromString = transform(
|
|
2210
|
+
* "BooleanFromString",
|
|
2211
|
+
* String,
|
|
2212
|
+
* Boolean,
|
|
2213
|
+
* {
|
|
2214
|
+
* from: (value): Result<boolean, BooleanFromStringError> =>
|
|
2215
|
+
* value === "true"
|
|
2216
|
+
* ? ok(true)
|
|
2217
|
+
* : value === "false"
|
|
2218
|
+
* ? ok(false)
|
|
2219
|
+
* : err({ type: "BooleanFromString", value }),
|
|
2220
|
+
* to: (value) => (value ? "true" : "false"),
|
|
2221
|
+
* },
|
|
2222
|
+
* () => 'Expected "true" or "false".',
|
|
2223
|
+
* );
|
|
2224
|
+
*
|
|
2225
|
+
* expectOk(BooleanFromString.fromUnknown("true"), true);
|
|
2226
|
+
* expect(BooleanFromString.to(false)).toBe("false");
|
|
2227
|
+
*
|
|
2228
|
+
* const invalid = BooleanFromString.fromUnknown("yes");
|
|
2229
|
+
* expectErr(invalid, { type: "BooleanFromString", value: "yes" });
|
|
2230
|
+
* expect(BooleanFromString.formatError(invalid.error)).toBe(
|
|
2231
|
+
* 'Expected "true" or "false".',
|
|
2232
|
+
* );
|
|
2233
|
+
* ```
|
|
2234
|
+
*/
|
|
2188
2235
|
export function transform<
|
|
2189
2236
|
Name extends TypeName,
|
|
2190
2237
|
ParentType extends ConcreteTypeNode,
|
|
@@ -2473,7 +2520,7 @@ function getTerminalRuntimeNode<Value>(
|
|
|
2473
2520
|
): RuntimeOperation<Value>;
|
|
2474
2521
|
function getTerminalRuntimeNode(node: RuntimeTypeNode): RuntimeTypeNode;
|
|
2475
2522
|
function getTerminalRuntimeNode(node: { readonly parent?: unknown }): unknown {
|
|
2476
|
-
while (node.parent) node = node.parent;
|
|
2523
|
+
while (node.parent != null) node = node.parent;
|
|
2477
2524
|
|
|
2478
2525
|
return node;
|
|
2479
2526
|
}
|
|
@@ -2949,7 +2996,30 @@ export function objectTag<Name extends keyof ObjectTagOutputByName>(
|
|
|
2949
2996
|
ObjectTagOutputByName[Name]
|
|
2950
2997
|
>;
|
|
2951
2998
|
|
|
2952
|
-
/**
|
|
2999
|
+
/**
|
|
3000
|
+
* Creates an object-tag Type by refining an existing object Type.
|
|
3001
|
+
*
|
|
3002
|
+
* ### Example
|
|
3003
|
+
*
|
|
3004
|
+
* ```ts
|
|
3005
|
+
* import { instanceOf, objectTag } from "@evolu/common";
|
|
3006
|
+
*
|
|
3007
|
+
* class TaggedValue {
|
|
3008
|
+
* readonly [globalThis.Symbol.toStringTag] = "TaggedValue";
|
|
3009
|
+
* }
|
|
3010
|
+
*
|
|
3011
|
+
* const TaggedValueType = objectTag(
|
|
3012
|
+
* "TaggedValue",
|
|
3013
|
+
* instanceOf(TaggedValue),
|
|
3014
|
+
* );
|
|
3015
|
+
* const value = new TaggedValue();
|
|
3016
|
+
* const result = TaggedValueType.fromUnknown(value);
|
|
3017
|
+
*
|
|
3018
|
+
* expectOk(result, value);
|
|
3019
|
+
* expectTypeOf(result.value).toExtend<TaggedValue>();
|
|
3020
|
+
* expect(TaggedValueType.expected).toBe("TaggedValue");
|
|
3021
|
+
* ```
|
|
3022
|
+
*/
|
|
2953
3023
|
export function objectTag<
|
|
2954
3024
|
Name extends TypeName,
|
|
2955
3025
|
OutputType extends ConcreteTypeNode & { readonly Output: object },
|
|
@@ -4145,6 +4215,7 @@ export interface TemplateLiteralType<
|
|
|
4145
4215
|
TypeOfError<"String"> | TemplateLiteralParseError<Parts>,
|
|
4146
4216
|
never,
|
|
4147
4217
|
TemplateLiteralStringOutput<Parts>,
|
|
4218
|
+
// oxlint-disable-next-line typescript/no-unnecessary-type-arguments -- TemplateLiteralType explicitly documents every Type invariant, including identity encoding.
|
|
4148
4219
|
true
|
|
4149
4220
|
> {
|
|
4150
4221
|
readonly [templateLiteralSyntaxSymbol]: true;
|
|
@@ -4650,6 +4721,7 @@ export function brand<
|
|
|
4650
4721
|
>(
|
|
4651
4722
|
name: ValidateConcreteTypeName<Name>,
|
|
4652
4723
|
parent: ValidateParent<ParentType>,
|
|
4724
|
+
// oxlint-disable-next-line typescript/no-unnecessary-type-arguments -- Explicit defaults in recursive Type declarations are benchmarked to avoid extra compiler work.
|
|
4653
4725
|
validate?: (value: ParentType["Output"]) => Result<void, never>,
|
|
4654
4726
|
): BrandType<ParentType, Name, never>;
|
|
4655
4727
|
|
|
@@ -5378,7 +5450,7 @@ export interface RegexError<
|
|
|
5378
5450
|
*/
|
|
5379
5451
|
export const UrlSafeString = /*#__PURE__*/ regex(
|
|
5380
5452
|
"UrlSafeString",
|
|
5381
|
-
/^[A-Za-z0-9_-]
|
|
5453
|
+
/^[A-Za-z0-9_-]+$/u,
|
|
5382
5454
|
)(String);
|
|
5383
5455
|
export type UrlSafeString = typeof UrlSafeString.Output;
|
|
5384
5456
|
|
|
@@ -5401,7 +5473,7 @@ const uint8ArrayToBase64UrlString = (bytes: Uint8Array): string => {
|
|
|
5401
5473
|
).join("");
|
|
5402
5474
|
const base64 = globalThis.btoa(binaryString);
|
|
5403
5475
|
|
|
5404
|
-
return base64.
|
|
5476
|
+
return base64.replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
5405
5477
|
};
|
|
5406
5478
|
|
|
5407
5479
|
const base64UrlStringToUint8Array = (value: string): Uint8Array => {
|
|
@@ -5414,7 +5486,7 @@ const base64UrlStringToUint8Array = (value: string): Uint8Array => {
|
|
|
5414
5486
|
return globalThis.Uint8Array.fromBase64(value, base64UrlOptions);
|
|
5415
5487
|
}
|
|
5416
5488
|
|
|
5417
|
-
let base64 = value.
|
|
5489
|
+
let base64 = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
5418
5490
|
while (base64.length % 4 !== 0) base64 += "=";
|
|
5419
5491
|
|
|
5420
5492
|
const binaryString = globalThis.atob(base64);
|
|
@@ -5756,6 +5828,7 @@ export interface TableId<Table extends TypeName> extends Type<
|
|
|
5756
5828
|
TableIdError<Table> | InferErrors<typeof String>,
|
|
5757
5829
|
ChildCustomFrom<typeof String, Id & Brand<Table>, TableIdError<Table>>,
|
|
5758
5830
|
CanonicalInputForChild<typeof String, Id & Brand<Table>>,
|
|
5831
|
+
// oxlint-disable-next-line typescript/no-unnecessary-type-arguments -- TableId must track its parent Type's identity encoding instead of hard-coding Type's default.
|
|
5759
5832
|
IdentityEncodingOf<typeof String>
|
|
5760
5833
|
> {
|
|
5761
5834
|
readonly table: Table;
|
|
@@ -5877,7 +5950,7 @@ export const Int64String = /*#__PURE__*/ brand(
|
|
|
5877
5950
|
|
|
5878
5951
|
if (
|
|
5879
5952
|
value.length > (negative ? 20 : 19) ||
|
|
5880
|
-
!/^(?:0|-?[1-9]\d*)
|
|
5953
|
+
!/^(?:0|-?[1-9]\d*)$/u.test(value)
|
|
5881
5954
|
) {
|
|
5882
5955
|
return err<Int64StringError>({ type: "Int64String", value });
|
|
5883
5956
|
}
|
|
@@ -6607,7 +6680,7 @@ export const DecimalString = /*#__PURE__*/ brand(
|
|
|
6607
6680
|
"DecimalString",
|
|
6608
6681
|
String,
|
|
6609
6682
|
(value) =>
|
|
6610
|
-
/^(?:0|-?(?:[1-9]\d*|(?:0|[1-9]\d*)\.\d*[1-9]))
|
|
6683
|
+
/^(?:0|-?(?:[1-9]\d*|(?:0|[1-9]\d*)\.\d*[1-9]))$/u.test(value)
|
|
6611
6684
|
? ok()
|
|
6612
6685
|
: err<DecimalStringError>({ type: "DecimalString", value }),
|
|
6613
6686
|
(error) =>
|
|
@@ -7494,7 +7567,7 @@ const copyArrayPrefix = (
|
|
|
7494
7567
|
value: ReadonlyArray<unknown>,
|
|
7495
7568
|
endIndex: number,
|
|
7496
7569
|
): Array<unknown> => {
|
|
7497
|
-
const output =
|
|
7570
|
+
const output = createMutableArray<unknown>(value.length);
|
|
7498
7571
|
|
|
7499
7572
|
for (let index = 0; index < endIndex; index++) {
|
|
7500
7573
|
output[index] = value[index];
|
|
@@ -8911,7 +8984,7 @@ export const record = <
|
|
|
8911
8984
|
encodeKey === identity && encodeValue === identity
|
|
8912
8985
|
? identity
|
|
8913
8986
|
: (input: Readonly<Record<string, unknown>>) => {
|
|
8914
|
-
const output = createMutableRecord
|
|
8987
|
+
const output = createMutableRecord();
|
|
8915
8988
|
let changed = false;
|
|
8916
8989
|
|
|
8917
8990
|
for (const inputKey of globalThis.Object.keys(input)) {
|
|
@@ -9269,7 +9342,7 @@ const validateRecordEntries = (
|
|
|
9269
9342
|
> => {
|
|
9270
9343
|
let issues:
|
|
9271
9344
|
Array<RecordIssue<TypeError, TypeError, RecordStructuralIssue>> | undefined;
|
|
9272
|
-
const output = createMutableRecord
|
|
9345
|
+
const output = createMutableRecord();
|
|
9273
9346
|
const inputKeyByOutputKey = createMutableRecord<string, string | symbol>();
|
|
9274
9347
|
let changed = false;
|
|
9275
9348
|
|
|
@@ -9543,7 +9616,32 @@ export function object<const Props extends ObjectProps>(
|
|
|
9543
9616
|
: [ValidationFailure<ObjectValidationError<Props>>]
|
|
9544
9617
|
): StrictObjectType<Props>;
|
|
9545
9618
|
|
|
9546
|
-
/**
|
|
9619
|
+
/**
|
|
9620
|
+
* Creates an Object Type with additional record properties.
|
|
9621
|
+
*
|
|
9622
|
+
* ### Example
|
|
9623
|
+
*
|
|
9624
|
+
* ```ts
|
|
9625
|
+
* import { String, object, record } from "@evolu/common";
|
|
9626
|
+
*
|
|
9627
|
+
* const RequestHeaders = object(
|
|
9628
|
+
* { authorization: String },
|
|
9629
|
+
* record(String, String),
|
|
9630
|
+
* );
|
|
9631
|
+
* const result = RequestHeaders.fromUnknown({
|
|
9632
|
+
* authorization: "Bearer token",
|
|
9633
|
+
* "x-request-id": "request-1",
|
|
9634
|
+
* });
|
|
9635
|
+
*
|
|
9636
|
+
* expectOk(result, {
|
|
9637
|
+
* authorization: "Bearer token",
|
|
9638
|
+
* "x-request-id": "request-1",
|
|
9639
|
+
* });
|
|
9640
|
+
* expectTypeOf(result.value["x-request-id"]).toEqualTypeOf<
|
|
9641
|
+
* string | undefined
|
|
9642
|
+
* >();
|
|
9643
|
+
* ```
|
|
9644
|
+
*/
|
|
9547
9645
|
export function object<
|
|
9548
9646
|
const Props extends ObjectProps,
|
|
9549
9647
|
const Rest extends RecordTypeNode & ConcreteTypeNode,
|
|
@@ -10831,6 +10929,35 @@ export type UnknownResult = typeof UnknownResult.Output;
|
|
|
10831
10929
|
* ### Example
|
|
10832
10930
|
*
|
|
10833
10931
|
* ```ts
|
|
10932
|
+
* import { String, discriminatedUnion, typed } from "@evolu/common";
|
|
10933
|
+
*
|
|
10934
|
+
* const Loading = typed("Loading");
|
|
10935
|
+
* const Loaded = typed("Loaded", { value: String });
|
|
10936
|
+
* const State = discriminatedUnion(Loading, Loaded);
|
|
10937
|
+
*
|
|
10938
|
+
* expectOk(State.fromUnknown({ type: "Loading" }), { type: "Loading" });
|
|
10939
|
+
* expectOk(State.fromUnknown({ type: "Loaded", value: "Evolu" }), {
|
|
10940
|
+
* type: "Loaded",
|
|
10941
|
+
* value: "Evolu",
|
|
10942
|
+
* });
|
|
10943
|
+
* expect(Loading.is({ type: "Loading", progress: 1 })).toBe(false);
|
|
10944
|
+
* expectTypeOf<typeof Loading.Output>().toExtend<{
|
|
10945
|
+
* readonly type: "Loading";
|
|
10946
|
+
* }>();
|
|
10947
|
+
* ```
|
|
10948
|
+
*
|
|
10949
|
+
* @group Discriminated unions
|
|
10950
|
+
*/
|
|
10951
|
+
export function typed<const Tag extends TypeName>(
|
|
10952
|
+
tag: ValidateTypedTag<Tag>,
|
|
10953
|
+
): TypedType<Tag>;
|
|
10954
|
+
|
|
10955
|
+
/**
|
|
10956
|
+
* Creates a Tagged Object Type with declared properties.
|
|
10957
|
+
*
|
|
10958
|
+
* ### Example
|
|
10959
|
+
*
|
|
10960
|
+
* ```ts
|
|
10834
10961
|
* import { String, typed } from "@evolu/common";
|
|
10835
10962
|
*
|
|
10836
10963
|
* const Pending = typed("Pending", {
|
|
@@ -10842,14 +10969,7 @@ export type UnknownResult = typeof UnknownResult.Output;
|
|
|
10842
10969
|
* label: "Waiting",
|
|
10843
10970
|
* });
|
|
10844
10971
|
* ```
|
|
10845
|
-
*
|
|
10846
|
-
* @group Discriminated unions
|
|
10847
10972
|
*/
|
|
10848
|
-
export function typed<const Tag extends TypeName>(
|
|
10849
|
-
tag: ValidateTypedTag<Tag>,
|
|
10850
|
-
): TypedType<Tag>;
|
|
10851
|
-
|
|
10852
|
-
/** Creates a Tagged Object Type with declared properties. */
|
|
10853
10973
|
export function typed<
|
|
10854
10974
|
const Tag extends TypeName,
|
|
10855
10975
|
const Props extends ObjectProps,
|
|
@@ -10861,7 +10981,29 @@ export function typed<
|
|
|
10861
10981
|
: [ValidationFailure<TypedValidationError<Props>>]
|
|
10862
10982
|
): TypedType<Tag, Props>;
|
|
10863
10983
|
|
|
10864
|
-
/**
|
|
10984
|
+
/**
|
|
10985
|
+
* Creates a Tagged Object Type with additional record properties.
|
|
10986
|
+
*
|
|
10987
|
+
* ### Example
|
|
10988
|
+
*
|
|
10989
|
+
* ```ts
|
|
10990
|
+
* import { String, record, typed } from "@evolu/common";
|
|
10991
|
+
*
|
|
10992
|
+
* const Open = typed("Open", { label: String }, record(String, String));
|
|
10993
|
+
* const result = Open.fromUnknown({
|
|
10994
|
+
* type: "Open",
|
|
10995
|
+
* label: "Ready",
|
|
10996
|
+
* note: "Connected",
|
|
10997
|
+
* });
|
|
10998
|
+
*
|
|
10999
|
+
* expectOk(result, {
|
|
11000
|
+
* type: "Open",
|
|
11001
|
+
* label: "Ready",
|
|
11002
|
+
* note: "Connected",
|
|
11003
|
+
* });
|
|
11004
|
+
* expectTypeOf(result.value.note).toEqualTypeOf<string | undefined>();
|
|
11005
|
+
* ```
|
|
11006
|
+
*/
|
|
10865
11007
|
export function typed<
|
|
10866
11008
|
const Tag extends TypeName,
|
|
10867
11009
|
const Props extends ObjectProps,
|
|
@@ -11242,19 +11384,19 @@ export function discriminatedUnion(
|
|
|
11242
11384
|
return err({
|
|
11243
11385
|
type: "DiscriminatedUnion",
|
|
11244
11386
|
reason: { kind: "PropertyAccess", key, reason: "Inherited" },
|
|
11245
|
-
} satisfies DiscriminatedUnionPropertyAccessError
|
|
11387
|
+
} satisfies DiscriminatedUnionPropertyAccessError);
|
|
11246
11388
|
}
|
|
11247
11389
|
discriminator = undefined;
|
|
11248
11390
|
} else if (!("value" in descriptor)) {
|
|
11249
11391
|
return err({
|
|
11250
11392
|
type: "DiscriminatedUnion",
|
|
11251
11393
|
reason: { kind: "PropertyAccess", key, reason: "Accessor" },
|
|
11252
|
-
} satisfies DiscriminatedUnionPropertyAccessError
|
|
11394
|
+
} satisfies DiscriminatedUnionPropertyAccessError);
|
|
11253
11395
|
} else if (!descriptor.enumerable) {
|
|
11254
11396
|
return err({
|
|
11255
11397
|
type: "DiscriminatedUnion",
|
|
11256
11398
|
reason: { kind: "PropertyAccess", key, reason: "NonEnumerable" },
|
|
11257
|
-
} satisfies DiscriminatedUnionPropertyAccessError
|
|
11399
|
+
} satisfies DiscriminatedUnionPropertyAccessError);
|
|
11258
11400
|
} else {
|
|
11259
11401
|
discriminator = descriptor.value;
|
|
11260
11402
|
}
|
|
@@ -12260,6 +12402,7 @@ const validateJsonValue = (
|
|
|
12260
12402
|
}
|
|
12261
12403
|
if (typeof value === "number") {
|
|
12262
12404
|
if (!globalThis.Number.isFinite(value)) {
|
|
12405
|
+
// oxlint-disable-next-line unicorn/no-lonely-if -- Keep issue construction separate from the finite-number guard.
|
|
12263
12406
|
if (
|
|
12264
12407
|
addIssue({
|
|
12265
12408
|
kind: "NonFiniteNumber",
|
|
@@ -12521,6 +12664,7 @@ const stringifyJsonValue = (value: JsonValue): Json => {
|
|
|
12521
12664
|
continue;
|
|
12522
12665
|
}
|
|
12523
12666
|
|
|
12667
|
+
// oxlint-disable-next-line typescript/switch-exhaustiveness-check -- JsonValue excludes the additional runtime types reported by tsgolint.
|
|
12524
12668
|
switch (typeof value) {
|
|
12525
12669
|
case "string":
|
|
12526
12670
|
chunks.push(globalThis.JSON.stringify(value));
|
package/src/WebSocket.ts
CHANGED
|
@@ -235,9 +235,13 @@ export const createWebSocket: CreateWebSocket =
|
|
|
235
235
|
const closeSocket = () => {
|
|
236
236
|
if (!socket) return;
|
|
237
237
|
|
|
238
|
+
// oxlint-disable-next-line unicorn/prefer-add-event-listener -- This adapter owns and clears one handler.
|
|
238
239
|
socket.onopen = null;
|
|
240
|
+
// oxlint-disable-next-line unicorn/prefer-add-event-listener -- This adapter owns and clears one handler.
|
|
239
241
|
socket.onclose = null;
|
|
242
|
+
// oxlint-disable-next-line unicorn/prefer-add-event-listener -- This adapter owns and clears one handler.
|
|
240
243
|
socket.onmessage = null;
|
|
244
|
+
// oxlint-disable-next-line unicorn/prefer-add-event-listener -- This adapter owns and clears one handler.
|
|
241
245
|
socket.onerror = null;
|
|
242
246
|
|
|
243
247
|
if (
|
|
@@ -266,11 +270,13 @@ export const createWebSocket: CreateWebSocket =
|
|
|
266
270
|
|
|
267
271
|
let isOpen = false;
|
|
268
272
|
|
|
273
|
+
// oxlint-disable-next-line unicorn/prefer-add-event-listener -- This adapter owns and clears one handler.
|
|
269
274
|
socket.onopen = () => {
|
|
270
275
|
isOpen = true;
|
|
271
276
|
onOpen?.();
|
|
272
277
|
};
|
|
273
278
|
|
|
279
|
+
// oxlint-disable-next-line unicorn/prefer-add-event-listener -- This adapter owns and clears one handler.
|
|
274
280
|
socket.onclose = (event) => {
|
|
275
281
|
onClose?.(event);
|
|
276
282
|
if (shouldRetryOnClose(event)) {
|
|
@@ -280,10 +286,12 @@ export const createWebSocket: CreateWebSocket =
|
|
|
280
286
|
}
|
|
281
287
|
};
|
|
282
288
|
|
|
289
|
+
// oxlint-disable-next-line unicorn/prefer-add-event-listener -- This adapter owns and clears one handler.
|
|
283
290
|
socket.onmessage = (event: MessageEvent<string | ArrayBuffer | Blob>) => {
|
|
284
291
|
onMessage?.(event.data);
|
|
285
292
|
};
|
|
286
293
|
|
|
294
|
+
// oxlint-disable-next-line unicorn/prefer-add-event-listener -- This adapter owns and clears one handler.
|
|
287
295
|
socket.onerror = (event) => {
|
|
288
296
|
const error: WebSocketConnectionError | WebSocketConnectError = isOpen
|
|
289
297
|
? { type: "WebSocketConnectionError", event }
|
package/src/Worker.ts
CHANGED
|
@@ -432,7 +432,7 @@ export const createBroadcastChannel: CreateBroadcastChannel = <
|
|
|
432
432
|
const dispatches = broadcastChannelDispatchesByName.get(name);
|
|
433
433
|
assert(dispatches, "Expected broadcast channel dispatches");
|
|
434
434
|
|
|
435
|
-
for (const otherDispatch of
|
|
435
|
+
for (const otherDispatch of dispatches) {
|
|
436
436
|
if (otherDispatch === dispatch) continue;
|
|
437
437
|
|
|
438
438
|
scheduleWorkerTask(() => {
|
package/src/local-first/Db.ts
CHANGED
|
@@ -924,7 +924,8 @@ const applyMessages =
|
|
|
924
924
|
*/
|
|
925
925
|
updateOwnerUsage(deps)(
|
|
926
926
|
ownerIdBytes,
|
|
927
|
-
|
|
927
|
+
// Placeholder until proper tracking implemented
|
|
928
|
+
onePositiveInt,
|
|
928
929
|
firstTimestamp,
|
|
929
930
|
lastTimestamp,
|
|
930
931
|
);
|
package/src/local-first/Evolu.ts
CHANGED
|
@@ -1248,6 +1248,7 @@ export const createEvolu =
|
|
|
1248
1248
|
console.info("disposeEvolu");
|
|
1249
1249
|
});
|
|
1250
1250
|
|
|
1251
|
+
// oxlint-disable-next-line react/rules-of-hooks -- useOwner manages sync ownership and is not a React Hook.
|
|
1251
1252
|
if (isNonEmptyArray(transports)) useOwner(appOwner);
|
|
1252
1253
|
|
|
1253
1254
|
run.signal.throwIfAborted();
|
package/src/local-first/Owner.ts
CHANGED
|
@@ -11,15 +11,15 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Individual changes in an append-only local-first history can only be marked
|
|
13
13
|
* as deleted. An entire owner, however, can be removed from devices and relays
|
|
14
|
-
* together with all of its data. The AppOwner must remain because it
|
|
15
|
-
* the deletion of other owners across devices.
|
|
14
|
+
* together with all of its data. The AppOwner must remain because it
|
|
15
|
+
* coordinates the deletion of other owners across devices.
|
|
16
16
|
*
|
|
17
17
|
* Choose an owner by how its data should live and be shared:
|
|
18
18
|
*
|
|
19
19
|
* - {@link AppOwner} coordinates sync and persists for the lifetime of the app
|
|
20
20
|
* identity.
|
|
21
|
-
* - {@link ShardOwner} partitions application data so it can be synced and
|
|
22
|
-
*
|
|
21
|
+
* - {@link ShardOwner} partitions application data so it can be synced and deleted
|
|
22
|
+
* independently.
|
|
23
23
|
* - {@link SharedOwner} grants collaborative read and write access.
|
|
24
24
|
* - {@link SharedReadonlyOwner} grants read-only access to shared data.
|
|
25
25
|
*
|
|
@@ -62,8 +62,8 @@ import { TimestampBytes } from "./Timestamp.ts";
|
|
|
62
62
|
/**
|
|
63
63
|
* {@link Owner} without a {@link OwnerWriteKey}.
|
|
64
64
|
*
|
|
65
|
-
* @see {@link createSharedReadonlyOwner}
|
|
66
65
|
* @group Core
|
|
66
|
+
* @see {@link createSharedReadonlyOwner}
|
|
67
67
|
*/
|
|
68
68
|
export interface ReadonlyOwner {
|
|
69
69
|
readonly id: OwnerId;
|
|
@@ -427,9 +427,9 @@ export type OwnerTransport = OwnerWebSocketTransport;
|
|
|
427
427
|
* Legitimate clients will be properly configured with valid credentials, so
|
|
428
428
|
* automatic retry is OK.
|
|
429
429
|
*
|
|
430
|
+
* @group Transport
|
|
430
431
|
* @see {@link createOwnerWebSocketTransport}
|
|
431
432
|
* @see {@link parseOwnerIdFromOwnerWebSocketTransportUrl}
|
|
432
|
-
* @group Transport
|
|
433
433
|
*/
|
|
434
434
|
export interface OwnerWebSocketTransport extends Typed<"WebSocket"> {
|
|
435
435
|
readonly url: string;
|
|
@@ -553,7 +553,8 @@ export interface OwnerUsage {
|
|
|
553
553
|
*
|
|
554
554
|
* Measures only {@link EncryptedDbChange} data and excludes {@link Storage}
|
|
555
555
|
* implementation overhead such as indexes and skip-list columns. This makes
|
|
556
|
-
* the measurement consistent across storage implementations and suitable
|
|
556
|
+
* the measurement consistent across storage implementations and suitable
|
|
557
|
+
* for:
|
|
557
558
|
*
|
|
558
559
|
* - **Predictable measurement** - same data = same byte count across all
|
|
559
560
|
* instances
|