@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.
- package/README.md +705 -36
- package/dist/config-wrapper.d.ts +6 -0
- package/dist/config-wrapper.js +7 -0
- package/dist/config.d.ts +68 -0
- package/dist/config.js +234 -0
- 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/fluent-mapper.d.ts +80 -0
- package/dist/fluent-mapper.js +171 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/mapper.d.ts +27 -0
- package/dist/mapper.js +144 -0
- package/dist/orm/index.d.ts +2 -2
- package/dist/orm/types.d.ts +1 -1
- package/package.json +5 -2
- package/dist/actions/ai-operation-suggestion.d.ts +0 -27
- package/dist/actions/ai-operation-suggestion.js +0 -46
- package/dist/actions/ai-schema-suggestion.d.ts +0 -24
- package/dist/actions/ai-schema-suggestion.js +0 -46
|
@@ -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;
|
package/dist/config.d.ts
ADDED
|
@@ -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, '&').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
|
+
}
|