@x12i/helpers
v1.7.0
Published
Small helper utilities for x12i projects.
Maintainers
Readme
@x12i/helpers
Small helper utilities for x12i projects.
Install
npm i @x12i/helpersHelpers (library of abilities)
Each helper has a focused doc page under helpers-docs/:
- Objects mapper:
helpers-docs/objects-mapper.md - HTTP tools:
helpers-docs/http-tools.md - API contracts:
helpers-docs/api-contracts.md - API mapper / protocol bridge:
helpers-docs/api-mapper.md - Firebase RTDB (mongo-like):
helpers-docs/firebase-rtdb.md - Firebase Firestore (admin init + databaseId):
helpers-docs/firebase-firestore.md - Google Cloud Storage (object I/O):
helpers-docs/gcs.md - MongoDB (mongo-like collection API):
helpers-docs/mongo.md - Amazon S3 (object I/O, GCS-shaped API):
helpers-docs/s3.md
Usage
Objects mapper
const { mapObject, createMapper, mapArray, deepGet, deepSet } = require("@x12i/helpers");
const source = { user: { first: "Jane", last: "Doe" } };
const mapping = [
"user.first -> profile.firstName",
"user.last -> profile.lastName",
{ template: "{user.first} {user.last}", to: "profile.displayName" }
];
console.log(mapObject(source, mapping));
// { profile: { firstName: 'Jane', lastName: 'Doe', displayName: 'Jane Doe' } }You can also import the mapper directly:
const { mapObject } = require("@x12i/helpers/objects-mapper");HTTP tools (request ⇄ curl, base URLs)
const {
requestToCurl,
curlToRequest,
buildUrl,
createBaseUrlClient,
} = require("@x12i/helpers");
const curl = requestToCurl({
method: "POST",
url: "https://api.example.com/v1/users",
headers: { authorization: "Bearer TOKEN" },
body: { name: "Jane" },
});
// curl -X POST -H 'authorization: Bearer TOKEN' -H 'content-type: application/json' --data-raw '{"name":"Jane"}' 'https://api.example.com/v1/users'
console.log(curl);
const req = curlToRequest(curl);
// { method: 'POST', url: 'https://api.example.com/v1/users', headers: { authorization: 'Bearer TOKEN', 'content-type': 'application/json' }, body: '{"name":"Jane"}' }
console.log(req);
console.log(buildUrl("https://api.example.com/", "/v1/users", { limit: 10, tags: ["a", "b"] }));
// https://api.example.com/v1/users?limit=10&tags=a&tags=b
const api = createBaseUrlClient("https://api.example.com/", {
headers: { authorization: "Bearer TOKEN" },
});
console.log(api.curl("/v1/health"));Direct import:
const { requestToCurl } = require("@x12i/helpers/http-tools");API contracts + protocol bridge
Define protocol-aware API contracts (REST/GraphQL/JSON-RPC/SOAP/custom) with optional request/response validation and auth application.
const { defineContract, createRegistry } = require("@x12i/helpers/api-contracts");
const { createBridge } = require("@x12i/helpers/api-mapper");
const registry = createRegistry();
registry.register({
name: "restGetUser",
protocol: "rest",
endpoint: "/api/users/{id}",
method: "GET",
auth: { type: "bearer" },
});
registry.register({
name: "gqlGetUser",
protocol: "graphql",
endpoint: "https://api.example.com/graphql",
query: "query GetUser($userId: ID!) { user(id: $userId) { id fullName } }",
auth: { type: "bearer" },
});
const bridge = createBridge({
source: "restGetUser",
target: "gqlGetUser",
registry,
requestMapping: [{ from: "pathParams.id", to: "variables.userId" }],
responseMapping: ["user.id", "user.fullName -> user.name"],
credentials: { target: { token: "TOKEN" } },
});
// bridge.call({ pathParams: { id: "42" } })Direct imports:
const { createBridge, batchCall, chainBridges } = require("@x12i/helpers/api-mapper");
const { defineContract, createRegistry } = require("@x12i/helpers/api-contracts");Firebase Realtime Database (native) — mongo-like helper
This helper uses the Firebase Admin SDK and wraps RTDB with a familiar Mongo-ish API (findOne, insertOne, updateOne, etc.).
This is not a Firestore helper. If your upstream app uses Firestore, you should initialize firebase-admin with service account credentials and use admin.firestore() (Firestore does not use FIREBASE_DATABASE_URL).
- Create
.env(see.env.example) and provide:
GOOGLE_SERVICE_ACCOUNT_BASE64: Base64-encoded Google service account JSONFIREBASE_DATABASE_URL: the exact Realtime Database URL for that same Firebase project (copied from Firebase Console → Realtime Database → Data)
Credentials (Base64 only)
All helpers use a Base64-encoded Google service account JSON.
Why:
- Works in serverless / edge environments
- No filesystem dependency
- Safer secret handling
How to generate:
cat service-account.json | base64Then set:
GOOGLE_SERVICE_ACCOUNT_BASE64=...Migration
Before:
FIREBASE_SERVICE_ACCOUNT_PATH=.secrets/firebase-service-account.jsonAfter:
cat .secrets/firebase-service-account.json | base64GOOGLE_SERVICE_ACCOUNT_BASE64=<result>- Use it:
const { initFirebaseRtdb } = require("@x12i/helpers/firebase-rtdb");
const fb = initFirebaseRtdb(); // loads .env by default
const users = fb.collection("users");
const { insertedId } = await users.insertOne({ email: "[email protected]", name: "Ami" });
const user = await users.findOne({ _id: insertedId });
await users.updateOne({ _id: insertedId }, { $set: { name: "Ami N." }, $inc: { loginCount: 1 } });
await users.deleteOne({ _id: insertedId });Important:
- Service account + RTDB URL must match the same Firebase project.
- Do not guess the RTDB URL (
firebaseio.comvsfirebasedatabase.app, region, etc.). In Firebase Console, open the project referenced by your service account JSON (project_id), then copy the Database URL shown under Realtime Database → Data and setFIREBASE_DATABASE_URLto that exact value.
Optional: to fail fast (avoid long hangs in test suites) set FIREBASE_CONNECT_TIMEOUT_MS or pass connectTimeoutMs:
const fb = initFirebaseRtdb({ connectTimeoutMs: 5000 });If you want native RTDB query features (server-side indexed filtering), use query():
const result = await users
.query({ orderByChild: "email", equalTo: "[email protected]", limitToFirst: 10 })
.find();Firebase Firestore — admin helper
If you want Firestore (not RTDB), use this helper. Firestore does not require a database URL; it authenticates via service account credentials.
Set
GOOGLE_SERVICE_ACCOUNT_BASE64(Base64-encoded service account JSON).Ensure Firestore exists for that project:
- Firebase Console → Firestore Database → Create database (this creates the default database for the project)
- Use it:
const { initFirebaseFirestore } = require("@x12i/helpers/firebase-firestore");
const { firestore } = initFirebaseFirestore(); // loads .env by default
const docRef = firestore.doc("catalogs/appId");
await docRef.set({ hello: "world" }, { merge: true });
const snap = await docRef.get();
console.log(snap.exists, snap.data());Optional: fail fast (avoid long hangs in test suites) set FIRESTORE_CONNECT_TIMEOUT_MS (or FIREBASE_CONNECT_TIMEOUT_MS) or pass connectTimeoutMs:
const fb = initFirebaseFirestore({ connectTimeoutMs: 5000 });Defaults:
FIRESTORE_DATABASE_IDdefaults tocataloxFIREBASE_PROJECT_IDdefaults tox12i(normally inferred from the service account’sproject_id, but you can override it)
If you want a different Firestore database id, set FIRESTORE_DATABASE_ID or pass databaseId:
initFirebaseFirestore({ databaseId: "catalox" });Google Cloud Storage (GCS)
Server-side object storage only: import the subpath @x12i/helpers/gcs so the main package entry does not load @google-cloud/storage.
Set
GOOGLE_SERVICE_ACCOUNT_BASE64(Base64-encoded service account JSON).Set a bucket name (defaults to
storage_bucket;STORAGE_BUCKET/GCS_BUCKETalso supported) or pass{ bucket }.Minimal usage:
const { createGcsClient } = require("@x12i/helpers/gcs");
const gcs = createGcsClient({
// envPath defaults to ".env"; loads GCS_BUCKET / GCS_OBJECT_PREFIX if set
prefix: "my-app/uploads",
});
await gcs.uploadObject("user/1/avatar.png", buffer, { contentType: "image/png" });
const data = await gcs.readObjectBuffer("user/1/avatar.png", { maxBytes: 2 * 1024 * 1024 });
const { items, nextPageToken } = await gcs.listObjects({ prefix: "user/1/", maxResults: 50 });When to use this helper instead of @google-cloud/storage directly:
- You want shared x12i conventions for loading
.envand service account material (same Base64-only credential mechanism as RTDB/Firestore helpers). - You want small, stable helpers for upload, buffered read (with a max size), list pagination, existence checks, and metadata, plus typed errors (
GcsNotFoundError,GcsPermissionDeniedError,GcsFailedPreconditionError) for mapping to your own issue types.
Optional emulator (CI or local): set STORAGE_EMULATOR_HOST (for example with fake-gcs-server). See test/gcs.emulator.test.js for an example command line.
Live tests in this repo: set STORAGE_LIVE_TESTS=1 (or legacy GCS_LIVE_TESTS=1) and STORAGE_BUCKET (or GCS_BUCKET) to a dedicated test bucket; see test/gcs.live.test.js.
MongoDB (mongo-like, RTDB-shaped API)
Subpath: @x12i/helpers/mongo. Exposes collection, ref, db, and client with the same mongo-like surface as @x12i/helpers/firebase-rtdb (insertOne, find, findOne, updateOne, query, per-doc get/set/update/delete, etc.). Uses MONGO_URI (or MONGODB_URI) from .env after loading via the same env loader as other helpers.
initMongoDb is async (MongoDB must connect over the network) unlike synchronous Firebase RTDB init:
const { initMongoDb, getMongoDb } = require("@x12i/helpers/mongo");
const mongo = await initMongoDb(); // reads MONGO_URI from .env
const users = mongo.collection("app/users");
const { insertedId } = await users.insertOne({ email: "[email protected]" });
await mongo.ref("app").remove(); // drops collections under path prefix (see docs)Live tests: MONGO_LIVE_TESTS=1 — see test/mongo.live.test.js.
Amazon S3 (object I/O)
Subpath: @x12i/helpers/s3. Same method names as GCS: createS3Client, uploadObject, openReadStream, readObjectBuffer, listObjects, objectExists, deleteObject, getObjectMetadata, setObjectMetadata, plus matching error classes (S3NotFoundError, …). Configure S3_BUCKET, AWS_REGION, optional S3_ENDPOINT / S3_FORCE_PATH_STYLE for MinIO or LocalStack, and standard AWS credentials.
const { createS3Client } = require("@x12i/helpers/s3");
const s3 = createS3Client({ prefix: "my-app/uploads" });
await s3.uploadObject("k.txt", "hi", { contentType: "text/plain" });Live tests: S3_LIVE_TESTS=1 — see test/s3.live.test.js.
Helper documentation
See helpers-docs/ for per-helper docs (what it is, why it exists, usage, and any .env keys it reads).
API
mapObject(source, mapping, opts)createMapper(mapping, opts)mapArray(sourceArray, mapping, opts)deepGet(obj, path)deepSet(obj, path, value)requestToCurl(req)curlToRequest(curlCommand)buildUrl(baseUrl, pathOrUrl, query)createBaseUrlClient(baseUrl, defaults)defineContract(config)createRegistry()createBridge(config)batchCall(bridge, paramsList, opts)chainBridges(...bridges)initFirebaseRtdb(options)getFirebaseRtdb()initFirebaseFirestore(options)getFirebaseFirestore()
Subpath only (not re-exported from the root package):
@x12i/helpers/gcs:createGcsClient(options),GcsHelperError,GcsNotFoundError,GcsPermissionDeniedError,GcsFailedPreconditionError,GcsObjectTooLargeError@x12i/helpers/mongo:initMongoDb(options),getMongoDb()@x12i/helpers/s3:createS3Client(options),S3HelperError,S3NotFoundError,S3PermissionDeniedError,S3FailedPreconditionError,S3ObjectTooLargeError
