@nxgt/janus-drizzle 0.3.2 → 0.3.3
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.js +372 -301
- package/dist/index.js.map +20 -6
- package/dist/relations/context.d.ts +9 -0
- package/dist/relations/context.d.ts.map +1 -0
- package/dist/{relations.d.ts → relations/index.d.ts} +3 -3
- package/dist/relations/index.d.ts.map +1 -0
- package/dist/relations/reads.d.ts +9 -0
- package/dist/relations/reads.d.ts.map +1 -0
- package/dist/relations/tuples.d.ts +12 -0
- package/dist/relations/tuples.d.ts.map +1 -0
- package/dist/relations/writes.d.ts +7 -0
- package/dist/relations/writes.d.ts.map +1 -0
- package/dist/stores/identity-tables.d.ts +5 -0
- package/dist/stores/identity-tables.d.ts.map +1 -0
- package/dist/{stores.d.ts → stores/index.d.ts} +4 -6
- package/dist/stores/index.d.ts.map +1 -0
- package/dist/stores/logins.d.ts +13 -0
- package/dist/stores/logins.d.ts.map +1 -0
- package/dist/stores/records.d.ts +23 -0
- package/dist/stores/records.d.ts.map +1 -0
- package/dist/stores/sessions.d.ts +5 -0
- package/dist/stores/sessions.d.ts.map +1 -0
- package/dist/stores/tokens.d.ts +5 -0
- package/dist/stores/tokens.d.ts.map +1 -0
- package/dist/stores/users.d.ts +5 -0
- package/dist/stores/users.d.ts.map +1 -0
- package/dist/tables/columns.d.ts +22 -0
- package/dist/tables/columns.d.ts.map +1 -0
- package/dist/{tables.d.ts → tables/index.d.ts} +1 -1
- package/dist/tables/index.d.ts.map +1 -0
- package/dist/tables/logins.d.ts +74 -0
- package/dist/tables/logins.d.ts.map +1 -0
- package/dist/tables/relations.d.ts +133 -0
- package/dist/tables/relations.d.ts.map +1 -0
- package/dist/tables/sessions.d.ts +131 -0
- package/dist/tables/sessions.d.ts.map +1 -0
- package/dist/tables/tokens.d.ts +153 -0
- package/dist/tables/tokens.d.ts.map +1 -0
- package/dist/tables/users.d.ts +261 -0
- package/dist/tables/users.d.ts.map +1 -0
- package/package.json +3 -3
- package/dist/relations.d.ts.map +0 -1
- package/dist/stores.d.ts.map +0 -1
- package/dist/tables.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,58 +1,19 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
isSubjectSet
|
|
5
|
-
} from "@nxgt/janus";
|
|
6
|
-
import { and, asc, eq, gt, isNotNull, isNull, or } from "drizzle-orm";
|
|
1
|
+
// src/tables/index.ts
|
|
2
|
+
import { pgTable } from "drizzle-orm/pg-core";
|
|
7
3
|
|
|
8
|
-
// src/tables.ts
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
customType,
|
|
14
|
-
foreignKey,
|
|
15
|
-
index,
|
|
16
|
-
integer,
|
|
17
|
-
jsonb,
|
|
18
|
-
pgTable,
|
|
19
|
-
primaryKey,
|
|
20
|
-
text,
|
|
21
|
-
timestamp,
|
|
22
|
-
unique
|
|
23
|
-
} from "drizzle-orm/pg-core";
|
|
4
|
+
// src/tables/logins.ts
|
|
5
|
+
import { foreignKey, index, primaryKey } from "drizzle-orm/pg-core";
|
|
6
|
+
|
|
7
|
+
// src/tables/columns.ts
|
|
8
|
+
import { customType, timestamp } from "drizzle-orm/pg-core";
|
|
24
9
|
var key = customType({
|
|
25
10
|
dataType: () => 'text collate "C"'
|
|
26
11
|
});
|
|
27
12
|
var at = (name) => timestamp(name, { withTimezone: true, precision: 3 });
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
id: key("id").primaryKey(),
|
|
33
|
-
type: key("type").notNull(),
|
|
34
|
-
schemaVersion: text("schema_version").notNull(),
|
|
35
|
-
active: boolean("active").notNull(),
|
|
36
|
-
fields: jsonb("fields").notNull(),
|
|
37
|
-
logins: text("logins").array().notNull(),
|
|
38
|
-
passwordHash: text("password_hash"),
|
|
39
|
-
passwordUpdatedAt: at("password_updated_at"),
|
|
40
|
-
secondFactorMethod: text("second_factor_method", { enum: ["totp"] }),
|
|
41
|
-
secondFactorSecret: text("second_factor_secret"),
|
|
42
|
-
secondFactorConfirmedAt: at("second_factor_confirmed_at"),
|
|
43
|
-
secondFactorLastStep: integer("second_factor_last_step"),
|
|
44
|
-
emailVerifiedAt: at("email_verified_at"),
|
|
45
|
-
version: integer("version").notNull(),
|
|
46
|
-
createdAt: at("created_at").notNull(),
|
|
47
|
-
updatedAt: at("updated_at").notNull()
|
|
48
|
-
}, (t) => [
|
|
49
|
-
unique("users_id_type_unique").on(t.id, t.type),
|
|
50
|
-
index("users_type_id").on(t.type, t.id),
|
|
51
|
-
check("users_password_whole", sql`(${t.passwordHash} is null) = (${t.passwordUpdatedAt} is null)`),
|
|
52
|
-
check("users_second_factor_whole", sql`(${t.secondFactorMethod} is null) = (${t.secondFactorSecret} is null) and (${t.secondFactorMethod} is not null or (${t.secondFactorConfirmedAt} is null and ${t.secondFactorLastStep} is null))`),
|
|
53
|
-
check("users_second_factor_values", sql`${t.secondFactorMethod} in ('totp') and ${t.secondFactorLastStep} >= 0`)
|
|
54
|
-
]);
|
|
55
|
-
const logins = table("logins", {
|
|
13
|
+
|
|
14
|
+
// src/tables/logins.ts
|
|
15
|
+
function loginsTable(table, users) {
|
|
16
|
+
return table("logins", {
|
|
56
17
|
type: key("type").notNull(),
|
|
57
18
|
login: key("login").notNull(),
|
|
58
19
|
userId: key("user_id").notNull()
|
|
@@ -65,7 +26,28 @@ function defineJanusTables(options = {}) {
|
|
|
65
26
|
}).onDelete("cascade"),
|
|
66
27
|
index("logins_user_id").on(t.userId)
|
|
67
28
|
]);
|
|
68
|
-
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/tables/relations.ts
|
|
32
|
+
import { index as index2, unique } from "drizzle-orm/pg-core";
|
|
33
|
+
function relationsTable(table) {
|
|
34
|
+
return table("relations", {
|
|
35
|
+
objectType: key("object_type").notNull(),
|
|
36
|
+
objectId: key("object_id").notNull(),
|
|
37
|
+
relation: key("relation").notNull(),
|
|
38
|
+
subjectType: key("subject_type").notNull(),
|
|
39
|
+
subjectId: key("subject_id").notNull(),
|
|
40
|
+
subjectRelation: key("subject_relation")
|
|
41
|
+
}, (t) => [
|
|
42
|
+
unique("relations_tuple_unique").on(t.subjectType, t.subjectId, t.subjectRelation, t.relation, t.objectType, t.objectId).nullsNotDistinct(),
|
|
43
|
+
index2("relations_object").on(t.objectType, t.objectId, t.relation)
|
|
44
|
+
]);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/tables/sessions.ts
|
|
48
|
+
import { index as index3, unique as unique2 } from "drizzle-orm/pg-core";
|
|
49
|
+
function sessionsTable(table) {
|
|
50
|
+
return table("sessions", {
|
|
69
51
|
id: key("id").primaryKey(),
|
|
70
52
|
tokenHash: key("token_hash").notNull(),
|
|
71
53
|
userId: key("user_id").notNull(),
|
|
@@ -74,11 +56,17 @@ function defineJanusTables(options = {}) {
|
|
|
74
56
|
revokedAt: at("revoked_at"),
|
|
75
57
|
createdAt: at("created_at").notNull()
|
|
76
58
|
}, (t) => [
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
59
|
+
unique2("sessions_token_hash_unique").on(t.tokenHash),
|
|
60
|
+
index3("sessions_user_id").on(t.userId),
|
|
61
|
+
index3("sessions_expires_at").on(t.expiresAt)
|
|
80
62
|
]);
|
|
81
|
-
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/tables/tokens.ts
|
|
66
|
+
import { sql } from "drizzle-orm";
|
|
67
|
+
import { check, index as index4, integer, text } from "drizzle-orm/pg-core";
|
|
68
|
+
function tokensTable(table) {
|
|
69
|
+
return table("tokens", {
|
|
82
70
|
tokenHash: key("token_hash").primaryKey(),
|
|
83
71
|
kind: text("kind", {
|
|
84
72
|
enum: ["verifyEmail", "resetPassword", "secondFactor", "signInCode"]
|
|
@@ -93,19 +81,57 @@ function defineJanusTables(options = {}) {
|
|
|
93
81
|
}, (t) => [
|
|
94
82
|
check("tokens_kind", sql`${t.kind} in ('verifyEmail', 'resetPassword', 'secondFactor', 'signInCode')`),
|
|
95
83
|
check("tokens_attempts", sql`${t.attempts} >= 0`),
|
|
96
|
-
|
|
84
|
+
index4("tokens_user_id").on(t.userId)
|
|
97
85
|
]);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/tables/users.ts
|
|
89
|
+
import { sql as sql2 } from "drizzle-orm";
|
|
90
|
+
import {
|
|
91
|
+
boolean,
|
|
92
|
+
check as check2,
|
|
93
|
+
index as index5,
|
|
94
|
+
integer as integer2,
|
|
95
|
+
jsonb,
|
|
96
|
+
text as text2,
|
|
97
|
+
unique as unique3
|
|
98
|
+
} from "drizzle-orm/pg-core";
|
|
99
|
+
function usersTable(table) {
|
|
100
|
+
return table("users", {
|
|
101
|
+
id: key("id").primaryKey(),
|
|
102
|
+
type: key("type").notNull(),
|
|
103
|
+
schemaVersion: text2("schema_version").notNull(),
|
|
104
|
+
active: boolean("active").notNull(),
|
|
105
|
+
fields: jsonb("fields").notNull(),
|
|
106
|
+
logins: text2("logins").array().notNull(),
|
|
107
|
+
passwordHash: text2("password_hash"),
|
|
108
|
+
passwordUpdatedAt: at("password_updated_at"),
|
|
109
|
+
secondFactorMethod: text2("second_factor_method", { enum: ["totp"] }),
|
|
110
|
+
secondFactorSecret: text2("second_factor_secret"),
|
|
111
|
+
secondFactorConfirmedAt: at("second_factor_confirmed_at"),
|
|
112
|
+
secondFactorLastStep: integer2("second_factor_last_step"),
|
|
113
|
+
emailVerifiedAt: at("email_verified_at"),
|
|
114
|
+
version: integer2("version").notNull(),
|
|
115
|
+
createdAt: at("created_at").notNull(),
|
|
116
|
+
updatedAt: at("updated_at").notNull()
|
|
105
117
|
}, (t) => [
|
|
106
|
-
|
|
107
|
-
|
|
118
|
+
unique3("users_id_type_unique").on(t.id, t.type),
|
|
119
|
+
index5("users_type_id").on(t.type, t.id),
|
|
120
|
+
check2("users_password_whole", sql2`(${t.passwordHash} is null) = (${t.passwordUpdatedAt} is null)`),
|
|
121
|
+
check2("users_second_factor_whole", sql2`(${t.secondFactorMethod} is null) = (${t.secondFactorSecret} is null) and (${t.secondFactorMethod} is not null or (${t.secondFactorConfirmedAt} is null and ${t.secondFactorLastStep} is null))`),
|
|
122
|
+
check2("users_second_factor_values", sql2`${t.secondFactorMethod} in ('totp') and ${t.secondFactorLastStep} >= 0`)
|
|
108
123
|
]);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/tables/index.ts
|
|
127
|
+
function defineJanusTables(options = {}) {
|
|
128
|
+
const { schema } = options;
|
|
129
|
+
const table = schema === undefined ? pgTable : schema.table;
|
|
130
|
+
const users = usersTable(table);
|
|
131
|
+
const logins = loginsTable(table, users);
|
|
132
|
+
const sessions = sessionsTable(table);
|
|
133
|
+
const tokens = tokensTable(table);
|
|
134
|
+
const relations = relationsTable(table);
|
|
109
135
|
return { users, logins, sessions, tokens, relations };
|
|
110
136
|
}
|
|
111
137
|
|
|
@@ -129,62 +155,12 @@ function loginTaken(operation, type, login) {
|
|
|
129
155
|
return new StoreConflict("login", `${operation}: the login is taken by another ${type}`, { login, userType: type, operation });
|
|
130
156
|
}
|
|
131
157
|
|
|
132
|
-
// src/relations.ts
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
type: tables.relations.subjectType,
|
|
139
|
-
id: tables.relations.subjectId,
|
|
140
|
-
relation: tables.relations.subjectRelation
|
|
141
|
-
}).from(tables.relations).where(and(eq(tables.relations.objectType, object.type), eq(tables.relations.objectId, object.id), eq(tables.relations.relation, relation), sets ? isNotNull(tables.relations.subjectRelation) : isNull(tables.relations.subjectRelation)));
|
|
142
|
-
const apply = async (tx, add, remove) => {
|
|
143
|
-
for (const tuple of inOrder(remove)) {
|
|
144
|
-
await tx.delete(tables.relations).where(matching(tuple));
|
|
145
|
-
}
|
|
146
|
-
if (add.length > 0) {
|
|
147
|
-
await tx.insert(tables.relations).values(inOrder(add).map(rowOf)).onConflictDoNothing();
|
|
148
|
-
}
|
|
149
|
-
};
|
|
150
|
-
return {
|
|
151
|
-
write: ({ add = [], remove = [] }) => run$("write", async () => {
|
|
152
|
-
if (add.length + remove.length <= 1)
|
|
153
|
-
return apply(db, add, remove);
|
|
154
|
-
await withTransaction(db, (tx) => apply(tx, add, remove));
|
|
155
|
-
}),
|
|
156
|
-
has: (tuple) => run$("has", async () => {
|
|
157
|
-
const found = await db.select({ relation: tables.relations.relation }).from(tables.relations).where(matching(tuple)).limit(1);
|
|
158
|
-
return found.length === 1;
|
|
159
|
-
}),
|
|
160
|
-
findSubjectSets: (object, relation) => run$("findSubjectSets", async () => (await holding(object, relation, true)).map((row) => ({
|
|
161
|
-
type: row.type,
|
|
162
|
-
id: row.id,
|
|
163
|
-
relation: row.relation ?? ""
|
|
164
|
-
}))),
|
|
165
|
-
findEntities: (object, relation) => run$("findEntities", async () => (await holding(object, relation, false)).map((row) => ({ type: row.type, id: row.id }))),
|
|
166
|
-
findObjects: ({ type, relation, subject, after, limit }) => run$("findObjects", async () => {
|
|
167
|
-
const conditions = [
|
|
168
|
-
subjectIs(subject),
|
|
169
|
-
eq(tables.relations.relation, relation),
|
|
170
|
-
eq(tables.relations.objectType, type)
|
|
171
|
-
];
|
|
172
|
-
if (after !== null)
|
|
173
|
-
conditions.push(gt(tables.relations.objectId, after));
|
|
174
|
-
const found = await db.select({ id: tables.relations.objectId }).from(tables.relations).where(and(...conditions)).orderBy(asc(tables.relations.objectId)).limit(limit + 1);
|
|
175
|
-
const items = found.slice(0, limit).map((row) => row.id);
|
|
176
|
-
const last = items.at(-1);
|
|
177
|
-
return {
|
|
178
|
-
items,
|
|
179
|
-
nextCursor: found.length > limit && last !== undefined ? last : null
|
|
180
|
-
};
|
|
181
|
-
}),
|
|
182
|
-
deleteEntity: (entity) => run$("deleteEntity", async () => {
|
|
183
|
-
const deleted = await db.delete(tables.relations).where(or(and(eq(tables.relations.objectType, entity.type), eq(tables.relations.objectId, entity.id)), and(eq(tables.relations.subjectType, entity.type), eq(tables.relations.subjectId, entity.id)))).returning({ relation: tables.relations.relation });
|
|
184
|
-
return deleted.length;
|
|
185
|
-
})
|
|
186
|
-
};
|
|
187
|
-
}
|
|
158
|
+
// src/relations/reads.ts
|
|
159
|
+
import { and as and2, asc, eq as eq2, gt, isNotNull, isNull as isNull2 } from "drizzle-orm";
|
|
160
|
+
|
|
161
|
+
// src/relations/tuples.ts
|
|
162
|
+
import { isSubjectSet } from "@nxgt/janus";
|
|
163
|
+
import { and, eq, isNull } from "drizzle-orm";
|
|
188
164
|
function inOrder(tuples) {
|
|
189
165
|
const keyOf = (tuple) => JSON.stringify([
|
|
190
166
|
tuple.object.type,
|
|
@@ -199,194 +175,103 @@ function inOrder(tuples) {
|
|
|
199
175
|
return x < y ? -1 : x > y ? 1 : 0;
|
|
200
176
|
});
|
|
201
177
|
}
|
|
202
|
-
function
|
|
203
|
-
function rowOf(tuple) {
|
|
204
|
-
return {
|
|
205
|
-
objectType: tuple.object.type,
|
|
206
|
-
objectId: tuple.object.id,
|
|
207
|
-
relation: tuple.relation,
|
|
208
|
-
subjectType: tuple.subject.type,
|
|
209
|
-
subjectId: tuple.subject.id,
|
|
210
|
-
subjectRelation: isSubjectSet(tuple.subject) ? tuple.subject.relation : null
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
function subjectIs(subject) {
|
|
214
|
-
return and(eq(relations.subjectType, subject.type), eq(relations.subjectId, subject.id), isSubjectSet(subject) ? eq(relations.subjectRelation, subject.relation) : isNull(relations.subjectRelation));
|
|
215
|
-
}
|
|
216
|
-
function matching(tuple) {
|
|
217
|
-
return and(eq(relations.objectType, tuple.object.type), eq(relations.objectId, tuple.object.id), eq(relations.relation, tuple.relation), subjectIs(tuple.subject));
|
|
218
|
-
}
|
|
219
|
-
return { rowOf, subjectIs, matching };
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// src/stores.ts
|
|
223
|
-
import { withTransaction as withTransaction2 } from "@nxgt/drizzle/pg";
|
|
224
|
-
import { NotFoundError, StoreConflict as StoreConflict2 } from "@nxgt/janus";
|
|
225
|
-
import { and as and2, asc as asc2, eq as eq2, gt as gt2, isNull as isNull2, lte, ne, sql as sql2 } from "drizzle-orm";
|
|
226
|
-
function createDrizzleStores(db, options = {}) {
|
|
227
|
-
const tables = options.tables ?? defineJanusTables();
|
|
178
|
+
function rowOf(tuple) {
|
|
228
179
|
return {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
180
|
+
objectType: tuple.object.type,
|
|
181
|
+
objectId: tuple.object.id,
|
|
182
|
+
relation: tuple.relation,
|
|
183
|
+
subjectType: tuple.subject.type,
|
|
184
|
+
subjectId: tuple.subject.id,
|
|
185
|
+
subjectRelation: isSubjectSet(tuple.subject) ? tuple.subject.relation : null
|
|
232
186
|
};
|
|
233
187
|
}
|
|
234
|
-
function
|
|
235
|
-
|
|
236
|
-
const find = async (id) => {
|
|
237
|
-
const [row] = await db.select().from(tables.users).where(eq2(tables.users.id, id));
|
|
238
|
-
return row === undefined ? null : toUser(row);
|
|
239
|
-
};
|
|
240
|
-
const claim = async (tx, operation, user, logins) => {
|
|
241
|
-
const distinct = [...new Set(logins)].sort();
|
|
242
|
-
if (distinct.length === 0)
|
|
243
|
-
return;
|
|
244
|
-
const written = await tx.insert(tables.logins).values(distinct.map((login) => ({ type: user.type, login, userId: user.id }))).onConflictDoNothing().returning({ login: tables.logins.login });
|
|
245
|
-
if (written.length === distinct.length)
|
|
246
|
-
return;
|
|
247
|
-
const claimed = new Set(written.map((row) => row.login));
|
|
248
|
-
const taken = distinct.find((login) => !claimed.has(login)) ?? "";
|
|
249
|
-
throw loginTaken(operation, user.type, taken);
|
|
250
|
-
};
|
|
251
|
-
return {
|
|
252
|
-
insertUser: (record) => run$("insertUser", async () => {
|
|
253
|
-
const inserted = await withTransaction2(db, async (tx) => {
|
|
254
|
-
const [row] = await tx.insert(tables.users).values(toUserRow(record)).onConflictDoNothing({ target: tables.users.id }).returning();
|
|
255
|
-
if (row === undefined)
|
|
256
|
-
return null;
|
|
257
|
-
await claim(tx, "insertUser", record, record.logins);
|
|
258
|
-
return toUser(row);
|
|
259
|
-
});
|
|
260
|
-
if (inserted !== null)
|
|
261
|
-
return inserted;
|
|
262
|
-
const stored = await find(record.id);
|
|
263
|
-
if (stored !== null)
|
|
264
|
-
return stored;
|
|
265
|
-
throw new NotFoundError("insertUser: the user was deleted meanwhile", {
|
|
266
|
-
userId: record.id,
|
|
267
|
-
operation: "insertUser"
|
|
268
|
-
});
|
|
269
|
-
}),
|
|
270
|
-
findUser: (id) => run$("findUser", () => find(id)),
|
|
271
|
-
findUserByLogin: (type, login) => run$("findUserByLogin", async () => {
|
|
272
|
-
const [row] = await db.select({ user: tables.users }).from(tables.logins).innerJoin(tables.users, eq2(tables.users.id, tables.logins.userId)).where(and2(eq2(tables.logins.type, type), eq2(tables.logins.login, login)));
|
|
273
|
-
return row === undefined ? null : toUser(row.user);
|
|
274
|
-
}),
|
|
275
|
-
listUsers: ({ type, after, limit }) => run$("listUsers", async () => {
|
|
276
|
-
const found = await db.select().from(tables.users).where(after === null ? eq2(tables.users.type, type) : and2(eq2(tables.users.type, type), gt2(tables.users.id, after))).orderBy(asc2(tables.users.id)).limit(limit + 1);
|
|
277
|
-
const items = found.slice(0, limit).map(toUser);
|
|
278
|
-
const last = items.at(-1);
|
|
279
|
-
return {
|
|
280
|
-
items,
|
|
281
|
-
nextCursor: found.length > limit && last !== undefined ? last.id : null
|
|
282
|
-
};
|
|
283
|
-
}),
|
|
284
|
-
updateUser: (id, patch, ifVersion) => run$("updateUser", () => withTransaction2(db, async (tx) => {
|
|
285
|
-
const [row] = await tx.update(tables.users).set({
|
|
286
|
-
...toUserSet(patch),
|
|
287
|
-
version: sql2`${tables.users.version} + 1`
|
|
288
|
-
}).where(and2(eq2(tables.users.id, id), eq2(tables.users.version, ifVersion))).returning();
|
|
289
|
-
if (row === undefined) {
|
|
290
|
-
const [stored] = await tx.select({ version: tables.users.version }).from(tables.users).where(eq2(tables.users.id, id));
|
|
291
|
-
if (stored === undefined) {
|
|
292
|
-
throw new NotFoundError("updateUser: no user has this id", {
|
|
293
|
-
userId: id,
|
|
294
|
-
operation: "updateUser"
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
throw new StoreConflict2("version", `updateUser: expected version ${ifVersion}, found ${stored.version}`, {
|
|
298
|
-
userId: id,
|
|
299
|
-
expectedVersion: ifVersion,
|
|
300
|
-
actualVersion: stored.version,
|
|
301
|
-
operation: "updateUser"
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
if (patch.logins !== undefined) {
|
|
305
|
-
await tx.delete(tables.logins).where(eq2(tables.logins.userId, id));
|
|
306
|
-
await claim(tx, "updateUser", row, patch.logins);
|
|
307
|
-
}
|
|
308
|
-
return toUser(row);
|
|
309
|
-
})),
|
|
310
|
-
deleteUser: (id) => run$("deleteUser", async () => {
|
|
311
|
-
const deleted = await db.delete(tables.users).where(eq2(tables.users.id, id)).returning({ id: tables.users.id });
|
|
312
|
-
return deleted.length === 1;
|
|
313
|
-
})
|
|
314
|
-
};
|
|
188
|
+
function subjectIs(relations, subject) {
|
|
189
|
+
return and(eq(relations.subjectType, subject.type), eq(relations.subjectId, subject.id), isSubjectSet(subject) ? eq(relations.subjectRelation, subject.relation) : isNull(relations.subjectRelation));
|
|
315
190
|
}
|
|
316
|
-
function
|
|
317
|
-
|
|
191
|
+
function matching(relations, tuple) {
|
|
192
|
+
return and(eq(relations.objectType, tuple.object.type), eq(relations.objectId, tuple.object.id), eq(relations.relation, tuple.relation), subjectIs(relations, tuple.subject));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/relations/reads.ts
|
|
196
|
+
async function has({ db, relations }, tuple) {
|
|
197
|
+
const found = await db.select({ relation: relations.relation }).from(relations).where(matching(relations, tuple)).limit(1);
|
|
198
|
+
return found.length === 1;
|
|
199
|
+
}
|
|
200
|
+
async function findSubjectSets(context, object, relation) {
|
|
201
|
+
return (await holding(context, object, relation, true)).map((row) => ({
|
|
202
|
+
type: row.type,
|
|
203
|
+
id: row.id,
|
|
204
|
+
relation: row.relation ?? ""
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
207
|
+
async function findEntities(context, object, relation) {
|
|
208
|
+
return (await holding(context, object, relation, false)).map((row) => ({ type: row.type, id: row.id }));
|
|
209
|
+
}
|
|
210
|
+
function holding({ db, relations }, object, relation, sets) {
|
|
211
|
+
return db.select({
|
|
212
|
+
type: relations.subjectType,
|
|
213
|
+
id: relations.subjectId,
|
|
214
|
+
relation: relations.subjectRelation
|
|
215
|
+
}).from(relations).where(and2(eq2(relations.objectType, object.type), eq2(relations.objectId, object.id), eq2(relations.relation, relation), sets ? isNotNull(relations.subjectRelation) : isNull2(relations.subjectRelation)));
|
|
216
|
+
}
|
|
217
|
+
async function findObjects({ db, relations }, { type, relation, subject, after, limit }) {
|
|
218
|
+
const conditions = [
|
|
219
|
+
subjectIs(relations, subject),
|
|
220
|
+
eq2(relations.relation, relation),
|
|
221
|
+
eq2(relations.objectType, type)
|
|
222
|
+
];
|
|
223
|
+
if (after !== null)
|
|
224
|
+
conditions.push(gt(relations.objectId, after));
|
|
225
|
+
const found = await db.select({ id: relations.objectId }).from(relations).where(and2(...conditions)).orderBy(asc(relations.objectId)).limit(limit + 1);
|
|
226
|
+
const items = found.slice(0, limit).map((row) => row.id);
|
|
227
|
+
const last = items.at(-1);
|
|
318
228
|
return {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}),
|
|
322
|
-
findSessionByTokenHash: (tokenHash) => run$("findSessionByTokenHash", async () => {
|
|
323
|
-
const [row] = await db.select().from(tables.sessions).where(eq2(tables.sessions.tokenHash, tokenHash));
|
|
324
|
-
return row === undefined ? null : toSession(row);
|
|
325
|
-
}),
|
|
326
|
-
extendSession: (id, expiresAt) => run$("extendSession", async () => {
|
|
327
|
-
const [row] = await db.update(tables.sessions).set({ expiresAt }).where(and2(eq2(tables.sessions.id, id), isNull2(tables.sessions.revokedAt))).returning();
|
|
328
|
-
return row === undefined ? null : toSession(row);
|
|
329
|
-
}),
|
|
330
|
-
revokeSession: (id, at) => run$("revokeSession", async () => {
|
|
331
|
-
const matched = await db.update(tables.sessions).set({
|
|
332
|
-
revokedAt: sql2`coalesce(${tables.sessions.revokedAt}, ${stamp(at)})`
|
|
333
|
-
}).where(eq2(tables.sessions.id, id)).returning({ id: tables.sessions.id });
|
|
334
|
-
return matched.length === 1;
|
|
335
|
-
}),
|
|
336
|
-
revokeUserSessions: (userId, at, except) => run$("revokeUserSessions", async () => {
|
|
337
|
-
const standing = and2(eq2(tables.sessions.userId, userId), isNull2(tables.sessions.revokedAt));
|
|
338
|
-
const revoked = await db.update(tables.sessions).set({ revokedAt: at }).where(except === undefined ? standing : and2(standing, ne(tables.sessions.id, except))).returning({ id: tables.sessions.id });
|
|
339
|
-
return revoked.length;
|
|
340
|
-
}),
|
|
341
|
-
deleteUserSessions: (userId) => run$("deleteUserSessions", async () => {
|
|
342
|
-
const deleted = await db.delete(tables.sessions).where(eq2(tables.sessions.userId, userId)).returning({ id: tables.sessions.id });
|
|
343
|
-
return deleted.length;
|
|
344
|
-
}),
|
|
345
|
-
deleteExpiredSessions: (before) => run$("deleteExpiredSessions", async () => {
|
|
346
|
-
const deleted = await db.delete(tables.sessions).where(lte(tables.sessions.expiresAt, before)).returning({ id: tables.sessions.id });
|
|
347
|
-
return deleted.length;
|
|
348
|
-
})
|
|
229
|
+
items,
|
|
230
|
+
nextCursor: found.length > limit && last !== undefined ? last : null
|
|
349
231
|
};
|
|
350
232
|
}
|
|
351
|
-
|
|
352
|
-
|
|
233
|
+
|
|
234
|
+
// src/relations/writes.ts
|
|
235
|
+
import { withTransaction } from "@nxgt/drizzle/pg";
|
|
236
|
+
import { and as and3, eq as eq3, or } from "drizzle-orm";
|
|
237
|
+
async function write({ db, relations }, add, remove) {
|
|
238
|
+
if (add.length + remove.length <= 1)
|
|
239
|
+
return apply(db, relations, add, remove);
|
|
240
|
+
await withTransaction(db, (tx) => apply(tx, relations, add, remove));
|
|
241
|
+
}
|
|
242
|
+
async function apply(tx, relations, add, remove) {
|
|
243
|
+
for (const tuple of inOrder(remove)) {
|
|
244
|
+
await tx.delete(relations).where(matching(relations, tuple));
|
|
245
|
+
}
|
|
246
|
+
if (add.length > 0) {
|
|
247
|
+
await tx.insert(relations).values(inOrder(add).map(rowOf)).onConflictDoNothing();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async function deleteEntity({ db, relations }, entity) {
|
|
251
|
+
const deleted = await db.delete(relations).where(or(and3(eq3(relations.objectType, entity.type), eq3(relations.objectId, entity.id)), and3(eq3(relations.subjectType, entity.type), eq3(relations.subjectId, entity.id)))).returning({ relation: relations.relation });
|
|
252
|
+
return deleted.length;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/relations/index.ts
|
|
256
|
+
function createDrizzleRelations(db, options = {}) {
|
|
257
|
+
const tables = options.tables ?? defineJanusTables();
|
|
258
|
+
const context = { db, relations: tables.relations };
|
|
259
|
+
const run$ = (operation, body) => run("relations", operation, body);
|
|
353
260
|
return {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
spentAt: sql2`coalesce(${tables.tokens.spentAt}, ${stamp(at)})`
|
|
361
|
-
}).from(before).where(eq2(tables.tokens.tokenHash, before.tokenHash)).returning({
|
|
362
|
-
tokenHash: before.tokenHash,
|
|
363
|
-
kind: before.kind,
|
|
364
|
-
userId: before.userId,
|
|
365
|
-
address: before.address,
|
|
366
|
-
codeHash: before.codeHash,
|
|
367
|
-
attempts: before.attempts,
|
|
368
|
-
expiresAt: before.expiresAt,
|
|
369
|
-
spentAt: before.spentAt,
|
|
370
|
-
createdAt: before.createdAt
|
|
371
|
-
});
|
|
372
|
-
return spent === undefined ? null : { ...spent, userId: spent.userId };
|
|
373
|
-
}),
|
|
374
|
-
countAttempt: (tokenHash, kind) => run$("countAttempt", async () => {
|
|
375
|
-
const match = and2(eq2(tables.tokens.tokenHash, tokenHash), eq2(tables.tokens.kind, kind));
|
|
376
|
-
const [counted] = await db.update(tables.tokens).set({ attempts: sql2`${tables.tokens.attempts} + 1` }).where(and2(match, isNull2(tables.tokens.spentAt))).returning();
|
|
377
|
-
const row = counted ?? (await db.select().from(tables.tokens).where(match))[0];
|
|
378
|
-
return row === undefined ? null : { ...row, userId: row.userId };
|
|
379
|
-
}),
|
|
380
|
-
spendUserTokens: (userId, kind, at, except) => run$("spendUserTokens", async () => {
|
|
381
|
-
const spent = await db.update(tables.tokens).set({ spentAt: at }).where(and2(eq2(tables.tokens.userId, userId), eq2(tables.tokens.kind, kind), isNull2(tables.tokens.spentAt), except === undefined ? undefined : ne(tables.tokens.tokenHash, except))).returning({ tokenHash: tables.tokens.tokenHash });
|
|
382
|
-
return spent.length;
|
|
383
|
-
}),
|
|
384
|
-
deleteUserTokens: (userId) => run$("deleteUserTokens", async () => {
|
|
385
|
-
const deleted = await db.delete(tables.tokens).where(eq2(tables.tokens.userId, userId)).returning({ tokenHash: tables.tokens.tokenHash });
|
|
386
|
-
return deleted.length;
|
|
387
|
-
})
|
|
261
|
+
write: ({ add = [], remove = [] }) => run$("write", () => write(context, add, remove)),
|
|
262
|
+
has: (tuple) => run$("has", () => has(context, tuple)),
|
|
263
|
+
findSubjectSets: (object, relation) => run$("findSubjectSets", () => findSubjectSets(context, object, relation)),
|
|
264
|
+
findEntities: (object, relation) => run$("findEntities", () => findEntities(context, object, relation)),
|
|
265
|
+
findObjects: ({ type, relation, subject, after, limit }) => run$("findObjects", () => findObjects(context, { type, relation, subject, after, limit })),
|
|
266
|
+
deleteEntity: (entity) => run$("deleteEntity", () => deleteEntity(context, entity))
|
|
388
267
|
};
|
|
389
268
|
}
|
|
269
|
+
|
|
270
|
+
// src/stores/sessions.ts
|
|
271
|
+
import { and as and4, eq as eq4, isNull as isNull3, lte, ne, sql as sql4 } from "drizzle-orm";
|
|
272
|
+
|
|
273
|
+
// src/stores/records.ts
|
|
274
|
+
import { sql as sql3 } from "drizzle-orm";
|
|
390
275
|
function toUserRow(record) {
|
|
391
276
|
return {
|
|
392
277
|
id: record.id,
|
|
@@ -468,7 +353,193 @@ function toSession(row) {
|
|
|
468
353
|
};
|
|
469
354
|
}
|
|
470
355
|
function stamp(at) {
|
|
471
|
-
return
|
|
356
|
+
return sql3`${at.toISOString()}::timestamptz`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// src/stores/sessions.ts
|
|
360
|
+
function sessionStore(db, tables) {
|
|
361
|
+
const run$ = (operation, body) => run("sessions", operation, body);
|
|
362
|
+
return {
|
|
363
|
+
insertSession: (record) => run$("insertSession", async () => {
|
|
364
|
+
await db.insert(tables.sessions).values(record).onConflictDoNothing({ target: tables.sessions.id });
|
|
365
|
+
}),
|
|
366
|
+
findSessionByTokenHash: (tokenHash) => run$("findSessionByTokenHash", async () => {
|
|
367
|
+
const [row] = await db.select().from(tables.sessions).where(eq4(tables.sessions.tokenHash, tokenHash));
|
|
368
|
+
return row === undefined ? null : toSession(row);
|
|
369
|
+
}),
|
|
370
|
+
extendSession: (id, expiresAt) => run$("extendSession", async () => {
|
|
371
|
+
const [row] = await db.update(tables.sessions).set({ expiresAt }).where(and4(eq4(tables.sessions.id, id), isNull3(tables.sessions.revokedAt))).returning();
|
|
372
|
+
return row === undefined ? null : toSession(row);
|
|
373
|
+
}),
|
|
374
|
+
revokeSession: (id, at) => run$("revokeSession", () => revokeSession(db, tables, id, at)),
|
|
375
|
+
revokeUserSessions: (userId, at, except) => run$("revokeUserSessions", () => revokeUserSessions(db, tables, userId, at, except)),
|
|
376
|
+
deleteUserSessions: (userId) => run$("deleteUserSessions", async () => {
|
|
377
|
+
const deleted = await db.delete(tables.sessions).where(eq4(tables.sessions.userId, userId)).returning({ id: tables.sessions.id });
|
|
378
|
+
return deleted.length;
|
|
379
|
+
}),
|
|
380
|
+
deleteExpiredSessions: (before) => run$("deleteExpiredSessions", async () => {
|
|
381
|
+
const deleted = await db.delete(tables.sessions).where(lte(tables.sessions.expiresAt, before)).returning({ id: tables.sessions.id });
|
|
382
|
+
return deleted.length;
|
|
383
|
+
})
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
async function revokeSession(db, tables, id, at) {
|
|
387
|
+
const matched = await db.update(tables.sessions).set({
|
|
388
|
+
revokedAt: sql4`coalesce(${tables.sessions.revokedAt}, ${stamp(at)})`
|
|
389
|
+
}).where(eq4(tables.sessions.id, id)).returning({ id: tables.sessions.id });
|
|
390
|
+
return matched.length === 1;
|
|
391
|
+
}
|
|
392
|
+
async function revokeUserSessions(db, tables, userId, at, except) {
|
|
393
|
+
const standing = and4(eq4(tables.sessions.userId, userId), isNull3(tables.sessions.revokedAt));
|
|
394
|
+
const revoked = await db.update(tables.sessions).set({ revokedAt: at }).where(except === undefined ? standing : and4(standing, ne(tables.sessions.id, except))).returning({ id: tables.sessions.id });
|
|
395
|
+
return revoked.length;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// src/stores/tokens.ts
|
|
399
|
+
import { and as and5, eq as eq5, isNull as isNull4, ne as ne2, sql as sql5 } from "drizzle-orm";
|
|
400
|
+
function tokenStore(db, tables) {
|
|
401
|
+
const run$ = (operation, body) => run("tokens", operation, body);
|
|
402
|
+
return {
|
|
403
|
+
insertToken: (record) => run$("insertToken", async () => {
|
|
404
|
+
await db.insert(tables.tokens).values(record).onConflictDoNothing();
|
|
405
|
+
}),
|
|
406
|
+
consumeToken: (tokenHash, kind, at) => run$("consumeToken", () => consumeToken(db, tables, tokenHash, kind, at)),
|
|
407
|
+
countAttempt: (tokenHash, kind) => run$("countAttempt", () => countAttempt(db, tables, tokenHash, kind)),
|
|
408
|
+
spendUserTokens: (userId, kind, at, except) => run$("spendUserTokens", async () => {
|
|
409
|
+
const spent = await db.update(tables.tokens).set({ spentAt: at }).where(and5(eq5(tables.tokens.userId, userId), eq5(tables.tokens.kind, kind), isNull4(tables.tokens.spentAt), except === undefined ? undefined : ne2(tables.tokens.tokenHash, except))).returning({ tokenHash: tables.tokens.tokenHash });
|
|
410
|
+
return spent.length;
|
|
411
|
+
}),
|
|
412
|
+
deleteUserTokens: (userId) => run$("deleteUserTokens", async () => {
|
|
413
|
+
const deleted = await db.delete(tables.tokens).where(eq5(tables.tokens.userId, userId)).returning({ tokenHash: tables.tokens.tokenHash });
|
|
414
|
+
return deleted.length;
|
|
415
|
+
})
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
async function consumeToken(db, tables, tokenHash, kind, at) {
|
|
419
|
+
const before = db.$with("before").as(db.select().from(tables.tokens).where(and5(eq5(tables.tokens.tokenHash, tokenHash), eq5(tables.tokens.kind, kind))).for("update"));
|
|
420
|
+
const [spent] = await db.with(before).update(tables.tokens).set({
|
|
421
|
+
spentAt: sql5`coalesce(${tables.tokens.spentAt}, ${stamp(at)})`
|
|
422
|
+
}).from(before).where(eq5(tables.tokens.tokenHash, before.tokenHash)).returning({
|
|
423
|
+
tokenHash: before.tokenHash,
|
|
424
|
+
kind: before.kind,
|
|
425
|
+
userId: before.userId,
|
|
426
|
+
address: before.address,
|
|
427
|
+
codeHash: before.codeHash,
|
|
428
|
+
attempts: before.attempts,
|
|
429
|
+
expiresAt: before.expiresAt,
|
|
430
|
+
spentAt: before.spentAt,
|
|
431
|
+
createdAt: before.createdAt
|
|
432
|
+
});
|
|
433
|
+
return spent === undefined ? null : { ...spent, userId: spent.userId };
|
|
434
|
+
}
|
|
435
|
+
async function countAttempt(db, tables, tokenHash, kind) {
|
|
436
|
+
const match = and5(eq5(tables.tokens.tokenHash, tokenHash), eq5(tables.tokens.kind, kind));
|
|
437
|
+
const [counted] = await db.update(tables.tokens).set({ attempts: sql5`${tables.tokens.attempts} + 1` }).where(and5(match, isNull4(tables.tokens.spentAt))).returning();
|
|
438
|
+
const row = counted ?? (await db.select().from(tables.tokens).where(match))[0];
|
|
439
|
+
return row === undefined ? null : { ...row, userId: row.userId };
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// src/stores/users.ts
|
|
443
|
+
import { withTransaction as withTransaction2 } from "@nxgt/drizzle/pg";
|
|
444
|
+
import { NotFoundError, StoreConflict as StoreConflict2 } from "@nxgt/janus";
|
|
445
|
+
import { and as and6, asc as asc2, eq as eq6, gt as gt2, sql as sql6 } from "drizzle-orm";
|
|
446
|
+
|
|
447
|
+
// src/stores/logins.ts
|
|
448
|
+
async function claimLogins(tx, tables, operation, user, logins) {
|
|
449
|
+
const distinct = [...new Set(logins)].sort();
|
|
450
|
+
if (distinct.length === 0)
|
|
451
|
+
return;
|
|
452
|
+
const written = await tx.insert(tables.logins).values(distinct.map((login) => ({ type: user.type, login, userId: user.id }))).onConflictDoNothing().returning({ login: tables.logins.login });
|
|
453
|
+
if (written.length === distinct.length)
|
|
454
|
+
return;
|
|
455
|
+
const claimed = new Set(written.map((row) => row.login));
|
|
456
|
+
const taken = distinct.find((login) => !claimed.has(login)) ?? "";
|
|
457
|
+
throw loginTaken(operation, user.type, taken);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// src/stores/users.ts
|
|
461
|
+
function userStore(db, tables) {
|
|
462
|
+
const run$ = (operation, body) => run("users", operation, body);
|
|
463
|
+
return {
|
|
464
|
+
insertUser: (record) => run$("insertUser", () => insertUser(db, tables, record)),
|
|
465
|
+
findUser: (id) => run$("findUser", () => findUser(db, tables, id)),
|
|
466
|
+
findUserByLogin: (type, login) => run$("findUserByLogin", async () => {
|
|
467
|
+
const [row] = await db.select({ user: tables.users }).from(tables.logins).innerJoin(tables.users, eq6(tables.users.id, tables.logins.userId)).where(and6(eq6(tables.logins.type, type), eq6(tables.logins.login, login)));
|
|
468
|
+
return row === undefined ? null : toUser(row.user);
|
|
469
|
+
}),
|
|
470
|
+
listUsers: ({ type, after, limit }) => run$("listUsers", async () => {
|
|
471
|
+
const found = await db.select().from(tables.users).where(after === null ? eq6(tables.users.type, type) : and6(eq6(tables.users.type, type), gt2(tables.users.id, after))).orderBy(asc2(tables.users.id)).limit(limit + 1);
|
|
472
|
+
const items = found.slice(0, limit).map(toUser);
|
|
473
|
+
const last = items.at(-1);
|
|
474
|
+
return {
|
|
475
|
+
items,
|
|
476
|
+
nextCursor: found.length > limit && last !== undefined ? last.id : null
|
|
477
|
+
};
|
|
478
|
+
}),
|
|
479
|
+
updateUser: (id, patch, ifVersion) => run$("updateUser", () => withTransaction2(db, (tx) => updateUser(tx, tables, id, patch, ifVersion))),
|
|
480
|
+
deleteUser: (id) => run$("deleteUser", async () => {
|
|
481
|
+
const deleted = await db.delete(tables.users).where(eq6(tables.users.id, id)).returning({ id: tables.users.id });
|
|
482
|
+
return deleted.length === 1;
|
|
483
|
+
})
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
async function findUser(db, tables, id) {
|
|
487
|
+
const [row] = await db.select().from(tables.users).where(eq6(tables.users.id, id));
|
|
488
|
+
return row === undefined ? null : toUser(row);
|
|
489
|
+
}
|
|
490
|
+
async function insertUser(db, tables, record) {
|
|
491
|
+
const inserted = await withTransaction2(db, async (tx) => {
|
|
492
|
+
const [row] = await tx.insert(tables.users).values(toUserRow(record)).onConflictDoNothing({ target: tables.users.id }).returning();
|
|
493
|
+
if (row === undefined)
|
|
494
|
+
return null;
|
|
495
|
+
await claimLogins(tx, tables, "insertUser", record, record.logins);
|
|
496
|
+
return toUser(row);
|
|
497
|
+
});
|
|
498
|
+
if (inserted !== null)
|
|
499
|
+
return inserted;
|
|
500
|
+
const stored = await findUser(db, tables, record.id);
|
|
501
|
+
if (stored !== null)
|
|
502
|
+
return stored;
|
|
503
|
+
throw new NotFoundError("insertUser: the user was deleted meanwhile", {
|
|
504
|
+
userId: record.id,
|
|
505
|
+
operation: "insertUser"
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
async function updateUser(tx, tables, id, patch, ifVersion) {
|
|
509
|
+
const [row] = await tx.update(tables.users).set({
|
|
510
|
+
...toUserSet(patch),
|
|
511
|
+
version: sql6`${tables.users.version} + 1`
|
|
512
|
+
}).where(and6(eq6(tables.users.id, id), eq6(tables.users.version, ifVersion))).returning();
|
|
513
|
+
if (row === undefined) {
|
|
514
|
+
const [stored] = await tx.select({ version: tables.users.version }).from(tables.users).where(eq6(tables.users.id, id));
|
|
515
|
+
if (stored === undefined) {
|
|
516
|
+
throw new NotFoundError("updateUser: no user has this id", {
|
|
517
|
+
userId: id,
|
|
518
|
+
operation: "updateUser"
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
throw new StoreConflict2("version", `updateUser: expected version ${ifVersion}, found ${stored.version}`, {
|
|
522
|
+
userId: id,
|
|
523
|
+
expectedVersion: ifVersion,
|
|
524
|
+
actualVersion: stored.version,
|
|
525
|
+
operation: "updateUser"
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
if (patch.logins !== undefined) {
|
|
529
|
+
await tx.delete(tables.logins).where(eq6(tables.logins.userId, id));
|
|
530
|
+
await claimLogins(tx, tables, "updateUser", row, patch.logins);
|
|
531
|
+
}
|
|
532
|
+
return toUser(row);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// src/stores/index.ts
|
|
536
|
+
function createDrizzleStores(db, options = {}) {
|
|
537
|
+
const tables = options.tables ?? defineJanusTables();
|
|
538
|
+
return {
|
|
539
|
+
users: userStore(db, tables),
|
|
540
|
+
sessions: sessionStore(db, tables),
|
|
541
|
+
tokens: tokenStore(db, tables)
|
|
542
|
+
};
|
|
472
543
|
}
|
|
473
544
|
|
|
474
545
|
// src/adapter.ts
|
|
@@ -485,5 +556,5 @@ export {
|
|
|
485
556
|
defineJanusTables
|
|
486
557
|
};
|
|
487
558
|
|
|
488
|
-
//# debugId=
|
|
559
|
+
//# debugId=3F6F0185055BC57C64756E2164756E21
|
|
489
560
|
//# sourceMappingURL=index.js.map
|