# Backend integration (/docs/backend) InProduct is imported into your backend. It does not need a separate hosted server. After [installation](/docs/installation), save this as `product.mjs` and run `node product.mjs`: ```js import { slugify } from '@mp-lb/inproduct'; const createProduct = (name, existingSlugs) => { const slug = slugify(name); if (!slug) throw new Error('product name must produce a non-empty slug'); if (existingSlugs.has(slug)) throw new Error('product slug already exists'); return { name: name.trim(), slug }; }; console.log(createProduct('Everyday Mug', new Set())); ``` In a real API, validate input at your request boundary, use your application's error handling, and enforce uniqueness transactionally in your own database. Authentication, storage, catalog, cart and checkout are application responsibilities. This example's Set is not persistent storage. ## MyShop backend example [#myshop-backend-example] `apps/myshop-backend` is a local-only consumer of the public storage library. It creates storage with the disk adapter and mounts the library's Node HTTP handler. `apps/myshop-frontend` uses the HTTP client and headless hook through Vite's proxy. Run `zap task myshop` to build the libraries and start both services. File uploads persist under ignored `apps/myshop-backend/.data/files` across browser reloads and backend restarts. See [MyShop](/docs/myshop) and [File storage](/docs/storage). The separate `apps/landing-page` is the public marketing site. The private core/server/tRPC packages remain shared scaffold examples; consumers of the public libraries do not need them. MyShop's file API uses the library's HTTP handler. # InProduct (/docs) InProduct puts reusable building blocks in the application that owns its data and workflows. This is an early scaffold: the published library currently contains **one utility, `slugify`**. Feature flags, audit logs, notifications, payments and commerce APIs are not implemented. ## Start small [#start-small] - [Install the package](/docs/installation) and call it from your own code. - [Initialize object storage](/docs/storage) with a local memory adapter or your own persistence. - [Integrate with a backend](/docs/backend) without a hosted service or API key. - [Run MyShop locally](/docs/myshop) to see a storefront and backend-powered file storage. The library is `@mp-lb/inproduct`. The repository's `@mp-lb/inproduct-myshop-backend` package is the private local demo backend, not a package to install in your app. Object storage is a separate package, `@mp-lb/inproduct-storage`; it is not re-exported by `@mp-lb/inproduct`. ## Planned pieces [#planned-pieces] These modules are under development and are not exported by the current package. The descriptions below are intended scope, not available APIs. ### Feature flags [#feature-flags] Turn features on or off for specific users. No feature-flag API is available yet. ### Audit logs [#audit-logs] Track who changed what, and when. No audit-log API is available yet. ### AI observability [#ai-observability] Inspect AI requests, responses and costs. No AI-observability API is available yet. ### Notifications [#notifications] Send and manage notifications from your app. No notification API is available yet. ### Prompts [#prompts] Manage prompts as version-controlled Markdown. No prompt-management API is available yet. # Installation (/docs/installation) Use Node.js 22.13 or newer with an ESM application: ```sh pnpm add @mp-lb/inproduct ``` ```ts import { slugify } from '@mp-lb/inproduct'; const slug = slugify(' Everyday Mug! '); console.log(slug); // everyday-mug ``` Type declarations and declaration maps ship with the package. No environment variables, account setup, database, network access or runtime dependencies are required by this utility. There is no CommonJS `require` export. ## Behavior and application responsibilities [#behavior-and-application-responsibilities] `slugify` trims and lowercases text, replaces runs outside ASCII `a-z` and `0-9` with hyphens, and removes leading/trailing hyphens. It does not transliterate Unicode. Punctuation-only or non-ASCII-only labels can produce an empty string. Different labels can produce the same slug. Your application must validate empty values and enforce uniqueness. It does not generate IDs, sanitize HTML, authorize access or reserve URLs. The package's TypeScript declarations provide its complete API surface. # MyShop local example (/docs/myshop) MyShop is a private, local-only React/Vite frontend with a Node HTTP backend. It demonstrates the public InProduct libraries with a real client/server boundary. It is not Next.js. There are no real payments, orders or customer records. ## Reproducible setup [#reproducible-setup] Install Node.js 22.13+, pnpm 11 and Zapper, then clone the repository: ```sh git clone https://github.com/mp-lb/inproduct.git cd inproduct zap task setup zap task context-build zap task myshop zap launch MyShop zap status ``` `zap task myshop` builds the libraries and starts `myshop-backend` and `myshop-frontend`. Zap allocates both ports; no database credentials or external services are needed. The separate `landing-page` service runs the public marketing site. ## Storefront and products [#storefront-and-products] Four deterministic products are loaded from `apps/myshop-frontend/src/catalog.ts`: Everyday mug ($18), Market tote ($24), Field notebook ($12) and Desk plant ($16). Prices use USD cents. 1. Add a mug and tote to the cart. Adjust quantities or remove an item. 2. Open **Admin → Manage products**, add a product and watch its slug preview update. 3. Rename or reprice a product. The storefront and existing cart lines update together. 4. Try a duplicate name or punctuation-only name to see application validation. 5. Delete a product; it disappears from the storefront and cart. 6. Select **Reset demo** to restore the products and clear the cart. Product slugs come from `@mp-lb/inproduct`. Catalog/cart logic remains example frontend code held in the current tab. There is no checkout or order creation. ## File storage through the backend [#file-storage-through-the-backend] Open **Admin → File storage**. Upload a file (up to 10 MiB) or choose **Add example file**. Search names/tags, filter category/tag, edit metadata, download, or confirm deletion. Usage totals cover all files regardless of filters; longer lists paginate. The backend creates a file service with the library's disk adapter and mounts its HTTP handler. The frontend creates an HTTP client and passes it to the headless hook. The app supplies navigation, its table and forms; the library supplies file operations, validation, transport, persistence and async state. See [File storage](/docs/storage). Files and metadata are saved in ignored `apps/myshop-backend/.data/files`. They survive browser reloads, frontend/backend restarts and **Reset demo**. Delete files explicitly in File storage. Files are not malware-scanned; no production storage is accessed. ## Local-only boundary [#local-only-boundary] Both MyShop packages are private, excluded from Changesets and ordinary builds, and have no production start script. `zap task myshop-check` builds both explicitly for local verification. Both services bind to loopback. The backend checks request hosts and origins, but has no production login or multi-user authorization. Never expose either service through a tunnel or add it to hosting/deployment settings. Stop with `zap down myshop-frontend myshop-backend`. Inspect logs with `zap logs myshop-frontend --no-follow` or `zap logs myshop-backend --no-follow`. # File storage (/docs/storage) `@mp-lb/inproduct-storage` is an independent ESM package for storing objects by key. It requires Node.js 22.13 or newer. It does not require the InProduct backend scaffold, an account, environment variables, or a hosted runtime. React is optional for the headless hook. ## Install and initialize [#install-and-initialize] Install the published package into your application: ```sh pnpm add @mp-lb/inproduct-storage ``` For development within this repository, use the workspace dependency and build it: ```sh pnpm --filter add @mp-lb/inproduct-storage@workspace:* pnpm --filter @mp-lb/inproduct-storage build ``` ```ts import { createStorage, createMemoryStorageAdapter } from '@mp-lb/inproduct-storage'; const storage = createStorage({ adapter: createMemoryStorageAdapter(), prefix: 'receipts', maxBytes: 5 * 1024 * 1024, }); await storage.put('2026/order-123.txt', { body: new TextEncoder().encode('Order 123'), contentType: 'text/plain', }); const receipt = await storage.get('2026/order-123.txt'); if (receipt) console.log(new TextDecoder().decode(receipt.body)); await storage.delete('2026/order-123.txt'); ``` ## Configure storage [#configure-storage] Pass an `adapter` to `createStorage`. The optional `prefix` and `maxBytes` values are captured at initialization; changing the original options later has no effect. - `prefix` namespaces keys: the example stores `receipts/2026/order-123.txt` in the adapter. Omit it for no namespace. An empty prefix is invalid. - `maxBytes` limits each write's byte length. It must be a nonnegative safe integer; zero allows empty objects only. Omit it for no limit. It does not limit reads or total stored bytes. Keys and prefixes are case-sensitive, nonempty slash-separated names. Empty segments, `.` and `..` segments, backslashes, and control characters are rejected. Percent escapes are literal and Unicode is not normalized. Adapters must treat keys as opaque names, not decode them into filesystem paths. Prefixes are naming conveniences, not tenant isolation; your application must authorize access before calling storage. ## Read, replace, and delete [#read-replace-and-delete] `put` stores a `Uint8Array` body and optional caller-supplied `contentType`. It replaces the complete object at an existing key. Content type is metadata, not file validation. `get` returns the object, or `undefined` when absent. `delete` succeeds even when the key is already absent. All three operations are asynchronous. Invalid configuration throws synchronously. Invalid keys reject operations with `TypeError`; writes exceeding `maxBytes` reject with `RangeError`. Adapter failures propagate unchanged, so permission or network failures must not be treated as absence. The shipped TypeScript declarations and declaration maps are the API reference. ## Memory adapter and persistence [#memory-adapter-and-persistence] **Memory storage is ephemeral. All objects are lost when the process exits.** Use `createMemoryStorageAdapter` for local development and tests. Each instance owns a separate store; share an adapter between facades when you want them to share data. Bytes and metadata are copied on writes and reads, including Node Buffer input, so caller mutations cannot change stored objects. Total memory use is unbounded. For durable storage, implement the exported `StorageAdapter` contract and pass it to `createStorage`. Your adapter owns credentials, persistence, concurrency, and transport. It must preserve bytes and content type, isolate stored data from caller mutation, replace objects on `put`, make `delete` idempotent, and return `undefined` only for missing objects on `get`. The low-level object API has no listing. It does not provide streaming, signed URLs, retries, or metadata database. Objects are buffered in memory. Your application owns authentication, authorization, and file validation. ## Build a backend-backed file manager [#build-a-backend-backed-file-manager] Create the storage service on your Node backend and mount the library's HTTP handler: ```ts import { createServer } from 'node:http'; import { createFileStorage } from '@mp-lb/inproduct-storage'; import { createDiskFileStorageAdapter, createFileStorageHandler } from '@mp-lb/inproduct-storage/node'; const storage = createFileStorage({ adapter: createDiskFileStorageAdapter({ directory: './data/files' }) }); const server = createServer(createFileStorageHandler({ storage, authorize: (request) => yourExistingAdminCheck(request), })); ``` `yourExistingAdminCheck` is your own authorization function, not a library export. The handler requires authorization for every operation. Use your application's configured port and existing authentication; scope storage to the appropriate tenant. The default path is `/api/files`. Serve it on the frontend's origin or behind your proxy. The frontend only needs an HTTP client, a hook and your table/forms: ```tsx import { createFileStorageClient } from '@mp-lb/inproduct-storage'; import { useFileStorage } from '@mp-lb/inproduct-storage/react'; const client = createFileStorageClient('/api/files'); export const FileStorageSection = () => { const storage = useFileStorage(client); // Render storage.files, loading, busy and error. // Wire uploads to storage.uploadFile(file), filters to storage.setFilters(...), // and actions to storage.edit(...), storage.download(id), storage.remove(id). return

{storage.usage?.count ?? 0} files

; }; ``` The hook handles refresh, stale responses, pagination and browser transfers. React 18 or 19 is required only for `/react`. The base entry point does not import React or Node built-ins. Import `/node` only on the backend. Operations return a success boolean and expose failures through `error`; no repeated event-handler catches needed. ## Durable local files [#durable-local-files] The disk adapter keeps bytes and metadata in the configured directory across browser reloads and backend restarts. Use one adapter instance per directory in a single process. Writes use atomic replacement and are serialized within the instance. It buffers file contents and scans records for listing; use another adapter for large collections, distributed processes or shared/network filesystems. Keep data outside public/static roots. Atomic replacement is not a backup or a power-loss guarantee. The service defaults to a 10 MiB file limit; the handler bounds request bodies to 11 MiB including multipart overhead. Increase both limits together when necessary. Duplicate names produce separate IDs. Deletion removes bytes immediately. Edits change metadata. Search covers names/tags; exact category/tag filters combine with search. Usage totals remain independent of filters. Files are not malware-scanned; no audit ledger, retention policy or total-memory/disk quota is supplied. For tests and ephemeral prototypes, `createFileStorage()` without an adapter still uses isolated memory storage. MyShop uses the disk adapter on its backend, never in-memory file operations in the browser. See [MyShop](/docs/myshop) for the complete frontend/backend demo and its local-only access boundary.