better-auth-elasticsearch 0.2.0 → 0.3.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/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { elasticsearchAdapter, type ElasticsearchAdapterConfig, } from "./elasticsearch-adapter.js";
2
2
  export { migrate, type MigrateOptions, type MigratedModel, type MigrationResult, } from "./migrate.js";
3
3
  export { purgeExpired, type PurgeResult } from "./purge.js";
4
+ export type { DuplicateValues } from "./unique-reservations.js";
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,KAAK,0BAA0B,GAChC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,OAAO,EACP,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,KAAK,0BAA0B,GAChC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,OAAO,EACP,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAC5D,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC"}
package/dist/migrate.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { MigratedIndex } from "./index-layout.js";
2
+ import type { DuplicateValues } from "./unique-reservations.js";
2
3
  /** What migrate() did to one model whose mapping had changed. */
3
4
  export interface MigratedModel extends MigratedIndex {
4
5
  /** The model's name in the better-auth schema, such as "user". */
@@ -27,6 +28,12 @@ export interface MigrateOptions {
27
28
  export interface MigrationResult {
28
29
  /** The models that moved to a new index; models already matching are left out. */
29
30
  migrated: MigratedModel[];
31
+ /**
32
+ * Values of unique fields that more than one row already holds, one entry
33
+ * per value, naming the rows but not the value. Such rows were written
34
+ * before the adapter enforced the field; resolve them by hand.
35
+ */
36
+ duplicates: DuplicateValues[];
30
37
  }
31
38
  /** Where the adapter hands migrate() the code that knows its indices. */
32
39
  export declare const MIGRATOR: unique symbol;
@@ -45,6 +52,10 @@ export interface AuthWithContext {
45
52
  * database is `elasticsearchAdapter(...)`. Reads carry on throughout; writes
46
53
  * to a model being moved wait until it is done (see `writeBlockTimeoutMs`).
47
54
  * Models already matching are left alone, so running it again is safe.
55
+ *
56
+ * Then reserves the values of unique fields for rows that hold no
57
+ * reservation yet, such as rows written before the adapter enforced them,
58
+ * and reports values more than one row holds.
48
59
  */
49
60
  export declare const migrate: (auth: AuthWithContext, options?: MigrateOptions) => Promise<MigrationResult>;
50
61
  //# sourceMappingURL=migrate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../src/migrate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,iEAAiE;AACjE,MAAM,WAAW,aAAc,SAAQ,aAAa;IAClD,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,kFAAkF;IAClF,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED,yEAAyE;AACzE,eAAO,MAAM,QAAQ,eAA+C,CAAC;AAErE,MAAM,MAAM,QAAQ,GAAG,CACrB,OAAO,EAAE,QAAQ,CAAC,cAAc,CAAC,KAC9B,OAAO,CAAC,eAAe,CAAC,CAAC;AAE9B,yDAAyD;AACzD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,OAAO,CAAC;QAAE,OAAO,EAAE;YAAE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;CAClE;AAED;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,GAClB,MAAM,eAAe,EACrB,UAAS,cAAmB,KAC3B,OAAO,CAAC,eAAe,CAczB,CAAC"}
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../src/migrate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEhE,iEAAiE;AACjE,MAAM,WAAW,aAAc,SAAQ,aAAa;IAClD,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,kFAAkF;IAClF,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B;;;;OAIG;IACH,UAAU,EAAE,eAAe,EAAE,CAAC;CAC/B;AAED,yEAAyE;AACzE,eAAO,MAAM,QAAQ,eAA+C,CAAC;AAErE,MAAM,MAAM,QAAQ,GAAG,CACrB,OAAO,EAAE,QAAQ,CAAC,cAAc,CAAC,KAC9B,OAAO,CAAC,eAAe,CAAC,CAAC;AAE9B,yDAAyD;AACzD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,OAAO,CAAC;QAAE,OAAO,EAAE;YAAE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;CAClE;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,OAAO,GAClB,MAAM,eAAe,EACrB,UAAS,cAAmB,KAC3B,OAAO,CAAC,eAAe,CAczB,CAAC"}
package/dist/migrate.js CHANGED
@@ -6,6 +6,10 @@ export const MIGRATOR = Symbol("better-auth-elasticsearch migrator");
6
6
  * database is `elasticsearchAdapter(...)`. Reads carry on throughout; writes
7
7
  * to a model being moved wait until it is done (see `writeBlockTimeoutMs`).
8
8
  * Models already matching are left alone, so running it again is safe.
9
+ *
10
+ * Then reserves the values of unique fields for rows that hold no
11
+ * reservation yet, such as rows written before the adapter enforced them,
12
+ * and reports values more than one row holds.
9
13
  */
10
14
  export const migrate = async (auth, options = {}) => {
11
15
  const { adapter } = await auth.$context;
@@ -1 +1 @@
1
- {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../src/migrate.ts"],"names":[],"mappings":"AAkCA,yEAAyE;AACzE,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,oCAAoC,CAAC,CAAC;AAWrE;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,KAAK,EAC1B,IAAqB,EACrB,UAA0B,EAAE,EACF,EAAE;IAC5B,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC;IACxC,MAAM,QAAQ,GAAI,OAAO,CAAC,OAA+C,EAAE,CACzE,QAAQ,CACe,CAAC;IAC1B,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACb,iHAAiH,CAClH,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;QACd,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,IAAI,KAAK;QACnD,eAAe,EAAE,OAAO,CAAC,eAAe,IAAI,MAAM;KACnD,CAAC,CAAC;AACL,CAAC,CAAC"}
1
+ {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../src/migrate.ts"],"names":[],"mappings":"AAyCA,yEAAyE;AACzE,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,oCAAoC,CAAC,CAAC;AAWrE;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,KAAK,EAC1B,IAAqB,EACrB,UAA0B,EAAE,EACF,EAAE;IAC5B,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC;IACxC,MAAM,QAAQ,GAAI,OAAO,CAAC,OAA+C,EAAE,CACzE,QAAQ,CACe,CAAC;IAC1B,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACb,iHAAiH,CAClH,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;QACd,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,IAAI,KAAK;QACnD,eAAe,EAAE,OAAO,CAAC,eAAe,IAAI,MAAM;KACnD,CAAC,CAAC;AACL,CAAC,CAAC"}
@@ -0,0 +1,123 @@
1
+ import type { Client } from "@elastic/elasticsearch";
2
+ import type { BetterAuthDBSchema } from "better-auth/db";
3
+ import type { IndexSettings } from "./index-layout.js";
4
+ /**
5
+ * How long a reservation whose row does not hold its value counts as a write
6
+ * still in flight. Older, it is left over from a crash and may be taken over,
7
+ * so a crash blocks a value for at most this long.
8
+ */
9
+ export declare const GRACE_MS = 120000;
10
+ type Source = Record<string, unknown>;
11
+ /**
12
+ * A set of fields whose values together must be unique: a field declared
13
+ * `unique`, or a unique index of the table, which may span several fields.
14
+ */
15
+ export interface UniqueConstraint {
16
+ /** The schema field names in index order, joined with "+". */
17
+ name: string;
18
+ /** The fields' schema and database names, in index order. */
19
+ fields: {
20
+ name: string;
21
+ dbName: string;
22
+ }[];
23
+ }
24
+ /** The values a row holds in a constraint's fields, in its field order. */
25
+ export interface HeldValues {
26
+ constraint: UniqueConstraint;
27
+ values: unknown[];
28
+ }
29
+ /** The unique values one row's write takes and gives up. */
30
+ export interface RowChange {
31
+ id: string;
32
+ claim: HeldValues[];
33
+ drop?: HeldValues[];
34
+ }
35
+ /** Rows that already hold the same value of a unique field or index. */
36
+ export interface DuplicateValues {
37
+ /** The model's name in the better-auth schema, such as "user". */
38
+ model: string;
39
+ /**
40
+ * The field's name in the better-auth schema, such as "email", or for an
41
+ * index over several fields their names joined with "+".
42
+ */
43
+ field: string;
44
+ /** The ids of the rows holding the value. */
45
+ ids: string[];
46
+ }
47
+ /**
48
+ * A reservation's `_id`: a hash, so the value itself is never stored. One
49
+ * field hashes as `[field, value]` however its uniqueness is declared;
50
+ * several hash as `[fields, values]`, which no single field can collide with.
51
+ */
52
+ export declare const reservationId: (dbFields: readonly string[], values: readonly unknown[]) => string;
53
+ export declare const duplicateError: (model: string, field: string) => Error;
54
+ export interface ReservationsOptions {
55
+ client: Client;
56
+ schema: BetterAuthDBSchema;
57
+ /** The schema's name for a database model name. */
58
+ getDefaultModelName: (model: string) => string;
59
+ /**
60
+ * Constraints left unenforced, as `model.field`, or `model.fieldA+fieldB`
61
+ * for an index over several fields, in schema names and index order.
62
+ */
63
+ skipUniqueFor: string[];
64
+ settings: IndexSettings;
65
+ withRetry: <T>(operation: () => Promise<T>) => Promise<T>;
66
+ }
67
+ /**
68
+ * Enforces the fields the better-auth schema declares unique and its tables'
69
+ * unique indexes. Every value of such a field, or combination of values of
70
+ * such an index, is backed by a reservation document in `<alias>_unique`
71
+ * whose `_id` derives from the fields and the values, so Elasticsearch refuses a
72
+ * second one the way it refuses a second row with the same id. Reservations
73
+ * are only read and written by `_id`, which is real-time, so none of their
74
+ * writes refresh.
75
+ */
76
+ export declare const createReservations: ({ client, schema, getDefaultModelName, skipUniqueFor, settings, withRetry, }: ReservationsOptions) => {
77
+ constraintsOf: (model: string) => UniqueConstraint[];
78
+ fieldsOf: (model: string, data?: Source) => string[];
79
+ constrainedFieldIn: (model: string, data: Source) => string | undefined;
80
+ valuesOf: (model: string, source: Source | undefined) => HeldValues[];
81
+ changeOf: (model: string, id: string, before: Source | undefined, changes: Source) => RowChange | undefined;
82
+ claims: (model: string, alias: string) => {
83
+ /**
84
+ * Claims the values `changes` take, in one bulk request, and re-stamps
85
+ * the values they give up so their release can tell whether a later
86
+ * write re-claimed them. Throws a duplicate key error, having given back
87
+ * what it created, if another row holds or is writing a value. Called
88
+ * again when a write reruns, it re-stamps its own claims.
89
+ */
90
+ claim(changes: RowChange[]): Promise<void>;
91
+ /**
92
+ * After a write of the rows `ids`: releases the values they gave up,
93
+ * unless a later write re-claimed them since this one re-stamped them.
94
+ */
95
+ written(ids: Iterable<string>): Promise<void>;
96
+ /**
97
+ * After the rows `ids` were certainly not written: gives back the
98
+ * reservations this write created for them, unless re-claimed since.
99
+ */
100
+ unwritten(ids: Iterable<string>): Promise<void>;
101
+ /**
102
+ * After the single row `id` failed to be written with `error`: gives
103
+ * back the reservations this write created, unless the write may have
104
+ * landed after all or the row holds the value. A write refused by a
105
+ * migration's write block keeps them for its rerun.
106
+ */
107
+ failed(id: string, error: unknown): Promise<void>;
108
+ /**
109
+ * The schema name of a value the row no longer owns, if any. A write
110
+ * landing later than half the grace period after its claim may have
111
+ * had one taken over meanwhile; one landing sooner cannot, so it is
112
+ * not asked.
113
+ */
114
+ lost(): Promise<string | undefined>;
115
+ };
116
+ release: (model: string, alias: string, rows: {
117
+ id: string;
118
+ source: Source | undefined;
119
+ }[]) => Promise<void>;
120
+ backfill: (defaultModel: string, alias: string) => Promise<DuplicateValues[]>;
121
+ };
122
+ export {};
123
+ //# sourceMappingURL=unique-reservations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unique-reservations.d.ts","sourceRoot":"","sources":["../src/unique-reservations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAW,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAQzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD;;;;GAIG;AACH,eAAO,MAAM,QAAQ,SAAU,CAAC;AAyBhC,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAatC;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC5C;AAED,2EAA2E;AAC3E,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,MAAM,EAAE,OAAO,EAAE,CAAC;CACnB;AAED,4DAA4D;AAC5D,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,IAAI,CAAC,EAAE,UAAU,EAAE,CAAC;CACrB;AAED,wEAAwE;AACxE,MAAM,WAAW,eAAe;IAC9B,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,GAAG,EAAE,MAAM,EAAE,CAAC;CACf;AAiDD;;;;GAIG;AACH,eAAO,MAAM,aAAa,GACxB,UAAU,SAAS,MAAM,EAAE,EAC3B,QAAQ,SAAS,OAAO,EAAE,WAQJ,CAAC;AAoCzB,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,EAAE,OAAO,MAAM,UAGxD,CAAC;AAEJ,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,kBAAkB,CAAC;IAC3B,mDAAmD;IACnD,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/C;;;OAGG;IACH,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,EAAE,aAAa,CAAC;IACxB,SAAS,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CAC3D;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAAI,8EAOhC,mBAAmB;2BA0CU,MAAM;sBAQX,MAAM,SAAS,MAAM;gCAYX,MAAM,QAAQ,MAAM;sBAwF9B,MAAM,UAAU,MAAM,GAAG,SAAS;sBAYlD,MAAM,MACT,MAAM,UACF,MAAM,GAAG,SAAS,WACjB,MAAM,KACd,SAAS,GAAG,SAAS;oBAuBD,MAAM,SAAS,MAAM;QAoGxC;;;;;;WAMG;uBACkB,SAAS,EAAE;QA8GhC;;;WAGG;qBACgB,QAAQ,CAAC,MAAM,CAAC;QAQnC;;;WAGG;uBACkB,QAAQ,CAAC,MAAM,CAAC;QAQrC;;;;;WAKG;mBACc,MAAM,SAAS,OAAO;QAoBvC;;;;;WAKG;gBACW,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;;qBA6BpC,MAAM,SACN,MAAM,QACP;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,EAAE;6BA0BpC,MAAM,SACb,MAAM,KACZ,OAAO,CAAC,eAAe,EAAE,CAAC;CAoK9B,CAAC"}