@obsidiane/meridiane 0.0.6 → 0.0.7

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.
Files changed (37) hide show
  1. package/README.md +42 -2
  2. package/cli.js +27 -1
  3. package/models.config.example.js +15 -0
  4. package/package.json +1 -3
  5. package/templates/_lib_template/src/lib/bridge/rest/api-platform.adapter.ts +48 -47
  6. package/templates/_lib_template/src/lib/bridge/rest/query-builder.ts +29 -29
  7. package/templates/_lib_template/src/lib/bridge/sse/eventsource-wrapper.ts +50 -54
  8. package/templates/_lib_template/src/lib/bridge/sse/mercure-topic.mapper.ts +48 -0
  9. package/templates/_lib_template/src/lib/bridge/sse/mercure-url.builder.ts +7 -7
  10. package/templates/_lib_template/src/lib/bridge/sse/mercure.adapter.ts +104 -49
  11. package/templates/_lib_template/src/lib/bridge/sse/ref-count-topic.registry.ts +23 -21
  12. package/templates/_lib_template/src/lib/bridge.types.ts +33 -0
  13. package/templates/_lib_template/src/lib/facades/bridge.facade.ts +116 -0
  14. package/templates/_lib_template/src/lib/facades/facade.factory.ts +21 -19
  15. package/templates/_lib_template/src/lib/facades/facade.interface.ts +13 -18
  16. package/templates/_lib_template/src/lib/facades/resource.facade.ts +42 -56
  17. package/templates/_lib_template/src/lib/interceptors/bridge-debug.interceptor.ts +32 -0
  18. package/templates/_lib_template/src/lib/interceptors/bridge-defaults.interceptor.ts +53 -0
  19. package/templates/_lib_template/src/lib/interceptors/content-type.interceptor.ts +36 -37
  20. package/templates/_lib_template/src/lib/ports/realtime.port.ts +5 -3
  21. package/templates/_lib_template/src/lib/ports/resource-repository.port.ts +14 -15
  22. package/templates/_lib_template/src/lib/provide-bridge.ts +122 -19
  23. package/templates/_lib_template/src/lib/tokens.ts +14 -1
  24. package/templates/_lib_template/src/lib/utils/url.ts +15 -0
  25. package/templates/_lib_template/src/public-api.ts +3 -2
  26. package/tools/dev-bridge.js +138 -0
  27. package/tools/generate-lib.js +60 -45
  28. package/tools/generate-models.js +73 -19
  29. package/tools/generator/models/openapi-to-models.js +13 -2
  30. package/tools/generator/models/schema-utils.js +59 -0
  31. package/tools/generator/models/utils.js +8 -2
  32. package/tools/init.js +148 -0
  33. package/tools/sandbox-bridge.js +77 -0
  34. package/tools/utils/cli-args.js +41 -0
  35. package/tools/utils/dotenv.js +55 -0
  36. package/templates/_lib_template/src/lib/bridge/credentials.policy.ts +0 -9
  37. package/templates/_lib_template/src/lib/utils/utils.ts +0 -21
package/tools/init.js ADDED
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env node
2
+ import path from 'node:path';
3
+ import process from 'node:process';
4
+ import {fileURLToPath} from 'node:url';
5
+ import fs from 'node:fs/promises';
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+
9
+ function hasFlag(name) {
10
+ return process.argv.includes(`--${name}`);
11
+ }
12
+
13
+ async function pathExists(p) {
14
+ try {
15
+ await fs.stat(p);
16
+ return true;
17
+ } catch {
18
+ return false;
19
+ }
20
+ }
21
+
22
+ async function ensureDir(p) {
23
+ await fs.mkdir(p, {recursive: true});
24
+ }
25
+
26
+ async function writeFileIfMissing(filePath, content, {force} = {}) {
27
+ const exists = await pathExists(filePath);
28
+ if (exists && !force) return {written: false, reason: 'exists'};
29
+ await ensureDir(path.dirname(filePath));
30
+ await fs.writeFile(filePath, content, 'utf8');
31
+ return {written: true};
32
+ }
33
+
34
+ async function copyFileIfMissing(src, dest, {force} = {}) {
35
+ const exists = await pathExists(dest);
36
+ if (exists && !force) return {written: false, reason: 'exists'};
37
+ await ensureDir(path.dirname(dest));
38
+ await fs.copyFile(src, dest);
39
+ return {written: true};
40
+ }
41
+
42
+ async function main() {
43
+ const force = hasFlag('force');
44
+ const debug = /^(1|true|yes)$/i.test(process.env.MERIDIANE_DEBUG ?? '') || hasFlag('debug');
45
+
46
+ const cwd = process.cwd();
47
+ const pkgRoot = path.resolve(__dirname, '..');
48
+
49
+ const outDir = path.resolve(cwd, 'meridiane');
50
+ await ensureDir(outDir);
51
+
52
+ const modelsExample = path.join(pkgRoot, 'models.config.example.js');
53
+ const modelsOut = path.resolve(cwd, 'models.config.js');
54
+ const envExampleOut = path.resolve(cwd, '.env.example');
55
+
56
+ const angularExampleOut = path.join(outDir, 'angular.example.ts');
57
+ const readmeOut = path.join(outDir, 'README.md');
58
+
59
+ if (debug) console.log('[meridiane init] cwd=', cwd);
60
+
61
+ const copiedCfg = await copyFileIfMissing(modelsExample, modelsOut, {force});
62
+ const wroteEnv = await writeFileIfMissing(
63
+ envExampleOut,
64
+ [
65
+ '# Meridiane (CLI) - exemple de configuration',
66
+ '# Copiez en `.env` pour activer la config locale.',
67
+ '',
68
+ '# Logs',
69
+ 'MERIDIANE_DEBUG=0',
70
+ '',
71
+ '# meridiane lib',
72
+ '# MERIDIANE_LIB_VERSION=0.1.0',
73
+ '# MERIDIANE_NPM_REGISTRY_URL=https://registry.npmjs.org/',
74
+ '',
75
+ '# meridiane models',
76
+ '# MERIDIANE_MODELS_OUT=projects/backend-bridge/src/models',
77
+ '# MERIDIANE_MODELS_REQUIRED_MODE=spec',
78
+ '# MERIDIANE_MODELS_ITEM_IMPORT=../lib/ports/resource-repository.port',
79
+ '# MERIDIANE_MODELS_PRESET=all',
80
+ '# MERIDIANE_MODELS_INDEX=1',
81
+ '',
82
+ '# Runtime (Angular) - à recopier dans provideBridge(...)',
83
+ '# MERIDIANE_API_BASE_URL=http://localhost:8000',
84
+ '# MERIDIANE_MERCURE_HUB_URL=http://localhost:8000/.well-known/mercure',
85
+ '',
86
+ ].join('\n'),
87
+ {force}
88
+ );
89
+
90
+ const wroteAngular = await writeFileIfMissing(
91
+ angularExampleOut,
92
+ [
93
+ "import {provideBridge} from '@acme/backend-bridge';",
94
+ '',
95
+ 'export const BRIDGE_PROVIDERS = [',
96
+ ' provideBridge({',
97
+ " baseUrl: 'http://localhost:8000',",
98
+ ' mercure: {',
99
+ " hubUrl: 'http://localhost:8000/.well-known/mercure',",
100
+ " init: {credentials: 'include'},",
101
+ ' },',
102
+ ' defaults: {',
103
+ ' headers: {',
104
+ " // 'X-Correlation-Id': crypto.randomUUID(),",
105
+ ' },',
106
+ ' timeoutMs: 30_000,',
107
+ ' retries: {count: 1, delayMs: 250},',
108
+ ' },',
109
+ ' // auth: {type: \"bearer\", token: \"<JWT>\"},',
110
+ ' debug: false,',
111
+ ' }),',
112
+ '];',
113
+ '',
114
+ ].join('\n'),
115
+ {force}
116
+ );
117
+
118
+ const wroteReadme = await writeFileIfMissing(
119
+ readmeOut,
120
+ [
121
+ '# Meridiane – fichiers générés',
122
+ '',
123
+ 'Ces fichiers sont créés par `meridiane init` pour accélérer l’intégration.',
124
+ '',
125
+ '## CLI',
126
+ '',
127
+ '- `models.config.js` : configuration par défaut de `meridiane models` (chargée depuis le CWD).',
128
+ "- `.env` : optionnel. Le CLI charge automatiquement `.env` (CWD) avant d'exécuter `lib/models`.",
129
+ '',
130
+ '## Angular',
131
+ '',
132
+ '- `angular.example.ts` : exemple `provideBridge({ baseUrl, mercure, defaults, auth, debug })` à adapter.',
133
+ '',
134
+ ].join('\n'),
135
+ {force}
136
+ );
137
+
138
+ console.log('✅ meridiane init');
139
+ console.log(`- ${path.relative(cwd, modelsOut)} ${copiedCfg.written ? 'créé' : '(existe déjà)'}`);
140
+ console.log(`- ${path.relative(cwd, envExampleOut)} ${wroteEnv.written ? 'créé' : '(existe déjà)'}`);
141
+ console.log(`- ${path.relative(cwd, angularExampleOut)} ${wroteAngular.written ? 'créé' : '(existe déjà)'}`);
142
+ console.log(`- ${path.relative(cwd, readmeOut)} ${wroteReadme.written ? 'créé' : '(existe déjà)'}`);
143
+ }
144
+
145
+ main().catch((err) => {
146
+ console.error(err);
147
+ process.exit(1);
148
+ });
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ import path from 'node:path';
3
+ import process from 'node:process';
4
+ import {fileURLToPath} from 'node:url';
5
+ import {spawn} from 'node:child_process';
6
+ import {loadDotEnv} from './utils/dotenv.js';
7
+ import {getArg, hasFlag} from './utils/cli-args.js';
8
+
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ const repoRoot = path.resolve(__dirname, '../../..');
11
+
12
+ function run(args) {
13
+ return new Promise((resolve) => {
14
+ const child = spawn(process.execPath, args, {stdio: 'inherit', env: process.env});
15
+ child.on('exit', (code) => resolve(code ?? 0));
16
+ });
17
+ }
18
+
19
+ function printHelp() {
20
+ console.log(`
21
+ Usage:
22
+ meridiane sandbox-bridge [options]
23
+
24
+ This command regenerates the Angular bridge library used by the sandbox:
25
+ - workspace: apps/sandbox
26
+ - libName: bridge-sandbox
27
+ - package: @obsidiane/bridge-sandbox
28
+ - version: 0.1.0 (override with --version)
29
+
30
+ Options:
31
+ --backend=http://localhost:8000
32
+ --spec=/api/docs.json
33
+ --models-out=projects/bridge-sandbox/src/models
34
+ --required-mode=all-optional|spec
35
+ --preset=all|native
36
+ --include=<substr>[,<substr>…] (repeatable)
37
+ --exclude=<substr>[,<substr>…] (repeatable)
38
+ --index=1|0
39
+ --no-models
40
+ --debug
41
+
42
+ Example:
43
+ npx -y @obsidiane/meridiane sandbox-bridge --backend=http://localhost:8000
44
+ `);
45
+ }
46
+
47
+ async function main() {
48
+ await loadDotEnv();
49
+
50
+ if (hasFlag('help') || hasFlag('h')) {
51
+ printHelp();
52
+ process.exit(0);
53
+ }
54
+
55
+ const version = getArg('version', process.env.MERIDIANE_SANDBOX_BRIDGE_VERSION ?? '0.1.0');
56
+
57
+ const passThrough = process.argv.slice(2).filter((a) => a.startsWith('--') && !a.startsWith('--version='));
58
+
59
+ const devBridgePath = path.join(__dirname, 'dev-bridge.js');
60
+ const workspaceAbs = path.join(repoRoot, 'apps', 'sandbox');
61
+ const args = [
62
+ devBridgePath,
63
+ 'bridge-sandbox',
64
+ '@obsidiane/bridge-sandbox',
65
+ version,
66
+ `--workspace=${workspaceAbs}`,
67
+ ...passThrough,
68
+ ];
69
+
70
+ const code = await run(args);
71
+ process.exit(code);
72
+ }
73
+
74
+ main().catch((err) => {
75
+ console.error(err);
76
+ process.exit(1);
77
+ });
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @param {string} name
3
+ * @param {string|undefined} def
4
+ * @param {string[]} [argv]
5
+ * @returns {string|undefined}
6
+ */
7
+ export function getArg(name, def, argv = process.argv) {
8
+ const a = argv.find((x) => x.startsWith(`--${name}=`));
9
+ return a ? a.split('=')[1] : def;
10
+ }
11
+
12
+ /**
13
+ * @param {string} name
14
+ * @param {string[]} [argv]
15
+ * @returns {string[]}
16
+ */
17
+ export function getArgs(name, argv = process.argv) {
18
+ return argv
19
+ .filter((x) => x.startsWith(`--${name}=`))
20
+ .map((x) => x.slice(`--${name}=`.length));
21
+ }
22
+
23
+ /**
24
+ * @param {string} name
25
+ * @param {string[]} [argv]
26
+ */
27
+ export function hasFlag(name, argv = process.argv) {
28
+ return argv.includes(`--${name}`);
29
+ }
30
+
31
+ /**
32
+ * @param {unknown} v
33
+ * @param {boolean} def
34
+ */
35
+ export function asBool(v, def) {
36
+ if (typeof v !== 'string') return def;
37
+ if (/^(1|true|yes)$/i.test(v)) return true;
38
+ if (/^(0|false|no)$/i.test(v)) return false;
39
+ return def;
40
+ }
41
+
@@ -0,0 +1,55 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ /**
5
+ * Charge un fichier `.env` (format KEY=VALUE) dans process.env, sans écraser les variables déjà définies.
6
+ * @param {{cwd?: string, filename?: string}} [opts]
7
+ * @returns {Promise<Record<string, string>>} variables chargées
8
+ */
9
+ export async function loadDotEnv(opts = {}) {
10
+ const cwd = opts.cwd ?? process.cwd();
11
+ const filename = opts.filename ?? '.env';
12
+ const envPath = await findUp(cwd, filename);
13
+ if (!envPath) return {};
14
+
15
+ const raw = await fs.readFile(envPath, 'utf8');
16
+
17
+ const loaded = {};
18
+ for (const line of raw.split(/\r?\n/)) {
19
+ const trimmed = line.trim();
20
+ if (!trimmed || trimmed.startsWith('#')) continue;
21
+ const normalized = trimmed.startsWith('export ') ? trimmed.slice('export '.length).trim() : trimmed;
22
+ const idx = normalized.indexOf('=');
23
+ if (idx < 0) continue;
24
+ const key = normalized.slice(0, idx).trim();
25
+ let value = normalized.slice(idx + 1).trim();
26
+ if (!key) continue;
27
+
28
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
29
+ value = value.slice(1, -1);
30
+ }
31
+
32
+ if (process.env[key] === undefined) {
33
+ process.env[key] = value;
34
+ loaded[key] = value;
35
+ }
36
+ }
37
+
38
+ return loaded;
39
+ }
40
+
41
+ async function findUp(startDir, fileName) {
42
+ let dir = startDir;
43
+ while (true) {
44
+ const candidate = path.join(dir, fileName);
45
+ try {
46
+ const st = await fs.stat(candidate);
47
+ if (st.isFile()) return candidate;
48
+ } catch {
49
+ // ignore
50
+ }
51
+ const parent = path.dirname(dir);
52
+ if (parent === dir) return undefined;
53
+ dir = parent;
54
+ }
55
+ }
@@ -1,9 +0,0 @@
1
- export class CredentialsPolicy {
2
- constructor(private readonly init: unknown) {
3
- }
4
-
5
- withCredentials(): boolean {
6
- const c = this.init as any;
7
- return c?.withCredentials === true || c?.credentials === 'include';
8
- }
9
- }
@@ -1,21 +0,0 @@
1
- import {WritableSignal} from '@angular/core';
2
- import {Item} from '../ports/resource-repository.port';
3
-
4
- export function upsertInSignal(
5
- signal: WritableSignal<readonly Item[]>,
6
- item: Item,
7
- ): void {
8
- signal.update(items => {
9
-
10
- const id = item['@id'];
11
- const existingIndex = items.findIndex(it => it['@id'] === id);
12
-
13
- if (existingIndex === -1) {
14
- return [item, ...items];
15
- }
16
-
17
- const next = items.slice() as Item[];
18
- next[existingIndex] = item;
19
- return next;
20
- });
21
- }