@neupgroup/mapper 1.0.0 → 1.1.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 CHANGED
@@ -1,27 +1,32 @@
1
1
  # Neup.Mapper (Library)
2
2
 
3
- Neup.Mapper is now a small, framework-agnostic TypeScript library focused on two things:
3
+ Neup.Mapper is a small, framework-agnostic TypeScript library that provides:
4
4
 
5
- - Database connectivity via adapter pattern (bring your own driver)
6
- - HTTP API access via adapter pattern (bring your own client)
5
+ - Database connectivity via an adapter pattern (bring your own driver)
6
+ - HTTP API access via an adapter pattern (bring your own client)
7
+ - A simple config/registry to define multiple named connections ("maps")
7
8
 
8
9
  ## Install
9
10
 
10
- This project is currently private. Build locally:
11
+ ```
12
+ npm install @neupgroup/mapper
13
+ ```
14
+
15
+ If you’re working locally on the source:
11
16
 
12
17
  ```
13
18
  npm install
14
19
  npm run build
15
20
  ```
16
21
 
17
- Artifacts are emitted to `dist/`.
22
+ Artifacts (`*.js`, `*.d.ts`) are emitted into the project root.
18
23
 
19
- ## Usage
24
+ ## Quick Start (Adapter Basics)
20
25
 
21
26
  ### Database
22
27
 
23
28
  ```
24
- import { createDb, IDbAdapter } from "neup-mapper";
29
+ import { createDb, IDbAdapter } from "@neupgroup/mapper";
25
30
 
26
31
  const adapter: IDbAdapter = {
27
32
  async connect(config) {
@@ -45,7 +50,7 @@ await db.close();
45
50
  ### API
46
51
 
47
52
  ```
48
- import { createApiClient, IApiAdapter } from "neup-mapper";
53
+ import { createApiClient, IApiAdapter } from "@neupgroup/mapper";
49
54
 
50
55
  const http: IApiAdapter = {
51
56
  async request(req) {
@@ -58,9 +63,159 @@ const api = createApiClient("https://api.example.com", http, { Authorization: "B
58
63
  const res = await api.get("/users");
59
64
  ```
60
65
 
61
- ## Layout
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
+ ```
62
220
 
63
- - Source files live at the project root: `index.ts`, `db.ts`, `api.ts`.
64
- - Build emits to `dist/` with `index.js` and `index.d.ts` for consumers.
65
- - No React/Next.js UI is included; the project is library-only.
66
- - Add any DB driver or HTTP client as dependencies in your own app.
221
+ Ensure your `package.json` has `"private": false` and `"publishConfig": { "access": "public" }`.
package/config.d.ts ADDED
@@ -0,0 +1,39 @@
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 ADDED
@@ -0,0 +1,49 @@
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/index.d.ts CHANGED
@@ -1,2 +1,4 @@
1
1
  export * from "./db";
2
2
  export * from "./api";
3
+ export * from "./config";
4
+ export * from "./mapper";
package/index.js CHANGED
@@ -1,2 +1,4 @@
1
1
  export * from "./db";
2
2
  export * from "./api";
3
+ export * from "./config";
4
+ export * from "./mapper";
package/mapper.d.ts ADDED
@@ -0,0 +1,59 @@
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 ADDED
@@ -0,0 +1,117 @@
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neupgroup/mapper",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "index.js",