@simplix-react/mock 0.0.1 → 0.0.2

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 ADDED
@@ -0,0 +1,304 @@
1
+ # @simplix-react/mock
2
+
3
+ Auto-generated MSW handlers and PGlite repositories from `@simplix-react/contract`.
4
+
5
+ > **Prerequisites:** Requires a contract defined with `@simplix-react/contract`.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pnpm add @simplix-react/mock
11
+ ```
12
+
13
+ Peer dependencies:
14
+
15
+ | Package | Required | Notes |
16
+ | --- | --- | --- |
17
+ | `@simplix-react/contract` | Yes | Provides the API contract definition |
18
+ | `zod` | Yes | `>=4.0.0` |
19
+ | `msw` | Optional | `>=2.0.0` — needed for MSW handler generation |
20
+ | `@electric-sql/pglite` | Optional | `>=0.2.0` — needed for in-browser PostgreSQL |
21
+
22
+ ## Quick Example
23
+
24
+ ```ts
25
+ import { defineContract } from "@simplix-react/contract";
26
+ import {
27
+ setupMockWorker,
28
+ deriveMockHandlers,
29
+ executeSql,
30
+ } from "@simplix-react/mock";
31
+ import { z } from "zod";
32
+
33
+ // 1. Define your contract
34
+ const projectContract = defineContract({
35
+ domain: "project",
36
+ basePath: "/api",
37
+ entities: {
38
+ task: {
39
+ path: "/tasks",
40
+ schema: z.object({
41
+ id: z.string(),
42
+ title: z.string(),
43
+ status: z.enum(["todo", "done"]),
44
+ createdAt: z.date(),
45
+ }),
46
+ createSchema: z.object({ title: z.string() }),
47
+ updateSchema: z.object({ status: z.enum(["todo", "done"]).optional() }),
48
+ },
49
+ },
50
+ });
51
+
52
+ // 2. Derive handlers and bootstrap
53
+ await setupMockWorker({
54
+ dataDir: "idb://project-mock",
55
+ migrations: [
56
+ async (db) => {
57
+ await executeSql(db, `
58
+ CREATE TABLE IF NOT EXISTS tasks (
59
+ id TEXT PRIMARY KEY,
60
+ title TEXT NOT NULL,
61
+ status TEXT NOT NULL DEFAULT 'todo',
62
+ created_at TIMESTAMP DEFAULT NOW(),
63
+ updated_at TIMESTAMP DEFAULT NOW()
64
+ )
65
+ `);
66
+ },
67
+ ],
68
+ seed: [],
69
+ handlers: deriveMockHandlers(projectContract.config),
70
+ });
71
+ ```
72
+
73
+ After `setupMockWorker` resolves, every `fetch("/api/tasks")` call in your application is intercepted by MSW and served from the in-browser PGlite database.
74
+
75
+ ## API Overview
76
+
77
+ ### Core
78
+
79
+ | Export | Description |
80
+ | --- | --- |
81
+ | `setupMockWorker(config)` | Bootstraps PGlite + MSW in one call |
82
+ | `deriveMockHandlers(config, mockConfig?)` | Generates CRUD MSW handlers from a contract |
83
+ | `MockServerConfig` | Configuration for `setupMockWorker` |
84
+ | `MockEntityConfig` | Per-entity mock configuration (table name, limits, relations) |
85
+
86
+ ### PGlite Lifecycle
87
+
88
+ | Export | Description |
89
+ | --- | --- |
90
+ | `initPGlite(dataDir)` | Initializes the PGlite singleton |
91
+ | `getPGliteInstance()` | Returns the active instance (throws if uninitialized) |
92
+ | `resetPGliteInstance()` | Clears the singleton (for test teardown) |
93
+
94
+ ### Result Types
95
+
96
+ | Export | Description |
97
+ | --- | --- |
98
+ | `MockResult<T>` | Discriminated success/failure result type |
99
+ | `mockSuccess(data)` | Creates a success result |
100
+ | `mockFailure(error)` | Creates a failure result |
101
+
102
+ ### SQL Utilities
103
+
104
+ | Export | Description |
105
+ | --- | --- |
106
+ | `mapRow(row)` | Converts a snake_case DB row to camelCase (with Date parsing) |
107
+ | `mapRows(rows)` | Maps an array of rows |
108
+ | `toCamelCase(str)` | `snake_case` → `camelCase` |
109
+ | `toSnakeCase(str)` | `camelCase` → `snake_case` |
110
+ | `DbRow` | Type alias for `Record<string, unknown>` |
111
+ | `buildSetClause(input)` | Builds a parameterized SQL SET clause |
112
+ | `SetClauseResult` | Return type of `buildSetClause` |
113
+ | `mapPgError(err)` | Maps PGlite errors to HTTP-friendly `MockError` |
114
+ | `MockError` | Structured error with status, code, message |
115
+
116
+ ### Migration Helpers
117
+
118
+ | Export | Description |
119
+ | --- | --- |
120
+ | `tableExists(db, tableName)` | Checks if a table exists |
121
+ | `columnExists(db, tableName, columnName)` | Checks if a column exists |
122
+ | `executeSql(db, sql)` | Executes semicolon-separated SQL statements |
123
+ | `addColumnIfNotExists(db, table, column, def)` | Idempotent column addition |
124
+
125
+ ## Key Concepts
126
+
127
+ ### MSW Handler Derivation
128
+
129
+ `deriveMockHandlers` reads your contract's entity definitions and generates five handlers per entity:
130
+
131
+ ```
132
+ GET {basePath}{entityPath} → List (with filter, sort, pagination)
133
+ GET {basePath}{entityPath}/:id → Get by ID (with relation loading)
134
+ POST {basePath}{entityPath} → Create (auto-generates UUID)
135
+ PATCH {basePath}{entityPath}/:id → Partial update
136
+ DELETE {basePath}{entityPath}/:id → Delete
137
+ ```
138
+
139
+ For child entities with a `parent` definition, list and create routes are nested under the parent path:
140
+
141
+ ```
142
+ GET {basePath}{parentPath}/:parentId{entityPath} → List by parent
143
+ POST {basePath}{parentPath}/:parentId{entityPath} → Create under parent
144
+ ```
145
+
146
+ #### Query Parameters
147
+
148
+ List endpoints support the following query parameters:
149
+
150
+ | Parameter | Example | Description |
151
+ | --- | --- | --- |
152
+ | `sort` | `sort=title:asc,createdAt:desc` | Comma-separated `field:direction` pairs |
153
+ | `page` | `page=2` | Offset-based page number (1-indexed) |
154
+ | `limit` | `limit=20` | Rows per page (capped by `maxLimit`) |
155
+ | `{field}` | `status=done` | Equality filter on any column |
156
+
157
+ #### Relation Loading
158
+
159
+ Configure `belongsTo` relations in `MockEntityConfig` to automatically JOIN related data on GET-by-ID requests:
160
+
161
+ ```ts
162
+ const handlers = deriveMockHandlers(contract.config, {
163
+ task: {
164
+ relations: {
165
+ project: {
166
+ table: "projects",
167
+ localKey: "projectId",
168
+ foreignKey: "id", // defaults to "id"
169
+ type: "belongsTo",
170
+ },
171
+ },
172
+ },
173
+ });
174
+ ```
175
+
176
+ ### PGlite Integration
177
+
178
+ PGlite provides a full PostgreSQL database running in the browser via WebAssembly. This package manages a singleton instance:
179
+
180
+ ```
181
+ initPGlite(dataDir) → getPGliteInstance() → resetPGliteInstance()
182
+ ```
183
+
184
+ Data persists across page reloads via IndexedDB when using an `idb://` data directory.
185
+
186
+ ### SQL Utilities
187
+
188
+ The SQL utility modules handle the mapping between JavaScript (camelCase) and PostgreSQL (snake_case) conventions:
189
+
190
+ - **Row mapping** — `mapRow`/`mapRows` convert query results to JS objects, automatically parsing `_at` columns as `Date` instances.
191
+ - **Query building** — `buildSetClause` constructs parameterized UPDATE queries from partial objects, always appending `updated_at = NOW()`.
192
+ - **Error mapping** — `mapPgError` classifies PGlite exceptions into structured errors with appropriate HTTP status codes.
193
+ - **Migration helpers** — Idempotent utilities (`tableExists`, `addColumnIfNotExists`, `executeSql`) for writing safe migration functions.
194
+
195
+ ## Guides
196
+
197
+ ### Writing Migrations
198
+
199
+ Migrations are async functions that receive a PGlite instance. Use the migration helpers for idempotent operations:
200
+
201
+ ```ts
202
+ import type { PGlite } from "@electric-sql/pglite";
203
+ import { tableExists, executeSql, addColumnIfNotExists } from "@simplix-react/mock";
204
+
205
+ export async function migrate(db: PGlite) {
206
+ if (!(await tableExists(db, "tasks"))) {
207
+ await executeSql(db, `
208
+ CREATE TABLE tasks (
209
+ id TEXT PRIMARY KEY,
210
+ title TEXT NOT NULL,
211
+ status TEXT NOT NULL DEFAULT 'todo',
212
+ project_id TEXT,
213
+ created_at TIMESTAMP DEFAULT NOW(),
214
+ updated_at TIMESTAMP DEFAULT NOW()
215
+ )
216
+ `);
217
+ }
218
+
219
+ // Safe to call multiple times
220
+ await addColumnIfNotExists(db, "tasks", "priority", "INTEGER DEFAULT 0");
221
+ }
222
+ ```
223
+
224
+ ### Writing Seed Functions
225
+
226
+ Seed functions populate the database with initial data after migrations:
227
+
228
+ ```ts
229
+ import type { PGlite } from "@electric-sql/pglite";
230
+
231
+ export async function seed(db: PGlite) {
232
+ await db.query(
233
+ `INSERT INTO tasks (id, title, status) VALUES ($1, $2, $3)
234
+ ON CONFLICT (id) DO NOTHING`,
235
+ ["task-1", "Sample Task", "todo"],
236
+ );
237
+ }
238
+ ```
239
+
240
+ ### Custom Repository Handlers
241
+
242
+ For advanced use cases beyond auto-generated CRUD, use the SQL utilities directly:
243
+
244
+ ```ts
245
+ import { http, HttpResponse } from "msw";
246
+ import {
247
+ getPGliteInstance,
248
+ mapRows,
249
+ mapPgError,
250
+ mockSuccess,
251
+ mockFailure,
252
+ } from "@simplix-react/mock";
253
+
254
+ const customHandler = http.get("/api/tasks/overdue", async () => {
255
+ try {
256
+ const db = getPGliteInstance();
257
+ const result = await db.query(
258
+ "SELECT * FROM tasks WHERE status != 'done' AND created_at < NOW() - INTERVAL '7 days'",
259
+ );
260
+ const tasks = mapRows(result.rows as Record<string, unknown>[]);
261
+ return HttpResponse.json({ data: mockSuccess(tasks) });
262
+ } catch (err) {
263
+ const mapped = mapPgError(err);
264
+ return HttpResponse.json(
265
+ { code: mapped.code, message: mapped.message },
266
+ { status: mapped.status },
267
+ );
268
+ }
269
+ });
270
+ ```
271
+
272
+ ### Testing with PGlite
273
+
274
+ Reset the singleton between tests to ensure isolation:
275
+
276
+ ```ts
277
+ import { initPGlite, resetPGliteInstance, executeSql } from "@simplix-react/mock";
278
+ import { afterEach, beforeEach, describe, it } from "vitest";
279
+
280
+ describe("task repository", () => {
281
+ beforeEach(async () => {
282
+ const db = await initPGlite("memory://");
283
+ await executeSql(db, `
284
+ CREATE TABLE tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL)
285
+ `);
286
+ });
287
+
288
+ afterEach(() => {
289
+ resetPGliteInstance();
290
+ });
291
+
292
+ it("inserts a task", async () => {
293
+ // ...
294
+ });
295
+ });
296
+ ```
297
+
298
+ ## Related Packages
299
+
300
+ | Package | Description |
301
+ | --- | --- |
302
+ | [`@simplix-react/contract`](../contract) | Define type-safe API contracts consumed by this package |
303
+ | [`@simplix-react/react`](../react) | React Query hooks derived from the same contract |
304
+ | [`@simplix-react/testing`](../testing) | Test utilities for contract-based mocking |
package/dist/index.d.ts CHANGED
@@ -4,73 +4,270 @@ import { EntityDefinition, OperationDefinition, ApiContractConfig } from '@simpl
4
4
  import { z } from 'zod';
5
5
 
6
6
  /**
7
- * Initialize a PGlite instance with IndexedDB persistence.
7
+ * Initializes a singleton PGlite instance with the given data directory.
8
+ *
9
+ * Uses a dynamic import so that `@electric-sql/pglite` remains an optional peer
10
+ * dependency. Subsequent calls return the already-initialized instance.
11
+ *
12
+ * @param dataDir - The IndexedDB data directory for persistence (e.g. `"idb://simplix-mock"`).
13
+ * @returns The initialized PGlite instance.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { initPGlite } from "@simplix-react/mock";
18
+ *
19
+ * const db = await initPGlite("idb://project-mock");
20
+ * await db.query("SELECT 1");
21
+ * ```
22
+ *
23
+ * @see {@link getPGliteInstance} - Retrieves the current instance without re-initializing.
24
+ * @see {@link resetPGliteInstance} - Clears the singleton for testing.
8
25
  */
9
26
  declare function initPGlite(dataDir: string): Promise<PGlite>;
10
27
  /**
11
- * Get the current PGlite instance.
12
- * Throws if not initialized.
28
+ * Returns the current PGlite singleton instance.
29
+ *
30
+ * Throws an error if {@link initPGlite} has not been called yet.
31
+ *
32
+ * @returns The active PGlite instance.
33
+ * @throws Error if PGlite has not been initialized.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * import { getPGliteInstance } from "@simplix-react/mock";
38
+ *
39
+ * const db = getPGliteInstance();
40
+ * const result = await db.query("SELECT * FROM tasks");
41
+ * ```
13
42
  */
14
43
  declare function getPGliteInstance(): PGlite;
15
44
  /**
16
- * Reset the PGlite instance (for testing).
45
+ * Resets the PGlite singleton instance to `null`.
46
+ *
47
+ * Intended for use in test teardown to ensure a clean state between test runs.
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * import { resetPGliteInstance } from "@simplix-react/mock";
52
+ *
53
+ * afterEach(() => {
54
+ * resetPGliteInstance();
55
+ * });
56
+ * ```
17
57
  */
18
58
  declare function resetPGliteInstance(): void;
19
59
 
20
60
  type RequestHandler = unknown;
61
+ /**
62
+ * Describes the configuration required by {@link setupMockWorker}.
63
+ *
64
+ * Combines PGlite database setup (migrations and seed data) with MSW request
65
+ * handlers into a single bootstrap configuration.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * import type { MockServerConfig } from "@simplix-react/mock";
70
+ * import { deriveMockHandlers } from "@simplix-react/mock";
71
+ * import { projectContract } from "./contract";
72
+ * import { runMigrations } from "./migrations";
73
+ * import { seedData } from "./seed";
74
+ *
75
+ * const config: MockServerConfig = {
76
+ * dataDir: "idb://project-mock",
77
+ * migrations: [runMigrations],
78
+ * seed: [seedData],
79
+ * handlers: deriveMockHandlers(projectContract.config),
80
+ * };
81
+ * ```
82
+ *
83
+ * @see {@link setupMockWorker} - Consumes this config to bootstrap the mock environment.
84
+ */
21
85
  interface MockServerConfig {
22
86
  /**
23
- * IndexedDB data directory for PGlite persistence
87
+ * IndexedDB data directory for PGlite persistence.
88
+ *
89
+ * @defaultValue `"idb://simplix-mock"`
24
90
  */
25
91
  dataDir?: string;
26
92
  /**
27
- * Migration functions to run in order
93
+ * Migration functions to run in order.
94
+ *
95
+ * Each function receives the PGlite instance and should create or alter tables.
28
96
  */
29
97
  migrations: Array<(db: PGlite) => Promise<void>>;
30
98
  /**
31
- * Seed functions to run in order (after migrations)
99
+ * Seed functions to run in order (after migrations).
100
+ *
101
+ * Each function receives the PGlite instance and should insert initial data.
32
102
  */
33
103
  seed: Array<(db: PGlite) => Promise<void>>;
34
104
  /**
35
- * MSW request handlers
105
+ * MSW request handlers to register with the service worker.
106
+ *
107
+ * Typically produced by {@link deriveMockHandlers}.
36
108
  */
37
109
  handlers: RequestHandler[];
38
110
  }
39
111
  /**
40
- * Set up the mock server with PGlite + MSW.
112
+ * Bootstraps a complete mock environment with PGlite and MSW.
41
113
  *
42
- * 1. Initializes PGlite at the given dataDir
43
- * 2. Runs all migrations sequentially
114
+ * Performs the following steps in order:
115
+ * 1. Initializes a PGlite instance at the configured `dataDir`
116
+ * 2. Runs all migration functions sequentially
44
117
  * 3. Runs all seed functions sequentially
45
- * 4. Starts the MSW service worker
118
+ * 4. Starts the MSW service worker with the provided handlers
119
+ *
120
+ * @param config - The {@link MockServerConfig} describing database setup and handlers.
121
+ *
122
+ * @example
123
+ * ```ts
124
+ * import { setupMockWorker, deriveMockHandlers } from "@simplix-react/mock";
125
+ * import { projectContract } from "./contract";
126
+ * import { runMigrations } from "./migrations";
127
+ * import { seedData } from "./seed";
128
+ *
129
+ * await setupMockWorker({
130
+ * dataDir: "idb://project-mock",
131
+ * migrations: [runMigrations],
132
+ * seed: [seedData],
133
+ * handlers: deriveMockHandlers(projectContract.config),
134
+ * });
135
+ * ```
136
+ *
137
+ * @see {@link MockServerConfig} - Configuration shape.
138
+ * @see {@link deriveMockHandlers} - Generates MSW handlers from a contract.
139
+ * @see {@link initPGlite} - Underlying PGlite initialization.
46
140
  */
47
141
  declare function setupMockWorker(config: MockServerConfig): Promise<void>;
48
142
 
49
143
  type AnyEntityDef = EntityDefinition<z.ZodTypeAny, z.ZodTypeAny, z.ZodTypeAny>;
50
144
  type AnyOperationDef = OperationDefinition<z.ZodTypeAny, z.ZodTypeAny>;
51
145
  /**
52
- * Extended entity definition with mock-specific config.
53
- * Users pass this as part of the contract config.
146
+ * Provides per-entity configuration for mock handler generation.
147
+ *
148
+ * Allows overriding the default table name, pagination limits, sort order,
149
+ * and relation loading for a specific entity when calling {@link deriveMockHandlers}.
150
+ *
151
+ * @example
152
+ * ```ts
153
+ * import type { MockEntityConfig } from "@simplix-react/mock";
154
+ *
155
+ * const taskConfig: MockEntityConfig = {
156
+ * tableName: "tasks",
157
+ * defaultLimit: 20,
158
+ * maxLimit: 100,
159
+ * defaultSort: "created_at DESC",
160
+ * relations: {
161
+ * project: {
162
+ * table: "projects",
163
+ * localKey: "projectId",
164
+ * type: "belongsTo",
165
+ * },
166
+ * },
167
+ * };
168
+ * ```
169
+ *
170
+ * @see {@link deriveMockHandlers} - Consumes this config per entity.
54
171
  */
55
172
  interface MockEntityConfig {
173
+ /** Overrides the auto-derived PostgreSQL table name. */
56
174
  tableName?: string;
175
+ /**
176
+ * Default number of rows per page.
177
+ *
178
+ * @defaultValue 50
179
+ */
180
+ defaultLimit?: number;
181
+ /**
182
+ * Maximum allowed rows per page.
183
+ *
184
+ * @defaultValue 100
185
+ */
186
+ maxLimit?: number;
187
+ /**
188
+ * Default SQL ORDER BY clause.
189
+ *
190
+ * @defaultValue `"created_at DESC"`
191
+ */
192
+ defaultSort?: string;
193
+ /** Map of relation names to their `belongsTo` join configuration. */
194
+ relations?: Record<string, {
195
+ table: string;
196
+ localKey: string;
197
+ foreignKey?: string;
198
+ type: "belongsTo";
199
+ }>;
57
200
  }
58
201
  /**
59
- * Derive MSW request handlers from an API contract config.
202
+ * Derives MSW request handlers from an {@link @simplix-react/contract!ApiContractConfig}.
60
203
  *
61
- * Generates standard CRUD handlers for each entity:
62
- * - GET list (by parent if defined)
63
- * - GET by id
64
- * - POST create
65
- * - PATCH update
66
- * - DELETE
204
+ * Generates a complete set of CRUD handlers for every entity defined in the contract:
67
205
  *
68
- * For entities with mock.tableName defined, auto-generates SQL queries.
206
+ * - **GET list** supports query-param filtering, sorting, and offset-based pagination
207
+ * - **GET by id** — supports `belongsTo` relation loading via joins
208
+ * - **POST create** — auto-generates a UUID `id` when not provided
209
+ * - **PATCH update** — partial updates with automatic `updated_at` timestamp
210
+ * - **DELETE** — removes the row by `id`
211
+ *
212
+ * All handlers read from and write to the PGlite singleton managed by
213
+ * {@link initPGlite}/{@link getPGliteInstance}.
214
+ *
215
+ * @typeParam TEntities - The entities map from the contract config.
216
+ * @typeParam TOperations - The operations map from the contract config.
217
+ * @param config - The API contract configuration object.
218
+ * @param mockConfig - Optional per-entity mock configuration keyed by entity name.
219
+ * @returns An array of MSW `HttpHandler` instances ready for use with `setupWorker`.
220
+ *
221
+ * @example
222
+ * ```ts
223
+ * import { deriveMockHandlers } from "@simplix-react/mock";
224
+ * import { projectContract } from "./contract";
225
+ *
226
+ * const handlers = deriveMockHandlers(projectContract.config, {
227
+ * task: {
228
+ * tableName: "tasks",
229
+ * defaultLimit: 20,
230
+ * relations: {
231
+ * project: {
232
+ * table: "projects",
233
+ * localKey: "projectId",
234
+ * type: "belongsTo",
235
+ * },
236
+ * },
237
+ * },
238
+ * });
239
+ * ```
240
+ *
241
+ * @see {@link MockEntityConfig} - Per-entity configuration options.
242
+ * @see {@link setupMockWorker} - High-level bootstrap that accepts these handlers.
243
+ * @see {@link @simplix-react/contract!ApiContractConfig} - The contract config shape.
69
244
  */
70
245
  declare function deriveMockHandlers<TEntities extends Record<string, AnyEntityDef>, TOperations extends Record<string, AnyOperationDef>>(config: ApiContractConfig<TEntities, TOperations>, mockConfig?: Record<string, MockEntityConfig>): msw.HttpHandler[];
71
246
 
72
247
  /**
73
- * Unified result type for all mock repository operations.
248
+ * Represents the outcome of a mock repository operation.
249
+ *
250
+ * Encapsulates either a successful result with data or a failure with an error code
251
+ * and message. Used throughout `@simplix-react/mock` to provide consistent return
252
+ * types from all database operations.
253
+ *
254
+ * @typeParam T - The type of the data payload on success.
255
+ *
256
+ * @example
257
+ * ```ts
258
+ * import type { MockResult } from "@simplix-react/mock";
259
+ *
260
+ * function handleResult(result: MockResult<{ id: string; name: string }>) {
261
+ * if (result.success) {
262
+ * console.log(result.data?.name);
263
+ * } else {
264
+ * console.error(result.error?.code, result.error?.message);
265
+ * }
266
+ * }
267
+ * ```
268
+ *
269
+ * @see {@link mockSuccess} - Creates a successful result.
270
+ * @see {@link mockFailure} - Creates a failure result.
74
271
  */
75
272
  interface MockResult<T> {
76
273
  success: boolean;
@@ -81,11 +278,34 @@ interface MockResult<T> {
81
278
  };
82
279
  }
83
280
  /**
84
- * Create a successful result.
281
+ * Creates a successful {@link MockResult} wrapping the given data.
282
+ *
283
+ * @typeParam T - The type of the data payload.
284
+ * @param data - The data to wrap in a success result.
285
+ * @returns A {@link MockResult} with `success: true` and the provided data.
286
+ *
287
+ * @example
288
+ * ```ts
289
+ * import { mockSuccess } from "@simplix-react/mock";
290
+ *
291
+ * const result = mockSuccess({ id: "1", title: "My Task" });
292
+ * // { success: true, data: { id: "1", title: "My Task" } }
293
+ * ```
85
294
  */
86
295
  declare function mockSuccess<T>(data: T): MockResult<T>;
87
296
  /**
88
- * Create a failure result.
297
+ * Creates a failure {@link MockResult} with the given error code and message.
298
+ *
299
+ * @param error - An object containing an error `code` and human-readable `message`.
300
+ * @returns A {@link MockResult} with `success: false` and the provided error.
301
+ *
302
+ * @example
303
+ * ```ts
304
+ * import { mockFailure } from "@simplix-react/mock";
305
+ *
306
+ * const result = mockFailure({ code: "not_found", message: "Task not found" });
307
+ * // { success: false, error: { code: "not_found", message: "Task not found" } }
308
+ * ```
89
309
  */
90
310
  declare function mockFailure(error: {
91
311
  code: string;
@@ -93,65 +313,259 @@ declare function mockFailure(error: {
93
313
  }): MockResult<never>;
94
314
 
95
315
  /**
96
- * Database row type
316
+ * Represents a raw database row with unknown column values.
317
+ *
318
+ * Used as the input type for row-mapping functions that convert snake_case
319
+ * database columns to camelCase JavaScript properties.
320
+ *
321
+ * @see {@link mapRow} - Maps a single row.
322
+ * @see {@link mapRows} - Maps an array of rows.
97
323
  */
98
324
  type DbRow = Record<string, unknown>;
99
325
  /**
100
- * Convert snake_case string to camelCase
326
+ * Converts a snake_case string to camelCase.
327
+ *
328
+ * @param str - The snake_case input string.
329
+ * @returns The camelCase equivalent.
330
+ *
331
+ * @example
332
+ * ```ts
333
+ * import { toCamelCase } from "@simplix-react/mock";
334
+ *
335
+ * toCamelCase("created_at"); // "createdAt"
336
+ * toCamelCase("project_id"); // "projectId"
337
+ * ```
101
338
  */
102
339
  declare function toCamelCase(str: string): string;
103
340
  /**
104
- * Convert camelCase string to snake_case
341
+ * Converts a camelCase string to snake_case.
342
+ *
343
+ * @param str - The camelCase input string.
344
+ * @returns The snake_case equivalent.
345
+ *
346
+ * @example
347
+ * ```ts
348
+ * import { toSnakeCase } from "@simplix-react/mock";
349
+ *
350
+ * toSnakeCase("createdAt"); // "created_at"
351
+ * toSnakeCase("projectId"); // "project_id"
352
+ * ```
105
353
  */
106
354
  declare function toSnakeCase(str: string): string;
107
355
  /**
108
- * Map a database row (snake_case) to a camelCase object.
109
- * Automatically converts columns ending in `_at` to Date objects.
356
+ * Maps a single database row from snake_case columns to a camelCase object.
357
+ *
358
+ * Columns ending in `_at` are automatically converted to `Date` objects.
359
+ *
360
+ * @typeParam T - The expected shape of the mapped object.
361
+ * @param row - The raw database row with snake_case keys.
362
+ * @returns The mapped object with camelCase keys.
363
+ *
364
+ * @example
365
+ * ```ts
366
+ * import { mapRow } from "@simplix-react/mock";
367
+ *
368
+ * const row = { id: "1", project_id: "p1", created_at: "2025-01-01T00:00:00Z" };
369
+ * const mapped = mapRow<{ id: string; projectId: string; createdAt: Date }>(row);
370
+ * // { id: "1", projectId: "p1", createdAt: Date("2025-01-01T00:00:00Z") }
371
+ * ```
372
+ *
373
+ * @see {@link mapRows} - Maps an array of rows.
374
+ * @see {@link toCamelCase} - Underlying case conversion.
110
375
  */
111
376
  declare function mapRow<T>(row: DbRow): T;
112
377
  /**
113
- * Map an array of database rows to camelCase objects.
378
+ * Maps an array of database rows from snake_case to camelCase objects.
379
+ *
380
+ * Delegates to {@link mapRow} for each row.
381
+ *
382
+ * @typeParam T - The expected shape of each mapped object.
383
+ * @param rows - The array of raw database rows.
384
+ * @returns An array of mapped camelCase objects.
385
+ *
386
+ * @example
387
+ * ```ts
388
+ * import { mapRows } from "@simplix-react/mock";
389
+ *
390
+ * const rows = [
391
+ * { id: "1", task_name: "Build" },
392
+ * { id: "2", task_name: "Test" },
393
+ * ];
394
+ * const mapped = mapRows<{ id: string; taskName: string }>(rows);
395
+ * // [{ id: "1", taskName: "Build" }, { id: "2", taskName: "Test" }]
396
+ * ```
114
397
  */
115
398
  declare function mapRows<T>(rows: DbRow[]): T[];
116
399
 
400
+ /**
401
+ * Represents the result of {@link buildSetClause}.
402
+ *
403
+ * Contains the SQL SET clause string, the ordered parameter values, and the next
404
+ * available parameter index for appending additional conditions (e.g. a WHERE clause).
405
+ *
406
+ * @see {@link buildSetClause} - Produces this result.
407
+ */
117
408
  interface SetClauseResult {
409
+ /** The SQL SET clause string (e.g. `"name = $1, updated_at = NOW()"`). */
118
410
  clause: string;
411
+ /** The ordered parameter values corresponding to the placeholders. */
119
412
  values: unknown[];
413
+ /** The next available `$N` parameter index. */
120
414
  nextIndex: number;
121
415
  }
122
416
  /**
123
- * Build a dynamic SQL SET clause from a partial object.
124
- * Converts camelCase keys to snake_case columns.
125
- * Skips undefined values.
126
- * Automatically appends `updated_at = NOW()`.
417
+ * Builds a parameterized SQL SET clause from a partial update object.
418
+ *
419
+ * Converts camelCase object keys to snake_case column names, skips `undefined`
420
+ * values, serializes nested objects as JSONB, and automatically appends
421
+ * `updated_at = NOW()`.
422
+ *
423
+ * @typeParam T - The shape of the update DTO.
424
+ * @param input - The partial object whose defined keys become SET assignments.
425
+ * @param startIndex - The starting `$N` placeholder index.
426
+ * @returns A {@link SetClauseResult} with the clause, values, and next index.
427
+ *
428
+ * @example
429
+ * ```ts
430
+ * import { buildSetClause } from "@simplix-react/mock";
431
+ *
432
+ * const { clause, values, nextIndex } = buildSetClause(
433
+ * { title: "Updated Task", status: "done" },
434
+ * );
435
+ * // clause: "title = $1, status = $2, updated_at = NOW()"
436
+ * // values: ["Updated Task", "done"]
437
+ * // nextIndex: 3
438
+ *
439
+ * const sql = `UPDATE tasks SET ${clause} WHERE id = $${nextIndex}`;
440
+ * // sql: "UPDATE tasks SET title = $1, status = $2, updated_at = NOW() WHERE id = $3"
441
+ * ```
442
+ *
443
+ * @see {@link SetClauseResult} - The return type.
127
444
  */
128
445
  declare function buildSetClause<T extends object>(input: T, startIndex?: number): SetClauseResult;
129
446
 
447
+ /**
448
+ * Represents a mapped database error with an HTTP-friendly status code.
449
+ *
450
+ * Produced by {@link mapPgError} from raw PostgreSQL/PGlite exceptions.
451
+ *
452
+ * @see {@link mapPgError} - Creates instances from raw errors.
453
+ */
130
454
  interface MockError {
455
+ /** The HTTP status code corresponding to the error type. */
131
456
  status: number;
457
+ /** A machine-readable error code (e.g. `"unique_violation"`, `"not_found"`). */
132
458
  code: string;
459
+ /** A human-readable error description. */
133
460
  message: string;
134
461
  }
135
462
  /**
136
- * Map PostgreSQL errors to HTTP-friendly MockError.
463
+ * Maps a raw PostgreSQL/PGlite error to an HTTP-friendly {@link MockError}.
464
+ *
465
+ * Inspects the error message to classify the error:
466
+ *
467
+ * | Pattern | Code | HTTP Status |
468
+ * | ------------------------ | ----------------------- | ----------- |
469
+ * | unique / duplicate | `unique_violation` | 409 |
470
+ * | foreign key | `foreign_key_violation` | 422 |
471
+ * | not-null / null value | `not_null_violation` | 422 |
472
+ * | not found / no rows | `not_found` | 404 |
473
+ * | (unrecognized) | `query_error` | 500 |
474
+ *
475
+ * @param err - The raw error thrown by a PGlite query.
476
+ * @returns A structured {@link MockError} with status, code, and message.
477
+ *
478
+ * @example
479
+ * ```ts
480
+ * import { mapPgError } from "@simplix-react/mock";
481
+ *
482
+ * try {
483
+ * await db.query("INSERT INTO tasks ...");
484
+ * } catch (err) {
485
+ * const mapped = mapPgError(err);
486
+ * console.error(mapped.code, mapped.status); // e.g. "unique_violation" 409
487
+ * }
488
+ * ```
137
489
  */
138
490
  declare function mapPgError(err: unknown): MockError;
139
491
 
140
492
  /**
141
- * Check if a table exists in the database.
493
+ * Checks whether a table exists in the database by querying `information_schema.tables`.
494
+ *
495
+ * @param db - The PGlite instance.
496
+ * @param tableName - The name of the table to check.
497
+ * @returns `true` if the table exists, `false` otherwise.
498
+ *
499
+ * @example
500
+ * ```ts
501
+ * import { initPGlite, tableExists } from "@simplix-react/mock";
502
+ *
503
+ * const db = await initPGlite("idb://project-mock");
504
+ * if (!(await tableExists(db, "tasks"))) {
505
+ * await db.query("CREATE TABLE tasks (id TEXT PRIMARY KEY)");
506
+ * }
507
+ * ```
142
508
  */
143
509
  declare function tableExists(db: PGlite, tableName: string): Promise<boolean>;
144
510
  /**
145
- * Check if a column exists in a table.
511
+ * Checks whether a column exists in a table by querying `information_schema.columns`.
512
+ *
513
+ * @param db - The PGlite instance.
514
+ * @param tableName - The table to inspect.
515
+ * @param columnName - The column name to check for.
516
+ * @returns `true` if the column exists, `false` otherwise.
517
+ *
518
+ * @example
519
+ * ```ts
520
+ * import { initPGlite, columnExists } from "@simplix-react/mock";
521
+ *
522
+ * const db = await initPGlite("idb://project-mock");
523
+ * const has = await columnExists(db, "tasks", "priority");
524
+ * ```
146
525
  */
147
526
  declare function columnExists(db: PGlite, tableName: string, columnName: string): Promise<boolean>;
148
527
  /**
149
- * Execute multiple SQL statements separated by semicolons.
528
+ * Executes multiple SQL statements separated by semicolons.
529
+ *
530
+ * Splits the input on `;`, trims each statement, filters out empty strings,
531
+ * and executes them sequentially.
532
+ *
533
+ * @param db - The PGlite instance.
534
+ * @param sql - A string containing one or more semicolon-separated SQL statements.
535
+ *
536
+ * @example
537
+ * ```ts
538
+ * import { initPGlite, executeSql } from "@simplix-react/mock";
539
+ *
540
+ * const db = await initPGlite("idb://project-mock");
541
+ * await executeSql(db, `
542
+ * CREATE TABLE projects (id TEXT PRIMARY KEY, name TEXT NOT NULL);
543
+ * CREATE TABLE tasks (id TEXT PRIMARY KEY, project_id TEXT REFERENCES projects(id));
544
+ * `);
545
+ * ```
150
546
  */
151
547
  declare function executeSql(db: PGlite, sql: string): Promise<void>;
152
548
  /**
153
- * Add a column to a table if it doesn't exist.
549
+ * Adds a column to a table only if it does not already exist.
550
+ *
551
+ * Combines {@link columnExists} with an `ALTER TABLE ADD COLUMN` statement
552
+ * for safe, idempotent schema migrations.
553
+ *
554
+ * @param db - The PGlite instance.
555
+ * @param tableName - The target table.
556
+ * @param columnName - The column name to add.
557
+ * @param columnDef - The column type definition (e.g. `"TEXT NOT NULL DEFAULT ''"`).
558
+ *
559
+ * @example
560
+ * ```ts
561
+ * import { initPGlite, addColumnIfNotExists } from "@simplix-react/mock";
562
+ *
563
+ * const db = await initPGlite("idb://project-mock");
564
+ * await addColumnIfNotExists(db, "tasks", "priority", "INTEGER DEFAULT 0");
565
+ * ```
566
+ *
567
+ * @see {@link columnExists} - Used internally to check existence.
154
568
  */
155
569
  declare function addColumnIfNotExists(db: PGlite, tableName: string, columnName: string, columnDef: string): Promise<void>;
156
570
 
157
- export { type DbRow, type MockError, type MockResult, type MockServerConfig, type SetClauseResult, addColumnIfNotExists, buildSetClause, columnExists, deriveMockHandlers, executeSql, getPGliteInstance, initPGlite, mapPgError, mapRow, mapRows, mockFailure, mockSuccess, resetPGliteInstance, setupMockWorker, tableExists, toCamelCase, toSnakeCase };
571
+ export { type DbRow, type MockEntityConfig, type MockError, type MockResult, type MockServerConfig, type SetClauseResult, addColumnIfNotExists, buildSetClause, columnExists, deriveMockHandlers, executeSql, getPGliteInstance, initPGlite, mapPgError, mapRow, mapRows, mockFailure, mockSuccess, resetPGliteInstance, setupMockWorker, tableExists, toCamelCase, toSnakeCase };
package/dist/index.js CHANGED
@@ -144,37 +144,65 @@ function deriveMockHandlers(config, mockConfig) {
144
144
  const handlers = [];
145
145
  const { basePath, entities } = config;
146
146
  for (const [name, entity] of Object.entries(entities)) {
147
- const tableName = mockConfig?.[name]?.tableName ?? toSnakeCase(name) + "s";
147
+ const entityConfig = mockConfig?.[name];
148
+ const tableName = entityConfig?.tableName ?? toSnakeCase(name) + "s";
149
+ const defaultLimit = entityConfig?.defaultLimit ?? 50;
150
+ const maxLimit = entityConfig?.maxLimit ?? 100;
151
+ const defaultSort = entityConfig?.defaultSort ?? "created_at DESC";
152
+ const relations = entityConfig?.relations;
148
153
  if (entity.parent) {
149
154
  const listPath = `${basePath}${entity.parent.path}/:${entity.parent.param}${entity.path}`;
150
155
  handlers.push(
151
- http.get(listPath, async ({ params }) => {
152
- const parentId = params[entity.parent.param];
156
+ http.get(listPath, async ({ request, params: routeParams }) => {
157
+ const parentId = routeParams[entity.parent.param];
153
158
  const parentColumn = toSnakeCase(entity.parent.param);
159
+ const searchParams = parseSearchParams(request.url);
154
160
  return toResponse(
155
- await queryList(tableName, parentColumn, parentId)
161
+ await queryListWithParams(
162
+ tableName,
163
+ parentColumn,
164
+ parentId,
165
+ searchParams,
166
+ defaultLimit,
167
+ maxLimit,
168
+ defaultSort
169
+ )
156
170
  );
157
171
  })
158
172
  );
159
173
  } else {
160
174
  handlers.push(
161
- http.get(`${basePath}${entity.path}`, async () => {
162
- return toResponse(await queryAll(tableName));
175
+ http.get(`${basePath}${entity.path}`, async ({ request }) => {
176
+ const searchParams = parseSearchParams(request.url);
177
+ return toResponse(
178
+ await queryListWithParams(
179
+ tableName,
180
+ void 0,
181
+ void 0,
182
+ searchParams,
183
+ defaultLimit,
184
+ maxLimit,
185
+ defaultSort
186
+ )
187
+ );
163
188
  })
164
189
  );
165
190
  }
166
191
  handlers.push(
167
- http.get(`${basePath}${entity.path}/:id`, async ({ params }) => {
168
- const id = params.id;
192
+ http.get(`${basePath}${entity.path}/:id`, async ({ params: routeParams }) => {
193
+ const id = routeParams.id;
194
+ if (relations) {
195
+ return toResponse(await queryByIdWithRelations(tableName, id, relations));
196
+ }
169
197
  return toResponse(await queryById(tableName, id));
170
198
  })
171
199
  );
172
200
  if (entity.parent) {
173
201
  const createPath = `${basePath}${entity.parent.path}/:${entity.parent.param}${entity.path}`;
174
202
  handlers.push(
175
- http.post(createPath, async ({ request, params }) => {
203
+ http.post(createPath, async ({ request, params: routeParams }) => {
176
204
  const dto = await request.json();
177
- const parentId = params[entity.parent.param];
205
+ const parentId = routeParams[entity.parent.param];
178
206
  dto[entity.parent.param] = parentId;
179
207
  return toResponse(await insertRow(tableName, dto), 201);
180
208
  })
@@ -188,52 +216,137 @@ function deriveMockHandlers(config, mockConfig) {
188
216
  );
189
217
  }
190
218
  handlers.push(
191
- http.patch(`${basePath}${entity.path}/:id`, async ({ request, params }) => {
192
- const id = params.id;
219
+ http.patch(`${basePath}${entity.path}/:id`, async ({ request, params: routeParams }) => {
220
+ const id = routeParams.id;
193
221
  const dto = await request.json();
194
222
  return toResponse(await updateRow(tableName, id, dto));
195
223
  })
196
224
  );
197
225
  handlers.push(
198
- http.delete(`${basePath}${entity.path}/:id`, async ({ params }) => {
199
- const id = params.id;
226
+ http.delete(`${basePath}${entity.path}/:id`, async ({ params: routeParams }) => {
227
+ const id = routeParams.id;
200
228
  return toResponse(await deleteRow(tableName, id));
201
229
  })
202
230
  );
203
231
  }
204
232
  return handlers;
205
233
  }
206
- async function queryAll(tableName) {
234
+ function parseSearchParams(requestUrl) {
235
+ const url = new URL(requestUrl, "http://localhost");
236
+ const filters = {};
237
+ let sort;
238
+ let page;
239
+ let limit;
240
+ let cursor;
241
+ url.searchParams.forEach((value, key) => {
242
+ if (key === "sort") {
243
+ sort = value;
244
+ } else if (key === "page") {
245
+ page = parseInt(value, 10);
246
+ } else if (key === "limit") {
247
+ limit = parseInt(value, 10);
248
+ } else if (key === "cursor") {
249
+ cursor = value;
250
+ } else {
251
+ filters[key] = value;
252
+ }
253
+ });
254
+ return { filters, sort, page, limit, cursor };
255
+ }
256
+ async function queryListWithParams(tableName, parentColumn, parentId, searchParams, defaultLimit, maxLimit, defaultSort) {
207
257
  try {
208
258
  const db = getPGliteInstance();
209
- const result = await db.query(`SELECT * FROM ${tableName} ORDER BY created_at DESC`);
210
- return mockSuccess(mapRows(result.rows));
259
+ const conditions = [];
260
+ const values = [];
261
+ let paramIndex = 1;
262
+ if (parentColumn && parentId) {
263
+ conditions.push(`${parentColumn} = $${paramIndex}`);
264
+ values.push(parentId);
265
+ paramIndex++;
266
+ }
267
+ for (const [key, value] of Object.entries(searchParams.filters)) {
268
+ const column = toSnakeCase(key);
269
+ conditions.push(`${column} = $${paramIndex}`);
270
+ values.push(value);
271
+ paramIndex++;
272
+ }
273
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
274
+ let orderClause = `ORDER BY ${defaultSort}`;
275
+ if (searchParams.sort) {
276
+ const sortParts = searchParams.sort.split(",").map((s) => {
277
+ const [field, dir] = s.trim().split(":");
278
+ const column = toSnakeCase(field);
279
+ const direction = dir === "desc" ? "DESC" : "ASC";
280
+ return `${column} ${direction}`;
281
+ });
282
+ orderClause = `ORDER BY ${sortParts.join(", ")}`;
283
+ }
284
+ const hasPagination = searchParams.page !== void 0 || searchParams.cursor !== void 0;
285
+ const limit = Math.min(searchParams.limit ?? defaultLimit, maxLimit);
286
+ if (hasPagination) {
287
+ const countSql = `SELECT COUNT(*) as count FROM ${tableName} ${whereClause}`;
288
+ const countResult = await db.query(countSql, values);
289
+ const total = parseInt(String(countResult.rows[0].count), 10);
290
+ const page = searchParams.page ?? 1;
291
+ const offset = (page - 1) * limit;
292
+ const dataSql = `SELECT * FROM ${tableName} ${whereClause} ${orderClause} LIMIT ${limit} OFFSET ${offset}`;
293
+ const dataResult = await db.query(dataSql, values);
294
+ const rows = mapRows(dataResult.rows);
295
+ return mockSuccess({
296
+ data: rows,
297
+ meta: {
298
+ total,
299
+ page,
300
+ limit,
301
+ hasNextPage: offset + rows.length < total
302
+ }
303
+ });
304
+ }
305
+ const sql = `SELECT * FROM ${tableName} ${whereClause} ${orderClause}`;
306
+ const result = await db.query(sql, values);
307
+ return mockSuccess({ data: mapRows(result.rows) });
211
308
  } catch (err) {
212
309
  const mapped = mapPgError(err);
213
310
  return mockFailure({ code: mapped.code, message: mapped.message });
214
311
  }
215
312
  }
216
- async function queryList(tableName, parentColumn, parentId) {
313
+ async function queryById(tableName, id) {
217
314
  try {
218
315
  const db = getPGliteInstance();
219
- const result = await db.query(
220
- `SELECT * FROM ${tableName} WHERE ${parentColumn} = $1 ORDER BY created_at DESC`,
221
- [parentId]
222
- );
223
- return mockSuccess(mapRows(result.rows));
316
+ const result = await db.query(`SELECT * FROM ${tableName} WHERE id = $1`, [id]);
317
+ if (result.rows.length === 0) {
318
+ return mockFailure({ code: "not_found", message: `${tableName} not found` });
319
+ }
320
+ return mockSuccess(mapRow(result.rows[0]));
224
321
  } catch (err) {
225
322
  const mapped = mapPgError(err);
226
323
  return mockFailure({ code: mapped.code, message: mapped.message });
227
324
  }
228
325
  }
229
- async function queryById(tableName, id) {
326
+ async function queryByIdWithRelations(tableName, id, relations) {
230
327
  try {
231
328
  const db = getPGliteInstance();
232
- const result = await db.query(`SELECT * FROM ${tableName} WHERE id = $1`, [id]);
233
- if (result.rows.length === 0) {
329
+ const mainResult = await db.query(`SELECT * FROM ${tableName} WHERE id = $1`, [id]);
330
+ if (mainResult.rows.length === 0) {
234
331
  return mockFailure({ code: "not_found", message: `${tableName} not found` });
235
332
  }
236
- return mockSuccess(mapRow(result.rows[0]));
333
+ const row = mainResult.rows[0];
334
+ const mapped = mapRow(row);
335
+ for (const [relationName, relation] of Object.entries(relations)) {
336
+ const localColumn = toSnakeCase(relation.localKey);
337
+ const fkValue = row[localColumn];
338
+ if (fkValue) {
339
+ const foreignKey = relation.foreignKey ?? "id";
340
+ const relResult = await db.query(
341
+ `SELECT * FROM ${relation.table} WHERE ${foreignKey} = $1`,
342
+ [fkValue]
343
+ );
344
+ if (relResult.rows.length > 0) {
345
+ mapped[relationName] = mapRow(relResult.rows[0]);
346
+ }
347
+ }
348
+ }
349
+ return mockSuccess(mapped);
237
350
  } catch (err) {
238
351
  const mapped = mapPgError(err);
239
352
  return mockFailure({ code: mapped.code, message: mapped.message });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplix-react/mock",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Auto-generated MSW handlers and PGlite repositories from @simplix-react/contract",
5
5
  "type": "module",
6
6
  "exports": {
@@ -9,7 +9,12 @@
9
9
  "import": "./dist/index.js"
10
10
  }
11
11
  },
12
- "files": ["dist"],
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
13
18
  "scripts": {
14
19
  "build": "tsup",
15
20
  "dev": "tsup --watch",
@@ -19,19 +24,24 @@
19
24
  "clean": "rm -rf dist .turbo"
20
25
  },
21
26
  "peerDependencies": {
22
- "@simplix-react/contract": "workspace:*",
23
27
  "@electric-sql/pglite": ">=0.2.0",
28
+ "@simplix-react/contract": "workspace:*",
24
29
  "msw": ">=2.0.0",
25
30
  "zod": ">=4.0.0"
26
31
  },
27
32
  "peerDependenciesMeta": {
28
- "@electric-sql/pglite": { "optional": true },
29
- "msw": { "optional": true }
33
+ "@electric-sql/pglite": {
34
+ "optional": true
35
+ },
36
+ "msw": {
37
+ "optional": true
38
+ }
30
39
  },
31
40
  "devDependencies": {
41
+ "@electric-sql/pglite": "^0.3.14",
42
+ "@simplix-react/config-eslint": "workspace:*",
32
43
  "@simplix-react/config-typescript": "workspace:*",
33
44
  "@simplix-react/contract": "workspace:*",
34
- "@electric-sql/pglite": "^0.3.14",
35
45
  "eslint": "^9.39.2",
36
46
  "msw": "^2.7.0",
37
47
  "tsup": "^8.5.1",