duet-kit 0.1.0

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/dist/schema.js ADDED
@@ -0,0 +1,122 @@
1
+ /**
2
+ * duet-kit - Schema
3
+ *
4
+ * Thin wrapper around Zod that adds metadata for UI labels and LLM context.
5
+ */
6
+ import { z } from 'zod';
7
+ // Schema container with metadata
8
+ export class DuetSchema {
9
+ constructor(name, fields) {
10
+ Object.defineProperty(this, "name", {
11
+ enumerable: true,
12
+ configurable: true,
13
+ writable: true,
14
+ value: name
15
+ });
16
+ Object.defineProperty(this, "fields", {
17
+ enumerable: true,
18
+ configurable: true,
19
+ writable: true,
20
+ value: fields
21
+ });
22
+ }
23
+ // Get default values for all fields
24
+ getDefaults() {
25
+ const defaults = {};
26
+ for (const [key, field] of Object.entries(this.fields)) {
27
+ defaults[key] = field.default;
28
+ }
29
+ return defaults;
30
+ }
31
+ // Validate a single field
32
+ validate(field, value) {
33
+ return this.fields[field].schema.safeParse(value);
34
+ }
35
+ // Validate all fields
36
+ validateAll(data) {
37
+ const errors = {};
38
+ for (const [key, value] of Object.entries(data)) {
39
+ if (key in this.fields) {
40
+ const result = this.fields[key].schema.safeParse(value);
41
+ if (!result.success) {
42
+ errors[key] = result.error.errors[0]?.message || 'Invalid value';
43
+ }
44
+ }
45
+ }
46
+ return { success: Object.keys(errors).length === 0, errors };
47
+ }
48
+ // Get field metadata
49
+ getField(fieldId) {
50
+ return this.fields[fieldId];
51
+ }
52
+ // Get all field IDs
53
+ getFieldIds() {
54
+ return Object.keys(this.fields);
55
+ }
56
+ // Generate description for LLM context
57
+ getDescription() {
58
+ let desc = `Schema: ${this.name}\nFields:\n`;
59
+ for (const [id, field] of Object.entries(this.fields)) {
60
+ const zodType = this.getZodTypeDescription(field.schema);
61
+ desc += ` - ${id} (${zodType}): ${field.label}\n`;
62
+ }
63
+ return desc;
64
+ }
65
+ // Convert Zod schema to JSON Schema (for function calling)
66
+ toJSONSchema() {
67
+ const properties = {};
68
+ for (const [id, field] of Object.entries(this.fields)) {
69
+ properties[id] = this.zodToJSONSchema(field.schema, field.label);
70
+ }
71
+ return {
72
+ type: 'object',
73
+ properties,
74
+ required: Object.keys(this.fields),
75
+ };
76
+ }
77
+ getZodTypeDescription(schema) {
78
+ if (schema instanceof z.ZodString)
79
+ return 'string';
80
+ if (schema instanceof z.ZodNumber)
81
+ return 'number';
82
+ if (schema instanceof z.ZodBoolean)
83
+ return 'boolean';
84
+ if (schema instanceof z.ZodEnum)
85
+ return `enum(${schema.options.join('|')})`;
86
+ if (schema instanceof z.ZodOptional)
87
+ return `optional<${this.getZodTypeDescription(schema.unwrap())}>`;
88
+ return 'unknown';
89
+ }
90
+ zodToJSONSchema(schema, description) {
91
+ const base = { description };
92
+ if (schema instanceof z.ZodString) {
93
+ return { ...base, type: 'string' };
94
+ }
95
+ if (schema instanceof z.ZodNumber) {
96
+ const checks = schema._def.checks || [];
97
+ const result = { ...base, type: 'number' };
98
+ for (const check of checks) {
99
+ if (check.kind === 'min')
100
+ result.minimum = check.value;
101
+ if (check.kind === 'max')
102
+ result.maximum = check.value;
103
+ }
104
+ return result;
105
+ }
106
+ if (schema instanceof z.ZodBoolean) {
107
+ return { ...base, type: 'boolean' };
108
+ }
109
+ if (schema instanceof z.ZodEnum) {
110
+ return { ...base, type: 'string', enum: schema.options };
111
+ }
112
+ return { ...base, type: 'string' };
113
+ }
114
+ }
115
+ // Factory function
116
+ export function createSchema(name, fields) {
117
+ return new DuetSchema(name, fields);
118
+ }
119
+ // Helper to define a field (for cleaner syntax)
120
+ export function field(schema, label, defaultValue) {
121
+ return { schema, label, default: defaultValue };
122
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * duet-kit - Store
3
+ *
4
+ * Zustand store factory with validation and persistence.
5
+ */
6
+ import { type StoreApi, type UseBoundStore } from 'zustand';
7
+ import type { SchemaFields, DuetState } from './types';
8
+ import type { DuetSchema } from './schema';
9
+ export interface StoreOptions {
10
+ /** localStorage key for persistence (omit to disable) */
11
+ persist?: string;
12
+ }
13
+ export type DuetStore<T extends SchemaFields> = UseBoundStore<StoreApi<DuetState<T>>>;
14
+ /**
15
+ * Create a Zustand store bound to a Duet schema
16
+ */
17
+ export declare function createStore<T extends SchemaFields>(schema: DuetSchema<T>, options?: StoreOptions): DuetStore<T>;
18
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAU,KAAK,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAa,SAAS,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,MAAM,WAAW,YAAY;IAC3B,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,YAAY,IAAI,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAEtF;;GAEG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,YAAY,EAChD,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,EACrB,OAAO,GAAE,YAAiB,GACzB,SAAS,CAAC,CAAC,CAAC,CAyDd"}
package/dist/store.js ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * duet-kit - Store
3
+ *
4
+ * Zustand store factory with validation and persistence.
5
+ */
6
+ import { create } from 'zustand';
7
+ import { persist } from 'zustand/middleware';
8
+ /**
9
+ * Create a Zustand store bound to a Duet schema
10
+ */
11
+ export function createStore(schema, options = {}) {
12
+ const initialData = schema.getDefaults();
13
+ const storeCreator = (set) => ({
14
+ data: initialData,
15
+ // Set a single field with validation
16
+ set: (field, value) => {
17
+ const result = schema.validate(field, value);
18
+ if (!result.success) {
19
+ console.warn(`Validation failed for ${String(field)}:`, result.error.message);
20
+ return false;
21
+ }
22
+ set((state) => ({
23
+ data: { ...state.data, [field]: result.data },
24
+ }));
25
+ return true;
26
+ },
27
+ // Set multiple fields with validation
28
+ setMany: (updates) => {
29
+ const validated = {};
30
+ for (const [key, value] of Object.entries(updates)) {
31
+ if (key in schema.fields) {
32
+ const result = schema.validate(key, value);
33
+ if (!result.success) {
34
+ console.warn(`Validation failed for ${key}:`, result.error.message);
35
+ return false;
36
+ }
37
+ validated[key] = result.data;
38
+ }
39
+ }
40
+ set((state) => ({
41
+ data: { ...state.data, ...validated },
42
+ }));
43
+ return true;
44
+ },
45
+ // Reset to defaults
46
+ reset: () => {
47
+ set(() => ({ data: schema.getDefaults() }));
48
+ },
49
+ });
50
+ // Apply persistence middleware if requested
51
+ if (options.persist) {
52
+ return create()(persist(storeCreator, {
53
+ name: options.persist,
54
+ partialize: (state) => ({ data: state.data }),
55
+ }));
56
+ }
57
+ return create()(storeCreator);
58
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * duet-kit - Core Types
3
+ */
4
+ import { z } from 'zod';
5
+ export interface FieldDef<T = unknown> {
6
+ schema: z.ZodType<T>;
7
+ label: string;
8
+ default: T;
9
+ }
10
+ export type SchemaFields = Record<string, FieldDef>;
11
+ export type InferData<T extends SchemaFields> = {
12
+ [K in keyof T]: z.infer<T[K]['schema']>;
13
+ };
14
+ export interface JsonPatchOp {
15
+ op: 'replace' | 'add' | 'remove';
16
+ path: string;
17
+ value?: unknown;
18
+ }
19
+ export type EditResult = {
20
+ success: true;
21
+ applied: number;
22
+ } | {
23
+ success: false;
24
+ error: string;
25
+ };
26
+ export interface HistoryEntry {
27
+ id: string;
28
+ timestamp: number;
29
+ patch: JsonPatchOp[];
30
+ source: 'user' | 'llm' | 'system';
31
+ result: EditResult;
32
+ }
33
+ export interface DuetState<T extends SchemaFields> {
34
+ data: InferData<T>;
35
+ set: <K extends keyof T>(field: K, value: z.infer<T[K]['schema']>) => boolean;
36
+ setMany: (updates: Partial<InferData<T>>) => boolean;
37
+ reset: () => void;
38
+ }
39
+ export interface LLMBridge<T extends SchemaFields> {
40
+ applyPatch: (patch: JsonPatchOp[], source?: 'user' | 'llm' | 'system') => EditResult;
41
+ applyJSON: (json: string, source?: 'user' | 'llm' | 'system') => EditResult;
42
+ getContext: () => string;
43
+ getCompactContext: () => string;
44
+ getFunctionSchema: () => object;
45
+ getCurrentValues: () => InferData<T>;
46
+ history: () => HistoryEntry[];
47
+ clearHistory: () => void;
48
+ }
49
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,WAAW,QAAQ,CAAC,CAAC,GAAG,OAAO;IACnC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,CAAC,CAAC;CACZ;AAGD,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AAGpD,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,YAAY,IAAI;KAC7C,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;CACxC,CAAC;AAGF,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,SAAS,GAAG,KAAK,GAAG,QAAQ,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAGD,MAAM,MAAM,UAAU,GAClB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAGtC,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAClC,MAAM,EAAE,UAAU,CAAC;CACpB;AAGD,MAAM,WAAW,SAAS,CAAC,CAAC,SAAS,YAAY;IAC/C,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;IACnB,GAAG,EAAE,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,KAAK,OAAO,CAAC;IAC9E,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC;IACrD,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAGD,MAAM,WAAW,SAAS,CAAC,CAAC,SAAS,YAAY;IAC/C,UAAU,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,QAAQ,KAAK,UAAU,CAAC;IACrF,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,QAAQ,KAAK,UAAU,CAAC;IAC5E,UAAU,EAAE,MAAM,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,MAAM,CAAC;IAChC,iBAAiB,EAAE,MAAM,MAAM,CAAC;IAChC,gBAAgB,EAAE,MAAM,SAAS,CAAC,CAAC,CAAC,CAAC;IACrC,OAAO,EAAE,MAAM,YAAY,EAAE,CAAC;IAC9B,YAAY,EAAE,MAAM,IAAI,CAAC;CAC1B"}
package/dist/types.js ADDED
@@ -0,0 +1,4 @@
1
+ /**
2
+ * duet-kit - Core Types
3
+ */
4
+ export {};
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "duet-kit",
3
+ "version": "0.1.0",
4
+ "description": "LLM-native UI toolkit - Build UIs where humans and AI edit the same validated state. Built on Zustand + Zod.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "dev": "tsc --watch",
21
+ "test": "vitest",
22
+ "test:run": "vitest run",
23
+ "prepublishOnly": "npm run build",
24
+ "clean": "rm -rf dist"
25
+ },
26
+ "dependencies": {
27
+ "zod": "^3.22.4",
28
+ "zustand": "^4.4.7"
29
+ },
30
+ "peerDependencies": {
31
+ "react": ">=17.0.0"
32
+ },
33
+ "peerDependenciesMeta": {
34
+ "react": {
35
+ "optional": true
36
+ }
37
+ },
38
+ "devDependencies": {
39
+ "@types/react": "^18.2.43",
40
+ "typescript": "^5.2.2",
41
+ "vitest": "^2.1.9"
42
+ },
43
+ "keywords": [
44
+ "llm",
45
+ "ai",
46
+ "react",
47
+ "zustand",
48
+ "zod",
49
+ "state-management",
50
+ "schema",
51
+ "validation",
52
+ "openai",
53
+ "anthropic",
54
+ "function-calling"
55
+ ],
56
+ "author": "",
57
+ "license": "MIT",
58
+ "repository": {
59
+ "type": "git",
60
+ "url": ""
61
+ },
62
+ "bugs": {
63
+ "url": ""
64
+ },
65
+ "homepage": ""
66
+ }