Skip to content

Commit dbfdbaf

Browse files
feat: introduce @ngrx/data library to the platform (#1754)
1 parent c56ea14 commit dbfdbaf

File tree

98 files changed

+18943
-1
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

98 files changed

+18943
-1
lines changed

.circleci/bazel.rc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ build --experimental_strict_action_env
1919
# Bazel doesn't calculate the memory ceiling correctly when running under Docker.
2020
# Limit Bazel to consuming resources that fit in CircleCI "medium" class which is the default:
2121
# https://circleci.com/docs/2.0/configuration-reference/#resource_class
22-
build --jobs 3 --local_resources=2506,2.0,1.0
22+
build --jobs 3 --local_resources=3072,2.0,1.0
2323

2424
# Also limit Bazel's own JVM heap to stay within our 4G container limit
2525
startup --host_jvm_args=-Xmx1g

modules/data/BUILD

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package(default_visibility = ["//visibility:public"])
2+
3+
load("//tools:defaults.bzl", "ng_module", "ng_package")
4+
5+
ng_module(
6+
name = "data",
7+
srcs = glob([
8+
"*.ts",
9+
"src/**/*.ts",
10+
]),
11+
module_name = "@ngrx/data",
12+
deps = [
13+
"//modules/effects",
14+
"//modules/entity",
15+
"//modules/store",
16+
"@npm//@angular/common",
17+
"@npm//@angular/core",
18+
"@npm//rxjs",
19+
],
20+
)
21+
22+
ng_package(
23+
name = "npm_package",
24+
srcs = glob(["**/*.externs.js"]) + [
25+
"package.json",
26+
],
27+
entry_point = "modules/data/index.js",
28+
packages = [
29+
],
30+
deps = [
31+
":data",
32+
],
33+
)

modules/data/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Change Log
2+
3+
See [CHANGELOG.md](https://github.com/ngrx/platform/blob/master/CHANGELOG.md)

modules/data/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# @ngrx/data
2+
3+
The sources for this package are in the main [NgRx](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo.
4+
5+
License: MIT

modules/data/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* DO NOT EDIT
3+
*
4+
* This file is automatically generated at build
5+
*/
6+
7+
export * from './public_api';

modules/data/package.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "@ngrx/data",
3+
"version": "0.0.0-PLACEHOLDER",
4+
"description": "API management for NgRx",
5+
"repository": {
6+
"type": "git",
7+
"url": "https://github.com/ngrx/platform.git"
8+
},
9+
"keywords": [
10+
"Angular",
11+
"Redux",
12+
"NgRx",
13+
"Schematics",
14+
"Angular CLI"
15+
],
16+
"author": "NgRx",
17+
"license": "MIT",
18+
"bugs": {
19+
"url": "https://github.com/ngrx/platform/issues"
20+
},
21+
"homepage": "https://github.com/ngrx/platform#readme",
22+
"peerDependencies": {
23+
"@angular/common": "NG_VERSION",
24+
"@angular/core": "NG_VERSION",
25+
"@ngrx/store": "0.0.0-PLACEHOLDER",
26+
"@ngrx/effects": "0.0.0-PLACEHOLDER",
27+
"@ngrx/entity": "0.0.0-PLACEHOLDER",
28+
"rxjs": "RXJS_VERSION"
29+
},
30+
"schematics": "MODULE_SCHEMATICS_COLLECTION",
31+
"sideEffects": false
32+
}

modules/data/public_api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './src/index';

modules/data/rollup.config.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export default {
2+
entry: './dist/data/@ngrx/data.es5.js',
3+
dest: './dist/data/bundles/data.umd.js',
4+
format: 'umd',
5+
exports: 'named',
6+
moduleName: 'ngrx.data',
7+
globals: {
8+
'@ngrx/store': 'ngrx.store',
9+
'@ngrx/effects': 'ngrx.effects',
10+
'@ngrx/entity': 'ngrx.entity',
11+
}
12+
}

modules/data/spec/BUILD

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
load("//tools:defaults.bzl", "jasmine_node_test", "ts_test_library")
2+
3+
ts_test_library(
4+
name = "test_lib",
5+
srcs = glob(
6+
[
7+
"**/*.ts",
8+
],
9+
),
10+
deps = [
11+
"//modules/data",
12+
"//modules/effects",
13+
"//modules/effects/testing",
14+
"//modules/entity",
15+
"//modules/store",
16+
"@npm//@angular/common",
17+
"@npm//rxjs",
18+
],
19+
)
20+
21+
jasmine_node_test(
22+
name = "test",
23+
deps = [
24+
":test_lib",
25+
"//modules/data",
26+
"//modules/effects",
27+
"//modules/entity",
28+
"//modules/store",
29+
],
30+
)
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
import {
2+
EntityAction,
3+
EntityActionOptions,
4+
EntityActionPayload,
5+
EntityOp,
6+
EntityActionFactory,
7+
MergeStrategy,
8+
CorrelationIdGenerator,
9+
} from '../../';
10+
11+
class Hero {
12+
id: number;
13+
name: string;
14+
}
15+
16+
describe('EntityActionFactory', () => {
17+
let factory: EntityActionFactory;
18+
19+
beforeEach(() => {
20+
factory = new EntityActionFactory();
21+
});
22+
23+
it('#create should create an EntityAction from entityName and entityOp', () => {
24+
const action = factory.create('Hero', EntityOp.QUERY_ALL);
25+
const { entityName, entityOp, data } = action.payload;
26+
expect(entityName).toBe('Hero');
27+
expect(entityOp).toBe(EntityOp.QUERY_ALL);
28+
expect(data).toBeUndefined('no data property');
29+
});
30+
31+
it('#create should create an EntityAction with the given data', () => {
32+
const hero: Hero = { id: 42, name: 'Francis' };
33+
const action = factory.create('Hero', EntityOp.ADD_ONE, hero);
34+
const { entityName, entityOp, data } = action.payload;
35+
expect(entityName).toBe('Hero');
36+
expect(entityOp).toBe(EntityOp.ADD_ONE);
37+
expect(data).toBe(hero);
38+
});
39+
40+
it('#create should create an EntityAction with options', () => {
41+
const options: EntityActionOptions = {
42+
correlationId: 'CRID42',
43+
isOptimistic: true,
44+
mergeStrategy: MergeStrategy.OverwriteChanges,
45+
tag: 'Foo',
46+
};
47+
48+
// Don't forget placeholder for missing optional data!
49+
const action = factory.create(
50+
'Hero',
51+
EntityOp.QUERY_ALL,
52+
undefined,
53+
options
54+
);
55+
const {
56+
entityName,
57+
entityOp,
58+
data,
59+
correlationId,
60+
isOptimistic,
61+
mergeStrategy,
62+
tag,
63+
} = action.payload;
64+
expect(entityName).toBe('Hero');
65+
expect(entityOp).toBe(EntityOp.QUERY_ALL);
66+
expect(data).toBeUndefined();
67+
expect(correlationId).toBe(options.correlationId);
68+
expect(isOptimistic).toBe(options.isOptimistic);
69+
expect(mergeStrategy).toBe(options.mergeStrategy);
70+
expect(tag).toBe(options.tag);
71+
});
72+
73+
it('#create create an EntityAction from an EntityActionPayload', () => {
74+
const hero: Hero = { id: 42, name: 'Francis' };
75+
const payload: EntityActionPayload = {
76+
entityName: 'Hero',
77+
entityOp: EntityOp.ADD_ONE,
78+
data: hero,
79+
correlationId: 'CRID42',
80+
isOptimistic: true,
81+
mergeStrategy: MergeStrategy.OverwriteChanges,
82+
tag: 'Foo',
83+
};
84+
const action = factory.create(payload);
85+
86+
const {
87+
entityName,
88+
entityOp,
89+
data,
90+
correlationId,
91+
isOptimistic,
92+
mergeStrategy,
93+
tag,
94+
} = action.payload;
95+
expect(entityName).toBe(payload.entityName);
96+
expect(entityOp).toBe(payload.entityOp);
97+
expect(data).toBe(payload.data);
98+
expect(correlationId).toBe(payload.correlationId);
99+
expect(isOptimistic).toBe(payload.isOptimistic);
100+
expect(mergeStrategy).toBe(payload.mergeStrategy);
101+
expect(tag).toBe(payload.tag);
102+
});
103+
104+
it('#createFromAction should create EntityAction from another EntityAction', () => {
105+
// pessimistic save
106+
const hero1: Hero = { id: undefined as any, name: 'Francis' };
107+
const action1 = factory.create('Hero', EntityOp.SAVE_ADD_ONE, hero1);
108+
109+
// after save succeeds
110+
const hero: Hero = { ...hero1, id: 42 };
111+
const action = factory.createFromAction(action1, {
112+
entityOp: EntityOp.SAVE_ADD_ONE_SUCCESS,
113+
data: hero,
114+
});
115+
const { entityName, entityOp, data } = action.payload;
116+
117+
expect(entityName).toBe('Hero');
118+
expect(entityOp).toBe(EntityOp.SAVE_ADD_ONE_SUCCESS);
119+
expect(data).toBe(hero);
120+
const expectedType = factory.formatActionType(
121+
EntityOp.SAVE_ADD_ONE_SUCCESS,
122+
'Hero'
123+
);
124+
expect(action.type).toEqual(expectedType);
125+
});
126+
127+
it('#createFromAction should copy the options from the source action', () => {
128+
const options: EntityActionOptions = {
129+
correlationId: 'CRID42',
130+
isOptimistic: true,
131+
mergeStrategy: MergeStrategy.OverwriteChanges,
132+
tag: 'Foo',
133+
};
134+
// Don't forget placeholder for missing optional data!
135+
const sourceAction = factory.create(
136+
'Hero',
137+
EntityOp.QUERY_ALL,
138+
undefined,
139+
options
140+
);
141+
142+
const queryResults: Hero[] = [
143+
{ id: 1, name: 'Francis' },
144+
{ id: 2, name: 'Alex' },
145+
];
146+
const action = factory.createFromAction(sourceAction, {
147+
entityOp: EntityOp.QUERY_ALL_SUCCESS,
148+
data: queryResults,
149+
});
150+
151+
const {
152+
entityName,
153+
entityOp,
154+
data,
155+
correlationId,
156+
isOptimistic,
157+
mergeStrategy,
158+
tag,
159+
} = action.payload;
160+
expect(entityName).toBe('Hero');
161+
expect(entityOp).toBe(EntityOp.QUERY_ALL_SUCCESS);
162+
expect(data).toBe(queryResults);
163+
expect(correlationId).toBe(options.correlationId);
164+
expect(isOptimistic).toBe(options.isOptimistic);
165+
expect(mergeStrategy).toBe(options.mergeStrategy);
166+
expect(tag).toBe(options.tag);
167+
});
168+
169+
it('#createFromAction can suppress the data property', () => {
170+
const hero: Hero = { id: 42, name: 'Francis' };
171+
const action1 = factory.create('Hero', EntityOp.ADD_ONE, hero);
172+
const action = factory.createFromAction(action1, {
173+
entityOp: EntityOp.SAVE_ADD_ONE,
174+
data: undefined,
175+
});
176+
const { entityName, entityOp, data } = action.payload;
177+
expect(entityName).toBe('Hero');
178+
expect(entityOp).toBe(EntityOp.SAVE_ADD_ONE);
179+
expect(data).toBeUndefined();
180+
});
181+
182+
it('#formatActionType should format type with the entityName', () => {
183+
const action = factory.create('Hero', EntityOp.QUERY_ALL);
184+
const expectedFormat = factory.formatActionType(EntityOp.QUERY_ALL, 'Hero');
185+
expect(action.type).toBe(expectedFormat);
186+
});
187+
188+
it('#formatActionType should format type with given tag instead of the entity name', () => {
189+
const tag = 'Hero - Tag Test';
190+
const action = factory.create('Hero', EntityOp.QUERY_ALL, null, { tag });
191+
expect(action.type).toContain(tag);
192+
});
193+
194+
it('can re-format generated action.type with a custom #formatActionType()', () => {
195+
factory.formatActionType = (op, entityName) =>
196+
`${entityName}_${op}`.toUpperCase();
197+
198+
const expected = ('Hero_' + EntityOp.QUERY_ALL).toUpperCase();
199+
const action = factory.create('Hero', EntityOp.QUERY_ALL);
200+
expect(action.type).toBe(expected);
201+
});
202+
203+
it('should throw if do not specify entityName', () => {
204+
expect(() => factory.create(null as any)).toThrow();
205+
});
206+
207+
it('should throw if do not specify EntityOp', () => {
208+
expect(() =>
209+
factory.create({ entityName: 'Hero', entityOp: null as any })
210+
).toThrow();
211+
});
212+
});

0 commit comments

Comments
 (0)