diff --git a/.github/workflows/feature.yml b/.github/workflows/feature.yml
new file mode 100644
index 0000000..429c869
--- /dev/null
+++ b/.github/workflows/feature.yml
@@ -0,0 +1,35 @@
+name: Feature Tests
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ branches: [master]
+
+jobs:
+ featureTests:
+ name: FeatureTests
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [18.x, 20.x, 22.x]
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v2
+ with:
+ node-version: ${{ matrix.node-version }}
+
+ - name: Create dynamo db for tests
+ run: docker compose -f ./dynamo-feature-tests.yml up -d
+
+ - name: Install dependencies
+ run: npm install
+
+ - name: Build
+ run: npm run build
+
+ - name: Run Cucumber Tests
+ run: npm run test:features
diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml
new file mode 100644
index 0000000..26795a3
--- /dev/null
+++ b/.github/workflows/quality.yml
@@ -0,0 +1,31 @@
+name: Quality
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ branches: [master]
+
+jobs:
+ quality:
+ name: Quality
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [22.x]
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v2
+ with:
+ node-version: ${{ matrix.node-version }}
+ - name: Install dependencies
+ run: npm install
+
+ - name: Prettier
+ run: npm run prettier:check
+
+ - name: Eslint
+ run: npm run eslint
diff --git a/.github/workflows/ut.yml b/.github/workflows/ut.yml
index cd3edec..9db5984 100644
--- a/.github/workflows/ut.yml
+++ b/.github/workflows/ut.yml
@@ -7,12 +7,13 @@ on:
branches: [master]
jobs:
- build:
+ tests:
+ name: Tests
runs-on: ubuntu-latest
strategy:
matrix:
- node-version: [18.x, 20.x]
+ node-version: [18.x, 20.x, 22.x]
steps:
- uses: actions/checkout@v2
@@ -24,7 +25,7 @@ jobs:
run: npm install
- name: Run Unit Tests
run: npm test
- - run: npm run coverage
+ - run: npm run test:coverage
- name: Coveralls
uses: coverallsapp/github-action@master
diff --git a/README.md b/README.md
index 782b4e3..e9d4afc 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,20 @@

[](https://coveralls.io/github/monolithst/functional-models-orm-dynamo?branch=master)
-Provides an functional-models-orm datastore provider for AWS Dynamo.
+Provides an functional-models datastore adapter for AWS Dynamo.
+
+# Important Notes - Don't Skip
+
+Dynamodb is great for the one thing that its good at (being a key->value store). But any searching is a bad idea.
+
+While this datastore adapter absolutely 100% works and works well in production (from years of experience), it should never ever
+be used for searching in production. This isn't a limitation of the adapter, it's a limitation of the datastore itself.
+
+Our implementation of `search` is deliberately unoptimized and will indeed pull the entire database across the wire one section at a time.
+
+`save`, `retrieve`, `bulkInsert`, `delete` work great, but `search` should only be used for limited development purposes.
+
+You have been warned.
## AWS SDK 3.0
diff --git a/dynamo-feature-tests.yml b/dynamo-feature-tests.yml
new file mode 100644
index 0000000..fa741db
--- /dev/null
+++ b/dynamo-feature-tests.yml
@@ -0,0 +1,36 @@
+version: '3'
+
+services:
+ dynamodb-local:
+ command: '-jar DynamoDBLocal.jar -sharedDb -dbPath ./data'
+ image: 'amazon/dynamodb-local:latest'
+ user: root
+ ports:
+ - '42514:8000'
+ volumes:
+ - './docker/dynamodb:/home/dynamodblocal/data'
+ working_dir: /home/dynamodblocal
+ healthcheck:
+ test:
+ [
+ 'CMD-SHELL',
+ '[ "$(curl -s -o /dev/null -I -w ''%{http_code}'' http://localhost:8000)" == "400" ]',
+ ]
+ interval: 10s
+ timeout: 10s
+ retries: 10
+ dynamodb-local-setup:
+ container_name: dynamodb-local-setup
+ depends_on:
+ dynamodb-local:
+ condition: service_healthy
+ image: amazon/aws-cli
+ volumes:
+ - './features/schemas:/tmp/dynamo'
+ environment:
+ AWS_ACCESS_KEY_ID: 'FAKEID'
+ AWS_SECRET_ACCESS_KEY: 'FAKEKEY'
+ AWS_REGION: 'us-east-1'
+ entrypoint:
+ - bash
+ command: '-c "for f in /tmp/dynamo/*.json; do aws dynamodb create-table --endpoint-url "http://dynamodb-local:8000" --cli-input-json file://"$${f#./}"; done"'
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 0000000..2328be6
--- /dev/null
+++ b/eslint.config.mjs
@@ -0,0 +1,298 @@
+import { fixupConfigRules, fixupPluginRules } from '@eslint/compat'
+import { FlatCompat } from '@eslint/eslintrc'
+import js from '@eslint/js'
+import typescriptEslint from '@typescript-eslint/eslint-plugin'
+import tsParser from '@typescript-eslint/parser'
+import functional from 'eslint-plugin-functional'
+import _import from 'eslint-plugin-import'
+import parser from 'esprima'
+import globals from 'globals'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+
+const __filename = fileURLToPath(import.meta.url)
+const __dirname = path.dirname(__filename)
+const compat = new FlatCompat({
+ baseDirectory: __dirname,
+ recommendedConfig: js.configs.recommended,
+ allConfig: js.configs.all,
+})
+
+export default [
+ {
+ ignores: [
+ 'buildDocs',
+ 'eslint.config.mjs',
+ 'dist/',
+ 'node_modules/',
+ 'test/',
+ 'coverage/',
+ 'features/',
+ 'stepDefinitions/',
+ 'cucumber.js',
+ ],
+ },
+ ...fixupConfigRules(
+ compat.extends(
+ 'eslint:recommended',
+ 'prettier',
+ 'plugin:import/typescript',
+ 'plugin:import/recommended',
+ 'plugin:@typescript-eslint/recommended'
+ )
+ ),
+ {
+ plugins: {
+ import: fixupPluginRules(_import),
+ functional,
+ '@typescript-eslint': fixupPluginRules(typescriptEslint),
+ },
+ languageOptions: {
+ globals: {
+ ...globals.browser,
+ ...globals.node,
+ ...globals.mocha,
+ },
+ ecmaVersion: 2020,
+ sourceType: 'commonjs',
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+
+ project: ['./tsconfig.json'],
+ },
+ },
+ settings: {
+ 'import/parsers': {
+ '@typescript-eslint/parser': ['.ts', '.tsx'],
+ },
+ 'import/ignore': ['node_modules'],
+ 'import/resolver': {
+ typescript: true,
+
+ moduleDirectory: ['node_modules', 'src/'],
+ node: {
+ extensions: ['.ts', '.tsx'],
+ },
+ },
+ },
+ rules: {
+ '@typescript-eslint/no-explicit-any': 0,
+ '@typescript-eslint/ban-ts-comment': 0,
+ 'no-await-in-loop': ['error'],
+ 'no-console': [
+ 'error',
+ {
+ allow: ['warn', 'error', 'info', 'debug'],
+ },
+ ],
+ 'no-constant-condition': ['error'],
+ 'no-extra-parens': 0,
+ 'no-extra-semi': ['error'],
+ 'no-loss-of-precision': ['error'],
+ 'no-promise-executor-return': ['error'],
+ 'no-template-curly-in-string': ['error'],
+ 'no-useless-backreference': ['error'],
+ 'no-unused-vars': 0,
+ 'require-atomic-updates': ['error'],
+ 'accessor-pairs': ['error'],
+ 'array-callback-return': ['error'],
+ 'block-scoped-var': ['error'],
+ 'class-methods-use-this': ['error'],
+ complexity: ['error'],
+ 'consistent-return': ['error'],
+ curly: ['error'],
+ 'default-case': ['error'],
+ 'default-case-last': ['error'],
+ 'default-param-last': 0,
+ 'dot-location': ['error', 'property'],
+ 'dot-notation': ['error'],
+ eqeqeq: ['error'],
+ 'grouped-accessor-pairs': ['error'],
+ 'guard-for-in': ['error'],
+ 'max-classes-per-file': ['error'],
+ 'no-alert': ['error'],
+ 'no-caller': ['error'],
+ 'no-constructor-return': ['error'],
+ 'no-div-regex': ['error'],
+ 'no-else-return': ['error'],
+ 'no-empty-function': ['error'],
+ 'no-eq-null': ['error'],
+ 'no-eval': ['error'],
+ 'no-extend-native': ['error'],
+ 'no-extra-bind': ['error'],
+ 'no-extra-label': ['error'],
+ 'no-floating-decimal': ['error'],
+ 'no-implicit-coercion': ['error'],
+ 'no-implicit-globals': ['error'],
+ 'no-implied-eval': ['error'],
+ 'no-invalid-this': ['error'],
+ 'no-iterator': ['error'],
+ 'no-labels': ['error'],
+ 'no-lone-blocks': ['error'],
+ 'no-loop-func': ['error'],
+ 'no-magic-numbers': [
+ 'error',
+ {
+ ignore: [1, -1, 0, 2],
+ },
+ ],
+ 'no-multi-spaces': ['error'],
+ 'no-multi-str': ['error'],
+ 'no-new': ['error'],
+ 'no-new-func': ['error'],
+ 'no-new-wrappers': ['error'],
+ 'no-octal-escape': ['error'],
+ 'no-proto': ['error'],
+ 'no-restricted-properties': ['error'],
+ 'no-return-assign': ['error'],
+ 'no-return-await': ['error'],
+ 'no-script-url': ['error'],
+ 'no-self-compare': ['error'],
+ 'no-sequences': ['error'],
+ 'no-throw-literal': ['error'],
+ 'no-unmodified-loop-condition': ['error'],
+ 'no-unused-expressions': ['error'],
+ 'no-useless-call': ['error'],
+ 'no-useless-concat': ['error'],
+ 'no-void': ['error'],
+ 'no-warning-comments': ['warn'],
+ 'prefer-named-capture-group': ['error'],
+ 'prefer-promise-reject-errors': 0,
+ 'prefer-regex-literals': ['error'],
+ radix: ['error'],
+ 'require-unicode-regexp': ['error'],
+ 'vars-on-top': ['error'],
+ 'wrap-iife': ['error'],
+ yoda: ['error'],
+ 'arrow-body-style': 0,
+ 'eol-last': ['error', 'always'],
+ 'comma-dangle': 0,
+ 'linebreak-style': 0,
+ 'no-underscore-dangle': 0,
+ 'no-unused-labels': 0,
+ 'object-shorthand': 0,
+ 'prefer-rest-params': 0,
+ semi: ['error', 'never'],
+ 'functional/immutable-data': [
+ 'error',
+ {
+ ignoreAccessorPattern: 'module.exports*',
+ },
+ ],
+ 'functional/no-let': ['error'],
+ 'functional/prefer-property-signatures': ['error'],
+ 'functional/prefer-readonly-type': 0,
+ 'functional/prefer-immutable-types': 0,
+ 'functional/prefer-tacit': ['error'],
+ 'functional/no-classes': ['error'],
+ 'functional/no-mixed-type': 0,
+ 'functional/no-this-expressions': ['error'],
+ 'functional/no-conditional-statements': 0,
+ 'functional/no-expression-statement': 0,
+ 'functional/no-loop-statements': ['error'],
+ 'functional/no-return-void': 0,
+ 'functional/no-promise-reject': 0,
+ 'functional/no-throw-statement': 0,
+ 'functional/no-try-statements': ['error'],
+ 'functional/readonly-type': ['error'],
+ 'functional/functional-parameters': 0,
+ 'import/no-unresolved': ['error'],
+ 'import/named': ['error'],
+ 'import/default': ['error'],
+ 'import/namespace': ['error'],
+ 'import/no-restricted-paths': ['error'],
+ 'import/no-absolute-path': ['error'],
+ 'import/no-dynamic-require': ['error'],
+ 'import/no-internal-modules': 0,
+ 'import/no-webpack-loader-syntax': ['error'],
+ 'import/no-self-import': ['error'],
+ 'import/no-cycle': ['error'],
+ 'import/no-useless-path-segments': ['error'],
+ 'import/no-relative-parent-imports': 0,
+ 'import/export': ['error'],
+ 'import/no-named-as-default': ['error'],
+ 'import/no-named-as-default-member': ['error'],
+ 'import/no-deprecated': ['error'],
+ 'import/no-extraneous-dependencies': ['error'],
+ 'import/no-mutable-exports': ['error'],
+ 'import/no-unused-modules': ['error'],
+ 'import/unambiguous': ['error'],
+ 'import/no-commonjs': 0,
+ 'import/no-amd': ['error'],
+ 'import/no-nodejs-modules': 0,
+ 'import/first': ['error'],
+ 'import/exports-last': 0,
+ 'import/no-duplicates': ['error'],
+ 'import/no-namespace': 0,
+ 'import/extensions': ['error'],
+ 'import/order': ['error'],
+ 'import/newline-after-import': ['error'],
+ 'import/prefer-default-export': 0,
+ 'import/no-unassigned-import': ['error'],
+ 'import/no-named-default': 0,
+ 'import/no-named-export': 0,
+ 'import/no-anonymous-default-export': 0,
+ 'import/group-exports': 0,
+ 'import/dynamic-import-chuckname': 0,
+ },
+ },
+ {
+ files: ['./src/*.js', './*.js'],
+ languageOptions: {
+ parser: parser,
+ ecmaVersion: 2020,
+ sourceType: 'script',
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ project: [],
+ },
+ },
+ },
+ {
+ files: ['./src/*.ts', './src/**/*.ts'],
+ languageOptions: {
+ parser: tsParser,
+ ecmaVersion: 2020,
+ sourceType: 'script',
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ project: ['./tsconfig.json'],
+ },
+ },
+ rules: {
+ '@typescript-eslint/no-unused-vars': [
+ 'error',
+ {
+ vars: 'all',
+ argsIgnorePattern: '(_+)|(action)',
+ args: 'after-used',
+ ignoreRestSiblings: false,
+ },
+ ],
+ },
+ },
+ {
+ files: ['./src/orval/**/*.ts'],
+ languageOptions: {
+ parser: tsParser,
+ ecmaVersion: 2020,
+ sourceType: 'script',
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ project: ['./tsconfig.json'],
+ },
+ },
+ rules: {
+ 'import/max-dependencies': 0,
+ 'import/order': 0,
+ },
+ },
+]
diff --git a/features/schemas/model1.json b/features/schemas/model1.json
new file mode 100644
index 0000000..91423b5
--- /dev/null
+++ b/features/schemas/model1.json
@@ -0,0 +1,19 @@
+{
+ "TableName": "functional-models-orm-dynamo-model-1",
+ "KeySchema": [
+ {
+ "AttributeName": "id",
+ "KeyType": "HASH"
+ }
+ ],
+ "AttributeDefinitions": [
+ {
+ "AttributeName": "id",
+ "AttributeType": "S"
+ }
+ ],
+ "ProvisionedThroughput": {
+ "ReadCapacityUnits": 5,
+ "WriteCapacityUnits": 5
+ }
+}
diff --git a/features/schemas/model2.json b/features/schemas/model2.json
new file mode 100644
index 0000000..bec4001
--- /dev/null
+++ b/features/schemas/model2.json
@@ -0,0 +1,19 @@
+{
+ "TableName": "functional-models-orm-dynamo-model-2",
+ "KeySchema": [
+ {
+ "AttributeName": "id",
+ "KeyType": "HASH"
+ }
+ ],
+ "AttributeDefinitions": [
+ {
+ "AttributeName": "id",
+ "AttributeType": "S"
+ }
+ ],
+ "ProvisionedThroughput": {
+ "ReadCapacityUnits": 5,
+ "WriteCapacityUnits": 5
+ }
+}
diff --git a/features/schemas/model3.json b/features/schemas/model3.json
new file mode 100644
index 0000000..d29c963
--- /dev/null
+++ b/features/schemas/model3.json
@@ -0,0 +1,19 @@
+{
+ "TableName": "functional-models-orm-dynamo-model-3",
+ "KeySchema": [
+ {
+ "AttributeName": "id",
+ "KeyType": "HASH"
+ }
+ ],
+ "AttributeDefinitions": [
+ {
+ "AttributeName": "id",
+ "AttributeType": "S"
+ }
+ ],
+ "ProvisionedThroughput": {
+ "ReadCapacityUnits": 5,
+ "WriteCapacityUnits": 5
+ }
+}
diff --git a/features/stepDefinitions/steps.mjs b/features/step_definitions/steps.ts
similarity index 64%
rename from features/stepDefinitions/steps.mjs
rename to features/step_definitions/steps.ts
index dc2192a..bcfdbcb 100644
--- a/features/stepDefinitions/steps.mjs
+++ b/features/step_definitions/steps.ts
@@ -1,36 +1,69 @@
import { randomUUID } from 'crypto'
import { assert } from 'chai'
import { Before, After, Given, When, Then } from '@cucumber/cucumber'
-import functionalModels, { ObjectProperty } from 'functional-models'
-import { ormQuery, orm } from 'functional-models-orm'
-import createDatastoreProvider from '../../dist/datastoreProvider.js'
+import {
+ ObjectProperty,
+ TextProperty,
+ ModelType,
+ PrimaryKeyUuidProperty,
+ createOrm,
+ queryBuilder,
+} from 'functional-models'
+import * as dynamoDatastoreAdapter from '../../src/datastoreAdapter'
import * as dynamo from '@aws-sdk/client-dynamodb'
import * as libDynamo from '@aws-sdk/lib-dynamodb'
-const { TextProperty, Model, UniqueId } = functionalModels
-
-const createDynamoDatastoreProvider = context => {
- if (!context.parameters.testTable) {
- throw new Error(
- `Must include a testing table that exists in the world parameters.`
- )
- }
- if (!context.parameters.awsRegion) {
- throw new Error(`Must include awsRegion in the world parameters.`)
- }
- context.table = context.parameters.testTable
+const createDynamoDatastoreProvider = async () => {
const dynamoDbClient = new dynamo.DynamoDBClient({
- region: context.parameters.awsRegion,
+ region: 'http://127.0.0.1:42514',
+ credentials: {
+ accessKeyId: 'x',
+ secretAccessKey: 'x',
+ },
+ endpoint: {
+ hostname: 'localhost',
+ port: 42514,
+ path: '',
+ protocol: 'http:',
+ },
})
- return createDatastoreProvider.default({
+ /*
+ const tableNames = ['model-1', 'model-2', 'model-3'].map(
+ name => `functional-models-orm-dynamo-${name}`
+ )
+ const commands = tableNames.map(name => {
+ return new dynamo.CreateTableCommand({
+ TableName: name,
+ AttributeDefinitions: [
+ {
+ AttributeName: 'id',
+ AttributeType: 'S',
+ },
+ ],
+ KeySchema: [
+ {
+ KeyType: 'HASH',
+ AttributeName: 'id',
+ },
+ ],
+ })
+ })
+ await Promise.all(
+ commands.map(x =>
+ dynamoDbClient.send(x).catch(e => {
+ console.log(e)
+ })
+ )
+ )
+
+ */
+
+ return dynamoDatastoreAdapter.create({
aws3: {
...dynamo,
...libDynamo,
dynamoDbClient,
},
- getTableNameForModel: () => {
- return `${context.table}`
- },
})
}
@@ -40,26 +73,32 @@ const DATASTORES = {
const MODELS = {
Model1: [
- 'Model1',
{
+ pluralName: 'Model1',
+ namespace: 'functional-models-orm-dynamo',
properties: {
+ id: TextProperty(),
name: TextProperty(),
},
},
],
Model2: [
- 'Model2',
{
+ pluralName: 'Model2',
+ namespace: 'functional-models-orm-dynamo',
properties: {
+ id: TextProperty(),
name: TextProperty({ required: false }),
key: TextProperty({ required: false }),
},
},
],
Model3: [
- 'Model3',
{
+ pluralName: 'Model3',
+ namespace: 'functional-models-orm-dynamo',
properties: {
+ id: TextProperty(),
name: TextProperty({ required: true }),
obj: ObjectProperty({ required: true }),
},
@@ -72,31 +111,31 @@ const MODEL_DATA = {
id: 'test-id',
name: 'test-name',
}),
- ModelData2: {
+ ModelData2: () => ({
id: 'test-id',
name: 'test-name',
key: undefined,
- },
- StoredModelData2: {
+ }),
+ StoredModelData2: () => ({
id: 'test-id',
name: 'test-name',
key: null,
- },
- ModelData3: {
+ }),
+ ModelData3: () => ({
id: 'test-id',
name: 'test-name',
obj: {
nested: 'value',
nested2: undefined,
},
- },
- StoredModelData3: {
+ }),
+ StoredModelData3: () => ({
id: 'test-id',
name: 'test-name',
obj: {
nested: 'value',
},
- },
+ }),
BulkModelData1: () => ({
id: randomUUID(),
name: 'test-me',
@@ -115,20 +154,15 @@ const MODEL_DATA = {
}
const QUERIES = {
- SearchQuery1: ormQuery
- .ormQueryBuilder()
- .property('name', 'test-name')
- .take(1)
- .compile(),
- BulkSearchQuery1: ormQuery
- .ormQueryBuilder()
+ SearchQuery1: queryBuilder().property('name', 'test-name').take(1).compile(),
+ BulkSearchQuery1: queryBuilder()
//.property('name', 'test-me')
.compile(),
}
-const _emptyDatastoreProvider = async (model, datastoreProvider) => {
- await datastoreProvider
- .search(model, ormQuery.ormQueryBuilder().compile())
+const _emptyDatastoreProvider = async (model, datastoreAdapter) => {
+ await datastoreAdapter
+ .search(model, queryBuilder().compile())
.then(async obj =>
Promise.all(
obj.instances.map(x => {
@@ -138,18 +172,18 @@ const _emptyDatastoreProvider = async (model, datastoreProvider) => {
)
}
-Given('orm using the {word}', function (store) {
- store = DATASTORES[store](this)
+Given('orm using the {word}', async function (store) {
+ store = await DATASTORES[store](this)
if (!store) {
throw new Error(`${store} did not result in a datastore.`)
}
- this.BaseModel = orm({ datastoreProvider: store }).BaseModel
- this.datastoreProvider = store
+ this.Model = createOrm({ datastoreAdapter: store }).Model
+ this.datastoreAdapter = store
})
Given('the datastore is emptied of models', function () {
- return _emptyDatastoreProvider(this.model, this.datastoreProvider)
+ return _emptyDatastoreProvider(this.model, this.datastoreAdapter)
})
Given('the orm is used to create {word}', function (modelType) {
@@ -157,15 +191,7 @@ Given('the orm is used to create {word}', function (modelType) {
if (!model) {
throw new Error(`${modelType} did not result in a model.`)
}
- this.model = this.BaseModel(...model)
-})
-
-Given('the ormQueryBuilder is used to make {word}', function (queryKey) {
- if (!QUERY_BUILDER_FUNCS[queryKey]) {
- throw new Error(`${queryKey} did not result in a query`)
- }
-
- this.query = QUERY_BUILDER_FUNCS[queryKey]()
+ this.model = this.Model(...model)
})
When('instances of the model are created with {word}', function (dataKey) {
@@ -191,7 +217,13 @@ When('save is called on the instances', function () {
})
When('save is called on the model', function () {
- return this.modelInstance.save().then(x => (this.saveResult = x))
+ return this.modelInstance
+ .save()
+ .catch(e => {
+ console.log(e)
+ throw e
+ })
+ .then(x => (this.saveResult = x))
})
When('delete is called on the model', function () {
@@ -201,20 +233,25 @@ When('delete is called on the model', function () {
When("the datastore's retrieve is called with values", function (table) {
const rows = table.rowsHash()
const id = rows.id
- return this.datastoreProvider.retrieve(this.model, id).then(obj => {
+ return this.datastoreAdapter.retrieve(this.model, id).then(obj => {
this.result = obj
})
})
-When("the datastore's delete is called with modelInstance", function () {
- return this.datastoreProvider.delete(this.modelInstance).then(obj => {
- this.result = obj
- })
+When("the datastore's delete is called with modelInstance", async function () {
+ return this.datastoreAdapter
+ .delete(
+ this.modelInstance.getModel(),
+ await this.modelInstance.getPrimaryKey()
+ )
+ .then(obj => {
+ this.result = obj
+ })
})
When("the datastore's search is called with {word}", function (key) {
const query = QUERIES[key]
- return this.datastoreProvider.search(this.model, query).then(async obj => {
+ return this.datastoreAdapter.search(this.model, query).then(async obj => {
this.result = obj
})
})
@@ -224,12 +261,10 @@ When('search is called on the Model using the query', function () {
})
Then('the result matches {word}', function (dataKey) {
- const data = MODEL_DATA[dataKey]
+ const data = MODEL_DATA[dataKey]()
try {
assert.deepEqual(this.result, data)
} catch (e) {
- console.log(this.result)
- console.log(data)
throw e
}
})
diff --git a/package.json b/package.json
index 980d564..2fd161b 100644
--- a/package.json
+++ b/package.json
@@ -1,19 +1,20 @@
{
"name": "functional-models-orm-dynamo",
- "version": "2.1.7",
- "description": "An implmentation of functional-models-orm for dynamodb.",
+ "version": "3.0.0",
+ "description": "An implmentation of functional-models for dynamodb.",
"main": "index.js",
"types": "index.d.ts",
"scripts": {
+ "build": "rm -Rf ./dist && tsc && cp package.json ./dist && cp README.md ./dist",
+ "build:watch": "nodemon -e '*' --watch ./src --exec npm run build",
+ "commit": "cz",
+ "dist": "npm run build && cd dist && npm publish",
"eslint": "eslint .",
"prettier": "prettier --write .",
- "test": "export TS_NODE_TRANSPILE_ONLY=true && export TS_NODE_PROJECT='./tsconfig.test.json' && mocha -r ts-node/register 'test/**/*.test.ts'",
+ "prettier:check": "prettier -c .",
+ "test": "mocha -r ts-node/register test/**/*.test.ts",
"test:coverage": "nyc npm run test",
- "feature-tests": "./node_modules/.bin/cucumber-js",
- "coverage": "nyc --all --reporter=lcov npm test",
- "build": "tsc -p ./tsconfig.build.json && cp package.json ./dist && cp README.md ./dist",
- "watch": "tsc -w -p ./tsconfig.build.json -noEmit",
- "dist": "npm run build && cd dist && npm publish"
+ "test:features": "TS_NODE_PROJECT=tsconfig.cucumber.json ./node_modules/.bin/cucumber-js --require ./features/step_definitions/steps.ts --require-module ts-node/register"
},
"publishConfig": {
"registry": "https://registry.npmjs.org"
@@ -22,10 +23,17 @@
"type": "git",
"url": "git+https://github.com/monolithst/functional-models-orm-dynamo.git"
},
+ "config": {
+ "commitizen": {
+ "path": "./node_modules/cz-conventional-changelog"
+ }
+ },
"keywords": [
"orm",
"dynamodb",
"nodejs",
+ "functional-models",
+ "aws",
"functional"
],
"author": "Mike Cornwell",
@@ -54,34 +62,40 @@
"dependencies": {
"@aws-sdk/client-dynamodb": "^3.623.0",
"@aws-sdk/lib-dynamodb": "^3.556.0",
- "functional-models": "^2.1.14",
- "functional-models-orm": "^2.1.12",
+ "functional-models": "^3.0.12",
+ "functional-models-orm-memory": "^3.0.0",
"lodash": "^4.17.21"
},
"devDependencies": {
- "@cucumber/cucumber": "^9.6.0",
+ "@cucumber/cucumber": "^11.2.0",
+ "@eslint/compat": "^1.2.0",
+ "@eslint/eslintrc": "^3.1.0",
+ "@eslint/js": "^9.12.0",
"@istanbuljs/nyc-config-typescript": "^1.0.2",
- "@types/chai": "^4.3.12",
- "@types/lodash": "^4.17.0",
+ "@types/chai": "^4.3.16",
+ "@types/chai-as-promised": "^7.1.8",
+ "@types/lodash": "^4.17.1",
"@types/mocha": "^10.0.6",
- "@types/node": "^20.11.27",
- "@types/proxyquire": "^1.3.31",
"@types/sinon": "^17.0.3",
- "@typescript-eslint/eslint-plugin": "^7.2.0",
- "@typescript-eslint/parser": "^7.2.0",
- "babel-eslint": "^10.1.0",
- "chai": "^4.2.0",
- "eslint": "^8.57.0",
+ "@typescript-eslint/eslint-plugin": "^8.20.0",
+ "@typescript-eslint/parser": "^8.20.0",
+ "chai": "^4.3.0",
+ "chai-as-promised": "^7.1.2",
+ "cz-conventional-changelog": "^3.3.0",
+ "eslint": "^9.18.0",
"eslint-config-prettier": "^9.1.0",
- "eslint-plugin-functional": "^6.1.1",
- "eslint-plugin-import": "^2.29.1",
- "mocha": "^10.8.2",
+ "eslint-import-resolver-typescript": "^3.6.3",
+ "eslint-plugin-functional": "~7.1.0",
+ "eslint-plugin-import": "^2.31.0",
+ "mocha": "^10.4.0",
+ "nodemon": "^3.1.9",
"nyc": "^15.1.0",
- "prettier": "^3.2.5",
+ "prettier-plugin-organize-imports": "^3.2.4",
"proxyquire": "^2.1.3",
- "sinon": "^17.0.1",
- "ts-mocha": "^10.0.0",
+ "sinon": "^11.1.2",
+ "source-map-support": "^0.5.21",
"ts-node": "^10.9.2",
+ "tsx": "^4.19.2",
"typescript": "^5.7.2"
},
"homepage": "https://github.com/monolithst/functional-models-orm-dynamo#readme"
diff --git a/src/datastoreProvider.ts b/src/datastoreAdapter.ts
similarity index 53%
rename from src/datastoreProvider.ts
rename to src/datastoreAdapter.ts
index c527eb4..4840de4 100644
--- a/src/datastoreProvider.ts
+++ b/src/datastoreAdapter.ts
@@ -1,87 +1,89 @@
import { get, merge } from 'lodash'
import {
- Model,
- FunctionalModel,
+ ModelType,
+ DataDescription,
ModelInstance,
PrimaryKeyType,
-} from 'functional-models/interfaces'
-import { OrmQuery, DatastoreProvider } from 'functional-models-orm/interfaces'
+ DatastoreAdapter,
+ OrmSearch,
+} from 'functional-models'
+import { filterResults } from 'functional-models-orm-memory'
import {
+ fromDynamo,
getTableNameForModel as defaultTableModelName,
splitArrayIntoArraysOfMaxSize,
+ buildScanQuery,
} from './lib'
-import queryBuilder from './queryBuilder'
import { SCAN_RETURN_THRESHOLD } from './constants'
const MAX_BATCH_SIZE = 25
+type WithRequired = T & { [P in K]-?: T[P] }
-type DatastoreProviderInputs = {
- readonly aws3: Aws3Client
- readonly getTableNameForModel?: (
- m: Model
- ) => string
- readonly createUniqueId?: ((s: any) => string) | undefined
-}
+type DatastoreAdapterInputs = Readonly<{
+ aws3: Aws3Client
+ getTableNameForModel?: (m: ModelType) => string
+}>
-type Aws3Client = {
- readonly dynamoDbClient: any
- readonly DynamoDBDocumentClient: any
- readonly PutCommand: any
- readonly GetCommand: any
- readonly DeleteCommand: any
- readonly ScanCommand: any
- readonly BatchWriteCommand: any
-}
+type Aws3Client = Readonly<{
+ dynamoDbClient: any
+ DynamoDBDocumentClient: any
+ PutCommand: any
+ GetCommand: any
+ DeleteCommand: any
+ ScanCommand: any
+ BatchWriteCommand: any
+}>
-const dynamoDatastoreProvider = ({
+const create = ({
aws3,
getTableNameForModel = defaultTableModelName,
- createUniqueId = undefined,
-}: DatastoreProviderInputs): DatastoreProvider => {
+}: DatastoreAdapterInputs): WithRequired => {
if (!getTableNameForModel) {
throw new Error(`Must include ${getTableNameForModel}`)
}
- const _doSearchUntilThresholdOrNoLastEvaluatedKey = (
+ const _doSearchUntilThresholdOrNoLastEvaluatedKey = async <
+ T extends DataDescription,
+ >(
dynamo: any,
tableName: string,
- ormQuery: OrmQuery,
- oldInstancesFound = []
+ ormQuery: OrmSearch,
+ oldInstancesFound: T[] = []
) => {
- const query = queryBuilder({ createUniqueId })(tableName, ormQuery)
+ const query = buildScanQuery(tableName, ormQuery.page)
const command = new aws3.ScanCommand(query)
- return dynamo.send(command).then((data: any) => {
- const instances = data.Items.map((item: object) => {
- return Object.entries(item).reduce((acc, [key, obj]) => {
- return merge(acc, { [key]: obj })
- }, {})
- }).concat(oldInstancesFound)
+ const data = await dynamo.send(command)
+ const unfiltered: T[] = data.Items.map(fromDynamo)
+
+ const filtered = filterResults(ormQuery, unfiltered).concat(
+ oldInstancesFound
+ )
- const usingTake = ormQuery.take && ormQuery.take > 0
- const take = usingTake ? ormQuery.take : SCAN_RETURN_THRESHOLD
- const lastEvaluatedKey = get(data, 'LastEvaluatedKey', null)
- /*
- We want to keep scanning until we've met our threshold OR
- there is no more keys to evaluate OR
- we have a "take" and we've hit our max.
- */
- const stopForThreshold = instances.length > take
- const stopForNoMore = !lastEvaluatedKey
- if (stopForThreshold || stopForNoMore) {
- return {
- instances: instances.slice(0, take),
- page: usingTake ? null : lastEvaluatedKey,
- }
+ const usingTake = ormQuery.take && ormQuery.take > 0
+ const take = usingTake ? ormQuery.take : SCAN_RETURN_THRESHOLD
+ const lastEvaluatedKey = get(data, 'LastEvaluatedKey', null)
+ /*
+ We want to keep scanning until we've met our threshold OR
+ there is no more keys to evaluate OR
+ we have a "take" and we've hit our max.
+ */
+ // @ts-ignore
+ const stopForThreshold = filtered.length > take
+ const stopForNoMore = !lastEvaluatedKey
+ if (stopForThreshold || stopForNoMore) {
+ return {
+ instances: filtered.slice(0, take),
+ page: usingTake ? null : lastEvaluatedKey,
}
- const newQuery = merge(ormQuery, {
- page: lastEvaluatedKey,
- })
- return _doSearchUntilThresholdOrNoLastEvaluatedKey(
- dynamo,
- tableName,
- newQuery,
- instances
- )
+ }
+ const newQuery = merge(ormQuery, {
+ page: lastEvaluatedKey,
})
+ return _doSearchUntilThresholdOrNoLastEvaluatedKey(
+ dynamo,
+ tableName,
+ newQuery,
+ filtered
+ )
}
const _getDocClient = () => {
@@ -95,9 +97,9 @@ const dynamoDatastoreProvider = ({
)
}
- const search = (
- model: Model,
- ormQuery: OrmQuery
+ const search = (
+ model: ModelType,
+ ormQuery: OrmSearch
) => {
return Promise.resolve().then(async () => {
const tableName = getTableNameForModel(model)
@@ -110,14 +112,14 @@ const dynamoDatastoreProvider = ({
})
}
- const retrieve = (
- model: Model,
+ const retrieve = (
+ model: ModelType,
id: PrimaryKeyType
) => {
return Promise.resolve().then(() => {
const tableName = getTableNameForModel(model)
const docClient = _getDocClient()
- const primaryKeyName = model.getPrimaryKeyName()
+ const primaryKeyName = model.getModelDefinition().primaryKeyName
const command = new aws3.GetCommand({
TableName: tableName,
Key: { [primaryKeyName]: `${id}` },
@@ -126,13 +128,15 @@ const dynamoDatastoreProvider = ({
})
}
- const save = async >(
- instance: ModelInstance
+ const save = async (
+ instance: ModelInstance
) => {
return Promise.resolve().then(async () => {
const tableName = getTableNameForModel(instance.getModel())
const docClient = _getDocClient()
- const primaryKeyName = instance.getModel().getPrimaryKeyName()
+ const primaryKeyName = instance
+ .getModel()
+ .getModelDefinition().primaryKeyName
const data = await instance.toObj()
const key = `${(data as any)[primaryKeyName]}`
const keyObj = { [primaryKeyName]: key }
@@ -145,15 +149,15 @@ const dynamoDatastoreProvider = ({
})
}
- const deleteObj = >(
- instance: ModelInstance
+ const deleteObj = (
+ model: ModelType,
+ primaryKey: PrimaryKeyType
) => {
return Promise.resolve().then(async () => {
- const tableName = getTableNameForModel(instance.getModel())
+ const tableName = getTableNameForModel(model)
const docClient = _getDocClient()
- const primaryKeyName = instance.getModel().getPrimaryKeyName()
- const id = await instance.getPrimaryKey()
- const keyObj = { [primaryKeyName]: `${id}` }
+ const primaryKeyName = model.getModelDefinition().primaryKeyName
+ const keyObj = { [primaryKeyName]: `${primaryKey}` }
const command = new aws3.DeleteCommand({
TableName: tableName,
Key: keyObj,
@@ -162,8 +166,8 @@ const dynamoDatastoreProvider = ({
})
}
- const bulkInsert = (
- model: Model,
+ const bulkInsert = (
+ model: ModelType,
instances: readonly ModelInstance[]
): Promise => {
return Promise.resolve().then(async () => {
@@ -209,4 +213,4 @@ const dynamoDatastoreProvider = ({
}
}
-export default dynamoDatastoreProvider
+export { create }
diff --git a/src/index.ts b/src/index.ts
index affba1a..d022e5f 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,3 +1,3 @@
-import datastoreProvider from './datastoreProvider'
+import * as datastoreAdapter from './datastoreAdapter'
-export { datastoreProvider }
+export { datastoreAdapter }
diff --git a/src/lib.ts b/src/lib.ts
index c8548c8..4e5f862 100644
--- a/src/lib.ts
+++ b/src/lib.ts
@@ -1,7 +1,11 @@
-import { FunctionalModel, Model } from 'functional-models/interfaces'
+import kebabCase from 'lodash/kebabCase'
+import { DataDescription, ModelType, ToObjectResult } from 'functional-models'
+import { merge } from 'lodash'
-const getTableNameForModel = (model: Model) => {
- return model.getName().toLowerCase().replace('_', '-').replace(' ', '-')
+const getTableNameForModel = (
+ model: ModelType
+) => {
+ return kebabCase(model.getName()).toLowerCase()
}
const _recursiveSplitArray = (
@@ -34,4 +38,33 @@ const splitArrayIntoArraysOfMaxSize = (
return _recursiveSplitArray([], array, maxSize)
}
-export { getTableNameForModel, splitArrayIntoArraysOfMaxSize }
+const fromDynamo = (
+ obj: object
+): ToObjectResult => {
+ return Object.entries(obj).reduce((acc, [key, obj]) => {
+ return merge(acc, { [key]: obj })
+ }, {}) as ToObjectResult
+}
+
+const buildScanQuery = (table: string, page?: string) => {
+ const startKey = page
+ ? {
+ ExclusiveStartKey: page,
+ FilterExpression: undefined,
+ ExpressionAttributeNames: undefined,
+ ExpressionAttributeValues: undefined,
+ }
+ : {}
+
+ return {
+ ...startKey,
+ TableName: table,
+ }
+}
+
+export {
+ getTableNameForModel,
+ splitArrayIntoArraysOfMaxSize,
+ fromDynamo,
+ buildScanQuery,
+}
diff --git a/src/queryBuilder.ts b/src/queryBuilder.ts
deleted file mode 100644
index d690271..0000000
--- a/src/queryBuilder.ts
+++ /dev/null
@@ -1,195 +0,0 @@
-import { randomUUID } from 'crypto'
-import { merge, get, identity } from 'lodash'
-import {
- OrmQuery,
- OrmQueryStatement,
- PropertyStatement,
-} from 'functional-models-orm/interfaces'
-import { ORMType } from 'functional-models-orm/constants'
-
-type DynamoOrmStatement = OrmQueryStatement & { myUniqueId: string }
-type DynamoPropertyStatement = PropertyStatement & { myUniqueId: string }
-
-const COMPARISONS = {
- EQ: 'EQ',
-}
-
-const DATA_TYPES = {
- STRING: 'S',
- NUMBER: 'N',
- BOOLEAN: 'BOOL',
- NULL: 'NULL',
- ARRAY: 'L',
- OBJECT: 'M',
-}
-
-const ORM_TYPE_TO_DYNAMO_TYPE: { [key in ORMType]: string } = {
- string: DATA_TYPES.STRING,
- number: DATA_TYPES.NUMBER,
- date: DATA_TYPES.STRING,
- object: DATA_TYPES.OBJECT,
- boolean: DATA_TYPES.BOOLEAN,
-}
-
-const _idGenerator = (existingFunc: any): ((s: any) => string) => {
- if (!existingFunc) {
- return (_: string) => randomUUID().replace(/-/gu, '').replace(/_/gu, '')
- }
- return existingFunc
-}
-
-const _isPropertyStatement = (obj: any): obj is DynamoPropertyStatement => {
- return get(obj, 'type', '') === 'property'
-}
-
-const queryBuilder =
- ({ createUniqueId }: { createUniqueId?: (s: any) => string }) =>
- (tableName: string, queryData: OrmQuery) => {
- const idGenerator = _idGenerator(createUniqueId)
- const properties = queryData.properties
- const page = queryData.page
-
- const flowObjs: DynamoOrmStatement[] = queryData.chain.map(x => ({
- ...x,
- myUniqueId: idGenerator(x),
- }))
- const realProperties: DynamoPropertyStatement[] = flowObjs
- .filter(_isPropertyStatement)
- .map(x => x as DynamoPropertyStatement)
-
- const startKey = page
- ? {
- ExclusiveStartKey: page,
- FilterExpression: undefined,
- ExpressionAttributeNames: undefined,
- ExpressionAttributeValues: undefined,
- }
- : {}
-
- if (Object.keys(properties).length < 1) {
- return {
- ...startKey,
- TableName: tableName,
- }
- }
-
- const propKeyToKey: { [s: string]: string } = realProperties.reduce(
- (acc, obj) => {
- return merge(acc, {
- [obj.myUniqueId]: `my${obj.myUniqueId
- .replace('-', '')
- .replace('_', '')}`,
- })
- },
- {}
- )
-
- const propNametoExpressionAttribute: { [s: string]: string } =
- realProperties.reduce((acc, obj) => {
- const newKey = `#my${obj.myUniqueId.replace('-', '').replace('_', '')}`
- return merge(acc, { [obj.myUniqueId]: newKey })
- }, {})
-
- const expressionAttributeNames = realProperties.reduce((acc, obj) => {
- return merge(acc, {
- [propNametoExpressionAttribute[obj.myUniqueId]]: obj.name,
- })
- }, {})
-
- const _getEqualitySymbol = (flowObj: DynamoOrmStatement) => {
- return get(flowObj, 'options.equalitySymbol')
- }
-
- // I broke this out into its own function, because typescript was having a really hard time with it.
- const _combineStatementAndGetPreviousOrm = (
- acc: string,
- previous: DynamoOrmStatement | undefined,
- flowObj: DynamoOrmStatement
- ): [string, DynamoOrmStatement | undefined] => {
- if (flowObj.type === 'property') {
- const key = flowObj.myUniqueId
- const expressionAttribute = propNametoExpressionAttribute[key]
- const propKey = propKeyToKey[key]
- if (previous && _isPropertyStatement(flowObj)) {
- if (previous.type !== 'and' && previous.type !== 'or') {
- return [
- acc +
- ` AND ${expressionAttribute} ${_getEqualitySymbol(flowObj)} :${propKey}`,
- flowObj,
- ]
- }
- return [
- acc +
- `${expressionAttribute} ${_getEqualitySymbol(flowObj)} :${propKey}`,
- flowObj,
- ]
- }
- return [
- acc +
- `${expressionAttribute} ${_getEqualitySymbol(flowObj)} :${propKey}`,
- flowObj,
- ]
- } else if (flowObj.type === 'and') {
- if (previous && previous.type !== 'property') {
- throw new Error(
- `Cannot preclude and/or with anything except a property`
- )
- }
- return [acc + ' AND ', flowObj]
- } else if (flowObj.type === 'or') {
- if (previous && previous.type !== 'property') {
- throw new Error(
- `Cannot preclude and/or with anything except a property`
- )
- }
- return [acc + ' OR ', flowObj]
- }
- return [acc, flowObj]
- }
-
- const _createFilterExpression = (): string => {
- return flowObjs.reduce(
- (
- [acc, previous]: [string, DynamoOrmStatement | undefined],
- flowObj: DynamoOrmStatement
- ) => {
- return _combineStatementAndGetPreviousOrm(acc, previous, flowObj)
- },
- ['', undefined]
- )[0]
- }
-
- const filterExpression = _createFilterExpression()
-
- const _getStringValue = (value: any) => {
- return value === null || value === undefined ? '' : value
- }
-
- const DATA_TYPE_TO_METHOD = {
- [DATA_TYPES.STRING]: _getStringValue,
- [DATA_TYPES.NUMBER]: identity,
- [DATA_TYPES.BOOLEAN]: identity,
- [DATA_TYPES.NULL]: identity,
- [DATA_TYPES.ARRAY]: identity,
- [DATA_TYPES.OBJECT]: identity,
- }
-
- const expressionAttributeValues = realProperties.reduce((acc, obj) => {
- const dataType = ORM_TYPE_TO_DYNAMO_TYPE[obj.valueType]
- const valueMethod = DATA_TYPE_TO_METHOD[dataType]
- const value = valueMethod(obj.value)
- return merge(acc, {
- [`:${propKeyToKey[obj.myUniqueId]}`]: value,
- })
- }, {})
-
- return {
- ...startKey,
- TableName: tableName,
- FilterExpression: filterExpression,
- ExpressionAttributeNames: expressionAttributeNames,
- ExpressionAttributeValues: expressionAttributeValues,
- }
- }
-
-export default queryBuilder
diff --git a/test/src/datastoreProvider.test.ts b/test/src/datastoreAdapter.test.ts
similarity index 69%
rename from test/src/datastoreProvider.test.ts
rename to test/src/datastoreAdapter.test.ts
index 070c9ac..e59de87 100644
--- a/test/src/datastoreProvider.test.ts
+++ b/test/src/datastoreAdapter.test.ts
@@ -1,11 +1,14 @@
import { assert } from 'chai'
import sinon from 'sinon'
-import proxyquire from 'proxyquire'
-import { Model, FunctionalModel } from 'functional-models/interfaces'
-import { OrmModel, OrmModelInstance } from 'functional-models-orm/interfaces'
-import { ormQueryBuilder } from 'functional-models-orm/ormQuery'
+import {
+ Model,
+ queryBuilder,
+ DataDescription,
+ OrmModel,
+ OrmModelInstance,
+} from 'functional-models'
import { createAws3MockClient } from '../commonMocks'
-import createDatastoreProvider from '../../src/datastoreProvider'
+import * as datastoreAdapter from '../../src/datastoreAdapter'
const createTestModel1 = ({ id, name }: { id: string; name: string }) => {
return {
@@ -15,14 +18,15 @@ const createTestModel1 = ({ id, name }: { id: string; name: string }) => {
},
toObj: () => Promise.resolve({ id, name }),
getPrimaryKey: () => id,
- getPrimaryKeyName: () => 'id',
getModel: () => {
return {
getName: () => 'TestModel1',
- getPrimaryKeyName: () => 'id',
+ getModelDefinition: () => ({
+ primaryKeyName: 'id',
+ }),
} as OrmModel
},
- } as unknown as OrmModelInstance>
+ } as unknown as OrmModelInstance>
}
const createTestModel2 = ({ notId, name }: any) =>
@@ -35,9 +39,11 @@ const createTestModel2 = ({ notId, name }: any) =>
getPrimaryKey: () => notId,
getModel: () => ({
getName: () => 'TestModel1',
- getPrimaryKeyName: () => 'notId',
+ getModelDefinition: () => ({
+ primaryKeyName: 'notId',
+ }),
}),
- }) as unknown as OrmModelInstance>
+ }) as unknown as OrmModelInstance>
const _createDynamoStringResult = (key: string, value: string) => {
return {
@@ -57,87 +63,53 @@ const _createDynamoNullResult = (key: string) => {
}
}
-describe('/src/datastoreProvider.ts', function () {
+describe('/src/datastoreAdapter.ts', function () {
this.timeout(20000)
describe('#()', () => {
it('should not throw an exception with basic arguments', () => {
const aws3 = createAws3MockClient()
- const dynamoOptions = { region: 'fake-region' }
assert.doesNotThrow(() => {
- const datastoreProvider = createDatastoreProvider({
+ datastoreAdapter.create({
aws3,
- dynamoOptions,
})
})
})
it('should throw an exception if getTaqbleNameForModel is null', () => {
const aws3 = createAws3MockClient()
- const dynamoOptions = { region: 'fake-region' }
assert.throws(() => {
- const datastoreProvider = createDatastoreProvider({
+ datastoreAdapter.create({
aws3,
- dynamoOptions,
+ // @ts-ignore
getTableNameForModel: null,
})
})
})
it('should have a "search" function', () => {
const aws3 = createAws3MockClient()
- const dynamoOptions = { region: 'fake-region' }
- const instance = createDatastoreProvider({ aws3, dynamoOptions })
+ const instance = datastoreAdapter.create({ aws3 })
assert.isFunction(instance.search)
})
it('should have a "retrieve" function', () => {
const aws3 = createAws3MockClient()
- const dynamoOptions = { region: 'fake-region' }
- const instance = createDatastoreProvider({ aws3, dynamoOptions })
+ const instance = datastoreAdapter.create({ aws3 })
assert.isFunction(instance.retrieve)
})
it('should have a "save" function', () => {
const aws3 = createAws3MockClient()
- const dynamoOptions = { region: 'fake-region' }
- const instance = createDatastoreProvider({ aws3, dynamoOptions })
+ const instance = datastoreAdapter.create({ aws3 })
assert.isFunction(instance.save)
})
it('should have a "delete" function', () => {
const aws3 = createAws3MockClient()
- const dynamoOptions = { region: 'fake-region' }
- const instance = createDatastoreProvider({ aws3, dynamoOptions })
+ const instance = datastoreAdapter.create({ aws3 })
assert.isFunction(instance.delete)
})
describe('#search()', () => {
- it('should pass createUniqueId into queryBuilder', async () => {
- const aws3 = createAws3MockClient()
- const dynamoOptions = { region: 'fake-region' }
- const myFunc = () => 'fake-id'
- const instance = createDatastoreProvider({
- aws3,
- dynamoOptions,
- createUniqueId: myFunc,
- })
- const obj = { id: 'my-id', name: 'my-name' }
- const query = ormQueryBuilder().property('name', 'my-name').compile()
- // @ts-ignore
- aws3.DynamoDBDocumentClient.sendSinon.onFirstCall().resolves({
- Items: [],
- LastEvaluatedKey: null,
- })
- await instance.search(createTestModel1(obj).getModel(), query)
- const actual = aws3.ScanCommand.sinon.getCall(0).args[0]
- const expected = {
- TableName: 'testmodel1',
- FilterExpression: '#myfakeid = :myfakeid',
- ExpressionAttributeNames: { '#myfakeid': 'name' },
- ExpressionAttributeValues: { ':myfakeid': 'my-name' },
- }
- assert.deepInclude(actual, expected)
- })
it('should call dynamo.scan once when LastEvaluatedKey is empty', async () => {
const aws3 = createAws3MockClient()
- const dynamoOptions = { region: 'fake-region' }
- const instance = createDatastoreProvider({ aws3, dynamoOptions })
+ const instance = datastoreAdapter.create({ aws3 })
const obj = { id: 'my-id', name: 'my-name' }
- const query = ormQueryBuilder().property('name', 'my-name').compile()
+ const query = queryBuilder().property('name', 'my-name').compile()
// @ts-ignore
aws3.DynamoDBDocumentClient.sendSinon.onFirstCall().resolves({
Items: [],
@@ -149,10 +121,9 @@ describe('/src/datastoreProvider.ts', function () {
})
it('should be able to process a string value result', async () => {
const aws3 = createAws3MockClient()
- const dynamoOptions = { region: 'fake-region' }
- const instance = createDatastoreProvider({ aws3, dynamoOptions })
+ const instance = datastoreAdapter.create({ aws3 })
const obj = { id: 'my-id', name: 'my-name' }
- const query = ormQueryBuilder().property('name', 'my-name').compile()
+ const query = queryBuilder().property('name', 'my-name').compile()
// @ts-ignore
aws3.DynamoDBDocumentClient.sendSinon.onFirstCall().resolves({
Items: [
@@ -175,10 +146,9 @@ describe('/src/datastoreProvider.ts', function () {
})
it('should be able to process a null value results', async () => {
const aws3 = createAws3MockClient()
- const dynamoOptions = { region: 'fake-region' }
- const instance = createDatastoreProvider({ aws3, dynamoOptions })
+ const instance = datastoreAdapter.create({ aws3 })
const obj = { id: 'my-id', name: 'my-name' }
- const query = ormQueryBuilder().property('name', null).compile()
+ const query = queryBuilder().property('name', null).compile()
// @ts-ignore
aws3.DynamoDBDocumentClient.sendSinon.onFirstCall().resolves({
Items: [
@@ -201,10 +171,9 @@ describe('/src/datastoreProvider.ts', function () {
})
it('should be able to process an array of strings', async () => {
const aws3 = createAws3MockClient()
- const dynamoOptions = { region: 'fake-region' }
- const instance = createDatastoreProvider({ aws3, dynamoOptions })
+ const instance = datastoreAdapter.create({ aws3 })
const obj = { id: 'my-id', name: 'my-name' }
- const query = ormQueryBuilder().property('names', 'my-name').compile()
+ const query = queryBuilder().property('id', 'my-id').compile()
// @ts-ignore
aws3.DynamoDBDocumentClient.sendSinon.onFirstCall().resolves({
Items: [
@@ -227,10 +196,12 @@ describe('/src/datastoreProvider.ts', function () {
})
it('should return only 1 object if query has "take:1" even if there are two results from dynamo', async () => {
const aws3 = createAws3MockClient()
- const dynamoOptions = { region: 'fake-region' }
- const instance = createDatastoreProvider({ aws3, dynamoOptions })
+ const instance = datastoreAdapter.create({ aws3 })
const obj = { id: 'my-id', name: 'my-name' }
- const query = ormQueryBuilder().property('name', null).take(1).compile()
+ const query = queryBuilder()
+ .property('name', 'name', { startsWith: true })
+ .take(1)
+ .compile()
// @ts-ignore
aws3.DynamoDBDocumentClient.sendSinon.onFirstCall().resolves({
Items: [
@@ -257,10 +228,9 @@ describe('/src/datastoreProvider.ts', function () {
})
it('should call dynamo.scan twice when LastEvaluatedKey is empty the second time', async () => {
const aws3 = createAws3MockClient()
- const dynamoOptions = { region: 'fake-region' }
- const instance = createDatastoreProvider({ aws3, dynamoOptions })
+ const instance = datastoreAdapter.create({ aws3 })
const obj = { id: 'my-id', name: 'my-name' }
- const query = ormQueryBuilder().property('name', 'my-name').compile()
+ const query = queryBuilder().property('name', 'my-name').compile()
// @ts-ignore
aws3.DynamoDBDocumentClient.sendSinon.onFirstCall().resolves({
Items: [],
@@ -277,11 +247,10 @@ describe('/src/datastoreProvider.ts', function () {
})
it('should call dynamo.scan twice when LastEvaluatedKey has a value the second time but take:2 and 3 items are returned', async () => {
const aws3 = createAws3MockClient()
- const dynamoOptions = { region: 'fake-region' }
- const instance = createDatastoreProvider({ aws3, dynamoOptions })
- const obj = { id: 'my-id', name: 'my-name' }
- const query = ormQueryBuilder()
- .property('name', 'my-name')
+ const instance = datastoreAdapter.create({ aws3 })
+ const obj = { id: 'name', name: 'returned' }
+ const query = queryBuilder()
+ .property('name', 'returned', { startsWith: true })
.take(2)
.compile()
// @ts-ignore
@@ -292,9 +261,9 @@ describe('/src/datastoreProvider.ts', function () {
// @ts-ignore
aws3.DynamoDBDocumentClient.sendSinon.onSecondCall().resolves({
Items: [
- { something: 'returned' },
- { something2: 'returned2' },
- { something3: 'returned3' },
+ { name: 'returned' },
+ { name: 'returned2' },
+ { name: 'returned3' },
],
LastEvaluatedKey: 'another-value',
})
@@ -308,10 +277,8 @@ describe('/src/datastoreProvider.ts', function () {
describe('#retrieve()', () => {
it('should create a document client with marshallOptions.removeUndefinedValues=true', async () => {
const aws3 = createAws3MockClient()
- const dynamoOptions = { region: 'fake-region' }
- const instance = createDatastoreProvider({
+ const instance = datastoreAdapter.create({
aws3,
- dynamoOptions,
getTableNameForModel: () => 'FakeTable',
})
const modelInstance = createTestModel1({ id: 'my-id', name: 'my-name' })
@@ -326,10 +293,8 @@ describe('/src/datastoreProvider.ts', function () {
})
it('should pass the correct table name into GetCommand', async () => {
const aws3 = createAws3MockClient()
- const dynamoOptions = { region: 'fake-region' }
- const instance = createDatastoreProvider({
+ const instance = datastoreAdapter.create({
aws3,
- dynamoOptions,
getTableNameForModel: () => 'FakeTable',
})
const modelInstance = createTestModel1({ id: 'my-id', name: 'my-name' })
@@ -340,46 +305,42 @@ describe('/src/datastoreProvider.ts', function () {
})
it('should pass the correct params to GetCommand', async () => {
const aws3 = createAws3MockClient()
- const dynamoOptions = { region: 'fake-region' }
- const instance = createDatastoreProvider({ aws3, dynamoOptions })
+ const instance = datastoreAdapter.create({ aws3 })
const modelInstance = createTestModel1({ id: 'my-id', name: 'my-name' })
await instance.retrieve(modelInstance.getModel(), 'my-id')
const actual = aws3.GetCommand.sinon.getCall(0).args[0]
- const expected = { Key: { id: 'my-id' }, TableName: 'testmodel1' }
+ const expected = { Key: { id: 'my-id' }, TableName: 'test-model-1' }
assert.deepEqual(actual, expected)
})
})
describe('#delete()', () => {
it('should pass the correct params to DeleteCommand', async () => {
const aws3 = createAws3MockClient()
- const dynamoOptions = { region: 'fake-region' }
- const instance = createDatastoreProvider({ aws3, dynamoOptions })
+ const instance = datastoreAdapter.create({ aws3 })
const modelInstance = createTestModel1({ id: 'my-id', name: 'my-name' })
- await instance.delete(modelInstance)
+ await instance.delete(modelInstance.getModel(), 'my-id')
const actual = aws3.DeleteCommand.sinon.getCall(0).args[0]
- const expected = { Key: { id: 'my-id' }, TableName: 'testmodel1' }
+ const expected = { Key: { id: 'my-id' }, TableName: 'test-model-1' }
assert.deepEqual(actual, expected)
})
})
describe('#save()', () => {
it('should pass results of modelInstance.functions.toObj() to PutCommand', async () => {
const aws3 = createAws3MockClient()
- const dynamoOptions = { region: 'fake-region' }
- const instance = createDatastoreProvider({ aws3, dynamoOptions })
+ const instance = datastoreAdapter.create({ aws3 })
const modelInstance = createTestModel1({ id: 'my-id', name: 'my-name' })
await instance.save(modelInstance)
const actual = aws3.PutCommand.sinon.getCall(0).args[0]
const expected = {
Key: { id: 'my-id' },
Item: { id: 'my-id', name: 'my-name' },
- TableName: 'testmodel1',
+ TableName: 'test-model-1',
}
assert.deepEqual(actual, expected)
})
it('should pass the correct primary key when changed by the model to PutCommand', async () => {
const aws3 = createAws3MockClient()
- const dynamoOptions = { region: 'fake-region' }
- const instance = createDatastoreProvider({ aws3, dynamoOptions })
+ const instance = datastoreAdapter.create({ aws3 })
const modelInstance = createTestModel2({
notId: 'my-id',
name: 'my-name',
@@ -389,7 +350,7 @@ describe('/src/datastoreProvider.ts', function () {
const expected = {
Key: { notId: 'my-id' },
Item: { notId: 'my-id', name: 'my-name' },
- TableName: 'testmodel1',
+ TableName: 'test-model-1',
}
assert.deepEqual(actual, expected)
})
@@ -397,8 +358,7 @@ describe('/src/datastoreProvider.ts', function () {
describe('#bulkInsert()', () => {
it('should format the objects correctly when passed to BatchWriteCommand', async () => {
const aws3 = createAws3MockClient()
- const dynamoOptions = { region: 'fake-region' }
- const instance = createDatastoreProvider({ aws3, dynamoOptions })
+ const instance = datastoreAdapter.create({ aws3 })
const models = [
createTestModel1({ id: '1', name: 'my-name' }),
createTestModel1({ id: '2', name: 'my-name' }),
@@ -406,8 +366,8 @@ describe('/src/datastoreProvider.ts', function () {
createTestModel1({ id: '4', name: 'my-name' }),
createTestModel1({ id: '5', name: 'my-name' }),
]
- // @ts-ignore
await instance.bulkInsert(
+ // @ts-ignore
{
getName: () => 'TestName',
},
@@ -416,7 +376,7 @@ describe('/src/datastoreProvider.ts', function () {
const actual = aws3.BatchWriteCommand.sinon.getCall(0).args[0]
const expected = {
RequestItems: {
- testname: [
+ 'test-name': [
{ PutRequest: { Item: { id: '1', name: 'my-name' } } },
{ PutRequest: { Item: { id: '2', name: 'my-name' } } },
{ PutRequest: { Item: { id: '3', name: 'my-name' } } },
@@ -429,11 +389,10 @@ describe('/src/datastoreProvider.ts', function () {
})
it('should not call BulkWriteCommand if there are no models', async () => {
const aws3 = createAws3MockClient()
- const dynamoOptions = { region: 'fake-region' }
- const instance = createDatastoreProvider({ aws3, dynamoOptions })
- const models = []
- // @ts-ignore
+ const instance = datastoreAdapter.create({ aws3 })
+ const models: any[] = []
await instance.bulkInsert(
+ // @ts-ignore
{
getName: () => 'TestName',
},
diff --git a/test/src/lib.test.ts b/test/src/lib.test.ts
index b40df2c..7205b0b 100644
--- a/test/src/lib.test.ts
+++ b/test/src/lib.test.ts
@@ -1,4 +1,4 @@
-import { Model } from 'functional-models/interfaces'
+import { ModelType } from 'functional-models'
import { assert } from 'chai'
import sinon from 'sinon'
import {
@@ -6,16 +6,17 @@ import {
splitArrayIntoArraysOfMaxSize,
} from '../../src/lib'
-const buildModel = (name: string): Model => {
+const buildModel = (name: string): ModelType => {
return {
getName: () => name,
- } as Model
+ } as ModelType
}
describe('/src/lib.ts', () => {
describe('#splitArrayIntoArraysOfMaxSize()', () => {
it('should throw an exception if the input is not an array', () => {
assert.throws(() => {
+ // @ts-ignore
splitArrayIntoArraysOfMaxSize('input', 2)
})
})
@@ -54,9 +55,9 @@ describe('/src/lib.ts', () => {
const expected = 'my-table'
assert.deepEqual(actual, expected)
})
- it('should return "mytable" for "MyTable"', () => {
+ it('should return "my-table" for "MyTable"', () => {
const actual = getTableNameForModel(buildModel('MyTable'))
- const expected = 'mytable'
+ const expected = 'my-table'
assert.deepEqual(actual, expected)
})
})
diff --git a/test/src/queryBuilder.test.ts b/test/src/queryBuilder.test.ts
deleted file mode 100644
index f1b2f43..0000000
--- a/test/src/queryBuilder.test.ts
+++ /dev/null
@@ -1,203 +0,0 @@
-import { get } from 'lodash'
-import { assert } from 'chai'
-import sinon from 'sinon'
-import { ormQueryBuilder } from 'functional-models-orm/ormQuery'
-import { EQUALITY_SYMBOLS, ORMType } from 'functional-models-orm/constants'
-import queryBuilder from '../../src/queryBuilder'
-
-const _nameId = () => ({ createUniqueId: (obj: any) => obj.name })
-
-describe('/src/queryBuilder.ts', () => {
- describe('#()', () => {
- it('should set TableName to what is passed in', () => {
- const query = ormQueryBuilder().property('name', 'value').compile()
- const actual = get(
- queryBuilder(_nameId())('my-table', query),
- 'TableName'
- )
- const expected = 'my-table'
- assert.deepEqual(actual, expected)
- })
- /*
- situations
- null values
- page and no page
- _ in name
- multiple properties and one property
- */
- it('should TableName to what is passed in', () => {
- const query = ormQueryBuilder().property('name', 'value').compile()
- const actual = get(
- queryBuilder(_nameId())('my-table', query),
- 'TableName'
- )
- const expected = 'my-table'
- assert.deepEqual(actual, expected)
- })
- it('should produce an expected FilterExpression for a single property', () => {
- const query = ormQueryBuilder().property('name', 'value').compile()
- const actual = queryBuilder(_nameId())('my-table', query).FilterExpression
- const expected = '#myname = :myname'
- assert.deepEqual(actual, expected)
- })
- it('should produce an expected FilterExpression for a single property with a > symbol', () => {
- const query = ormQueryBuilder()
- .property('name', 5, {
- type: ORMType.number,
- equalitySymbol: EQUALITY_SYMBOLS.GT,
- })
- .compile()
- const actual = queryBuilder(_nameId())('my-table', query).FilterExpression
- const expected = '#myname > :myname'
- assert.deepEqual(actual, expected)
- })
- it('should produce an expected FilterExpression for a single property when the name has a dash', () => {
- const query = ormQueryBuilder().property('name-name', 'value').compile()
- const actual = queryBuilder(_nameId())('my-table', query).FilterExpression
- const expected = '#mynamename = :mynamename'
- assert.deepEqual(actual, expected)
- })
- it('should produce an expected FilterExpression for two properties', () => {
- const query = ormQueryBuilder()
- .property('name', 'value')
- .property('secondname', 'value')
- .compile()
- const actual = queryBuilder(_nameId())('my-table', query).FilterExpression
- const expected = '#myname = :myname AND #mysecondname = :mysecondname'
- assert.deepEqual(actual, expected)
- })
- it('should produce an expected FilterExpression for two properties with an AND', () => {
- const query = ormQueryBuilder()
- .property('name', 'value')
- .and()
- .property('secondname', 'value')
- .compile()
- const actual = queryBuilder(_nameId())('my-table', query).FilterExpression
- const expected = '#myname = :myname AND #mysecondname = :mysecondname'
- assert.deepEqual(actual, expected)
- })
- it('should produce an expected FilterExpression for two properties with an OR', () => {
- const query = ormQueryBuilder()
- .property('name', 'value')
- .or()
- .property('secondname', 'value')
- .compile()
- const actual = queryBuilder(_nameId())('my-table', query).FilterExpression
- const expected = '#myname = :myname OR #mysecondname = :mysecondname'
- assert.deepEqual(actual, expected)
- })
- it('should throw an exception if two OR are called', () => {
- const query = ormQueryBuilder()
- .property('name', 'value')
- .or()
- .or()
- .property('secondname', 'value')
- .compile()
- try {
- const actual = queryBuilder(_nameId())(
- 'my-table',
- query
- ).FilterExpression
- throw new Error(`No exception thrown`)
- } catch {}
- })
- it('should throw an exception if two AND are called', () => {
- const query = ormQueryBuilder()
- .property('name', 'value')
- .and()
- .and()
- .property('secondname', 'value')
- .compile()
- try {
- const actual = queryBuilder(_nameId())(
- 'my-table',
- query
- ).FilterExpression
- throw new Error(`No exception thrown`)
- } catch {}
- })
- it('should produce an expected ExpressionAttributeNames for a single property when the name has a dash', () => {
- const query = ormQueryBuilder().property('name-name', 'value').compile()
- const actual = queryBuilder(_nameId())(
- 'my-table',
- query
- ).ExpressionAttributeNames
- const expected = {
- '#mynamename': 'name-name',
- }
- assert.deepEqual(actual, expected)
- })
- it('should produce an expected ExpressionAttributeNames for two properties', () => {
- const query = ormQueryBuilder()
- .property('name', 'value')
- .property('description', 'the-description')
- .compile()
- const actual = queryBuilder(_nameId())(
- 'my-table',
- query
- ).ExpressionAttributeNames
- const expected = {
- '#myname': 'name',
- '#mydescription': 'description',
- }
- assert.deepEqual(actual, expected)
- })
- it('should produce an expected ExpressionAttributeValues for one property', () => {
- const query = ormQueryBuilder().property('name', 'value').compile()
- const actual = queryBuilder(_nameId())(
- 'my-table',
- query
- ).ExpressionAttributeValues
- const expected = {
- ':myname': 'value',
- }
- assert.deepEqual(actual, expected)
- })
- it('should produce an expected ExpressionAttributeValues for one property that has a null value', () => {
- const query = ormQueryBuilder().property('name', null).compile()
- const actual = queryBuilder(_nameId())(
- 'my-table',
- query
- ).ExpressionAttributeValues
- const expected = {
- ':myname': '',
- }
- assert.deepEqual(actual, expected)
- })
- it('should produce an expected ExpressionAttributeValues for two properties with one being null', () => {
- const query = ormQueryBuilder()
- .property('name', 'value')
- .property('other', null)
- .compile()
- const actual = queryBuilder(_nameId())(
- 'my-table',
- query
- ).ExpressionAttributeValues
- const expected = {
- ':myname': 'value',
- ':myother': '',
- }
- assert.deepEqual(actual, expected)
- })
- it('should produce an expected search when no properties are provided', () => {
- const query = ormQueryBuilder().compile()
- const actual = queryBuilder(_nameId())('my-table', query)
- const expected = {
- TableName: 'my-table',
- }
- assert.deepEqual(actual, expected)
- })
- it('should produce an expected search when no properties are provided but there is a page', () => {
- const query = ormQueryBuilder().pagination({ custom: 'page' }).compile()
- const actual = queryBuilder(_nameId())('my-table', query)
- const expected = {
- TableName: 'my-table',
- ExclusiveStartKey: { custom: 'page' },
- ExpressionAttributeNames: undefined,
- ExpressionAttributeValues: undefined,
- FilterExpression: undefined,
- }
- assert.deepEqual(actual as object, expected as object)
- })
- })
-})
diff --git a/tsconfig.build.json b/tsconfig.build.json
deleted file mode 100644
index 7827a17..0000000
--- a/tsconfig.build.json
+++ /dev/null
@@ -1,106 +0,0 @@
-{
- "compilerOptions": {
- /* Visit https://aka.ms/tsconfig.json to read more about this file */
-
- /* Projects */
- // "incremental": true, /* Enable incremental compilation */
- // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
- // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
- // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
- // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
- // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
-
- /* Language and Environment */
- "target": "es6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
- "lib": ["es2021", "dom", "es2015"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
- // "jsx": "preserve", /* Specify what JSX code is generated. */
- // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
- // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
- // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
- // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
- // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
- // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
- // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
- // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
-
- /* Modules */
- "module": "commonjs", /* Specify what module code is generated. */
- "rootDir": "./src" /* Specify the root folder within your source files. */,
- // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
- // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
- // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
- //"rootDirs": ["./src","], /* Allow multiple folders to be treated as one when resolving modules. */
- // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
- // "types": [], /* Specify type package names to be included without being referenced in a source file. */
- // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
- // "resolveJsonModule": true, /* Enable importing .json files */
- // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */
-
- /* JavaScript Support */
- // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
- // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
- // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
-
- /* Emit */
- "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
- // "declarationMap": true, /* Create sourcemaps for d.ts files. */
- // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
- "sourceMap": true, /* Create source map files for emitted JavaScript files. */
- // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
- "outDir": "./dist" /* Specify an output folder for all emitted files. */,
- // "removeComments": true, /* Disable emitting comments. */
- // "noEmit": true, /* Disable emitting files from a compilation. */
- // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
- // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
- // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
- // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
- // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
- // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
- // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
- // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
- // "newLine": "crlf", /* Set the newline character for emitting files. */
- // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
- // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
- // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
- // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
- // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
-
- /* Interop Constraints */
- // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
- // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
- "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
- // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
- "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
-
- /* Type Checking */
- "strict": true, /* Enable all strict type-checking options. */
- // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
- // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
- // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
- // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
- // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
- // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
- // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
- // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
- // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
- // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
- // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
- // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
- // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
- // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
- // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
- // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
- // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
- // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
-
- /* Completeness */
- // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
- "skipLibCheck": true /* Skip type checking all .d.ts files. */
- },
- "exclude": [
- "node_modules",
- "dist",
- "stepDefinitions",
- "test"
- ]
-}
diff --git a/tsconfig.cucumber.json b/tsconfig.cucumber.json
new file mode 100644
index 0000000..733bb33
--- /dev/null
+++ b/tsconfig.cucumber.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "es6",
+ "lib": ["es2023", "es2021", "dom", "es2017"],
+ "module": "node16",
+ "moduleResolution": "node16",
+ "resolveJsonModule": true,
+ "noImplicitAny": false,
+ "suppressImplicitAnyIndexErrors": true,
+ "declaration": true,
+ "sourceMap": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "strict": false,
+ "skipLibCheck": true,
+ "rootDir": "./src",
+ "outDir": "./dist"
+ },
+ "exclude": ["node_modules", "dist", "features", "coverage", "test"]
+}
diff --git a/tsconfig.json b/tsconfig.json
index c59ee45..b2fb974 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,18 +1,18 @@
{
"compilerOptions": {
"target": "es6",
- "lib": ["es2021", "dom", "es2015"],
+ "lib": ["es2023", "es2021", "dom", "es2017"],
+ "module": "node16",
+ "moduleResolution": "node16",
+ "resolveJsonModule": true,
"declaration": true,
- "module": "commonjs",
"sourceMap": true,
- "moduleResolution": "Node",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
- "skipLibCheck": true
+ "skipLibCheck": true,
+ "rootDir": "./src",
+ "outDir": "./dist"
},
- "exclude": [
- "node_modules",
- "dist"
- ]
+ "exclude": ["node_modules", "dist", "features", "coverage", "test"]
}