@neupgroup/mapper 1.1.0 → 1.2.0
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/actions/ai-operation-suggestion.d.ts +27 -0
- package/dist/actions/ai-operation-suggestion.js +46 -0
- package/dist/actions/ai-schema-suggestion.d.ts +24 -0
- package/dist/actions/ai-schema-suggestion.js +46 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.js +278 -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
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from 'genkit';
|
|
2
|
+
export declare const AIOperationSuggestionInputSchema: z.ZodObject<{
|
|
3
|
+
operationDescription: z.ZodString;
|
|
4
|
+
databaseType: z.ZodString;
|
|
5
|
+
schema: z.ZodString;
|
|
6
|
+
}, "strip", z.ZodTypeAny, {
|
|
7
|
+
databaseType: string;
|
|
8
|
+
operationDescription: string;
|
|
9
|
+
schema: string;
|
|
10
|
+
}, {
|
|
11
|
+
databaseType: string;
|
|
12
|
+
operationDescription: string;
|
|
13
|
+
schema: string;
|
|
14
|
+
}>;
|
|
15
|
+
export type AIOperationSuggestionInput = z.infer<typeof AIOperationSuggestionInputSchema>;
|
|
16
|
+
export declare const AIOperationSuggestionOutputSchema: z.ZodObject<{
|
|
17
|
+
suggestedCode: z.ZodString;
|
|
18
|
+
rationale: z.ZodString;
|
|
19
|
+
}, "strip", z.ZodTypeAny, {
|
|
20
|
+
rationale: string;
|
|
21
|
+
suggestedCode: string;
|
|
22
|
+
}, {
|
|
23
|
+
rationale: string;
|
|
24
|
+
suggestedCode: string;
|
|
25
|
+
}>;
|
|
26
|
+
export type AIOperationSuggestionOutput = z.infer<typeof AIOperationSuggestionOutputSchema>;
|
|
27
|
+
export declare function createSuggestOperation(ai: any): (input: AIOperationSuggestionInput) => Promise<any>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from 'genkit';
|
|
2
|
+
export const AIOperationSuggestionInputSchema = z.object({
|
|
3
|
+
operationDescription: z
|
|
4
|
+
.string()
|
|
5
|
+
.describe('A detailed description of the operation to be performed.'),
|
|
6
|
+
databaseType: z
|
|
7
|
+
.string()
|
|
8
|
+
.describe('The type of database (e.g., MongoDB, Firestore, SQL).'),
|
|
9
|
+
schema: z.string().describe('The JSON schema of the data structure.'),
|
|
10
|
+
});
|
|
11
|
+
export const AIOperationSuggestionOutputSchema = z.object({
|
|
12
|
+
suggestedCode: z
|
|
13
|
+
.string()
|
|
14
|
+
.describe('The suggested code for the operation, as a string.'),
|
|
15
|
+
rationale: z
|
|
16
|
+
.string()
|
|
17
|
+
.describe('Explanation of why the suggested code is optimal.'),
|
|
18
|
+
});
|
|
19
|
+
export function createSuggestOperation(ai) {
|
|
20
|
+
const aiOperationSuggestionPrompt = ai.definePrompt({
|
|
21
|
+
name: 'aiOperationSuggestionPrompt',
|
|
22
|
+
input: { schema: AIOperationSuggestionInputSchema },
|
|
23
|
+
output: { schema: AIOperationSuggestionOutputSchema },
|
|
24
|
+
prompt: `You are an expert database engineer. Given a description of a database operation, a database type, and a data schema, you will generate the code to perform that operation. The operation can be for inserting, fetching, or updating data.
|
|
25
|
+
|
|
26
|
+
Operation Description: {{{operationDescription}}}
|
|
27
|
+
Database Type: {{{databaseType}}}
|
|
28
|
+
Schema: {{{schema}}}
|
|
29
|
+
|
|
30
|
+
Generate the code for the specified database type. Provide a rationale for the generated code.
|
|
31
|
+
|
|
32
|
+
Return the suggested code as a string, and include the rationale.
|
|
33
|
+
`,
|
|
34
|
+
});
|
|
35
|
+
const aiOperationSuggestionFlow = ai.defineFlow({
|
|
36
|
+
name: 'aiOperationSuggestionFlow',
|
|
37
|
+
inputSchema: AIOperationSuggestionInputSchema,
|
|
38
|
+
outputSchema: AIOperationSuggestionOutputSchema,
|
|
39
|
+
}, async (input) => {
|
|
40
|
+
const { output } = await aiOperationSuggestionPrompt(input);
|
|
41
|
+
return output;
|
|
42
|
+
});
|
|
43
|
+
return async function suggestOperation(input) {
|
|
44
|
+
return aiOperationSuggestionFlow(input);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'genkit';
|
|
2
|
+
export declare const AISchemaSuggestionInputSchema: z.ZodObject<{
|
|
3
|
+
dataDescription: z.ZodString;
|
|
4
|
+
databaseType: z.ZodString;
|
|
5
|
+
}, "strip", z.ZodTypeAny, {
|
|
6
|
+
dataDescription: string;
|
|
7
|
+
databaseType: string;
|
|
8
|
+
}, {
|
|
9
|
+
dataDescription: string;
|
|
10
|
+
databaseType: string;
|
|
11
|
+
}>;
|
|
12
|
+
export type AISchemaSuggestionInput = z.infer<typeof AISchemaSuggestionInputSchema>;
|
|
13
|
+
export declare const AISchemaSuggestionOutputSchema: z.ZodObject<{
|
|
14
|
+
suggestedSchema: z.ZodString;
|
|
15
|
+
rationale: z.ZodString;
|
|
16
|
+
}, "strip", z.ZodTypeAny, {
|
|
17
|
+
suggestedSchema: string;
|
|
18
|
+
rationale: string;
|
|
19
|
+
}, {
|
|
20
|
+
suggestedSchema: string;
|
|
21
|
+
rationale: string;
|
|
22
|
+
}>;
|
|
23
|
+
export type AISchemaSuggestionOutput = z.infer<typeof AISchemaSuggestionOutputSchema>;
|
|
24
|
+
export declare function createSuggestSchema(ai: any): (input: AISchemaSuggestionInput) => Promise<any>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from 'genkit';
|
|
2
|
+
export const AISchemaSuggestionInputSchema = z.object({
|
|
3
|
+
dataDescription: z
|
|
4
|
+
.string()
|
|
5
|
+
.describe('A detailed description of the data that the schema should represent.'),
|
|
6
|
+
databaseType: z
|
|
7
|
+
.string()
|
|
8
|
+
.describe('The type of database for which the schema is being created (e.g., MongoDB, Firestore, SQL).'),
|
|
9
|
+
});
|
|
10
|
+
export const AISchemaSuggestionOutputSchema = z.object({
|
|
11
|
+
suggestedSchema: z
|
|
12
|
+
.string()
|
|
13
|
+
.describe('The suggested schema structure, formatted as a JSON string, tailored for the specified database type.'),
|
|
14
|
+
rationale: z
|
|
15
|
+
.string()
|
|
16
|
+
.describe('Explanation of why the suggested schema is optimal for the given data description and database type.'),
|
|
17
|
+
});
|
|
18
|
+
export function createSuggestSchema(ai) {
|
|
19
|
+
const aiSchemaSuggestionPrompt = ai.definePrompt({
|
|
20
|
+
name: 'aiSchemaSuggestionPrompt',
|
|
21
|
+
input: { schema: AISchemaSuggestionInputSchema },
|
|
22
|
+
output: { schema: AISchemaSuggestionOutputSchema },
|
|
23
|
+
prompt: `You are an expert database architect. Given a description of data and a database type, you will suggest an optimal schema structure.
|
|
24
|
+
|
|
25
|
+
Data Description: {{{dataDescription}}}
|
|
26
|
+
Database Type: {{{databaseType}}}
|
|
27
|
+
|
|
28
|
+
Consider the characteristics and constraints of the specified database type when formulating the schema.
|
|
29
|
+
|
|
30
|
+
Return the suggested schema as a JSON string, and include a rationale explaining why the schema is optimal.
|
|
31
|
+
|
|
32
|
+
Ensure that the suggested schema is valid and can be directly used with the specified database.
|
|
33
|
+
`,
|
|
34
|
+
});
|
|
35
|
+
const aiSchemaSuggestionFlow = ai.defineFlow({
|
|
36
|
+
name: 'aiSchemaSuggestionFlow',
|
|
37
|
+
inputSchema: AISchemaSuggestionInputSchema,
|
|
38
|
+
outputSchema: AISchemaSuggestionOutputSchema,
|
|
39
|
+
}, async (input) => {
|
|
40
|
+
const { output } = await aiSchemaSuggestionPrompt(input);
|
|
41
|
+
return output;
|
|
42
|
+
});
|
|
43
|
+
return async function suggestSchema(input) {
|
|
44
|
+
return aiSchemaSuggestionFlow(input);
|
|
45
|
+
};
|
|
46
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { DbAdapter, QueryOptions } from './orm';
|
|
2
|
+
export type ColumnType = 'string' | 'number' | 'boolean' | 'date' | 'int';
|
|
3
|
+
export type ConnectionType = 'mysql' | 'sql' | 'firestore' | 'mongodb' | 'api';
|
|
4
|
+
export interface Field {
|
|
5
|
+
name: string;
|
|
6
|
+
type: ColumnType;
|
|
7
|
+
editable?: boolean;
|
|
8
|
+
autoIncrement?: boolean;
|
|
9
|
+
nullable?: boolean;
|
|
10
|
+
defaultValue?: unknown;
|
|
11
|
+
}
|
|
12
|
+
export interface SchemaDef {
|
|
13
|
+
name: string;
|
|
14
|
+
connectionName: string;
|
|
15
|
+
collectionName: string;
|
|
16
|
+
fields: Field[];
|
|
17
|
+
allowUndefinedFields?: boolean;
|
|
18
|
+
}
|
|
19
|
+
interface ConnectionConfig {
|
|
20
|
+
name: string;
|
|
21
|
+
type: ConnectionType;
|
|
22
|
+
key: Record<string, any>;
|
|
23
|
+
}
|
|
24
|
+
declare class ConnectionBuilder {
|
|
25
|
+
private manager;
|
|
26
|
+
private name;
|
|
27
|
+
private type;
|
|
28
|
+
constructor(manager: Connections, name: string, type: ConnectionType);
|
|
29
|
+
key(config: Record<string, any>): Connections;
|
|
30
|
+
}
|
|
31
|
+
export declare class Connections {
|
|
32
|
+
private connections;
|
|
33
|
+
private adapters;
|
|
34
|
+
create(name: string, type: ConnectionType): ConnectionBuilder;
|
|
35
|
+
register(config: ConnectionConfig): this;
|
|
36
|
+
attachAdapter(name: string, adapter: DbAdapter): this;
|
|
37
|
+
get(name: string): ConnectionConfig | undefined;
|
|
38
|
+
getAdapter(name: string): DbAdapter | undefined;
|
|
39
|
+
list(): ConnectionConfig[];
|
|
40
|
+
}
|
|
41
|
+
declare class SchemaBuilder {
|
|
42
|
+
private manager;
|
|
43
|
+
private name;
|
|
44
|
+
private connectionName?;
|
|
45
|
+
private collectionName?;
|
|
46
|
+
private fields;
|
|
47
|
+
private allowUndefinedFields;
|
|
48
|
+
constructor(manager: SchemaManager, name: string);
|
|
49
|
+
use(options: {
|
|
50
|
+
connection: string;
|
|
51
|
+
collection: string;
|
|
52
|
+
}): this;
|
|
53
|
+
setStructure(structure: Record<string, string> | Field[]): SchemaManager;
|
|
54
|
+
}
|
|
55
|
+
declare class SchemaQuery {
|
|
56
|
+
private manager;
|
|
57
|
+
private def;
|
|
58
|
+
private filters;
|
|
59
|
+
private rawWhere;
|
|
60
|
+
private pendingUpdate;
|
|
61
|
+
constructor(manager: SchemaManager, def: SchemaDef);
|
|
62
|
+
where(fieldOrPair: string | [string, any], value?: any, operator?: string): this;
|
|
63
|
+
whereComplex(raw: string): this;
|
|
64
|
+
private buildOptions;
|
|
65
|
+
to(update: Record<string, any>): this;
|
|
66
|
+
get(): Promise<Record<string, any>[]>;
|
|
67
|
+
getOne(): Promise<Record<string, any> | null>;
|
|
68
|
+
add(data: Record<string, any>): Promise<string>;
|
|
69
|
+
delete(): Promise<void>;
|
|
70
|
+
deleteOne(): Promise<void>;
|
|
71
|
+
update(): Promise<void>;
|
|
72
|
+
updateOne(): Promise<void>;
|
|
73
|
+
}
|
|
74
|
+
export declare class SchemaManager {
|
|
75
|
+
private connections;
|
|
76
|
+
private schemas;
|
|
77
|
+
constructor(connections: Connections);
|
|
78
|
+
create(name: string): SchemaBuilder;
|
|
79
|
+
register(def: SchemaDef): this;
|
|
80
|
+
use(name: string): SchemaQuery;
|
|
81
|
+
getAdapter(connectionName: string): DbAdapter | undefined;
|
|
82
|
+
list(): SchemaDef[];
|
|
83
|
+
}
|
|
84
|
+
export declare function connection(): Connections;
|
|
85
|
+
export declare function schema(conns?: Connections): SchemaManager;
|
|
86
|
+
export declare const schemas: SchemaManager;
|
|
87
|
+
export { createOrm } from './orm';
|
|
88
|
+
export type { DbAdapter, QueryOptions };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
class AdapterRegistry {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.adaptersByConnection = new Map();
|
|
4
|
+
}
|
|
5
|
+
attach(connectionName, adapter) {
|
|
6
|
+
this.adaptersByConnection.set(connectionName, adapter);
|
|
7
|
+
}
|
|
8
|
+
get(connectionName) {
|
|
9
|
+
return this.adaptersByConnection.get(connectionName);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
class ConnectionBuilder {
|
|
13
|
+
constructor(manager, name, type) {
|
|
14
|
+
this.manager = manager;
|
|
15
|
+
this.name = name;
|
|
16
|
+
this.type = type;
|
|
17
|
+
}
|
|
18
|
+
key(config) {
|
|
19
|
+
this.manager.register({ name: this.name, type: this.type, key: config });
|
|
20
|
+
return this.manager;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class Connections {
|
|
24
|
+
constructor() {
|
|
25
|
+
this.connections = new Map();
|
|
26
|
+
this.adapters = new AdapterRegistry();
|
|
27
|
+
}
|
|
28
|
+
create(name, type) {
|
|
29
|
+
if (this.connections.has(name)) {
|
|
30
|
+
throw new Error(`Connection with name '${name}' already exists`);
|
|
31
|
+
}
|
|
32
|
+
return new ConnectionBuilder(this, name, type);
|
|
33
|
+
}
|
|
34
|
+
register(config) {
|
|
35
|
+
if (this.connections.has(config.name)) {
|
|
36
|
+
throw new Error(`Connection with name '${config.name}' already exists`);
|
|
37
|
+
}
|
|
38
|
+
this.connections.set(config.name, config);
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
attachAdapter(name, adapter) {
|
|
42
|
+
if (!this.connections.has(name)) {
|
|
43
|
+
throw new Error(`Cannot attach adapter: unknown connection '${name}'`);
|
|
44
|
+
}
|
|
45
|
+
this.adapters.attach(name, adapter);
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
get(name) {
|
|
49
|
+
return this.connections.get(name);
|
|
50
|
+
}
|
|
51
|
+
getAdapter(name) {
|
|
52
|
+
return this.adapters.get(name);
|
|
53
|
+
}
|
|
54
|
+
list() {
|
|
55
|
+
return Array.from(this.connections.values());
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function parseDescriptorStructure(struct) {
|
|
59
|
+
let allowUndefinedFields = false;
|
|
60
|
+
const fields = [];
|
|
61
|
+
for (const [key, descriptor] of Object.entries(struct)) {
|
|
62
|
+
if (key === '?field') {
|
|
63
|
+
// Presence of '?field' enables accepting fields not defined in the schema
|
|
64
|
+
allowUndefinedFields = true;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const tokens = descriptor.split(/\s+/).map(t => t.trim().toLowerCase()).filter(Boolean);
|
|
68
|
+
const field = {
|
|
69
|
+
name: key,
|
|
70
|
+
type: tokens.find(t => ['string', 'number', 'boolean', 'date', 'int'].includes(t)) || 'string',
|
|
71
|
+
editable: tokens.includes('editable'),
|
|
72
|
+
autoIncrement: tokens.includes('auto_increment') || tokens.includes('autoincrement'),
|
|
73
|
+
};
|
|
74
|
+
fields.push(field);
|
|
75
|
+
}
|
|
76
|
+
return { fields, allowUndefinedFields };
|
|
77
|
+
}
|
|
78
|
+
class SchemaBuilder {
|
|
79
|
+
constructor(manager, name) {
|
|
80
|
+
this.manager = manager;
|
|
81
|
+
this.name = name;
|
|
82
|
+
this.fields = [];
|
|
83
|
+
this.allowUndefinedFields = false;
|
|
84
|
+
}
|
|
85
|
+
use(options) {
|
|
86
|
+
this.connectionName = options.connection;
|
|
87
|
+
this.collectionName = options.collection;
|
|
88
|
+
return this;
|
|
89
|
+
}
|
|
90
|
+
setStructure(structure) {
|
|
91
|
+
if (Array.isArray(structure)) {
|
|
92
|
+
this.fields = structure;
|
|
93
|
+
this.allowUndefinedFields = false;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
const parsed = parseDescriptorStructure(structure);
|
|
97
|
+
this.fields = parsed.fields;
|
|
98
|
+
this.allowUndefinedFields = parsed.allowUndefinedFields;
|
|
99
|
+
}
|
|
100
|
+
// Finalize schema registration
|
|
101
|
+
if (!this.connectionName || !this.collectionName) {
|
|
102
|
+
throw new Error('Schema.use({ connection, collection }) must be set before setStructure');
|
|
103
|
+
}
|
|
104
|
+
this.manager.register({
|
|
105
|
+
name: this.name,
|
|
106
|
+
connectionName: this.connectionName,
|
|
107
|
+
collectionName: this.collectionName,
|
|
108
|
+
fields: this.fields,
|
|
109
|
+
allowUndefinedFields: this.allowUndefinedFields,
|
|
110
|
+
});
|
|
111
|
+
return this.manager;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
class SchemaQuery {
|
|
115
|
+
constructor(manager, def) {
|
|
116
|
+
this.manager = manager;
|
|
117
|
+
this.def = def;
|
|
118
|
+
this.filters = [];
|
|
119
|
+
this.rawWhere = null;
|
|
120
|
+
this.pendingUpdate = null;
|
|
121
|
+
}
|
|
122
|
+
// where('field','value', operator?) or where([field, value])
|
|
123
|
+
where(fieldOrPair, value, operator) {
|
|
124
|
+
if (Array.isArray(fieldOrPair)) {
|
|
125
|
+
const [field, v] = fieldOrPair;
|
|
126
|
+
this.filters.push({ field, operator: '=', value: v });
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
const field = fieldOrPair;
|
|
130
|
+
this.filters.push({ field, operator: operator !== null && operator !== void 0 ? operator : '=', value });
|
|
131
|
+
}
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
// Accept a raw complex where clause string
|
|
135
|
+
whereComplex(raw) {
|
|
136
|
+
this.rawWhere = raw;
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
buildOptions() {
|
|
140
|
+
return {
|
|
141
|
+
collectionName: this.def.collectionName,
|
|
142
|
+
filters: this.filters.map(f => ({ field: f.field, operator: f.operator, value: f.value })),
|
|
143
|
+
limit: null,
|
|
144
|
+
offset: null,
|
|
145
|
+
sortBy: null,
|
|
146
|
+
fields: this.def.fields.map(f => f.name),
|
|
147
|
+
rawWhere: this.rawWhere,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
to(update) {
|
|
151
|
+
this.pendingUpdate = update;
|
|
152
|
+
return this;
|
|
153
|
+
}
|
|
154
|
+
async get() {
|
|
155
|
+
const adapter = this.manager.getAdapter(this.def.connectionName);
|
|
156
|
+
if (!adapter)
|
|
157
|
+
throw new Error(`No adapter attached for connection '${this.def.connectionName}'`);
|
|
158
|
+
const options = this.buildOptions();
|
|
159
|
+
const docs = adapter.get ? await adapter.get(options) : await adapter.getDocuments(options);
|
|
160
|
+
return docs;
|
|
161
|
+
}
|
|
162
|
+
async getOne() {
|
|
163
|
+
var _a;
|
|
164
|
+
const adapter = this.manager.getAdapter(this.def.connectionName);
|
|
165
|
+
if (!adapter)
|
|
166
|
+
throw new Error(`No adapter attached for connection '${this.def.connectionName}'`);
|
|
167
|
+
const options = this.buildOptions();
|
|
168
|
+
if (adapter.getOne) {
|
|
169
|
+
const one = await adapter.getOne(options);
|
|
170
|
+
return (_a = one) !== null && _a !== void 0 ? _a : null;
|
|
171
|
+
}
|
|
172
|
+
const results = adapter.get ? await adapter.get(options) : await adapter.getDocuments(options);
|
|
173
|
+
return results[0] || null;
|
|
174
|
+
}
|
|
175
|
+
async add(data) {
|
|
176
|
+
const adapter = this.manager.getAdapter(this.def.connectionName);
|
|
177
|
+
if (!adapter)
|
|
178
|
+
throw new Error(`No adapter attached for connection '${this.def.connectionName}'`);
|
|
179
|
+
if (!this.def.allowUndefinedFields) {
|
|
180
|
+
const allowed = new Set(this.def.fields.map(f => f.name));
|
|
181
|
+
data = Object.fromEntries(Object.entries(data).filter(([k]) => allowed.has(k)));
|
|
182
|
+
}
|
|
183
|
+
return adapter.addDocument(this.def.collectionName, data);
|
|
184
|
+
}
|
|
185
|
+
async delete() {
|
|
186
|
+
const adapter = this.manager.getAdapter(this.def.connectionName);
|
|
187
|
+
if (!adapter)
|
|
188
|
+
throw new Error(`No adapter attached for connection '${this.def.connectionName}'`);
|
|
189
|
+
const docs = await this.get();
|
|
190
|
+
// Expect each doc has an 'id' field
|
|
191
|
+
for (const d of docs) {
|
|
192
|
+
const id = d.id;
|
|
193
|
+
if (!id)
|
|
194
|
+
throw new Error('Document missing id; cannot delete');
|
|
195
|
+
await adapter.deleteDocument(this.def.collectionName, id);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async deleteOne() {
|
|
199
|
+
const one = await this.getOne();
|
|
200
|
+
if (!one)
|
|
201
|
+
return;
|
|
202
|
+
const adapter = this.manager.getAdapter(this.def.connectionName);
|
|
203
|
+
if (!adapter)
|
|
204
|
+
throw new Error(`No adapter attached for connection '${this.def.connectionName}'`);
|
|
205
|
+
const id = one.id;
|
|
206
|
+
if (!id)
|
|
207
|
+
throw new Error('Document missing id; cannot deleteOne');
|
|
208
|
+
await adapter.deleteDocument(this.def.collectionName, id);
|
|
209
|
+
}
|
|
210
|
+
async update() {
|
|
211
|
+
const adapter = this.manager.getAdapter(this.def.connectionName);
|
|
212
|
+
if (!adapter)
|
|
213
|
+
throw new Error(`No adapter attached for connection '${this.def.connectionName}'`);
|
|
214
|
+
if (!this.pendingUpdate)
|
|
215
|
+
throw new Error('No update payload set; call to({ ... }) first');
|
|
216
|
+
const docs = await this.get();
|
|
217
|
+
for (const d of docs) {
|
|
218
|
+
const id = d.id;
|
|
219
|
+
if (!id)
|
|
220
|
+
throw new Error('Document missing id; cannot update');
|
|
221
|
+
await adapter.updateDocument(this.def.collectionName, id, this.pendingUpdate);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async updateOne() {
|
|
225
|
+
const adapter = this.manager.getAdapter(this.def.connectionName);
|
|
226
|
+
if (!adapter)
|
|
227
|
+
throw new Error(`No adapter attached for connection '${this.def.connectionName}'`);
|
|
228
|
+
if (!this.pendingUpdate)
|
|
229
|
+
throw new Error('No update payload set; call to({ ... }) first');
|
|
230
|
+
const one = await this.getOne();
|
|
231
|
+
if (!one)
|
|
232
|
+
return;
|
|
233
|
+
const id = one.id;
|
|
234
|
+
if (!id)
|
|
235
|
+
throw new Error('Document missing id; cannot updateOne');
|
|
236
|
+
await adapter.updateDocument(this.def.collectionName, id, this.pendingUpdate);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
export class SchemaManager {
|
|
240
|
+
constructor(connections) {
|
|
241
|
+
this.connections = connections;
|
|
242
|
+
this.schemas = new Map();
|
|
243
|
+
}
|
|
244
|
+
create(name) {
|
|
245
|
+
if (this.schemas.has(name)) {
|
|
246
|
+
throw new Error(`Schema with name '${name}' already exists`);
|
|
247
|
+
}
|
|
248
|
+
return new SchemaBuilder(this, name);
|
|
249
|
+
}
|
|
250
|
+
register(def) {
|
|
251
|
+
this.schemas.set(def.name, def);
|
|
252
|
+
return this;
|
|
253
|
+
}
|
|
254
|
+
use(name) {
|
|
255
|
+
const def = this.schemas.get(name);
|
|
256
|
+
if (!def)
|
|
257
|
+
throw new Error(`Unknown schema '${name}'`);
|
|
258
|
+
return new SchemaQuery(this, def);
|
|
259
|
+
}
|
|
260
|
+
getAdapter(connectionName) {
|
|
261
|
+
return this.connections.getAdapter(connectionName);
|
|
262
|
+
}
|
|
263
|
+
list() {
|
|
264
|
+
return Array.from(this.schemas.values());
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
export function connection() {
|
|
268
|
+
return new Connections();
|
|
269
|
+
}
|
|
270
|
+
export function schema(conns) {
|
|
271
|
+
const ctx = conns || new Connections();
|
|
272
|
+
return new SchemaManager(ctx);
|
|
273
|
+
}
|
|
274
|
+
export const schemas = (() => {
|
|
275
|
+
const conns = new Connections();
|
|
276
|
+
return new SchemaManager(conns);
|
|
277
|
+
})();
|
|
278
|
+
export { createOrm } from './orm';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DbAdapter, QueryOptions } from './types';
|
|
2
|
+
export declare function createOrm(adapter: DbAdapter): {
|
|
3
|
+
get(options: QueryOptions): Promise<import("@firebase/firestore").DocumentData[]>;
|
|
4
|
+
getOne(options: QueryOptions): Promise<any>;
|
|
5
|
+
getDocuments(options: QueryOptions): Promise<import("@firebase/firestore").DocumentData[]>;
|
|
6
|
+
addDocument(collectionName: string, data: Record<string, any>): Promise<string>;
|
|
7
|
+
updateDocument(collectionName: string, docId: string, data: Record<string, any>): Promise<void>;
|
|
8
|
+
deleteDocument(collectionName: string, docId: string): Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
export type { DbAdapter, QueryOptions };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function createOrm(adapter) {
|
|
2
|
+
return {
|
|
3
|
+
async get(options) {
|
|
4
|
+
if (adapter.get)
|
|
5
|
+
return adapter.get(options);
|
|
6
|
+
return adapter.getDocuments(options);
|
|
7
|
+
},
|
|
8
|
+
async getOne(options) {
|
|
9
|
+
var _a;
|
|
10
|
+
if (adapter.getOne)
|
|
11
|
+
return adapter.getOne(options);
|
|
12
|
+
const arr = adapter.get ? await adapter.get(options) : await adapter.getDocuments(options);
|
|
13
|
+
return (_a = arr[0]) !== null && _a !== void 0 ? _a : null;
|
|
14
|
+
},
|
|
15
|
+
async getDocuments(options) {
|
|
16
|
+
return adapter.getDocuments(options);
|
|
17
|
+
},
|
|
18
|
+
async addDocument(collectionName, data) {
|
|
19
|
+
return adapter.addDocument(collectionName, data);
|
|
20
|
+
},
|
|
21
|
+
async updateDocument(collectionName, docId, data) {
|
|
22
|
+
return adapter.updateDocument(collectionName, docId, data);
|
|
23
|
+
},
|
|
24
|
+
async deleteDocument(collectionName, docId) {
|
|
25
|
+
return adapter.deleteDocument(collectionName, docId);
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { DocumentData } from 'firebase/firestore';
|
|
2
|
+
export interface QueryOptions {
|
|
3
|
+
collectionName: string;
|
|
4
|
+
filters: {
|
|
5
|
+
field: string;
|
|
6
|
+
operator: any;
|
|
7
|
+
value: any;
|
|
8
|
+
}[];
|
|
9
|
+
limit: number | null;
|
|
10
|
+
offset: number | null;
|
|
11
|
+
sortBy: {
|
|
12
|
+
field: string;
|
|
13
|
+
direction: 'asc' | 'desc';
|
|
14
|
+
} | null;
|
|
15
|
+
fields: string[];
|
|
16
|
+
rawWhere?: string | null;
|
|
17
|
+
}
|
|
18
|
+
export interface DbAdapter {
|
|
19
|
+
get?(options: QueryOptions): Promise<DocumentData[]>;
|
|
20
|
+
getOne?(options: QueryOptions): Promise<DocumentData | null>;
|
|
21
|
+
getDocuments(options: QueryOptions): Promise<DocumentData[]>;
|
|
22
|
+
addDocument(collectionName: string, data: DocumentData): Promise<string>;
|
|
23
|
+
updateDocument(collectionName: string, docId: string, data: DocumentData): Promise<void>;
|
|
24
|
+
deleteDocument(collectionName: string, docId: string): Promise<void>;
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neupgroup/mapper",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Neup.Mapper core library for schema and mapping utilities",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "index.js",
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc -p ./tsconfig.json"
|
|
13
15
|
},
|
|
14
16
|
"publishConfig": {
|
|
15
17
|
"access": "public"
|
|
16
|
-
},
|
|
17
|
-
"files": ["*.js", "*.d.ts"],
|
|
18
|
-
"scripts": {
|
|
19
|
-
"build": "tsc -p tsconfig.json",
|
|
20
|
-
"typecheck": "tsc --noEmit"
|
|
21
|
-
},
|
|
22
|
-
"dependencies": {},
|
|
23
|
-
"devDependencies": {
|
|
24
|
-
"typescript": "^5"
|
|
25
18
|
}
|
|
26
19
|
}
|
|
20
|
+
|
package/api.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export interface ApiRequest {
|
|
2
|
-
method: string;
|
|
3
|
-
url: string;
|
|
4
|
-
headers?: Record<string, string>;
|
|
5
|
-
body?: unknown;
|
|
6
|
-
}
|
|
7
|
-
export interface ApiResponse<T = unknown> {
|
|
8
|
-
status: number;
|
|
9
|
-
headers?: Record<string, string>;
|
|
10
|
-
data?: T;
|
|
11
|
-
}
|
|
12
|
-
export interface ApiAdapter {
|
|
13
|
-
request<T = unknown>(req: ApiRequest): Promise<ApiResponse<T>>;
|
|
14
|
-
}
|
|
15
|
-
export interface ApiClient {
|
|
16
|
-
get<T = unknown>(path: string, headers?: Record<string, string>): Promise<ApiResponse<T>>;
|
|
17
|
-
post<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<ApiResponse<T>>;
|
|
18
|
-
put<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<ApiResponse<T>>;
|
|
19
|
-
delete<T = unknown>(path: string, headers?: Record<string, string>): Promise<ApiResponse<T>>;
|
|
20
|
-
}
|
|
21
|
-
export declare function createApiClient(baseUrl: string, adapter: ApiAdapter, defaultHeaders?: Record<string, string>): ApiClient;
|
|
22
|
-
export type { ApiAdapter as IApiAdapter };
|
package/api.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export function createApiClient(baseUrl, adapter, defaultHeaders) {
|
|
2
|
-
const buildUrl = (path) => {
|
|
3
|
-
if (!baseUrl)
|
|
4
|
-
return path;
|
|
5
|
-
return baseUrl.endsWith("/") ? `${baseUrl}${path.replace(/^\//, "")}` : `${baseUrl}/${path.replace(/^\//, "")}`;
|
|
6
|
-
};
|
|
7
|
-
const mergeHeaders = (headers) => ({ ...(defaultHeaders || {}), ...(headers || {}) });
|
|
8
|
-
return {
|
|
9
|
-
get: (path, headers) => adapter.request({ method: "GET", url: buildUrl(path), headers: mergeHeaders(headers) }),
|
|
10
|
-
post: (path, body, headers) => adapter.request({ method: "POST", url: buildUrl(path), headers: mergeHeaders(headers), body }),
|
|
11
|
-
put: (path, body, headers) => adapter.request({ method: "PUT", url: buildUrl(path), headers: mergeHeaders(headers), body }),
|
|
12
|
-
delete: (path, headers) => adapter.request({ method: "DELETE", url: buildUrl(path), headers: mergeHeaders(headers) }),
|
|
13
|
-
};
|
|
14
|
-
}
|
package/config.d.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { type ApiAdapter, type ApiClient } from "./api";
|
|
2
|
-
import { type DbAdapter, type Db } from "./db";
|
|
3
|
-
export type MapKind = "api" | "db";
|
|
4
|
-
export interface ApiMapConfig {
|
|
5
|
-
kind: "api";
|
|
6
|
-
name: string;
|
|
7
|
-
baseUrl?: string;
|
|
8
|
-
defaultHeaders?: Record<string, string>;
|
|
9
|
-
adapter: ApiAdapter;
|
|
10
|
-
}
|
|
11
|
-
export interface DbMapConfig {
|
|
12
|
-
kind: "db";
|
|
13
|
-
name: string;
|
|
14
|
-
connection?: Record<string, unknown>;
|
|
15
|
-
adapter: DbAdapter;
|
|
16
|
-
}
|
|
17
|
-
export type MapConfig = ApiMapConfig | DbMapConfig;
|
|
18
|
-
export interface MapperConfig {
|
|
19
|
-
maps: MapConfig[];
|
|
20
|
-
}
|
|
21
|
-
export interface MapperRegistry {
|
|
22
|
-
/** Get a named API client */
|
|
23
|
-
api: (name: string) => ApiClient;
|
|
24
|
-
/** Get a named DB client */
|
|
25
|
-
db: (name: string) => Db;
|
|
26
|
-
/** Get any named client (API or DB) */
|
|
27
|
-
get: (name: string) => ApiClient | Db;
|
|
28
|
-
/** List registered maps */
|
|
29
|
-
list: () => {
|
|
30
|
-
name: string;
|
|
31
|
-
kind: MapKind;
|
|
32
|
-
}[];
|
|
33
|
-
}
|
|
34
|
-
/** Helper to define config with proper type inference */
|
|
35
|
-
export declare function defineConfig(config: MapperConfig): MapperConfig;
|
|
36
|
-
/** Create a registry of named API/DB maps from configuration */
|
|
37
|
-
export declare function createRegistry(config: MapperConfig): MapperRegistry;
|
|
38
|
-
/** alias */
|
|
39
|
-
export declare const loadConfig: typeof createRegistry;
|
package/config.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { createApiClient } from "./api";
|
|
2
|
-
import { createDb } from "./db";
|
|
3
|
-
/** Helper to define config with proper type inference */
|
|
4
|
-
export function defineConfig(config) {
|
|
5
|
-
return config;
|
|
6
|
-
}
|
|
7
|
-
/** Create a registry of named API/DB maps from configuration */
|
|
8
|
-
export function createRegistry(config) {
|
|
9
|
-
const entries = new Map();
|
|
10
|
-
for (const m of config.maps) {
|
|
11
|
-
if (!m.name)
|
|
12
|
-
throw new Error("Map config requires a unique 'name'");
|
|
13
|
-
if (entries.has(m.name))
|
|
14
|
-
throw new Error(`Duplicate map name '${m.name}'`);
|
|
15
|
-
if (m.kind === "api") {
|
|
16
|
-
const client = createApiClient(m.baseUrl ?? "", m.adapter, m.defaultHeaders);
|
|
17
|
-
entries.set(m.name, { kind: "api", value: client });
|
|
18
|
-
}
|
|
19
|
-
else if (m.kind === "db") {
|
|
20
|
-
const db = createDb(m.adapter);
|
|
21
|
-
// Optional: pre-connect using provided connection config.
|
|
22
|
-
// Consumers may choose to call db.connect() themselves.
|
|
23
|
-
// if (m.connection) await db.connect(m.connection);
|
|
24
|
-
entries.set(m.name, { kind: "db", value: db });
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
const getApi = (name) => {
|
|
28
|
-
const entry = entries.get(name);
|
|
29
|
-
if (!entry || entry.kind !== "api")
|
|
30
|
-
throw new Error(`API map '${name}' not found`);
|
|
31
|
-
return entry.value;
|
|
32
|
-
};
|
|
33
|
-
const getDb = (name) => {
|
|
34
|
-
const entry = entries.get(name);
|
|
35
|
-
if (!entry || entry.kind !== "db")
|
|
36
|
-
throw new Error(`DB map '${name}' not found`);
|
|
37
|
-
return entry.value;
|
|
38
|
-
};
|
|
39
|
-
const get = (name) => {
|
|
40
|
-
const entry = entries.get(name);
|
|
41
|
-
if (!entry)
|
|
42
|
-
throw new Error(`Map '${name}' not found`);
|
|
43
|
-
return entry.value;
|
|
44
|
-
};
|
|
45
|
-
const list = () => Array.from(entries.entries()).map(([name, e]) => ({ name, kind: e.kind }));
|
|
46
|
-
return { api: getApi, db: getDb, get, list };
|
|
47
|
-
}
|
|
48
|
-
/** alias */
|
|
49
|
-
export const loadConfig = createRegistry;
|
package/db.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export type QueryParams = unknown[] | Record<string, unknown> | undefined;
|
|
2
|
-
export interface DbAdapter {
|
|
3
|
-
connect(config: Record<string, unknown>): Promise<void>;
|
|
4
|
-
query<T = unknown>(sql: string, params?: QueryParams): Promise<T[]>;
|
|
5
|
-
execute?(sql: string, params?: QueryParams): Promise<{
|
|
6
|
-
affectedRows?: number;
|
|
7
|
-
}>;
|
|
8
|
-
close(): Promise<void>;
|
|
9
|
-
}
|
|
10
|
-
export interface Db {
|
|
11
|
-
connect: (config: Record<string, unknown>) => Promise<void>;
|
|
12
|
-
query: <T = unknown>(sql: string, params?: QueryParams) => Promise<T[]>;
|
|
13
|
-
execute: (sql: string, params?: QueryParams) => Promise<{
|
|
14
|
-
affectedRows?: number;
|
|
15
|
-
}>;
|
|
16
|
-
close: () => Promise<void>;
|
|
17
|
-
}
|
|
18
|
-
export declare function createDb(adapter: DbAdapter): Db;
|
|
19
|
-
export type { DbAdapter as IDbAdapter };
|
package/db.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export function createDb(adapter) {
|
|
2
|
-
const ensure = (fn) => fn();
|
|
3
|
-
return {
|
|
4
|
-
connect: (config) => ensure(() => adapter.connect(config)),
|
|
5
|
-
query: (sql, params) => ensure(() => adapter.query(sql, params)),
|
|
6
|
-
execute: (sql, params) => ensure(async () => {
|
|
7
|
-
if (typeof adapter.execute === "function") {
|
|
8
|
-
return adapter.execute(sql, params);
|
|
9
|
-
}
|
|
10
|
-
await adapter.query(sql, params);
|
|
11
|
-
return { affectedRows: undefined };
|
|
12
|
-
}),
|
|
13
|
-
close: () => ensure(() => adapter.close()),
|
|
14
|
-
};
|
|
15
|
-
}
|
package/index.d.ts
DELETED
package/index.js
DELETED
package/mapper.d.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { type ApiAdapter, type ApiClient } from "./api";
|
|
2
|
-
import { type DbAdapter, type Db, type QueryParams } from "./db";
|
|
3
|
-
import type { MapKind, MapperConfig } from "./config";
|
|
4
|
-
export declare function useConfig(config: MapperConfig): void;
|
|
5
|
-
export declare function list(): {
|
|
6
|
-
name: string;
|
|
7
|
-
kind: MapKind;
|
|
8
|
-
}[];
|
|
9
|
-
export interface ApiPath {
|
|
10
|
-
get<T = unknown>(headers?: Record<string, string>): Promise<{
|
|
11
|
-
status: number;
|
|
12
|
-
headers?: Record<string, string>;
|
|
13
|
-
data?: T;
|
|
14
|
-
}>;
|
|
15
|
-
post<T = unknown>(body?: unknown, headers?: Record<string, string>): Promise<{
|
|
16
|
-
status: number;
|
|
17
|
-
headers?: Record<string, string>;
|
|
18
|
-
data?: T;
|
|
19
|
-
}>;
|
|
20
|
-
put<T = unknown>(body?: unknown, headers?: Record<string, string>): Promise<{
|
|
21
|
-
status: number;
|
|
22
|
-
headers?: Record<string, string>;
|
|
23
|
-
data?: T;
|
|
24
|
-
}>;
|
|
25
|
-
delete<T = unknown>(headers?: Record<string, string>): Promise<{
|
|
26
|
-
status: number;
|
|
27
|
-
headers?: Record<string, string>;
|
|
28
|
-
data?: T;
|
|
29
|
-
}>;
|
|
30
|
-
}
|
|
31
|
-
export interface DbTable {
|
|
32
|
-
selectAll<T = unknown>(): Promise<T[]>;
|
|
33
|
-
query<T = unknown>(sqlSuffix?: string, params?: QueryParams): Promise<T[]>;
|
|
34
|
-
}
|
|
35
|
-
export interface MapperHandle {
|
|
36
|
-
name(): string;
|
|
37
|
-
kind(): MapKind;
|
|
38
|
-
client(): ApiClient | Db;
|
|
39
|
-
table(name: string): DbTable;
|
|
40
|
-
path(p: string): ApiPath;
|
|
41
|
-
}
|
|
42
|
-
export interface MapperBuilderStart {
|
|
43
|
-
create(name: string, kind: MapKind): MapperBuilder;
|
|
44
|
-
}
|
|
45
|
-
export interface MapperBuilder {
|
|
46
|
-
adapter(adapter: ApiAdapter | DbAdapter): MapperBuilder;
|
|
47
|
-
save(): MapperHandle;
|
|
48
|
-
baseUrl(url: string): MapperBuilder;
|
|
49
|
-
headers(headers: Record<string, string>): MapperBuilder;
|
|
50
|
-
header(key: string, value: string): MapperBuilder;
|
|
51
|
-
host(host: string): MapperBuilder;
|
|
52
|
-
dbname(name: string): MapperBuilder;
|
|
53
|
-
user(user: string): MapperBuilder;
|
|
54
|
-
pass(pass: string): MapperBuilder;
|
|
55
|
-
port(port: number): MapperBuilder;
|
|
56
|
-
option(key: string, value: unknown): MapperBuilder;
|
|
57
|
-
}
|
|
58
|
-
export declare function mapper(): MapperBuilderStart;
|
|
59
|
-
export declare function mapper(name: string): MapperHandle;
|
package/mapper.js
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { createApiClient } from "./api";
|
|
2
|
-
import { createDb } from "./db";
|
|
3
|
-
const registry = new Map();
|
|
4
|
-
function ensureUnique(name) {
|
|
5
|
-
if (registry.has(name))
|
|
6
|
-
throw new Error(`Duplicate map name '${name}'`);
|
|
7
|
-
}
|
|
8
|
-
export function useConfig(config) {
|
|
9
|
-
for (const m of config.maps) {
|
|
10
|
-
ensureUnique(m.name);
|
|
11
|
-
if (m.kind === "api") {
|
|
12
|
-
const client = createApiClient(m.baseUrl ?? "", m.adapter, m.defaultHeaders);
|
|
13
|
-
registry.set(m.name, { kind: "api", value: client });
|
|
14
|
-
}
|
|
15
|
-
else {
|
|
16
|
-
const db = createDb(m.adapter);
|
|
17
|
-
registry.set(m.name, { kind: "db", value: db });
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
export function list() {
|
|
22
|
-
return Array.from(registry.entries()).map(([name, e]) => ({ name, kind: e.kind }));
|
|
23
|
-
}
|
|
24
|
-
function asHandle(name, entry) {
|
|
25
|
-
const base = {
|
|
26
|
-
name: () => name,
|
|
27
|
-
kind: () => entry.kind,
|
|
28
|
-
client: () => entry.value,
|
|
29
|
-
};
|
|
30
|
-
const table = (t) => {
|
|
31
|
-
if (entry.kind !== "db")
|
|
32
|
-
throw new Error(`Map '${name}' is not a DB. Use path() for API.`);
|
|
33
|
-
const db = entry.value;
|
|
34
|
-
return {
|
|
35
|
-
selectAll: () => db.query(`SELECT * FROM ${t}`),
|
|
36
|
-
query: (suffix, params) => {
|
|
37
|
-
const sql = suffix ? `SELECT * FROM ${t} ${suffix}` : `SELECT * FROM ${t}`;
|
|
38
|
-
return db.query(sql, params);
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
};
|
|
42
|
-
const path = (p) => {
|
|
43
|
-
if (entry.kind !== "api")
|
|
44
|
-
throw new Error(`Map '${name}' is not an API. Use table() for DB.`);
|
|
45
|
-
const api = entry.value;
|
|
46
|
-
const normalized = p.startsWith("/") ? p : `/${p}`;
|
|
47
|
-
return {
|
|
48
|
-
get: (headers) => api.get(normalized, headers),
|
|
49
|
-
post: (body, headers) => api.post(normalized, body, headers),
|
|
50
|
-
put: (body, headers) => api.put(normalized, body, headers),
|
|
51
|
-
delete: (headers) => api.delete(normalized, headers),
|
|
52
|
-
};
|
|
53
|
-
};
|
|
54
|
-
return { ...base, table, path };
|
|
55
|
-
}
|
|
56
|
-
class Builder {
|
|
57
|
-
constructor() {
|
|
58
|
-
this.conf = {};
|
|
59
|
-
}
|
|
60
|
-
create(name, kind) {
|
|
61
|
-
this.conf.name = name;
|
|
62
|
-
this.conf.kind = kind;
|
|
63
|
-
return this;
|
|
64
|
-
}
|
|
65
|
-
adapter(adapter) {
|
|
66
|
-
this.conf.adapter = adapter;
|
|
67
|
-
return this;
|
|
68
|
-
}
|
|
69
|
-
// API
|
|
70
|
-
baseUrl(url) { var _a; (_a = this.conf).api ?? (_a.api = {}); this.conf.api.baseUrl = url; return this; }
|
|
71
|
-
headers(headers) {
|
|
72
|
-
var _a;
|
|
73
|
-
(_a = this.conf).api ?? (_a.api = {});
|
|
74
|
-
const current = this.conf.api.defaultHeaders ?? {};
|
|
75
|
-
this.conf.api.defaultHeaders = { ...current, ...headers };
|
|
76
|
-
return this;
|
|
77
|
-
}
|
|
78
|
-
header(key, value) { return this.headers({ [key]: value }); }
|
|
79
|
-
// DB
|
|
80
|
-
ensureConn() {
|
|
81
|
-
var _a;
|
|
82
|
-
(_a = this.conf).db ?? (_a.db = {});
|
|
83
|
-
if (!this.conf.db.connection)
|
|
84
|
-
this.conf.db.connection = {};
|
|
85
|
-
}
|
|
86
|
-
host(host) { this.ensureConn(); this.conf.db.connection.host = host; return this; }
|
|
87
|
-
dbname(name) { this.ensureConn(); this.conf.db.connection.dbname = name; return this; }
|
|
88
|
-
user(user) { this.ensureConn(); this.conf.db.connection.user = user; return this; }
|
|
89
|
-
pass(pass) { this.ensureConn(); this.conf.db.connection.pass = pass; return this; }
|
|
90
|
-
port(port) { this.ensureConn(); this.conf.db.connection.port = port; return this; }
|
|
91
|
-
option(key, value) { this.ensureConn(); this.conf.db.connection[key] = value; return this; }
|
|
92
|
-
save() {
|
|
93
|
-
const name = this.conf.name;
|
|
94
|
-
const kind = this.conf.kind;
|
|
95
|
-
const adapter = this.conf.adapter;
|
|
96
|
-
if (!name || !kind || !adapter)
|
|
97
|
-
throw new Error("Builder requires name, kind, and adapter before save()");
|
|
98
|
-
ensureUnique(name);
|
|
99
|
-
if (kind === "api") {
|
|
100
|
-
const client = createApiClient(this.conf.api?.baseUrl ?? "", adapter, this.conf.api?.defaultHeaders);
|
|
101
|
-
registry.set(name, { kind: "api", value: client });
|
|
102
|
-
return asHandle(name, registry.get(name));
|
|
103
|
-
}
|
|
104
|
-
const db = createDb(adapter);
|
|
105
|
-
registry.set(name, { kind: "db", value: db });
|
|
106
|
-
return asHandle(name, registry.get(name));
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
export function mapper(name) {
|
|
110
|
-
if (typeof name === "string") {
|
|
111
|
-
const entry = registry.get(name);
|
|
112
|
-
if (!entry)
|
|
113
|
-
throw new Error(`Map '${name}' not found. Register with useConfig() or builder.`);
|
|
114
|
-
return asHandle(name, entry);
|
|
115
|
-
}
|
|
116
|
-
return new Builder();
|
|
117
|
-
}
|