@obsidiane/meridiane 2.0.0 → 2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obsidiane/meridiane",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Générateur de librairie Angular et modèles TypeScript (OpenAPI) pour un bridge Symfony/API Platform + Mercure.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,194 @@
1
+ # __PACKAGE_NAME__
2
+
3
+ Bridge Angular (runtime + models TypeScript) pour une API Platform (Hydra/JSON-LD), avec support Mercure/SSE optionnel.
4
+
5
+ Le package expose une API Angular minimaliste :
6
+ - `provideBridge()` pour configurer le bridge (HTTP + tokens + Mercure) ;
7
+ - `FacadeFactory` / `ResourceFacade<T>` pour une API orientée ressource ;
8
+ - `BridgeFacade` pour des appels ad-hoc.
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm i __PACKAGE_NAME__
14
+ ```
15
+
16
+ Le bridge est conçu pour être installé dans une application Angular. Les dépendances Angular et RxJS sont des `peerDependencies`.
17
+
18
+ ## Compatibilité
19
+
20
+ - Angular `@angular/*` `^20.1.0`
21
+ - RxJS `^7.8.0`
22
+
23
+ ## Démarrage rapide
24
+
25
+ Configurez le bridge au démarrage de l’application avec `provideBridge()`.
26
+
27
+ ### Application standalone (recommandé)
28
+
29
+ ```ts
30
+ // app.config.ts (ou main.ts)
31
+ import {ApplicationConfig} from '@angular/core';
32
+ import {provideBridge} from '__PACKAGE_NAME__';
33
+
34
+ export const appConfig: ApplicationConfig = {
35
+ providers: [
36
+ provideBridge({
37
+ baseUrl: 'https://api.example.com',
38
+ mercure: {hubUrl: 'https://api.example.com/.well-known/mercure'},
39
+ }),
40
+ ],
41
+ };
42
+ ```
43
+
44
+ ### Application NgModule
45
+
46
+ ```ts
47
+ import {NgModule} from '@angular/core';
48
+ import {provideBridge} from '__PACKAGE_NAME__';
49
+
50
+ @NgModule({
51
+ providers: [
52
+ provideBridge({baseUrl: 'https://api.example.com'}),
53
+ ],
54
+ })
55
+ export class AppModule {}
56
+ ```
57
+
58
+ ## Configuration
59
+
60
+ ### Cookies / `withCredentials`
61
+
62
+ Le bridge peut envoyer des cookies (sessions) côté HTTP et SSE.
63
+
64
+ Le comportement `withCredentials` par défaut est déduit de `mercure.init` :
65
+ - `credentials: 'include'` (défaut) → cookies envoyés
66
+ - `credentials: 'omit'` → cookies non envoyés
67
+
68
+ Vous pouvez aussi surcharger au cas par cas via `opts.withCredentials` sur les appels HTTP.
69
+
70
+ ```ts
71
+ import {provideBridge} from '__PACKAGE_NAME__';
72
+
73
+ provideBridge({
74
+ baseUrl: 'https://api.example.com',
75
+ mercure: {
76
+ hubUrl: 'https://api.example.com/.well-known/mercure',
77
+ init: {credentials: 'omit'},
78
+ },
79
+ });
80
+ ```
81
+
82
+ ### Auth (Bearer)
83
+
84
+ `auth` accepte :
85
+ - une string (token Bearer),
86
+ - `{ type: 'bearer', token }`,
87
+ - `{ type: 'bearer', getToken }` (sync ou async),
88
+ - ou un `HttpInterceptorFn` custom.
89
+
90
+ ```ts
91
+ import {provideBridge} from '__PACKAGE_NAME__';
92
+
93
+ provideBridge({
94
+ baseUrl: 'https://api.example.com',
95
+ auth: {type: 'bearer', getToken: () => localStorage.getItem('token') ?? undefined},
96
+ });
97
+ ```
98
+
99
+ ### Defaults (headers / timeout / retries)
100
+
101
+ ```ts
102
+ import {provideBridge} from '__PACKAGE_NAME__';
103
+
104
+ provideBridge({
105
+ baseUrl: 'https://api.example.com',
106
+ defaults: {
107
+ headers: {'X-Requested-With': 'fetch'},
108
+ timeoutMs: 15_000,
109
+ retries: {count: 2, delayMs: 250, methods: ['GET']},
110
+ },
111
+ });
112
+ ```
113
+
114
+ ### Debug
115
+
116
+ ```ts
117
+ import {provideBridge} from '__PACKAGE_NAME__';
118
+
119
+ provideBridge({baseUrl: 'https://api.example.com', debug: true});
120
+ ```
121
+
122
+ ## Appeler l’API
123
+
124
+ ### Style orienté ressource (`FacadeFactory` + `ResourceFacade<T>`)
125
+
126
+ ```ts
127
+ import {inject} from '@angular/core';
128
+ import {FacadeFactory, ResourceFacade, Item} from '__PACKAGE_NAME__';
129
+
130
+ type Book = Item & {title?: string};
131
+
132
+ export class BooksService {
133
+ private readonly factory = inject(FacadeFactory);
134
+ readonly books: ResourceFacade<Book> = this.factory.create<Book>({url: '/api/books'});
135
+ }
136
+ ```
137
+
138
+ Exemples :
139
+
140
+ ```ts
141
+ // collection Hydra
142
+ this.books.getCollection$({page: 1, itemsPerPage: 20, filters: {title: 'Dune'}});
143
+
144
+ // item (souvent via @id)
145
+ this.books.get$(book['@id']!);
146
+
147
+ // write
148
+ this.books.post$({title: 'Neuromancer'});
149
+ this.books.patch$(book['@id']!, {title: 'Count Zero'});
150
+ this.books.delete$(book['@id']!);
151
+ ```
152
+
153
+ ### Style ad-hoc (`BridgeFacade`)
154
+
155
+ ```ts
156
+ import {inject} from '@angular/core';
157
+ import {BridgeFacade} from '__PACKAGE_NAME__';
158
+
159
+ export class HealthService {
160
+ private readonly bridge = inject(BridgeFacade);
161
+ getHealth$() {
162
+ return this.bridge.get$<{status: string}>('/health');
163
+ }
164
+ }
165
+ ```
166
+
167
+ ## Realtime (Mercure/SSE)
168
+
169
+ Le realtime est inactif tant que `mercure.hubUrl` n’est pas fourni à `provideBridge()`.
170
+
171
+ ```ts
172
+ provideBridge({
173
+ baseUrl: 'https://api.example.com',
174
+ mercure: {hubUrl: 'https://api.example.com/.well-known/mercure', topicMode: 'url'},
175
+ });
176
+ ```
177
+
178
+ API :
179
+ - `ResourceFacade<T>.watch$(iri | iri[])` / `unwatch(iri | iri[])`
180
+ - `ResourceFacade<T>.watchSubResource$(iri | iri[], 'field.path')`
181
+ - `BridgeFacade.watch$(iri | iri[])` / `unwatch(iri | iri[])`
182
+
183
+ Note SSR : la connexion SSE ne s’ouvre que dans le navigateur.
184
+
185
+ ## Models TypeScript
186
+
187
+ Les models générés (si présents) sont exportés au même niveau que le runtime :
188
+
189
+ ```ts
190
+ import type {Item} from '__PACKAGE_NAME__';
191
+ // import type {Book} from '__PACKAGE_NAME__';
192
+ ```
193
+
194
+ En JSON-LD (`application/ld+json`), l’IRI est typiquement dans `model['@id']`.
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3
3
  "dest": "../../dist/__LIB_NAME__",
4
+ "assets": [
5
+ "README.md"
6
+ ],
4
7
  "lib": {
5
8
  "entryFile": "src/public-api.ts"
6
9
  }
@@ -1,12 +1,12 @@
1
1
  import fs from 'node:fs/promises';
2
- import { existsSync } from 'node:fs';
2
+ import {existsSync} from 'node:fs';
3
3
  import path from 'node:path';
4
- import { fileURLToPath } from 'node:url';
4
+ import {fileURLToPath} from 'node:url';
5
5
 
6
- import { readJson, writeJsonIfChanged } from './json.js';
7
- import { renderTemplateToFile } from '../generator/models/handlebars.js';
8
- import { ensureCleanDir } from '../generator/models/utils.js';
9
- import { buildModelsFromOpenAPI } from '../generator/models/openapi-to-models.js';
6
+ import {readJson, writeJsonIfChanged} from './json.js';
7
+ import {renderTemplateToFile} from '../generator/models/handlebars.js';
8
+ import {ensureCleanDir} from '../generator/models/utils.js';
9
+ import {buildModelsFromOpenAPI} from '../generator/models/openapi-to-models.js';
10
10
 
11
11
  const __filename = fileURLToPath(import.meta.url);
12
12
  const __dirname = path.dirname(__filename);
@@ -14,7 +14,7 @@ const __dirname = path.dirname(__filename);
14
14
  const pkgRoot = path.resolve(__dirname, '../..'); // packages/meridiane
15
15
 
16
16
  async function replacePlaceholdersInDir(dir, placeholders) {
17
- const entries = await fs.readdir(dir, { withFileTypes: true });
17
+ const entries = await fs.readdir(dir, {withFileTypes: true});
18
18
  for (const entry of entries) {
19
19
  const fullPath = path.join(dir, entry.name);
20
20
  if (entry.isDirectory()) {
@@ -29,17 +29,25 @@ async function replacePlaceholdersInDir(dir, placeholders) {
29
29
  }
30
30
  }
31
31
 
32
- async function generateLibrary({ cwd, libName, packageName, version, debug }) {
32
+ async function generateLibrary({cwd, libName, packageName, version, debug}) {
33
33
  const tplDir = path.resolve(pkgRoot, 'templates/_lib_template');
34
34
  const targetDir = path.resolve(cwd, 'projects', libName);
35
35
 
36
- if (debug) console.log('[meridiane] generate lib', { libName, packageName, version, targetDir: path.relative(cwd, targetDir) });
36
+ if (debug) console.log('[meridiane] generate lib', {
37
+ libName,
38
+ packageName,
39
+ version,
40
+ targetDir: path.relative(cwd, targetDir)
41
+ });
37
42
 
38
- await fs.rm(targetDir, { recursive: true, force: true });
39
- await fs.mkdir(targetDir, { recursive: true });
40
- await fs.cp(tplDir, targetDir, { recursive: true });
43
+ await fs.rm(targetDir, {recursive: true, force: true});
44
+ await fs.mkdir(targetDir, {recursive: true});
45
+ await fs.cp(tplDir, targetDir, {recursive: true});
41
46
 
42
- await replacePlaceholdersInDir(targetDir, { '__LIB_NAME__': libName });
47
+ await replacePlaceholdersInDir(targetDir, {
48
+ '__LIB_NAME__': libName,
49
+ '__PACKAGE_NAME__': packageName,
50
+ });
43
51
 
44
52
  const libPackageJsonPath = path.join(targetDir, 'package.json');
45
53
  if (existsSync(libPackageJsonPath)) {
@@ -52,7 +60,7 @@ async function generateLibrary({ cwd, libName, packageName, version, debug }) {
52
60
  // angular.json / workspace patching is intentionally not done here (Meridiane is standalone).
53
61
  }
54
62
 
55
- async function generateModels({ cwd, libName, spec, requiredMode, formats, include, exclude, debug }) {
63
+ async function generateModels({cwd, libName, spec, requiredMode, formats, include, exclude, debug}) {
56
64
  const outDir = path.resolve(cwd, 'projects', libName, 'src', 'models');
57
65
  const templatesDir = path.resolve(pkgRoot, 'tools', 'generator', 'models', 'templates');
58
66
 
@@ -68,7 +76,7 @@ async function generateModels({ cwd, libName, spec, requiredMode, formats, inclu
68
76
  await ensureCleanDir(outDir);
69
77
 
70
78
  // Write models + index.ts.
71
- const { models } = buildModelsFromOpenAPI(spec, {
79
+ const {models} = buildModelsFromOpenAPI(spec, {
72
80
  requiredMode,
73
81
  formats: Array.isArray(formats) ? formats : [],
74
82
  includeSchemaNames: include,
@@ -92,7 +100,7 @@ async function generateModels({ cwd, libName, spec, requiredMode, formats, inclu
92
100
  await renderTemplateToFile({
93
101
  templatePath: path.join(templatesDir, 'index.hbs'),
94
102
  outPath: path.join(outDir, 'index.ts'),
95
- ctx: { models: models.map((m) => ({ name: m.name })) },
103
+ ctx: {models: models.map((m) => ({name: m.name}))},
96
104
  });
97
105
 
98
106
  if (debug) console.log(`[meridiane] ${models.length} model(s) generated`);
@@ -134,7 +142,7 @@ export async function generateBridgeWorkspace(params) {
134
142
  } = params;
135
143
 
136
144
  log?.step?.(`génération de la librairie (projects/${libName})`);
137
- await generateLibrary({ cwd, libName, packageName, version, debug });
145
+ await generateLibrary({cwd, libName, packageName, version, debug});
138
146
 
139
147
  // If a dist root is provided, force ng-packagr output to that directory.
140
148
  if (distRoot) {
@@ -144,7 +152,7 @@ export async function generateBridgeWorkspace(params) {
144
152
  const projectDir = path.dirname(ngPackagePath);
145
153
  ngPkg.dest = path.relative(projectDir, desiredDestAbs).split(path.sep).join('/');
146
154
  await writeJsonIfChanged(ngPackagePath, ngPkg);
147
- log?.debug?.('ng-package.json dest overridden', { dest: ngPkg.dest });
155
+ log?.debug?.('ng-package.json dest overridden', {dest: ngPkg.dest});
148
156
  }
149
157
 
150
158
  if (noModels) {
@@ -155,6 +163,6 @@ export async function generateBridgeWorkspace(params) {
155
163
 
156
164
  if (!spec) throw new Error('Internal error: spec is required when noModels=false');
157
165
  log?.step?.('génération des models (OpenAPI)');
158
- await generateModels({ cwd, libName, spec, requiredMode, formats, include, exclude, debug });
166
+ await generateModels({cwd, libName, spec, requiredMode, formats, include, exclude, debug});
159
167
  log?.success?.('génération terminée');
160
168
  }