electrodb 2.3.4 → 2.4.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/index.d.ts CHANGED
@@ -116,6 +116,18 @@ export type IsolatedCollectionAttributes<E extends {[name: string]: Entity<any,
116
116
  }[keyof E]
117
117
  }
118
118
 
119
+ type DynamoDBAttributeType =
120
+ | 'S'
121
+ | 'SS'
122
+ | 'N'
123
+ | 'NS'
124
+ | 'B'
125
+ | 'BS'
126
+ | 'BOOL'
127
+ | 'NULL'
128
+ | 'L'
129
+ | 'M'
130
+
119
131
  export interface CollectionWhereOperations {
120
132
  eq: <T, A extends WhereAttributeSymbol<T>>(attr: A, value: T) => string;
121
133
  ne: <T, A extends WhereAttributeSymbol<T>>(attr: A, value: T) => string;
@@ -131,6 +143,12 @@ export interface CollectionWhereOperations {
131
143
  notContains: <T, A extends WhereAttributeSymbol<T>>(attr: A, value: T) => string;
132
144
  value: <T, A extends WhereAttributeSymbol<T>>(attr: A, value: T) => string;
133
145
  name: <T, A extends WhereAttributeSymbol<T>>(attr: A) => string;
146
+ size: <T, A extends WhereAttributeSymbol<T>>(attr: A) => string;
147
+ type: <T, A extends WhereAttributeSymbol<T>>(attr: A, type: DynamoDBAttributeType) => string;
148
+ escape: <T extends string | number | boolean>(value: T) => T extends string ? string
149
+ : T extends number ? number
150
+ : T extends boolean ? boolean
151
+ : never;
134
152
  }
135
153
 
136
154
  export type CollectionWhereCallback<E extends {[name: string]: Entity<any, any, any, any>}, I extends Partial<AllEntityAttributes<E>>> =
@@ -2199,6 +2217,13 @@ export interface WhereOperations<A extends string, F extends string, C extends s
2199
2217
  notContains: <T, A extends WhereAttributeSymbol<T>>(attr: A, value: T) => string;
2200
2218
  value: <T, A extends WhereAttributeSymbol<T>>(attr: A, value: A extends WhereAttributeSymbol<infer V> ? V : never) => A extends WhereAttributeSymbol<infer V> ? V : never;
2201
2219
  name: <A extends WhereAttributeSymbol<any>>(attr: A) => string;
2220
+ size: <T, A extends WhereAttributeSymbol<T>>(attr: A) => number;
2221
+ type: <T, A extends WhereAttributeSymbol<T>>(attr: A, type: DynamoDBAttributeType) => string;
2222
+ escape: <T extends string | number | boolean>(value: T) =>
2223
+ T extends string ? string
2224
+ : T extends number ? number
2225
+ : T extends boolean ? boolean
2226
+ : never;
2202
2227
  }
2203
2228
 
2204
2229
  export interface DataUpdateOperations<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, I extends UpdateData<A,F,C,S>> {
@@ -2347,4 +2372,6 @@ declare function CustomAttributeType<T>(
2347
2372
  : 'any'
2348
2373
  ): T extends string | number | boolean
2349
2374
  ? OpaquePrimitiveTypeName<T>
2350
- : CustomAttributeTypeName<T>;
2375
+ : CustomAttributeTypeName<T>;
2376
+
2377
+ declare function createSchema<A extends string, F extends string, C extends string, S extends Schema<A,F,C>>(schema: S): S
package/index.js CHANGED
@@ -1,12 +1,13 @@
1
1
  const { Entity } = require("./src/entity");
2
2
  const { Service } = require("./src/service");
3
- const { createCustomAttribute, CustomAttributeType } = require('./src/schema');
3
+ const { createCustomAttribute, CustomAttributeType, createSchema } = require('./src/schema');
4
4
  const { ElectroError, ElectroValidationError, ElectroUserValidationError, ElectroAttributeValidationError } = require('./src/errors');
5
5
 
6
6
  module.exports = {
7
7
  Entity,
8
8
  Service,
9
9
  ElectroError,
10
+ createSchema,
10
11
  CustomAttributeType,
11
12
  createCustomAttribute,
12
13
  ElectroValidationError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electrodb",
3
- "version": "2.3.4",
3
+ "version": "2.4.0",
4
4
  "description": "A library to more easily create and interact with multiple entities and heretical relationships in dynamodb",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/src/entity.js CHANGED
@@ -307,7 +307,7 @@ class Entity {
307
307
  return Promise.reject(err);
308
308
  } else {
309
309
  if (err.__isAWSError) {
310
- stackTrace.message = new e.ElectroError(e.ErrorCodes.AWSError, err.message).message;
310
+ stackTrace.message = new e.ElectroError(e.ErrorCodes.AWSError, `Error thrown by DynamoDB client: "${err.message}"`).message;
311
311
  return Promise.reject(stackTrace);
312
312
  } else if (err.isElectroError) {
313
313
  return Promise.reject(err);
package/src/errors.js CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  function getHelpLink(section) {
9
9
  section = section || "unknown-error-5001";
10
- return `https://github.com/tywalch/electrodb#${section}`;
10
+ return `https://electrodb.dev/en/reference/errors/#${section}`;
11
11
  }
12
12
 
13
13
  const ErrorCode = Symbol("error-code");
package/src/operations.js CHANGED
@@ -1,4 +1,4 @@
1
- const {AttributeTypes, ItemOperations, AttributeProxySymbol, BuilderTypes} = require("./types");
1
+ const {AttributeTypes, ItemOperations, AttributeProxySymbol, BuilderTypes, DynamoDBAttributeTypes} = require("./types");
2
2
  const e = require("./errors");
3
3
  const u = require("./util");
4
4
 
@@ -159,8 +159,26 @@ const UpdateOperations = {
159
159
  }
160
160
 
161
161
  const FilterOperations = {
162
+ escape: {
163
+ template: function escape(options, attr) {
164
+ return `${attr}`;
165
+ },
166
+ noAttribute: true,
167
+ },
168
+ size: {
169
+ template: function size(options, attr, name) {
170
+ return `size(${name})`
171
+ },
172
+ strict: false,
173
+ },
174
+ type: {
175
+ template: function attributeType(options, attr, name, value) {
176
+ return `attribute_type(${name}, ${value})`;
177
+ },
178
+ strict: false
179
+ },
162
180
  ne: {
163
- template: function eq(options, attr, name, value) {
181
+ template: function ne(options, attr, name, value) {
164
182
  return `${name} <> ${value}`;
165
183
  },
166
184
  strict: false,
@@ -339,6 +357,9 @@ class AttributeOperationProxy {
339
357
 
340
358
  fromObject(operation, record) {
341
359
  for (let path of Object.keys(record)) {
360
+ if (record[path] === undefined) {
361
+ continue;
362
+ }
342
363
  const value = record[path];
343
364
  const parts = u.parseJSONPath(path);
344
365
  let attribute = this.attributes;
@@ -376,31 +397,61 @@ class AttributeOperationProxy {
376
397
 
377
398
  static buildOperations(builder, operations) {
378
399
  let ops = {};
379
- let seen = new Set();
400
+ let seen = new Map();
380
401
  for (let operation of Object.keys(operations)) {
381
- let {template, canNest} = operations[operation];
402
+ let {template, canNest, noAttribute} = operations[operation];
382
403
  Object.defineProperty(ops, operation, {
383
404
  get: () => {
384
405
  return (property, ...values) => {
385
406
  if (property === undefined) {
386
407
  throw new e.ElectroError(e.ErrorCodes.InvalidWhere, `Invalid/Unknown property passed in where clause passed to operation: '${operation}'`);
387
408
  }
388
- if (property.__is_clause__ === AttributeProxySymbol) {
389
- const {paths, root, target} = property();
409
+ if (property[AttributeProxySymbol]) {
410
+ const {commit, target} = property();
411
+ const fixedValues = values.map((value) => target.applyFixings(value))
412
+ .filter(value => value !== undefined);
413
+ const isFilterBuilder = builder.type === BuilderTypes.filter;
414
+ const takesValueArgument = template.length > 3;
415
+ const isAcceptableValue = fixedValues.every(value => {
416
+ const seenAttributes = seen.get(value);
417
+ if (seenAttributes) {
418
+ return seenAttributes.every(v => target.acceptable(v))
419
+ }
420
+ return target.acceptable(value);
421
+ });
422
+
423
+ const shouldCommit =
424
+ // if it is a filterBuilder than we don't care what they pass because the user needs more freedom here
425
+ isFilterBuilder ||
426
+ // if the operation does not take a value argument then not committing here could cause problems.
427
+ // this should be revisited to make more robust, we could hypothetically store the commit in the
428
+ // "seen" map for when the value is used, but that's a lot of new complexity
429
+ !takesValueArgument ||
430
+ // if the operation takes a value, we should determine if that value is acceptable. For
431
+ // example, in the cases of a "set" we check to see if it is empty, or if the value is
432
+ // undefined, we should not commit. The "fixedValues" length check is because the
433
+ // "fixedValues" array has been filtered for undefined, so no length there indicates an
434
+ // undefined value was passed.
435
+ (takesValueArgument && isAcceptableValue && fixedValues.length > 0);
436
+
437
+ if (!shouldCommit) {
438
+ return '';
439
+ }
440
+
441
+ const paths = commit();
390
442
  const attributeValues = [];
391
443
  let hasNestedValue = false;
392
- for (let value of values) {
393
- value = target.applyFixings(value);
394
- // template.length is to see if function takes value argument
395
- if (template.length > 3) {
396
- if (seen.has(value)) {
397
- attributeValues.push(value);
398
- hasNestedValue = true;
399
- } else {
400
- let attributeValueName = builder.setValue(target.name, value);
401
- builder.setPath(paths.json, {value, name: attributeValueName});
402
- attributeValues.push(attributeValueName);
403
- }
444
+ for (let fixedValue of fixedValues) {
445
+ if (seen.has(fixedValue)) {
446
+ attributeValues.push(fixedValue);
447
+ hasNestedValue = true;
448
+ } else {
449
+ let attributeValueName = builder.setValue(target.name, fixedValue);
450
+ builder.setPath(paths.json, {
451
+ value: fixedValue,
452
+ name: attributeValueName
453
+ });
454
+ attributeValues.push(attributeValueName);
404
455
  }
405
456
  }
406
457
 
@@ -411,8 +462,8 @@ class AttributeOperationProxy {
411
462
  const formatted = template(options, target, paths.expression, ...attributeValues);
412
463
  builder.setImpacted(operation, paths.json, target);
413
464
  if (canNest) {
414
- seen.add(paths.expression);
415
- seen.add(formatted);
465
+ seen.set(paths.expression, attributeValues);
466
+ seen.set(formatted, attributeValues);
416
467
  }
417
468
 
418
469
  if (builder.type === BuilderTypes.update && formatted && typeof formatted.operation === "string" && typeof formatted.expression === "string") {
@@ -420,6 +471,17 @@ class AttributeOperationProxy {
420
471
  return formatted.expression;
421
472
  }
422
473
 
474
+ return formatted;
475
+ } else if (noAttribute) {
476
+ // const {json, expression} = builder.setName({}, property, property);
477
+ let attributeValueName = builder.setValue(property, property);
478
+ builder.setPath(property, {
479
+ value: property,
480
+ name: attributeValueName,
481
+ });
482
+ const formatted = template({}, attributeValueName);
483
+ seen.set(attributeValueName, [property]);
484
+ seen.set(formatted, [property]);
423
485
  return formatted;
424
486
  } else {
425
487
  throw new e.ElectroError(e.ErrorCodes.InvalidWhere, `Invalid Attribute in where clause passed to operation '${operation}'. Use injected attributes only.`);
@@ -434,15 +496,15 @@ class AttributeOperationProxy {
434
496
  static pathProxy(build) {
435
497
  return new Proxy(() => build(), {
436
498
  get: (_, prop, o) => {
437
- if (prop === "__is_clause__") {
438
- return AttributeProxySymbol;
499
+ if (prop === AttributeProxySymbol) {
500
+ return true;
439
501
  } else {
440
502
  return AttributeOperationProxy.pathProxy(() => {
441
- const { paths, root, target, builder } = build();
503
+ const { commit, root, target, builder } = build();
442
504
  const attribute = target.getChild(prop);
443
505
  let field;
444
506
  if (attribute === undefined) {
445
- throw new Error(`Invalid attribute "${prop}" at path "${paths.json}".`);
507
+ throw new Error(`Invalid attribute "${prop}" at path "${target.path}.${prop}"`);
446
508
  } else if (attribute === root && attribute.type === AttributeTypes.any) {
447
509
  // This function is only called if a nested property is called. If this attribute is ultimately the root, don't use the root's field name
448
510
  field = prop;
@@ -454,7 +516,10 @@ class AttributeOperationProxy {
454
516
  root,
455
517
  builder,
456
518
  target: attribute,
457
- paths: builder.setName(paths, prop, field),
519
+ commit: () => {
520
+ const paths = commit();
521
+ return builder.setName(paths, prop, field);
522
+ },
458
523
  }
459
524
  });
460
525
  }
@@ -468,12 +533,11 @@ class AttributeOperationProxy {
468
533
  Object.defineProperty(attr, name, {
469
534
  get: () => {
470
535
  return AttributeOperationProxy.pathProxy(() => {
471
- const paths = builder.setName({}, attribute.name, attribute.field);
472
536
  return {
473
- paths,
474
537
  root: attribute,
475
538
  target: attribute,
476
539
  builder,
540
+ commit: () => builder.setName({}, attribute.name, attribute.field)
477
541
  }
478
542
  });
479
543
  }
package/src/schema.js CHANGED
@@ -238,6 +238,10 @@ class Attribute {
238
238
 
239
239
  _makeApplyFixings({ prefix = "", postfix = "", casing= KeyCasing.none } = {}) {
240
240
  return (value) => {
241
+ if (value === undefined) {
242
+ return;
243
+ }
244
+
241
245
  if ([AttributeTypes.string, AttributeTypes.enum].includes(this.type)) {
242
246
  value = `${prefix}${value}${postfix}`;
243
247
  }
@@ -305,6 +309,10 @@ class Attribute {
305
309
  };
306
310
  }
307
311
 
312
+ acceptable(val) {
313
+ return val !== undefined;
314
+ }
315
+
308
316
  getPathType(type, parentType) {
309
317
  if (parentType === AttributeTypes.list || parentType === AttributeTypes.set) {
310
318
  return PathTypes.item;
@@ -458,7 +466,7 @@ class Attribute {
458
466
  let reason = [];
459
467
  switch (this.type) {
460
468
  case AttributeTypes.enum:
461
- case AttributeTypes.enumSet:
469
+ // case AttributeTypes.enumSet:
462
470
  // isTyped = this.enumArray.every(enumValue => {
463
471
  // const val = Array.isArray(value) ? value : [value];
464
472
  // return val.includes(enumValue);
@@ -876,12 +884,18 @@ class SetAttribute extends Attribute {
876
884
  value = Array.isArray(value)
877
885
  ? Array.from(new Set(value))
878
886
  : value;
879
- return this.client.createSet(value, {validate: true});
887
+ return this.client.createSet(value, { validate: true });
880
888
  } else {
881
889
  return new DynamoDBSet(value, this.items.type);
882
890
  }
883
891
  }
884
892
 
893
+ acceptable(val) {
894
+ return Array.isArray(val)
895
+ ? val.length > 0
896
+ : this.items.acceptable(val);
897
+ }
898
+
885
899
  toDDBSet(value) {
886
900
  const valueType = getValueType(value);
887
901
  let array;
@@ -1474,11 +1488,16 @@ function CustomAttributeType(base) {
1474
1488
  return base;
1475
1489
  }
1476
1490
 
1491
+ function createSchema(schema) {
1492
+ return v.model(schema);
1493
+ }
1494
+
1477
1495
  module.exports = {
1478
1496
  Schema,
1479
1497
  Attribute,
1480
- SetAttribute,
1481
1498
  CastTypes,
1499
+ SetAttribute,
1500
+ createSchema,
1482
1501
  CustomAttributeType,
1483
1502
  createCustomAttribute,
1484
1503
  };
package/src/service.js CHANGED
@@ -155,7 +155,7 @@ class Service {
155
155
  default:
156
156
  /** start beta/v1 condition **/
157
157
  if (modelVersion !== this._modelVersion) {
158
- throw new e.ElectroError(e.ErrorCodes.InvalidJoin, "Invalid instance: Valid instances to join include Models and Entity instances. Additionally, all models must be in the same format (v1 vs beta). Review https://github.com/tywalch/electrodb#version-v1-migration for more detail.");
158
+ throw new e.ElectroError(e.ErrorCodes.InvalidJoin, "Invalid instance: Valid instances to join include Models and Entity instances.");
159
159
  } else if (modelVersion === ModelVersions.beta) {
160
160
  instance = applyBetaModelOverrides(instance, this._modelOverrides);
161
161
  } else {
package/src/types.js CHANGED
@@ -271,6 +271,23 @@ const ResultOrderOption = {
271
271
 
272
272
  const ResultOrderParam = 'ScanIndexForward';
273
273
 
274
+ const DynamoDBAttributeTypes = Object.entries({
275
+ string: 'S',
276
+ stringSet: 'SS',
277
+ number: 'N',
278
+ numberSet: 'NS',
279
+ binary: 'B',
280
+ binarySet: 'BS',
281
+ boolean: 'BOOL',
282
+ null: 'NULL',
283
+ list: 'L',
284
+ map: 'M',
285
+ }).reduce((obj, [name, type]) => {
286
+ obj[name] = type;
287
+ obj[type] = type;
288
+ return obj;
289
+ }, {});
290
+
274
291
  module.exports = {
275
292
  Pager,
276
293
  KeyTypes,
@@ -303,6 +320,7 @@ module.exports = {
303
320
  ElectroInstanceTypes,
304
321
  MethodTypeTranslation,
305
322
  EventSubscriptionTypes,
323
+ DynamoDBAttributeTypes,
306
324
  AttributeMutationMethods,
307
325
  AllPages,
308
326
  ResultOrderOption,