@pyreon/mcp 0.7.14 → 0.11.1
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 +21 -0
- package/lib/analysis/index.js.html +1 -1
- package/lib/index.js +455 -77
- package/lib/index.js.map +1 -1
- package/package.json +3 -3
- package/src/api-reference.ts +541 -0
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "MCP server for Pyreon — AI-powered framework assistance",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/pyreon/pyreon.git",
|
|
9
|
-
"directory": "packages/mcp"
|
|
9
|
+
"directory": "packages/tools/mcp"
|
|
10
10
|
},
|
|
11
11
|
"homepage": "https://github.com/pyreon/pyreon/tree/main/packages/mcp#readme",
|
|
12
12
|
"bugs": {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"prepublishOnly": "bun run build"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@pyreon/compiler": "^0.
|
|
45
|
+
"@pyreon/compiler": "^0.11.1",
|
|
46
46
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
47
47
|
"zod": "^3.25.76"
|
|
48
48
|
},
|
package/src/api-reference.ts
CHANGED
|
@@ -570,4 +570,545 @@ hydrateRoot(<App />, document.getElementById("app")!)`,
|
|
|
570
570
|
.fade-enter-from, .fade-leave-to { opacity: 0 }
|
|
571
571
|
*/`,
|
|
572
572
|
},
|
|
573
|
+
|
|
574
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
575
|
+
// @pyreon/store
|
|
576
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
577
|
+
|
|
578
|
+
"store/defineStore": {
|
|
579
|
+
signature: "defineStore<T>(id: string, setup: () => T): () => StoreApi<T>",
|
|
580
|
+
example: `const useCounter = defineStore('counter', () => {
|
|
581
|
+
const count = signal(0)
|
|
582
|
+
const increment = () => count.update(n => n + 1)
|
|
583
|
+
return { count, increment }
|
|
584
|
+
})
|
|
585
|
+
|
|
586
|
+
const { store } = useCounter()
|
|
587
|
+
store.count() // 0
|
|
588
|
+
store.increment() // reactive update`,
|
|
589
|
+
notes:
|
|
590
|
+
"Composition-style stores. Singleton by ID. Returns StoreApi with .store, .patch(), .subscribe(), .onAction(), .reset(), .dispose().",
|
|
591
|
+
},
|
|
592
|
+
|
|
593
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
594
|
+
// @pyreon/form
|
|
595
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
596
|
+
|
|
597
|
+
"form/useForm": {
|
|
598
|
+
signature:
|
|
599
|
+
"useForm<T>(options: { initialValues: T, onSubmit: (values: T) => void | Promise<void>, schema?, validateOn?, debounceMs? }): FormInstance<T>",
|
|
600
|
+
example: `const form = useForm({
|
|
601
|
+
initialValues: { name: '', email: '' },
|
|
602
|
+
onSubmit: async (values) => await api.save(values),
|
|
603
|
+
validateOn: 'blur',
|
|
604
|
+
})
|
|
605
|
+
|
|
606
|
+
form.handleSubmit() // triggers validation + onSubmit
|
|
607
|
+
form.reset() // reset to initial values`,
|
|
608
|
+
notes:
|
|
609
|
+
"Signal-based form state. Use useField() for individual field binding, useFieldArray() for dynamic arrays.",
|
|
610
|
+
},
|
|
611
|
+
|
|
612
|
+
"form/useField": {
|
|
613
|
+
signature: "useField<T>(form: FormInstance<T>, name: keyof T): FieldInstance",
|
|
614
|
+
example: `const name = useField(form, 'name')
|
|
615
|
+
|
|
616
|
+
<input {...name.register()} />
|
|
617
|
+
// name.value(), name.error(), name.hasError(), name.showError()`,
|
|
618
|
+
},
|
|
619
|
+
|
|
620
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
621
|
+
// @pyreon/query
|
|
622
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
623
|
+
|
|
624
|
+
"query/useQuery": {
|
|
625
|
+
signature:
|
|
626
|
+
"useQuery<T>(options: { queryKey: unknown[], queryFn: () => Promise<T>, ... }): { data: Signal<T>, error: Signal<Error>, isFetching: Signal<boolean>, ... }",
|
|
627
|
+
example: `const { data, error, isFetching } = useQuery({
|
|
628
|
+
queryKey: ['users'],
|
|
629
|
+
queryFn: () => fetch('/api/users').then(r => r.json()),
|
|
630
|
+
})`,
|
|
631
|
+
notes:
|
|
632
|
+
"TanStack Query adapter. Fine-grained signals per field. Reactive options via function getter. Also: useMutation, useInfiniteQuery, useSuspenseQuery, useSubscription (WebSocket).",
|
|
633
|
+
},
|
|
634
|
+
|
|
635
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
636
|
+
// @pyreon/permissions
|
|
637
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
638
|
+
|
|
639
|
+
"permissions/createPermissions": {
|
|
640
|
+
signature: "createPermissions<T extends PermissionMap>(initial?: T): PermissionsInstance",
|
|
641
|
+
example: `const can = createPermissions({
|
|
642
|
+
'posts.read': true,
|
|
643
|
+
'posts.delete': (post) => post.authorId === userId,
|
|
644
|
+
'admin.*': false,
|
|
645
|
+
})
|
|
646
|
+
|
|
647
|
+
can('posts.read') // true (reactive)
|
|
648
|
+
can('posts.delete', post) // evaluates predicate
|
|
649
|
+
can.not('admin.dashboard')
|
|
650
|
+
can.all('posts.read', 'posts.create')
|
|
651
|
+
can.any('admin.users', 'posts.read')`,
|
|
652
|
+
notes:
|
|
653
|
+
"Reactive permissions. Supports RBAC, ABAC, feature flags, subscription tiers. Wildcard matching with '*'. PermissionsProvider/usePermissions for context.",
|
|
654
|
+
},
|
|
655
|
+
|
|
656
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
657
|
+
// @pyreon/machine
|
|
658
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
659
|
+
|
|
660
|
+
"machine/createMachine": {
|
|
661
|
+
signature: "createMachine<S, E>(config: MachineConfig<S, E>): Machine<S, E>",
|
|
662
|
+
example: `const traffic = createMachine({
|
|
663
|
+
initial: 'red',
|
|
664
|
+
states: {
|
|
665
|
+
red: { on: { NEXT: 'green' } },
|
|
666
|
+
green: { on: { NEXT: 'yellow' } },
|
|
667
|
+
yellow: { on: { NEXT: 'red' } },
|
|
668
|
+
},
|
|
669
|
+
})
|
|
670
|
+
|
|
671
|
+
traffic() // 'red' (reactive)
|
|
672
|
+
traffic.send('NEXT') // 'green'
|
|
673
|
+
traffic.matches('green') // true
|
|
674
|
+
traffic.can('NEXT') // true`,
|
|
675
|
+
notes:
|
|
676
|
+
"Constrained signal with type-safe transitions. Guards: { target, guard: (payload?) => boolean }. No context — use signals alongside.",
|
|
677
|
+
},
|
|
678
|
+
|
|
679
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
680
|
+
// @pyreon/storage
|
|
681
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
682
|
+
|
|
683
|
+
"storage/useStorage": {
|
|
684
|
+
signature:
|
|
685
|
+
"useStorage<T>(key: string, defaultValue: T, options?: StorageOptions<T>): StorageSignal<T>",
|
|
686
|
+
example: `const theme = useStorage('theme', 'light')
|
|
687
|
+
theme() // 'light'
|
|
688
|
+
theme.set('dark') // persists + cross-tab sync
|
|
689
|
+
theme.remove() // delete from storage`,
|
|
690
|
+
notes:
|
|
691
|
+
"localStorage by default. Also: useSessionStorage, useCookie, useIndexedDB, useMemoryStorage, createStorage(backend). All return StorageSignal<T> extending Signal<T> with .remove().",
|
|
692
|
+
},
|
|
693
|
+
|
|
694
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
695
|
+
// @pyreon/i18n
|
|
696
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
697
|
+
|
|
698
|
+
"i18n/createI18n": {
|
|
699
|
+
signature:
|
|
700
|
+
"createI18n(options: { locale: string, messages: Record<string, Record<string, string>>, loader?, fallbackLocale?, pluralRules? }): I18nInstance",
|
|
701
|
+
example: `const i18n = createI18n({
|
|
702
|
+
locale: 'en',
|
|
703
|
+
messages: { en: { greeting: 'Hello, {{name}}!' } },
|
|
704
|
+
loader: (locale, ns) => import(\`./locales/\${locale}/\${ns}.json\`),
|
|
705
|
+
})
|
|
706
|
+
|
|
707
|
+
const { t, locale } = useI18n()
|
|
708
|
+
t('greeting', { name: 'World' }) // "Hello, World!"
|
|
709
|
+
locale.set('fr') // switch reactively`,
|
|
710
|
+
notes:
|
|
711
|
+
"Interpolation with {{name}}, pluralization with _one/_other suffixes. Namespace lazy loading. <Trans> component for rich JSX interpolation.",
|
|
712
|
+
},
|
|
713
|
+
|
|
714
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
715
|
+
// @pyreon/document
|
|
716
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
717
|
+
|
|
718
|
+
"document/createDocument": {
|
|
719
|
+
signature: "createDocument(props?: DocumentProps): DocumentBuilder",
|
|
720
|
+
example: `const doc = createDocument({ title: 'Report' })
|
|
721
|
+
.heading('Sales Report')
|
|
722
|
+
.table({ columns: ['Region', 'Revenue'], rows: [['US', '$1M']] })
|
|
723
|
+
|
|
724
|
+
await doc.toPdf() // PDF
|
|
725
|
+
await doc.toEmail() // Outlook-safe HTML
|
|
726
|
+
await doc.toDocx() // Word document
|
|
727
|
+
await doc.toSlack() // Slack Block Kit JSON
|
|
728
|
+
await doc.toNotion() // Notion blocks`,
|
|
729
|
+
notes:
|
|
730
|
+
"14+ output formats. JSX primitives: Document, Page, Heading, Text, Table, Image, List, Code, etc. Heavy renderers lazy-loaded.",
|
|
731
|
+
},
|
|
732
|
+
|
|
733
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
734
|
+
// @pyreon/flow
|
|
735
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
736
|
+
|
|
737
|
+
"flow/createFlow": {
|
|
738
|
+
signature: "createFlow(config: { nodes: FlowNode[], edges: FlowEdge[], ... }): FlowInstance",
|
|
739
|
+
example: `const flow = createFlow({
|
|
740
|
+
nodes: [
|
|
741
|
+
{ id: '1', position: { x: 0, y: 0 }, data: { label: 'Start' } },
|
|
742
|
+
{ id: '2', position: { x: 200, y: 100 }, data: { label: 'End' } },
|
|
743
|
+
],
|
|
744
|
+
edges: [{ id: 'e1', source: '1', target: '2' }],
|
|
745
|
+
})
|
|
746
|
+
|
|
747
|
+
flow.addNode({ id: '3', position: { x: 100, y: 200 }, data: { label: 'New' } })
|
|
748
|
+
await flow.layout('layered') // auto-layout via elkjs
|
|
749
|
+
|
|
750
|
+
<Flow instance={flow}><Background /><Controls /><MiniMap /></Flow>`,
|
|
751
|
+
notes:
|
|
752
|
+
"Signal-native nodes/edges. Auto-layout via elkjs (lazy-loaded). Pan/zoom via pointer events + CSS transforms. No D3.",
|
|
753
|
+
},
|
|
754
|
+
|
|
755
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
756
|
+
// @pyreon/code
|
|
757
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
758
|
+
|
|
759
|
+
"code/createEditor": {
|
|
760
|
+
signature:
|
|
761
|
+
"createEditor(config: { value?: string, language?: string, theme?: string, minimap?: boolean, ... }): EditorInstance",
|
|
762
|
+
example: `const editor = createEditor({
|
|
763
|
+
value: '// hello',
|
|
764
|
+
language: 'typescript',
|
|
765
|
+
theme: 'dark',
|
|
766
|
+
minimap: true,
|
|
767
|
+
})
|
|
768
|
+
|
|
769
|
+
editor.value() // reactive Signal<string>
|
|
770
|
+
editor.goToLine(42)
|
|
771
|
+
editor.insert('new code')
|
|
772
|
+
|
|
773
|
+
<CodeEditor instance={editor} />
|
|
774
|
+
<DiffEditor original="old" modified="new" />`,
|
|
775
|
+
notes:
|
|
776
|
+
"Built on CodeMirror 6 (~250KB vs Monaco's ~2.5MB). loadLanguage() for lazy grammars. TabbedEditor for multi-file.",
|
|
777
|
+
},
|
|
778
|
+
|
|
779
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
780
|
+
// @pyreon/hotkeys
|
|
781
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
782
|
+
|
|
783
|
+
"hotkeys/useHotkey": {
|
|
784
|
+
signature:
|
|
785
|
+
"useHotkey(shortcut: string, handler: (e: KeyboardEvent) => void, options?: HotkeyOptions): void",
|
|
786
|
+
example: `useHotkey('mod+s', (e) => {
|
|
787
|
+
e.preventDefault()
|
|
788
|
+
save()
|
|
789
|
+
})
|
|
790
|
+
|
|
791
|
+
useHotkey('mod+k', () => openSearch(), { scope: 'global' })
|
|
792
|
+
useHotkeyScope('editor') // activate scope for component lifetime`,
|
|
793
|
+
notes:
|
|
794
|
+
"Component-scoped, auto-unregisters on unmount. 'mod' = ⌘ on Mac, Ctrl elsewhere. Scope-based activation for context-aware shortcuts.",
|
|
795
|
+
},
|
|
796
|
+
|
|
797
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
798
|
+
// @pyreon/table
|
|
799
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
800
|
+
|
|
801
|
+
"table/useTable": {
|
|
802
|
+
signature: "useTable<T>(options: TableOptions<T>): Table<T>",
|
|
803
|
+
example: `const table = useTable({
|
|
804
|
+
data: () => users(),
|
|
805
|
+
columns: [
|
|
806
|
+
{ accessorKey: 'name', header: 'Name' },
|
|
807
|
+
{ accessorKey: 'email', header: 'Email' },
|
|
808
|
+
],
|
|
809
|
+
})
|
|
810
|
+
|
|
811
|
+
// flexRender for column templates:
|
|
812
|
+
flexRender(cell.column.columnDef.cell, cell.getContext())`,
|
|
813
|
+
notes: "TanStack Table adapter with reactive options and auto state sync.",
|
|
814
|
+
},
|
|
815
|
+
|
|
816
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
817
|
+
// @pyreon/virtual
|
|
818
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
819
|
+
|
|
820
|
+
"virtual/useVirtualizer": {
|
|
821
|
+
signature:
|
|
822
|
+
"useVirtualizer(options: VirtualizerOptions): { virtualItems: Signal, totalSize: Signal, scrollToIndex: (i) => void, ... }",
|
|
823
|
+
example: `const { virtualItems, totalSize } = useVirtualizer({
|
|
824
|
+
count: 10000,
|
|
825
|
+
getScrollElement: () => scrollRef.current,
|
|
826
|
+
estimateSize: () => 35,
|
|
827
|
+
})`,
|
|
828
|
+
notes: "TanStack Virtual adapter. Also: useWindowVirtualizer for window-scoped virtualization.",
|
|
829
|
+
},
|
|
830
|
+
|
|
831
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
832
|
+
// @pyreon/feature
|
|
833
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
834
|
+
|
|
835
|
+
"feature/defineFeature": {
|
|
836
|
+
signature:
|
|
837
|
+
"defineFeature<T>(config: { name: string, schema: FeatureSchema<T>, api: FeatureApi<T> }): Feature<T>",
|
|
838
|
+
example: `const Posts = defineFeature({
|
|
839
|
+
name: 'posts',
|
|
840
|
+
schema: { title: 'string', body: 'string', author: reference('users') },
|
|
841
|
+
api: { baseUrl: '/api/posts' },
|
|
842
|
+
})
|
|
843
|
+
|
|
844
|
+
// Auto-generated hooks:
|
|
845
|
+
Posts.useList() // paginated query
|
|
846
|
+
Posts.useById(id) // single item query
|
|
847
|
+
Posts.useCreate() // mutation
|
|
848
|
+
Posts.useForm(id) // edit form with validation
|
|
849
|
+
Posts.useTable() // TanStack Table config`,
|
|
850
|
+
notes:
|
|
851
|
+
"Schema-driven CRUD. Composes @pyreon/query, @pyreon/form, @pyreon/validation, @pyreon/store, @pyreon/table.",
|
|
852
|
+
},
|
|
853
|
+
|
|
854
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
855
|
+
// @pyreon/storybook
|
|
856
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
857
|
+
|
|
858
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
859
|
+
// @pyreon/lint
|
|
860
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
861
|
+
|
|
862
|
+
"lint/lint": {
|
|
863
|
+
signature: "lint(options?: LintOptions): LintResult",
|
|
864
|
+
example: `import { lint } from "@pyreon/lint"
|
|
865
|
+
|
|
866
|
+
const result = lint({ paths: ["src/"], preset: "recommended" })
|
|
867
|
+
console.log(result.totalErrors, result.totalWarnings)
|
|
868
|
+
|
|
869
|
+
// With config file auto-loading + rule overrides
|
|
870
|
+
lint({ paths: ["."], ruleOverrides: { "pyreon/no-classname": "off" } })`,
|
|
871
|
+
notes:
|
|
872
|
+
"Programmatic API. 55 rules across 12 categories. Auto-loads .pyreonlintrc.json. Presets: recommended, strict, app, lib. Uses oxc-parser with AST caching.",
|
|
873
|
+
},
|
|
874
|
+
|
|
875
|
+
"lint/lintFile": {
|
|
876
|
+
signature:
|
|
877
|
+
"lintFile(filePath: string, sourceText: string, rules: Rule[], config: LintConfig, cache?: AstCache): LintFileResult",
|
|
878
|
+
example: `import { lintFile, allRules, getPreset, AstCache } from "@pyreon/lint"
|
|
879
|
+
|
|
880
|
+
const cache = new AstCache()
|
|
881
|
+
const config = getPreset("recommended")
|
|
882
|
+
const result = lintFile("app.tsx", source, allRules, config, cache)`,
|
|
883
|
+
notes: "Low-level single-file API. Optional AstCache for repeat runs (FNV-1a hash keyed).",
|
|
884
|
+
},
|
|
885
|
+
|
|
886
|
+
"lint/cli": {
|
|
887
|
+
signature:
|
|
888
|
+
"pyreon-lint [--preset name] [--fix] [--format text|json|compact] [--quiet] [--watch] [--list] [--config path] [--ignore path] [--rule id=severity] [path...]",
|
|
889
|
+
example: `pyreon-lint --preset strict --quiet # CI mode
|
|
890
|
+
pyreon-lint --fix # auto-fix
|
|
891
|
+
pyreon-lint --watch src/ # watch mode
|
|
892
|
+
pyreon-lint --list # list all 55 rules
|
|
893
|
+
pyreon-lint --format json # machine-readable`,
|
|
894
|
+
notes:
|
|
895
|
+
"CLI entry. Config: .pyreonlintrc.json, package.json 'pyreonlint' field. Ignore: .pyreonlintignore + .gitignore. Watch: fs.watch recursive with 100ms debounce.",
|
|
896
|
+
},
|
|
897
|
+
|
|
898
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
899
|
+
// @pyreon/ui-core
|
|
900
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
901
|
+
|
|
902
|
+
"ui-core/PyreonUI": {
|
|
903
|
+
signature:
|
|
904
|
+
"PyreonUI(props: { theme?: Theme; mode?: 'light' | 'dark' | 'system'; inversed?: boolean; children: VNodeChild }): VNodeChild",
|
|
905
|
+
example: `import { PyreonUI } from "@pyreon/ui-core"
|
|
906
|
+
import { enrichTheme } from "@pyreon/unistyle"
|
|
907
|
+
|
|
908
|
+
const theme = enrichTheme({ colors: { primary: "#3b82f6" } })
|
|
909
|
+
|
|
910
|
+
<PyreonUI theme={theme} mode="system">
|
|
911
|
+
<App />
|
|
912
|
+
</PyreonUI>
|
|
913
|
+
|
|
914
|
+
// mode="system" auto-detects OS dark mode via prefers-color-scheme
|
|
915
|
+
// inversed flips the resolved mode (light↔dark)`,
|
|
916
|
+
notes:
|
|
917
|
+
"Unified provider replacing 3 separate providers (theme, mode, config). Calls init() internally. mode='system' uses matchMedia('(prefers-color-scheme: dark)') and reactively updates.",
|
|
918
|
+
mistakes: `- Using ThemeProvider + ModeProvider + ConfigProvider separately → Use PyreonUI instead
|
|
919
|
+
- Forgetting enrichTheme() → raw theme objects miss default breakpoints/spacing`,
|
|
920
|
+
},
|
|
921
|
+
|
|
922
|
+
"ui-core/useMode": {
|
|
923
|
+
signature: "useMode(): Signal<'light' | 'dark'>",
|
|
924
|
+
example: `import { useMode } from "@pyreon/ui-core"
|
|
925
|
+
|
|
926
|
+
const mode = useMode()
|
|
927
|
+
// mode() returns "light" or "dark" (resolved, reactive)
|
|
928
|
+
// Reflects OS preference when PyreonUI mode="system"`,
|
|
929
|
+
notes:
|
|
930
|
+
"Returns the resolved mode as a reactive signal. When mode='system', reflects the OS preference. When inversed is true, the mode is flipped.",
|
|
931
|
+
},
|
|
932
|
+
|
|
933
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
934
|
+
// @pyreon/unistyle
|
|
935
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
936
|
+
|
|
937
|
+
"unistyle/enrichTheme": {
|
|
938
|
+
signature: "enrichTheme(theme: PartialTheme): Theme",
|
|
939
|
+
example: `import { enrichTheme } from "@pyreon/unistyle"
|
|
940
|
+
|
|
941
|
+
const theme = enrichTheme({
|
|
942
|
+
colors: { primary: "#3b82f6", secondary: "#6366f1" },
|
|
943
|
+
fonts: { body: "Inter, sans-serif" },
|
|
944
|
+
})
|
|
945
|
+
|
|
946
|
+
// Merges user overrides with default breakpoints, spacing, and units`,
|
|
947
|
+
notes:
|
|
948
|
+
"Merges a partial theme with the full default theme (breakpoints, spacing, unit utilities). Always use when passing a theme to PyreonUI.",
|
|
949
|
+
},
|
|
950
|
+
|
|
951
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
952
|
+
// @pyreon/storybook
|
|
953
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
954
|
+
|
|
955
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
956
|
+
// @pyreon/rx
|
|
957
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
958
|
+
|
|
959
|
+
"rx/filter": {
|
|
960
|
+
signature:
|
|
961
|
+
"filter<T>(source: Signal<T[]> | T[], predicate: (item: T) => boolean): Computed<T[]> | T[]",
|
|
962
|
+
example: `import { filter } from '@pyreon/rx'
|
|
963
|
+
|
|
964
|
+
// Signal input → Computed output (auto-tracks):
|
|
965
|
+
const items = signal([1, 2, 3, 4, 5])
|
|
966
|
+
const evens = filter(items, n => n % 2 === 0) // Computed<number[]>
|
|
967
|
+
evens() // [2, 4]
|
|
968
|
+
|
|
969
|
+
// Plain input → plain output:
|
|
970
|
+
const result = filter([1, 2, 3, 4, 5], n => n > 3) // [4, 5]`,
|
|
971
|
+
notes:
|
|
972
|
+
"Every @pyreon/rx function is overloaded: Signal<T[]> input produces Computed<T[]>, plain T[] input produces plain T[]. 24 functions total: filter, map, sortBy, groupBy, keyBy, uniqBy, take, skip, last, chunk, flatten, find, mapValues, count, sum, min, max, average, distinct, scan, combine, debounce, throttle, search.",
|
|
973
|
+
},
|
|
974
|
+
|
|
975
|
+
"rx/pipe": {
|
|
976
|
+
signature: "pipe<T>(source: Signal<T[]> | T[], ...operators: Operator[]): Computed<T[]> | T[]",
|
|
977
|
+
example: `import { pipe, filter, sortBy, map } from '@pyreon/rx'
|
|
978
|
+
|
|
979
|
+
const users = signal([
|
|
980
|
+
{ name: 'Charlie', age: 35 },
|
|
981
|
+
{ name: 'Alice', age: 25 },
|
|
982
|
+
{ name: 'Bob', age: 30 },
|
|
983
|
+
])
|
|
984
|
+
|
|
985
|
+
// Compose transforms left-to-right:
|
|
986
|
+
const result = pipe(
|
|
987
|
+
users,
|
|
988
|
+
filter(u => u.age >= 30),
|
|
989
|
+
sortBy('name'),
|
|
990
|
+
map(u => u.name),
|
|
991
|
+
)
|
|
992
|
+
// Computed<string[]> → ["Bob", "Charlie"]`,
|
|
993
|
+
notes:
|
|
994
|
+
"Pipe composes operators left-to-right. Signal source produces reactive Computed that re-derives when source changes.",
|
|
995
|
+
},
|
|
996
|
+
|
|
997
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
998
|
+
// @pyreon/toast
|
|
999
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1000
|
+
|
|
1001
|
+
"toast/toast": {
|
|
1002
|
+
signature:
|
|
1003
|
+
"toast(message: string, options?: ToastOptions): string\ntoast.success/error/warning/info/loading(message): string\ntoast.update(id, options): void\ntoast.dismiss(id?): void\ntoast.promise(promise, { loading, success, error }): string",
|
|
1004
|
+
example: `import { toast, Toaster } from '@pyreon/toast'
|
|
1005
|
+
|
|
1006
|
+
// Basic:
|
|
1007
|
+
toast('Hello!')
|
|
1008
|
+
toast.success('Saved!')
|
|
1009
|
+
toast.error('Failed!')
|
|
1010
|
+
|
|
1011
|
+
// Loading → success pattern:
|
|
1012
|
+
const id = toast.loading('Saving...')
|
|
1013
|
+
await save()
|
|
1014
|
+
toast.update(id, { type: 'success', message: 'Done!' })
|
|
1015
|
+
|
|
1016
|
+
// Promise helper:
|
|
1017
|
+
toast.promise(fetchData(), {
|
|
1018
|
+
loading: 'Loading...',
|
|
1019
|
+
success: 'Loaded!',
|
|
1020
|
+
error: 'Failed to load',
|
|
1021
|
+
})
|
|
1022
|
+
|
|
1023
|
+
// Dismiss:
|
|
1024
|
+
toast.dismiss(id) // one
|
|
1025
|
+
toast.dismiss() // all
|
|
1026
|
+
|
|
1027
|
+
// Mount Toaster once in your app:
|
|
1028
|
+
<Toaster />`,
|
|
1029
|
+
notes:
|
|
1030
|
+
"Imperative API — call from anywhere, no context needed. <Toaster /> renders via Portal with CSS transitions, auto-dismiss, pause on hover. Accessible: role='alert', aria-live='polite'.",
|
|
1031
|
+
},
|
|
1032
|
+
|
|
1033
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1034
|
+
// @pyreon/url-state
|
|
1035
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1036
|
+
|
|
1037
|
+
"url-state/useUrlState": {
|
|
1038
|
+
signature:
|
|
1039
|
+
"useUrlState<T>(key: string, defaultValue: T): UrlStateSignal<T>\nuseUrlState<T extends Record<string, unknown>>(schema: T): UrlStateSchema<T>",
|
|
1040
|
+
example: `import { useUrlState } from '@pyreon/url-state'
|
|
1041
|
+
|
|
1042
|
+
// Single param — synced to ?page=:
|
|
1043
|
+
const page = useUrlState('page', 1)
|
|
1044
|
+
page() // 1 (auto-coerced number)
|
|
1045
|
+
page.set(2) // URL → ?page=2
|
|
1046
|
+
|
|
1047
|
+
// Schema mode — multiple params:
|
|
1048
|
+
const filters = useUrlState({ page: 1, sort: 'name', desc: false })
|
|
1049
|
+
filters.page() // 1
|
|
1050
|
+
filters.sort() // "name"
|
|
1051
|
+
filters.set({ page: 2, sort: 'date' })`,
|
|
1052
|
+
notes:
|
|
1053
|
+
"Auto type coercion (numbers, booleans, arrays). Uses replaceState (no history spam). Configurable debounce. SSR-safe — reads request URL on server.",
|
|
1054
|
+
},
|
|
1055
|
+
|
|
1056
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1057
|
+
// @pyreon/query — useSSE
|
|
1058
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1059
|
+
|
|
1060
|
+
"query/useSSE": {
|
|
1061
|
+
signature:
|
|
1062
|
+
"useSSE<T>(options: { queryKey: unknown[], url: string, transform?: (event: MessageEvent) => T, ... }): { data: Signal<T>, error: Signal<Error>, status: Signal<string> }",
|
|
1063
|
+
example: `import { useSSE } from '@pyreon/query'
|
|
1064
|
+
|
|
1065
|
+
const { data, error, status } = useSSE({
|
|
1066
|
+
queryKey: ['events'],
|
|
1067
|
+
url: '/api/events',
|
|
1068
|
+
transform: (event) => JSON.parse(event.data),
|
|
1069
|
+
})
|
|
1070
|
+
|
|
1071
|
+
// data() reactively updates on each SSE message
|
|
1072
|
+
// Auto-reconnects on disconnect
|
|
1073
|
+
// Integrates with QueryClient for cache invalidation`,
|
|
1074
|
+
notes:
|
|
1075
|
+
"Server-Sent Events hook. Same pattern as useSubscription but read-only (no send). Integrates with QueryClient cache.",
|
|
1076
|
+
},
|
|
1077
|
+
|
|
1078
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1079
|
+
// @pyreon/router — useIsActive
|
|
1080
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1081
|
+
|
|
1082
|
+
"router/useIsActive": {
|
|
1083
|
+
signature: "useIsActive(path: string, exact?: boolean): () => boolean",
|
|
1084
|
+
example: `import { useIsActive } from '@pyreon/router'
|
|
1085
|
+
|
|
1086
|
+
const isHome = useIsActive('/')
|
|
1087
|
+
const isAdmin = useIsActive('/admin') // prefix match
|
|
1088
|
+
const isExactAdmin = useIsActive('/admin', true) // exact only
|
|
1089
|
+
|
|
1090
|
+
// Reactive — updates when route changes:
|
|
1091
|
+
<a class={{ active: isAdmin() }} href="/admin">Admin</a>`,
|
|
1092
|
+
notes:
|
|
1093
|
+
"Returns a reactive boolean. Segment-aware prefix matching: /admin matches /admin/users but not /admin-panel. Pass exact=true for exact-only matching.",
|
|
1094
|
+
},
|
|
1095
|
+
|
|
1096
|
+
"storybook/renderToCanvas": {
|
|
1097
|
+
signature: "renderToCanvas(context: StoryContext, canvasElement: HTMLElement): void",
|
|
1098
|
+
example: `// .storybook/main.ts:
|
|
1099
|
+
export default { framework: '@pyreon/storybook' }
|
|
1100
|
+
|
|
1101
|
+
// Story file:
|
|
1102
|
+
import type { Meta, StoryObj } from '@pyreon/storybook'
|
|
1103
|
+
import { Button } from './Button'
|
|
1104
|
+
|
|
1105
|
+
const meta: Meta<typeof Button> = { component: Button }
|
|
1106
|
+
export default meta
|
|
1107
|
+
|
|
1108
|
+
export const Primary: StoryObj<typeof meta> = {
|
|
1109
|
+
args: { variant: 'primary', label: 'Click me' },
|
|
1110
|
+
}`,
|
|
1111
|
+
notes:
|
|
1112
|
+
"Storybook renderer for Pyreon components. Re-exports h, Fragment, signal, computed, effect, mount for story convenience.",
|
|
1113
|
+
},
|
|
573
1114
|
}
|