Skip to content

Commit

Permalink
feat: v0.18.0 (#356)
Browse files Browse the repository at this point in the history
## Testing
* test: update jest config to ts file
* test(global-config): ensure values not bleeding across tests
* test: set up multiple test environments
* test(collection): add shuffle test
* test(api-calls): add connect, trace and options test
* test(attributes): clarify variable name
* test(helpers): update test name

## Continuous Integration
* ci: separate testing for environments
* ci: move dependency install step into later jobs
* ci: use checkout in later jobs too
* ci: use npx to run jest
* ci: bump node version

## Features
* feat(collection): added `is` method
* feat(attributes): create simple access to accessors
* feat(api-calls): add options trace and connect request options
* feat: add an experimental Getter type

## Documentation
* docs: improve documentation accuracy
* docs(helpers): add documentation for dataGet
* docs: grammar and spacing fixes
* docs: add word clarification
* docs(attributes): document magic access

## Revert
* revert(collection): change introduced in 8d8023a

## Refactor:
* refactor: use `in` operator as opposed to hasOwnProperty function
  * This is compatible with objects without a prototype to inherit from
  However, this checks the property from the prototype chain
* refactor(collection): improve readability of unique function
* refactor: improve the type `Data`

## Performance
* perf(attributes): iterate objects using for...of on Object.entries
  * This has a small performance improvement when it comes to using it with large objects
* perf(attributes): use an object with no inheritance for original&attributes
  * This provides a slight performance improvement when it comes to memory
* perf: remove lodash in favour of specific lodash modules

## Chores
* chore: update dependencies
* chore: bump version
* chore: add exports for node environments

## Fix
* fix(helpers): remove circular dependency between a collection and dataGet
  * This also means no longer importing the collection when just using dateGet
  • Loading branch information
nandi95 authored Jul 28, 2023
1 parent cde0411 commit 49b6ae5
Show file tree
Hide file tree
Showing 34 changed files with 2,523 additions and 1,497 deletions.
38 changes: 29 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,44 @@ on:
- '**/tsconfig.json'
- 'tests/**/*.ts'
- 'src/**/*.ts'
- 'jest.config.js'
- 'jest.config.ts'
- 'package-lock.json'
branches:
- main
- 'release/*'

jobs:
jest:
timeout-minutes: 10
setup-test:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
strategy:
matrix:
# current and active LTS
node: [ 18, 19 ]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
steps:
# subsequent calls to this action will use already downloaded code from the workspace
- uses: actions/checkout@v3
jest-browser:
needs: setup-test
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
cache: 'npm'
node-version: 'latest'
- name: Install dependencies
run: npm ci --ignore-scripts
- name: Run tests on jsdom
run: npx jest --selectProjects=jsdom
jest-node:
needs: setup-test
timeout-minutes: 10
runs-on: ubuntu-latest
strategy:
matrix:
# current and active LTS
node: [ 18, 20 ]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
Expand All @@ -33,5 +53,5 @@ jobs:
node-version: ${{ matrix.node }}
- name: Install dependencies
run: npm ci --ignore-scripts
- name: Run tests
run: npm run test
- name: Run tests on node v${{ matrix.node }}
run: npx jest --selectProjects=node
18 changes: 17 additions & 1 deletion docs/calliope/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ user.setGuarded(['dob']).isGuarded('dob'); // false
Besides [casts](#casting) there is also an alternative method to transform your values on the fly. You can define accessors which are called when you're accessing an attribute and mutators which are called when you're setting a value. You can define them like the following example

```js
User.js
// User.js
import { Model } from '@upfrontjs/framework';

export default class User extends Model {
Expand Down Expand Up @@ -334,6 +334,22 @@ const user = User.make({ title: 'Dr.', fullName: 'John Doe' });
user.fullName; // 'Dr. John Doe'
```

Furthermore, when defining an accessor that has had no value set previously, the value will be made available on the model as a property. This means that you can access the value like so:

```js
// User.js
import { Model } from '@upfrontjs/framework';

export default class User extends Model {
getFullNameAttribute(name) {
return name ?? 'no name set';
}
}

const user = User.make();
user.fullName; // 'no name set'
```
## Attribute management
#### attributeCasing
Expand Down
10 changes: 5 additions & 5 deletions docs/cookbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,11 @@ const experiencedSafeDrivers = await User
With the default [ApiCaller](./services#apicaller) and [HandlesApiRequest](./services#handlesapiresponse) implementations, you can interact with your api without the need for the models or any of their methods.

```js
import { GlobalConfig, API, ApiResponseHandler } from '@upfrontjs/framework';
import { GlobalConfig } from '@upfrontjs/framework';

const config = new GlobalConfig;
const handler = new ApiResponseHandler;
const api = new API;
const handler = new (config.get('apiResponseHandler'));
const api = new new (config.get('api'));

const form = new FormData;
// ... collect the form entries
Expand Down Expand Up @@ -322,11 +322,11 @@ interface PaginatedApiResponse<T = Attributes> {
/**
* From all the existing records this is where the current items start from.
*/
from: number;
from: number | null;
/**
* From all the existing records this is where the current items go to.
*/
to: number;
to: number | null;
last_page: number;
/**
* String representation of a number.
Expand Down
4 changes: 3 additions & 1 deletion docs/getting-started/readme.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Introduction

## What is it?
It's a model centric data handling solution. With it, you can write expressive, readable, concise syntax that you already know and love from back-end MVC frameworks while staying back-end agnostic. It provides an elegant structure to complex data with a plethora of features to manage the data, and additional helpers for common tasks like handling lists, pagination, string manipulation etc.
It's a model centric data handling solution. With it, you can write expressive, readable, concise syntax that you already know and love from back-end MVC frameworks while staying back-end agnostic. It provides an elegant interface to complex data with a plethora of features to access it, and additional helpers for common tasks like handling lists, pagination, string manipulation and more.

```ts
import User from '@models/User';
Expand All @@ -13,6 +13,8 @@ const excellentStudentNames = students
.pluck('name');
```

Furthermore, the package can be run in multiple environments, including node, react-native, electron etc.

## What does it solve?
There are number of solutions out there for fetching data and working with the response. However not all of these might be as maintainable as one would hope. With state management, you might have a getter for users, but those users include all users, meaning for a custom collection you would need a new getter method. An on-demand ajax request written specifically to solve a single issue, while it is simple to do, it quickly gets repetitive and laborious to maintain. [Upfront](./installation.md) solves the above by creating abstraction over the data in a unified api. Just like the above examples it can be used to fetch data on demand or be complimentary to state management libraries.

Expand Down
14 changes: 14 additions & 0 deletions docs/helpers/collection.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,20 @@ numCollection.shuffle(); // Collection[3, 1, 4, 2, 5]
numCollection.shuffle(); // Collection[2, 5, 1, 4, 3]
```

#### is

The `is` method determines if the collection is equal to the given collection. It uses deep equality to ensure every element in the collection is equal to the given collection in the same order.

```js
import { Collection } from '@upfrontjs/framework';

const collection = new Collection([1, 2, 3, 4, 5]);
collection.is(collection); // true
collection.is(new Collection([1, 2, 3, 4, 5])); // true
collection.is(collection.slice(0, 1)); // false
(new Collection([{ id: 1 }])).is(new Collection([{ id: 2 }])); // false
```

#### isEmpty

The `isEmpty` method determines whether the collection is empty or not.
Expand Down
25 changes: 25 additions & 0 deletions docs/helpers/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -448,3 +448,28 @@ value(
1
); // 2
```

#### dataGet

The `dataGet` is a helper method to safely access any path within an object or array. If the path does not exist, it will return the default value (default: `undefined`). Optionally the path may include a wildcard `*` to match array elements.

```ts
import { dataGet } from '@upfrontjs/framework';

const complexStructure = [
{
users: [
{
shifts: [
{ id: 1 }
]
}
]
}
];

dataGet(complexStructure, '0.users.0.shifts.0.id'); // 1
dataGet(complexStructure, '0.users.0.shifts.*.id'); // [1]
dataGet(complexStructure, '0.users.0.shifts.0.name'); // undefined
dataGet(complexStructure, '0.users.0.shifts.0.name', 'default name'); // 'default name'
```
8 changes: 4 additions & 4 deletions docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
home: true
heroImage: https://raw.githubusercontent.com/upfrontjs/design/main/upfrontjs.png
heroText:
tagline: Data handling framework complementary to backend active record systems.
tagline: Isomorphic data handling framework complementary to active record systems.
actions:
- text: Get Started →
link: /getting-started/
type: primary
features:
- title: Simple
details: The syntax naturally lends itself to how you manage data in MVC architecture.
- title: Scalable
details: Typescript and object-oriented patterns ensures no matter how big project you drop this in, it's going to work.
details: The syntax naturally lends itself to how you work with objects from ORM frameworks.
- title: Robust
details: Typescript and vast amount of testing and ensures no matter how big project you drop this in, it's going to work.
- title: Customisable
details: Written with the developer in mind. While keeping it simple, you're not locked into any patterns.
footer: MIT Licensed | Copyright © 2020-present Nandor Kraszlan
Expand Down
30 changes: 0 additions & 30 deletions jest.config.js

This file was deleted.

48 changes: 48 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { JestConfigWithTsJest } from 'ts-jest';

const commonProjectConfig: Exclude<JestConfigWithTsJest['projects'], undefined>[number] = {
transform: {
// eslint-disable-next-line @typescript-eslint/naming-convention
'^.+\\.[t]sx?$': 'ts-jest'
},
setupFilesAfterEnv: ['<rootDir>/tests/setupTests.ts']
};

const config: JestConfigWithTsJest = {
preset: 'ts-jest',
rootDir: './',
projects: [
{
...commonProjectConfig,
displayName: 'jsdom',
testEnvironment: 'jsdom'
},
{
...commonProjectConfig,
displayName: 'node',
testEnvironment: 'node'
}
],
collectCoverageFrom: [
'<rootDir>/src/**/*.ts',
'!<rootDir>/src/index.ts'
],
cacheDirectory: '<rootDir>/tests/cache',
coverageDirectory: '<rootDir>/tests/coverage',
coverageProvider: 'babel',
coverageReporters: ['json', 'text'],
testMatch: [
'<rootDir>tests/**/*(*.)@(test).[tj]s?(x)'
],
errorOnDeprecated: true,
bail: true,
sandboxInjectedGlobals: [
'Function',
'Array',
'String',
'Math',
'Date'
]
};

export default config;
Loading

0 comments on commit 49b6ae5

Please sign in to comment.