Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new automation package #726

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
327964e
feat: add automation package building blocks, including state machine…
FedericoAmura Nov 27, 2024
40bbafe
feat: add ids in state machines
FedericoAmura Nov 27, 2024
1f76cc6
feat: state machine creation using declarative config
FedericoAmura Nov 29, 2024
720cce3
feat: native and erc20 balances monitoring transitions declarative in…
FedericoAmura Dec 3, 2024
be62dbc
feat: add lit action run state machine capability as a state
FedericoAmura Dec 3, 2024
47cc68a
feat: give state machine ability to sign and send transactions and to…
FedericoAmura Dec 5, 2024
4b39424
feat: add machine PKP and private key handling and refactor LitAction…
FedericoAmura Dec 6, 2024
6a1b481
feat: include transitions definitions inside states definitions
FedericoAmura Dec 6, 2024
3e37a56
fix: missing change, transitions property in StateDefinition
FedericoAmura Dec 6, 2024
751b1dd
feat: refactor types
FedericoAmura Dec 6, 2024
dcf0f07
Merge branch 'refs/heads/master' into feature/lit-4031-new-listener-sdk
FedericoAmura Dec 9, 2024
06a6da5
feat: add state machine context to dynamically share information betw…
FedericoAmura Dec 11, 2024
3889f78
feat: generalize context usage to states variables
FedericoAmura Dec 12, 2024
c4786f9
feat: types improvement
FedericoAmura Dec 12, 2024
7598448
fix: queue events in transition to avoid multiple racing calls to che…
FedericoAmura Dec 12, 2024
3e12d72
fix: remove unnecessary context functions in machine
FedericoAmura Dec 12, 2024
2c26270
feat: error handling while in automation execution
FedericoAmura Dec 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"@nx/web": "17.3.0",
"@solana/web3.js": "1.95.3",
"@types/depd": "^1.1.36",
"@types/events": "^3.0.3",
"@types/jest": "27.4.1",
"@types/node": "18.19.18",
"@types/secp256k1": "^4.0.6",
Expand Down
10 changes: 10 additions & 0 deletions packages/automation/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"presets": [
[
"@nx/web/babel",
{
"useBuiltIns": "usage"
}
]
]
}
18 changes: 18 additions & 0 deletions packages/automation/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
3 changes: 3 additions & 0 deletions packages/automation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Quick Start

This submodule is used to automate different actions using the Lit Protocol network or other useful events providing listening and responding abilities based on state machines.
16 changes: 16 additions & 0 deletions packages/automation/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* eslint-disable */
export default {
displayName: 'types',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
transform: {
'^.+\\.[t]s$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/packages/automation',
setupFilesAfterEnv: ['../../jest.setup.js'],
};
32 changes: 32 additions & 0 deletions packages/automation/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@lit-protocol/automation",
"type": "commonjs",
"license": "MIT",
"homepage": "https://github.com/Lit-Protocol/js-sdk",
"repository": {
"type": "git",
"url": "https://github.com/LIT-Protocol/js-sdk"
},
"keywords": [
"library"
],
"bugs": {
"url": "https://github.com/LIT-Protocol/js-sdk/issues"
},
"publishConfig": {
"access": "public",
"directory": "../../dist/packages/automation"
},
"tags": [
"universal"
],
"buildOptions": {
"genReact": false
},
"scripts": {
"generate-lit-actions": "yarn node ./esbuild.config.js"
},
"version": "7.0.2",
"main": "./dist/src/index.js",
"typings": "./dist/src/index.d.ts"
}
37 changes: 37 additions & 0 deletions packages/automation/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "automation",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/automation/src",
"projectType": "library",
"targets": {
"build": {
"cache": false,
"executor": "@nx/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/packages/automation",
"main": "packages/automation/src/index.ts",
"tsConfig": "packages/automation/tsconfig.lib.json",
"assets": ["packages/automation/*.md"],
"updateBuildableProjectDepsInPackageJson": true
},
"dependsOn": ["^build"]
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["packages/automation/**/*.ts"]
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/packages/automation"],
"options": {
"jestConfig": "packages/automation/jest.config.ts",
"passWithNoTests": true
}
}
},
"tags": []
}
4 changes: 4 additions & 0 deletions packages/automation/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './lib/listeners';
export * from './lib/states';
export * from './lib/transitions';
export * from './lib/state-machine';
1 change: 1 addition & 0 deletions packages/automation/src/lib/context/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './machine-context';
186 changes: 186 additions & 0 deletions packages/automation/src/lib/context/machine-context.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { MachineContext } from './machine-context';

const deepCopy = <T>(obj: T): T => JSON.parse(JSON.stringify(obj));

describe('MachineContext', () => {
let context: MachineContext;
const initialContext = {
contracts: {
token: '0x123...',
},
values: {
amount: 100,
},
existingArray: [1, 2, 3],
};

beforeEach(() => {
context = new MachineContext(deepCopy(initialContext));
});

it('should initialize with provided context', () => {
expect(context.get()).toEqual(initialContext);
});

it('should initialize empty when no context provided', () => {
const emptyContext = new MachineContext();
expect(emptyContext.get()).toEqual({});
});

it('should get context values using dot notation', () => {
expect(context.get('contracts.token')).toBe('0x123...');
expect(context.get('values.amount')).toBe(100);
});

it('should get context values using array notation', () => {
expect(context.get(['contracts', 'token'])).toBe('0x123...');
expect(context.get(['values', 'amount'])).toBe(100);
});

it('should set context values using dot notation', () => {
context.set('new.value', 42);
expect(context.get('new.value')).toBe(42);
});

it('should set context values using array notation', () => {
context.set(['deeply', 'nested', 'value'], 'test');
expect(context.get('deeply.nested.value')).toBe('test');
});

it('should handle missing context paths gracefully', () => {
expect(context.get('non.existent.path')).toBeUndefined();
});

it('should create intermediate objects when setting deep paths', () => {
context.set('a.b.c', 'value');
expect(context.get()).toEqual(
expect.objectContaining({
a: {
b: {
c: 'value',
},
},
})
);
});

it('should override existing values', () => {
context.set('contracts.token', '0xnew...');
expect(context.get('contracts.token')).toBe('0xnew...');
});

it('should create new array when path does not exist', () => {
context.push('newArray', 1);
expect(context.get('newArray')).toEqual([1]);
});

it('should push to existing array', () => {
context.push('existingArray', 4);
expect(context.get('existingArray')).toEqual([1, 2, 3, 4]);
});

it('should convert non-array value to array when pushing', () => {
context.push('contracts.token', '0xnew...');
expect(context.get('contracts.token')).toEqual(['0x123...', '0xnew...']);
});

it('should work with array notation', () => {
context.push(['deeply', 'nested', 'array'], 'value');
expect(context.get('deeply.nested.array')).toEqual(['value']);
});

it('should maintain array reference when pushing', () => {
const before = context.get('existingArray');
context.push('existingArray', 4);
const after = context.get('existingArray');
expect(before).toBe(after); // Same array reference
});

it('should handle pushing multiple values', () => {
context.push('newArray', 1);
context.push('newArray', 2);
context.push('newArray', 3);
expect(context.get('newArray')).toEqual([1, 2, 3]);
});

it('should handle pushing to nested paths', () => {
context.push('nested.path.to.array', 'first');
context.push('nested.path.to.array', 'second');
expect(context.get('nested.path.to.array')).toEqual(['first', 'second']);
});

it('should convert non-array values in nested paths', () => {
context.set('deep.nested.value', 'original');
context.push('deep.nested.value', 'new');
expect(context.get('deep.nested.value')).toEqual(['original', 'new']);
});

describe('array indexing', () => {
beforeEach(() => {
context = new MachineContext(
deepCopy({
simple: ['a', 'b', 'c'],
complex: [
{ id: 1, value: { foo: 'bar' } },
{ id: 2, value: { foo: 'baz' } },
],
nested: {
arrays: [
[1, 2],
[3, 4],
],
},
})
);
});

it('should access array elements using index notation', () => {
expect(context.get('simple[1]')).toBe('b');
expect(context.get('complex[0].id')).toBe(1);
expect(context.get('complex[0].value.foo')).toBe('bar');
});

it('should access nested array elements', () => {
expect(context.get('nested.arrays[1][0]')).toBe(3);
});

it('should set array elements using index notation', () => {
context.set('simple[1]', 'x');
expect(context.get('simple')).toEqual(['a', 'x', 'c']);
});

it('should set nested array elements', () => {
context.set('complex[1].value.foo', 'qux');
expect(context.get('complex[1].value.foo')).toBe('qux');
});

it('should create arrays when setting with index notation', () => {
context.set('new[2].foo', 'bar');
expect(context.get('new')).toEqual([
undefined,
undefined,
{ foo: 'bar' },
]);
});

it('should handle array notation with dot notation mixed', () => {
context.set('mixed.array[0].nested.value[1]', 42);
expect(context.get('mixed.array[0].nested.value[1]')).toBe(42);
});

it('should work with array paths', () => {
expect(context.get(['complex', '0', 'value', 'foo'])).toBe('bar');
});

it('should push to arrays accessed via index notation', () => {
context.push('nested.arrays[0]', 3);
expect(context.get('nested.arrays[0]')).toEqual([1, 2, 3]);
});

it('should handle out of bounds indices by filling with empty objects', () => {
context.set('sparse[5].value', 'test');
expect((context.get('sparse') as any[]).length).toBe(6);
expect(context.get('sparse[5].value')).toBe('test');
});
});
});
Loading
Loading