{"_id":"@avandar/clients","_rev":"3-a84fe628ce9e039dfc220e091279bf85","name":"@avandar/clients","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.0":{"name":"@avandar/clients","version":"0.1.0","license":"MIT","_id":"@avandar/clients@0.1.0","maintainers":[{"name":"jpsyx","email":"pablowritescode@gmail.com"}],"homepage":"https://github.com/AvandarLabs/avandar/tree/main/packages/shared/clients#readme","bugs":{"url":"https://github.com/AvandarLabs/avandar/issues"},"dist":{"shasum":"13993748c63ff834793527cf8f8685e360444484","tarball":"https://registry.npmjs.org/@avandar/clients/-/clients-0.1.0.tgz","fileCount":6,"integrity":"sha512-1nrwkEwRoJmbwM3mE/Tsp6jeOTujky8Uzf8/M+Xg0J6VJw59Whb7/L+FHibSypTckFnWK7bwMQfDlngOKel43g==","signatures":[{"sig":"MEQCIHc5779byUpqT6gJd3bjBL3sM/dcQXkEicsUq34NWY9YAiBh5cstFtGX6TGe6NpYXq+/hWaWl92js8F8BBhzPHi3SA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@avandar%2fclients@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":126386},"type":"module","_from":"file:avandar-clients-0.1.0.tgz","engines":{"node":">=22.0.0"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"scripts":{"test":"vitest run","build":"tsup","test:watch":"vitest","type-check":"tsc --noEmit"},"_npmUser":{"name":"jpsyx","email":"pablowritescode@gmail.com"},"_resolved":"/tmp/b14f431d358251a76373af63a770c681/avandar-clients-0.1.0.tgz","_integrity":"sha512-1nrwkEwRoJmbwM3mE/Tsp6jeOTujky8Uzf8/M+Xg0J6VJw59Whb7/L+FHibSypTckFnWK7bwMQfDlngOKel43g==","repository":{"url":"git+https://github.com/AvandarLabs/avandar.git","type":"git","directory":"packages/shared/clients"},"_npmVersion":"11.12.1","description":"Typed CRUD client primitives and database-specific CRUD type helpers","directories":{},"sideEffects":false,"_nodeVersion":"24.15.0","dependencies":{"type-fest":"^5.4.4","ts-pattern":"^5.7.0","@avandar/utils":"0.1.0","@avandar/logger":"0.1.0","@avandar/modules":"0.1.0","@supabase/postgrest-js":"^2.99.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"zod":"^4.1.13","tsup":"^8.5.0","vitest":"^3.2.4","typescript":"~5.9.3","@supabase/supabase-js":"^2.98.0"},"peerDependencies":{"zod":"^4.1.13","@supabase/supabase-js":"^2.98.0"},"_npmOperationalInternal":{"tmp":"tmp/clients_0.1.0_1786411752104_0.6688319104527967","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@avandar/clients","version":"0.1.1","license":"MIT","_id":"@avandar/clients@0.1.1","maintainers":[{"name":"jpsyx","email":"pablowritescode@gmail.com"}],"homepage":"https://github.com/AvandarLabs/avandar/tree/main/packages/shared/clients#readme","bugs":{"url":"https://github.com/AvandarLabs/avandar/issues"},"dist":{"shasum":"0a79769bb684ba7debcb37e960c3d1f8cd5730af","tarball":"https://registry.npmjs.org/@avandar/clients/-/clients-0.1.1.tgz","fileCount":6,"integrity":"sha512-qcLHjC9NCFUIXhCJtYw46p2VT0xJPsaL4jtSYkbNedgxu3cGnjmn3A1d3aOhkCdZ7IhFiT06fpjwMjQT0hIX8Q==","signatures":[{"sig":"MEQCIAko0P6yAU+MvT+ffIuR7PpnySIeGEKHGhpLgFpRUa6GAiAwwwO6L0DJJj543hSznGSH/odxf/c4QaGgc7SMy/21jQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@avandar%2fclients@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":126386},"type":"module","_from":"file:avandar-clients-0.1.1.tgz","engines":{"node":">=22.0.0"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"scripts":{"test":"vitest run","build":"tsup","test:watch":"vitest","type-check":"tsc --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:8bfa5b0e-df2e-437b-8f22-646b260d1919"}},"_resolved":"/tmp/f8ccb913073cc7c587ddf250f2876587/avandar-clients-0.1.1.tgz","_integrity":"sha512-qcLHjC9NCFUIXhCJtYw46p2VT0xJPsaL4jtSYkbNedgxu3cGnjmn3A1d3aOhkCdZ7IhFiT06fpjwMjQT0hIX8Q==","repository":{"url":"git+https://github.com/AvandarLabs/avandar.git","type":"git","directory":"packages/shared/clients"},"_npmVersion":"11.12.1","description":"Typed CRUD client primitives and database-specific CRUD type helpers","directories":{},"sideEffects":false,"_nodeVersion":"24.15.0","dependencies":{"type-fest":"^5.4.4","ts-pattern":"^5.7.0","@avandar/utils":"0.1.1","@avandar/logger":"0.1.1","@avandar/modules":"0.1.1","@supabase/postgrest-js":"^2.99.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"zod":"^4.1.13","tsup":"^8.5.0","vitest":"^3.2.4","typescript":"~5.9.3","@supabase/supabase-js":"^2.98.0"},"peerDependencies":{"zod":"^4.1.13","@supabase/supabase-js":"^2.98.0"},"_npmOperationalInternal":{"tmp":"tmp/clients_0.1.1_1786447322213_0.5871620281871057","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@avandar/clients","version":"0.1.2","description":"Typed CRUD client primitives and database-specific CRUD type helpers","license":"MIT","repository":{"type":"git","url":"git+https://github.com/AvandarLabs/avandar.git","directory":"packages/shared/clients"},"homepage":"https://github.com/AvandarLabs/avandar/tree/main/packages/shared/clients#readme","bugs":{"url":"https://github.com/AvandarLabs/avandar/issues"},"type":"module","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"sideEffects":false,"dependencies":{"@supabase/postgrest-js":"^2.99.0","ts-pattern":"^5.7.0","type-fest":"^5.4.4","@avandar/logger":"0.1.2","@avandar/modules":"0.1.2","@avandar/utils":"0.1.2"},"peerDependencies":{"@supabase/supabase-js":"^2.98.0","zod":"^4.1.13"},"devDependencies":{"@supabase/supabase-js":"^2.98.0","tsup":"^8.5.0","typescript":"~5.9.3","vitest":"^3.2.4","zod":"^4.1.13"},"engines":{"node":">=22.0.0"},"publishConfig":{"access":"public"},"scripts":{"build":"tsup","test":"vitest run","test:watch":"vitest","type-check":"tsc --noEmit"},"_id":"@avandar/clients@0.1.2","_integrity":"sha512-YLCDqVJ8hyv00l6eOg0ImNin+UGXvA3cdrMXks+O1iuH8BDXQOa1FuY8SiYWCYCjFN4V6sDOj+RzLTCjM+jEzg==","_resolved":"/tmp/5e3f7a5008ff4c074e11e764728d0a3d/avandar-clients-0.1.2.tgz","_from":"file:avandar-clients-0.1.2.tgz","_nodeVersion":"24.15.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-YLCDqVJ8hyv00l6eOg0ImNin+UGXvA3cdrMXks+O1iuH8BDXQOa1FuY8SiYWCYCjFN4V6sDOj+RzLTCjM+jEzg==","shasum":"63a61b9c06c5cdde7fc4d75cead511a8abc3d495","tarball":"https://registry.npmjs.org/@avandar/clients/-/clients-0.1.2.tgz","fileCount":6,"unpackedSize":130513,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@avandar%2fclients@0.1.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCID+EyeJZnmOGYJjyQC7Qvty+EaBkkOEPv1LYiyBnFCaeAiA5Gs6QZZL3PvdskZCMPW2gNljarC7T+fK2vE3SNZViZA=="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:8bfa5b0e-df2e-437b-8f22-646b260d1919"}},"directories":{},"maintainers":[{"name":"jpsyx","email":"pablowritescode@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/clients_0.1.2_1786500349501_0.8005045025393953"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-11T01:29:11.941Z","modified":"2026-08-12T02:05:50.086Z","0.1.0":"2026-08-11T01:29:12.396Z","0.1.1":"2026-08-11T11:22:02.343Z","0.1.2":"2026-08-12T02:05:49.697Z"},"bugs":{"url":"https://github.com/AvandarLabs/avandar/issues"},"license":"MIT","homepage":"https://github.com/AvandarLabs/avandar/tree/main/packages/shared/clients#readme","repository":{"type":"git","url":"git+https://github.com/AvandarLabs/avandar.git","directory":"packages/shared/clients"},"description":"Typed CRUD client primitives and database-specific CRUD type helpers","maintainers":[{"name":"jpsyx","email":"pablowritescode@gmail.com"}],"readme":"# @avandar/clients\n\nTyped CRUD client primitives. Provides:\n\n- A base `ServiceClient` module — the lowest-level client primitive (just a\n  named module). All other client builders extend it.\n- A generic `ModelCrudClient` — a database-agnostic CRUD client for any\n  data model. Defines the standard interface (`getById`, `getAll`,\n  `getPage`, `insert`, `update`, `delete`, etc.) that `@avandar/query-hooks`'s\n  `withQueryHooks` knows how to wrap.\n- A `SupabaseCrudClient` — a concrete Supabase implementation of the\n  generic CRUD client, with table-name-aware types pulled from a registered\n  database type.\n- A `makeParserRegistry` builder for translating between database row\n  variants and frontend model variants.\n- A `Register` interface that downstream consumers augment to register\n  their Supabase `Database` type.\n\nThis package separates *what a model client does* (CRUD operations) from\n*where the data lives* (Supabase, an HTTP API, an in-memory store, etc.).\n\nESM only. Requires Node 22+.\n\n## Install\n\n```sh\npnpm add @avandar/clients\npnpm add zod @supabase/supabase-js\n```\n\n`zod` and `@supabase/supabase-js` are peer dependencies. `zod` is required:\nthe parser registry is built on it and its types appear throughout the public\nAPI. `@supabase/supabase-js` is a peer because you pass a live `SupabaseClient`\nacross the API boundary, so it must be a single shared copy.\n\n## Usage\n\n```ts\nimport {\n  createServiceClient,\n  createSupabaseCrudClient,\n  makeParserRegistry,\n} from \"@avandar/clients\";\n\n// Register your Supabase Database type once, anywhere in the codebase\ndeclare module \"@avandar/clients\" {\n  interface Register {\n    supabaseDatabase: Database;\n  }\n}\n\nconst userParsers = makeParserRegistry<UserCrudSpec>().build({\n  modelName: \"User\",\n  DBReadSchema: UserDBReadSchema,\n  fromDBReadToModelRead: (db) => ({ id: db.id, name: db.full_name }),\n  fromModelInsertToDBInsert: (m) => ({ full_name: m.name }),\n  fromModelUpdateToDBUpdate: (m) => ({ full_name: m.name }),\n});\n\nconst UserClient = createSupabaseCrudClient({\n  modelName: \"User\",\n  tableName: \"users\",\n  dbTablePrimaryKey: \"id\",\n  parsers: userParsers,\n  dbClient: supabase,\n});\n\nconst users = await UserClient.getAll();\nconst user = await UserClient.getById({ id: \"...\" });\n```\n\n---\n\n## Service client\n\nThe base building block. Every client in this package is composed on top of\na `ServiceClient`.\n\n### `createServiceClient(clientName)`\n\nCreates a named `@avandar/modules` module with a single member,\n`getClientName()`. By convention `clientName` ends in `\"Client\"`.\n\n| Type            | Description                                              |\n| --------------- | -------------------------------------------------------- |\n| `ServiceClient` | The module type returned by `createServiceClient`        |\n\n---\n\n## Model CRUD client\n\nA database-agnostic CRUD client for any model. Implementers provide the\nlow-level `crudFunctions` (one function per CRUD operation, working in\n\"DB\" types) and parsers (converting between DB and frontend model types).\nThe client exposes a high-level surface in frontend model types.\n\n### `createModelCrudClient(options)`\n\nBuilds a `ModelCrudClient<M>`.\n\n| Option                  | Description                                                                       |\n| ----------------------- | --------------------------------------------------------------------------------- |\n| `modelName`             | Model name (used to brand the client and log lines)                               |\n| `parsers`               | A parser registry from `makeParserRegistry`                                       |\n| `crudFunctions`         | The implementation of each CRUD operation against the data store                  |\n| `defaultGetAllBatchSize`| Page size used by `getAll` to paginate (default `500`)                            |\n| `additionalQueries`     | Extra query functions merged into the client; eligible for auto-generated hooks   |\n| `additionalMutations`   | Extra mutation functions merged into the client; eligible for auto-generated hooks |\n\nThe returned client exposes the following methods (all return promises):\n\n| Method                   | Description                                                       |\n| ------------------------ | ----------------------------------------------------------------- |\n| `getById({ id })`        | Single read by primary key. `id` may be nullish (returns `undefined`) |\n| `getCount({ where? })`   | Total row count matching the filter                               |\n| `getPage({ where?, pageSize, pageNum })` | One page of rows plus pagination metadata         |\n| `getAll({ where?, batchSize? })`         | All rows, internally paginated                    |\n| `getOne({ where? })`     | First row matching the filter                                     |\n| `insert({ data, upsert?, onConflict? })` | Insert (or upsert) a single row                   |\n| `bulkInsert({ data[], ... })`            | Insert (or upsert) many rows                      |\n| `update({ id, data })`   | Update a single row                                               |\n| `delete({ id })`         | Delete a single row                                               |\n| `bulkDelete({ ids[] })`  | Delete many rows                                                  |\n| `parsers`                | The parser registry                                               |\n| `crudFunctions`          | The raw CRUD functions, in case direct DB-type access is needed   |\n\n### Types\n\n| Type                          | Description                                                                |\n| ----------------------------- | -------------------------------------------------------------------------- |\n| `CrudModelSpec`               | Generic spec: `modelName`, `modelPrimaryKeyType`, plus `DBRead`/`DBInsert`/`DBUpdate` and `Read`/`Insert`/`Update` shapes |\n| `ModelCrudClient<M>`          | The full client surface for a given `CrudModelSpec`                        |\n| `ClientReturningOnlyPromises` | Record shape required for `additionalQueries` and `additionalMutations`    |\n| `UpsertOptions`               | `{ upsert?, onConflict? }` shared by `insert` / `bulkInsert`               |\n\n---\n\n## Supabase CRUD client\n\nA concrete CRUD client backed by Supabase. Reads `DBRead`/`DBInsert`/\n`DBUpdate` from the database type registered through the `Register`\ninterface, so callers only need to declare frontend model types.\n\n### `createSupabaseCrudClient(options)`\n\n| Option              | Description                                                                                |\n| ------------------- | ------------------------------------------------------------------------------------------ |\n| `modelName`         | Model name                                                                                 |\n| `tableName`         | Supabase table name (typed against the registered database)                                |\n| `dbTablePrimaryKey` | Primary key column name (typed against the table row)                                      |\n| `parsers`           | Parser registry from `makeParserRegistry`                                                  |\n| `dbClient`          | A `SupabaseClient<RegisteredSupabaseDatabase>` instance                                    |\n| `queries?`          | Builder that returns extra promise-returning query functions; receives `dbClient`, parsers, logger |\n| `mutations?`        | Builder that returns extra promise-returning mutation functions; same arguments            |\n\nThe returned client extends `ModelCrudClient` with `setDBClient(newClient)`\nfor swapping out the underlying Supabase client (used to seed data with an\nadmin client during tests).\n\n### `withSupabaseClient(client, initializer)`\n\nLower-level helper used internally to attach a `setDBClient` method to any\n`ServiceClient`. Exposed in case you need to build a Supabase-aware client\nwithout using the full CRUD machinery.\n\n### Types\n\n| Type                  | Description                                                              |\n| --------------------- | ------------------------------------------------------------------------ |\n| `SupabaseCrudModelSpec` | Wrapper that derives DB types from the registered Supabase `Database`  |\n| `WithSupabaseClient`  | A `ServiceClient` augmented with `setDBClient`                           |\n\n---\n\n## SQLite CRUD client\n\n`createSqliteCrudClient` mirrors the public surface of\n`createSupabaseCrudClient` so callers can be branched between the two without\nchanging consumer code. It is used for local-first / desktop setups where reads\nand writes hit a local SQLite mirror.\n\nIt does **not** know how to reach your database. You inject a transport, which\nkeeps this package free of any particular IPC layer or driver and lets the\nclient work against Electrobun IPC, better-sqlite3, a remote endpoint, or a\nfake in tests:\n\n```ts\nimport type { SqliteTransport } from \"@avandar/clients\";\n\nconst transport: SqliteTransport = {\n  query: ({ sql, params }) => runReturningRows(sql, params),\n  run: ({ sql, params }) => runReturningNothing(sql, params),\n};\n\nconst WidgetClient = createSqliteCrudClient({\n  modelName: \"Widget\",\n  tableName: \"widgets\",\n  dbTablePrimaryKey: \"id\",\n  parsers: widgetParsers,\n  dbClient,\n  transport,\n});\n```\n\n| Type              | Description                                            |\n| ----------------- | ------------------------------------------------------ |\n| `SqliteTransport` | `{ query, run }`, both taking `{ sql, params }`         |\n\nKnown limitations: JSON-typed columns are stringified on write but returned as\nraw strings on read, and boolean columns come back as integer 0/1. Model\nparsers must coerce both.\n\n---\n\n## Parser registry\n\n### `makeParserRegistry<M>().build(config)`\n\nBuilds a `ModelCrudParserRegistry<M>` from:\n\n- a Zod schema for `DBRead` rows (validated on every read),\n- a `fromDBReadToModelRead` parser,\n- a `fromModelInsertToDBInsert` parser,\n- a `fromModelUpdateToDBUpdate` parser.\n\nThe builder hardens each parser:\n\n- `fromDBReadToModelRead` first runs the Zod schema with a per-model error\n  map.\n- `fromModelInsert` / `fromModelUpdate` strip any keys not present in the\n  DB schema (Supabase rejects unknown keys) and remove `undefined` values\n  that may have been re-introduced by the `pick`.\n\n| Type                     | Description                                              |\n| ------------------------ | -------------------------------------------------------- |\n| `ModelCrudParserRegistry`| The shape returned from `.build(...)`                    |\n\n---\n\n## Register interface\n\n`Register` is an empty interface intended for declaration-merging by the\nconsumer.\n\n```ts\nimport type { Database } from \"./database.types\";\n\ndeclare module \"@avandar/clients\" {\n  interface Register {\n    supabaseDatabase: Database;\n  }\n}\n```\n\nOnce registered, `tableName`, `DBRead`, `DBInsert`, and `DBUpdate` types\nflow through `createSupabaseCrudClient` automatically.\n\n| Type       | Description                                                  |\n| ---------- | ------------------------------------------------------------ |\n| `Register` | Augmentation target for registering a Supabase `Database`    |\n\n## License\n\nMIT\n","readmeFilename":"README.md"}