@evolu/common 6.0.1-preview.0 → 6.0.1-preview.10
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/Owner.d.ts +6 -1
- package/dist/src/Evolu/Owner.d.ts.map +1 -1
- package/dist/src/Evolu/Owner.js +5 -0
- package/dist/src/Evolu/Protocol.d.ts +134 -64
- package/dist/src/Evolu/Protocol.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.js +227 -77
- 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/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/Owner.ts +7 -1
- package/src/Evolu/Protocol.ts +276 -110
- package/src/Evolu/Relay.ts +45 -4
- package/src/Evolu/Schema.ts +102 -130
- package/src/Evolu/Storage.ts +12 -0
|
@@ -2,7 +2,7 @@ import { Kysely, SelectQueryBuilder } from "kysely";
|
|
|
2
2
|
import { ReadonlyRecord } from "../Object.js";
|
|
3
3
|
import { Result } from "../Result.js";
|
|
4
4
|
import { SqliteBoolean, SqliteQueryOptions, SqliteValue } from "../Sqlite.js";
|
|
5
|
-
import { AnyType, BrandType, InferErrors, InferInput, InferType, MergeObjectTypeErrors, NullableToOptionalProps, ObjectType, OptionalType, Type, TypeError } from "../Type.js";
|
|
5
|
+
import { AnyType, BrandType, IdType, InferErrors, InferInput, InferType, MergeObjectTypeErrors, NullableToOptionalProps, ObjectType, OptionalType, Type, TypeError } from "../Type.js";
|
|
6
6
|
import { Simplify } from "../Types.js";
|
|
7
7
|
import { DbSchema } from "./Db.js";
|
|
8
8
|
import { DbIndexesBuilder } from "./Kysely.js";
|
|
@@ -13,12 +13,6 @@ import { BinaryTimestamp } from "./Timestamp.js";
|
|
|
13
13
|
/**
|
|
14
14
|
* Defines the schema of an Evolu database.
|
|
15
15
|
*
|
|
16
|
-
* - Each top-level key represents a table name.
|
|
17
|
-
* - The value for each table name is a record of column names mapped to their
|
|
18
|
-
* respective data types, defined by {@link Type}.
|
|
19
|
-
* - Each table must include a mandatory `id` column of type {@link Id}.
|
|
20
|
-
* - No table may contain {@link DefaultColumns}.
|
|
21
|
-
*
|
|
22
16
|
* Table schema defines columns that are required for table rows. For not
|
|
23
17
|
* required columns, use {@link nullOr}.
|
|
24
18
|
*
|
|
@@ -50,9 +44,28 @@ import { BinaryTimestamp } from "./Timestamp.js";
|
|
|
50
44
|
* };
|
|
51
45
|
* ```
|
|
52
46
|
*/
|
|
53
|
-
export type EvoluSchema = ReadonlyRecord<string, ReadonlyRecord<string, Type<any, any, any, any, any
|
|
54
|
-
|
|
55
|
-
}
|
|
47
|
+
export type EvoluSchema = ReadonlyRecord<string, ReadonlyRecord<string, Type<any, any, any, any, any>>>;
|
|
48
|
+
/**
|
|
49
|
+
* Validates an {@link EvoluSchema} at compile time, returning the first error
|
|
50
|
+
* found as a readable string literal type. This approach provides much clearer
|
|
51
|
+
* and more actionable TypeScript errors than the default, which are often hard
|
|
52
|
+
* to read.
|
|
53
|
+
*
|
|
54
|
+
* Validates the following schema requirements:
|
|
55
|
+
*
|
|
56
|
+
* 1. All tables must have an 'id' column
|
|
57
|
+
* 2. The 'id' column must be a branded ID type (created with id() function)
|
|
58
|
+
* 3. Tables cannot use default column names (createdAt, updatedAt, isDeleted)
|
|
59
|
+
* 4. All column types must be compatible with SQLite (extend SqliteValue)
|
|
60
|
+
*/
|
|
61
|
+
export type ValidateSchema<S extends EvoluSchema> = ValidateSchemaHasId<S> extends never ? ValidateIdColumnType<S> extends never ? ValidateNoDefaultColumns<S> extends never ? ValidateColumnTypes<S> extends never ? S : ValidateColumnTypes<S> : ValidateNoDefaultColumns<S> : ValidateIdColumnType<S> : ValidateSchemaHasId<S>;
|
|
62
|
+
export type ValidateSchemaHasId<S extends EvoluSchema> = keyof S extends infer TableName ? TableName extends keyof S ? "id" extends keyof S[TableName] ? never : SchemaValidationError<`Table "${TableName & string}" is missing required id column.`> : never : never;
|
|
63
|
+
export type ValidateIdColumnType<S extends EvoluSchema> = keyof S extends infer TableName ? TableName extends keyof S ? "id" extends keyof S[TableName] ? S[TableName]["id"] extends IdType<any> ? never : SchemaValidationError<`Table "${TableName & string}" id column must be a branded ID type (created with id("${TableName & string}")).`> : never : never : never;
|
|
64
|
+
export type ValidateNoDefaultColumns<S extends EvoluSchema> = keyof S extends infer TableName ? TableName extends keyof S ? keyof S[TableName] extends infer ColumnName ? ColumnName extends keyof S[TableName] ? ColumnName extends "createdAt" | "updatedAt" | "isDeleted" ? SchemaValidationError<`Table "${TableName & string}" uses default column name "${ColumnName & string}". Default columns (createdAt, updatedAt, isDeleted) are added automatically.`> : never : never : never : never : never;
|
|
65
|
+
export type ValidateColumnTypes<S extends EvoluSchema> = keyof S extends infer TableName ? TableName extends keyof S ? keyof S[TableName] extends infer ColumnName ? ColumnName extends keyof S[TableName] ? InferType<S[TableName][ColumnName]> extends SqliteValue ? never : SchemaValidationError<`Table "${TableName & string}" column "${ColumnName & string}" type is not compatible with SQLite. Column types must extend SqliteValue (string, number, Uint8Array, or null).`> : never : never : never : never;
|
|
66
|
+
/** Schema validation error that shows clear, readable messages */
|
|
67
|
+
export type SchemaValidationError<Message extends string> = `❌ Schema Error: ${Message}`;
|
|
68
|
+
export declare const evoluSchemaToDbSchema: (schema: EvoluSchema, indexes?: DbIndexesBuilder) => DbSchema;
|
|
56
69
|
export type CreateQuery<S extends EvoluSchema> = <R extends Row>(queryCallback: (db: Pick<Kysely<{
|
|
57
70
|
[Table in keyof S]: {
|
|
58
71
|
readonly [Column in keyof S[Table]]: Column extends "id" | "createdAt" | "updatedAt" ? InferType<S[Table][Column]> : InferType<S[Table][Column]> | null;
|
|
@@ -61,7 +74,7 @@ export type CreateQuery<S extends EvoluSchema> = <R extends Row>(queryCallback:
|
|
|
61
74
|
readonly evolu_history: {
|
|
62
75
|
readonly timestamp: BinaryTimestamp;
|
|
63
76
|
readonly table: keyof S;
|
|
64
|
-
readonly
|
|
77
|
+
readonly id: BinaryId;
|
|
65
78
|
readonly column: string;
|
|
66
79
|
readonly value: SqliteValue;
|
|
67
80
|
};
|
|
@@ -72,35 +85,6 @@ export declare const DefaultColumns: ObjectType<{
|
|
|
72
85
|
isDeleted: import("../Type.js").UnionType<[Type<"Null", null, null, import("../Type.js").NullError, null, import("../Type.js").NullError>, import("../Type.js").TransformType<Type<"Boolean", boolean, boolean, import("../Type.js").BooleanError, boolean, import("../Type.js").BooleanError>, import("../Type.js").UnionType<[import("../Type.js").LiteralType<0>, import("../Type.js").LiteralType<1>]>, never>]>;
|
|
73
86
|
}>;
|
|
74
87
|
export type DefaultColumns = typeof DefaultColumns.Type;
|
|
75
|
-
/**
|
|
76
|
-
* Valid {@link EvoluSchema}.
|
|
77
|
-
*
|
|
78
|
-
* - Table and column names must be Base64Url strings.
|
|
79
|
-
* - Each table must include an `id` column of type {@link Id}.
|
|
80
|
-
* - Default column names (`createdAt`, `updatedAt`, `isDeleted`) are not allowed.
|
|
81
|
-
*/
|
|
82
|
-
export declare const ValidEvoluSchema: BrandType<import("../Type.js").RecordType<"Brand", string & import("../Types.js").Brand<"Base64Url"> & import("../Types.js").Brand<"MaxLength256">, string, import("../Type.js").MaxLengthError<256>, string & import("../Types.js").Brand<"Base64Url">, import("../Type.js").StringError | import("../Type.js").RegexError<"Base64Url">, import("../Type.js").ObjectWithRecordType<{
|
|
83
|
-
id: Type<"EvoluType", AnyType, AnyType, import("../Type.js").EvoluTypeError, AnyType, import("../Type.js").EvoluTypeError>;
|
|
84
|
-
}, "Brand", string & import("../Types.js").Brand<"Base64Url"> & import("../Types.js").Brand<"MaxLength256">, string, import("../Type.js").MaxLengthError<256>, string & import("../Types.js").Brand<"Base64Url">, import("../Type.js").StringError | import("../Type.js").RegexError<"Base64Url">, Type<"Unknown", unknown, unknown, never, unknown, never>>>, "ValidEvoluSchema", ValidEvoluSchemaError, import("../Type.js").RecordError<import("../Type.js").MaxLengthError<256>, import("../Type.js").ObjectWithRecordError<{
|
|
85
|
-
id: import("../Type.js").EvoluTypeError;
|
|
86
|
-
}, import("../Type.js").MaxLengthError<256>, never>> | import("../Type.js").RecordError<import("../Type.js").StringError | import("../Type.js").RegexError<"Base64Url">, import("../Type.js").ObjectWithRecordError<{
|
|
87
|
-
id: import("../Type.js").EvoluTypeError;
|
|
88
|
-
}, import("../Type.js").StringError | import("../Type.js").RegexError<"Base64Url">, never>>>;
|
|
89
|
-
export type ValidEvoluSchema = typeof ValidEvoluSchema.Type;
|
|
90
|
-
export interface ValidEvoluSchemaError extends TypeError<"ValidEvoluSchema"> {
|
|
91
|
-
readonly reason: {
|
|
92
|
-
kind: "DefaultColumnError";
|
|
93
|
-
tableName: string;
|
|
94
|
-
columnName: string;
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Asserts that the given value is {@link ValidEvoluSchema}.
|
|
99
|
-
*
|
|
100
|
-
* Throws an error if the value is not a valid Evolu schema.
|
|
101
|
-
*/
|
|
102
|
-
export declare const assertValidEvoluSchema: (value: unknown) => ValidEvoluSchema;
|
|
103
|
-
export declare const validEvoluSchemaToDbSchema: (validEvoluSchema: ValidEvoluSchema, indexes?: DbIndexesBuilder) => DbSchema;
|
|
104
88
|
export type MutationKind = "insert" | "update" | "upsert";
|
|
105
89
|
export type Mutation<S extends EvoluSchema, Kind extends MutationKind> = <TableName extends keyof S>(table: TableName, props: InferInput<ObjectType<MutationMapping<S[TableName], Kind>>>, options?: MutationOptions) => Result<{
|
|
106
90
|
readonly id: S[TableName]["id"]["Type"];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Schema.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"Schema.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAGpD,OAAO,EAA8B,cAAc,EAAE,MAAM,cAAc,CAAC;AAC1E,OAAO,EAAW,MAAM,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EACL,OAAO,EAEP,SAAS,EAGT,MAAM,EACN,WAAW,EACX,UAAU,EACV,SAAS,EACT,qBAAqB,EAErB,uBAAuB,EAGvB,UAAU,EAGV,YAAY,EACZ,IAAI,EACJ,SAAS,EACV,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAiB,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAY,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAgC,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,MAAM,WAAW,GAAG,cAAc,CACtC,MAAM,EAEN,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CACtD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,WAAW,IAC9C,mBAAmB,CAAC,CAAC,CAAC,SAAS,KAAK,GAChC,oBAAoB,CAAC,CAAC,CAAC,SAAS,KAAK,GACnC,wBAAwB,CAAC,CAAC,CAAC,SAAS,KAAK,GACvC,mBAAmB,CAAC,CAAC,CAAC,SAAS,KAAK,GAClC,CAAC,GACD,mBAAmB,CAAC,CAAC,CAAC,GACxB,wBAAwB,CAAC,CAAC,CAAC,GAC7B,oBAAoB,CAAC,CAAC,CAAC,GACzB,mBAAmB,CAAC,CAAC,CAAC,CAAC;AAE7B,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,WAAW,IACnD,MAAM,CAAC,SAAS,MAAM,SAAS,GAC3B,SAAS,SAAS,MAAM,CAAC,GACvB,IAAI,SAAS,MAAM,CAAC,CAAC,SAAS,CAAC,GAC7B,KAAK,GACL,qBAAqB,CAAC,UAAU,SAAS,GAAG,MAAM,kCAAkC,CAAC,GACvF,KAAK,GACP,KAAK,CAAC;AAEZ,MAAM,MAAM,oBAAoB,CAAC,CAAC,SAAS,WAAW,IACpD,MAAM,CAAC,SAAS,MAAM,SAAS,GAC3B,SAAS,SAAS,MAAM,CAAC,GACvB,IAAI,SAAS,MAAM,CAAC,CAAC,SAAS,CAAC,GAC7B,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,SAAS,MAAM,CAAC,GAAG,CAAC,GACpC,KAAK,GACL,qBAAqB,CAAC,UAAU,SAAS,GAAG,MAAM,2DAA2D,SAAS,GAAG,MAAM,MAAM,CAAC,GACxI,KAAK,GACP,KAAK,GACP,KAAK,CAAC;AAEZ,MAAM,MAAM,wBAAwB,CAAC,CAAC,SAAS,WAAW,IACxD,MAAM,CAAC,SAAS,MAAM,SAAS,GAC3B,SAAS,SAAS,MAAM,CAAC,GACvB,MAAM,CAAC,CAAC,SAAS,CAAC,SAAS,MAAM,UAAU,GACzC,UAAU,SAAS,MAAM,CAAC,CAAC,SAAS,CAAC,GACnC,UAAU,SAAS,WAAW,GAAG,WAAW,GAAG,WAAW,GACxD,qBAAqB,CAAC,UAAU,SAAS,GAAG,MAAM,+BAA+B,UAAU,GAAG,MAAM,+EAA+E,CAAC,GACpL,KAAK,GACP,KAAK,GACP,KAAK,GACP,KAAK,GACP,KAAK,CAAC;AAEZ,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,WAAW,IACnD,MAAM,CAAC,SAAS,MAAM,SAAS,GAC3B,SAAS,SAAS,MAAM,CAAC,GACvB,MAAM,CAAC,CAAC,SAAS,CAAC,SAAS,MAAM,UAAU,GACzC,UAAU,SAAS,MAAM,CAAC,CAAC,SAAS,CAAC,GACnC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,CAAC,CAAC,SAAS,WAAW,GACrD,KAAK,GACL,qBAAqB,CAAC,UAAU,SAAS,GAAG,MAAM,aAAa,UAAU,GAAG,MAAM,mHAAmH,CAAC,GACxM,KAAK,GACP,KAAK,GACP,KAAK,GACP,KAAK,CAAC;AAEZ,kEAAkE;AAClE,MAAM,MAAM,qBAAqB,CAAC,OAAO,SAAS,MAAM,IACtD,mBAAmB,OAAO,EAAE,CAAC;AAE/B,eAAO,MAAM,qBAAqB,GAChC,QAAQ,WAAW,EACnB,UAAU,gBAAgB,KACzB,QAgBF,CAAC;AAEF,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,WAAW,IAAI,CAAC,CAAC,SAAS,GAAG,EAC7D,aAAa,EAAE,CACb,EAAE,EAAE,IAAI,CACN,MAAM,CACJ;KACG,KAAK,IAAI,MAAM,CAAC,GAAG;QAClB,QAAQ,EAAE,MAAM,IAAI,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,MAAM,SACvC,IAAI,GACJ,WAAW,GACX,WAAW,GACX,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,GAC3B,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI;KACvC,GAAG,cAAc;CACnB,GAAG;IACF,QAAQ,CAAC,aAAa,EAAE;QACtB,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;QACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACxB,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;QACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;KAC7B,CAAC;CACH,CACF,EACD,YAAY,GAAG,IAAI,GAAG,MAAM,GAAG,eAAe,CAC/C,KACE,kBAAkB,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,EACpC,OAAO,CAAC,EAAE,kBAAkB,KACzB,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AAExB,eAAO,MAAM,cAAc;;;;EAIzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAC;AAExD,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE1D,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,WAAW,EAAE,IAAI,SAAS,YAAY,IAAI,CACvE,SAAS,SAAS,MAAM,CAAC,EAEzB,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,UAAU,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,EAClE,OAAO,CAAC,EAAE,eAAe,KACtB,MAAM,CACT;IAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAA;CAAE,EACzC,sBAAsB,GACtB,qBAAqB,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CACzE,CAAC;AAEF,MAAM,MAAM,eAAe,CACzB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,CAAC,SAAS,YAAY,IACpB,CAAC,SAAS,QAAQ,GAClB,eAAe,CAAC,CAAC,CAAC,GAClB,CAAC,SAAS,QAAQ,GAChB,eAAe,CAAC,CAAC,CAAC,GAClB,eAAe,CAAC,CAAC,CAAC,CAAC;AAEzB,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAEhC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,WAAW,CAAC;CAC3C;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe,SAAS,CAAC;AAStC,MAAM,WAAW,sBACf,SAAQ,SAAS,CAAC,mBAAmB,CAAC;CAAG;AAE3C,eAAO,MAAM,4BAA4B,iEAItC,CAAC;AAEJ,MAAM,MAAM,iBAAiB,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IACjE,SAAS,CACP,UAAU,CAAC,KAAK,CAAC,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAC/B,CAAC;AAEJ;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,UAAU,GAAI,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9D,OAAO,KAAK,KACX,iBAAiB,CAAC,eAAe,CAAC,KAAK,CAAC,CAI1C,CAAC;AAEF,MAAM,MAAM,eAAe,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,IAAI,CACvE,uBAAuB,CAAC,KAAK,CAAC,EAC9B,IAAI,CACL,CAAC;AAEF,MAAM,MAAM,UAAU,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,UAAU,CACxE,UAAU,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CACnC,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,UAAU,GAAI,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9D,OAAO,KAAK,KACX,iBAAiB,CAAC,eAAe,CAAC,KAAK,CAAC,CAM1C,CAAC;AAEF,MAAM,MAAM,eAAe,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;KAClE,CAAC,IAAI,MAAM,KAAK,GAAG,CAAC,SAAS,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;CACvE,GAAG;IAAE,SAAS,EAAE,YAAY,CAAC,OAAO,aAAa,CAAC,CAAA;CAAE,CAAC;AAEtD,MAAM,MAAM,UAAU,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,UAAU,CACxE,UAAU,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CACnC,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,UAAU,GAAI,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9D,OAAO,KAAK,KACX,iBAAiB,CAAC,eAAe,CAAC,KAAK,CAAC,CACG,CAAC;AAE/C,MAAM,MAAM,eAAe,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAC/D,uBAAuB,CAAC,KAAK,CAAC,CAAC;AAEjC,MAAM,MAAM,UAAU,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,UAAU,CACxE,UAAU,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CACnC,CAAC;AAEF,MAAM,MAAM,qBAAqB,CAAC,CAAC,SAAS,WAAW,IAAI;KACxD,KAAK,IAAI,MAAM,CAAC,GAAG,uBAAuB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;CACtD,CAAC,MAAM,CAAC,CAAC,CAAC;AAEX,MAAM,MAAM,uBAAuB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IACjE,iBAAiB,CAAC,CAAC,EAAE,QAAQ,CAAC,GAC9B,iBAAiB,CAAC,CAAC,EAAE,QAAQ,CAAC,GAC9B,iBAAiB,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AAEnC,MAAM,MAAM,iBAAiB,CAC3B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,CAAC,SAAS,YAAY,IACpB;KACD,MAAM,IAAI,MAAM,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,WAAW,CAClD,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAC9B;CACF,CAAC,MAAM,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC"}
|
package/dist/src/Evolu/Schema.js
CHANGED
|
@@ -1,86 +1,27 @@
|
|
|
1
1
|
import { pack } from "msgpackr";
|
|
2
|
+
import { assert } from "../Assert.js";
|
|
2
3
|
import { mapObject, objectToEntries } from "../Object.js";
|
|
3
4
|
import { err, ok } from "../Result.js";
|
|
4
5
|
import { SqliteBoolean } from "../Sqlite.js";
|
|
5
|
-
import { brand, createTypeErrorFormatter, DateIsoString,
|
|
6
|
+
import { brand, createTypeErrorFormatter, DateIsoString, nullableToOptional, nullOr, object, omit, optional, } from "../Type.js";
|
|
7
|
+
import { DbSchema } from "./Db.js";
|
|
6
8
|
import { createIndexes } from "./Kysely.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
createdAt: DateIsoString,
|
|
10
|
-
updatedAt: DateIsoString,
|
|
11
|
-
isDeleted: nullOr(SqliteBoolean),
|
|
12
|
-
});
|
|
13
|
-
const isDefaultColumnName = (value) => value === "createdAt" || value === "updatedAt" || value === "isDeleted";
|
|
14
|
-
/**
|
|
15
|
-
* Valid {@link EvoluSchema}.
|
|
16
|
-
*
|
|
17
|
-
* - Table and column names must be Base64Url strings.
|
|
18
|
-
* - Each table must include an `id` column of type {@link Id}.
|
|
19
|
-
* - Default column names (`createdAt`, `updatedAt`, `isDeleted`) are not allowed.
|
|
20
|
-
*/
|
|
21
|
-
export const ValidEvoluSchema = brand("ValidEvoluSchema", record(Base64Url256, object({ id: EvoluType }, record(Base64Url256, Unknown))), (value) => {
|
|
22
|
-
for (const tableName in value) {
|
|
23
|
-
for (const columnName in value[tableName]) {
|
|
24
|
-
if (isDefaultColumnName(columnName)) {
|
|
25
|
-
return err({
|
|
26
|
-
type: "ValidEvoluSchema",
|
|
27
|
-
value,
|
|
28
|
-
reason: {
|
|
29
|
-
kind: "DefaultColumnError",
|
|
30
|
-
tableName,
|
|
31
|
-
columnName,
|
|
32
|
-
},
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return ok(value);
|
|
38
|
-
});
|
|
39
|
-
/**
|
|
40
|
-
* Asserts that the given value is {@link ValidEvoluSchema}.
|
|
41
|
-
*
|
|
42
|
-
* Throws an error if the value is not a valid Evolu schema.
|
|
43
|
-
*/
|
|
44
|
-
export const assertValidEvoluSchema = (value) => {
|
|
45
|
-
const validEvoluSchema = ValidEvoluSchema.fromUnknown(value);
|
|
46
|
-
if (!validEvoluSchema.ok) {
|
|
47
|
-
const message = formatValidEvoluSchemaError(validEvoluSchema.error);
|
|
48
|
-
throw new Error(`Invalid Evolu schema: ${message}`);
|
|
49
|
-
}
|
|
50
|
-
return validEvoluSchema.value;
|
|
51
|
-
};
|
|
52
|
-
const formatValidEvoluSchemaError = (error) => {
|
|
53
|
-
if (error.type === "Record") {
|
|
54
|
-
if (error.reason.kind === "Key") {
|
|
55
|
-
return `The table "${error.reason.key}" has invalid name. A table name must be Base64Url256 string (A-Z, a-z, 0-9, -, _).`;
|
|
56
|
-
}
|
|
57
|
-
if (error.reason.kind === "Value" &&
|
|
58
|
-
error.reason.error.reason.kind === "Props" &&
|
|
59
|
-
error.reason.error.reason.errors.id?.type === "EvoluType") {
|
|
60
|
-
return `The table "${error.reason.key}" has invalid ID column. Check examples.`;
|
|
61
|
-
}
|
|
62
|
-
if (error.reason.kind === "Value" &&
|
|
63
|
-
error.reason.error.reason.kind === "IndexKey") {
|
|
64
|
-
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, -, _).`;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
if (error.type === "ValidEvoluSchema") {
|
|
68
|
-
return `The table "${error.reason.tableName}" uses reserved column name "${error.reason.columnName}". Reserved column names are: createdAt, updatedAt, isDeleted.`;
|
|
69
|
-
}
|
|
70
|
-
return JSON.stringify(error, null, 2);
|
|
71
|
-
};
|
|
72
|
-
export const validEvoluSchemaToDbSchema = (validEvoluSchema, indexes) => {
|
|
73
|
-
const tables = objectToEntries(validEvoluSchema).map(([tableName, table]) => ({
|
|
9
|
+
export const evoluSchemaToDbSchema = (schema, indexes) => {
|
|
10
|
+
const tables = objectToEntries(schema).map(([tableName, table]) => ({
|
|
74
11
|
name: tableName,
|
|
75
12
|
columns: objectToEntries(table)
|
|
76
13
|
.filter(([k]) => k !== "id")
|
|
77
14
|
.map(([k]) => k),
|
|
78
15
|
}));
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
};
|
|
16
|
+
const dbSchema = { tables, indexes: createIndexes(indexes) };
|
|
17
|
+
assert(DbSchema.is(dbSchema), "Invalid EvoluSchema: Table and column names must use only characters A-Za-z0-9_- and be at most 256 characters long.");
|
|
18
|
+
return dbSchema;
|
|
83
19
|
};
|
|
20
|
+
export const DefaultColumns = object({
|
|
21
|
+
createdAt: DateIsoString,
|
|
22
|
+
updatedAt: DateIsoString,
|
|
23
|
+
isDeleted: nullOr(SqliteBoolean),
|
|
24
|
+
});
|
|
84
25
|
/**
|
|
85
26
|
* Evolu has to limit the maximum mutation size. Otherwise, sync couldn't use
|
|
86
27
|
* the {@link maxProtocolMessageRangesSize}. The max size is 640KB in bytes,
|
|
@@ -36,6 +36,7 @@ export interface SqliteStorageBase {
|
|
|
36
36
|
readonly fingerprintRanges: Storage["fingerprintRanges"];
|
|
37
37
|
readonly findLowerBound: Storage["findLowerBound"];
|
|
38
38
|
readonly iterate: Storage["iterate"];
|
|
39
|
+
readonly deleteOwner: Storage["deleteOwner"];
|
|
39
40
|
}
|
|
40
41
|
export interface SqliteStorageBaseDep {
|
|
41
42
|
readonly storage: SqliteStorageBase;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Storage.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAM,MAAM,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAO,SAAS,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAe,cAAc,EAAe,MAAM,YAAY,CAAC;AAGtE,OAAO,EACL,aAAa,EAQb,OAAO,EAER,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAwB,MAAM,gBAAgB,CAAC;AAEvE,kEAAkE;AAClE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,eAAe,EAAE,CACxB,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,eAAe,KACvB,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAE/B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IACrC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IAC7C,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACzD,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACnD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"Storage.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAM,MAAM,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAO,SAAS,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAe,cAAc,EAAe,MAAM,YAAY,CAAC;AAGtE,OAAO,EACL,aAAa,EAQb,OAAO,EAER,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAwB,MAAM,gBAAgB,CAAC;AAEvE,kEAAkE;AAClE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,eAAe,EAAE,CACxB,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,eAAe,KACvB,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAE/B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IACrC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IAC7C,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACzD,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACnD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IACrC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;CACrC;AAED,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,SAAS,CAAC;AAEtD,MAAM,WAAW,8BAA8B;IAC7C,cAAc,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;CAC9C;AAED,eAAO,MAAM,uBAAuB,GACjC,MAAM,iBAAiB,MAEtB,SAAS,8BAA8B,KACtC,MAAM,CAAC,iBAAiB,EAAE,WAAW,CAoJvC,CAAC;AA09BJ,eAAO,MAAM,mBAAmB,GAC7B,MAAM,SAAS,MAEd,SAAS,aAAa,EACtB,OAAO,cAAc,KACpB,MAAM,CAAC,eAAe,EAAE,WAAW,CA6ErC,CAAC"}
|
|
@@ -141,6 +141,16 @@ export const createSqliteStorageBase = (deps) => (options) => {
|
|
|
141
141
|
return;
|
|
142
142
|
}
|
|
143
143
|
},
|
|
144
|
+
deleteOwner: (ownerId) => {
|
|
145
|
+
const result = deps.sqlite.exec(sql `
|
|
146
|
+
delete from evolu_timestamp where ownerId = ${ownerId};
|
|
147
|
+
`);
|
|
148
|
+
if (!result.ok) {
|
|
149
|
+
options.onStorageError(result.error);
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
},
|
|
144
154
|
});
|
|
145
155
|
};
|
|
146
156
|
const assertBeginEnd = (begin, end) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evolu/common",
|
|
3
|
-
"version": "6.0.1-preview.
|
|
3
|
+
"version": "6.0.1-preview.10",
|
|
4
4
|
"description": "TypeScript library and local-first framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"evolu",
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
"@bokuweb/zstd-wasm": "0.0.27",
|
|
55
55
|
"@types/better-sqlite3": "^7.6.13",
|
|
56
56
|
"@types/ws": "^8.18.1",
|
|
57
|
-
"better-sqlite3": "^
|
|
57
|
+
"better-sqlite3": "^12.1.1",
|
|
58
58
|
"shx": "^0.3.4",
|
|
59
59
|
"typescript": "^5.8.3",
|
|
60
|
-
"vitest": "^3.2.
|
|
60
|
+
"vitest": "^3.2.3",
|
|
61
61
|
"ws": "^8.18.2",
|
|
62
62
|
"@evolu/tsconfig": "0.0.2"
|
|
63
63
|
},
|
package/src/Assert.ts
CHANGED
|
@@ -32,9 +32,12 @@ import type { Type } from "./Type.js";
|
|
|
32
32
|
* assert(true, "true is not true"); // no-op
|
|
33
33
|
* assert(false, "true is not true"); // throws Error
|
|
34
34
|
*
|
|
35
|
-
* const
|
|
36
|
-
* //
|
|
37
|
-
* assert(
|
|
35
|
+
* const length = buffer.getLength();
|
|
36
|
+
* // We know length is logically non-negative, but TypeScript doesn't
|
|
37
|
+
* assert(
|
|
38
|
+
* NonNegativeInt.is(length),
|
|
39
|
+
* "buffer length should be non-negative",
|
|
40
|
+
* );
|
|
38
41
|
* ```
|
|
39
42
|
*/
|
|
40
43
|
export const assert: (
|
package/src/Crypto.ts
CHANGED
|
@@ -189,3 +189,16 @@ export const padmePaddedLength = (length: NonNegativeInt): NonNegativeInt => {
|
|
|
189
189
|
export const padmePaddingLength = (length: NonNegativeInt): NonNegativeInt => {
|
|
190
190
|
return (padmePaddedLength(length) - length) as NonNegativeInt;
|
|
191
191
|
};
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Performs a timing-safe comparison of two Uint8Arrays. Returns true if they
|
|
195
|
+
* are equal, false otherwise. Takes constant time regardless of where the
|
|
196
|
+
* arrays differ.
|
|
197
|
+
*
|
|
198
|
+
* @see https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b
|
|
199
|
+
*/
|
|
200
|
+
export type TimingSafeEqual = (a: Uint8Array, b: Uint8Array) => boolean;
|
|
201
|
+
|
|
202
|
+
export interface TimingSafeEqualDep {
|
|
203
|
+
readonly timingSafeEqual: TimingSafeEqual;
|
|
204
|
+
}
|
package/src/Evolu/Config.ts
CHANGED
|
@@ -5,9 +5,12 @@ import type { DbIndexesBuilder } from "./Kysely.js";
|
|
|
5
5
|
|
|
6
6
|
export interface Config extends ConsoleConfig {
|
|
7
7
|
/**
|
|
8
|
-
* The name of Evolu
|
|
9
|
-
* instances concurrently.
|
|
10
|
-
*
|
|
8
|
+
* The name of the Evolu instance. Evolu is multitenant - it can run multiple
|
|
9
|
+
* instances concurrently. Each instance must have a unique name.
|
|
10
|
+
*
|
|
11
|
+
* The instance name is used as the SQLite database filename for persistent
|
|
12
|
+
* storage, ensuring that database files are separated and invisible to each
|
|
13
|
+
* other.
|
|
11
14
|
*
|
|
12
15
|
* The default value is: `Evolu`.
|
|
13
16
|
*
|
|
@@ -22,7 +25,7 @@ export interface Config extends ConsoleConfig {
|
|
|
22
25
|
/**
|
|
23
26
|
* URL for Evolu sync and backup server.
|
|
24
27
|
*
|
|
25
|
-
* The default value is `
|
|
28
|
+
* The default value is `wss://free.evoluhq.com`.
|
|
26
29
|
*/
|
|
27
30
|
readonly syncUrl: string;
|
|
28
31
|
|
|
@@ -68,6 +71,17 @@ export interface Config extends ConsoleConfig {
|
|
|
68
71
|
* until special UX requirements are needed (e.g., multitenancy).
|
|
69
72
|
*/
|
|
70
73
|
readonly mnemonic?: Mnemonic;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Use in-memory SQLite database instead of persistent storage. Useful for
|
|
77
|
+
* testing or temporary data that doesn't need persistence.
|
|
78
|
+
*
|
|
79
|
+
* In-memory databases exist only in RAM and are completely destroyed when
|
|
80
|
+
* the process ends, making them forensically safe for sensitive data.
|
|
81
|
+
*
|
|
82
|
+
* The default value is: `false`.
|
|
83
|
+
*/
|
|
84
|
+
readonly inMemory?: boolean;
|
|
71
85
|
}
|
|
72
86
|
|
|
73
87
|
export interface ConfigDep {
|
|
@@ -76,7 +90,7 @@ export interface ConfigDep {
|
|
|
76
90
|
|
|
77
91
|
export const defaultConfig: Config = {
|
|
78
92
|
name: getOrThrow(SimpleName.fromParent("Evolu")),
|
|
79
|
-
syncUrl: "
|
|
93
|
+
syncUrl: "wss://free.evoluhq.com",
|
|
80
94
|
reloadUrl: "/",
|
|
81
95
|
maxDrift: 5 * 60 * 1000,
|
|
82
96
|
enableLogging: false,
|