@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.
package/dist/client.js CHANGED
@@ -1,29 +1,61 @@
1
- // Core types for Datomic data model
1
+ // AnyDB TypeScript SDK v2 - strict, no-fallback client
2
+ // - Explicit EDN symbolic types (Keyword, Symbol, UUID)
3
+ // - Strict SymbolicQuery API
4
+ // - Explicit DataScript selection via storage alias (no fallback)
5
+ // - Keywordized JS<->EDN conversion for object keys
2
6
  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
- */
7
+ import { DataScriptBackend, shouldUseDataScript, isDataScriptAvailable, } from "./datascript-backend";
8
+ export const sym = (name) => ({ _type: "symbol", value: name });
9
+ export const uuid = (id) => ({ _type: "uuid", value: id });
10
+ export const kw = (name) => {
11
+ if (typeof name !== "string" || !name) {
12
+ throw new QueryInputError("kw() requires a non-empty string name");
13
+ }
14
+ if (name.startsWith(":")) {
15
+ throw new QueryInputError("kw() expects bare keyword without leading ':'", {
16
+ name,
17
+ });
18
+ }
19
+ return {
20
+ _type: "keyword",
21
+ value: name,
22
+ };
23
+ };
24
+ // ===== Errors =====
25
+ class AnyDBError extends Error {
26
+ constructor(message, details) {
27
+ super(message);
28
+ this.details = details;
29
+ this.name = this.constructor.name;
30
+ }
31
+ }
32
+ export class AuthError extends AnyDBError {
33
+ }
34
+ export class QueryInputError extends AnyDBError {
35
+ }
36
+ export class SchemaError extends AnyDBError {
37
+ }
38
+ export class TransportError extends AnyDBError {
39
+ }
40
+ export class ServerError extends AnyDBError {
41
+ }
42
+ // ===== EDN Stringifier with keywordized key handling =====
9
43
  export function stringifyEdn(obj) {
10
44
  // Handle symbolic types first
11
45
  if (typeof obj === "object" && obj !== null && "_type" in obj) {
12
46
  const typed = obj;
13
47
  switch (typed._type) {
14
- case 'symbol':
48
+ case "symbol":
15
49
  return typed.value; // ?e, ?id, etc.
16
- case 'uuid':
50
+ case "uuid":
17
51
  return `#uuid "${typed.value}"`;
18
- case 'keyword':
19
- return typed.value.startsWith(':') ? typed.value : `:${typed.value}`;
52
+ case "keyword":
53
+ // Ensure leading colon when emitting
54
+ return `:${typed.value}`;
20
55
  }
21
56
  }
22
57
  if (typeof obj === "string") {
23
- // Keywords and symbols don't get quoted, strings do
24
- if (obj.startsWith(":") || obj.startsWith("?")) {
25
- return obj;
26
- }
58
+ // Strings are always quoted; callers must use sym/kw for special values
27
59
  return `"${obj.replace(/"/g, '\\"')}"`;
28
60
  }
29
61
  if (typeof obj === "number")
@@ -38,360 +70,296 @@ export function stringifyEdn(obj) {
38
70
  return "[" + obj.map(stringifyEdn).join(" ") + "]";
39
71
  }
40
72
  if (typeof obj === "object") {
73
+ // Keywordize JS object keys by emitting them as EDN keywords
41
74
  const pairs = Object.entries(obj).map(([key, value]) => {
42
- // Handle Datomic keyword keys
43
- const ednKey = key.startsWith(":") ? key : `:${key}`;
75
+ if (key.startsWith(":")) {
76
+ throw new QueryInputError("Object keys representing keywords must not include a leading ':'", { key });
77
+ }
78
+ const ednKey = `:${key}`;
44
79
  return `${ednKey} ${stringifyEdn(value)}`;
45
80
  });
46
81
  return "{" + pairs.join(" ") + "}";
47
82
  }
48
83
  return String(obj);
49
84
  }
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;
85
+ // Normalize edn-data parsed value into JS with keywordized keys (no leading colon)
86
+ function normalizeEdn(value) {
87
+ if (value === null || value === undefined)
88
+ return value;
89
+ // Preserve native Date instances returned by the parser
90
+ if (value instanceof Date)
91
+ return value;
92
+ // edn-data often wraps keywords/symbols/strings as { key: '...' } or { sym: '...' }
93
+ if (typeof value === "object" && value) {
94
+ if (typeof value.key === "string") {
95
+ const key = value.key; // e.g., ":db/ident"
96
+ return key.startsWith(":") ? key.slice(1) : key;
97
+ }
98
+ if (typeof value.sym === "string") {
99
+ return value.sym;
63
100
  }
64
- // Sets -> Arrays
65
101
  if (value instanceof Set) {
66
- return Array.from(value, (v) => this.normalizeEdn(v));
102
+ return Array.from(value, (v) => normalizeEdn(v));
67
103
  }
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));
104
+ if (Array.isArray(value.set)) {
105
+ return value.set.map((v) => normalizeEdn(v));
71
106
  }
72
- // edn-data map representation: { map: [[k,v] ...] }
73
- if (typeof value === 'object' && value && Array.isArray(value.map)) {
107
+ if (Array.isArray(value.map)) {
74
108
  const out = {};
75
109
  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);
110
+ const kNorm = normalizeEdn(k);
111
+ const kStr = typeof kNorm === "string" ? kNorm : String(kNorm);
112
+ const bareKey = kStr.startsWith(":") ? kStr.slice(1) : kStr;
113
+ out[bareKey] = normalizeEdn(v);
79
114
  }
80
115
  return out;
81
116
  }
82
- // Arrays
83
- if (Array.isArray(value)) {
84
- return value.map((v) => this.normalizeEdn(v));
117
+ }
118
+ if (Array.isArray(value))
119
+ return value.map((v) => normalizeEdn(v));
120
+ // Tagged values
121
+ if (typeof value === "object" &&
122
+ value &&
123
+ value.tag === "inst" &&
124
+ typeof value.val === "string") {
125
+ const d = new Date(value.val);
126
+ return isNaN(d.getTime()) ? value.val : d;
127
+ }
128
+ if (typeof value === "object" &&
129
+ value &&
130
+ value.tag === "uuid" &&
131
+ typeof value.val === "string") {
132
+ return { _type: "uuid", value: value.val };
133
+ }
134
+ if (typeof value === "object") {
135
+ const out = Array.isArray(value) ? [] : {};
136
+ for (const [k, v] of Object.entries(value)) {
137
+ out[k] = normalizeEdn(v);
85
138
  }
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);
139
+ return out;
140
+ }
141
+ return value;
142
+ }
143
+ // ===== Validation =====
144
+ function isKeyword(v) {
145
+ return typeof v === "object" && v !== null && v._type === "keyword";
146
+ }
147
+ function isSymbol(v) {
148
+ return typeof v === "object" && v !== null && v._type === "symbol";
149
+ }
150
+ function validateSymbolicQuery(q) {
151
+ if (!q ||
152
+ typeof q !== "object" ||
153
+ !Array.isArray(q.find) ||
154
+ !Array.isArray(q.where)) {
155
+ throw new QueryInputError("Invalid query object: requires { find: [], where: [] }");
156
+ }
157
+ // Enforce attribute positions to be Keyword in where clauses when pattern triples
158
+ for (let i = 0; i < q.where.length; i++) {
159
+ const clause = q.where[i];
160
+ if (Array.isArray(clause) &&
161
+ clause.length >= 3 &&
162
+ !Array.isArray(clause[0])) {
163
+ const attr = clause[1];
164
+ if (!isKeyword(attr)) {
165
+ throw new QueryInputError("Attribute position in where clause must be a Keyword", {
166
+ clauseIndex: i,
167
+ clause,
168
+ });
103
169
  }
104
- return out;
105
170
  }
106
- return value;
107
171
  }
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}`);
172
+ }
173
+ // ===== Root HTTP Client =====
174
+ export class DatomicClient {
175
+ constructor(config) {
176
+ this.config = config;
177
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
178
+ this.fetchImpl = (config.fetchImpl ||
179
+ globalThis.fetch);
180
+ if (!this.fetchImpl) {
181
+ throw new TransportError("fetch implementation is required in this environment");
126
182
  }
127
- // Parse EDN response
128
- const responseText = await response.text();
129
- const parsed = parseEdn(responseText);
130
- return this.normalizeEdn(parsed);
131
183
  }
132
- // List databases in a storage
133
- async listDatabases(storageAlias) {
134
- return this.request(`/data/${storageAlias}/`);
184
+ async authHeader() {
185
+ let token = this.config.authToken;
186
+ if (!token && this.config.getAuthToken) {
187
+ const s = this.config.getAuthToken();
188
+ token = typeof s === "string" ? s : await s;
189
+ }
190
+ if (!token) {
191
+ throw new AuthError("Missing signing secret (provide authentication jwt or getAuthToken in client config)");
192
+ }
193
+ return { Authorization: `Bearer ${token}` };
135
194
  }
136
- // Create database
137
- async createDatabase(storageAlias, dbName) {
138
- return this.request(`/data/${storageAlias}/`, {
195
+ async request(endpoint, body) {
196
+ const url = `${this.baseUrl}${endpoint}`;
197
+ const headers = {
198
+ "Content-Type": "application/edn",
199
+ Accept: "application/edn",
200
+ ...(this.config.headers || {}),
201
+ ...(await this.authHeader()),
202
+ };
203
+ const res = await this.fetchImpl(url, {
139
204
  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",
205
+ headers,
206
+ body: body === undefined ? undefined : stringifyEdn(body),
147
207
  });
208
+ if (!res.ok) {
209
+ let serverBody = undefined;
210
+ try {
211
+ const txt = await res.text();
212
+ serverBody = normalizeEdn(parseEdn(txt));
213
+ }
214
+ catch (_) {
215
+ // ignore parse errors
216
+ }
217
+ throw new ServerError(`HTTP ${res.status} ${res.statusText}`, {
218
+ status: res.status,
219
+ endpoint,
220
+ serverBody,
221
+ });
222
+ }
223
+ const text = await res.text();
224
+ const parsed = parseEdn(text);
225
+ return normalizeEdn(parsed);
148
226
  }
149
- // Transaction operations
227
+ // Low-level endpoints (path-style)
150
228
  async transact(storageAlias, dbName, txData) {
229
+ // Basic schema validation: txData must be an array of entity maps or operation vectors
230
+ if (!Array.isArray(txData)) {
231
+ throw new SchemaError("txData must be an array of entities/ops");
232
+ }
151
233
  return this.request(`/data/${storageAlias}/${dbName}/`, {
152
- method: "POST",
153
- body: { ":tx-data": txData },
234
+ "tx-data": txData,
154
235
  });
155
236
  }
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) {
237
+ async querySymbolic(q, args = [], options) {
238
+ validateSymbolicQuery(q);
211
239
  const body = {
212
- ":q": query,
213
- ":args": args,
240
+ q: [
241
+ kw("find"),
242
+ ...q.find,
243
+ ...(q.in && q.in.length ? [kw("in"), ...q.in] : []),
244
+ kw("where"),
245
+ ...q.where,
246
+ ],
247
+ args,
214
248
  };
215
249
  if (options?.limit !== undefined)
216
- body[":limit"] = options.limit;
250
+ body["limit"] = options.limit;
217
251
  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;
252
+ body["offset"] = options.offset;
253
+ return this.request(`/api/query`, body);
228
254
  }
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
255
  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]];
256
+ const factory = this.config.eventSourceFactory || globalThis.EventSource;
257
+ if (!factory) {
258
+ throw new TransportError("EventSource not available. Provide eventSourceFactory in client config for Node.");
269
259
  }
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 };
260
+ const url = `${this.baseUrl}/events/${storageAlias}/${dbName}`;
261
+ const es = new factory(url);
262
+ es.onmessage = onEvent;
263
+ if (onError)
264
+ es.onerror = onError;
265
+ return es;
342
266
  }
343
- return value;
344
267
  }
345
- // Convenience class for working with a specific database
346
- export class DatomicDatabase {
268
+ // ===== Convenience wrapper for a specific database =====
269
+ export class AnyDBClient {
347
270
  constructor(client, storageAlias, dbName) {
348
271
  this.client = client;
349
272
  this.storageAlias = storageAlias;
350
273
  this.dbName = dbName;
351
274
  }
275
+ async info() {
276
+ if (shouldUseDataScript(this.storageAlias)) {
277
+ const backend = new DataScriptBackend(this.storageAlias, this.dbName);
278
+ return backend.databaseInfo();
279
+ }
280
+ // Exercise connectivity/auth via a trivial query; propagate errors to caller.
281
+ const q = { find: [sym("?e")], where: [[sym("?e"), kw("db/id"), 0]] };
282
+ await this.query(q);
283
+ return { "basis-t": -1, "db/alias": `${this.storageAlias}/${this.dbName}` };
284
+ }
352
285
  async transact(txData) {
286
+ if (shouldUseDataScript(this.storageAlias)) {
287
+ const backend = new DataScriptBackend(this.storageAlias, this.dbName);
288
+ return backend.transact(txData);
289
+ }
353
290
  return this.client.transact(this.storageAlias, this.dbName, txData);
354
291
  }
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) {
292
+ async query(q, ...args) {
293
+ if (shouldUseDataScript(this.storageAlias)) {
294
+ const backend = new DataScriptBackend(this.storageAlias, this.dbName);
295
+ validateSymbolicQuery(q);
296
+ const ednQuery = [
297
+ kw("find"),
298
+ ...q.find,
299
+ ...(q.in && q.in.length ? [kw("in"), ...q.in] : []),
300
+ kw("where"),
301
+ ...q.where,
302
+ ];
303
+ return backend.query(ednQuery, ...args);
304
+ }
305
+ // Server path: use /api/query with db descriptor as first arg
368
306
  const dbDescriptor = {
369
307
  "db/alias": `${this.storageAlias}/${this.dbName}`,
370
308
  };
371
- return this.client.query(query, [dbDescriptor, ...args]);
309
+ return this.client.querySymbolic(q, [dbDescriptor, ...args]);
372
310
  }
373
- async querySymbolic(query, ...args) {
374
- const dbDescriptor = {
375
- "db/alias": `${this.storageAlias}/${this.dbName}`,
311
+ async entity(entityId, basisT = "-", options) {
312
+ if (shouldUseDataScript(this.storageAlias)) {
313
+ const backend = new DataScriptBackend(this.storageAlias, this.dbName);
314
+ return backend.entity(entityId);
315
+ }
316
+ // Use the query API to retrieve entity via pull
317
+ const q = {
318
+ find: [[sym("pull"), sym("?e"), [kw("db/id")]]],
319
+ where: [[sym("?e"), kw("db/id"), entityId]],
376
320
  };
377
- return this.client.querySymbolic(query, [dbDescriptor, ...args]);
321
+ const rows = await this.query(q);
322
+ const first = Array.isArray(rows) ? rows[0]?.[0] : null;
323
+ return first || { "db/id": entityId };
324
+ }
325
+ async datoms(index, options = {}) {
326
+ if (shouldUseDataScript(this.storageAlias)) {
327
+ const backend = new DataScriptBackend(this.storageAlias, this.dbName);
328
+ return backend.datoms(index, {
329
+ e: options.e,
330
+ a: options.a,
331
+ v: options.v,
332
+ limit: options.limit,
333
+ offset: options.offset,
334
+ });
335
+ }
336
+ // Server exposes datoms via query; implement minimal AVET example
337
+ const clauses = [];
338
+ const e = options.e !== undefined ? options.e : sym("?e");
339
+ const a = options.a !== undefined ? kw(options.a) : sym("?a");
340
+ const v = options.v !== undefined ? options.v : sym("?v");
341
+ clauses.push([e, a, v]);
342
+ const q = { find: [e, a, v], where: clauses };
343
+ const rows = await this.query(q);
344
+ return rows.map((r) => ({ e: r[0], a: r[1], v: r[2], tx: 0, added: true }));
378
345
  }
379
346
  subscribeToEvents(onEvent, onError) {
380
347
  return this.client.subscribeToEvents(this.storageAlias, this.dbName, onEvent, onError);
381
348
  }
382
349
  }
383
- // Factory functions
350
+ // ===== Factories =====
384
351
  export function createDatomicClient(config) {
385
352
  return new DatomicClient(config);
386
353
  }
387
- export function createDatomicDatabase(client, storageAlias, dbName) {
388
- return new DatomicDatabase(client, storageAlias, dbName);
354
+ export function createAnyDBClient(client, storageAlias, dbName) {
355
+ return new AnyDBClient(client, storageAlias, dbName);
389
356
  }
390
- // Small helpers
357
+ // Convenience
391
358
  export function pluckFirstColumn(rows) {
392
359
  if (!Array.isArray(rows))
393
360
  return [];
394
361
  return rows.map((r) => (Array.isArray(r) ? r[0] : r));
395
362
  }
396
- // Export everything
363
+ // Re-export DataScript helpers for consumers that need feature-detection
364
+ export { shouldUseDataScript, isDataScriptAvailable };
397
365
  export default DatomicClient;