@jaypie/dynamodb 0.1.0 → 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.
@@ -1,71 +1,40 @@
1
1
  import type { FabricEntity } from "./types.js";
2
- /**
3
- * Parameters for getEntity
4
- */
5
- export interface GetEntityParams {
6
- /** Entity ID (sort key) */
7
- id: string;
8
- /** Entity model (partition key) */
9
- model: string;
10
- }
11
- /**
12
- * Parameters for putEntity
13
- */
14
- export interface PutEntityParams<T extends FabricEntity> {
15
- /** The entity to save */
16
- entity: T;
17
- }
18
- /**
19
- * Parameters for updateEntity
20
- */
21
- export interface UpdateEntityParams<T extends FabricEntity> {
22
- /** The entity with updated fields */
23
- entity: T;
24
- }
25
- /**
26
- * Parameters for deleteEntity (soft delete)
27
- */
28
- export interface DeleteEntityParams {
29
- /** Entity ID (sort key) */
30
- id: string;
31
- /** Entity model (partition key) */
32
- model: string;
33
- }
34
- /**
35
- * Parameters for archiveEntity
36
- */
37
- export interface ArchiveEntityParams {
38
- /** Entity ID (sort key) */
39
- id: string;
40
- /** Entity model (partition key) */
41
- model: string;
42
- }
43
2
  /**
44
3
  * Get a single entity by primary key
45
4
  */
46
- export declare function getEntity<T extends FabricEntity = FabricEntity>(params: GetEntityParams): Promise<T | null>;
5
+ export declare const getEntity: import("@jaypie/vocabulary").ServiceHandlerFunction<Record<string, unknown>, FabricEntity | null>;
47
6
  /**
48
7
  * Put (create or replace) an entity
49
8
  * Auto-populates GSI index keys via indexEntity
9
+ *
10
+ * Note: This is a regular async function (not serviceHandler) because it accepts
11
+ * complex FabricEntity objects that can't be coerced by vocabulary's type system.
50
12
  */
51
- export declare function putEntity<T extends FabricEntity>(params: PutEntityParams<T>): Promise<T>;
13
+ export declare function putEntity({ entity, }: {
14
+ entity: FabricEntity;
15
+ }): Promise<FabricEntity>;
52
16
  /**
53
17
  * Update an existing entity
54
18
  * Auto-populates GSI index keys and sets updatedAt
19
+ *
20
+ * Note: This is a regular async function (not serviceHandler) because it accepts
21
+ * complex FabricEntity objects that can't be coerced by vocabulary's type system.
55
22
  */
56
- export declare function updateEntity<T extends FabricEntity>(params: UpdateEntityParams<T>): Promise<T>;
23
+ export declare function updateEntity({ entity, }: {
24
+ entity: FabricEntity;
25
+ }): Promise<FabricEntity>;
57
26
  /**
58
27
  * Soft delete an entity by setting deletedAt timestamp
59
28
  * Re-indexes with appropriate suffix based on archived/deleted state
60
29
  */
61
- export declare function deleteEntity(params: DeleteEntityParams): Promise<boolean>;
30
+ export declare const deleteEntity: import("@jaypie/vocabulary").ServiceHandlerFunction<Record<string, unknown>, boolean>;
62
31
  /**
63
32
  * Archive an entity by setting archivedAt timestamp
64
33
  * Re-indexes with appropriate suffix based on archived/deleted state
65
34
  */
66
- export declare function archiveEntity(params: ArchiveEntityParams): Promise<boolean>;
35
+ export declare const archiveEntity: import("@jaypie/vocabulary").ServiceHandlerFunction<Record<string, unknown>, boolean>;
67
36
  /**
68
37
  * Hard delete an entity (permanently removes from table)
69
38
  * Use with caution - prefer deleteEntity for soft delete
70
39
  */
71
- export declare function destroyEntity(params: DeleteEntityParams): Promise<boolean>;
40
+ export declare const destroyEntity: import("@jaypie/vocabulary").ServiceHandlerFunction<Record<string, unknown>, boolean>;
@@ -3,6 +3,7 @@
3
3
  var clientDynamodb = require('@aws-sdk/client-dynamodb');
4
4
  var libDynamodb = require('@aws-sdk/lib-dynamodb');
5
5
  var errors = require('@jaypie/errors');
6
+ var vocabulary = require('@jaypie/vocabulary');
6
7
 
7
8
  const DEFAULT_REGION = "us-east-1";
8
9
  const LOCAL_CREDENTIALS = {
@@ -187,25 +188,51 @@ function indexEntity(entity, suffix = "") {
187
188
  }
188
189
 
189
190
  /**
190
- * Get a single entity by primary key
191
+ * Calculate suffix based on entity's archived/deleted state
191
192
  */
192
- async function getEntity(params) {
193
- const { id, model } = params;
194
- const docClient = getDocClient();
195
- const tableName = getTableName();
196
- const command = new libDynamodb.GetCommand({
197
- Key: { id, model },
198
- TableName: tableName,
199
- });
200
- const response = await docClient.send(command);
201
- return response.Item ?? null;
193
+ function calculateEntitySuffix(entity) {
194
+ const hasArchived = Boolean(entity.archivedAt);
195
+ const hasDeleted = Boolean(entity.deletedAt);
196
+ if (hasArchived && hasDeleted) {
197
+ return ARCHIVED_SUFFIX + DELETED_SUFFIX;
198
+ }
199
+ if (hasArchived) {
200
+ return ARCHIVED_SUFFIX;
201
+ }
202
+ if (hasDeleted) {
203
+ return DELETED_SUFFIX;
204
+ }
205
+ return "";
202
206
  }
207
+ /**
208
+ * Get a single entity by primary key
209
+ */
210
+ const getEntity = vocabulary.serviceHandler({
211
+ alias: "getEntity",
212
+ description: "Get a single entity by primary key",
213
+ input: {
214
+ id: { type: String, description: "Entity ID (sort key)" },
215
+ model: { type: String, description: "Entity model (partition key)" },
216
+ },
217
+ service: async ({ id, model }) => {
218
+ const docClient = getDocClient();
219
+ const tableName = getTableName();
220
+ const command = new libDynamodb.GetCommand({
221
+ Key: { id, model },
222
+ TableName: tableName,
223
+ });
224
+ const response = await docClient.send(command);
225
+ return response.Item ?? null;
226
+ },
227
+ });
203
228
  /**
204
229
  * Put (create or replace) an entity
205
230
  * Auto-populates GSI index keys via indexEntity
231
+ *
232
+ * Note: This is a regular async function (not serviceHandler) because it accepts
233
+ * complex FabricEntity objects that can't be coerced by vocabulary's type system.
206
234
  */
207
- async function putEntity(params) {
208
- const { entity } = params;
235
+ async function putEntity({ entity, }) {
209
236
  const docClient = getDocClient();
210
237
  const tableName = getTableName();
211
238
  // Auto-populate index keys
@@ -220,9 +247,11 @@ async function putEntity(params) {
220
247
  /**
221
248
  * Update an existing entity
222
249
  * Auto-populates GSI index keys and sets updatedAt
250
+ *
251
+ * Note: This is a regular async function (not serviceHandler) because it accepts
252
+ * complex FabricEntity objects that can't be coerced by vocabulary's type system.
223
253
  */
224
- async function updateEntity(params) {
225
- const { entity } = params;
254
+ async function updateEntity({ entity, }) {
226
255
  const docClient = getDocClient();
227
256
  const tableName = getTableName();
228
257
  // Update timestamp and re-index
@@ -237,98 +266,102 @@ async function updateEntity(params) {
237
266
  await docClient.send(command);
238
267
  return updatedEntity;
239
268
  }
240
- /**
241
- * Calculate suffix based on entity's archived/deleted state
242
- */
243
- function calculateEntitySuffix(entity) {
244
- const hasArchived = Boolean(entity.archivedAt);
245
- const hasDeleted = Boolean(entity.deletedAt);
246
- if (hasArchived && hasDeleted) {
247
- return ARCHIVED_SUFFIX + DELETED_SUFFIX;
248
- }
249
- if (hasArchived) {
250
- return ARCHIVED_SUFFIX;
251
- }
252
- if (hasDeleted) {
253
- return DELETED_SUFFIX;
254
- }
255
- return "";
256
- }
257
269
  /**
258
270
  * Soft delete an entity by setting deletedAt timestamp
259
271
  * Re-indexes with appropriate suffix based on archived/deleted state
260
272
  */
261
- async function deleteEntity(params) {
262
- const { id, model } = params;
263
- const docClient = getDocClient();
264
- const tableName = getTableName();
265
- // Fetch the current entity
266
- const existing = await getEntity({ id, model });
267
- if (!existing) {
268
- return false;
269
- }
270
- const now = new Date().toISOString();
271
- // Build updated entity with deletedAt
272
- const updatedEntity = {
273
- ...existing,
274
- deletedAt: now,
275
- updatedAt: now,
276
- };
277
- // Calculate suffix based on combined state (may already be archived)
278
- const suffix = calculateEntitySuffix(updatedEntity);
279
- const deletedEntity = indexEntity(updatedEntity, suffix);
280
- const command = new libDynamodb.PutCommand({
281
- Item: deletedEntity,
282
- TableName: tableName,
283
- });
284
- await docClient.send(command);
285
- return true;
286
- }
273
+ const deleteEntity = vocabulary.serviceHandler({
274
+ alias: "deleteEntity",
275
+ description: "Soft delete an entity (sets deletedAt timestamp)",
276
+ input: {
277
+ id: { type: String, description: "Entity ID (sort key)" },
278
+ model: { type: String, description: "Entity model (partition key)" },
279
+ },
280
+ service: async ({ id, model }) => {
281
+ const docClient = getDocClient();
282
+ const tableName = getTableName();
283
+ // Fetch the current entity
284
+ const existing = await getEntity({ id, model });
285
+ if (!existing) {
286
+ return false;
287
+ }
288
+ const now = new Date().toISOString();
289
+ // Build updated entity with deletedAt
290
+ const updatedEntity = {
291
+ ...existing,
292
+ deletedAt: now,
293
+ updatedAt: now,
294
+ };
295
+ // Calculate suffix based on combined state (may already be archived)
296
+ const suffix = calculateEntitySuffix(updatedEntity);
297
+ const deletedEntity = indexEntity(updatedEntity, suffix);
298
+ const command = new libDynamodb.PutCommand({
299
+ Item: deletedEntity,
300
+ TableName: tableName,
301
+ });
302
+ await docClient.send(command);
303
+ return true;
304
+ },
305
+ });
287
306
  /**
288
307
  * Archive an entity by setting archivedAt timestamp
289
308
  * Re-indexes with appropriate suffix based on archived/deleted state
290
309
  */
291
- async function archiveEntity(params) {
292
- const { id, model } = params;
293
- const docClient = getDocClient();
294
- const tableName = getTableName();
295
- // Fetch the current entity
296
- const existing = await getEntity({ id, model });
297
- if (!existing) {
298
- return false;
299
- }
300
- const now = new Date().toISOString();
301
- // Build updated entity with archivedAt
302
- const updatedEntity = {
303
- ...existing,
304
- archivedAt: now,
305
- updatedAt: now,
306
- };
307
- // Calculate suffix based on combined state (may already be deleted)
308
- const suffix = calculateEntitySuffix(updatedEntity);
309
- const archivedEntity = indexEntity(updatedEntity, suffix);
310
- const command = new libDynamodb.PutCommand({
311
- Item: archivedEntity,
312
- TableName: tableName,
313
- });
314
- await docClient.send(command);
315
- return true;
316
- }
310
+ const archiveEntity = vocabulary.serviceHandler({
311
+ alias: "archiveEntity",
312
+ description: "Archive an entity (sets archivedAt timestamp)",
313
+ input: {
314
+ id: { type: String, description: "Entity ID (sort key)" },
315
+ model: { type: String, description: "Entity model (partition key)" },
316
+ },
317
+ service: async ({ id, model }) => {
318
+ const docClient = getDocClient();
319
+ const tableName = getTableName();
320
+ // Fetch the current entity
321
+ const existing = await getEntity({ id, model });
322
+ if (!existing) {
323
+ return false;
324
+ }
325
+ const now = new Date().toISOString();
326
+ // Build updated entity with archivedAt
327
+ const updatedEntity = {
328
+ ...existing,
329
+ archivedAt: now,
330
+ updatedAt: now,
331
+ };
332
+ // Calculate suffix based on combined state (may already be deleted)
333
+ const suffix = calculateEntitySuffix(updatedEntity);
334
+ const archivedEntity = indexEntity(updatedEntity, suffix);
335
+ const command = new libDynamodb.PutCommand({
336
+ Item: archivedEntity,
337
+ TableName: tableName,
338
+ });
339
+ await docClient.send(command);
340
+ return true;
341
+ },
342
+ });
317
343
  /**
318
344
  * Hard delete an entity (permanently removes from table)
319
345
  * Use with caution - prefer deleteEntity for soft delete
320
346
  */
321
- async function destroyEntity(params) {
322
- const { id, model } = params;
323
- const docClient = getDocClient();
324
- const tableName = getTableName();
325
- const command = new libDynamodb.DeleteCommand({
326
- Key: { id, model },
327
- TableName: tableName,
328
- });
329
- await docClient.send(command);
330
- return true;
331
- }
347
+ const destroyEntity = vocabulary.serviceHandler({
348
+ alias: "destroyEntity",
349
+ description: "Hard delete an entity (permanently removes from table)",
350
+ input: {
351
+ id: { type: String, description: "Entity ID (sort key)" },
352
+ model: { type: String, description: "Entity model (partition key)" },
353
+ },
354
+ service: async ({ id, model }) => {
355
+ const docClient = getDocClient();
356
+ const tableName = getTableName();
357
+ const command = new libDynamodb.DeleteCommand({
358
+ Key: { id, model },
359
+ TableName: tableName,
360
+ });
361
+ await docClient.send(command);
362
+ return true;
363
+ },
364
+ });
332
365
 
333
366
  /**
334
367
  * Calculate the suffix based on archived/deleted flags
@@ -377,76 +410,126 @@ async function executeQuery(indexName, keyValue, options = {}) {
377
410
  * Query entities by organizational unit (parent hierarchy)
378
411
  * Uses indexOu GSI
379
412
  *
380
- * @param params.archived - Query archived entities instead of active ones
381
- * @param params.deleted - Query deleted entities instead of active ones
382
- * @throws ConfigurationError if both archived and deleted are true
413
+ * Note: This is a regular async function (not serviceHandler) because it accepts
414
+ * complex startKey objects that can't be coerced by vocabulary's type system.
383
415
  */
384
- async function queryByOu(params) {
385
- const { archived, deleted, model, ou, ...options } = params;
416
+ async function queryByOu({ archived = false, ascending = false, deleted = false, limit, model, ou, startKey, }) {
386
417
  const suffix = calculateSuffix({ archived, deleted });
387
418
  const keyValue = buildIndexOu(ou, model) + suffix;
388
- return executeQuery(INDEX_OU, keyValue, options);
419
+ return executeQuery(INDEX_OU, keyValue, {
420
+ ascending,
421
+ limit,
422
+ startKey,
423
+ });
389
424
  }
390
425
  /**
391
426
  * Query a single entity by human-friendly alias
392
427
  * Uses indexAlias GSI
393
- *
394
- * @param params.archived - Query archived entities instead of active ones
395
- * @param params.deleted - Query deleted entities instead of active ones
396
- * @throws ConfigurationError if both archived and deleted are true
397
- * @returns The matching entity or null if not found
398
428
  */
399
- async function queryByAlias(params) {
400
- const { alias, archived, deleted, model, ou } = params;
401
- const suffix = calculateSuffix({ archived, deleted });
402
- const keyValue = buildIndexAlias(ou, model, alias) + suffix;
403
- const result = await executeQuery(INDEX_ALIAS, keyValue, { limit: 1 });
404
- return result.items[0] ?? null;
405
- }
429
+ const queryByAlias = vocabulary.serviceHandler({
430
+ alias: "queryByAlias",
431
+ description: "Query a single entity by human-friendly alias",
432
+ input: {
433
+ alias: { type: String, description: "Human-friendly alias" },
434
+ archived: {
435
+ type: Boolean,
436
+ default: false,
437
+ required: false,
438
+ description: "Query archived entities instead of active ones",
439
+ },
440
+ deleted: {
441
+ type: Boolean,
442
+ default: false,
443
+ required: false,
444
+ description: "Query deleted entities instead of active ones",
445
+ },
446
+ model: { type: String, description: "Entity model name" },
447
+ ou: { type: String, description: "Organizational unit (@ for root)" },
448
+ },
449
+ service: async ({ alias, archived, deleted, model, ou, }) => {
450
+ const aliasStr = alias;
451
+ const archivedBool = archived;
452
+ const deletedBool = deleted;
453
+ const modelStr = model;
454
+ const ouStr = ou;
455
+ const suffix = calculateSuffix({ archived: archivedBool, deleted: deletedBool });
456
+ const keyValue = buildIndexAlias(ouStr, modelStr, aliasStr) + suffix;
457
+ const result = await executeQuery(INDEX_ALIAS, keyValue, {
458
+ limit: 1,
459
+ });
460
+ return result.items[0] ?? null;
461
+ },
462
+ });
406
463
  /**
407
464
  * Query entities by category classification
408
465
  * Uses indexClass GSI
409
466
  *
410
- * @param params.archived - Query archived entities instead of active ones
411
- * @param params.deleted - Query deleted entities instead of active ones
412
- * @throws ConfigurationError if both archived and deleted are true
467
+ * Note: This is a regular async function (not serviceHandler) because it accepts
468
+ * complex startKey objects that can't be coerced by vocabulary's type system.
413
469
  */
414
- async function queryByClass(params) {
415
- const { archived, deleted, model, ou, recordClass, ...options } = params;
470
+ async function queryByClass({ archived = false, ascending = false, deleted = false, limit, model, ou, recordClass, startKey, }) {
416
471
  const suffix = calculateSuffix({ archived, deleted });
417
472
  const keyValue = buildIndexClass(ou, model, recordClass) + suffix;
418
- return executeQuery(INDEX_CLASS, keyValue, options);
473
+ return executeQuery(INDEX_CLASS, keyValue, {
474
+ ascending,
475
+ limit,
476
+ startKey,
477
+ });
419
478
  }
420
479
  /**
421
480
  * Query entities by type classification
422
481
  * Uses indexType GSI
423
482
  *
424
- * @param params.archived - Query archived entities instead of active ones
425
- * @param params.deleted - Query deleted entities instead of active ones
426
- * @throws ConfigurationError if both archived and deleted are true
483
+ * Note: This is a regular async function (not serviceHandler) because it accepts
484
+ * complex startKey objects that can't be coerced by vocabulary's type system.
427
485
  */
428
- async function queryByType(params) {
429
- const { archived, deleted, model, ou, type, ...options } = params;
486
+ async function queryByType({ archived = false, ascending = false, deleted = false, limit, model, ou, startKey, type, }) {
430
487
  const suffix = calculateSuffix({ archived, deleted });
431
488
  const keyValue = buildIndexType(ou, model, type) + suffix;
432
- return executeQuery(INDEX_TYPE, keyValue, options);
489
+ return executeQuery(INDEX_TYPE, keyValue, {
490
+ ascending,
491
+ limit,
492
+ startKey,
493
+ });
433
494
  }
434
495
  /**
435
496
  * Query a single entity by external ID
436
497
  * Uses indexXid GSI
437
- *
438
- * @param params.archived - Query archived entities instead of active ones
439
- * @param params.deleted - Query deleted entities instead of active ones
440
- * @throws ConfigurationError if both archived and deleted are true
441
- * @returns The matching entity or null if not found
442
498
  */
443
- async function queryByXid(params) {
444
- const { archived, deleted, model, ou, xid } = params;
445
- const suffix = calculateSuffix({ archived, deleted });
446
- const keyValue = buildIndexXid(ou, model, xid) + suffix;
447
- const result = await executeQuery(INDEX_XID, keyValue, { limit: 1 });
448
- return result.items[0] ?? null;
449
- }
499
+ const queryByXid = vocabulary.serviceHandler({
500
+ alias: "queryByXid",
501
+ description: "Query a single entity by external ID",
502
+ input: {
503
+ archived: {
504
+ type: Boolean,
505
+ default: false,
506
+ required: false,
507
+ description: "Query archived entities instead of active ones",
508
+ },
509
+ deleted: {
510
+ type: Boolean,
511
+ default: false,
512
+ required: false,
513
+ description: "Query deleted entities instead of active ones",
514
+ },
515
+ model: { type: String, description: "Entity model name" },
516
+ ou: { type: String, description: "Organizational unit (@ for root)" },
517
+ xid: { type: String, description: "External ID" },
518
+ },
519
+ service: async ({ archived, deleted, model, ou, xid, }) => {
520
+ const archivedBool = archived;
521
+ const deletedBool = deleted;
522
+ const modelStr = model;
523
+ const ouStr = ou;
524
+ const xidStr = xid;
525
+ const suffix = calculateSuffix({ archived: archivedBool, deleted: deletedBool });
526
+ const keyValue = buildIndexXid(ouStr, modelStr, xidStr) + suffix;
527
+ const result = await executeQuery(INDEX_XID, keyValue, {
528
+ limit: 1,
529
+ });
530
+ return result.items[0] ?? null;
531
+ },
532
+ });
450
533
 
451
534
  exports.APEX = APEX;
452
535
  exports.ARCHIVED_SUFFIX = ARCHIVED_SUFFIX;