igp-personalization
v1.1.1
Published
Unified TypeScript SDK for IGP-style product personalization: local studio engine, storage adapters, validation, signing, and hosted API client.
Maintainers
Readme
igp-personalization
Unified TypeScript SDK for IGP-style product personalization. This package combines the old igp-personalization-core and igp-personalization-sdk responsibilities into one typed package:
- Local, in-process personalization engine through
createStudio(...). - Storage adapters through
createMemoryStorage()andcreateJsonFileStorage(...). - Product and design validation helpers.
- HMAC request signing shared by server and client code.
- Hosted API client through
new Client(...).
The package is written in TypeScript, builds to CommonJS in dist/, and emits .d.ts declarations for full type safety.
Install
npm install igp-personalizationRequires Node.js 18+ because the remote client uses the built-in fetch, FormData, and Blob APIs.
Build From This Repo
cd personalization-unified-sdk
npm install
npm run build
npm testOutput is generated into dist/:
dist/index.js
dist/index.d.ts
dist/**/*.js
dist/**/*.d.tsImport
CommonJS:
const {
Client,
createStudio,
createMemoryStorage,
createJsonFileStorage,
signing,
} = require('igp-personalization');TypeScript:
import {
Client,
createStudio,
createMemoryStorage,
type DesignInput,
type Product,
} from 'igp-personalization';Local Studio Engine
Use createStudio({ storage }) when you want the personalization logic to run inside your own process without HTTP.
import { createStudio, createMemoryStorage } from 'igp-personalization';
const studio = createStudio({
storage: createMemoryStorage(),
});
const products = await studio.products.list();
const mug = await studio.products.get('mug-white-11oz');
const design = await studio.designs.save({
productId: mug.id,
productName: mug.name,
layers: [{ type: 'text', text: 'Happy Birthday!', x: 300, y: 300 }],
});createStudio({ storage })
Returns:
{
storage,
products,
designs,
}Throws if the storage adapter does not expose collection(name).
Product Functions
studio.products.list()
Returns all built-in products plus custom products from storage.
const products = await studio.products.list();studio.products.get(id)
Returns one product by ID.
const product = await studio.products.get('mug-white-11oz');Throws NotFoundError if the product does not exist.
studio.products.addCustom(input)
Creates a custom product.
const product = await studio.products.addCustom({
name: 'Tote Bag',
mockupUrl: '/mockups/tote.svg',
price: 499,
category: 'Bags',
canvasWidth: 600,
canvasHeight: 600,
printAreas: [
{ x: 60, y: 80, width: 280, height: 300, shape: 'rectangle' },
],
});Throws ValidationError when required fields are missing or print areas are invalid.
studio.products.removeCustom(id)
Deletes a custom product.
await studio.products.removeCustom('custom-abc123');Throws NotFoundError if the ID is unknown.
Design Functions
studio.designs.list()
Returns saved designs, newest first.
const designs = await studio.designs.list();studio.designs.get(id)
Returns one saved design.
const design = await studio.designs.get('design_id');Throws NotFoundError if the design does not exist.
studio.designs.save(input)
Saves a design.
const design = await studio.designs.save({
productId: 'mug-white-11oz',
productName: 'Classic White Mug (11oz)',
layers: [
{ type: 'text', text: 'Happy Birthday!', x: 300, y: 300 },
{ type: 'image', url: 'https://cdn.example.com/logo.png', x: 120, y: 180 },
],
previewDataUrl: 'data:image/png;base64,...',
});Throws ValidationError if productId or layers[] is missing.
studio.designs.delete(id)
Deletes a saved design.
await studio.designs.delete('design_id');Throws NotFoundError if the design does not exist.
Storage Adapters
A storage adapter implements:
interface StorageAdapter {
collection(name: string): {
all(): Promise<Array<{ id: string }>>;
get(id: string): Promise<{ id: string } | null>;
put(doc: { id: string }): Promise<void>;
remove(id: string): Promise<boolean>;
count?(): Promise<number>;
};
}createMemoryStorage()
Ephemeral storage for tests, scripts, and demos.
const storage = createMemoryStorage();
const studio = createStudio({ storage });createJsonFileStorage({ dir })
Persists each collection as a JSON file under dir.
const storage = createJsonFileStorage({ dir: './data' });
const studio = createStudio({ storage });This creates files such as:
data/designs.json
data/custom-products.jsonValidation Helpers
sanitizePoints(points)
Validates custom polygon points. Returns sanitized points or null.
const points = sanitizePoints([
[10, 10],
[100, 10],
[100, 100],
]);sanitizeArea(area, index?)
Validates and normalizes one print area. Returns a PrintArea or null.
const area = sanitizeArea({
x: 10,
y: 20,
width: 200,
height: 120,
shape: 'rounded-rect',
});buildCustomProductFields(input)
Validates custom product input and returns normalized fields without ID or timestamps. ProductService.addCustom(...) uses this internally.
const fields = buildCustomProductFields({
name: 'Poster',
mockupUrl: '/mockups/poster.svg',
printAreas: [{ x: 50, y: 50, width: 300, height: 400, shape: 'rectangle' }],
});validateDesignInput(input)
Throws ValidationError unless the design has productId and layers[].
validateDesignInput({
productId: 'mug-white-11oz',
layers: [{ type: 'text', text: 'Hello' }],
});Signing
The SDK signs requests with:
HMAC-SHA256(secret, "METHOD\nPATH\nTIMESTAMP\nSHA256(body)")signing.sign(input)
Creates a signature.
const signature = signing.sign({
secret: 'sk_test',
method: 'POST',
path: '/v1/designs',
timestamp: Math.floor(Date.now() / 1000),
body: JSON.stringify({ productId: 'mug-white-11oz', layers: [] }),
});signing.verify(input)
Verifies the signature and timestamp skew.
const ok = signing.verify({
secret: 'sk_test',
method: 'POST',
path: '/v1/designs',
timestamp,
body,
signature,
});signing.buildStringToSign(input)
Returns the canonical string used by HMAC.
const canonical = signing.buildStringToSign({
method: 'GET',
path: '/v1/products',
timestamp: 1710000000,
body: '',
});signing.sha256Hex(input)
Returns the SHA-256 hex digest of a string or buffer.
const digest = signing.sha256Hex('hello');The individual functions sign, verify, buildStringToSign, and sha256Hex are also exported directly.
Hosted API Client
Use Client when calling the hosted /v1 API. The API secret stays on your server and is never sent over the network.
import { Client } from 'igp-personalization';
const client = new Client({
apiKey: process.env.PERSONALIZATION_API_KEY!,
apiSecret: process.env.PERSONALIZATION_API_SECRET!,
baseUrl: 'https://api.yourco.com',
});client.products.list()
const products = await client.products.list();client.products.get(id)
const product = await client.products.get('mug-white-11oz');client.products.addCustom(input)
const product = await client.products.addCustom({
name: 'Tote Bag',
mockupUrl: '/mockups/tote.svg',
printAreas: [{ x: 60, y: 80, width: 280, height: 300, shape: 'rectangle' }],
});client.products.removeCustom(id)
await client.products.removeCustom('custom-abc123');client.designs.list()
const designs = await client.designs.list();client.designs.get(id)
const design = await client.designs.get('design_id');client.designs.save(input)
const saved = await client.designs.save({
productId: 'mug-white-11oz',
layers: [{ type: 'text', text: 'Hello', x: 100, y: 120 }],
});client.designs.delete(id)
await client.designs.delete('design_id');client.uploadImage(data, options?)
Uploads an image using multipart form data. data can be a Buffer, Uint8Array, or Blob.
import fs from 'fs';
const uploaded = await client.uploadImage(fs.readFileSync('./logo.png'), {
filename: 'logo.png',
contentType: 'image/png',
});
console.log(uploaded.url);client.account()
Returns the authenticated tenant, usage, and limits.
const account = await client.account();Errors
ValidationError
Thrown by local validation when input is invalid.
try {
await studio.designs.save({} as never);
} catch (error) {
if (error instanceof ValidationError) {
console.error(error.code, error.message);
}
}NotFoundError
Thrown by local services when a product or design is missing.
PersonalizationApiError
Thrown by Client when the hosted API returns a non-2xx response.
try {
await client.designs.save({} as never);
} catch (error) {
if (error instanceof PersonalizationApiError) {
console.error(error.status, error.body);
}
}Common statuses:
400: invalid request payload.401: invalid API key or signature.402: plan limit reached.413: upload too large.429: rate limit or quota exceeded.
Public Exports
Client
PersonalizationApiError
createStudio
createMemoryStorage
createJsonFileStorage
ProductService
DesignService
BUILTIN_PRODUCTS
ALLOWED_SHAPES
normalizeProduct
sanitizeArea
sanitizePoints
buildCustomProductFields
validateDesignInput
signing
sign
verify
buildStringToSign
sha256Hex
createId
ValidationError
NotFoundErrorType exports include Product, PrintArea, Design, DesignInput, CustomProductInput, StorageAdapter, StorageCollection, Studio, ClientOptions, UploadResult, and signing input types.
