@obsidiane/auth-client-js 1.0.1
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/DEVELOPMENT.md +226 -0
- package/README.md +181 -0
- package/dist/index.js +10 -0
- package/index.ts +10 -0
- package/package.json +45 -0
- package/src/lib/bridge/rest/api-platform.adapter.ts +84 -0
- package/src/lib/bridge/rest/http-request.options.ts +21 -0
- package/src/lib/bridge/rest/query-builder.ts +43 -0
- package/src/lib/bridge/sse/eventsource-wrapper.ts +70 -0
- package/src/lib/bridge/sse/mercure-topic.mapper.ts +48 -0
- package/src/lib/bridge/sse/mercure-url.builder.ts +17 -0
- package/src/lib/bridge/sse/mercure.adapter.ts +261 -0
- package/src/lib/bridge/sse/ref-count-topic.registry.ts +45 -0
- package/src/lib/bridge.types.ts +33 -0
- package/src/lib/facades/bridge.facade.ts +108 -0
- package/src/lib/facades/facade.factory.ts +38 -0
- package/src/lib/facades/facade.interface.ts +30 -0
- package/src/lib/facades/resource.facade.ts +101 -0
- package/src/lib/interceptors/bridge-debug.interceptor.ts +32 -0
- package/src/lib/interceptors/bridge-defaults.interceptor.ts +53 -0
- package/src/lib/interceptors/content-type.interceptor.ts +49 -0
- package/src/lib/interceptors/singleflight.interceptor.ts +55 -0
- package/src/lib/ports/realtime.port.ts +36 -0
- package/src/lib/ports/resource-repository.port.ts +78 -0
- package/src/lib/provide-bridge.ts +148 -0
- package/src/lib/tokens.ts +20 -0
- package/src/lib/utils/url.ts +15 -0
- package/src/models/Auth.ts +5 -0
- package/src/models/AuthInviteCompleteInputInviteComplete.ts +7 -0
- package/src/models/AuthInviteUserInputInviteSend.ts +5 -0
- package/src/models/AuthLdJson.ts +5 -0
- package/src/models/AuthPasswordForgotInputPasswordForgot.ts +5 -0
- package/src/models/AuthPasswordResetInputPasswordReset.ts +6 -0
- package/src/models/AuthRegisterUserInputUserRegister.ts +6 -0
- package/src/models/FrontendConfig.ts +12 -0
- package/src/models/InvitePreview.ts +8 -0
- package/src/models/InviteUserInviteRead.ts +9 -0
- package/src/models/Setup.ts +5 -0
- package/src/models/SetupRegisterUserInputUserRegister.ts +6 -0
- package/src/models/UserUpdateUserRolesInputUserRoles.ts +5 -0
- package/src/models/UserUserRead.ts +9 -0
- package/src/models/index.ts +14 -0
- package/src/public-api.ts +9 -0
- package/tsconfig.json +23 -0
package/DEVELOPMENT.md
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# Development Guide
|
|
2
|
+
|
|
3
|
+
## Architecture Overview
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
@obsidiane/auth-client-js
|
|
7
|
+
├── Bridge (Meridiane-generated) ← Auto-generated from OpenAPI
|
|
8
|
+
│ ├── src/lib/ ← HTTP client, facades, interceptors
|
|
9
|
+
│ ├── src/models/ ← TypeScript types
|
|
10
|
+
│ └── src/public-api.ts ← Main exports
|
|
11
|
+
│
|
|
12
|
+
└── Config
|
|
13
|
+
├── package.json ← NPM metadata
|
|
14
|
+
├── tsconfig.json ← TypeScript config
|
|
15
|
+
└── .gitignore ← Ignore generated /src/
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## File Structure
|
|
19
|
+
|
|
20
|
+
### Generated Files (auto-ignored)
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
src/ ← GENERATED by Meridiane, ignored in git
|
|
24
|
+
├── lib/
|
|
25
|
+
│ ├── bridge/
|
|
26
|
+
│ │ ├── rest/ ← HTTP adapters
|
|
27
|
+
│ │ └── sse/ ← Realtime (optional)
|
|
28
|
+
│ ├── facades/ ← Resource facades
|
|
29
|
+
│ ├── interceptors/ ← HTTP interceptors
|
|
30
|
+
│ ├── ports/ ← Interfaces
|
|
31
|
+
│ ├── utils/ ← Helpers
|
|
32
|
+
│ ├── provide-bridge.ts ← Angular provider
|
|
33
|
+
│ ├── bridge.types.ts ← Core types
|
|
34
|
+
│ └── tokens.ts ← DI tokens
|
|
35
|
+
├── models/ ← Generated from OpenAPI schemas
|
|
36
|
+
└── public-api.ts ← Main exports
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Manual Files (version-controlled)
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
package.json
|
|
43
|
+
tsconfig.json
|
|
44
|
+
README.md
|
|
45
|
+
DEVELOPMENT.md (this file)
|
|
46
|
+
.gitignore
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Regeneration Workflow
|
|
50
|
+
|
|
51
|
+
### When Backend API Changes
|
|
52
|
+
|
|
53
|
+
1. **Backend deploys** new endpoints to `/api/docs.json`
|
|
54
|
+
|
|
55
|
+
2. **Run generation**:
|
|
56
|
+
```bash
|
|
57
|
+
make sdks
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
3. **What happens**:
|
|
61
|
+
- Downloads OpenAPI spec from `http://localhost:9000/api/docs.json`
|
|
62
|
+
- Runs Meridiane to generate the bridge
|
|
63
|
+
- Copies `src/` into `packages/auth-client-js/src/`
|
|
64
|
+
|
|
65
|
+
4. **Commit**:
|
|
66
|
+
```bash
|
|
67
|
+
git add packages/auth-client-js/
|
|
68
|
+
git commit -m "chore: regenerate SDKs from OpenAPI spec"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
5. **Publish** (if needed):
|
|
72
|
+
```bash
|
|
73
|
+
cd packages/auth-client-js
|
|
74
|
+
npm run build
|
|
75
|
+
npm publish
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Adding New Features
|
|
79
|
+
|
|
80
|
+
### Scenario 1: New HTTP Endpoint
|
|
81
|
+
|
|
82
|
+
**The endpoint is added in the backend API** → automatically generated by Meridiane.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
make sdk-npm
|
|
86
|
+
# Done! New endpoint is in src/ automatically
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Building for Publication
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Install dependencies (if needed for TypeScript compilation)
|
|
93
|
+
npm install
|
|
94
|
+
|
|
95
|
+
# Build TypeScript → JavaScript
|
|
96
|
+
npm run build
|
|
97
|
+
|
|
98
|
+
# Output in ./dist/
|
|
99
|
+
ls -la dist/
|
|
100
|
+
|
|
101
|
+
# Publish to npm
|
|
102
|
+
npm publish
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Using in Applications
|
|
106
|
+
|
|
107
|
+
### In Angular App
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// app.config.ts
|
|
111
|
+
import { provideBridge } from '@obsidiane/auth-client-js';
|
|
112
|
+
|
|
113
|
+
export const appConfig: ApplicationConfig = {
|
|
114
|
+
providers: [
|
|
115
|
+
provideBridge({
|
|
116
|
+
baseUrl: 'http://localhost:9000',
|
|
117
|
+
}),
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### In a Service
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { FacadeFactory } from '@obsidiane/auth-client-js';
|
|
126
|
+
import type { UserUserRead } from '@obsidiane/auth-client-js';
|
|
127
|
+
|
|
128
|
+
@Injectable({ providedIn: 'root' })
|
|
129
|
+
export class UsersService {
|
|
130
|
+
private readonly factory = inject(FacadeFactory);
|
|
131
|
+
private readonly facade = this.factory.create<UserUserRead>({
|
|
132
|
+
url: '/api/users',
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
getAll() {
|
|
136
|
+
return this.facade.getCollection$();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
getOne(id: string) {
|
|
140
|
+
return this.facade.get$(id);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Testing
|
|
146
|
+
|
|
147
|
+
The bridge (Meridiane) is tested separately in its own project.
|
|
148
|
+
|
|
149
|
+
## Troubleshooting
|
|
150
|
+
|
|
151
|
+
### Build Fails
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
npm run build
|
|
155
|
+
|
|
156
|
+
# If tsc not found
|
|
157
|
+
npm install
|
|
158
|
+
npm run build
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### "Cannot find module 'src/public-api'"
|
|
162
|
+
|
|
163
|
+
The `src/` folder doesn't exist. You need to generate it:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
make sdk-npm
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## CI/CD Integration
|
|
170
|
+
|
|
171
|
+
Example GitHub Actions workflow:
|
|
172
|
+
|
|
173
|
+
```yaml
|
|
174
|
+
name: Publish SDK
|
|
175
|
+
|
|
176
|
+
on:
|
|
177
|
+
push:
|
|
178
|
+
branches: [main]
|
|
179
|
+
|
|
180
|
+
jobs:
|
|
181
|
+
publish:
|
|
182
|
+
runs-on: ubuntu-latest
|
|
183
|
+
steps:
|
|
184
|
+
- uses: actions/checkout@v3
|
|
185
|
+
|
|
186
|
+
# Regenerate from OpenAPI (ensures freshness)
|
|
187
|
+
- run: make sdk-npm
|
|
188
|
+
|
|
189
|
+
# Build
|
|
190
|
+
- run: npm ci
|
|
191
|
+
working-directory: packages/auth-client-js
|
|
192
|
+
- run: npm run build
|
|
193
|
+
working-directory: packages/auth-client-js
|
|
194
|
+
|
|
195
|
+
# Publish to npm
|
|
196
|
+
- run: npm publish
|
|
197
|
+
working-directory: packages/auth-client-js
|
|
198
|
+
env:
|
|
199
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Synchronization with webfront/bridge
|
|
203
|
+
|
|
204
|
+
Both the SDK and the webfront bridge are generated from the same OpenAPI spec:
|
|
205
|
+
|
|
206
|
+
- **webfront/bridge**: Direct use in the Angular app
|
|
207
|
+
- **packages/auth-client-js**: Packaged for reuse in other projects
|
|
208
|
+
|
|
209
|
+
They should be **identical in functionality** but:
|
|
210
|
+
- Bridge: used directly via imports (`import { provideBridge }`)
|
|
211
|
+
- SDK: packaged as npm module (`npm install @obsidiane/auth-client-js`)
|
|
212
|
+
|
|
213
|
+
Both can coexist in your monorepo.
|
|
214
|
+
|
|
215
|
+
## Maintenance Checklist
|
|
216
|
+
|
|
217
|
+
- [ ] Run `make sdks` after API changes
|
|
218
|
+
- [ ] Version bump before publishing (in `package.json`)
|
|
219
|
+
- [ ] Run `npm run build` before release
|
|
220
|
+
- [ ] Commit `package.json` + `package-lock.json` changes
|
|
221
|
+
|
|
222
|
+
## References
|
|
223
|
+
|
|
224
|
+
- [Meridiane Documentation](https://github.com/obsidiane-lab/meridiane)
|
|
225
|
+
- [Angular HTTP Client](https://angular.io/guide/http)
|
|
226
|
+
- [Angular Dependency Injection](https://angular.io/guide/dependency-injection)
|
package/README.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# @obsidiane/auth-client-js
|
|
2
|
+
|
|
3
|
+
Obsidiane Auth API Client for Angular/TypeScript.
|
|
4
|
+
|
|
5
|
+
A lightweight SDK for interacting with the Obsidiane Auth service, featuring:
|
|
6
|
+
- **Bridge from Meridiane**: Auto-generated HTTP client, facades, and TypeScript models from OpenAPI spec
|
|
7
|
+
- **Zero runtime dependencies**: Uses native browser APIs and Angular's built-in HTTP client
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @obsidiane/auth-client-js
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
### 1. Setup in your Angular app
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// app.config.ts
|
|
21
|
+
import { provideBridge } from '@obsidiane/auth-client-js';
|
|
22
|
+
|
|
23
|
+
export const appConfig: ApplicationConfig = {
|
|
24
|
+
providers: [
|
|
25
|
+
// Provide the Meridiane bridge (HTTP client + facades)
|
|
26
|
+
provideBridge({
|
|
27
|
+
baseUrl: 'http://localhost:9000',
|
|
28
|
+
}),
|
|
29
|
+
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 2. Use in your services
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { Injectable, inject } from '@angular/core';
|
|
38
|
+
import { FacadeFactory } from '@obsidiane/auth-client-js';
|
|
39
|
+
import type { UserUserRead } from '@obsidiane/auth-client-js';
|
|
40
|
+
|
|
41
|
+
@Injectable({ providedIn: 'root' })
|
|
42
|
+
export class AuthService {
|
|
43
|
+
private readonly factory = inject(FacadeFactory);
|
|
44
|
+
private readonly users = this.factory.create<UserUserRead>({
|
|
45
|
+
url: '/api/users',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
getUsers() {
|
|
49
|
+
return this.users.getCollection$();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getUser(id: string) {
|
|
53
|
+
return this.users.get$(id);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## API Reference
|
|
59
|
+
|
|
60
|
+
### Bridge Exports (from Meridiane)
|
|
61
|
+
|
|
62
|
+
The bridge provides:
|
|
63
|
+
|
|
64
|
+
#### **FacadeFactory**
|
|
65
|
+
Create resource facades for CRUD operations:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
const facade = factory.create<MyResource>({
|
|
69
|
+
url: '/api/resources',
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Collection operations
|
|
73
|
+
facade.getCollection$({ page: 1, itemsPerPage: 20 })
|
|
74
|
+
|
|
75
|
+
// Item operations
|
|
76
|
+
facade.get$(id)
|
|
77
|
+
facade.post$(item)
|
|
78
|
+
facade.patch$(id, partialItem)
|
|
79
|
+
facade.delete$(id)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
#### **BridgeFacade**
|
|
83
|
+
Low-level HTTP client for custom endpoints:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
const bridge = inject(BridgeFacade);
|
|
87
|
+
|
|
88
|
+
// Custom GET
|
|
89
|
+
bridge.get$<MyResponse>('/api/custom-endpoint')
|
|
90
|
+
|
|
91
|
+
// Custom POST
|
|
92
|
+
bridge.post$<MyResponse>('/api/custom-endpoint', { payload })
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
#### **Models**
|
|
96
|
+
TypeScript types for all API resources:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import type {
|
|
100
|
+
UserUserRead,
|
|
101
|
+
Auth,
|
|
102
|
+
InviteUserInviteRead,
|
|
103
|
+
// ... all other models
|
|
104
|
+
} from '@obsidiane/auth-client-js';
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## How It Works
|
|
108
|
+
|
|
109
|
+
### Bridge Layer (Meridiane)
|
|
110
|
+
|
|
111
|
+
The bridge is auto-generated from the OpenAPI spec (`/api/docs.json`) using Meridiane. It includes:
|
|
112
|
+
|
|
113
|
+
- **HTTP Client**: Optimized fetch wrapper with automatic retries and deduplication
|
|
114
|
+
- **Facades**: Resource-based API for common CRUD operations
|
|
115
|
+
- **Models**: TypeScript interfaces for all API request/response types
|
|
116
|
+
- **Interceptors**: Built-in handling for headers, errors, and single-flight requests
|
|
117
|
+
|
|
118
|
+
## Development
|
|
119
|
+
|
|
120
|
+
### Regenerate from OpenAPI
|
|
121
|
+
|
|
122
|
+
The bridge is regenerated from the backend OpenAPI spec:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# From project root
|
|
126
|
+
make sdk-npm
|
|
127
|
+
|
|
128
|
+
# Or manually
|
|
129
|
+
npx meridiane generate "@obsidiane/auth-client-js" \
|
|
130
|
+
--spec http://localhost:9000/api/docs.json \
|
|
131
|
+
--formats "application/ld+json"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Build
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npm run build
|
|
138
|
+
# Output: ./dist/
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Publish
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
npm publish
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Migration from Old SDK
|
|
148
|
+
|
|
149
|
+
The old SDK (`packages/auth-client-js.backup/`) had 758 lines of custom HTTP client code. This new version:
|
|
150
|
+
|
|
151
|
+
- **Removes**: Custom HTTP client, request builders, response decoders
|
|
152
|
+
- **Adds**: Meridiane bridge for consistency with the main app
|
|
153
|
+
|
|
154
|
+
The API remains similar but cleaner:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// Old way (custom client)
|
|
158
|
+
import { AuthClient } from '@obsidiane/auth-client-js';
|
|
159
|
+
const client = new AuthClient({ baseUrl: 'http://localhost:9000' });
|
|
160
|
+
client.auth.login(email, password);
|
|
161
|
+
|
|
162
|
+
// New way (Meridiane bridge)
|
|
163
|
+
import { BridgeFacade } from '@obsidiane/auth-client-js';
|
|
164
|
+
const bridge = inject(BridgeFacade);
|
|
165
|
+
bridge.post$('/api/auth/login', { email, password });
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Architecture
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
@obsidiane/auth-client-js
|
|
172
|
+
├── Bridge (Meridiane-generated)
|
|
173
|
+
│ ├── HTTP client (fetch wrapper)
|
|
174
|
+
│ ├── Facades (FacadeFactory, BridgeFacade)
|
|
175
|
+
│ ├── Models (TypeScript interfaces)
|
|
176
|
+
│ └── Interceptors
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Obsidiane Auth Client for Angular/TypeScript
|
|
3
|
+
*
|
|
4
|
+
* Main entry point that exports the Meridiane-generated bridge:
|
|
5
|
+
* - HTTP client (BridgeFacade)
|
|
6
|
+
* - Facades (resource-based API)
|
|
7
|
+
* - Models (auto-generated types from OpenAPI)
|
|
8
|
+
*/
|
|
9
|
+
export * from './src/public-api';
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
package/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Obsidiane Auth Client for Angular/TypeScript
|
|
3
|
+
*
|
|
4
|
+
* Main entry point that exports the Meridiane-generated bridge:
|
|
5
|
+
* - HTTP client (BridgeFacade)
|
|
6
|
+
* - Facades (resource-based API)
|
|
7
|
+
* - Models (auto-generated types from OpenAPI)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export * from './src/public-api';
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@obsidiane/auth-client-js",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Obsidiane Auth API Client - Angular/TypeScript SDK",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"prepublish": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"@angular/common": "^20.0.0",
|
|
20
|
+
"@angular/core": "^20.0.0",
|
|
21
|
+
"rxjs": "^7.8.0"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"tslib": "^2.8.1"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@angular/common": "^20.0.2",
|
|
28
|
+
"@angular/core": "^20.0.2",
|
|
29
|
+
"rxjs": "^7.8.1",
|
|
30
|
+
"typescript": "^5.8.3"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/obsidiane-lab/obsidiane-auth.git",
|
|
35
|
+
"directory": "packages/auth-client-js"
|
|
36
|
+
},
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"keywords": [
|
|
39
|
+
"auth",
|
|
40
|
+
"authentication",
|
|
41
|
+
"obsidiane",
|
|
42
|
+
"api-client",
|
|
43
|
+
"angular"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {HttpClient} from '@angular/common/http';
|
|
2
|
+
import {Observable} from 'rxjs';
|
|
3
|
+
import {toHttpParams} from './query-builder';
|
|
4
|
+
import {buildHttpRequestOptions} from './http-request.options';
|
|
5
|
+
import {
|
|
6
|
+
Collection,
|
|
7
|
+
HttpCallOptions,
|
|
8
|
+
HttpRequestConfig,
|
|
9
|
+
Iri,
|
|
10
|
+
IriRequired,
|
|
11
|
+
Item,
|
|
12
|
+
AnyQuery,
|
|
13
|
+
ResourceRepository,
|
|
14
|
+
} from '../../ports/resource-repository.port';
|
|
15
|
+
import {resolveUrl} from '../../utils/url';
|
|
16
|
+
|
|
17
|
+
export class ApiPlatformRestRepository<T extends Item> implements ResourceRepository<T> {
|
|
18
|
+
constructor(
|
|
19
|
+
private readonly http: HttpClient,
|
|
20
|
+
private readonly apiBase: string,
|
|
21
|
+
private readonly resourcePath: Iri,
|
|
22
|
+
private readonly withCredentialsDefault: boolean,
|
|
23
|
+
) {
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
getCollection$(query?: AnyQuery, opts?: HttpCallOptions): Observable<Collection<T>> {
|
|
27
|
+
const params = toHttpParams(query);
|
|
28
|
+
return this.http.get<Collection<T>>(this.resolveUrl(this.resourcePath), {
|
|
29
|
+
params,
|
|
30
|
+
headers: opts?.headers,
|
|
31
|
+
withCredentials: opts?.withCredentials ?? this.withCredentialsDefault,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get$(iri: IriRequired, opts?: HttpCallOptions): Observable<T> {
|
|
36
|
+
return this.http.get<T>(this.resolveUrl(iri), {
|
|
37
|
+
headers: opts?.headers,
|
|
38
|
+
withCredentials: opts?.withCredentials ?? this.withCredentialsDefault,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
post$(payload: Partial<T>, opts?: HttpCallOptions): Observable<T> {
|
|
43
|
+
return this.http.post<T>(this.resolveUrl(this.resourcePath), payload, {
|
|
44
|
+
headers: opts?.headers,
|
|
45
|
+
withCredentials: opts?.withCredentials ?? this.withCredentialsDefault,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
patch$(iri: IriRequired, changes: Partial<T>, opts?: HttpCallOptions): Observable<T> {
|
|
50
|
+
return this.http.patch<T>(this.resolveUrl(iri), changes, {
|
|
51
|
+
headers: opts?.headers,
|
|
52
|
+
withCredentials: opts?.withCredentials ?? this.withCredentialsDefault,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
put$(iri: IriRequired, payload: Partial<T>, opts?: HttpCallOptions): Observable<T> {
|
|
57
|
+
return this.http.put<T>(this.resolveUrl(iri), payload, {
|
|
58
|
+
headers: opts?.headers,
|
|
59
|
+
withCredentials: opts?.withCredentials ?? this.withCredentialsDefault,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
delete$(iri: IriRequired, opts?: HttpCallOptions): Observable<void> {
|
|
64
|
+
return this.http.delete<void>(this.resolveUrl(iri), {
|
|
65
|
+
headers: opts?.headers,
|
|
66
|
+
withCredentials: opts?.withCredentials ?? this.withCredentialsDefault,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
request$<R = unknown, B = unknown>(req: HttpRequestConfig<B>): Observable<R> {
|
|
71
|
+
// Low-level escape hatch for non-standard endpoints (custom controllers, uploads, etc.).
|
|
72
|
+
const {method, url} = req;
|
|
73
|
+
|
|
74
|
+
const targetUrl = this.resolveUrl(url ?? this.resourcePath);
|
|
75
|
+
const mergedOptions = buildHttpRequestOptions(req, {withCredentialsDefault: this.withCredentialsDefault});
|
|
76
|
+
return this.http.request<R>(method, targetUrl, mergedOptions as {observe: 'body'});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private resolveUrl(path?: Iri): string {
|
|
80
|
+
const effectivePath = path ?? this.resourcePath;
|
|
81
|
+
if (!effectivePath) throw new Error('ApiPlatformRestRepository: missing url and resourcePath');
|
|
82
|
+
return resolveUrl(this.apiBase, effectivePath);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {HttpRequestConfig} from '../../ports/resource-repository.port';
|
|
2
|
+
import {toHttpParams} from './query-builder';
|
|
3
|
+
|
|
4
|
+
export function buildHttpRequestOptions(
|
|
5
|
+
req: HttpRequestConfig,
|
|
6
|
+
{withCredentialsDefault}: {withCredentialsDefault: boolean}
|
|
7
|
+
): Record<string, unknown> {
|
|
8
|
+
const {query, body, headers, responseType, withCredentials, options = {}} = req;
|
|
9
|
+
const mergedOptions: Record<string, unknown> = {...options};
|
|
10
|
+
|
|
11
|
+
if (headers) mergedOptions['headers'] = headers;
|
|
12
|
+
if (query) mergedOptions['params'] = toHttpParams(query);
|
|
13
|
+
if (body !== undefined) mergedOptions['body'] = body;
|
|
14
|
+
|
|
15
|
+
mergedOptions['responseType'] = (responseType ?? (mergedOptions['responseType'] as any) ?? 'json') as any;
|
|
16
|
+
mergedOptions['withCredentials'] =
|
|
17
|
+
withCredentials ?? (mergedOptions['withCredentials'] as any) ?? withCredentialsDefault;
|
|
18
|
+
mergedOptions['observe'] = 'body';
|
|
19
|
+
|
|
20
|
+
return mergedOptions;
|
|
21
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {HttpParams} from '@angular/common/http';
|
|
2
|
+
import {AnyQuery, Query, QueryParamValue} from '../../ports/resource-repository.port';
|
|
3
|
+
|
|
4
|
+
export function toHttpParams(q: AnyQuery | undefined): HttpParams {
|
|
5
|
+
if (!q) return new HttpParams();
|
|
6
|
+
if (q instanceof HttpParams) return q;
|
|
7
|
+
|
|
8
|
+
const fromObject: Record<string, string | string[]> = {};
|
|
9
|
+
|
|
10
|
+
const consumed = new Set<string>();
|
|
11
|
+
const maybeQuery = q as Query;
|
|
12
|
+
if (maybeQuery.page != null) {
|
|
13
|
+
fromObject['page'] = String(maybeQuery.page);
|
|
14
|
+
consumed.add('page');
|
|
15
|
+
}
|
|
16
|
+
if (maybeQuery.itemsPerPage != null) {
|
|
17
|
+
fromObject['itemsPerPage'] = String(maybeQuery.itemsPerPage);
|
|
18
|
+
consumed.add('itemsPerPage');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (q.filters) {
|
|
22
|
+
consumed.add('filters');
|
|
23
|
+
for (const [k, v] of Object.entries(q.filters)) {
|
|
24
|
+
assign(fromObject, k, v);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
for (const [k, v] of Object.entries(q as Record<string, QueryParamValue>)) {
|
|
29
|
+
if (consumed.has(k)) continue;
|
|
30
|
+
assign(fromObject, k, v);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return new HttpParams({fromObject});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function assign(target: Record<string, string | string[]>, key: string, value: QueryParamValue | undefined) {
|
|
37
|
+
if (value == null) return;
|
|
38
|
+
if (Array.isArray(value)) {
|
|
39
|
+
target[key] = value.map(String);
|
|
40
|
+
} else {
|
|
41
|
+
target[key] = String(value);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {ReplaySubject, Subject} from 'rxjs';
|
|
2
|
+
import {SseEvent, SseOptions, RealtimeStatus} from '../../ports/realtime.port';
|
|
3
|
+
import {BridgeLogger} from '../../bridge.types';
|
|
4
|
+
|
|
5
|
+
export class EventSourceWrapper {
|
|
6
|
+
private es?: EventSource;
|
|
7
|
+
|
|
8
|
+
private readonly statusSub = new ReplaySubject<RealtimeStatus>(1);
|
|
9
|
+
private readonly eventSub = new Subject<SseEvent>();
|
|
10
|
+
|
|
11
|
+
readonly status$ = this.statusSub.asObservable();
|
|
12
|
+
readonly events$ = this.eventSub.asObservable();
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
private readonly url: string,
|
|
16
|
+
private readonly opts: SseOptions = {},
|
|
17
|
+
private readonly logger?: BridgeLogger,
|
|
18
|
+
) {
|
|
19
|
+
this.setState('closed');
|
|
20
|
+
this.log('[SSE] init', {url, withCredentials: !!opts.withCredentials});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
open(): void {
|
|
24
|
+
if (this.es) {
|
|
25
|
+
this.log('[SSE] open() ignored: already open');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.setState('connecting');
|
|
30
|
+
this.log('[SSE] open', {url: this.url});
|
|
31
|
+
|
|
32
|
+
const es = new EventSource(this.url, {
|
|
33
|
+
withCredentials: !!this.opts.withCredentials,
|
|
34
|
+
});
|
|
35
|
+
this.es = es;
|
|
36
|
+
|
|
37
|
+
es.onopen = () => {
|
|
38
|
+
this.setState('connected');
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
es.onmessage = (ev) => {
|
|
42
|
+
this.eventSub.next({type: 'message', data: ev.data, lastEventId: ev.lastEventId || undefined});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
es.onerror = () => {
|
|
46
|
+
// The browser will retry automatically. We stay in "connecting".
|
|
47
|
+
this.log('[SSE] error');
|
|
48
|
+
this.setState('connecting');
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
close(): void {
|
|
53
|
+
if (this.es) {
|
|
54
|
+
this.es.close();
|
|
55
|
+
this.es = undefined;
|
|
56
|
+
this.log('[SSE] closed');
|
|
57
|
+
}
|
|
58
|
+
this.setState('closed');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ──────────────── internals ────────────────
|
|
62
|
+
|
|
63
|
+
private setState(state: RealtimeStatus): void {
|
|
64
|
+
this.statusSub.next(state);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private log(...args: unknown[]): void {
|
|
68
|
+
this.logger?.debug?.(...args);
|
|
69
|
+
}
|
|
70
|
+
}
|