pouch-js 0.0.11
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/LICENSE.md +21 -0
- package/README.md +282 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +81 -0
- package/package.json +41 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Emmanuel C.Alozie
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# pouch-js
|
|
2
|
+
|
|
3
|
+
A schema-aware reactive FormData builder with Zod validation and full Blob/File support. Manage form state, validate data, and generate submission-ready FormData with a reactive proxy.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ **Framework Agnostic**: Works with any UI library or vanilla JS
|
|
8
|
+
- ✅ **Automatic Schema Inference**: Derive Zod schemas from your data structures
|
|
9
|
+
- ✅ **Manual Schema Validation**: Use custom Zod schemas for precise control
|
|
10
|
+
- ✅ **Reactive Data Proxy**: Live updates with deep object reactivity
|
|
11
|
+
- ✅ **Blob & File Support**: Perfect for file uploads and image handling
|
|
12
|
+
- ✅ **TypeScript First**: Full type safety and intelligent inference
|
|
13
|
+
- ✅ **FormData Generation**: Create fetch-ready FormData objects automatically
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
Copy the `src/index.ts` into your project if you don't want to add additional dependencies to your project or
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install pouch-js zod
|
|
21
|
+
|
|
22
|
+
pnpm add pouch-js zod
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Note: Zod is a peer dependency and must be installed separately.
|
|
26
|
+
|
|
27
|
+
## Basic Usage
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { pack } from "pouch-js";
|
|
31
|
+
|
|
32
|
+
// Create a form manager with automatic schema inference
|
|
33
|
+
const { data, getFormData } = pack({
|
|
34
|
+
user: {
|
|
35
|
+
name: "Alice",
|
|
36
|
+
email: "alice@example.com",
|
|
37
|
+
age: 30,
|
|
38
|
+
},
|
|
39
|
+
preferences: {
|
|
40
|
+
newsletter: true,
|
|
41
|
+
tags: ["tech", "programming"],
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Update values reactively
|
|
46
|
+
data.user.name = "Bob"; // Changes are tracked
|
|
47
|
+
data.preferences.tags.push("javascript");
|
|
48
|
+
|
|
49
|
+
// Generate FormData for submission
|
|
50
|
+
const formData = getFormData();
|
|
51
|
+
// formData contains: user[name]=Bob, user[email]=alice@example.com, etc.
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## File Upload Example
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { pack } from "pouch-js";
|
|
58
|
+
|
|
59
|
+
// Handle file inputs with full type safety
|
|
60
|
+
document.getElementById("avatar").addEventListener("change", (e) => {
|
|
61
|
+
const file = e.target.files[0];
|
|
62
|
+
|
|
63
|
+
const { data, getFormData } = pack({
|
|
64
|
+
profile: {
|
|
65
|
+
name: "John Doe",
|
|
66
|
+
avatar: file, // File object handled properly
|
|
67
|
+
documents: [file], // Works in arrays too
|
|
68
|
+
metadata: {
|
|
69
|
+
uploadDate: new Date().toISOString(),
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Submit to server
|
|
75
|
+
fetch("/api/upload", {
|
|
76
|
+
method: "POST",
|
|
77
|
+
body: getFormData(), // Contains the file properly
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Advanced Schema Validation
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { z } from "zod";
|
|
86
|
+
import { pack } from "pouch-js";
|
|
87
|
+
|
|
88
|
+
const userSchema = z.object({
|
|
89
|
+
name: z.string().min(2),
|
|
90
|
+
email: z.string().email(),
|
|
91
|
+
avatar: z.instanceof(Blob).optional(),
|
|
92
|
+
age: z.number().min(18),
|
|
93
|
+
tags: z.array(z.string()),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const { data, getFormData } = pack(
|
|
97
|
+
{
|
|
98
|
+
name: "Alice",
|
|
99
|
+
email: "alice@example.com",
|
|
100
|
+
age: 25,
|
|
101
|
+
tags: ["developer"],
|
|
102
|
+
avatar: someFile, // Optional blob
|
|
103
|
+
},
|
|
104
|
+
userSchema, // Custom schema override
|
|
105
|
+
);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## API Reference
|
|
109
|
+
|
|
110
|
+
### 1. `pack(initialData)`
|
|
111
|
+
|
|
112
|
+
Creates a form manager with inferred schema.
|
|
113
|
+
|
|
114
|
+
**Parameters:**
|
|
115
|
+
|
|
116
|
+
- `initialData`: Object or array to use as initial state
|
|
117
|
+
|
|
118
|
+
**Returns:**
|
|
119
|
+
|
|
120
|
+
- `data`: Reactive proxy of the initial data
|
|
121
|
+
- `getFormData()`: FormData: Function that returns current state as FormData
|
|
122
|
+
|
|
123
|
+
### 2. `pack(initialData, schema)`
|
|
124
|
+
|
|
125
|
+
Creates a form manager with custom Zod schema.
|
|
126
|
+
|
|
127
|
+
**Parameters:**
|
|
128
|
+
|
|
129
|
+
- `initialData`: Data matching the provided schema
|
|
130
|
+
- `schema`: Zod schema for validation
|
|
131
|
+
|
|
132
|
+
**Returns:**
|
|
133
|
+
|
|
134
|
+
- `data`: Reactive proxy of validated data
|
|
135
|
+
- `getFormData()`: FormData: Function that returns current state as FormData
|
|
136
|
+
|
|
137
|
+
## Reactive Data Pattern
|
|
138
|
+
|
|
139
|
+
The `data` object is a deep proxy that tracks changes:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { pack } from "pouch-js";
|
|
143
|
+
|
|
144
|
+
const { data, getFormData } = pack({
|
|
145
|
+
user: { name: "Alice", settings: { darkMode: true } },
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// All changes are tracked
|
|
149
|
+
data.user.name = "Bob"; // Simple property
|
|
150
|
+
data.user.settings.darkMode = false; // Nested property
|
|
151
|
+
data.user.settings.fontSize = 16; // New properties
|
|
152
|
+
|
|
153
|
+
// getFormData() captures all current changes
|
|
154
|
+
const formData = getFormData(); // Contains latest state
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### FormData Output Format
|
|
158
|
+
|
|
159
|
+
The generated FormData uses standard encoding:
|
|
160
|
+
|
|
161
|
+
**Object properties:**
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
user[name]=Alice
|
|
165
|
+
user[email]=alice@example.com
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Array elements:**
|
|
169
|
+
|
|
170
|
+
```text
|
|
171
|
+
tags[0]=tech
|
|
172
|
+
tags[1]=programming
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**File uploads:**
|
|
176
|
+
|
|
177
|
+
Handled as native `File`/`Blob` objects in FormData.
|
|
178
|
+
|
|
179
|
+
## Type Utilities
|
|
180
|
+
|
|
181
|
+
`DeepWritable<T>`
|
|
182
|
+
|
|
183
|
+
Utility type to make deeply nested readonly types writable:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import type { DeepWritable } from "pouch-js";
|
|
187
|
+
|
|
188
|
+
type User = DeepWritable<typeof userSchema>;
|
|
189
|
+
// Now all properties are mutable
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Error Handling
|
|
193
|
+
|
|
194
|
+
The library throws Zod validation errors when provided data doesn't match the schema:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
import { pack } from "pouch-js";
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
const manager = pack(
|
|
201
|
+
{ email: "invalid-email" },
|
|
202
|
+
z.object({ email: z.string().email() }),
|
|
203
|
+
);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.error("Validation failed:", error.errors);
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Common Use Cases
|
|
210
|
+
|
|
211
|
+
### 1. Form State Management
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
import { pack } from "pouch-js";
|
|
215
|
+
|
|
216
|
+
const { data, getFormData } = pack(initialFormState);
|
|
217
|
+
|
|
218
|
+
// Bind to input events
|
|
219
|
+
input.addEventListener("change", (e) => {
|
|
220
|
+
data.user.name = e.target.value;
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Submit handler
|
|
224
|
+
form.addEventListener("submit", async (e) => {
|
|
225
|
+
e.preventDefault();
|
|
226
|
+
await fetch("/submit", { body: getFormData() });
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### 2. File Upload Forms
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
import { pack } from "pouch-js";
|
|
234
|
+
|
|
235
|
+
const { data, getFormData } = pack({
|
|
236
|
+
title: "",
|
|
237
|
+
description: "",
|
|
238
|
+
attachments: [], // Will hold files
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Add files to array
|
|
242
|
+
fileInput.addEventListener("change", (e) => {
|
|
243
|
+
data.attachments.push(...e.target.files);
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### 3. Configuration Objects
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
import { pack } from "pouch-js";
|
|
251
|
+
|
|
252
|
+
const { data, getFormData } = pack({
|
|
253
|
+
settings: {
|
|
254
|
+
theme: "dark",
|
|
255
|
+
notifications: true,
|
|
256
|
+
layout: { grid: true, spacing: 2 },
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Limitations
|
|
262
|
+
|
|
263
|
+
- **Root Types**: Only objects and arrays are supported as root values
|
|
264
|
+
- **FormData Encoding**: Uses standard multipart/form-data encoding (not JSON)
|
|
265
|
+
- **Circular References**: Not supported in the data structure
|
|
266
|
+
- **Server Processing**: Requires server-side support for bracket notation (e.g., `user[name]`)
|
|
267
|
+
|
|
268
|
+
## Browser Support
|
|
269
|
+
|
|
270
|
+
Works in all modern browsers that support:
|
|
271
|
+
|
|
272
|
+
- Proxy API (ES2015)
|
|
273
|
+
- FormData API
|
|
274
|
+
- Blob/File API
|
|
275
|
+
|
|
276
|
+
## Contributing
|
|
277
|
+
|
|
278
|
+
Found an issue? Want to add a feature? Please open an issue or PR on our GitHub repository.
|
|
279
|
+
|
|
280
|
+
## License
|
|
281
|
+
|
|
282
|
+
MIT License - feel free to use in your projects!
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z, ZodTypeAny } from "zod";
|
|
2
|
+
type Proxiable = object | any[];
|
|
3
|
+
export type DeepWritable<T> = {
|
|
4
|
+
-readonly [K in keyof T]: T[K] extends object ? DeepWritable<T[K]> : T[K];
|
|
5
|
+
};
|
|
6
|
+
export declare function pack<T extends Proxiable>(initialData: T): {
|
|
7
|
+
data: T;
|
|
8
|
+
getFormData: () => FormData;
|
|
9
|
+
};
|
|
10
|
+
export declare function pack<T extends ZodTypeAny>(initialData: z.infer<T>, schema: T): {
|
|
11
|
+
data: z.infer<T>;
|
|
12
|
+
getFormData: () => FormData;
|
|
13
|
+
};
|
|
14
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pack = pack;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const isProxiable = (v) => typeof v === "object" && v !== null;
|
|
6
|
+
const isBlobLike = (v) => v instanceof Blob || v instanceof File;
|
|
7
|
+
const isProxiableNotBlob = (v) => isProxiable(v) && !isBlobLike(v);
|
|
8
|
+
function inferValueSchema(value) {
|
|
9
|
+
if (isProxiableNotBlob(value))
|
|
10
|
+
return inferSchema(value);
|
|
11
|
+
if (isBlobLike(value))
|
|
12
|
+
return zod_1.z.instanceof(Blob);
|
|
13
|
+
if (typeof value === "string")
|
|
14
|
+
return zod_1.z.string();
|
|
15
|
+
if (typeof value === "number")
|
|
16
|
+
return zod_1.z.number();
|
|
17
|
+
if (typeof value === "boolean")
|
|
18
|
+
return zod_1.z.boolean();
|
|
19
|
+
if (value === null)
|
|
20
|
+
return zod_1.z.null();
|
|
21
|
+
if (value === undefined)
|
|
22
|
+
return zod_1.z.undefined();
|
|
23
|
+
return zod_1.z.unknown();
|
|
24
|
+
}
|
|
25
|
+
function inferSchema(data) {
|
|
26
|
+
if (Array.isArray(data)) {
|
|
27
|
+
const types = data.map(inferValueSchema);
|
|
28
|
+
return zod_1.z.array(types.length > 0
|
|
29
|
+
? zod_1.z.union(types)
|
|
30
|
+
: zod_1.z.unknown());
|
|
31
|
+
}
|
|
32
|
+
if (isProxiableNotBlob(data)) {
|
|
33
|
+
const shape = Object.fromEntries(Object.keys(data).map((k) => [k, inferValueSchema(data[k])]));
|
|
34
|
+
return zod_1.z.object(shape);
|
|
35
|
+
}
|
|
36
|
+
return (isBlobLike(data)
|
|
37
|
+
? zod_1.z.instanceof(Blob)
|
|
38
|
+
: zod_1.z.unknown());
|
|
39
|
+
}
|
|
40
|
+
const handler = {
|
|
41
|
+
set(target, property, value) {
|
|
42
|
+
Reflect.set(target, property, isProxiableNotBlob(value) ? createDeepProxy(value, handler) : value);
|
|
43
|
+
return true;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
function createDeepProxy(target, handler) {
|
|
47
|
+
for (const key of Object.keys(target)) {
|
|
48
|
+
const value = target[key];
|
|
49
|
+
if (isProxiableNotBlob(value)) {
|
|
50
|
+
target[key] = createDeepProxy(value, handler);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return new Proxy(target, handler);
|
|
54
|
+
}
|
|
55
|
+
function appendToFormData(formData, data, key = "") {
|
|
56
|
+
if (Array.isArray(data)) {
|
|
57
|
+
data.forEach((value, i) => appendToFormData(formData, value, key ? `${key}[${i}]` : `${i}`));
|
|
58
|
+
}
|
|
59
|
+
else if (isProxiableNotBlob(data)) {
|
|
60
|
+
for (const [k, value] of Object.entries(data)) {
|
|
61
|
+
appendToFormData(formData, value, key ? `${key}[${k}]` : k);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else if (key) {
|
|
65
|
+
formData.append(key, isBlobLike(data) ? data : String(data));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function pack(initialData, schema) {
|
|
69
|
+
if (!isProxiable(initialData)) {
|
|
70
|
+
throw new Error("createFormDataManager only supports object and array root types");
|
|
71
|
+
}
|
|
72
|
+
const data = createDeepProxy(structuredClone((schema ?? inferSchema(initialData)).parse(initialData)), handler);
|
|
73
|
+
return {
|
|
74
|
+
data,
|
|
75
|
+
getFormData: () => {
|
|
76
|
+
const formData = new FormData();
|
|
77
|
+
appendToFormData(formData, data);
|
|
78
|
+
return formData;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pouch-js",
|
|
3
|
+
"version": "0.0.11",
|
|
4
|
+
"main": "./dist/index.js",
|
|
5
|
+
"types": "./dist/index.d.ts",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"package.json",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"dev": "npx tsc -w",
|
|
13
|
+
"build": "npx tsc",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"formdata",
|
|
18
|
+
"zod",
|
|
19
|
+
"validation",
|
|
20
|
+
"typescript",
|
|
21
|
+
"forms",
|
|
22
|
+
"file-upload",
|
|
23
|
+
"blob",
|
|
24
|
+
"proxy",
|
|
25
|
+
"reactive",
|
|
26
|
+
"schema",
|
|
27
|
+
"form-validation"
|
|
28
|
+
],
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"description": "A schema-aware reactive FormData builder with Zod validation and full Blob/File support",
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"zod": "^3.0.0"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"url": "https://github.com/techwithmanuel/frmz"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"typescript": "^5.8.3",
|
|
39
|
+
"zod": "^3.24.4"
|
|
40
|
+
}
|
|
41
|
+
}
|