@neupgroup/mapper 1.2.0 → 1.2.2

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.
@@ -0,0 +1,6 @@
1
+ import { ConfigBasedMapper, createDefaultMapper, createConfigMapper, getConfigMapper } from './config';
2
+ import type { MapperConfig, ConnectionConfig, DatabaseConnectionConfig, ApiConnectionConfig } from './config';
3
+ export { ConfigBasedMapper, createDefaultMapper, createConfigMapper, getConfigMapper };
4
+ export type { MapperConfig, ConnectionConfig, DatabaseConnectionConfig, ApiConnectionConfig };
5
+ export declare const mapper: ConfigBasedMapper;
6
+ export default mapper;
@@ -0,0 +1,7 @@
1
+ import { ConfigBasedMapper, createDefaultMapper, createConfigMapper, getConfigMapper } from './config';
2
+ // Re-export the config-based system as the primary interface
3
+ export { ConfigBasedMapper, createDefaultMapper, createConfigMapper, getConfigMapper };
4
+ // Create and export a default configured mapper instance
5
+ export const mapper = createDefaultMapper();
6
+ // Export the config-based mapper as the default export
7
+ export default mapper;
@@ -0,0 +1,68 @@
1
+ import { Connections, SchemaManager } from './index';
2
+ export interface DatabaseConnectionConfig {
3
+ name: string;
4
+ type: 'mysql' | 'sql' | 'firestore' | 'mongodb';
5
+ host: string;
6
+ port: number;
7
+ database: string;
8
+ user: string;
9
+ password?: string;
10
+ ssl?: boolean;
11
+ [key: string]: any;
12
+ }
13
+ export interface ApiConnectionConfig {
14
+ name: string;
15
+ type: 'api';
16
+ url: string;
17
+ headers?: Record<string, string>;
18
+ timeout?: number;
19
+ [key: string]: any;
20
+ }
21
+ export type ConnectionConfig = DatabaseConnectionConfig | ApiConnectionConfig;
22
+ export interface ConfigSchema {
23
+ name: string;
24
+ connection: string;
25
+ collection: string;
26
+ structure?: Record<string, string> | Array<{
27
+ name: string;
28
+ type: 'string' | 'number' | 'boolean' | 'date' | 'int';
29
+ [key: string]: any;
30
+ }>;
31
+ }
32
+ export interface MapperConfig {
33
+ connections: ConnectionConfig[];
34
+ schemas?: ConfigSchema[];
35
+ }
36
+ export declare class ConfigLoader {
37
+ private static instance;
38
+ private config?;
39
+ static getInstance(): ConfigLoader;
40
+ load(config: MapperConfig): void;
41
+ loadFromFile(path: string): void;
42
+ getConfig(): MapperConfig | undefined;
43
+ }
44
+ export declare class ConfigBasedMapper {
45
+ private mapper;
46
+ private configLoader;
47
+ private initialized;
48
+ constructor();
49
+ configure(config: MapperConfig): this;
50
+ configureFromFile(path: string): this;
51
+ private initializeFromConfig;
52
+ private initializeConnection;
53
+ private initializeSchema;
54
+ getConnections(): Connections;
55
+ getSchemaManager(): SchemaManager;
56
+ use(schemaName: string): any;
57
+ schema(name: string): any;
58
+ connect(name: string, type: 'mysql' | 'sql' | 'firestore' | 'mongodb' | 'api', config: Record<string, any>): import("./mapper").Mapper;
59
+ get(schemaName: string, filters?: Record<string, any>): Promise<Record<string, any>[]>;
60
+ getOne(schemaName: string, filters?: Record<string, any>): Promise<Record<string, any> | null>;
61
+ add(schemaName: string, data: Record<string, any>): Promise<any>;
62
+ update(schemaName: string, filters: Record<string, any>, data: Record<string, any>): Promise<void>;
63
+ delete(schemaName: string, filters: Record<string, any>): Promise<void>;
64
+ }
65
+ export declare function createConfigMapper(config?: MapperConfig): ConfigBasedMapper;
66
+ export declare function getConfigMapper(): ConfigBasedMapper;
67
+ export declare function createDefaultMapper(config?: MapperConfig): ConfigBasedMapper;
68
+ export default ConfigBasedMapper;
package/dist/config.js ADDED
@@ -0,0 +1,234 @@
1
+ import { createMapper } from './mapper';
2
+ export class ConfigLoader {
3
+ static getInstance() {
4
+ if (!ConfigLoader.instance) {
5
+ ConfigLoader.instance = new ConfigLoader();
6
+ }
7
+ return ConfigLoader.instance;
8
+ }
9
+ load(config) {
10
+ this.config = config;
11
+ }
12
+ loadFromFile(path) {
13
+ try {
14
+ // In Node.js environment
15
+ if (typeof require !== 'undefined') {
16
+ const fs = require('fs');
17
+ const configData = fs.readFileSync(path, 'utf8');
18
+ this.config = JSON.parse(configData);
19
+ }
20
+ else {
21
+ // In browser environment, fetch the config file
22
+ fetch(path)
23
+ .then(response => response.json())
24
+ .then(config => {
25
+ this.config = config;
26
+ })
27
+ .catch((error) => {
28
+ throw new Error(`Failed to load config from ${path}: ${error.message}`);
29
+ });
30
+ }
31
+ }
32
+ catch (error) {
33
+ throw new Error(`Failed to load config from ${path}: ${error.message}`);
34
+ }
35
+ }
36
+ getConfig() {
37
+ return this.config;
38
+ }
39
+ }
40
+ export class ConfigBasedMapper {
41
+ constructor() {
42
+ this.initialized = false;
43
+ this.mapper = createMapper();
44
+ this.configLoader = ConfigLoader.getInstance();
45
+ }
46
+ configure(config) {
47
+ this.configLoader.load(config);
48
+ this.initializeFromConfig();
49
+ return this;
50
+ }
51
+ configureFromFile(path) {
52
+ this.configLoader.loadFromFile(path);
53
+ this.initializeFromConfig();
54
+ return this;
55
+ }
56
+ initializeFromConfig() {
57
+ const config = this.configLoader.getConfig();
58
+ if (!config) {
59
+ throw new Error('No configuration loaded');
60
+ }
61
+ // Initialize connections
62
+ for (const connectionConfig of config.connections) {
63
+ this.initializeConnection(connectionConfig);
64
+ }
65
+ // Initialize schemas
66
+ if (config.schemas) {
67
+ for (const schemaConfig of config.schemas) {
68
+ this.initializeSchema(schemaConfig);
69
+ }
70
+ }
71
+ this.initialized = true;
72
+ }
73
+ initializeConnection(config) {
74
+ const { name, type } = config;
75
+ if (type === 'api') {
76
+ const apiConfig = config;
77
+ this.mapper.connect(name, type, apiConfig);
78
+ }
79
+ else {
80
+ const dbConfig = config;
81
+ this.mapper.connect(name, type, dbConfig);
82
+ }
83
+ }
84
+ initializeSchema(config) {
85
+ const schemaBuilder = this.mapper.schema(config.name);
86
+ schemaBuilder.use({ connection: config.connection, collection: config.collection });
87
+ if (config.structure) {
88
+ schemaBuilder.setStructure(config.structure);
89
+ }
90
+ }
91
+ // Delegate methods to the underlying mapper
92
+ getConnections() {
93
+ return this.mapper.getConnections();
94
+ }
95
+ getSchemaManager() {
96
+ return this.mapper.getSchemaManager();
97
+ }
98
+ use(schemaName) {
99
+ if (!this.initialized) {
100
+ throw new Error('Mapper not initialized. Call configure() first.');
101
+ }
102
+ return this.mapper.use(schemaName);
103
+ }
104
+ schema(name) {
105
+ if (!this.initialized) {
106
+ throw new Error('Mapper not initialized. Call configure() first.');
107
+ }
108
+ return this.mapper.schema(name);
109
+ }
110
+ connect(name, type, config) {
111
+ if (!this.initialized) {
112
+ throw new Error('Mapper not initialized. Call configure() first.');
113
+ }
114
+ return this.mapper.connect(name, type, config);
115
+ }
116
+ // Quick query methods
117
+ async get(schemaName, filters) {
118
+ if (!this.initialized) {
119
+ throw new Error('Mapper not initialized. Call configure() first.');
120
+ }
121
+ return this.mapper.get(schemaName, filters);
122
+ }
123
+ async getOne(schemaName, filters) {
124
+ if (!this.initialized) {
125
+ throw new Error('Mapper not initialized. Call configure() first.');
126
+ }
127
+ return this.mapper.getOne(schemaName, filters);
128
+ }
129
+ async add(schemaName, data) {
130
+ if (!this.initialized) {
131
+ throw new Error('Mapper not initialized. Call configure() first.');
132
+ }
133
+ return this.mapper.add(schemaName, data);
134
+ }
135
+ async update(schemaName, filters, data) {
136
+ if (!this.initialized) {
137
+ throw new Error('Mapper not initialized. Call configure() first.');
138
+ }
139
+ return this.mapper.update(schemaName, filters, data);
140
+ }
141
+ async delete(schemaName, filters) {
142
+ if (!this.initialized) {
143
+ throw new Error('Mapper not initialized. Call configure() first.');
144
+ }
145
+ return this.mapper.delete(schemaName, filters);
146
+ }
147
+ }
148
+ // Create a default instance
149
+ let defaultConfigMapper = null;
150
+ export function createConfigMapper(config) {
151
+ const mapper = new ConfigBasedMapper();
152
+ if (config) {
153
+ mapper.configure(config);
154
+ }
155
+ defaultConfigMapper = mapper;
156
+ return mapper;
157
+ }
158
+ // Export a function to get the default instance
159
+ export function getConfigMapper() {
160
+ if (!defaultConfigMapper) {
161
+ defaultConfigMapper = new ConfigBasedMapper();
162
+ }
163
+ return defaultConfigMapper;
164
+ }
165
+ // Create a default configured mapper instance
166
+ export function createDefaultMapper(config) {
167
+ const mapper = new ConfigBasedMapper();
168
+ // If no config provided, try to load from environment or default locations
169
+ if (!config) {
170
+ // Try to load from environment
171
+ const envConfig = loadConfigFromEnvironment();
172
+ if (envConfig) {
173
+ mapper.configure(envConfig);
174
+ }
175
+ else {
176
+ // Try to load from default config file locations
177
+ const defaultPaths = ['./mapper.config.json', './config/mapper.json', '/etc/mapper/config.json'];
178
+ for (const path of defaultPaths) {
179
+ try {
180
+ mapper.configureFromFile(path);
181
+ break;
182
+ }
183
+ catch (error) {
184
+ // Continue trying other paths
185
+ }
186
+ }
187
+ }
188
+ }
189
+ else {
190
+ mapper.configure(config);
191
+ }
192
+ return mapper;
193
+ }
194
+ function loadConfigFromEnvironment() {
195
+ if (typeof process !== 'undefined' && process.env) {
196
+ // Check for MAPPER_CONFIG environment variable
197
+ const envConfig = process.env.MAPPER_CONFIG;
198
+ if (envConfig) {
199
+ try {
200
+ return JSON.parse(envConfig);
201
+ }
202
+ catch (error) {
203
+ console.warn('Failed to parse MAPPER_CONFIG environment variable:', error);
204
+ }
205
+ }
206
+ // Check for individual database connection environment variables
207
+ const databaseUrl = process.env.DATABASE_URL;
208
+ if (databaseUrl) {
209
+ return {
210
+ connections: [{
211
+ name: 'default',
212
+ type: inferConnectionType(databaseUrl),
213
+ host: databaseUrl,
214
+ port: 5432,
215
+ database: 'default',
216
+ user: 'default'
217
+ }]
218
+ };
219
+ }
220
+ }
221
+ return null;
222
+ }
223
+ function inferConnectionType(url) {
224
+ if (url.includes('mysql'))
225
+ return 'mysql';
226
+ if (url.includes('postgres') || url.includes('postgresql'))
227
+ return 'sql';
228
+ if (url.includes('mongodb'))
229
+ return 'mongodb';
230
+ if (url.includes('firestore'))
231
+ return 'firestore';
232
+ return 'sql'; // default to sql
233
+ }
234
+ export default ConfigBasedMapper;
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')).join('\n')}</code></pre>`);
345
+ }
346
+ flushParagraph(paraBuf);
347
+ return html.join('\n');
348
+ }
349
+ export function getDocumentationHtml() {
350
+ return markdownToHtml(documentationMd);
351
+ }