From 3bd4591d68f43da505f306e576e83cf30edd9b1e Mon Sep 17 00:00:00 2001 From: Merit Date: Thu, 7 Nov 2024 23:01:43 -0800 Subject: [PATCH 1/6] Init stored constraint plugin Signed-off-by: Merit --- package-lock.json | 53 +++++++++++- plugins/stored-constraints/CHANGELOG.md | 1 + plugins/stored-constraints/README.md | 34 ++++++++ plugins/stored-constraints/jest.config.cjs | 16 ++++ plugins/stored-constraints/package.json | 74 +++++++++++++++++ plugins/stored-constraints/src/index.ts | 1 + .../src/stored.constraint.e2e.spec.ts | 80 +++++++++++++++++++ .../src/stored.constraint.ts | 57 +++++++++++++ plugins/stored-constraints/src/utils.ts | 33 ++++++++ plugins/stored-constraints/tsup.config.mjs | 3 + 10 files changed, 348 insertions(+), 4 deletions(-) create mode 100644 plugins/stored-constraints/CHANGELOG.md create mode 100644 plugins/stored-constraints/README.md create mode 100644 plugins/stored-constraints/jest.config.cjs create mode 100644 plugins/stored-constraints/package.json create mode 100644 plugins/stored-constraints/src/index.ts create mode 100644 plugins/stored-constraints/src/stored.constraint.e2e.spec.ts create mode 100644 plugins/stored-constraints/src/stored.constraint.ts create mode 100644 plugins/stored-constraints/src/utils.ts create mode 100644 plugins/stored-constraints/tsup.config.mjs diff --git a/package-lock.json b/package-lock.json index 376d11792..275f9ada1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -293,6 +293,7 @@ "flatfilers/playground": { "name": "@private/playground", "version": "0.0.0", + "extraneous": true, "license": "ISC", "dependencies": { "@flatfile/api": "^1.9.19", @@ -4104,6 +4105,10 @@ "resolved": "plugins/space-configure", "link": true }, + "node_modules/@flatfile/plugin-stored-constraints": { + "resolved": "plugins/stored-constraints", + "link": true + }, "node_modules/@flatfile/plugin-tsv-extractor": { "resolved": "plugins/tsv-extractor", "link": true @@ -4982,10 +4987,6 @@ "node": ">=14" } }, - "node_modules/@private/playground": { - "resolved": "flatfilers/playground", - "link": true - }, "node_modules/@private/sandbox": { "resolved": "flatfilers/sandbox", "link": true @@ -12146,6 +12147,11 @@ "node": ">=8" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.camelcase": { "version": "4.3.0", "license": "MIT" @@ -12347,6 +12353,14 @@ "yallist": "^3.0.2" } }, + "node_modules/luxon": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz", + "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==", + "engines": { + "node": ">=12" + } + }, "node_modules/magic-string": { "version": "0.30.12", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", @@ -16819,6 +16833,14 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "license": "MIT", @@ -17864,6 +17886,29 @@ "@flatfile/listener": "^1.1.0" } }, + "plugins/stored-constraints": { + "name": "@flatfile/plugin-stored-constraints", + "version": "0.0.0", + "license": "ISC", + "dependencies": { + "country-state-city": "^3.2.1", + "lodash": "^4.17.21", + "luxon": "^3.5.0", + "validator": "^13.12.0" + }, + "devDependencies": { + "@flatfile/bundler-config-tsup": "^0.1.0", + "@flatfile/plugin-record-hook": "^1.9.0" + }, + "engines": { + "node": ">= 16" + }, + "peerDependencies": { + "@flatfile/api": "^1.9.19", + "@flatfile/listener": "^1.1.0", + "@flatfile/plugin-record-hook": "^1.9.0" + } + }, "plugins/tsv-extractor": { "name": "@flatfile/plugin-tsv-extractor", "version": "1.10.0", diff --git a/plugins/stored-constraints/CHANGELOG.md b/plugins/stored-constraints/CHANGELOG.md new file mode 100644 index 000000000..65fe599a5 --- /dev/null +++ b/plugins/stored-constraints/CHANGELOG.md @@ -0,0 +1 @@ +# @flatfile/plugin-stored-constraints \ No newline at end of file diff --git a/plugins/stored-constraints/README.md b/plugins/stored-constraints/README.md new file mode 100644 index 000000000..40aed592d --- /dev/null +++ b/plugins/stored-constraints/README.md @@ -0,0 +1,34 @@ +# @flatfile/plugin-constraints + +This plugin introduces the ability to register external constraints for blueprint. + +## Usage + +```ts +listener.use( + externalConstraint('length', (value, key, { config, record }) => { + if (value.length > config.max) { + record.addError(key, `Text must be under ${config.max} characters`) + // alternatively throw the error + } + }) +) +``` + +```js +// blueprint fields +[ + { + key: 'name', + type: 'string', + constraints: [ + { type: 'external', validator: 'length', config: { max: 100 } } + ] + }, + { + key: 'age', + type: 'number', + } +] +``` + diff --git a/plugins/stored-constraints/jest.config.cjs b/plugins/stored-constraints/jest.config.cjs new file mode 100644 index 000000000..e6d7ca40b --- /dev/null +++ b/plugins/stored-constraints/jest.config.cjs @@ -0,0 +1,16 @@ +module.exports = { + testEnvironment: 'node', + + transform: { + '^.+\\.tsx?$': 'ts-jest', + }, + setupFiles: ['../../test/dotenv-config.js'], + setupFilesAfterEnv: [ + '../../test/betterConsoleLog.js', + '../../test/unit.cleanup.js', + ], + testTimeout: 60_000, + globalSetup: '../../test/setup-global.js', + forceExit: true, + passWithNoTests: true, +} diff --git a/plugins/stored-constraints/package.json b/plugins/stored-constraints/package.json new file mode 100644 index 000000000..be65cf868 --- /dev/null +++ b/plugins/stored-constraints/package.json @@ -0,0 +1,74 @@ +{ + "name": "@flatfile/plugin-stored-constraints", + "version": "0.0.0", + "url": "https://github.com/FlatFilers/flatfile-plugins/tree/main/plugins/stored-constraints", + "description": "A plugin for running stored constraints", + "type": "module", + "engines": { + "node": ">= 16" + }, + "registryMetadata": { + "category": "records" + }, + "exports": { + ".": { + "node": { + "types": { + "import": "./dist/index.d.ts", + "require": "./dist/index.d.cts" + }, + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "browser": { + "types": { + "import": "./dist/index.d.ts", + "require": "./dist/index.d.cts" + }, + "import": "./dist/index.browser.js", + "require": "./dist/index.browser.cjs" + }, + "default": "./dist/index.js" + }, + "./package.json": "./package.json" + }, + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "source": "./src/index.ts", + "types": "./dist/index.d.ts", + "files": [ + "dist/**" + ], + "scripts": { + "build": "tsup", + "build:watch": "tsup --watch", + "build:prod": "NODE_ENV=production tsup", + "checks": "tsc --noEmit && attw --pack . && publint .", + "lint": "tsc --noEmit", + "test": "jest src/*.spec.ts --detectOpenHandles", + "test:unit": "jest src/*.spec.ts --testPathIgnorePatterns=.*\\.e2e\\.spec\\.ts$ --detectOpenHandles", + "test:e2e": "jest src/*.e2e.spec.ts --detectOpenHandles" + }, + "keywords": [], + "repository": { + "type": "git", + "url": "git+https://github.com/FlatFilers/flatfile-plugins.git", + "directory": "plugins/stored-constraints" + }, + "license": "ISC", + "dependencies": { + "country-state-city": "^3.2.1", + "lodash": "^4.17.21", + "luxon": "^3.5.0", + "validator": "^13.12.0" + }, + "peerDependencies": { + "@flatfile/api": "^1.9.19", + "@flatfile/listener": "^1.1.0", + "@flatfile/plugin-record-hook": "^1.9.0" + }, + "devDependencies": { + "@flatfile/plugin-record-hook": "^1.9.0", + "@flatfile/bundler-config-tsup": "^0.1.0" + } +} \ No newline at end of file diff --git a/plugins/stored-constraints/src/index.ts b/plugins/stored-constraints/src/index.ts new file mode 100644 index 000000000..4fd8ff18e --- /dev/null +++ b/plugins/stored-constraints/src/index.ts @@ -0,0 +1 @@ +export * from './stored.constraint' diff --git a/plugins/stored-constraints/src/stored.constraint.e2e.spec.ts b/plugins/stored-constraints/src/stored.constraint.e2e.spec.ts new file mode 100644 index 000000000..ba3ffdcbc --- /dev/null +++ b/plugins/stored-constraints/src/stored.constraint.e2e.spec.ts @@ -0,0 +1,80 @@ +import { Flatfile } from '@flatfile/api' +import { + createRecords, + deleteSpace, + getRecords, + setupListener, + setupSimpleWorkbook, + setupSpace, +} from '@flatfile/utils-testing' +import { storedConstraint } from './stored.constraint' +import * as utils from './utils' + +jest.mock('./utils', () => ({ + ...jest.requireActual('./utils'), + getAppConstraints: jest.fn(), +})) + +describe('storedConstraint()', () => { + const listener = setupListener() + + let spaceId: string + let sheetId: string + + beforeAll(async () => { + const space = await setupSpace() + spaceId = space.id + const workbook = await setupSimpleWorkbook( + space.id, + defaultSimpleValueSchema + ) + sheetId = workbook.sheets![0].id + }) + + beforeEach(() => { + listener.use(storedConstraint); + (utils.getAppConstraints as jest.Mock).mockResolvedValue({ + data: [ + { type: 'stored', validator: 'test', function: 'function constraint() {}' }, + ], + }) + }) + + afterAll(async () => { + await deleteSpace(spaceId) + }) + + it('correctly assigns an error', async () => { + }) + + it('correctly handles thrown errors', async () => { + }) + + it('allows setting of values', async () => { + }) +}) + +export const defaultSimpleValueSchema: Array = [ + { + key: 'name', + type: 'string', + constraints: [ + { type: 'external', validator: 'test', config: { flag: true } }, + ], + }, + { + key: 'age', + type: 'number', + }, +] + +export const defaultSimpleValueData = [ + { + name: 'John Doe', + age: 1, + }, + { + name: 'Jane Doe', + age: 1, + }, +] diff --git a/plugins/stored-constraints/src/stored.constraint.ts b/plugins/stored-constraints/src/stored.constraint.ts new file mode 100644 index 000000000..093bca6a3 --- /dev/null +++ b/plugins/stored-constraints/src/stored.constraint.ts @@ -0,0 +1,57 @@ +import { bulkRecordHook } from '@flatfile/plugin-record-hook' +import { + applyConstraintToRecord, + crossEach, + getStoredConstraints, + getFields, + getSheet, + getValidator, + hasStoredConstraints, + getAppConstraints, +} from './utils' +import validator from 'validator' +import * as countryStateCity from 'country-state-city' +import { DateTime } from 'luxon' +import type { FlatfileEvent, FlatfileListener } from '@flatfile/listener' +import type { FlatfileRecord } from '@flatfile/hooks' + +const deps = { validator, countryStateCity, luxon: DateTime, } + +export interface Constraint { + validator: string + function: string + type?: string +} + +async function getValidators(event: FlatfileEvent): Promise { + const constraints = await getAppConstraints(event.context.appId) + return constraints.data.map((c: any) => { + return { + validator: c.validator, + function: c.function, + } + }) +} + +export function storedConstraint() { + return (listener: FlatfileListener) => { + listener.use( + bulkRecordHook('**', async (records: FlatfileRecord[], event: FlatfileEvent) => { + const sheet = await getSheet(event) + const storedConstraintFields = getFields(sheet).filter(hasStoredConstraints) + const validators = await getValidators(event) + + crossEach([records, storedConstraintFields], (record: FlatfileRecord, field: any) => { + getStoredConstraints(field.constraints).forEach( + async ({ validator }: { validator: string }) => { + const constraint = await getValidator(validators, validator) + if (constraint) { + applyConstraintToRecord(constraint, record, field, deps, sheet) + } + }, + ) + }) + }), + ) + } +} \ No newline at end of file diff --git a/plugins/stored-constraints/src/utils.ts b/plugins/stored-constraints/src/utils.ts new file mode 100644 index 000000000..8d888f947 --- /dev/null +++ b/plugins/stored-constraints/src/utils.ts @@ -0,0 +1,33 @@ +import api from '@flatfile/api' +import * as _ from 'lodash' +import type { FlatfileEvent } from '@flatfile/listener' +import { Constraint } from './stored.constraint' + +export const getSheet = (event: FlatfileEvent ) => api.sheets.get(event.context.sheetId) + +export const getFields = ({ data }) => data.config.fields + +export const getStoredConstraints = (constraints: Constraint[]) => constraints?.filter((c) => c.type == 'stored') + +export const hasStoredConstraints = (field) => + getStoredConstraints(field.constraints || []).length > 0 + +export const getValidator = (v, n) => v.find((w) => w.validator === n) +export const applyConstraintToRecord = (constraint: Constraint, record, field, deps, sheet) => { + const storedConstraint = field.constraints?.find((fieldConstraint) => fieldConstraint.validator === constraint.validator) + const { config = {} } = storedConstraint || {} + const constraintFn = constraint.function.startsWith('function') + ? constraint.function.includes('function constraint') + ? eval( + '(' + constraint.function.replace('function constraint', 'function') + ')', + ) + : eval('(' + constraint.function + ')') + : eval(constraint.function) + + constraintFn(record.get(field.key), field.key, { config, record, deps, sheet }) +} +export const crossProduct = (...a) => + a.reduce((u, c) => _.flatMap(u, (a) => c.map((b) => a.concat(b))), [[]]) +export const crossEach = (a, cb) => crossProduct(...a).forEach((p) => cb(...p)) +export const getAppConstraints = (a) => + api.apps.getConstraints(a, { includeBuiltins: true }) diff --git a/plugins/stored-constraints/tsup.config.mjs b/plugins/stored-constraints/tsup.config.mjs new file mode 100644 index 000000000..bd6420831 --- /dev/null +++ b/plugins/stored-constraints/tsup.config.mjs @@ -0,0 +1,3 @@ +import { defineConfig } from '@flatfile/bundler-config-tsup' + +export default defineConfig({}) From 100e2b5876cde45f439ab51dd24249147fe2d43c Mon Sep 17 00:00:00 2001 From: Merit Date: Tue, 12 Nov 2024 21:00:48 -0800 Subject: [PATCH 2/6] Fix api usage --- package-lock.json | 72 ++++++++++++----- plugins/stored-constraints/README.md | 31 +------ plugins/stored-constraints/package.json | 17 ++-- .../src/stored.constraint.e2e.spec.ts | 80 ------------------- plugins/stored-constraints/src/utils.ts | 4 +- 5 files changed, 66 insertions(+), 138 deletions(-) delete mode 100644 plugins/stored-constraints/src/stored.constraint.e2e.spec.ts diff --git a/package-lock.json b/package-lock.json index 275f9ada1..e59640a8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -293,7 +293,6 @@ "flatfilers/playground": { "name": "@private/playground", "version": "0.0.0", - "extraneous": true, "license": "ISC", "dependencies": { "@flatfile/api": "^1.9.19", @@ -3838,7 +3837,9 @@ } }, "node_modules/@flatfile/api": { - "version": "1.10.0", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@flatfile/api/-/api-1.11.0.tgz", + "integrity": "sha512-kz7BdxdiCWEXZSSxwcB8BfujWUKAwNivlKuWM5UCyCAyp5qf4VEDfUgd3FQcxa6KJ01E6aYkkp+45ICxrJL/Cw==", "dependencies": { "@flatfile/cross-env-config": "0.0.4", "@types/pako": "2.0.1", @@ -4987,6 +4988,10 @@ "node": ">=14" } }, + "node_modules/@private/playground": { + "resolved": "flatfilers/playground", + "link": true + }, "node_modules/@private/sandbox": { "resolved": "flatfilers/sandbox", "link": true @@ -6137,9 +6142,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.24.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.3.tgz", - "integrity": "sha512-mnEOh4iE4USSccBOtcrjF5nj+5/zm6NcNhbSEfR3Ot0pxBwvEn5QVUXcuOwwPkapDtGZ6pT02xLoPaNv06w7KQ==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.4.tgz", + "integrity": "sha512-K03TljaaoPK5FOyNMZAAEmhlyO49LaE4qCsr0lYHUKyb6QacTNF9pnfPpXnFlFD3TXuFbFbz7tJ51FujUXkXYA==", "cpu": [ "x64" ], @@ -8027,6 +8032,10 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/country-state-city": { + "version": "3.2.1", + "license": "GPL-3.0" + }, "node_modules/cross-fetch": { "version": "4.0.0", "license": "MIT", @@ -12149,8 +12158,7 @@ }, "node_modules/lodash": { "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "license": "MIT" }, "node_modules/lodash.camelcase": { "version": "4.3.0", @@ -12355,8 +12363,7 @@ }, "node_modules/luxon": { "version": "3.5.0", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz", - "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==", + "license": "MIT", "engines": { "node": ">=12" } @@ -15705,9 +15712,9 @@ } }, "node_modules/std-env": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", - "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==" + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==" }, "node_modules/stoppable": { "version": "1.1.0", @@ -16835,8 +16842,7 @@ }, "node_modules/validator": { "version": "13.12.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", - "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "license": "MIT", "engines": { "node": ">= 0.10" } @@ -17657,10 +17663,10 @@ }, "plugins/json-extractor": { "name": "@flatfile/plugin-json-extractor", - "version": "0.10.0", + "version": "0.11.0", "license": "ISC", "dependencies": { - "@flatfile/util-extractor": "^2.3.0" + "@flatfile/util-extractor": "^2.4.0" }, "devDependencies": { "@flatfile/bundler-config-tsup": "^0.2.0", @@ -17897,16 +17903,17 @@ "validator": "^13.12.0" }, "devDependencies": { - "@flatfile/bundler-config-tsup": "^0.1.0", - "@flatfile/plugin-record-hook": "^1.9.0" + "@flatfile/bundler-config-tsup": "^0.2.0", + "@flatfile/config-vitest": "^0.0.0", + "@flatfile/plugin-record-hook": "^1.10.0" }, "engines": { - "node": ">= 16" + "node": ">= 18" }, "peerDependencies": { "@flatfile/api": "^1.9.19", "@flatfile/listener": "^1.1.0", - "@flatfile/plugin-record-hook": "^1.9.0" + "@flatfile/plugin-record-hook": "^1.10.0" } }, "plugins/tsv-extractor": { @@ -18101,6 +18108,29 @@ "node": ">= 18" } }, + "utils/asdf": { + "name": "@flatfile/util-asdf", + "version": "1.0.0", + "extraneous": true, + "license": "ISC", + "dependencies": { + "chrono-node": "^2.7.7", + "collect.js": "^4.36.1", + "cross-fetch": "^4.0.0", + "jsonlines": "^0.1.1" + }, + "devDependencies": { + "@flatfile/bundler-config-tsup": "^0.2.0", + "@flatfile/config-vitest": "^0.0.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@flatfile/api": "^1.9.19", + "@flatfile/listener": "^1.1.0" + } + }, "utils/common": { "name": "@flatfile/util-common", "version": "1.6.0", @@ -18127,7 +18157,7 @@ }, "utils/extractor": { "name": "@flatfile/util-extractor", - "version": "2.3.0", + "version": "2.4.0", "license": "ISC", "dependencies": { "@flatfile/util-common": "^1.6.0", diff --git a/plugins/stored-constraints/README.md b/plugins/stored-constraints/README.md index 40aed592d..8eae3bec6 100644 --- a/plugins/stored-constraints/README.md +++ b/plugins/stored-constraints/README.md @@ -1,34 +1,9 @@ # @flatfile/plugin-constraints -This plugin introduces the ability to register external constraints for blueprint. +This plugin enabled running stored constraints. ## Usage ```ts -listener.use( - externalConstraint('length', (value, key, { config, record }) => { - if (value.length > config.max) { - record.addError(key, `Text must be under ${config.max} characters`) - // alternatively throw the error - } - }) -) -``` - -```js -// blueprint fields -[ - { - key: 'name', - type: 'string', - constraints: [ - { type: 'external', validator: 'length', config: { max: 100 } } - ] - }, - { - key: 'age', - type: 'number', - } -] -``` - + listener.use(storedConstraint()) +``` \ No newline at end of file diff --git a/plugins/stored-constraints/package.json b/plugins/stored-constraints/package.json index be65cf868..d9c4640e4 100644 --- a/plugins/stored-constraints/package.json +++ b/plugins/stored-constraints/package.json @@ -5,7 +5,7 @@ "description": "A plugin for running stored constraints", "type": "module", "engines": { - "node": ">= 16" + "node": ">= 18" }, "registryMetadata": { "category": "records" @@ -45,9 +45,9 @@ "build:prod": "NODE_ENV=production tsup", "checks": "tsc --noEmit && attw --pack . && publint .", "lint": "tsc --noEmit", - "test": "jest src/*.spec.ts --detectOpenHandles", - "test:unit": "jest src/*.spec.ts --testPathIgnorePatterns=.*\\.e2e\\.spec\\.ts$ --detectOpenHandles", - "test:e2e": "jest src/*.e2e.spec.ts --detectOpenHandles" + "test": "vitest run --mode defaults src/*.spec.ts --passWithNoTests", + "test:unit": "vitest run --mode defaults src/*.spec.ts --passWithNoTests --exclude src/*.e2e.spec.ts", + "test:e2e": "vitest run --mode defaults src/*.e2e.spec.ts --passWithNoTests --no-file-parallelism" }, "keywords": [], "repository": { @@ -65,10 +65,11 @@ "peerDependencies": { "@flatfile/api": "^1.9.19", "@flatfile/listener": "^1.1.0", - "@flatfile/plugin-record-hook": "^1.9.0" + "@flatfile/plugin-record-hook": "^1.10.0" }, "devDependencies": { - "@flatfile/plugin-record-hook": "^1.9.0", - "@flatfile/bundler-config-tsup": "^0.1.0" + "@flatfile/plugin-record-hook": "^1.10.0", + "@flatfile/bundler-config-tsup": "^0.2.0", + "@flatfile/config-vitest": "^0.0.0" } -} \ No newline at end of file +} diff --git a/plugins/stored-constraints/src/stored.constraint.e2e.spec.ts b/plugins/stored-constraints/src/stored.constraint.e2e.spec.ts deleted file mode 100644 index ba3ffdcbc..000000000 --- a/plugins/stored-constraints/src/stored.constraint.e2e.spec.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { Flatfile } from '@flatfile/api' -import { - createRecords, - deleteSpace, - getRecords, - setupListener, - setupSimpleWorkbook, - setupSpace, -} from '@flatfile/utils-testing' -import { storedConstraint } from './stored.constraint' -import * as utils from './utils' - -jest.mock('./utils', () => ({ - ...jest.requireActual('./utils'), - getAppConstraints: jest.fn(), -})) - -describe('storedConstraint()', () => { - const listener = setupListener() - - let spaceId: string - let sheetId: string - - beforeAll(async () => { - const space = await setupSpace() - spaceId = space.id - const workbook = await setupSimpleWorkbook( - space.id, - defaultSimpleValueSchema - ) - sheetId = workbook.sheets![0].id - }) - - beforeEach(() => { - listener.use(storedConstraint); - (utils.getAppConstraints as jest.Mock).mockResolvedValue({ - data: [ - { type: 'stored', validator: 'test', function: 'function constraint() {}' }, - ], - }) - }) - - afterAll(async () => { - await deleteSpace(spaceId) - }) - - it('correctly assigns an error', async () => { - }) - - it('correctly handles thrown errors', async () => { - }) - - it('allows setting of values', async () => { - }) -}) - -export const defaultSimpleValueSchema: Array = [ - { - key: 'name', - type: 'string', - constraints: [ - { type: 'external', validator: 'test', config: { flag: true } }, - ], - }, - { - key: 'age', - type: 'number', - }, -] - -export const defaultSimpleValueData = [ - { - name: 'John Doe', - age: 1, - }, - { - name: 'Jane Doe', - age: 1, - }, -] diff --git a/plugins/stored-constraints/src/utils.ts b/plugins/stored-constraints/src/utils.ts index 8d888f947..ba9cd83bf 100644 --- a/plugins/stored-constraints/src/utils.ts +++ b/plugins/stored-constraints/src/utils.ts @@ -1,8 +1,10 @@ -import api from '@flatfile/api' +import { FlatfileClient } from '@flatfile/api' import * as _ from 'lodash' import type { FlatfileEvent } from '@flatfile/listener' import { Constraint } from './stored.constraint' +const api = new FlatfileClient() + export const getSheet = (event: FlatfileEvent ) => api.sheets.get(event.context.sheetId) export const getFields = ({ data }) => data.config.fields From 33b7a960d7aa24d52b9aee47e5de12e6174ac9a0 Mon Sep 17 00:00:00 2001 From: Merit Date: Wed, 13 Nov 2024 00:16:42 -0800 Subject: [PATCH 3/6] Prettier Signed-off-by: Merit --- .../src/stored.constraint.ts | 45 ++++++++++++------- plugins/stored-constraints/src/utils.ts | 31 ++++++++++--- 2 files changed, 53 insertions(+), 23 deletions(-) diff --git a/plugins/stored-constraints/src/stored.constraint.ts b/plugins/stored-constraints/src/stored.constraint.ts index 093bca6a3..0f8fb946d 100644 --- a/plugins/stored-constraints/src/stored.constraint.ts +++ b/plugins/stored-constraints/src/stored.constraint.ts @@ -15,7 +15,7 @@ import { DateTime } from 'luxon' import type { FlatfileEvent, FlatfileListener } from '@flatfile/listener' import type { FlatfileRecord } from '@flatfile/hooks' -const deps = { validator, countryStateCity, luxon: DateTime, } +const deps = { validator, countryStateCity, luxon: DateTime } export interface Constraint { validator: string @@ -36,22 +36,35 @@ async function getValidators(event: FlatfileEvent): Promise { export function storedConstraint() { return (listener: FlatfileListener) => { listener.use( - bulkRecordHook('**', async (records: FlatfileRecord[], event: FlatfileEvent) => { - const sheet = await getSheet(event) - const storedConstraintFields = getFields(sheet).filter(hasStoredConstraints) - const validators = await getValidators(event) + bulkRecordHook( + '**', + async (records: FlatfileRecord[], event: FlatfileEvent) => { + const sheet = await getSheet(event) + const storedConstraintFields = + getFields(sheet).filter(hasStoredConstraints) + const validators = await getValidators(event) - crossEach([records, storedConstraintFields], (record: FlatfileRecord, field: any) => { - getStoredConstraints(field.constraints).forEach( - async ({ validator }: { validator: string }) => { - const constraint = await getValidator(validators, validator) - if (constraint) { - applyConstraintToRecord(constraint, record, field, deps, sheet) - } - }, + crossEach( + [records, storedConstraintFields], + (record: FlatfileRecord, field: any) => { + getStoredConstraints(field.constraints).forEach( + async ({ validator }: { validator: string }) => { + const constraint = await getValidator(validators, validator) + if (constraint) { + applyConstraintToRecord( + constraint, + record, + field, + deps, + sheet + ) + } + } + ) + } ) - }) - }), + } + ) ) } -} \ No newline at end of file +} diff --git a/plugins/stored-constraints/src/utils.ts b/plugins/stored-constraints/src/utils.ts index ba9cd83bf..090219a83 100644 --- a/plugins/stored-constraints/src/utils.ts +++ b/plugins/stored-constraints/src/utils.ts @@ -1,32 +1,49 @@ import { FlatfileClient } from '@flatfile/api' import * as _ from 'lodash' import type { FlatfileEvent } from '@flatfile/listener' -import { Constraint } from './stored.constraint' +import { Constraint } from './stored.constraint' const api = new FlatfileClient() -export const getSheet = (event: FlatfileEvent ) => api.sheets.get(event.context.sheetId) +export const getSheet = (event: FlatfileEvent) => + api.sheets.get(event.context.sheetId) export const getFields = ({ data }) => data.config.fields -export const getStoredConstraints = (constraints: Constraint[]) => constraints?.filter((c) => c.type == 'stored') +export const getStoredConstraints = (constraints: Constraint[]) => + constraints?.filter((c) => c.type == 'stored') export const hasStoredConstraints = (field) => getStoredConstraints(field.constraints || []).length > 0 export const getValidator = (v, n) => v.find((w) => w.validator === n) -export const applyConstraintToRecord = (constraint: Constraint, record, field, deps, sheet) => { - const storedConstraint = field.constraints?.find((fieldConstraint) => fieldConstraint.validator === constraint.validator) +export const applyConstraintToRecord = ( + constraint: Constraint, + record, + field, + deps, + sheet +) => { + const storedConstraint = field.constraints?.find( + (fieldConstraint) => fieldConstraint.validator === constraint.validator + ) const { config = {} } = storedConstraint || {} const constraintFn = constraint.function.startsWith('function') ? constraint.function.includes('function constraint') ? eval( - '(' + constraint.function.replace('function constraint', 'function') + ')', + '(' + + constraint.function.replace('function constraint', 'function') + + ')' ) : eval('(' + constraint.function + ')') : eval(constraint.function) - constraintFn(record.get(field.key), field.key, { config, record, deps, sheet }) + constraintFn(record.get(field.key), field.key, { + config, + record, + deps, + sheet, + }) } export const crossProduct = (...a) => a.reduce((u, c) => _.flatMap(u, (a) => c.map((b) => a.concat(b))), [[]]) From 2e43c07fca75da7914b978a595b712021c72a0b8 Mon Sep 17 00:00:00 2001 From: Carl Brugger Date: Mon, 25 Nov 2024 09:20:07 -0600 Subject: [PATCH 4/6] jest -> vitest & README update --- package-lock.json | 2 +- plugins/stored-constraints/README.md | 26 +++++++++++++++++---- plugins/stored-constraints/jest.config.cjs | 16 ------------- plugins/stored-constraints/package.json | 2 +- plugins/stored-constraints/vitest.config.ts | 4 ++++ 5 files changed, 28 insertions(+), 22 deletions(-) delete mode 100644 plugins/stored-constraints/jest.config.cjs create mode 100644 plugins/stored-constraints/vitest.config.ts diff --git a/package-lock.json b/package-lock.json index e59640a8f..a5a52af86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17911,7 +17911,7 @@ "node": ">= 18" }, "peerDependencies": { - "@flatfile/api": "^1.9.19", + "@flatfile/api": "^1.11.0", "@flatfile/listener": "^1.1.0", "@flatfile/plugin-record-hook": "^1.10.0" } diff --git a/plugins/stored-constraints/README.md b/plugins/stored-constraints/README.md index 8eae3bec6..a4203cdd2 100644 --- a/plugins/stored-constraints/README.md +++ b/plugins/stored-constraints/README.md @@ -1,9 +1,27 @@ -# @flatfile/plugin-constraints + -This plugin enabled running stored constraints. +# @flatfile/plugin-stored-constraints + +The `@flatfile/plugin-stored-constraints` plugin enables running stored constraints. + +**Event Type:** +`listener.on('commit:created')` + + + +## Installation + +```bash install +npm i @flatfile/plugin-stored-constraints +``` ## Usage -```ts +```ts listener.ts +import type { FlatfileListener } from "@flatfile/listener"; +import { storedConstraint } from '@flatfile/plugin-stored-constraints' + +export default function (listener: FlatfileListener) { listener.use(storedConstraint()) -``` \ No newline at end of file +} +``` diff --git a/plugins/stored-constraints/jest.config.cjs b/plugins/stored-constraints/jest.config.cjs deleted file mode 100644 index e6d7ca40b..000000000 --- a/plugins/stored-constraints/jest.config.cjs +++ /dev/null @@ -1,16 +0,0 @@ -module.exports = { - testEnvironment: 'node', - - transform: { - '^.+\\.tsx?$': 'ts-jest', - }, - setupFiles: ['../../test/dotenv-config.js'], - setupFilesAfterEnv: [ - '../../test/betterConsoleLog.js', - '../../test/unit.cleanup.js', - ], - testTimeout: 60_000, - globalSetup: '../../test/setup-global.js', - forceExit: true, - passWithNoTests: true, -} diff --git a/plugins/stored-constraints/package.json b/plugins/stored-constraints/package.json index d9c4640e4..2f2f53e56 100644 --- a/plugins/stored-constraints/package.json +++ b/plugins/stored-constraints/package.json @@ -63,7 +63,7 @@ "validator": "^13.12.0" }, "peerDependencies": { - "@flatfile/api": "^1.9.19", + "@flatfile/api": "^1.11.0", "@flatfile/listener": "^1.1.0", "@flatfile/plugin-record-hook": "^1.10.0" }, diff --git a/plugins/stored-constraints/vitest.config.ts b/plugins/stored-constraints/vitest.config.ts new file mode 100644 index 000000000..e9e1729f9 --- /dev/null +++ b/plugins/stored-constraints/vitest.config.ts @@ -0,0 +1,4 @@ +import config from '@flatfile/config-vitest' +import { defineConfig } from 'vitest/config' + +export default defineConfig(config) From 67a18fbaece0c9010a7fa4cdf15725a094fa3f8e Mon Sep 17 00:00:00 2001 From: Carl Brugger Date: Mon, 25 Nov 2024 09:39:14 -0600 Subject: [PATCH 5/6] Mostly types & replace lodash w/ vanilla JS flatMap --- package-lock.json | 5 -- plugins/stored-constraints/package.json | 1 - .../src/stored.constraint.ts | 63 ++++++++----------- plugins/stored-constraints/src/utils.ts | 50 +++++++++------ 4 files changed, 58 insertions(+), 61 deletions(-) diff --git a/package-lock.json b/package-lock.json index a5a52af86..edc4cd06f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12156,10 +12156,6 @@ "node": ">=8" } }, - "node_modules/lodash": { - "version": "4.17.21", - "license": "MIT" - }, "node_modules/lodash.camelcase": { "version": "4.3.0", "license": "MIT" @@ -17898,7 +17894,6 @@ "license": "ISC", "dependencies": { "country-state-city": "^3.2.1", - "lodash": "^4.17.21", "luxon": "^3.5.0", "validator": "^13.12.0" }, diff --git a/plugins/stored-constraints/package.json b/plugins/stored-constraints/package.json index 2f2f53e56..9cc003d70 100644 --- a/plugins/stored-constraints/package.json +++ b/plugins/stored-constraints/package.json @@ -58,7 +58,6 @@ "license": "ISC", "dependencies": { "country-state-city": "^3.2.1", - "lodash": "^4.17.21", "luxon": "^3.5.0", "validator": "^13.12.0" }, diff --git a/plugins/stored-constraints/src/stored.constraint.ts b/plugins/stored-constraints/src/stored.constraint.ts index 0f8fb946d..429b3eb43 100644 --- a/plugins/stored-constraints/src/stored.constraint.ts +++ b/plugins/stored-constraints/src/stored.constraint.ts @@ -1,19 +1,20 @@ +import type { Flatfile } from '@flatfile/api' +import type { FlatfileRecord } from '@flatfile/hooks' +import type { FlatfileEvent } from '@flatfile/listener' import { bulkRecordHook } from '@flatfile/plugin-record-hook' +import * as countryStateCity from 'country-state-city' +import { DateTime } from 'luxon' +import validator from 'validator' import { applyConstraintToRecord, crossEach, - getStoredConstraints, + getAppConstraints, getFields, getSheet, + getStoredConstraints, getValidator, hasStoredConstraints, - getAppConstraints, } from './utils' -import validator from 'validator' -import * as countryStateCity from 'country-state-city' -import { DateTime } from 'luxon' -import type { FlatfileEvent, FlatfileListener } from '@flatfile/listener' -import type { FlatfileRecord } from '@flatfile/hooks' const deps = { validator, countryStateCity, luxon: DateTime } @@ -25,7 +26,7 @@ export interface Constraint { async function getValidators(event: FlatfileEvent): Promise { const constraints = await getAppConstraints(event.context.appId) - return constraints.data.map((c: any) => { + return constraints.data.map((c: Flatfile.ConstraintResource) => { return { validator: c.validator, function: c.function, @@ -34,37 +35,27 @@ async function getValidators(event: FlatfileEvent): Promise { } export function storedConstraint() { - return (listener: FlatfileListener) => { - listener.use( - bulkRecordHook( - '**', - async (records: FlatfileRecord[], event: FlatfileEvent) => { - const sheet = await getSheet(event) - const storedConstraintFields = - getFields(sheet).filter(hasStoredConstraints) - const validators = await getValidators(event) + return bulkRecordHook( + '**', + async (records: FlatfileRecord[], event: FlatfileEvent) => { + const sheet: Flatfile.SheetResponse = await getSheet(event) + const storedConstraintFields = + getFields(sheet).filter(hasStoredConstraints) + const validators = await getValidators(event) - crossEach( - [records, storedConstraintFields], - (record: FlatfileRecord, field: any) => { - getStoredConstraints(field.constraints).forEach( - async ({ validator }: { validator: string }) => { - const constraint = await getValidator(validators, validator) - if (constraint) { - applyConstraintToRecord( - constraint, - record, - field, - deps, - sheet - ) - } - } - ) + crossEach( + [records, storedConstraintFields], + (record: FlatfileRecord, field: Flatfile.Property) => { + getStoredConstraints(field.constraints).forEach( + async ({ validator }: { validator: string }) => { + const constraint = await getValidator(validators, validator) + if (constraint) { + applyConstraintToRecord(constraint, record, field, deps, sheet) + } } ) } ) - ) - } + } + ) } diff --git a/plugins/stored-constraints/src/utils.ts b/plugins/stored-constraints/src/utils.ts index 090219a83..90ea5c325 100644 --- a/plugins/stored-constraints/src/utils.ts +++ b/plugins/stored-constraints/src/utils.ts @@ -1,30 +1,39 @@ -import { FlatfileClient } from '@flatfile/api' -import * as _ from 'lodash' +import { type Flatfile, FlatfileClient } from '@flatfile/api' import type { FlatfileEvent } from '@flatfile/listener' +import type { FlatfileRecord } from '@flatfile/plugin-record-hook' import { Constraint } from './stored.constraint' const api = new FlatfileClient() -export const getSheet = (event: FlatfileEvent) => - api.sheets.get(event.context.sheetId) +export const getSheet = async ( + event: FlatfileEvent +): Promise => + await api.sheets.get(event.context.sheetId) -export const getFields = ({ data }) => data.config.fields +export const getFields = ({ data }: { data: Flatfile.Sheet }) => + data.config.fields -export const getStoredConstraints = (constraints: Constraint[]) => - constraints?.filter((c) => c.type == 'stored') +export const getStoredConstraints = ( + constraints: Flatfile.Constraint[] +): Flatfile.StoredConstraint[] => + constraints?.filter((c) => c.type === 'stored') -export const hasStoredConstraints = (field) => +export const hasStoredConstraints = (field: Flatfile.Property) => getStoredConstraints(field.constraints || []).length > 0 -export const getValidator = (v, n) => v.find((w) => w.validator === n) +export const getValidator = ( + v: Constraint[], + n: string +): Constraint | undefined => v.find((w) => w.validator === n) + export const applyConstraintToRecord = ( constraint: Constraint, - record, - field, - deps, - sheet + record: FlatfileRecord, + field: Flatfile.Property, + deps: any, + sheet: Flatfile.SheetResponse ) => { - const storedConstraint = field.constraints?.find( + const storedConstraint = getStoredConstraints(field.constraints || []).find( (fieldConstraint) => fieldConstraint.validator === constraint.validator ) const { config = {} } = storedConstraint || {} @@ -45,8 +54,11 @@ export const applyConstraintToRecord = ( sheet, }) } -export const crossProduct = (...a) => - a.reduce((u, c) => _.flatMap(u, (a) => c.map((b) => a.concat(b))), [[]]) -export const crossEach = (a, cb) => crossProduct(...a).forEach((p) => cb(...p)) -export const getAppConstraints = (a) => - api.apps.getConstraints(a, { includeBuiltins: true }) +export const crossProduct = (...a: T[][]): T[][] => + a.reduce((u, c) => u.flatMap((x) => c.map((b) => [...x, b])), [[]]) +export const crossEach = (a: T[][], cb: (...args: T[]) => void): void => + crossProduct(...a).forEach((p) => cb(...p)) +export const getAppConstraints = async ( + a: Flatfile.AppId +): Promise => + await api.apps.getConstraints(a, { includeBuiltins: true }) From a37d295f20f03e2b1bf57b485f99cc0c39d2d2a8 Mon Sep 17 00:00:00 2001 From: Merit Date: Mon, 2 Dec 2024 08:48:13 -0800 Subject: [PATCH 6/6] Changeset --- .changeset/witty-books-turn.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/witty-books-turn.md diff --git a/.changeset/witty-books-turn.md b/.changeset/witty-books-turn.md new file mode 100644 index 000000000..d9791c7d9 --- /dev/null +++ b/.changeset/witty-books-turn.md @@ -0,0 +1,5 @@ +--- +'@flatfile/plugin-stored-constraints': patch +--- + +Runs the stored constraints for an app