@neupgroup/mapper 1.1.0 → 1.2.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/README.md +37 -221
- package/dist/docs.d.ts +3 -0
- package/dist/docs.js +351 -0
- package/dist/env.d.ts +25 -0
- package/dist/env.js +88 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.js +280 -0
- package/dist/orm/index.d.ts +10 -0
- package/dist/orm/index.js +28 -0
- package/dist/orm/types.d.ts +25 -0
- package/dist/orm/types.js +1 -0
- package/package.json +12 -18
- package/api.d.ts +0 -22
- package/api.js +0 -14
- package/config.d.ts +0 -39
- package/config.js +0 -49
- package/db.d.ts +0 -19
- package/db.js +0 -15
- package/index.d.ts +0 -4
- package/index.js +0 -4
- package/mapper.d.ts +0 -59
- package/mapper.js +0 -117
package/README.md
CHANGED
|
@@ -1,221 +1,37 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
Neup.Mapper
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
async close() {
|
|
40
|
-
// close driver resources
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const db = createDb(adapter);
|
|
45
|
-
await db.connect({ host: "localhost" });
|
|
46
|
-
const rows = await db.query("SELECT 1");
|
|
47
|
-
await db.close();
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### API
|
|
51
|
-
|
|
52
|
-
```
|
|
53
|
-
import { createApiClient, IApiAdapter } from "@neupgroup/mapper";
|
|
54
|
-
|
|
55
|
-
const http: IApiAdapter = {
|
|
56
|
-
async request(req) {
|
|
57
|
-
// use fetch/axios/undici, return { status, headers, data }
|
|
58
|
-
return { status: 200, data: { ok: true } };
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const api = createApiClient("https://api.example.com", http, { Authorization: "Bearer token" });
|
|
63
|
-
const res = await api.get("/users");
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## Config & Registry (Multiple Named Maps)
|
|
67
|
-
|
|
68
|
-
You can define multiple named connections (maps), similar to Firebase/Laravel style configs, and then refer to them in your application.
|
|
69
|
-
|
|
70
|
-
Create a `mapper.config.ts` in your app:
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
// mapper.config.ts
|
|
74
|
-
import { defineConfig, IApiAdapter, IDbAdapter } from "@neupgroup/mapper";
|
|
75
|
-
|
|
76
|
-
// Provide your concrete adapters
|
|
77
|
-
const http: IApiAdapter = {
|
|
78
|
-
async request(req) {
|
|
79
|
-
// Make HTTP requests using fetch/axios and return { status, headers, data }
|
|
80
|
-
return { status: 200, data: { ok: true } };
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const dbAdapter: IDbAdapter = {
|
|
85
|
-
async connect(config) {
|
|
86
|
-
// Initialize DB driver with credentials from config
|
|
87
|
-
},
|
|
88
|
-
async query(sql, params) {
|
|
89
|
-
return [];
|
|
90
|
-
},
|
|
91
|
-
async close() {},
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
export default defineConfig({
|
|
95
|
-
maps: [
|
|
96
|
-
{
|
|
97
|
-
kind: "api",
|
|
98
|
-
name: "core",
|
|
99
|
-
baseUrl: "https://api.example.com",
|
|
100
|
-
defaultHeaders: { Authorization: "Bearer <token>" },
|
|
101
|
-
adapter: http,
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
kind: "db",
|
|
105
|
-
name: "analytics",
|
|
106
|
-
connection: { host: "db.local", user: "app", password: "secret" },
|
|
107
|
-
adapter: dbAdapter,
|
|
108
|
-
},
|
|
109
|
-
],
|
|
110
|
-
});
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
Use the registry in your app code:
|
|
114
|
-
|
|
115
|
-
```
|
|
116
|
-
import config from "./mapper.config";
|
|
117
|
-
import { loadConfig } from "@neupgroup/mapper";
|
|
118
|
-
|
|
119
|
-
const maps = loadConfig(config);
|
|
120
|
-
|
|
121
|
-
// Use API
|
|
122
|
-
const api = maps.api("core");
|
|
123
|
-
const res = await api.get("/users");
|
|
124
|
-
|
|
125
|
-
// Use DB
|
|
126
|
-
const db = maps.db("analytics");
|
|
127
|
-
await db.connect({ host: "db.local" });
|
|
128
|
-
const rows = await db.query("SELECT * FROM events WHERE kind = ?", ["click"]);
|
|
129
|
-
await db.close();
|
|
130
|
-
|
|
131
|
-
// Introspection
|
|
132
|
-
maps.list(); // [{ name: "core", kind: "api" }, { name: "analytics", kind: "db" }]
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
## Global `mapper()` API (No App Init)
|
|
136
|
-
|
|
137
|
-
If you prefer a PHP‑style fluent syntax without explicit app initialization, use the global `mapper()` function:
|
|
138
|
-
|
|
139
|
-
### Access existing maps (registered via config)
|
|
140
|
-
|
|
141
|
-
```
|
|
142
|
-
import appConfig from './mapper.config';
|
|
143
|
-
import { useConfig, mapper } from '@neupgroup/mapper';
|
|
144
|
-
|
|
145
|
-
// Register once (e.g., at app entry) — then use anywhere
|
|
146
|
-
useConfig(appConfig);
|
|
147
|
-
|
|
148
|
-
// DB usage
|
|
149
|
-
const usersTable = mapper('analytics').table('users');
|
|
150
|
-
const allUsers = await usersTable.selectAll();
|
|
151
|
-
const activeUsers = await usersTable.query('WHERE status = ?', ['active']);
|
|
152
|
-
|
|
153
|
-
// API usage
|
|
154
|
-
const usersEndpoint = mapper('core').path('/users');
|
|
155
|
-
const res = await usersEndpoint.get();
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
### Create ephemeral maps with chaining
|
|
159
|
-
|
|
160
|
-
```
|
|
161
|
-
import { mapper } from '@neupgroup/mapper';
|
|
162
|
-
import type { IDbAdapter, IApiAdapter } from '@neupgroup/mapper';
|
|
163
|
-
|
|
164
|
-
const dbAdapter: IDbAdapter = { /* implement connect/query/close */ };
|
|
165
|
-
const httpAdapter: IApiAdapter = { /* implement request */ };
|
|
166
|
-
|
|
167
|
-
// Create and register a DB map
|
|
168
|
-
mapper()
|
|
169
|
-
.create('tempDb', 'db')
|
|
170
|
-
.host('localhost')
|
|
171
|
-
.dbname('app')
|
|
172
|
-
.user('user')
|
|
173
|
-
.pass('secret')
|
|
174
|
-
.adapter(dbAdapter)
|
|
175
|
-
.save();
|
|
176
|
-
|
|
177
|
-
// Use it anywhere
|
|
178
|
-
const t = mapper('tempDb').table('logs');
|
|
179
|
-
const rows = await t.query('WHERE level = ?', ['error']);
|
|
180
|
-
|
|
181
|
-
// Create and register an API map
|
|
182
|
-
mapper()
|
|
183
|
-
.create('tempApi', 'api')
|
|
184
|
-
.baseUrl('https://api.example.com')
|
|
185
|
-
.header('Authorization', 'Bearer token')
|
|
186
|
-
.adapter(httpAdapter)
|
|
187
|
-
.save();
|
|
188
|
-
|
|
189
|
-
const endpoint = mapper('tempApi').path('/events');
|
|
190
|
-
await endpoint.post({ type: 'click' });
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
Notes:
|
|
194
|
-
- `mapper('name').table('...')` is for DB maps; `mapper('name').path('...')` is for API maps.
|
|
195
|
-
- Duplicate map names are prevented; attempting to re‑use a name throws a clear error.
|
|
196
|
-
- The builder requires `create(name, kind)` and `adapter(...)` before `save()`.
|
|
197
|
-
|
|
198
|
-
### Notes
|
|
199
|
-
|
|
200
|
-
- The registry does not auto-connect databases; call `db.connect()` when appropriate.
|
|
201
|
-
- The API client merges `defaultHeaders` with any per-call headers.
|
|
202
|
-
- Each map is keyed by a unique `name`; use `maps.get(name)` if you don’t know the type at compile time.
|
|
203
|
-
|
|
204
|
-
## API Reference
|
|
205
|
-
|
|
206
|
-
- `createDb(adapter: IDbAdapter): Db`
|
|
207
|
-
- `createApiClient(baseUrl: string, adapter: IApiAdapter, defaultHeaders?: Record<string,string>): ApiClient`
|
|
208
|
-
- `defineConfig(config: MapperConfig): MapperConfig`
|
|
209
|
-
- `createRegistry(config: MapperConfig): MapperRegistry`
|
|
210
|
-
- `loadConfig(config: MapperConfig): MapperRegistry` (alias)
|
|
211
|
-
|
|
212
|
-
## Publish
|
|
213
|
-
|
|
214
|
-
This package is configured for public publish (scoped):
|
|
215
|
-
|
|
216
|
-
```
|
|
217
|
-
npm run build
|
|
218
|
-
npm publish --access public
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
Ensure your `package.json` has `"private": false` and `"publishConfig": { "access": "public" }`.
|
|
1
|
+
# @neupgroup/mapper
|
|
2
|
+
|
|
3
|
+
Core library for Neup.Mapper. Provides simple schema registration utilities that can be expanded over time.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @neupgroup/mapper
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import Mapper from '@neupgroup/mapper';
|
|
15
|
+
|
|
16
|
+
const mapper = new Mapper();
|
|
17
|
+
|
|
18
|
+
mapper.register({
|
|
19
|
+
name: 'User',
|
|
20
|
+
fields: [
|
|
21
|
+
{ name: 'id', type: 'string' },
|
|
22
|
+
{ name: 'email', type: 'string' },
|
|
23
|
+
{ name: 'createdAt', type: 'date' },
|
|
24
|
+
],
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
console.log(mapper.list());
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Build
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx tsc -p ./tsconfig.json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Outputs are generated to `dist/` with type declarations.
|
|
37
|
+
|
package/dist/docs.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const documentationMd = "\n# Mapper Library Documentation\n\nWelcome to `@neupgroup/mapper`. This guide covers:\n- Installation\n- Configuring connections (DSL and UI)\n- Using connections in code\n- Creating schemas (ORM)\n- Configuring and using schemas\n- CRUD operations: insert, update, delete, fetch\n- Error handling and troubleshooting\n\n---\n\n## Installation\n\n- Install from npm:\n `npm install @neupgroup/mapper`\n- In this workspace, the app depends on the library via `workspace:*`. Build the library when you update it:\n `cd library && npm run build`\n- Import helpers:\n `import { parseConnectionsDsl, toNormalizedConnections } from '@neupgroup/mapper'`\n\n---\n\n## Configure Connections\n\nYou can configure connections in two ways:\n\n1) DSL File (recommended)\n2) UI Configure page (runtime setup)\n\n### 1) DSL Format\n\nCreate `connections.dsl` at your project root:\n\n```\nconnections = [\n mysql_prod: {\n type: mysql\n host: 127.0.0.1\n port: 3306\n user: root\n password: \"s3cr3t\"\n database: appdb\n }\n\n mongo_dev: {\n type: mongodb\n uri: \"mongodb://127.0.0.1:27017\"\n database: devdb\n }\n\n firestore_local: {\n type: firestore\n projectId: my-project\n applicationDefault: true\n }\n\n http_api: {\n type: api\n baseUrl: \"https://api.example.com\"\n token: \"abc123\"\n }\n]\n```\n\nNotes:\n- `type` (or `dbType`) defaults to `api` if omitted.\n- Values can be unquoted or quoted; comments using `#` are ignored.\n\nParse and normalize:\n\n```ts\nimport { parseConnectionsDsl, toNormalizedConnections } from '@neupgroup/mapper'\n\nconst text = await fs.promises.readFile('connections.dsl', 'utf8')\nconst envMap = parseConnectionsDsl(text)\nconst connections = toNormalizedConnections(envMap)\n// connections: Array<{ name, type, key }>\n```\n\n### 2) UI Configure Page\n\n- Go to `/configure` to define connections at runtime.\n- Use \"Generate collective env\" to produce `connections.dsl` from configured connections.\n- Download the file and commit it or load at startup.\n\n---\n\n## Use Connections in Code\n\n### Normalize and route by type\n\n```ts\nimport { parseConnectionsDsl, toNormalizedConnections } from '@neupgroup/mapper'\nimport { connection, schema } from '@neupgroup/mapper'\n\nconst text = await fs.promises.readFile('connections.dsl', 'utf8')\nconst envMap = parseConnectionsDsl(text)\nconst conns = toNormalizedConnections(envMap)\n\n// Register connections\nconst conRegistry = connection()\nfor (const c of conns) {\n conRegistry.register({ name: c.name, type: c.type, key: c.key })\n}\n\n// Use with schemas\nconst sm = schema(conRegistry)\n```\n\n### Direct construction\n\n```ts\nconst conRegistry = connection()\nconRegistry.create('mysql_prod', 'mysql').key({\n host: '127.0.0.1',\n port: 3306,\n user: 'root',\n password: 's3cr3t',\n database: 'appdb',\n})\n```\n\n---\n\n## Schemas and Models\n\nSchemas define structure for collections/tables bound to a connection.\n\n### Define and register a schema\n\n```ts\nimport { schema } from '@neupgroup/mapper'\n\nconst sm = schema(conRegistry)\n\nsm.create('User')\n .use({ connection: 'mysql_prod', collection: 'users' })\n .setStructure({\n id: 'string primary',\n email: 'string unique',\n name: 'string editable',\n createdAt: 'date',\n '?field': 'allow-undefined', // optional: permit fields not listed\n })\n\nconst User = sm.use('User')\n```\n\n---\n\n## CRUD Operations\n\nAll operations return Promises and may throw on errors.\n\n### Insert\n\n```ts\nconst createdId = await User.add({\n id: 'u_123',\n email: 'alice@example.com',\n name: 'Alice',\n createdAt: new Date(),\n})\n```\n\n### Update\n\n```ts\nawait User.where(['id', 'u_123']).to({ name: 'Alice Cooper' }).updateOne()\n```\n\n### Delete\n\n```ts\nawait User.where(['id', 'u_123']).deleteOne()\n```\n\n### Fetch / Query\n\n```ts\nconst one = await User.where(['id', 'u_123']).getOne()\nconst many = await User.where('email', '%@example.com', 'like').get()\n```\n\n---\n\n## Schema Configuration Details\n\n- `primary`: marks primary key; used for updates/deletes.\n- `unique`: enforces uniqueness in supported backends.\n- `editable`: indicates fields commonly modified via UI.\n- `type`: one of `string`, `number`, `boolean`, `date`, `int`.\n- `?field`: enables accepting fields not defined in the schema.\n\n---\n\n## Error Handling\n\nWrap operations in `try/catch` and inspect known error shapes.\n\n```ts\ntry {\n const user = await User.where(['id', 'u_404']).getOne()\n if (!user) {\n // handle not found gracefully\n }\n} catch (err) {\n if (err && typeof err === 'object' && 'code' in err) {\n console.error('Database error code:', (err as any).code)\n }\n console.error('Unexpected error', err)\n}\n```\n\nRecommendations:\n- Validate required creds before initializing connections.\n- Prefer parameterized queries or ORM filters over string concatenation.\n- Log request IDs and timestamps for audit trails.\n\n---\n\n## Troubleshooting\n\n- \"Connection refused\": check host/port/firewall and credentials.\n- \"Authentication failed\": verify tokens/passwords and token scopes.\n- \"Timeout\": review network paths and optimize query.\n- \"Schema mismatch\": ensure field names/types match the backend.\n\n---\n\n## API Quick Reference\n\n- `parseConnectionsDsl(text)` \u2192 Map of connectionName \u2192 key/value creds\n- `toNormalizedConnections(map)` \u2192 Array of { name, type, key }\n- `connection()` \u2192 Connection registry (create/register/list/get)\n- `schema(connections?)` \u2192 Schema manager; define, register, and use schemas\n\n---\n\n## End-to-End Example\n\n```ts\nimport { parseConnectionsDsl, toNormalizedConnections, connection, schema } from '@neupgroup/mapper'\n\nconst text = await fs.promises.readFile('connections.dsl', 'utf8')\nconst map = parseConnectionsDsl(text)\nconst conns = toNormalizedConnections(map)\n\nconst conRegistry = connection()\nfor (const c of conns) conRegistry.register({ name: c.name, type: c.type, key: c.key })\n\nconst sm = schema(conRegistry)\nsm.create('Product')\n .use({ connection: conns[0].name, collection: 'products' })\n .setStructure({\n id: 'string primary',\n title: 'string',\n price: 'number',\n tags: 'string',\n })\nconst Product = sm.use('Product')\n\nawait Product.add({ id: 'p_1', title: 'Widget', price: 9.99, tags: 'sale' })\nawait Product.where(['id', 'p_1']).to({ price: 7.99 }).updateOne()\nconst items = await Product.where('price', 10, '<').get()\nawait Product.where(['id', 'p_1']).deleteOne()\n```\n";
|
|
2
|
+
export declare function markdownToHtml(md: string): string;
|
|
3
|
+
export declare function getDocumentationHtml(): string;
|
package/dist/docs.js
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
// Centralized documentation for @neupgroup/mapper.
|
|
2
|
+
// Exports Markdown plus a minimal Markdown→HTML converter so apps can render
|
|
3
|
+
// without a heavy dependency. Keep content comprehensive and up-to-date.
|
|
4
|
+
export const documentationMd = `
|
|
5
|
+
# Mapper Library Documentation
|
|
6
|
+
|
|
7
|
+
Welcome to \`@neupgroup/mapper\`. This guide covers:
|
|
8
|
+
- Installation
|
|
9
|
+
- Configuring connections (DSL and UI)
|
|
10
|
+
- Using connections in code
|
|
11
|
+
- Creating schemas (ORM)
|
|
12
|
+
- Configuring and using schemas
|
|
13
|
+
- CRUD operations: insert, update, delete, fetch
|
|
14
|
+
- Error handling and troubleshooting
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
- Install from npm:
|
|
21
|
+
\`npm install @neupgroup/mapper\`
|
|
22
|
+
- In this workspace, the app depends on the library via \`workspace:*\`. Build the library when you update it:
|
|
23
|
+
\`cd library && npm run build\`
|
|
24
|
+
- Import helpers:
|
|
25
|
+
\`import { parseConnectionsDsl, toNormalizedConnections } from '@neupgroup/mapper'\`
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Configure Connections
|
|
30
|
+
|
|
31
|
+
You can configure connections in two ways:
|
|
32
|
+
|
|
33
|
+
1) DSL File (recommended)
|
|
34
|
+
2) UI Configure page (runtime setup)
|
|
35
|
+
|
|
36
|
+
### 1) DSL Format
|
|
37
|
+
|
|
38
|
+
Create \`connections.dsl\` at your project root:
|
|
39
|
+
|
|
40
|
+
\`\`\`
|
|
41
|
+
connections = [
|
|
42
|
+
mysql_prod: {
|
|
43
|
+
type: mysql
|
|
44
|
+
host: 127.0.0.1
|
|
45
|
+
port: 3306
|
|
46
|
+
user: root
|
|
47
|
+
password: "s3cr3t"
|
|
48
|
+
database: appdb
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
mongo_dev: {
|
|
52
|
+
type: mongodb
|
|
53
|
+
uri: "mongodb://127.0.0.1:27017"
|
|
54
|
+
database: devdb
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
firestore_local: {
|
|
58
|
+
type: firestore
|
|
59
|
+
projectId: my-project
|
|
60
|
+
applicationDefault: true
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
http_api: {
|
|
64
|
+
type: api
|
|
65
|
+
baseUrl: "https://api.example.com"
|
|
66
|
+
token: "abc123"
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
\`\`\`
|
|
70
|
+
|
|
71
|
+
Notes:
|
|
72
|
+
- \`type\` (or \`dbType\`) defaults to \`api\` if omitted.
|
|
73
|
+
- Values can be unquoted or quoted; comments using \`#\` are ignored.
|
|
74
|
+
|
|
75
|
+
Parse and normalize:
|
|
76
|
+
|
|
77
|
+
\`\`\`ts
|
|
78
|
+
import { parseConnectionsDsl, toNormalizedConnections } from '@neupgroup/mapper'
|
|
79
|
+
|
|
80
|
+
const text = await fs.promises.readFile('connections.dsl', 'utf8')
|
|
81
|
+
const envMap = parseConnectionsDsl(text)
|
|
82
|
+
const connections = toNormalizedConnections(envMap)
|
|
83
|
+
// connections: Array<{ name, type, key }>
|
|
84
|
+
\`\`\`
|
|
85
|
+
|
|
86
|
+
### 2) UI Configure Page
|
|
87
|
+
|
|
88
|
+
- Go to \`/configure\` to define connections at runtime.
|
|
89
|
+
- Use \"Generate collective env\" to produce \`connections.dsl\` from configured connections.
|
|
90
|
+
- Download the file and commit it or load at startup.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Use Connections in Code
|
|
95
|
+
|
|
96
|
+
### Normalize and route by type
|
|
97
|
+
|
|
98
|
+
\`\`\`ts
|
|
99
|
+
import { parseConnectionsDsl, toNormalizedConnections } from '@neupgroup/mapper'
|
|
100
|
+
import { connection, schema } from '@neupgroup/mapper'
|
|
101
|
+
|
|
102
|
+
const text = await fs.promises.readFile('connections.dsl', 'utf8')
|
|
103
|
+
const envMap = parseConnectionsDsl(text)
|
|
104
|
+
const conns = toNormalizedConnections(envMap)
|
|
105
|
+
|
|
106
|
+
// Register connections
|
|
107
|
+
const conRegistry = connection()
|
|
108
|
+
for (const c of conns) {
|
|
109
|
+
conRegistry.register({ name: c.name, type: c.type, key: c.key })
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Use with schemas
|
|
113
|
+
const sm = schema(conRegistry)
|
|
114
|
+
\`\`\`
|
|
115
|
+
|
|
116
|
+
### Direct construction
|
|
117
|
+
|
|
118
|
+
\`\`\`ts
|
|
119
|
+
const conRegistry = connection()
|
|
120
|
+
conRegistry.create('mysql_prod', 'mysql').key({
|
|
121
|
+
host: '127.0.0.1',
|
|
122
|
+
port: 3306,
|
|
123
|
+
user: 'root',
|
|
124
|
+
password: 's3cr3t',
|
|
125
|
+
database: 'appdb',
|
|
126
|
+
})
|
|
127
|
+
\`\`\`
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Schemas and Models
|
|
132
|
+
|
|
133
|
+
Schemas define structure for collections/tables bound to a connection.
|
|
134
|
+
|
|
135
|
+
### Define and register a schema
|
|
136
|
+
|
|
137
|
+
\`\`\`ts
|
|
138
|
+
import { schema } from '@neupgroup/mapper'
|
|
139
|
+
|
|
140
|
+
const sm = schema(conRegistry)
|
|
141
|
+
|
|
142
|
+
sm.create('User')
|
|
143
|
+
.use({ connection: 'mysql_prod', collection: 'users' })
|
|
144
|
+
.setStructure({
|
|
145
|
+
id: 'string primary',
|
|
146
|
+
email: 'string unique',
|
|
147
|
+
name: 'string editable',
|
|
148
|
+
createdAt: 'date',
|
|
149
|
+
'?field': 'allow-undefined', // optional: permit fields not listed
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
const User = sm.use('User')
|
|
153
|
+
\`\`\`
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## CRUD Operations
|
|
158
|
+
|
|
159
|
+
All operations return Promises and may throw on errors.
|
|
160
|
+
|
|
161
|
+
### Insert
|
|
162
|
+
|
|
163
|
+
\`\`\`ts
|
|
164
|
+
const createdId = await User.add({
|
|
165
|
+
id: 'u_123',
|
|
166
|
+
email: 'alice@example.com',
|
|
167
|
+
name: 'Alice',
|
|
168
|
+
createdAt: new Date(),
|
|
169
|
+
})
|
|
170
|
+
\`\`\`
|
|
171
|
+
|
|
172
|
+
### Update
|
|
173
|
+
|
|
174
|
+
\`\`\`ts
|
|
175
|
+
await User.where(['id', 'u_123']).to({ name: 'Alice Cooper' }).updateOne()
|
|
176
|
+
\`\`\`
|
|
177
|
+
|
|
178
|
+
### Delete
|
|
179
|
+
|
|
180
|
+
\`\`\`ts
|
|
181
|
+
await User.where(['id', 'u_123']).deleteOne()
|
|
182
|
+
\`\`\`
|
|
183
|
+
|
|
184
|
+
### Fetch / Query
|
|
185
|
+
|
|
186
|
+
\`\`\`ts
|
|
187
|
+
const one = await User.where(['id', 'u_123']).getOne()
|
|
188
|
+
const many = await User.where('email', '%@example.com', 'like').get()
|
|
189
|
+
\`\`\`
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Schema Configuration Details
|
|
194
|
+
|
|
195
|
+
- \`primary\`: marks primary key; used for updates/deletes.
|
|
196
|
+
- \`unique\`: enforces uniqueness in supported backends.
|
|
197
|
+
- \`editable\`: indicates fields commonly modified via UI.
|
|
198
|
+
- \`type\`: one of \`string\`, \`number\`, \`boolean\`, \`date\`, \`int\`.
|
|
199
|
+
- \`?field\`: enables accepting fields not defined in the schema.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Error Handling
|
|
204
|
+
|
|
205
|
+
Wrap operations in \`try/catch\` and inspect known error shapes.
|
|
206
|
+
|
|
207
|
+
\`\`\`ts
|
|
208
|
+
try {
|
|
209
|
+
const user = await User.where(['id', 'u_404']).getOne()
|
|
210
|
+
if (!user) {
|
|
211
|
+
// handle not found gracefully
|
|
212
|
+
}
|
|
213
|
+
} catch (err) {
|
|
214
|
+
if (err && typeof err === 'object' && 'code' in err) {
|
|
215
|
+
console.error('Database error code:', (err as any).code)
|
|
216
|
+
}
|
|
217
|
+
console.error('Unexpected error', err)
|
|
218
|
+
}
|
|
219
|
+
\`\`\`
|
|
220
|
+
|
|
221
|
+
Recommendations:
|
|
222
|
+
- Validate required creds before initializing connections.
|
|
223
|
+
- Prefer parameterized queries or ORM filters over string concatenation.
|
|
224
|
+
- Log request IDs and timestamps for audit trails.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Troubleshooting
|
|
229
|
+
|
|
230
|
+
- \"Connection refused\": check host/port/firewall and credentials.
|
|
231
|
+
- \"Authentication failed\": verify tokens/passwords and token scopes.
|
|
232
|
+
- \"Timeout\": review network paths and optimize query.
|
|
233
|
+
- \"Schema mismatch\": ensure field names/types match the backend.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## API Quick Reference
|
|
238
|
+
|
|
239
|
+
- \`parseConnectionsDsl(text)\` → Map of connectionName → key/value creds
|
|
240
|
+
- \`toNormalizedConnections(map)\` → Array of { name, type, key }
|
|
241
|
+
- \`connection()\` → Connection registry (create/register/list/get)
|
|
242
|
+
- \`schema(connections?)\` → Schema manager; define, register, and use schemas
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## End-to-End Example
|
|
247
|
+
|
|
248
|
+
\`\`\`ts
|
|
249
|
+
import { parseConnectionsDsl, toNormalizedConnections, connection, schema } from '@neupgroup/mapper'
|
|
250
|
+
|
|
251
|
+
const text = await fs.promises.readFile('connections.dsl', 'utf8')
|
|
252
|
+
const map = parseConnectionsDsl(text)
|
|
253
|
+
const conns = toNormalizedConnections(map)
|
|
254
|
+
|
|
255
|
+
const conRegistry = connection()
|
|
256
|
+
for (const c of conns) conRegistry.register({ name: c.name, type: c.type, key: c.key })
|
|
257
|
+
|
|
258
|
+
const sm = schema(conRegistry)
|
|
259
|
+
sm.create('Product')
|
|
260
|
+
.use({ connection: conns[0].name, collection: 'products' })
|
|
261
|
+
.setStructure({
|
|
262
|
+
id: 'string primary',
|
|
263
|
+
title: 'string',
|
|
264
|
+
price: 'number',
|
|
265
|
+
tags: 'string',
|
|
266
|
+
})
|
|
267
|
+
const Product = sm.use('Product')
|
|
268
|
+
|
|
269
|
+
await Product.add({ id: 'p_1', title: 'Widget', price: 9.99, tags: 'sale' })
|
|
270
|
+
await Product.where(['id', 'p_1']).to({ price: 7.99 }).updateOne()
|
|
271
|
+
const items = await Product.where('price', 10, '<').get()
|
|
272
|
+
await Product.where(['id', 'p_1']).deleteOne()
|
|
273
|
+
\`\`\`
|
|
274
|
+
`;
|
|
275
|
+
// Minimal Markdown → HTML converter (headings, lists, code fences, inline code, paragraphs)
|
|
276
|
+
export function markdownToHtml(md) {
|
|
277
|
+
const lines = md.split(/\r?\n/);
|
|
278
|
+
let html = [];
|
|
279
|
+
let inCode = false;
|
|
280
|
+
let codeBuf = [];
|
|
281
|
+
function escapeHtml(s) {
|
|
282
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
283
|
+
}
|
|
284
|
+
function inline(s) {
|
|
285
|
+
s = s.replace(/`([^`]+)`/g, (_m, g1) => `<code>${escapeHtml(g1)}</code>`);
|
|
286
|
+
s = s.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
|
287
|
+
s = s.replace(/\*([^*]+)\*/g, '<em>$1</em>');
|
|
288
|
+
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
|
289
|
+
return s;
|
|
290
|
+
}
|
|
291
|
+
function flushParagraph(buf) {
|
|
292
|
+
const text = buf.join(' ').trim();
|
|
293
|
+
if (!text)
|
|
294
|
+
return;
|
|
295
|
+
html.push(`<p>${inline(escapeHtml(text))}</p>`);
|
|
296
|
+
buf.length = 0;
|
|
297
|
+
}
|
|
298
|
+
let paraBuf = [];
|
|
299
|
+
for (let i = 0; i < lines.length; i++) {
|
|
300
|
+
const line = lines[i];
|
|
301
|
+
if (line.trim().startsWith('```')) {
|
|
302
|
+
if (inCode) {
|
|
303
|
+
html.push(`<pre><code>${escapeHtml(codeBuf.join('\n'))}</code></pre>`);
|
|
304
|
+
codeBuf = [];
|
|
305
|
+
inCode = false;
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
flushParagraph(paraBuf);
|
|
309
|
+
inCode = true;
|
|
310
|
+
}
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (inCode) {
|
|
314
|
+
codeBuf.push(line);
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
const hMatch = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
318
|
+
if (hMatch) {
|
|
319
|
+
flushParagraph(paraBuf);
|
|
320
|
+
const level = hMatch[1].length;
|
|
321
|
+
const content = hMatch[2];
|
|
322
|
+
html.push(`<h${level}>${inline(escapeHtml(content))}</h${level}>`);
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (/^\s*[-*]\s+/.test(line)) {
|
|
326
|
+
flushParagraph(paraBuf);
|
|
327
|
+
let ulItems = [];
|
|
328
|
+
ulItems.push(line.replace(/^\s*[-*]\s+/, ''));
|
|
329
|
+
while (i + 1 < lines.length && /^\s*[-*]\s+/.test(lines[i + 1])) {
|
|
330
|
+
i++;
|
|
331
|
+
ulItems.push(lines[i].replace(/^\s*[-*]\s+/, ''));
|
|
332
|
+
}
|
|
333
|
+
const lis = ulItems.map(item => `<li>${inline(escapeHtml(item))}</li>`).join('');
|
|
334
|
+
html.push(`<ul>${lis}</ul>`);
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (/^\s*$/.test(line)) {
|
|
338
|
+
flushParagraph(paraBuf);
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
paraBuf.push(line);
|
|
342
|
+
}
|
|
343
|
+
if (inCode) {
|
|
344
|
+
html.push(`<pre><code>${codeBuf.map(s => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')).join('\n')}</code></pre>`);
|
|
345
|
+
}
|
|
346
|
+
flushParagraph(paraBuf);
|
|
347
|
+
return html.join('\n');
|
|
348
|
+
}
|
|
349
|
+
export function getDocumentationHtml() {
|
|
350
|
+
return markdownToHtml(documentationMd);
|
|
351
|
+
}
|
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type EnvDslConnections = Record<string, Record<string, string>>;
|
|
2
|
+
/**
|
|
3
|
+
* Parse a simple DSL of the form:
|
|
4
|
+
* connections = [
|
|
5
|
+
* connectionName = [
|
|
6
|
+
* key: value,
|
|
7
|
+
* key2: "value2",
|
|
8
|
+
* ],
|
|
9
|
+
* other = [
|
|
10
|
+
* type: SQL,
|
|
11
|
+
* host: localhost,
|
|
12
|
+
* ]
|
|
13
|
+
* ]
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseConnectionsDsl(text: string): EnvDslConnections;
|
|
16
|
+
export type NormalizedConnection = {
|
|
17
|
+
name: string;
|
|
18
|
+
type: 'mysql' | 'sql' | 'firestore' | 'mongodb' | 'api';
|
|
19
|
+
key: Record<string, any>;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Convert DSL map to normalized connections compatible with library Connections.
|
|
23
|
+
* If no explicit type is provided, defaults to 'api'.
|
|
24
|
+
*/
|
|
25
|
+
export declare function toNormalizedConnections(map: EnvDslConnections): NormalizedConnection[];
|