@schorts/shared-kernel 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.idx/airules.md +186 -0
- package/.idx/dev.nix +54 -0
- package/.vscode/settings.json +7 -0
- package/CHANGELOG +0 -0
- package/README.md +98 -0
- package/__tests__/auth/auth-provider.test.ts +45 -0
- package/__tests__/auth/require-auth.decorator.test.ts +88 -0
- package/__tests__/criteria/criteria.test.ts +159 -0
- package/__tests__/criteria/direction.test.ts +11 -0
- package/__tests__/criteria/filter-criterion.test.ts +15 -0
- package/__tests__/criteria/operator.test.ts +22 -0
- package/__tests__/criteria/order.test.ts +14 -0
- package/__tests__/domain-events/domain-event-primitives.test.ts +34 -0
- package/__tests__/domain-events/domain-event.test.ts +50 -0
- package/__tests__/entities/entity.test.ts +114 -0
- package/__tests__/formatters/pascal-camel-to-snake.test.ts +19 -0
- package/__tests__/http/fetch-http-provider.test.ts +155 -0
- package/__tests__/http/http-provider.test.ts +55 -0
- package/__tests__/json-api/json-api-connector.test.ts +78 -0
- package/__tests__/json-api/json-api-list.test.ts +24 -0
- package/__tests__/json-api/json-api-single.test.ts +24 -0
- package/__tests__/json-api/url-criteria-builder.test.ts +74 -0
- package/__tests__/messages/message.test.ts +16 -0
- package/__tests__/models/base-model.test.ts +10 -0
- package/__tests__/state-manager/state-manager.test.ts +101 -0
- package/__tests__/utils/url/url-with-params-builder.test.ts +39 -0
- package/__tests__/value-objects/coordinates-value.test.ts +68 -0
- package/__tests__/value-objects/email-value.test.ts +43 -0
- package/__tests__/value-objects/enum-value.test.ts +51 -0
- package/__tests__/value-objects/integer-value.test.ts +115 -0
- package/__tests__/value-objects/phone-value.test.ts +82 -0
- package/__tests__/value-objects/slug-value.test.ts +43 -0
- package/__tests__/value-objects/string-value.test.ts +121 -0
- package/__tests__/value-objects/uuid-value.test.ts +67 -0
- package/__tests__/value-objects/value-object.test.ts +25 -0
- package/jest.config.js +25 -0
- package/package.json +289 -0
- package/src/auth/auth-provider.ts +10 -0
- package/src/auth/exceptions/index.ts +1 -0
- package/src/auth/exceptions/not-authenticated.ts +1 -0
- package/src/auth/index.ts +3 -0
- package/src/auth/require-auth.decorator.ts +41 -0
- package/src/criteria/criteria.ts +51 -0
- package/src/criteria/direction.ts +1 -0
- package/src/criteria/exceptions/index.ts +2 -0
- package/src/criteria/exceptions/limit-not-valid.ts +1 -0
- package/src/criteria/exceptions/offset-not-valid.ts +1 -0
- package/src/criteria/filter-criterion.ts +6 -0
- package/src/criteria/index.ts +7 -0
- package/src/criteria/operator.ts +12 -0
- package/src/criteria/order.ts +4 -0
- package/src/domain-events/domain-event-primitives.ts +7 -0
- package/src/domain-events/domain-event.ts +15 -0
- package/src/domain-events/index.ts +2 -0
- package/src/entities/entity.ts +22 -0
- package/src/entities/index.ts +1 -0
- package/src/formatters/index.ts +1 -0
- package/src/formatters/pascal-camel-to-snake.ts +8 -0
- package/src/http/exceptions/http-exception.ts +8 -0
- package/src/http/exceptions/index.ts +1 -0
- package/src/http/fetch-http-provider.ts +120 -0
- package/src/http/http-provider.ts +7 -0
- package/src/http/index.ts +4 -0
- package/src/json-api/index.ts +4 -0
- package/src/json-api/json-api-connector.ts +56 -0
- package/src/json-api/json-api-list.ts +13 -0
- package/src/json-api/json-api-single.ts +13 -0
- package/src/json-api/url-criteria-builder.ts +49 -0
- package/src/messages/index.ts +1 -0
- package/src/messages/message.ts +3 -0
- package/src/models/base-model.ts +3 -0
- package/src/models/index.ts +1 -0
- package/src/state-manager/index.ts +1 -0
- package/src/state-manager/state-manager.ts +28 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/url/index.ts +1 -0
- package/src/utils/url/url-with-params-builder.ts +19 -0
- package/src/value-objects/coordinates-value.ts +50 -0
- package/src/value-objects/email-value.ts +25 -0
- package/src/value-objects/enum-value.ts +25 -0
- package/src/value-objects/index.ts +10 -0
- package/src/value-objects/integer-value.ts +29 -0
- package/src/value-objects/phone-value.ts +53 -0
- package/src/value-objects/slug-value.ts +25 -0
- package/src/value-objects/string-value.ts +27 -0
- package/src/value-objects/uuid-value.ts +34 -0
- package/src/value-objects/value-object.ts +7 -0
- package/tsconfig.json +46 -0
package/.idx/airules.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Gemini AI Rules for Firebase Studio Nix Projects
|
|
2
|
+
|
|
3
|
+
## 1. Persona & Expertise
|
|
4
|
+
|
|
5
|
+
You are an expert in configuring development environments within Firebase Studio. You are proficient in using the `dev.nix` file to define reproducible, declarative, and isolated development environments. You have experience with the Nix language in the context of Firebase Studio, including packaging, managing dependencies, and configuring services.
|
|
6
|
+
|
|
7
|
+
## 2. Project Context
|
|
8
|
+
|
|
9
|
+
This project is a Nix-based environment for Firebase Studio, defined by a `.idx/dev.nix` file. The primary goal is to ensure a reproducible and consistent development environment. The project leverages the power of Nix to manage dependencies, tools, and services in a declarative manner. **Note:** This is not a Nix Flake-based environment.
|
|
10
|
+
|
|
11
|
+
## 3. `dev.nix` Configuration
|
|
12
|
+
|
|
13
|
+
The `.idx/dev.nix` file is the single source of truth for the development environment. Here are some of the most common configuration options:
|
|
14
|
+
|
|
15
|
+
### `channel`
|
|
16
|
+
The `nixpkgs` channel determines which package versions are available.
|
|
17
|
+
|
|
18
|
+
```nix
|
|
19
|
+
{ pkgs, ... }: {
|
|
20
|
+
channel = "stable-24.05"; # or "unstable"
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### `packages`
|
|
25
|
+
A list of packages to install from the specified channel. You can search for packages on the [NixOS package search](https://search.nixos.org/packages).
|
|
26
|
+
|
|
27
|
+
```nix
|
|
28
|
+
{ pkgs, ... }: {
|
|
29
|
+
packages = [
|
|
30
|
+
pkgs.nodejs_20
|
|
31
|
+
pkgs.go
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### `env`
|
|
37
|
+
A set of environment variables to define within the workspace.
|
|
38
|
+
|
|
39
|
+
```nix
|
|
40
|
+
{ pkgs, ... }: {
|
|
41
|
+
env = {
|
|
42
|
+
API_KEY = "your-secret-key";
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### `idx.extensions`
|
|
48
|
+
A list of VS Code extensions to install from the [Open VSX Registry](https://open-vsx.org/).
|
|
49
|
+
|
|
50
|
+
```nix
|
|
51
|
+
{ pkgs, ... }: {
|
|
52
|
+
idx = {
|
|
53
|
+
extensions = [
|
|
54
|
+
"vscodevim.vim"
|
|
55
|
+
"golang.go"
|
|
56
|
+
];
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### `idx.workspace`
|
|
62
|
+
Workspace lifecycle hooks.
|
|
63
|
+
|
|
64
|
+
- **`onCreate`:** Runs when a workspace is first created.
|
|
65
|
+
- **`onStart`:** Runs every time the workspace is (re)started.
|
|
66
|
+
|
|
67
|
+
```nix
|
|
68
|
+
{ pkgs, ... }: {
|
|
69
|
+
idx = {
|
|
70
|
+
workspace = {
|
|
71
|
+
onCreate = {
|
|
72
|
+
npm-install = "npm install";
|
|
73
|
+
};
|
|
74
|
+
onStart = {
|
|
75
|
+
start-server = "npm run dev";
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### `idx.previews`
|
|
83
|
+
Configure a web preview for your application. The `$PORT` variable is dynamically assigned.
|
|
84
|
+
|
|
85
|
+
```nix
|
|
86
|
+
{ pkgs, ... }: {
|
|
87
|
+
idx = {
|
|
88
|
+
previews = {
|
|
89
|
+
enable = true;
|
|
90
|
+
previews = {
|
|
91
|
+
web = {
|
|
92
|
+
command = ["npm" "run" "dev" "--" "--port" "$PORT"];
|
|
93
|
+
manager = "web";
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## 4. Example Setups for Common Frameworks
|
|
102
|
+
|
|
103
|
+
Here are some examples of how to configure your `dev.nix` for common languages and frameworks.
|
|
104
|
+
|
|
105
|
+
### Node.js Web Server
|
|
106
|
+
This example sets up a Node.js environment, installs dependencies, and runs a development server with a web preview.
|
|
107
|
+
|
|
108
|
+
```nix
|
|
109
|
+
{ pkgs, ... }: {
|
|
110
|
+
packages = [ pkgs.nodejs_20 ];
|
|
111
|
+
idx = {
|
|
112
|
+
extensions = [ "dbaeumer.vscode-eslint" ];
|
|
113
|
+
workspace = {
|
|
114
|
+
onCreate = {
|
|
115
|
+
npm-install = "npm install";
|
|
116
|
+
};
|
|
117
|
+
onStart = {
|
|
118
|
+
dev-server = "npm run dev";
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
previews = {
|
|
122
|
+
enable = true;
|
|
123
|
+
previews = {
|
|
124
|
+
web = {
|
|
125
|
+
command = ["npm" "run" "dev" "--" "--port" "$PORT"];
|
|
126
|
+
manager = "web";
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Python with Flask
|
|
135
|
+
This example sets up a Python environment for a Flask web server. Remember to create a `requirements.txt` file with `Flask` in it.
|
|
136
|
+
|
|
137
|
+
```nix
|
|
138
|
+
{ pkgs, ... }: {
|
|
139
|
+
packages = [ pkgs.python3 pkgs.pip ];
|
|
140
|
+
idx = {
|
|
141
|
+
extensions = [ "ms-python.python" ];
|
|
142
|
+
workspace = {
|
|
143
|
+
onCreate = {
|
|
144
|
+
pip-install = "pip install -r requirements.txt";
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
previews = {
|
|
148
|
+
enable = true;
|
|
149
|
+
previews = {
|
|
150
|
+
web = {
|
|
151
|
+
command = ["flask" "run" "--port" "$PORT"];
|
|
152
|
+
manager = "web";
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Go CLI
|
|
161
|
+
This example sets up a Go environment for building a command-line interface.
|
|
162
|
+
|
|
163
|
+
```nix
|
|
164
|
+
{ pkgs, ... }: {
|
|
165
|
+
packages = [ pkgs.go ];
|
|
166
|
+
idx = {
|
|
167
|
+
extensions = [ "golang.go" ];
|
|
168
|
+
workspace = {
|
|
169
|
+
onCreate = {
|
|
170
|
+
go-mod = "go mod tidy";
|
|
171
|
+
};
|
|
172
|
+
onStart = {
|
|
173
|
+
run-app = "go run .";
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## 5. Interaction Guidelines
|
|
181
|
+
|
|
182
|
+
- Assume the user is familiar with general software development concepts but may be new to Nix and Firebase Studio.
|
|
183
|
+
- When generating Nix code, provide comments to explain the purpose of different sections.
|
|
184
|
+
- Explain the benefits of using `dev.nix` for reproducibility and dependency management.
|
|
185
|
+
- If a request is ambiguous, ask for clarification on the desired tools, libraries, and versions to be included in the environment.
|
|
186
|
+
- When suggesting changes to `dev.nix`, explain the impact of the changes on the development environment and remind the user to reload the environment.
|
package/.idx/dev.nix
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# To learn more about how to use Nix to configure your environment
|
|
2
|
+
# see: https://developers.google.com/idx/guides/customize-idx-env
|
|
3
|
+
{ pkgs, ... }: {
|
|
4
|
+
# Which nixpkgs channel to use.
|
|
5
|
+
channel = "stable-24.05"; # or "unstable"
|
|
6
|
+
# Use https://search.nixos.org/packages to find packages
|
|
7
|
+
packages = [
|
|
8
|
+
# pkgs.go
|
|
9
|
+
# pkgs.python311
|
|
10
|
+
# pkgs.python311Packages.pip
|
|
11
|
+
pkgs.nodejs_20
|
|
12
|
+
# pkgs.nodePackages.nodemon
|
|
13
|
+
];
|
|
14
|
+
# Sets environment variables in the workspace
|
|
15
|
+
env = {};
|
|
16
|
+
idx = {
|
|
17
|
+
# Search for the extensions you want on https://open-vsx.org/ and use "publisher.id"
|
|
18
|
+
extensions = [
|
|
19
|
+
# "vscodevim.vim"
|
|
20
|
+
"google.gemini-cli-vscode-ide-companion"
|
|
21
|
+
];
|
|
22
|
+
# Enable previews
|
|
23
|
+
previews = {
|
|
24
|
+
enable = true;
|
|
25
|
+
previews = {
|
|
26
|
+
# web = {
|
|
27
|
+
# # Example: run "npm run dev" with PORT set to IDX's defined port for previews,
|
|
28
|
+
# # and show it in IDX's web preview panel
|
|
29
|
+
# command = ["npm" "run" "dev"];
|
|
30
|
+
# manager = "web";
|
|
31
|
+
# env = {
|
|
32
|
+
# # Environment variables to set for your server
|
|
33
|
+
# PORT = "$PORT";
|
|
34
|
+
# };
|
|
35
|
+
# };
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
# Workspace lifecycle hooks
|
|
39
|
+
workspace = {
|
|
40
|
+
# Runs when a workspace is first created
|
|
41
|
+
onCreate = {
|
|
42
|
+
# Example: install JS dependencies from NPM
|
|
43
|
+
# npm-install = "npm install";
|
|
44
|
+
# Open editors for the following files by default, if they exist:
|
|
45
|
+
default.openFiles = [ ".idx/dev.nix" "README.md" ];
|
|
46
|
+
};
|
|
47
|
+
# Runs when the workspace is (re)started
|
|
48
|
+
onStart = {
|
|
49
|
+
# Example: start a background task to watch and re-build backend code
|
|
50
|
+
# watch-backend = "npm run watch-backend";
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
}
|
package/CHANGELOG
ADDED
|
File without changes
|
package/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# 📦 Shared Kernel
|
|
2
|
+
|
|
3
|
+
A modular, type-safe foundation for building expressive, maintainable applications. This package provides core abstractions for domain modeling, HTTP integration, authentication, state management, and more — designed to be framework-agnostic and highly extensible.
|
|
4
|
+
|
|
5
|
+
## 🚀 Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @schorts/shared-kernel --save
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 🧱 Modules
|
|
12
|
+
|
|
13
|
+
### 🔐 Auth
|
|
14
|
+
|
|
15
|
+
- **AuthProvider:** Abstract interface for authentication strategies.
|
|
16
|
+
- **RequireAuthDecorator:** Method-level decorator for enforcing authentication.
|
|
17
|
+
|
|
18
|
+
### 📊 Criteria
|
|
19
|
+
|
|
20
|
+
- **Criteria:** Fluent query builder for filtering, sorting, and pagination.
|
|
21
|
+
|
|
22
|
+
### 📣 Domain Events
|
|
23
|
+
|
|
24
|
+
- **DomainEvent:** Base class for domain-driven event dispatching.
|
|
25
|
+
|
|
26
|
+
### 🧬 Entities
|
|
27
|
+
|
|
28
|
+
- **Entity:** Base class for identity-based domain entities.
|
|
29
|
+
|
|
30
|
+
### 🧹 Formatters
|
|
31
|
+
|
|
32
|
+
- **PascalCamelToSnake:** Utility for converting PascalCase or camelCase to snake_case.
|
|
33
|
+
|
|
34
|
+
### 🌐 HTTP
|
|
35
|
+
|
|
36
|
+
- **HTTPProvider:** Abstract interface for HTTP transport.
|
|
37
|
+
- **FetchHTTPProvider:** Concrete implementation using fetch.
|
|
38
|
+
|
|
39
|
+
### 🔗 JSON:API
|
|
40
|
+
|
|
41
|
+
- **JSONAPIConnector:** Connector for interacting with JSON:API-compliant endpoints.
|
|
42
|
+
|
|
43
|
+
### 🧩 Models
|
|
44
|
+
|
|
45
|
+
- **BaseModel:** Base class for serializable, type-safe models.
|
|
46
|
+
|
|
47
|
+
### 🧠 State Manager
|
|
48
|
+
|
|
49
|
+
- **StateManager:** Abstract reactive state manager with listener support.
|
|
50
|
+
|
|
51
|
+
### 🧪 Value Objects
|
|
52
|
+
|
|
53
|
+
- **CoordinatesValue**
|
|
54
|
+
- **EmailValue**
|
|
55
|
+
- **EnumValue**
|
|
56
|
+
- **IntegerValue**
|
|
57
|
+
- **PhoneValue**
|
|
58
|
+
- **SlugValue**
|
|
59
|
+
- **StringValue**
|
|
60
|
+
- **UUIDValue**
|
|
61
|
+
|
|
62
|
+
Each value object enforces domain constraints and immutability, ensuring correctness at the boundary of your system.
|
|
63
|
+
|
|
64
|
+
## 🧠 Philosophy
|
|
65
|
+
|
|
66
|
+
This kernel is built around:
|
|
67
|
+
|
|
68
|
+
- **Type safety:** Every abstraction is strongly typed and composable.
|
|
69
|
+
- **Domain expressiveness:** Value objects, entities, and events encode business logic directly.
|
|
70
|
+
- **Extensibility:** Plug in your own HTTP, auth, or state strategies.
|
|
71
|
+
- **Framework independence:** Use it with Node.js, frontend apps, or serverless functions.
|
|
72
|
+
|
|
73
|
+
## 📦 Example Usage
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { Criteria, JSONAPIConnector, FetchHTTPProvider } from "@schorts/shared-kernel";
|
|
77
|
+
|
|
78
|
+
const criteria = new Criteria()
|
|
79
|
+
.where("status", "EQUAL", "active")
|
|
80
|
+
.orderBy("created_at", "DESC")
|
|
81
|
+
.limitResults(10);
|
|
82
|
+
|
|
83
|
+
const connector = new JSONAPIConnector(new FetchHTTPProvider());
|
|
84
|
+
|
|
85
|
+
const users = await connector.findMany(new URL("/users", base), criteria);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## 🧪 Testing
|
|
89
|
+
|
|
90
|
+
Each module is fully covered with unit tests. To run them:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm run test
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 📜 License
|
|
97
|
+
|
|
98
|
+
LGPL
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { expectTypeOf } from "expect-type";
|
|
2
|
+
|
|
3
|
+
import { AuthProvider } from "../../src/auth";
|
|
4
|
+
import { Entity } from "../../src/entities";
|
|
5
|
+
|
|
6
|
+
type Model = {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
class TestUser extends Entity<Model> {
|
|
12
|
+
toPrimitives(): Model {
|
|
13
|
+
return {
|
|
14
|
+
id: this.id.toString(),
|
|
15
|
+
name: "test",
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('AuthProvider', () => {
|
|
21
|
+
it('should declare an "authenticate" method', () => {
|
|
22
|
+
expectTypeOf<AuthProvider<TestUser>['authenticate']>().toBeFunction();
|
|
23
|
+
expectTypeOf<AuthProvider<TestUser>['authenticate']>().returns.toEqualTypeOf<Promise<void>>();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should declare a "logout" method', () => {
|
|
27
|
+
expectTypeOf<AuthProvider<TestUser>['logout']>().toBeFunction();
|
|
28
|
+
expectTypeOf<AuthProvider<TestUser >['logout']>().returns.toEqualTypeOf<Promise<void>>();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should declare a "isAuthenticated" method', () => {
|
|
32
|
+
expectTypeOf<AuthProvider<TestUser>['isAuthenticated']>().toBeFunction();
|
|
33
|
+
expectTypeOf<AuthProvider<TestUser>['isAuthenticated']>().returns.toEqualTypeOf<Promise<boolean>>();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should declare a "currentUser" method', () => {
|
|
37
|
+
expectTypeOf<AuthProvider<TestUser>['currentUser']>().toBeFunction();
|
|
38
|
+
expectTypeOf<AuthProvider<TestUser>['currentUser']>().returns.toEqualTypeOf<Promise<TestUser | null>>();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should declare a "onAuthChange" method', () => {
|
|
42
|
+
expectTypeOf<AuthProvider<TestUser>['onAuthChange']>().toBeFunction();
|
|
43
|
+
expectTypeOf<AuthProvider<TestUser>['onAuthChange']>().returns.toEqualTypeOf<() => void>();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { RequireAuth, NotAuthenticated } from '../../src/auth';
|
|
2
|
+
|
|
3
|
+
class FakeAuthProvider {
|
|
4
|
+
constructor(private authenticated: boolean) {}
|
|
5
|
+
async isAuthenticated() {
|
|
6
|
+
return this.authenticated;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
class TestService {
|
|
11
|
+
authProvider: FakeAuthProvider;
|
|
12
|
+
|
|
13
|
+
constructor(authenticated: boolean) {
|
|
14
|
+
this.authProvider = new FakeAuthProvider(authenticated);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async securedMethod(): Promise<string> {
|
|
18
|
+
return 'should not reach';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('RequireAuth decorator (manual application)', () => {
|
|
23
|
+
it('should throw NotAuthenticated when not authenticated and no onFail', async () => {
|
|
24
|
+
const service = new TestService(false);
|
|
25
|
+
const originalMethod = service.securedMethod;
|
|
26
|
+
const decorated = RequireAuth()(originalMethod, {
|
|
27
|
+
kind: 'method',
|
|
28
|
+
name: 'securedMethod',
|
|
29
|
+
access: { has: () => true, get: () => originalMethod },
|
|
30
|
+
static: false,
|
|
31
|
+
private: false,
|
|
32
|
+
addInitializer: () => {},
|
|
33
|
+
metadata: {
|
|
34
|
+
get: () => undefined,
|
|
35
|
+
set: () => {},
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
const wrappedMethod = typeof decorated === 'function' ? decorated : decorated?.value;
|
|
39
|
+
service.securedMethod = wrappedMethod.bind(service);
|
|
40
|
+
|
|
41
|
+
await expect(service.securedMethod()).rejects.toThrow(NotAuthenticated);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should return fallback value when not authenticated and onFail is provided', async () => {
|
|
45
|
+
const service = new TestService(false);
|
|
46
|
+
const fallback = () => 'fallback';
|
|
47
|
+
const originalMethod = service.securedMethod;
|
|
48
|
+
const decorated = RequireAuth(fallback)(originalMethod, {
|
|
49
|
+
kind: 'method',
|
|
50
|
+
name: 'securedMethod',
|
|
51
|
+
access: { has: () => true, get: () => originalMethod },
|
|
52
|
+
static: false,
|
|
53
|
+
private: false,
|
|
54
|
+
addInitializer: () => {},
|
|
55
|
+
metadata: {
|
|
56
|
+
get: () => undefined,
|
|
57
|
+
set: () => {},
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
const wrappedMethod = typeof decorated === 'function' ? decorated : decorated?.value;
|
|
61
|
+
service.securedMethod = wrappedMethod.bind(service);
|
|
62
|
+
const result = await service.securedMethod();
|
|
63
|
+
|
|
64
|
+
expect(result).toBe('fallback');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should call original method when authenticated', async () => {
|
|
68
|
+
const service = new TestService(true);
|
|
69
|
+
const originalMethod = service.securedMethod;
|
|
70
|
+
const decorated = RequireAuth()(originalMethod, {
|
|
71
|
+
kind: 'method',
|
|
72
|
+
name: 'securedMethod',
|
|
73
|
+
access: { has: () => true, get: () => originalMethod },
|
|
74
|
+
static: false,
|
|
75
|
+
private: false,
|
|
76
|
+
addInitializer: () => {},
|
|
77
|
+
metadata: {
|
|
78
|
+
get: () => undefined,
|
|
79
|
+
set: () => {},
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
const wrappedMethod = typeof decorated === 'function' ? decorated : decorated?.value;
|
|
83
|
+
service.securedMethod = wrappedMethod.bind(service);
|
|
84
|
+
const result = await service.securedMethod();
|
|
85
|
+
|
|
86
|
+
expect(result).toBe('should not reach');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { expectTypeOf } from "expect-type";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Criteria,
|
|
5
|
+
FilterCriterion,
|
|
6
|
+
Order,
|
|
7
|
+
Operator,
|
|
8
|
+
Direction,
|
|
9
|
+
PaginationNotValid,
|
|
10
|
+
OffsetNotValid,
|
|
11
|
+
LimitNotValid,
|
|
12
|
+
} from "../../src/criteria";
|
|
13
|
+
|
|
14
|
+
describe('Criteria', () => {
|
|
15
|
+
describe('filters', () => {
|
|
16
|
+
it('should be defined', () => {
|
|
17
|
+
expectTypeOf<Criteria>().toHaveProperty("filters");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should be of type Record<string, FilterCriterion>', () => {
|
|
21
|
+
expectTypeOf<Criteria["filters"]>().toEqualTypeOf<Record<string, FilterCriterion>>();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should be empty', () => {
|
|
25
|
+
const criteria = new Criteria();
|
|
26
|
+
const filters = criteria.filters;
|
|
27
|
+
const filterKeys = Object.keys(filters);
|
|
28
|
+
|
|
29
|
+
expect(filterKeys).toHaveLength(0);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('orders', () => {
|
|
34
|
+
it('should be defined', () => {
|
|
35
|
+
expectTypeOf<Criteria>().toHaveProperty("orders");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should be of type Array<Order>', () => {
|
|
39
|
+
expectTypeOf<Criteria["orders"]>().toEqualTypeOf<Array<Order>>();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should be empty', () => {
|
|
43
|
+
const criteria = new Criteria();
|
|
44
|
+
const orders = criteria.orders;
|
|
45
|
+
|
|
46
|
+
expect(orders).toHaveLength(0);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should have a "limit" property of type number or undefined', () => {
|
|
51
|
+
expectTypeOf<Criteria>().toHaveProperty("limit");
|
|
52
|
+
expectTypeOf<Criteria["limit"]>().toEqualTypeOf<number | undefined>();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should have a "offset" property of type number or undefined', () => {
|
|
56
|
+
expectTypeOf<Criteria>().toHaveProperty("offset");
|
|
57
|
+
expectTypeOf<Criteria["offset"]>().toEqualTypeOf<number | undefined>();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('#where', () => {
|
|
61
|
+
it('should receive field, operator and value', () => {
|
|
62
|
+
expectTypeOf<Criteria["where"]>().parameters.toEqualTypeOf<[string, Operator, any]>();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should return the same instance', () => {
|
|
66
|
+
const criteria = new Criteria();
|
|
67
|
+
const result = criteria.where('field', 'EQUAL', 'value');
|
|
68
|
+
|
|
69
|
+
expect(result).toEqual(criteria);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should add the filter to the "filters" property', () => {
|
|
73
|
+
const criteria = new Criteria();
|
|
74
|
+
const result = criteria.where('field', 'EQUAL', 'value');
|
|
75
|
+
const filters = result.filters;
|
|
76
|
+
const expectedFilters = { field: { value: "value", operator: "EQUAL" } };
|
|
77
|
+
|
|
78
|
+
expect(filters["field"]).toBeDefined();
|
|
79
|
+
expect(filters).toEqual(expectedFilters);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('#orderBy', () => {
|
|
84
|
+
it('should receive field and direction', () => {
|
|
85
|
+
expectTypeOf<Criteria["orderBy"]>().parameters.toEqualTypeOf<[string, Direction?]>();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should return the same instance', () => {
|
|
89
|
+
const criteria = new Criteria();
|
|
90
|
+
const result = criteria.orderBy('field', 'DESC');
|
|
91
|
+
|
|
92
|
+
expect(result).toEqual(criteria);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should add the order to the "orders" property', () => {
|
|
96
|
+
const criteria = new Criteria();
|
|
97
|
+
const result = criteria.orderBy("field", "DESC");
|
|
98
|
+
const orders = result.orders;
|
|
99
|
+
const expectedOrders = [{ field: "field", direction: "DESC" }];
|
|
100
|
+
|
|
101
|
+
expect(orders).toHaveLength(1);
|
|
102
|
+
expect(orders).toEqual(expectedOrders);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('#limitResults', () => {
|
|
107
|
+
it('should receive a number', () => {
|
|
108
|
+
expectTypeOf<Criteria["limitResults"]>().parameters.toEqualTypeOf<[number]>();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should return the same instance', () => {
|
|
112
|
+
const criteria = new Criteria();
|
|
113
|
+
const result = criteria.limitResults(1);
|
|
114
|
+
|
|
115
|
+
expect(result).toEqual(criteria);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should assign the value to the "limit" property', () => {
|
|
119
|
+
const criteria = new Criteria();
|
|
120
|
+
const limit = 1;
|
|
121
|
+
const result = criteria.limitResults(limit);
|
|
122
|
+
|
|
123
|
+
expect(result.limit).toEqual(limit);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should throw an LimitNotValid exception if value is less than 1', () => {
|
|
127
|
+
const criteria = new Criteria();
|
|
128
|
+
|
|
129
|
+
expect(() => criteria.limitResults(0)).toThrow(LimitNotValid);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('#offsetResults', () => {
|
|
134
|
+
it('should receive a number', () => {
|
|
135
|
+
expectTypeOf<Criteria["offsetResults"]>().parameters.toEqualTypeOf<[number]>();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should return the same instance', () => {
|
|
139
|
+
const criteria = new Criteria();
|
|
140
|
+
const result = criteria.offsetResults(1);
|
|
141
|
+
|
|
142
|
+
expect(result).toEqual(criteria);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should assign the value to the "offset" property', () => {
|
|
146
|
+
const criteria = new Criteria();
|
|
147
|
+
const offset = 1;
|
|
148
|
+
const result = criteria.offsetResults(offset);
|
|
149
|
+
|
|
150
|
+
expect(result.offset).toEqual(offset);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should throw an OffsetNotValid exception if value is less than 1', () => {
|
|
154
|
+
const criteria = new Criteria();
|
|
155
|
+
|
|
156
|
+
expect(() => criteria.offsetResults(0)).toThrow(OffsetNotValid);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { expectTypeOf } from "expect-type";
|
|
2
|
+
|
|
3
|
+
import { Direction } from "../../src/criteria";
|
|
4
|
+
|
|
5
|
+
type ExpectedDirection = "ASC" | "DESC";
|
|
6
|
+
|
|
7
|
+
describe('Direction', () => {
|
|
8
|
+
it('should match the expected type', () => {
|
|
9
|
+
expectTypeOf<Direction>().toEqualTypeOf<ExpectedDirection>();
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { expectTypeOf } from "expect-type";
|
|
2
|
+
|
|
3
|
+
import { FilterCriterion, Operator } from "../../src/criteria";
|
|
4
|
+
|
|
5
|
+
type ExpectedFilterCriterion = {
|
|
6
|
+
value: any;
|
|
7
|
+
operator: Operator;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe('FilterCriterion', () => {
|
|
11
|
+
it('should match the expected type', () => {
|
|
12
|
+
expectTypeOf<FilterCriterion>().toEqualTypeOf<ExpectedFilterCriterion>();
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { expectTypeOf } from "expect-type";
|
|
2
|
+
|
|
3
|
+
import { Operator } from "../../src/criteria";
|
|
4
|
+
|
|
5
|
+
type ExpectedOperator =
|
|
6
|
+
| "EQUAL"
|
|
7
|
+
| "GREATER_THAN"
|
|
8
|
+
| "LESS_THAN"
|
|
9
|
+
| "GREATER_THAN_OR_EQUAL"
|
|
10
|
+
| "LESS_THAN_OR_EQUAL"
|
|
11
|
+
| "NOT_EQUAL"
|
|
12
|
+
| "IN"
|
|
13
|
+
| "NOT_IN"
|
|
14
|
+
| "LIKE"
|
|
15
|
+
| "BETWEEN"
|
|
16
|
+
| "GEO_RADIUS";
|
|
17
|
+
|
|
18
|
+
describe('Operator', () => {
|
|
19
|
+
it('should match the expected type', () => {
|
|
20
|
+
expectTypeOf<Operator>().toEqualTypeOf<ExpectedOperator>();
|
|
21
|
+
});
|
|
22
|
+
});
|