@anysoftinc/anydb-sdk 0.1.2 → 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.
@@ -1,100 +1,89 @@
1
- import { SchemaBuilder, DatomicUtils, kw, sym, uuid as uuidEdn } from "./client";
1
+ import { kw, sym, uuid as uuidEdn } from "./client";
2
+ /**
3
+ * Creates NextAuth cookie configuration with proper security settings
4
+ * Uses __Host- prefix and secure cookies in production, regular cookies in development
5
+ */
6
+ export function createNextAuthCookies(cookieIdentifier) {
7
+ const isProduction = process.env.NODE_ENV === "production";
8
+ const prefix = isProduction ? "__Host-" : "";
9
+ const secure = isProduction;
10
+ return {
11
+ sessionToken: {
12
+ name: `${prefix}${cookieIdentifier}.session-token`,
13
+ options: {
14
+ httpOnly: true,
15
+ sameSite: "lax",
16
+ path: "/",
17
+ secure,
18
+ },
19
+ },
20
+ callbackUrl: {
21
+ name: `${prefix}${cookieIdentifier}.callback-url`,
22
+ options: {
23
+ httpOnly: true,
24
+ sameSite: "lax",
25
+ path: "/",
26
+ secure,
27
+ },
28
+ },
29
+ csrfToken: {
30
+ name: `${prefix}${cookieIdentifier}.csrf-token`,
31
+ options: {
32
+ httpOnly: true,
33
+ sameSite: "lax",
34
+ path: "/",
35
+ secure,
36
+ },
37
+ },
38
+ };
39
+ }
2
40
  // Datomic schema idents
3
41
  const USER = {
4
- id: ":auth.user.v1/id", // uuid identity
5
- name: ":auth.user.v1/name",
6
- email: ":auth.user.v1/email",
7
- emailVerified: ":auth.user.v1/email-verified",
8
- image: ":auth.user.v1/image",
42
+ id: "anydb.auth.user.v1/id", // uuid identity
43
+ name: "anydb.auth.user.v1/name",
44
+ email: "anydb.auth.user.v1/email",
45
+ emailVerified: "anydb.auth.user.v1/email-verified",
46
+ image: "anydb.auth.user.v1/image",
9
47
  };
10
48
  const ACCOUNT = {
11
- id: ":auth.account.v1/id", // uuid identity
12
- userId: ":auth.account.v1/user-id", // uuid ref by value
13
- type: ":auth.account.v1/type",
14
- provider: ":auth.account.v1/provider",
15
- providerAccountId: ":auth.account.v1/provider-account-id",
16
- refreshToken: ":auth.account.v1/refresh-token",
17
- accessToken: ":auth.account.v1/access-token",
18
- expiresAt: ":auth.account.v1/expires-at",
19
- tokenType: ":auth.account.v1/token-type",
20
- scope: ":auth.account.v1/scope",
21
- idToken: ":auth.account.v1/id-token",
22
- sessionState: ":auth.account.v1/session-state",
49
+ id: "anydb.auth.account.v1/id", // uuid identity
50
+ userId: "anydb.auth.account.v1/user-id", // uuid ref by value
51
+ type: "anydb.auth.account.v1/type",
52
+ provider: "anydb.auth.account.v1/provider",
53
+ providerAccountId: "anydb.auth.account.v1/provider-account-id",
54
+ refreshToken: "anydb.auth.account.v1/refresh-token",
55
+ accessToken: "anydb.auth.account.v1/access-token",
56
+ expiresAt: "anydb.auth.account.v1/expires-at",
57
+ tokenType: "anydb.auth.account.v1/token-type",
58
+ scope: "anydb.auth.account.v1/scope",
59
+ idToken: "anydb.auth.account.v1/id-token",
60
+ sessionState: "anydb.auth.account.v1/session-state",
23
61
  };
24
62
  const SESSION = {
25
- id: ":auth.session.v1/id", // uuid identity
26
- sessionToken: ":auth.session.v1/session-token",
27
- userId: ":auth.session.v1/user-id",
28
- expires: ":auth.session.v1/expires",
63
+ id: "anydb.auth.session.v1/id", // uuid identity
64
+ sessionToken: "anydb.auth.session.v1/session-token",
65
+ userId: "anydb.auth.session.v1/user-id",
66
+ expires: "anydb.auth.session.v1/expires",
29
67
  };
30
68
  const VTOKEN = {
31
- id: ":auth.vtoken.v1/id", // uuid identity
32
- identifier: ":auth.vtoken.v1/identifier",
33
- token: ":auth.vtoken.v1/token",
34
- expires: ":auth.vtoken.v1/expires",
69
+ id: "anydb.auth.vtoken.v1/id", // uuid identity
70
+ identifier: "anydb.auth.vtoken.v1/identifier",
71
+ token: "anydb.auth.vtoken.v1/token",
72
+ expires: "anydb.auth.vtoken.v1/expires",
35
73
  };
36
- async function ensureAuthSchema(db) {
37
- const defs = [];
38
- // Helper to conditionally add attribute definitions
39
- const addAttr = async (ident, vt, card, opts = {}) => {
40
- const exists = await db.querySymbolic({ find: [sym("?e")], where: [[sym("?e"), kw(":db/ident"), kw(ident)]] });
41
- if (!exists || (Array.isArray(exists) && exists.length === 0)) {
42
- defs.push(SchemaBuilder.attribute({ ":db/ident": ident, ":db/valueType": vt, ":db/cardinality": card, ...opts }));
43
- }
44
- };
45
- // User attributes
46
- await addAttr(USER.id, ":db.type/uuid", ":db.cardinality/one", { ":db/unique": ":db.unique/identity", ":db/index": true });
47
- await addAttr(USER.name, ":db.type/string", ":db.cardinality/one");
48
- // Make email an identity attribute so we can upsert users by email
49
- await addAttr(USER.email, ":db.type/string", ":db.cardinality/one", { ":db/unique": ":db.unique/identity", ":db/index": true });
50
- await addAttr(USER.emailVerified, ":db.type/instant", ":db.cardinality/one");
51
- await addAttr(USER.image, ":db.type/string", ":db.cardinality/one");
52
- // Account attributes
53
- await addAttr(ACCOUNT.id, ":db.type/uuid", ":db.cardinality/one", { ":db/unique": ":db.unique/identity", ":db/index": true });
54
- await addAttr(ACCOUNT.userId, ":db.type/uuid", ":db.cardinality/one", { ":db/index": true });
55
- await addAttr(ACCOUNT.type, ":db.type/string", ":db.cardinality/one");
56
- await addAttr(ACCOUNT.provider, ":db.type/string", ":db.cardinality/one", { ":db/index": true });
57
- await addAttr(ACCOUNT.providerAccountId, ":db.type/string", ":db.cardinality/one", { ":db/index": true });
58
- await addAttr(ACCOUNT.refreshToken, ":db.type/string", ":db.cardinality/one");
59
- await addAttr(ACCOUNT.accessToken, ":db.type/string", ":db.cardinality/one");
60
- await addAttr(ACCOUNT.expiresAt, ":db.type/long", ":db.cardinality/one");
61
- await addAttr(ACCOUNT.tokenType, ":db.type/string", ":db.cardinality/one");
62
- await addAttr(ACCOUNT.scope, ":db.type/string", ":db.cardinality/one");
63
- await addAttr(ACCOUNT.idToken, ":db.type/string", ":db.cardinality/one");
64
- await addAttr(ACCOUNT.sessionState, ":db.type/string", ":db.cardinality/one");
65
- // Session attributes
66
- await addAttr(SESSION.id, ":db.type/uuid", ":db.cardinality/one", { ":db/unique": ":db.unique/identity", ":db/index": true });
67
- await addAttr(SESSION.userId, ":db.type/uuid", ":db.cardinality/one", { ":db/index": true });
68
- await addAttr(SESSION.sessionToken, ":db.type/string", ":db.cardinality/one", { ":db/unique": ":db.unique/identity", ":db/index": true });
69
- await addAttr(SESSION.expires, ":db.type/instant", ":db.cardinality/one");
70
- // Verification token attributes
71
- await addAttr(VTOKEN.id, ":db.type/uuid", ":db.cardinality/one", { ":db/unique": ":db.unique/identity", ":db/index": true });
72
- await addAttr(VTOKEN.identifier, ":db.type/string", ":db.cardinality/one", { ":db/index": true });
73
- await addAttr(VTOKEN.token, ":db.type/string", ":db.cardinality/one", { ":db/unique": ":db.unique/identity", ":db/index": true });
74
- await addAttr(VTOKEN.expires, ":db.type/instant", ":db.cardinality/one");
75
- // Install schema if there are new definitions
76
- if (defs.length) {
77
- try {
78
- await db.transact(defs);
79
- }
80
- catch (e) {
81
- // Schema attributes may already exist, which is fine
82
- }
83
- }
84
- }
85
74
  function toUser(m) {
86
75
  const get = (k) => m && (m[k] ?? m[`:${k}`]);
87
76
  // USER.id is ":auth.user.v1/id", so slice(1) gives "auth.user.v1/id"
88
- const idKey = USER.id.slice(1); // "auth.user.v1/id"
77
+ const idKey = USER.id; // "auth.user.v1/id"
89
78
  const idVal = get(idKey);
90
- const id = typeof idVal === 'string' ? idVal : idVal?.value ?? String(idVal);
91
- const emailVerified = get(USER.emailVerified.slice(1));
79
+ const id = typeof idVal === "string" ? idVal : idVal?.value ?? String(idVal);
80
+ const emailVerified = get(USER.emailVerified);
92
81
  return {
93
82
  id,
94
- name: get(USER.name.slice(1)) || null,
95
- email: get(USER.email.slice(1)) || null,
83
+ name: get(USER.name) || null,
84
+ email: get(USER.email) || null,
96
85
  emailVerified: emailVerified ? new Date(emailVerified) : null,
97
- image: get(USER.image.slice(1)) || null,
86
+ image: get(USER.image) || null,
98
87
  };
99
88
  }
100
89
  /**
@@ -118,15 +107,44 @@ function toUser(m) {
118
107
  */
119
108
  export function AnyDBAdapter(db) {
120
109
  async function getUserByEmail(email) {
121
- await ensureAuthSchema(db);
122
- const q = `[:find (pull ?e [${USER.id} ${USER.name} ${USER.email} ${USER.emailVerified} ${USER.image}]) :where [?e ${USER.email} "${email}"]]`;
110
+ const norm = typeof email === "string" ? email.toLowerCase() : email;
111
+ const q = {
112
+ find: [
113
+ [
114
+ sym("pull"),
115
+ sym("?e"),
116
+ [
117
+ kw(USER.id),
118
+ kw(USER.name),
119
+ kw(USER.email),
120
+ kw(USER.emailVerified),
121
+ kw(USER.image),
122
+ ],
123
+ ],
124
+ ],
125
+ where: [[sym("?e"), kw(USER.email), norm]],
126
+ };
123
127
  const res = await db.query(q);
124
128
  const rows = Array.isArray(res) ? res : [];
125
129
  return rows.length ? toUser(rows[0][0]) : null;
126
130
  }
127
131
  async function getUser(id) {
128
- await ensureAuthSchema(db);
129
- const q = `[:find (pull ?e [${USER.id} ${USER.name} ${USER.email} ${USER.emailVerified} ${USER.image}]) :where [?e ${USER.id} #uuid "${id}"]]`;
132
+ const q = {
133
+ find: [
134
+ [
135
+ sym("pull"),
136
+ sym("?e"),
137
+ [
138
+ kw(USER.id),
139
+ kw(USER.name),
140
+ kw(USER.email),
141
+ kw(USER.emailVerified),
142
+ kw(USER.image),
143
+ ],
144
+ ],
145
+ ],
146
+ where: [[sym("?e"), kw(USER.id), uuidEdn(id)]],
147
+ };
130
148
  const res = await db.query(q);
131
149
  const rows = Array.isArray(res) ? res : [];
132
150
  if (!rows.length)
@@ -136,7 +154,6 @@ export function AnyDBAdapter(db) {
136
154
  return {
137
155
  // Users
138
156
  async createUser(data) {
139
- await ensureAuthSchema(db);
140
157
  // Check if user already exists by email
141
158
  if (data.email) {
142
159
  const existing = await getUserByEmail(data.email);
@@ -144,47 +161,66 @@ export function AnyDBAdapter(db) {
144
161
  return existing;
145
162
  }
146
163
  }
147
- const id = globalThis.crypto?.randomUUID?.() || (await import("crypto")).randomUUID();
164
+ const id = globalThis.crypto?.randomUUID?.() ||
165
+ (await import("crypto")).randomUUID();
148
166
  const tx = {
149
- 'db/id': DatomicUtils.tempId(),
150
167
  [USER.id]: uuidEdn(id),
151
168
  ...(data.name ? { [USER.name]: data.name } : {}),
152
- ...(data.email ? { [USER.email]: data.email } : {}),
153
- ...(data.emailVerified ? { [USER.emailVerified]: data.emailVerified } : {}),
169
+ ...(data.email ? { [USER.email]: String(data.email).toLowerCase() } : {}),
170
+ ...(data.emailVerified
171
+ ? { [USER.emailVerified]: data.emailVerified }
172
+ : {}),
154
173
  ...(data.image ? { [USER.image]: data.image } : {}),
155
174
  };
156
175
  await db.transact([tx]);
157
176
  return {
158
177
  id,
159
178
  name: data.name ?? null,
160
- email: data.email ?? null,
179
+ email: (data.email ? String(data.email).toLowerCase() : null),
161
180
  emailVerified: data.emailVerified ?? null,
162
- image: data.image ?? null
181
+ image: data.image ?? null,
163
182
  };
164
183
  },
165
184
  getUser,
166
185
  getUserByEmail,
167
186
  async getUserByAccount({ provider, providerAccountId }) {
168
- await ensureAuthSchema(db);
169
- const q = `[:find (pull ?u [${USER.id} ${USER.name} ${USER.email} ${USER.emailVerified} ${USER.image}])
170
- :where [?a ${ACCOUNT.provider} "${provider}"]
171
- [?a ${ACCOUNT.providerAccountId} "${providerAccountId}"]
172
- [?a ${ACCOUNT.userId} ?uid]
173
- [?u ${USER.id} ?uid]]`;
187
+ const q = {
188
+ find: [
189
+ [
190
+ sym("pull"),
191
+ sym("?u"),
192
+ [
193
+ kw(USER.id),
194
+ kw(USER.name),
195
+ kw(USER.email),
196
+ kw(USER.emailVerified),
197
+ kw(USER.image),
198
+ ],
199
+ ],
200
+ ],
201
+ where: [
202
+ [sym("?a"), kw(ACCOUNT.provider), provider],
203
+ [sym("?a"), kw(ACCOUNT.providerAccountId), providerAccountId],
204
+ [sym("?a"), kw(ACCOUNT.userId), sym("?uid")],
205
+ [sym("?u"), kw(USER.id), sym("?uid")],
206
+ ],
207
+ };
174
208
  const res = await db.query(q);
175
209
  const rows = Array.isArray(res) ? res : [];
176
210
  return rows.length ? toUser(rows[0][0]) : null;
177
211
  },
178
212
  async updateUser(data) {
179
- await ensureAuthSchema(db);
180
213
  if (!data.id)
181
214
  throw new Error("updateUser requires id");
182
215
  const tx = {
183
- 'db/id': DatomicUtils.tempId(),
184
216
  [USER.id]: uuidEdn(data.id),
185
217
  ...(data.name !== undefined ? { [USER.name]: data.name } : {}),
186
- ...(data.email !== undefined ? { [USER.email]: data.email } : {}),
187
- ...(data.emailVerified !== undefined ? { [USER.emailVerified]: data.emailVerified } : {}),
218
+ ...(data.email !== undefined
219
+ ? { [USER.email]: data.email ? String(data.email).toLowerCase() : null }
220
+ : {}),
221
+ ...(data.emailVerified !== undefined
222
+ ? { [USER.emailVerified]: data.emailVerified }
223
+ : {}),
188
224
  ...(data.image !== undefined ? { [USER.image]: data.image } : {}),
189
225
  };
190
226
  await db.transact([tx]);
@@ -192,58 +228,74 @@ export function AnyDBAdapter(db) {
192
228
  return u;
193
229
  },
194
230
  async deleteUser(id) {
195
- await ensureAuthSchema(db);
196
- await db.transact([[":db/retractEntity", [USER.id], uuidEdn(id)]]);
231
+ await db.transact([["db/retractEntity", [USER.id], uuidEdn(id)]]);
197
232
  return null;
198
233
  },
199
234
  // Accounts
200
235
  async linkAccount(account) {
201
- await ensureAuthSchema(db);
202
- const id = globalThis.crypto?.randomUUID?.() || (await import("crypto")).randomUUID();
236
+ const id = globalThis.crypto?.randomUUID?.() ||
237
+ (await import("crypto")).randomUUID();
203
238
  const tx = {
204
- 'db/id': DatomicUtils.tempId(),
205
239
  [ACCOUNT.id]: uuidEdn(id),
206
240
  [ACCOUNT.userId]: uuidEdn(account.userId),
207
241
  [ACCOUNT.type]: account.type,
208
242
  [ACCOUNT.provider]: account.provider,
209
243
  [ACCOUNT.providerAccountId]: account.providerAccountId,
210
- ...(account.refresh_token ? { [ACCOUNT.refreshToken]: account.refresh_token } : {}),
211
- ...(account.access_token ? { [ACCOUNT.accessToken]: account.access_token } : {}),
212
- ...(account.expires_at ? { [ACCOUNT.expiresAt]: account.expires_at } : {}),
213
- ...(account.token_type ? { [ACCOUNT.tokenType]: account.token_type } : {}),
244
+ ...(account.refresh_token
245
+ ? { [ACCOUNT.refreshToken]: account.refresh_token }
246
+ : {}),
247
+ ...(account.access_token
248
+ ? { [ACCOUNT.accessToken]: account.access_token }
249
+ : {}),
250
+ ...(account.expires_at
251
+ ? { [ACCOUNT.expiresAt]: account.expires_at }
252
+ : {}),
253
+ ...(account.token_type
254
+ ? { [ACCOUNT.tokenType]: account.token_type }
255
+ : {}),
214
256
  ...(account.scope ? { [ACCOUNT.scope]: account.scope } : {}),
215
257
  ...(account.id_token ? { [ACCOUNT.idToken]: account.id_token } : {}),
216
- ...(account.session_state ? { [ACCOUNT.sessionState]: account.session_state } : {}),
258
+ ...(account.session_state
259
+ ? { [ACCOUNT.sessionState]: account.session_state }
260
+ : {}),
217
261
  };
218
262
  await db.transact([tx]);
219
263
  return account;
220
264
  },
221
265
  async unlinkAccount({ provider, providerAccountId }) {
222
- await ensureAuthSchema(db);
223
- const q = `[:find ?id :where [?a ${ACCOUNT.provider} "${provider}"] [?a ${ACCOUNT.providerAccountId} "${providerAccountId}"] [?a ${ACCOUNT.id} ?id]]`;
266
+ const q = {
267
+ find: [sym("?id")],
268
+ where: [
269
+ [sym("?a"), kw(ACCOUNT.provider), provider],
270
+ [sym("?a"), kw(ACCOUNT.providerAccountId), providerAccountId],
271
+ [sym("?a"), kw(ACCOUNT.id), sym("?id")],
272
+ ],
273
+ };
224
274
  const res = await db.query(q);
225
275
  const id = Array.isArray(res) && res[0]?.[0];
226
276
  if (id) {
227
- await db.transact([[":db/retractEntity", [ACCOUNT.id], id]]);
277
+ await db.transact([["db/retractEntity", [ACCOUNT.id], id]]);
228
278
  }
229
279
  },
230
280
  // Sessions
231
281
  async createSession(session) {
232
- await ensureAuthSchema(db);
233
- const id = globalThis.crypto?.randomUUID?.() || (await import("crypto")).randomUUID();
282
+ const id = globalThis.crypto?.randomUUID?.() ||
283
+ (await import("crypto")).randomUUID();
234
284
  let expiresDate;
235
- if (session.expires instanceof Date && !isNaN(session.expires.getTime())) {
285
+ if (session.expires instanceof Date &&
286
+ !isNaN(session.expires.getTime())) {
236
287
  expiresDate = session.expires;
237
288
  }
238
289
  else if (session.expires) {
239
290
  const d = new Date(session.expires);
240
- expiresDate = isNaN(d.getTime()) ? new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) : d;
291
+ expiresDate = isNaN(d.getTime())
292
+ ? new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
293
+ : d;
241
294
  }
242
295
  else {
243
296
  expiresDate = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
244
297
  }
245
298
  const tx = {
246
- 'db/id': DatomicUtils.tempId(),
247
299
  [SESSION.id]: uuidEdn(id),
248
300
  [SESSION.userId]: uuidEdn(session.userId),
249
301
  [SESSION.sessionToken]: session.sessionToken,
@@ -253,11 +305,36 @@ export function AnyDBAdapter(db) {
253
305
  return { ...session, expires: expiresDate };
254
306
  },
255
307
  async getSessionAndUser(sessionToken) {
256
- await ensureAuthSchema(db);
257
- const q = `[:find (pull ?s [${SESSION.id} ${SESSION.sessionToken} ${SESSION.userId} ${SESSION.expires}]) (pull ?u [${USER.id} ${USER.name} ${USER.email} ${USER.emailVerified} ${USER.image}])
258
- :where [?s ${SESSION.sessionToken} "${sessionToken}"]
259
- [?s ${SESSION.userId} ?uid]
260
- [?u ${USER.id} ?uid]]`;
308
+ const q = {
309
+ find: [
310
+ [
311
+ sym("pull"),
312
+ sym("?s"),
313
+ [
314
+ kw(SESSION.id),
315
+ kw(SESSION.sessionToken),
316
+ kw(SESSION.userId),
317
+ kw(SESSION.expires),
318
+ ],
319
+ ],
320
+ [
321
+ sym("pull"),
322
+ sym("?u"),
323
+ [
324
+ kw(USER.id),
325
+ kw(USER.name),
326
+ kw(USER.email),
327
+ kw(USER.emailVerified),
328
+ kw(USER.image),
329
+ ],
330
+ ],
331
+ ],
332
+ where: [
333
+ [sym("?s"), kw(SESSION.sessionToken), sessionToken],
334
+ [sym("?s"), kw(SESSION.userId), sym("?uid")],
335
+ [sym("?u"), kw(USER.id), sym("?uid")],
336
+ ],
337
+ };
261
338
  const res = await db.query(q);
262
339
  const row = Array.isArray(res) ? res[0] : null;
263
340
  if (!row)
@@ -265,49 +342,63 @@ export function AnyDBAdapter(db) {
265
342
  const s = row[0];
266
343
  const u = row[1];
267
344
  const getS = (k) => s && (s[k] ?? s[`:${k}`]);
268
- const expiresRaw = getS(SESSION.expires.slice(1));
345
+ const expiresRaw = getS(SESSION.expires);
269
346
  const expires = expiresRaw instanceof Date ? expiresRaw : new Date(expiresRaw);
270
347
  const sessionResult = {
271
- sessionToken: getS(SESSION.sessionToken.slice(1)),
272
- userId: (getS(SESSION.userId.slice(1))?.value ?? getS(SESSION.userId.slice(1))),
348
+ sessionToken: getS(SESSION.sessionToken),
349
+ userId: (getS(SESSION.userId)?.value ?? getS(SESSION.userId)),
273
350
  expires,
274
351
  };
275
352
  return { session: sessionResult, user: toUser(u) };
276
353
  },
277
354
  async updateSession(partial) {
278
- await ensureAuthSchema(db);
279
355
  let expiresDate;
280
356
  if (partial.expires instanceof Date) {
281
- expiresDate = isNaN(partial.expires.getTime()) ? undefined : partial.expires;
357
+ expiresDate = isNaN(partial.expires.getTime())
358
+ ? undefined
359
+ : partial.expires;
282
360
  }
283
361
  else if (partial.expires) {
284
362
  const d = new Date(partial.expires);
285
363
  expiresDate = isNaN(d.getTime()) ? undefined : d;
286
364
  }
365
+ if (!partial.sessionToken) {
366
+ throw new Error("updateSession requires sessionToken to upsert by identity");
367
+ }
287
368
  const tx = {
288
- 'db/id': DatomicUtils.tempId(),
289
- ...(partial.sessionToken ? { [SESSION.sessionToken]: partial.sessionToken } : {}),
290
- ...(partial.userId ? { [SESSION.userId]: uuidEdn(partial.userId) } : {}),
369
+ ...(partial.sessionToken
370
+ ? { [SESSION.sessionToken]: partial.sessionToken }
371
+ : {}),
372
+ ...(partial.userId
373
+ ? { [SESSION.userId]: uuidEdn(partial.userId) }
374
+ : {}),
291
375
  ...(expiresDate ? { [SESSION.expires]: expiresDate } : {}),
292
376
  };
293
377
  await db.transact([tx]);
294
- return { ...partial, ...(expiresDate ? { expires: expiresDate } : {}) };
378
+ return {
379
+ ...partial,
380
+ ...(expiresDate ? { expires: expiresDate } : {}),
381
+ };
295
382
  },
296
383
  async deleteSession(sessionToken) {
297
- await ensureAuthSchema(db);
298
- const q = `[:find ?id :where [?s ${SESSION.sessionToken} "${sessionToken}"] [?s ${SESSION.id} ?id]]`;
384
+ const q = {
385
+ find: [sym("?id")],
386
+ where: [
387
+ [sym("?s"), kw(SESSION.sessionToken), sessionToken],
388
+ [sym("?s"), kw(SESSION.id), sym("?id")],
389
+ ],
390
+ };
299
391
  const res = await db.query(q);
300
392
  const id = Array.isArray(res) && res[0]?.[0];
301
393
  if (id) {
302
- await db.transact([[":db/retractEntity", [SESSION.id], id]]);
394
+ await db.transact([["db/retractEntity", [SESSION.id], id]]);
303
395
  }
304
396
  },
305
397
  // Verification tokens (email sign-in, passwordless)
306
398
  async createVerificationToken(token) {
307
- await ensureAuthSchema(db);
308
- const id = globalThis.crypto?.randomUUID?.() || (await import("crypto")).randomUUID();
399
+ const id = globalThis.crypto?.randomUUID?.() ||
400
+ (await import("crypto")).randomUUID();
309
401
  const tx = {
310
- 'db/id': DatomicUtils.tempId(),
311
402
  [VTOKEN.id]: uuidEdn(id),
312
403
  [VTOKEN.identifier]: token.identifier,
313
404
  [VTOKEN.token]: token.token,
@@ -317,23 +408,37 @@ export function AnyDBAdapter(db) {
317
408
  return token;
318
409
  },
319
410
  async useVerificationToken(params) {
320
- await ensureAuthSchema(db);
321
- const q = `[:find (pull ?e [${VTOKEN.id} ${VTOKEN.identifier} ${VTOKEN.token} ${VTOKEN.expires}])
322
- :where [?e ${VTOKEN.identifier} "${params.identifier}"]
323
- [?e ${VTOKEN.token} "${params.token}"]]`;
411
+ const q = {
412
+ find: [
413
+ [
414
+ sym("pull"),
415
+ sym("?e"),
416
+ [
417
+ kw(VTOKEN.id),
418
+ kw(VTOKEN.identifier),
419
+ kw(VTOKEN.token),
420
+ kw(VTOKEN.expires),
421
+ ],
422
+ ],
423
+ ],
424
+ where: [
425
+ [sym("?e"), kw(VTOKEN.identifier), params.identifier],
426
+ [sym("?e"), kw(VTOKEN.token), params.token],
427
+ ],
428
+ };
324
429
  const res = await db.query(q);
325
430
  const rows = Array.isArray(res) ? res : [];
326
431
  if (!rows.length)
327
432
  return null;
328
433
  const m = rows[0][0];
329
434
  const get = (k) => m && (m[k] ?? m[`:${k}`]);
330
- const entityId = get(VTOKEN.id.slice(1));
435
+ const entityId = get(VTOKEN.id);
331
436
  // Delete the token after using it
332
- await db.transact([[":db/retractEntity", [VTOKEN.id], entityId]]);
437
+ await db.transact([[kw("db/retractEntity"), entityId]]);
333
438
  return {
334
- identifier: get(VTOKEN.identifier.slice(1)),
335
- token: get(VTOKEN.token.slice(1)),
336
- expires: new Date(get(VTOKEN.expires.slice(1))),
439
+ identifier: get(VTOKEN.identifier),
440
+ token: get(VTOKEN.token),
441
+ expires: new Date(get(VTOKEN.expires)),
337
442
  };
338
443
  },
339
444
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anysoftinc/anydb-sdk",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "description": "AnyDB TypeScript SDK for querying and transacting with Datomic databases",
5
5
  "main": "dist/client.js",
6
6
  "types": "dist/client.d.ts",
@@ -23,12 +23,14 @@
23
23
  },
24
24
  "scripts": {
25
25
  "build": "tsc",
26
+ "build:cljs": "npx shadow-cljs compile datascript",
26
27
  "dev": "tsc --watch",
28
+ "dev:cljs": "npx shadow-cljs watch datascript",
27
29
  "prepublishOnly": "npm run build",
28
30
  "test": "jest",
29
31
  "test:watch": "jest --watch",
30
32
  "test:coverage": "jest --coverage",
31
- "clean": "rm -rf dist"
33
+ "clean": "rm -rf dist dist-cljs target"
32
34
  },
33
35
  "dependencies": {
34
36
  "edn-data": "^1.1.2"
@@ -46,6 +48,7 @@
46
48
  "@types/node": "^20",
47
49
  "jest": "^29.7.0",
48
50
  "next-auth": "^4.24.11",
51
+ "shadow-cljs": "^2.25.10",
49
52
  "ts-jest": "^29.1.4",
50
53
  "typescript": "^5"
51
54
  },