@anysoftinc/anydb-sdk 0.1.1

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/client.js ADDED
@@ -0,0 +1,397 @@
1
+ // Core types for Datomic data model
2
+ import { parseEDNString as parseEdn } from "edn-data";
3
+ export const sym = (name) => ({ _type: 'symbol', value: name });
4
+ export const uuid = (id) => ({ _type: 'uuid', value: id });
5
+ export const kw = (name) => ({ _type: 'keyword', value: name });
6
+ /**
7
+ * Enhanced EDN stringifier with support for symbolic types
8
+ */
9
+ export function stringifyEdn(obj) {
10
+ // Handle symbolic types first
11
+ if (typeof obj === "object" && obj !== null && "_type" in obj) {
12
+ const typed = obj;
13
+ switch (typed._type) {
14
+ case 'symbol':
15
+ return typed.value; // ?e, ?id, etc.
16
+ case 'uuid':
17
+ return `#uuid "${typed.value}"`;
18
+ case 'keyword':
19
+ return typed.value.startsWith(':') ? typed.value : `:${typed.value}`;
20
+ }
21
+ }
22
+ if (typeof obj === "string") {
23
+ // Keywords and symbols don't get quoted, strings do
24
+ if (obj.startsWith(":") || obj.startsWith("?")) {
25
+ return obj;
26
+ }
27
+ return `"${obj.replace(/"/g, '\\"')}"`;
28
+ }
29
+ if (typeof obj === "number")
30
+ return String(obj);
31
+ if (typeof obj === "boolean")
32
+ return String(obj);
33
+ if (obj === null || obj === undefined)
34
+ return "nil";
35
+ if (obj instanceof Date)
36
+ return `#inst "${obj.toISOString()}"`;
37
+ if (Array.isArray(obj)) {
38
+ return "[" + obj.map(stringifyEdn).join(" ") + "]";
39
+ }
40
+ if (typeof obj === "object") {
41
+ const pairs = Object.entries(obj).map(([key, value]) => {
42
+ // Handle Datomic keyword keys
43
+ const ednKey = key.startsWith(":") ? key : `:${key}`;
44
+ return `${ednKey} ${stringifyEdn(value)}`;
45
+ });
46
+ return "{" + pairs.join(" ") + "}";
47
+ }
48
+ return String(obj);
49
+ }
50
+ // Main SDK Client
51
+ export class DatomicClient {
52
+ constructor(config) {
53
+ this.config = config;
54
+ this.baseUrl = config.baseUrl.replace(/\/$/, ""); // Remove trailing slash
55
+ }
56
+ // Normalize EDN parsed values to JS-friendly shapes
57
+ normalizeEdn(value) {
58
+ if (value === null || value === undefined)
59
+ return value;
60
+ // edn-data often wraps symbols/keywords/strings as { key: '...' }
61
+ if (typeof value === 'object' && value && typeof value.key === 'string') {
62
+ return value.key;
63
+ }
64
+ // Sets -> Arrays
65
+ if (value instanceof Set) {
66
+ return Array.from(value, (v) => this.normalizeEdn(v));
67
+ }
68
+ // edn-data set representation: { set: [...] }
69
+ if (typeof value === 'object' && value && Array.isArray(value.set)) {
70
+ return value.set.map((v) => this.normalizeEdn(v));
71
+ }
72
+ // edn-data map representation: { map: [[k,v] ...] }
73
+ if (typeof value === 'object' && value && Array.isArray(value.map)) {
74
+ const out = {};
75
+ for (const [k, v] of value.map) {
76
+ const keyNorm = this.normalizeEdn(k);
77
+ const keyStr = typeof keyNorm === 'string' ? keyNorm : String(keyNorm);
78
+ out[keyStr] = this.normalizeEdn(v);
79
+ }
80
+ return out;
81
+ }
82
+ // Arrays
83
+ if (Array.isArray(value)) {
84
+ return value.map((v) => this.normalizeEdn(v));
85
+ }
86
+ // Objects: attempt to descend shallowly
87
+ if (typeof value === 'object') {
88
+ // Convert common UUID representations to a consistent shape
89
+ if (value.tag === 'uuid' && typeof value.val === 'string') {
90
+ return { _type: 'uuid', value: value.val };
91
+ }
92
+ if (value.type === 'uuid' && typeof value.value === 'string') {
93
+ return value; // already in {_type:'uuid', value}
94
+ }
95
+ // Convert inst tag to Date
96
+ if (value.tag === 'inst' && typeof value.val === 'string') {
97
+ const d = new Date(value.val);
98
+ return isNaN(d.getTime()) ? value.val : d;
99
+ }
100
+ const out = Array.isArray(value) ? [] : {};
101
+ for (const [k, v] of Object.entries(value)) {
102
+ out[k] = this.normalizeEdn(v);
103
+ }
104
+ return out;
105
+ }
106
+ return value;
107
+ }
108
+ async request(endpoint, options = {}) {
109
+ const url = `${this.baseUrl}${endpoint}`;
110
+ // Convert body to EDN if present
111
+ const processedOptions = { ...options };
112
+ if (processedOptions.body && typeof processedOptions.body !== "string") {
113
+ processedOptions.body = stringifyEdn(processedOptions.body);
114
+ }
115
+ const response = await fetch(url, {
116
+ headers: {
117
+ "Content-Type": "application/edn",
118
+ Accept: "application/edn",
119
+ ...this.config.headers,
120
+ ...options.headers,
121
+ },
122
+ ...processedOptions,
123
+ });
124
+ if (!response.ok) {
125
+ throw new Error(`Datomic API error: ${response.status} ${response.statusText}`);
126
+ }
127
+ // Parse EDN response
128
+ const responseText = await response.text();
129
+ const parsed = parseEdn(responseText);
130
+ return this.normalizeEdn(parsed);
131
+ }
132
+ // List databases in a storage
133
+ async listDatabases(storageAlias) {
134
+ return this.request(`/data/${storageAlias}/`);
135
+ }
136
+ // Create database
137
+ async createDatabase(storageAlias, dbName) {
138
+ return this.request(`/data/${storageAlias}/`, {
139
+ method: "POST",
140
+ body: { ":db-name": dbName },
141
+ });
142
+ }
143
+ // Delete database
144
+ async deleteDatabase(storageAlias, dbName) {
145
+ return this.request(`/data/${storageAlias}/${dbName}/`, {
146
+ method: "DELETE",
147
+ });
148
+ }
149
+ // Transaction operations
150
+ async transact(storageAlias, dbName, txData) {
151
+ return this.request(`/data/${storageAlias}/${dbName}/`, {
152
+ method: "POST",
153
+ body: { ":tx-data": txData },
154
+ });
155
+ }
156
+ // Retrieve database info
157
+ async databaseInfo(storageAlias, dbName, basisT = "-") {
158
+ return this.request(`/data/${storageAlias}/${dbName}/${basisT}/`);
159
+ }
160
+ // Datoms operations
161
+ async datoms(storageAlias, dbName, basisT, options) {
162
+ const params = new URLSearchParams();
163
+ // Required parameter
164
+ params.append("index", options.index);
165
+ // Optional parameters
166
+ if (options.e !== undefined)
167
+ params.append("e", String(options.e));
168
+ if (options.a !== undefined)
169
+ params.append("a", String(options.a));
170
+ if (options.v !== undefined)
171
+ params.append("v", String(options.v));
172
+ if (options.start !== undefined)
173
+ params.append("start", String(options.start));
174
+ if (options.end !== undefined)
175
+ params.append("end", String(options.end));
176
+ if (options.limit !== undefined)
177
+ params.append("limit", String(options.limit));
178
+ if (options.offset !== undefined)
179
+ params.append("offset", String(options.offset));
180
+ if (options["as-of"] !== undefined)
181
+ params.append("as-of", String(options["as-of"]));
182
+ if (options.since !== undefined)
183
+ params.append("since", String(options.since));
184
+ if (options.history !== undefined)
185
+ params.append("history", String(options.history));
186
+ return this.request(`/data/${storageAlias}/${dbName}/${basisT}/datoms?${params.toString()}`);
187
+ }
188
+ // Entity operations
189
+ async entity(storageAlias, dbName, basisT, entityId, options) {
190
+ const params = new URLSearchParams();
191
+ params.append("e", String(entityId));
192
+ if (options?.["as-of"] !== undefined)
193
+ params.append("as-of", String(options["as-of"]));
194
+ if (options?.since !== undefined)
195
+ params.append("since", String(options.since));
196
+ return this.request(`/data/${storageAlias}/${dbName}/${basisT}/entity?${params.toString()}`);
197
+ }
198
+ // Query operations (GET version for simple queries)
199
+ async queryGet(query, args, options) {
200
+ const params = new URLSearchParams();
201
+ params.append("q", stringifyEdn(query));
202
+ params.append("args", stringifyEdn(args));
203
+ if (options?.limit !== undefined)
204
+ params.append("limit", String(options.limit));
205
+ if (options?.offset !== undefined)
206
+ params.append("offset", String(options.offset));
207
+ return this.request(`/api/query?${params.toString()}`);
208
+ }
209
+ // Query operations (POST version for complex queries)
210
+ async query(query, args, options) {
211
+ const body = {
212
+ ":q": query,
213
+ ":args": args,
214
+ };
215
+ if (options?.limit !== undefined)
216
+ body[":limit"] = options.limit;
217
+ if (options?.offset !== undefined)
218
+ body[":offset"] = options.offset;
219
+ const res = await this.request("/api/query", {
220
+ method: "POST",
221
+ body: body,
222
+ });
223
+ // Ensure result is array of rows, not Set
224
+ if (res instanceof Set) {
225
+ return Array.from(res);
226
+ }
227
+ return res;
228
+ }
229
+ // Symbolic query operations
230
+ async querySymbolic(query, args = [], options) {
231
+ // Convert symbolic query to EDN vector format
232
+ const ednQuery = [":find", ...query.find];
233
+ if (query.in && query.in.length > 0) {
234
+ ednQuery.push(":in", ...query.in);
235
+ }
236
+ ednQuery.push(":where", ...query.where);
237
+ return this.query(ednQuery, args, options);
238
+ }
239
+ // Server-Sent Events for transaction reports
240
+ subscribeToEvents(storageAlias, dbName, onEvent, onError) {
241
+ const eventSource = new EventSource(`${this.baseUrl}/events/${storageAlias}/${dbName}`);
242
+ eventSource.onmessage = onEvent;
243
+ if (onError)
244
+ eventSource.onerror = onError;
245
+ return eventSource;
246
+ }
247
+ }
248
+ // Utility functions for common Datomic operations
249
+ export class DatomicUtils {
250
+ static tempId(partition = "db.part/user") {
251
+ return `tempid:${partition}:${Math.random().toString(36).substr(2, 9)}`;
252
+ }
253
+ static keyword(namespace, name) {
254
+ return `:${namespace}/${name}`;
255
+ }
256
+ static createEntity(attributes, tempId) {
257
+ const id = tempId || this.tempId();
258
+ return Object.entries(attributes).map(([attr, value]) => ({
259
+ "db/id": id,
260
+ [attr]: value,
261
+ }));
262
+ }
263
+ static retractEntity(entityId) {
264
+ return [[":db/retractEntity", entityId]];
265
+ }
266
+ static retractAttribute(entityId, attribute, value) {
267
+ if (value !== undefined) {
268
+ return [[":db/retract", entityId, attribute, value]];
269
+ }
270
+ return [[":db/retract", entityId, attribute]];
271
+ }
272
+ static addAttribute(entityId, attribute, value) {
273
+ return [[":db/add", entityId, attribute, value]];
274
+ }
275
+ }
276
+ export class SchemaBuilder {
277
+ static attribute(def) {
278
+ return {
279
+ "db/id": DatomicUtils.tempId(),
280
+ ...def,
281
+ };
282
+ }
283
+ static enum(ident, doc) {
284
+ const enumDef = {
285
+ "db/id": DatomicUtils.tempId(),
286
+ ":db/ident": ident,
287
+ };
288
+ if (doc) {
289
+ enumDef[":db/doc"] = doc;
290
+ }
291
+ return enumDef;
292
+ }
293
+ }
294
+ // EDN template literal for natural Datalog queries
295
+ export function edn(strings, ...values) {
296
+ // Combine template strings and interpolated values
297
+ let ednString = '';
298
+ for (let i = 0; i < strings.length; i++) {
299
+ ednString += strings[i];
300
+ if (i < values.length) {
301
+ const value = values[i];
302
+ // Convert interpolated values to EDN strings
303
+ ednString += stringifyEdn(value);
304
+ }
305
+ }
306
+ // Use the proper EDN parser
307
+ const parsed = parseEdn(ednString);
308
+ // Apply normalization similar to the client's normalizeEdn method
309
+ return normalizeEdnForQuery(parsed);
310
+ }
311
+ function normalizeEdnForQuery(value) {
312
+ if (value === null || value === undefined)
313
+ return value;
314
+ // Handle edn-data keyword representations: {key: "find"} -> ":find"
315
+ if (typeof value === 'object' && value && typeof value.key === 'string') {
316
+ const key = value.key;
317
+ return key.startsWith(':') ? key : `:${key}`;
318
+ }
319
+ // Handle edn-data symbol representations: {sym: "?e"} -> "?e"
320
+ if (typeof value === 'object' && value && typeof value.sym === 'string') {
321
+ return value.sym;
322
+ }
323
+ // Handle edn-data list representations: {list: [...]} -> [...]
324
+ if (typeof value === 'object' && value && Array.isArray(value.list)) {
325
+ return value.list.map((v) => normalizeEdnForQuery(v));
326
+ }
327
+ // Sets -> Arrays
328
+ if (value instanceof Set) {
329
+ return Array.from(value, (v) => normalizeEdnForQuery(v));
330
+ }
331
+ // edn-data set representation: { set: [...] }
332
+ if (typeof value === 'object' && value && Array.isArray(value.set)) {
333
+ return value.set.map((v) => normalizeEdnForQuery(v));
334
+ }
335
+ // Arrays
336
+ if (Array.isArray(value)) {
337
+ return value.map((v) => normalizeEdnForQuery(v));
338
+ }
339
+ // Convert common UUID representations
340
+ if (typeof value === 'object' && value && value.tag === 'uuid' && typeof value.val === 'string') {
341
+ return { _type: 'uuid', value: value.val };
342
+ }
343
+ return value;
344
+ }
345
+ // Convenience class for working with a specific database
346
+ export class DatomicDatabase {
347
+ constructor(client, storageAlias, dbName) {
348
+ this.client = client;
349
+ this.storageAlias = storageAlias;
350
+ this.dbName = dbName;
351
+ }
352
+ async transact(txData) {
353
+ return this.client.transact(this.storageAlias, this.dbName, txData);
354
+ }
355
+ async info(basisT = "-") {
356
+ return this.client.databaseInfo(this.storageAlias, this.dbName, basisT);
357
+ }
358
+ async datoms(index, basisT = "-", options) {
359
+ return this.client.datoms(this.storageAlias, this.dbName, basisT, {
360
+ index,
361
+ ...options,
362
+ });
363
+ }
364
+ async entity(entityId, basisT = "-", options) {
365
+ return this.client.entity(this.storageAlias, this.dbName, basisT, entityId, options);
366
+ }
367
+ async query(query, ...args) {
368
+ const dbDescriptor = {
369
+ "db/alias": `${this.storageAlias}/${this.dbName}`,
370
+ };
371
+ return this.client.query(query, [dbDescriptor, ...args]);
372
+ }
373
+ async querySymbolic(query, ...args) {
374
+ const dbDescriptor = {
375
+ "db/alias": `${this.storageAlias}/${this.dbName}`,
376
+ };
377
+ return this.client.querySymbolic(query, [dbDescriptor, ...args]);
378
+ }
379
+ subscribeToEvents(onEvent, onError) {
380
+ return this.client.subscribeToEvents(this.storageAlias, this.dbName, onEvent, onError);
381
+ }
382
+ }
383
+ // Factory functions
384
+ export function createDatomicClient(config) {
385
+ return new DatomicClient(config);
386
+ }
387
+ export function createDatomicDatabase(client, storageAlias, dbName) {
388
+ return new DatomicDatabase(client, storageAlias, dbName);
389
+ }
390
+ // Small helpers
391
+ export function pluckFirstColumn(rows) {
392
+ if (!Array.isArray(rows))
393
+ return [];
394
+ return rows.map((r) => (Array.isArray(r) ? r[0] : r));
395
+ }
396
+ // Export everything
397
+ export default DatomicClient;
@@ -0,0 +1,23 @@
1
+ import type { Adapter } from "next-auth/adapters";
2
+ import type { DatomicDatabase } from "./client";
3
+ /**
4
+ * Creates a NextAuth.js adapter for AnyDB/Datomic
5
+ *
6
+ * @param db - DatomicDatabase instance
7
+ * @returns NextAuth.js Adapter
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { createDatomicDatabase } from '@anysoftinc/anydb-sdk';
12
+ * import { AnyDBAdapter } from '@anysoftinc/anydb-sdk/nextauth-adapter';
13
+ *
14
+ * const db = createDatomicDatabase(client, 'storage', 'auth-db');
15
+ *
16
+ * export default NextAuth({
17
+ * adapter: AnyDBAdapter(db),
18
+ * // ... other config
19
+ * });
20
+ * ```
21
+ */
22
+ export declare function AnyDBAdapter(db: DatomicDatabase): Adapter;
23
+ //# sourceMappingURL=nextauth-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nextauth-adapter.d.ts","sourceRoot":"","sources":["../src/nextauth-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAkB,MAAM,oBAAoB,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAmHhD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,eAAe,GAAG,OAAO,CA4PzD"}