@datar-platform/better-auth-dynamodb 0.1.0-alpha.0 → 0.1.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/index.d.ts +267 -17
  3. package/dist/index.js +623 -13
  4. package/dist/index.js.map +1 -1
  5. package/package.json +33 -23
  6. package/dist/adapter.d.ts +0 -19
  7. package/dist/adapter.d.ts.map +0 -1
  8. package/dist/adapter.js +0 -153
  9. package/dist/adapter.js.map +0 -1
  10. package/dist/index-map.d.ts +0 -33
  11. package/dist/index-map.d.ts.map +0 -1
  12. package/dist/index-map.js +0 -1
  13. package/dist/index-map.js.map +0 -1
  14. package/dist/index.d.ts.map +0 -1
  15. package/dist/pagination.d.ts +0 -25
  16. package/dist/pagination.d.ts.map +0 -1
  17. package/dist/pagination.js +0 -86
  18. package/dist/pagination.js.map +0 -1
  19. package/dist/planner.d.ts +0 -36
  20. package/dist/planner.d.ts.map +0 -1
  21. package/dist/planner.js +0 -55
  22. package/dist/planner.js.map +0 -1
  23. package/dist/stores/default/derive-index-map.d.ts +0 -18
  24. package/dist/stores/default/derive-index-map.d.ts.map +0 -1
  25. package/dist/stores/default/derive-index-map.js +0 -46
  26. package/dist/stores/default/derive-index-map.js.map +0 -1
  27. package/dist/stores/default/key-codec.d.ts +0 -57
  28. package/dist/stores/default/key-codec.d.ts.map +0 -1
  29. package/dist/stores/default/key-codec.js +0 -106
  30. package/dist/stores/default/key-codec.js.map +0 -1
  31. package/dist/stores/default/schema.d.ts +0 -28
  32. package/dist/stores/default/schema.d.ts.map +0 -1
  33. package/dist/stores/default/schema.js +0 -91
  34. package/dist/stores/default/schema.js.map +0 -1
  35. package/dist/stores/default/single-table-store.d.ts +0 -26
  36. package/dist/stores/default/single-table-store.d.ts.map +0 -1
  37. package/dist/stores/default/single-table-store.js +0 -128
  38. package/dist/stores/default/single-table-store.js.map +0 -1
  39. package/dist/types.d.ts +0 -97
  40. package/dist/types.d.ts.map +0 -1
  41. package/dist/types.js +0 -1
  42. package/dist/types.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,15 +1,625 @@
1
- /**
2
- * @datar-platform/better-auth-dynamodb
3
- *
4
- * A generic DynamoDB adapter for Better Auth. Use the built-in single-table
5
- * store, or bring your own by implementing {@link DynamoStore}.
1
+ // src/adapter.ts
2
+ import { createAdapterFactory } from "better-auth/adapters";
3
+
4
+ // src/pagination.ts
5
+ async function drainPages(fetch) {
6
+ const out = [];
7
+ let cursor = void 0;
8
+ do {
9
+ const page = await fetch(cursor);
10
+ out.push(...page.items);
11
+ cursor = page.cursor;
12
+ } while (cursor);
13
+ return out;
14
+ }
15
+ function matchOne(item, w) {
16
+ const actual = item[w.field];
17
+ const expected = w.value;
18
+ switch (w.operator) {
19
+ case "eq":
20
+ return actual === expected;
21
+ case "ne":
22
+ return actual !== expected;
23
+ case "lt":
24
+ return actual < expected;
25
+ case "lte":
26
+ return actual <= expected;
27
+ case "gt":
28
+ return actual > expected;
29
+ case "gte":
30
+ return actual >= expected;
31
+ case "in":
32
+ return Array.isArray(expected) && expected.includes(actual);
33
+ case "not_in":
34
+ return Array.isArray(expected) && !expected.includes(actual);
35
+ case "contains":
36
+ return String(actual).includes(String(expected));
37
+ case "starts_with":
38
+ return String(actual).startsWith(String(expected));
39
+ case "ends_with":
40
+ return String(actual).endsWith(String(expected));
41
+ default:
42
+ return actual === expected;
43
+ }
44
+ }
45
+ function matchesResidual(item, residual) {
46
+ const andOk = residual.filter((w) => w.connector !== "OR").every((w) => matchOne(item, w));
47
+ if (!andOk) return false;
48
+ const or = residual.filter((w) => w.connector === "OR");
49
+ return or.length === 0 || or.some((w) => matchOne(item, w));
50
+ }
51
+ function applySort(items, sortBy) {
52
+ if (!sortBy) return items;
53
+ const { field, direction } = sortBy;
54
+ const dir = direction === "asc" ? 1 : -1;
55
+ return [...items].sort((a, b) => {
56
+ if (a[field] < b[field]) return -dir;
57
+ if (a[field] > b[field]) return dir;
58
+ return 0;
59
+ });
60
+ }
61
+ function applyWindow(items, offset, limit) {
62
+ let out = items;
63
+ if (offset && offset > 0) out = out.slice(offset);
64
+ if (limit && limit > 0) out = out.slice(0, limit);
65
+ return out;
66
+ }
67
+
68
+ // src/planner.ts
69
+ function isKeyable(w) {
70
+ return w.operator === "eq" && w.connector === "AND" && w.value != null;
71
+ }
72
+ function planQuery(model, where, indexMap) {
73
+ const eqByField = /* @__PURE__ */ new Map();
74
+ for (const w of where) {
75
+ if (isKeyable(w) && !eqByField.has(w.field)) eqByField.set(w.field, w);
76
+ }
77
+ const idClause = eqByField.get("id");
78
+ if (idClause) {
79
+ return {
80
+ kind: "byId",
81
+ id: String(idClause.value),
82
+ residual: where.filter((w) => w !== idClause)
83
+ };
84
+ }
85
+ const patterns = indexMap[model] ?? [];
86
+ for (const pattern of patterns) {
87
+ if (!pattern.pk.every((f) => eqByField.has(f))) continue;
88
+ const key = {};
89
+ const consumed = /* @__PURE__ */ new Set();
90
+ for (const f of pattern.pk) {
91
+ const clause = eqByField.get(f);
92
+ key[f] = clause.value;
93
+ consumed.add(clause);
94
+ }
95
+ for (const f of pattern.sk ?? []) {
96
+ const clause = eqByField.get(f);
97
+ if (!clause) break;
98
+ key[f] = clause.value;
99
+ consumed.add(clause);
100
+ }
101
+ return {
102
+ kind: "index",
103
+ index: pattern.index,
104
+ key,
105
+ residual: where.filter((w) => !consumed.has(w))
106
+ };
107
+ }
108
+ return { kind: "listByType", residual: where };
109
+ }
110
+
111
+ // src/stores/default/derive-index-map.ts
112
+ function lookupIndexName(field) {
113
+ return `by_${field}`;
114
+ }
115
+ function deriveIndexMap(schema) {
116
+ const map = {};
117
+ for (const [model, table] of Object.entries(schema)) {
118
+ const patterns = [];
119
+ const seen = /* @__PURE__ */ new Set();
120
+ const add = (field) => {
121
+ if (field === "id" || seen.has(field)) return;
122
+ seen.add(field);
123
+ patterns.push({ index: lookupIndexName(field), pk: [field] });
124
+ };
125
+ for (const [field, attr] of Object.entries(table.fields)) {
126
+ if (attr.unique) add(field);
127
+ }
128
+ for (const [field, attr] of Object.entries(table.fields)) {
129
+ if (attr.references) add(field);
130
+ }
131
+ for (const [field, attr] of Object.entries(table.fields)) {
132
+ if (attr.index) add(field);
133
+ }
134
+ if (patterns.length > 0) map[model] = patterns;
135
+ }
136
+ return map;
137
+ }
138
+
139
+ // src/stores/default/single-table-store.ts
140
+ import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
141
+ import {
142
+ DeleteCommand,
143
+ DynamoDBDocumentClient,
144
+ GetCommand,
145
+ PutCommand,
146
+ QueryCommand
147
+ } from "@aws-sdk/lib-dynamodb";
148
+
149
+ // src/stores/default/key-codec.ts
150
+ var PK = "__ba_pk";
151
+ var SK = "__ba_sk";
152
+ var TYPE_PK = "__ba_tpk";
153
+ var TYPE_SK = "__ba_tsk";
154
+ var TYPE_INDEX = "byType";
155
+ var SK_CONST = "#";
156
+ var RESERVED_PREFIX = "__ba_";
157
+ var gsiName = (slot) => `lookup${slot}`;
158
+ var gsiPk = (slot) => `__ba_g${slot}pk`;
159
+ var gsiSk = (slot) => `__ba_g${slot}sk`;
160
+ var enc = (v) => String(v);
161
+ var joinValues = (values) => values.map(enc).join("#");
162
+ function assignSlots(indexMap) {
163
+ const slots = {};
164
+ let maxSlots = 0;
165
+ for (const [model, patterns] of Object.entries(indexMap)) {
166
+ const modelSlots = {};
167
+ patterns.forEach((pattern, i) => {
168
+ modelSlots[pattern.index] = i + 1;
169
+ });
170
+ slots[model] = modelSlots;
171
+ maxSlots = Math.max(maxSlots, patterns.length);
172
+ }
173
+ return { slots, maxSlots };
174
+ }
175
+ var primaryKey = (model, id) => ({
176
+ [PK]: `${model}#${id}`,
177
+ [SK]: SK_CONST
178
+ });
179
+ var lookupPkValue = (model, index, values) => `${model}#${index}#${joinValues(values)}`;
180
+ function encodeKeys(model, item, indexMap, assignment) {
181
+ const id = String(item.id);
182
+ const keys = {
183
+ ...primaryKey(model, id),
184
+ [TYPE_PK]: model,
185
+ [TYPE_SK]: `${enc(item.createdAt ?? "")}#${id}`
186
+ };
187
+ for (const pattern of indexMap[model] ?? []) {
188
+ const pkValues = pattern.pk.map((f) => item[f]);
189
+ if (pkValues.some((v) => v == null)) continue;
190
+ const slot = assignment.slots[model]?.[pattern.index];
191
+ if (!slot) continue;
192
+ keys[gsiPk(slot)] = lookupPkValue(model, pattern.index, pkValues);
193
+ const skValues = (pattern.sk ?? []).map((f) => item[f]);
194
+ keys[gsiSk(slot)] = skValues.some((v) => v == null) ? `${id}` : `${joinValues(skValues)}#${id}`;
195
+ }
196
+ return keys;
197
+ }
198
+ function encodeLookupQuery(model, index, key, indexMap, assignment) {
199
+ const slot = assignment.slots[model]?.[index];
200
+ if (!slot) {
201
+ throw new Error(
202
+ `No physical slot for index "${index}" on model "${model}". Ensure the index is present in the adapter's index map.`
203
+ );
204
+ }
205
+ const pattern = (indexMap[model] ?? []).find((p) => p.index === index);
206
+ const pkValues = pattern.pk.map((f) => key[f]);
207
+ const skPresent = (pattern.sk ?? []).map((f) => key[f]).filter((v) => v != null);
208
+ return {
209
+ indexName: gsiName(slot),
210
+ pkAttr: gsiPk(slot),
211
+ pkValue: lookupPkValue(model, index, pkValues),
212
+ skAttr: gsiSk(slot),
213
+ skPrefix: skPresent.length > 0 ? `${joinValues(skPresent)}#` : void 0
214
+ };
215
+ }
216
+ function stripReserved(item) {
217
+ if (!item) return null;
218
+ const clean = {};
219
+ for (const [k, v] of Object.entries(item)) {
220
+ if (!k.startsWith(RESERVED_PREFIX)) clean[k] = v;
221
+ }
222
+ return clean;
223
+ }
224
+
225
+ // src/stores/default/schema.ts
226
+ import {
227
+ CreateTableCommand,
228
+ ResourceInUseException,
229
+ waitUntilTableExists
230
+ } from "@aws-sdk/client-dynamodb";
231
+ function buildTableDefinition(tableName, lookupSlots) {
232
+ const attr = (name) => ({
233
+ AttributeName: name,
234
+ AttributeType: "S"
235
+ });
236
+ const attributeDefinitions = [
237
+ attr(PK),
238
+ attr(SK),
239
+ attr(TYPE_PK),
240
+ attr(TYPE_SK)
241
+ ];
242
+ const globalSecondaryIndexes = [
243
+ {
244
+ IndexName: TYPE_INDEX,
245
+ KeySchema: [
246
+ { AttributeName: TYPE_PK, KeyType: "HASH" },
247
+ { AttributeName: TYPE_SK, KeyType: "RANGE" }
248
+ ],
249
+ Projection: { ProjectionType: "ALL" }
250
+ }
251
+ ];
252
+ for (let slot = 1; slot <= lookupSlots; slot++) {
253
+ attributeDefinitions.push(attr(gsiPk(slot)), attr(gsiSk(slot)));
254
+ globalSecondaryIndexes.push({
255
+ IndexName: gsiName(slot),
256
+ KeySchema: [
257
+ { AttributeName: gsiPk(slot), KeyType: "HASH" },
258
+ { AttributeName: gsiSk(slot), KeyType: "RANGE" }
259
+ ],
260
+ Projection: { ProjectionType: "ALL" }
261
+ });
262
+ }
263
+ return {
264
+ TableName: tableName,
265
+ BillingMode: "PAY_PER_REQUEST",
266
+ KeySchema: [
267
+ { AttributeName: PK, KeyType: "HASH" },
268
+ { AttributeName: SK, KeyType: "RANGE" }
269
+ ],
270
+ AttributeDefinitions: attributeDefinitions,
271
+ GlobalSecondaryIndexes: globalSecondaryIndexes
272
+ };
273
+ }
274
+ async function ensureSchema(opts) {
275
+ const input = buildTableDefinition(opts.tableName, opts.lookupSlots);
276
+ try {
277
+ await opts.client.send(new CreateTableCommand(input));
278
+ } catch (error) {
279
+ if (!(error instanceof ResourceInUseException)) throw error;
280
+ }
281
+ await waitUntilTableExists(
282
+ { client: opts.client, maxWaitTime: 60 },
283
+ { TableName: opts.tableName }
284
+ );
285
+ }
286
+ function generateSchemaFile(opts) {
287
+ const table = buildTableDefinition(opts.tableName, opts.lookupSlots);
288
+ const template = {
289
+ AWSTemplateFormatVersion: "2010-09-09",
290
+ Resources: {
291
+ BetterAuthTable: { Type: "AWS::DynamoDB::Table", Properties: table }
292
+ }
293
+ };
294
+ const code = `/**
295
+ * Auto-generated DynamoDB CloudFormation template for @datar-platform/better-auth-dynamodb.
296
+ * Table: ${opts.tableName} \u2014 ${opts.lookupSlots} lookup GSI(s) + 1 byType GSI.
297
+ * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html
6
298
  */
7
- export { dynamoAdapter } from "./adapter.js";
8
- export { planQuery } from "./planner.js";
9
- export { matchesResidual } from "./pagination.js";
10
- // Built-in single-table store (opt-in for advanced wiring / tests).
11
- export { createSingleTableStore } from "./stores/default/single-table-store.js";
12
- export { deriveIndexMap } from "./stores/default/derive-index-map.js";
13
- export { buildTableDefinition, ensureSchema, generateSchemaFile, } from "./stores/default/schema.js";
14
- export { assignSlots } from "./stores/default/key-codec.js";
299
+ export const dynamoDBSchema = ${JSON.stringify(template, null, 2)} as const;
300
+
301
+ export default dynamoDBSchema;
302
+ `;
303
+ return {
304
+ code,
305
+ path: opts.file ?? "dynamodb-cloudformation.ts",
306
+ overwrite: true
307
+ };
308
+ }
309
+
310
+ // src/stores/default/single-table-store.ts
311
+ function createSingleTableStore(opts) {
312
+ const tableName = opts.tableName ?? process.env.DYNAMODB_TABLE_NAME ?? "better-auth";
313
+ const doc = opts.documentClient ?? DynamoDBDocumentClient.from(
314
+ new DynamoDBClient({
315
+ ...opts.region ? { region: opts.region } : {},
316
+ ...opts.endpoint ? { endpoint: opts.endpoint } : {}
317
+ }),
318
+ { marshallOptions: { removeUndefinedValues: true } }
319
+ );
320
+ const indexMap = opts.indexMap;
321
+ const assignment = assignSlots(indexMap);
322
+ const query = async (input, cursor, countOnly = false) => {
323
+ const res = await doc.send(
324
+ new QueryCommand({
325
+ TableName: tableName,
326
+ ExclusiveStartKey: cursor,
327
+ ...countOnly ? { Select: "COUNT" } : {},
328
+ ...input
329
+ })
330
+ );
331
+ return {
332
+ items: countOnly ? [] : (res.Items ?? []).map((i) => stripReserved(i)),
333
+ count: res.Count ?? 0,
334
+ cursor: res.LastEvaluatedKey
335
+ };
336
+ };
337
+ return {
338
+ async put(model, item) {
339
+ await doc.send(
340
+ new PutCommand({
341
+ TableName: tableName,
342
+ Item: { ...item, ...encodeKeys(model, item, indexMap, assignment) }
343
+ })
344
+ );
345
+ return item;
346
+ },
347
+ async getById(model, id) {
348
+ const res = await doc.send(
349
+ new GetCommand({ TableName: tableName, Key: primaryKey(model, id) })
350
+ );
351
+ return stripReserved(res.Item);
352
+ },
353
+ async update(model, id, patch) {
354
+ const res = await doc.send(
355
+ new GetCommand({ TableName: tableName, Key: primaryKey(model, id) })
356
+ );
357
+ const existing = stripReserved(res.Item);
358
+ if (!existing) return null;
359
+ const merged = { ...existing, ...patch };
360
+ await doc.send(
361
+ new PutCommand({
362
+ TableName: tableName,
363
+ Item: {
364
+ ...merged,
365
+ ...encodeKeys(model, merged, indexMap, assignment)
366
+ }
367
+ })
368
+ );
369
+ return merged;
370
+ },
371
+ async deleteById(model, id) {
372
+ await doc.send(
373
+ new DeleteCommand({ TableName: tableName, Key: primaryKey(model, id) })
374
+ );
375
+ },
376
+ async queryIndex({ model, index, key, cursor }) {
377
+ const lookup = encodeLookupQuery(model, index, key, indexMap, assignment);
378
+ const names = { "#pk": lookup.pkAttr };
379
+ const values = { ":pk": lookup.pkValue };
380
+ let condition = "#pk = :pk";
381
+ if (lookup.skPrefix) {
382
+ names["#sk"] = lookup.skAttr;
383
+ values[":skp"] = lookup.skPrefix;
384
+ condition += " AND begins_with(#sk, :skp)";
385
+ }
386
+ const { items, cursor: next } = await query(
387
+ {
388
+ IndexName: lookup.indexName,
389
+ KeyConditionExpression: condition,
390
+ ExpressionAttributeNames: names,
391
+ ExpressionAttributeValues: values
392
+ },
393
+ cursor
394
+ );
395
+ return { items, cursor: next };
396
+ },
397
+ async listByType({ model, cursor }) {
398
+ const { items, cursor: next } = await query(
399
+ {
400
+ IndexName: TYPE_INDEX,
401
+ KeyConditionExpression: "#tpk = :tpk",
402
+ ExpressionAttributeNames: { "#tpk": TYPE_PK },
403
+ ExpressionAttributeValues: { ":tpk": model }
404
+ },
405
+ cursor
406
+ );
407
+ return { items, cursor: next };
408
+ },
409
+ async count({ model, index, key }) {
410
+ let base;
411
+ if (index && key) {
412
+ const lookup = encodeLookupQuery(
413
+ model,
414
+ index,
415
+ key,
416
+ indexMap,
417
+ assignment
418
+ );
419
+ base = {
420
+ IndexName: lookup.indexName,
421
+ KeyConditionExpression: "#pk = :pk",
422
+ ExpressionAttributeNames: { "#pk": lookup.pkAttr },
423
+ ExpressionAttributeValues: { ":pk": lookup.pkValue }
424
+ };
425
+ } else {
426
+ base = {
427
+ IndexName: TYPE_INDEX,
428
+ KeyConditionExpression: "#tpk = :tpk",
429
+ ExpressionAttributeNames: { "#tpk": TYPE_PK },
430
+ ExpressionAttributeValues: { ":tpk": model }
431
+ };
432
+ }
433
+ let total = 0;
434
+ let cursor = void 0;
435
+ do {
436
+ const page = await query(base, cursor, true);
437
+ total += page.count;
438
+ cursor = page.cursor;
439
+ } while (cursor);
440
+ return total;
441
+ },
442
+ createSchema: ({ file }) => Promise.resolve(
443
+ generateSchemaFile({
444
+ tableName,
445
+ lookupSlots: assignment.maxSlots,
446
+ file
447
+ })
448
+ )
449
+ };
450
+ }
451
+
452
+ // src/adapter.ts
453
+ var dynamoAdapter = (config = {}) => {
454
+ return createAdapterFactory({
455
+ config: {
456
+ adapterId: "dynamodb",
457
+ adapterName: "DynamoDB",
458
+ // DynamoDB keys are strings; let Better Auth generate string ids.
459
+ supportsNumericIds: false,
460
+ // We serialize JSON ourselves via the SDK document client.
461
+ supportsJSON: true,
462
+ // No native Date type — stored as ISO strings (see transforms below).
463
+ supportsDates: false,
464
+ supportsBooleans: true,
465
+ usePlural: false,
466
+ debugLogs: config.debugLogs,
467
+ // Dates <-> ISO strings, since supportsDates is false.
468
+ customTransformInput: ({ data, fieldAttributes }) => fieldAttributes.type === "date" && data instanceof Date ? data.toISOString() : data,
469
+ customTransformOutput: ({ data, fieldAttributes }) => fieldAttributes.type === "date" && typeof data === "string" ? new Date(data) : data
470
+ },
471
+ adapter: ({ schema, debugLog, getDefaultModelName }) => {
472
+ const indexMap = config.indexMap ?? deriveIndexMap(schema);
473
+ const store = config.store ?? createSingleTableStore({
474
+ tableName: config.tableName,
475
+ region: config.region,
476
+ endpoint: config.endpoint,
477
+ indexMap
478
+ });
479
+ const queryAll = async (model, where) => {
480
+ const clauses = where ?? [];
481
+ const plan = planQuery(model, clauses, indexMap);
482
+ debugLog("queryAll", { model, plan: plan.kind });
483
+ let candidates;
484
+ if (plan.kind === "byId") {
485
+ const one = await store.getById(model, plan.id);
486
+ candidates = one ? [one] : [];
487
+ } else if (plan.kind === "index") {
488
+ candidates = await drainPages(
489
+ (cursor) => store.queryIndex({
490
+ model,
491
+ index: plan.index,
492
+ key: plan.key,
493
+ cursor
494
+ })
495
+ );
496
+ } else {
497
+ candidates = await drainPages(
498
+ (cursor) => store.listByType({ model, cursor })
499
+ );
500
+ }
501
+ return candidates.filter(
502
+ (item) => matchesResidual(item, plan.residual)
503
+ );
504
+ };
505
+ const resolveId = async (model, where) => {
506
+ const plan = planQuery(model, where, indexMap);
507
+ if (plan.kind === "byId") return plan.id;
508
+ const [first] = await queryAll(model, where);
509
+ return first ? String(first.id) : null;
510
+ };
511
+ return {
512
+ async create({
513
+ model,
514
+ data
515
+ }) {
516
+ const m = getDefaultModelName(model);
517
+ const created = await store.put(m, data);
518
+ return created;
519
+ },
520
+ async update({
521
+ model,
522
+ where,
523
+ update
524
+ }) {
525
+ const m = getDefaultModelName(model);
526
+ const id = await resolveId(m, where);
527
+ if (!id) return null;
528
+ const updated = await store.update(m, id, update);
529
+ return updated;
530
+ },
531
+ async updateMany({
532
+ model,
533
+ where,
534
+ update
535
+ }) {
536
+ const m = getDefaultModelName(model);
537
+ const items = await queryAll(m, where);
538
+ await runBatched(
539
+ items,
540
+ (item) => store.update(m, String(item.id), update)
541
+ );
542
+ return items.length;
543
+ },
544
+ async delete({
545
+ model,
546
+ where
547
+ }) {
548
+ const m = getDefaultModelName(model);
549
+ const id = await resolveId(m, where);
550
+ if (id) await store.deleteById(m, id);
551
+ },
552
+ async deleteMany({
553
+ model,
554
+ where
555
+ }) {
556
+ const m = getDefaultModelName(model);
557
+ const items = await queryAll(m, where);
558
+ await runBatched(
559
+ items,
560
+ (item) => store.deleteById(m, String(item.id))
561
+ );
562
+ return items.length;
563
+ },
564
+ async findOne({
565
+ model,
566
+ where
567
+ }) {
568
+ const m = getDefaultModelName(model);
569
+ const [first] = await queryAll(m, where);
570
+ return first ?? null;
571
+ },
572
+ async findMany({
573
+ model,
574
+ where,
575
+ limit,
576
+ sortBy,
577
+ offset
578
+ }) {
579
+ const m = getDefaultModelName(model);
580
+ const items = applyWindow(
581
+ applySort(await queryAll(m, where), sortBy),
582
+ offset,
583
+ limit
584
+ );
585
+ return items;
586
+ },
587
+ async count({
588
+ model,
589
+ where
590
+ }) {
591
+ const m = getDefaultModelName(model);
592
+ const clauses = where ?? [];
593
+ const plan = planQuery(m, clauses, indexMap);
594
+ if (plan.residual.length === 0 && store.count) {
595
+ const fast = await store.count(
596
+ plan.kind === "index" ? { model: m, index: plan.index, key: plan.key } : { model: m }
597
+ );
598
+ if (fast != null) return fast;
599
+ }
600
+ return (await queryAll(m, clauses)).length;
601
+ },
602
+ ...store.createSchema ? {
603
+ createSchema: (props) => store.createSchema(props)
604
+ } : {}
605
+ };
606
+ }
607
+ });
608
+ };
609
+ async function runBatched(items, op, chunkSize = 10) {
610
+ for (let i = 0; i < items.length; i += chunkSize) {
611
+ await Promise.all(items.slice(i, i + chunkSize).map(op));
612
+ }
613
+ }
614
+ export {
615
+ assignSlots,
616
+ buildTableDefinition,
617
+ createSingleTableStore,
618
+ deriveIndexMap,
619
+ dynamoAdapter,
620
+ ensureSchema,
621
+ generateSchemaFile,
622
+ matchesResidual,
623
+ planQuery
624
+ };
15
625
  //# sourceMappingURL=index.js.map