ivue 1.5.8 → 2.0.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/README.md +170 -33
- package/bin/ivue.mjs +152 -0
- package/dist/Reactive.d.ts +131 -0
- package/dist/env.d.ts +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.es.js +90 -81
- package/dist/index.umd.js +1 -1
- package/lib/Reactive.ts +509 -0
- package/lib/__tests__/Reactive.vitest.spec.ts +1056 -0
- package/lib/__tests__/ReactiveAdversarial.vitest.spec.ts +182 -0
- package/lib/__tests__/coverage-completion.vitest.spec.ts +24 -0
- package/lib/__tests__/ivue.vitest.spec.ts +1460 -150
- package/lib/env.d.ts +1 -0
- package/lib/index.ts +1 -1
- package/lib/ivue.ts +661 -241
- package/lib/kernel.ts +18 -0
- package/package.json +51 -13
- package/skills/ivue/SKILL.md +726 -0
- package/dist/ivue.d.ts +0 -234
package/README.md
CHANGED
|
@@ -1,48 +1,185 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://ivue.dev/">
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs_v2/public/brand-lockup-dark.png">
|
|
5
|
+
<img src="docs_v2/public/brand-lockup-light.png" alt="ivue — Infinite Vue" width="340">
|
|
6
|
+
</picture>
|
|
7
|
+
</a>
|
|
8
|
+
</p>
|
|
2
9
|
|
|
3
|
-
|
|
10
|
+
<h3 align="center">Plain classes. Full reactivity.<br>Infinite scalability. One kilobyte.</h3>
|
|
4
11
|
|
|
5
|
-
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="https://www.npmjs.com/package/ivue"><img src="https://img.shields.io/npm/v/ivue.svg" alt="npm"></a>
|
|
14
|
+
<a href="https://github.com/infinite-system/ivue/actions/workflows/ci.yml"><img src="https://github.com/infinite-system/ivue/actions/workflows/ci.yml/badge.svg?branch=main" alt="build status"></a>
|
|
15
|
+
</p>
|
|
6
16
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
– Extend Props Defaults<br />
|
|
11
|
-
– Extend Emits<br />
|
|
12
|
-
– Extend Slots<br />
|
|
13
|
-
– Extend Components<br />
|
|
14
|
-
– Improves DX by elegantly dealing with `.value` in the background<br />
|
|
15
|
-
– Can be used both as a Store and a View Model for Components<br />
|
|
16
|
-
– Zero dependencies except Vue 3<br />
|
|
17
|
+
<p align="center">
|
|
18
|
+
<strong>Docs: <a href="https://ivue.dev/">ivue.dev</a></strong>
|
|
19
|
+
</p>
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
ivue builds Vue 3 reactivity out of plain TypeScript classes. Real
|
|
22
|
+
inheritance, real encapsulation, real polymorphism — on ordinary objects,
|
|
23
|
+
with nothing paid until first access. The whole engine is **1.1kb gzipped**
|
|
24
|
+
with zero dependencies.
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
- **Native class API** — `extends`, `super`, getters, setters, private
|
|
27
|
+
fields. Real inheritance, encapsulation and polymorphism, all reactive.
|
|
28
|
+
- **Zero-cost creation** — instances are plain objects. A million of them
|
|
29
|
+
take 22 ms — 6 to 132× faster than the alternatives.
|
|
30
|
+
- **One kilobyte** — 1.1kb gzipped, zero dependencies, 100% test coverage.
|
|
31
|
+
Stripped to the load-bearing core — an API you can hold in your head.
|
|
32
|
+
- **Store or ViewModel** — the same class serves as a global store, a
|
|
33
|
+
component ViewModel, or a domain model. One mental model everywhere.
|
|
34
|
+
- **Composition API, fully compatible** — composables plug in through
|
|
35
|
+
`$`-getters. The entire Vue ecosystem works inside your classes.
|
|
36
|
+
- **TypeScript first** — writable ref-getters, fully typed instances,
|
|
37
|
+
precise inference. The type system shaped the engine's design.
|
|
32
38
|
|
|
39
|
+
## Getting started
|
|
33
40
|
|
|
41
|
+
```sh
|
|
42
|
+
npm i ivue vue
|
|
43
|
+
```
|
|
34
44
|
|
|
35
|
-
|
|
45
|
+
## Built for humans and AI
|
|
36
46
|
|
|
37
|
-
|
|
47
|
+
ivue ships with an [Operating Manual](https://ivue.dev/guide/standard) —
|
|
48
|
+
the complete authoring standard as annotated templates, rules, and a review
|
|
49
|
+
checklist. It reads as documentation and works as a drop-in skill for AI
|
|
50
|
+
coding agents, so generated code follows the same standard your team writes:
|
|
38
51
|
|
|
39
|
-
|
|
52
|
+
```sh
|
|
53
|
+
npx ivue skill # installs .claude/skills/ivue/SKILL.md, version-locked
|
|
54
|
+
npx ivue skill --all # + Codex/Cursor/Copilot where already in use
|
|
55
|
+
```
|
|
40
56
|
|
|
41
|
-
|
|
57
|
+
### Usage
|
|
42
58
|
|
|
43
|
-
|
|
59
|
+
```ts
|
|
60
|
+
import { Reactive } from 'ivue';
|
|
61
|
+
import { ref } from 'vue';
|
|
44
62
|
|
|
63
|
+
class $Counter {
|
|
64
|
+
get count() {
|
|
65
|
+
return ref(0)
|
|
66
|
+
}
|
|
67
|
+
get double() {
|
|
68
|
+
return this.count.value * 2 // plain getter — derives on read
|
|
69
|
+
}
|
|
70
|
+
increment() {
|
|
71
|
+
this.count.value++
|
|
72
|
+
}
|
|
73
|
+
}
|
|
45
74
|
|
|
46
|
-
|
|
75
|
+
export namespace Counter {
|
|
76
|
+
export const $Class = $Counter; // raw — children `extends` this
|
|
77
|
+
export let Class = Reactive($Class); // reactive — you `new` this
|
|
78
|
+
export type Instance = typeof Class.Instance; // the unwrapping-surface type
|
|
79
|
+
}
|
|
80
|
+
```
|
|
47
81
|
|
|
48
|
-
|
|
82
|
+
In a component:
|
|
83
|
+
|
|
84
|
+
```vue
|
|
85
|
+
<script setup lang="ts">
|
|
86
|
+
import { Counter } from './Counter';
|
|
87
|
+
|
|
88
|
+
const counter = new Counter.Class();
|
|
89
|
+
|
|
90
|
+
// the state destructure — every Ref the template touches, unwrapped uniformly
|
|
91
|
+
const { count } = counter;
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
<template>
|
|
95
|
+
<button @click="counter.increment()">{{ count }} · double {{ counter.double }}</button>
|
|
96
|
+
</template>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Full walkthrough: [Getting Started](https://ivue.dev/guide/getting-started).
|
|
100
|
+
|
|
101
|
+
## Hard problems, solved together
|
|
102
|
+
|
|
103
|
+
Each of these sank earlier class-reactivity attempts. ivue ships all of them
|
|
104
|
+
as one coherent design:
|
|
105
|
+
|
|
106
|
+
- **Bound methods** — `this.method` is always correct, always the same reference.
|
|
107
|
+
- **Reactive inheritance** — deep `super.x.value` chains resolve level-safe.
|
|
108
|
+
- **Development parity** — the same class identity, direct binding, and engine branches in development and production.
|
|
109
|
+
- **Circular import immunity** — the namespace pattern resolves mutual references in any load order.
|
|
110
|
+
- **Writable getter types** — ref-returning getters type as writable; instances fully inferred.
|
|
111
|
+
- **Deterministic teardown** — `$watch` scopes per instance, `$stopEffects()` cleans up.
|
|
112
|
+
- **Minimal memory footprint** — derivations are shared prototype getters, not per-instance allocations.
|
|
113
|
+
- **Hot paths** — reads hoist to native ref speed with one line where it matters.
|
|
114
|
+
|
|
115
|
+
## One idea, carried through
|
|
116
|
+
|
|
117
|
+
`Reactive()` transforms a class **once**. A getter returning `ref()` becomes
|
|
118
|
+
state: created on first touch, cached, stable forever. A plain getter stays
|
|
119
|
+
plain and re-derives on every read — reactive with zero allocation. Methods
|
|
120
|
+
bind themselves once, to the right `this`. Instances stay ordinary objects:
|
|
121
|
+
no proxy wraps them, no work happens at construction.
|
|
122
|
+
|
|
123
|
+
Inheritance, teardown, development parity, speed — consequences of that one move.
|
|
124
|
+
|
|
125
|
+
Composables plug straight in — the entire Vue ecosystem works inside your
|
|
126
|
+
classes:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import { useMouse } from '@vueuse/core';
|
|
130
|
+
|
|
131
|
+
class $Pointer {
|
|
132
|
+
private get $mouse() {
|
|
133
|
+
return useMouse() // created once, encapsulated
|
|
134
|
+
}
|
|
135
|
+
get x() {
|
|
136
|
+
return this.$mouse.x
|
|
137
|
+
}
|
|
138
|
+
get y() {
|
|
139
|
+
return this.$mouse.y
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## The numbers
|
|
145
|
+
|
|
146
|
+
Measured, not promised — method and live in-browser benchmarks in
|
|
147
|
+
[the docs](https://ivue.dev/guide/benchmarks).
|
|
148
|
+
|
|
149
|
+
| creating 1,000,000 instances | time | ivue is |
|
|
150
|
+
| --- | --- | --- |
|
|
151
|
+
| **ivue `new Class()`** | **21.7 ms** | the baseline |
|
|
152
|
+
| composable factory | 139 ms | **6.4× faster** |
|
|
153
|
+
| native `reactive()` | 470 ms | **22× faster** |
|
|
154
|
+
|
|
155
|
+
| memory heap at 100,000 live instances | per instance | 100k total |
|
|
156
|
+
| --- | --- | --- |
|
|
157
|
+
| **ivue class, 30 getters** | **3.7 KB** | 364 MB |
|
|
158
|
+
| composable, 30 closures | 8.0 KB | 781 MB |
|
|
159
|
+
| `reactive()`, fields + getters | 10.4 KB | 1.02 GB |
|
|
160
|
+
| composable, 30 computeds | 19.7 KB | 1.93 GB |
|
|
161
|
+
|
|
162
|
+
Taken all the way down: a fully reactive spreadsheet model holding
|
|
163
|
+
**20,000,000 live cells at 4.7 bytes each** — 8.5× below the plain-object
|
|
164
|
+
floor — because in ivue, everything costs proportional to what's *observed*,
|
|
165
|
+
nothing costs proportional to what *exists*.
|
|
166
|
+
|
|
167
|
+
## A note on the size
|
|
168
|
+
|
|
169
|
+
The complete engine is **1.1kb gzipped**: lazy prototype transformation,
|
|
170
|
+
bound methods, inheritance, lifecycle ownership, and the public utilities.
|
|
171
|
+
Development uses that same engine without a second hot-update execution path.
|
|
172
|
+
|
|
173
|
+
> *Perfection is achieved, not when there is nothing more to add, but when
|
|
174
|
+
> there is nothing left to take away.* — Antoine de Saint-Exupéry
|
|
175
|
+
|
|
176
|
+
## Documentation
|
|
177
|
+
|
|
178
|
+
Full guide, principles, live benchmarks, advanced examples (a 1,000,000-row
|
|
179
|
+
virtual scroller, a 20,000,000-cell flyweight grid, production-grade Quasar
|
|
180
|
+
field components), and the API reference:
|
|
181
|
+
**https://ivue.dev/**
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
[MIT](./LICENSE)
|
package/bin/ivue.mjs
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ivue CLI.
|
|
4
|
+
*
|
|
5
|
+
* npx ivue skill [--force]
|
|
6
|
+
*
|
|
7
|
+
* Installs the operating manual — the same document that renders as the
|
|
8
|
+
* docs' Standard page — into ./.claude/skills/ivue/SKILL.md, where Claude
|
|
9
|
+
* Code (and compatible agents) pick it up as a skill. The copy comes from
|
|
10
|
+
* the installed package, so the manual always matches the engine version
|
|
11
|
+
* the project actually runs.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
14
|
+
import { dirname, join, resolve } from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
|
|
17
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
18
|
+
const [command, ...flags] = process.argv.slice(2);
|
|
19
|
+
|
|
20
|
+
function fail(message) {
|
|
21
|
+
console.error(`ivue: ${message}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (command !== 'skill') {
|
|
26
|
+
console.log(`ivue — commands:
|
|
27
|
+
|
|
28
|
+
npx ivue skill [targets] [--force]
|
|
29
|
+
|
|
30
|
+
Installs the ivue operating manual for coding agents. Targets:
|
|
31
|
+
(none) / --claude .claude/skills/ivue/SKILL.md
|
|
32
|
+
--cursor .cursor/rules/ivue.mdc
|
|
33
|
+
--copilot .github/instructions/ivue.instructions.md
|
|
34
|
+
--agents / --codex a managed section in AGENTS.md (Codex CLI,
|
|
35
|
+
Windsurf, Gemini CLI and others read this file)
|
|
36
|
+
--all Claude + every vendor whose footprint exists
|
|
37
|
+
(.cursor/, .github/, AGENTS.md) — creates nothing new
|
|
38
|
+
--force overwrites locally modified copies.`);
|
|
39
|
+
process.exit(command === undefined || command === 'help' ? 0 : 1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const force = flags.includes('--force');
|
|
43
|
+
const version = JSON.parse(
|
|
44
|
+
readFileSync(join(packageRoot, 'package.json'), 'utf8'),
|
|
45
|
+
).version;
|
|
46
|
+
|
|
47
|
+
// published packages carry skills/; the repo itself carries .claude/skills/
|
|
48
|
+
const sourcePath = [
|
|
49
|
+
join(packageRoot, 'skills', 'ivue', 'SKILL.md'),
|
|
50
|
+
join(packageRoot, '.claude', 'skills', 'ivue', 'SKILL.md'),
|
|
51
|
+
].find(existsSync);
|
|
52
|
+
if (!sourcePath) fail('SKILL.md not found in the installed package.');
|
|
53
|
+
|
|
54
|
+
const skillText = readFileSync(sourcePath, 'utf8');
|
|
55
|
+
// the manual's body, without the Claude skill frontmatter
|
|
56
|
+
const skillBody = skillText.replace(/^---\n[\s\S]*?\n---\n+/, '');
|
|
57
|
+
const skillDescription =
|
|
58
|
+
/description:\s*([^\n]+)/.exec(skillText)?.[1] ??
|
|
59
|
+
'The ivue operating manual.';
|
|
60
|
+
|
|
61
|
+
/** Write one target idempotently; refuse to clobber local edits sans --force. */
|
|
62
|
+
function install(relativePath, content, label) {
|
|
63
|
+
const targetPath = join(process.cwd(), relativePath);
|
|
64
|
+
if (existsSync(targetPath)) {
|
|
65
|
+
const existing = readFileSync(targetPath, 'utf8');
|
|
66
|
+
if (existing === content) {
|
|
67
|
+
console.log(`ivue: ${label} already up to date (ivue v${version}).`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (!force) {
|
|
71
|
+
fail(
|
|
72
|
+
`${relativePath} exists and differs from ivue v${version}'s copy.\n` +
|
|
73
|
+
' Re-run with --force to overwrite it.',
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
78
|
+
writeFileSync(targetPath, content);
|
|
79
|
+
console.log(`ivue: ${label} installed at ${relativePath} (ivue v${version}).`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Explicit flags always install (creating the folder is the point).
|
|
83
|
+
// --all is detect-and-equip: vendor targets only where their footprint
|
|
84
|
+
// already exists — it never scaffolds a tool you don't use. Claude is the
|
|
85
|
+
// native format and installs in every mode.
|
|
86
|
+
const wantAll = flags.includes('--all');
|
|
87
|
+
const detected = (marker) => existsSync(join(process.cwd(), marker));
|
|
88
|
+
const skipped = (label, marker, flag) =>
|
|
89
|
+
console.log(`ivue: ${label} skipped — no ${marker} here (pass ${flag} to create it).`);
|
|
90
|
+
|
|
91
|
+
const wantCursor = flags.includes('--cursor') || (wantAll && detected('.cursor'));
|
|
92
|
+
const wantCopilot = flags.includes('--copilot') || (wantAll && detected('.github'));
|
|
93
|
+
const wantAgents =
|
|
94
|
+
flags.includes('--agents') ||
|
|
95
|
+
flags.includes('--codex') || // Codex CLI reads AGENTS.md — same target
|
|
96
|
+
(wantAll && detected('AGENTS.md'));
|
|
97
|
+
const wantClaude =
|
|
98
|
+
wantAll ||
|
|
99
|
+
flags.includes('--claude') ||
|
|
100
|
+
(!flags.includes('--cursor') &&
|
|
101
|
+
!flags.includes('--copilot') &&
|
|
102
|
+
!flags.includes('--agents') &&
|
|
103
|
+
!flags.includes('--codex'));
|
|
104
|
+
|
|
105
|
+
if (wantClaude) {
|
|
106
|
+
install('.claude/skills/ivue/SKILL.md', skillText, 'Claude skill');
|
|
107
|
+
}
|
|
108
|
+
if (wantAll && !wantCursor) skipped('Cursor rule', '.cursor/', '--cursor');
|
|
109
|
+
if (wantAll && !wantCopilot) skipped('Copilot instructions', '.github/', '--copilot');
|
|
110
|
+
if (wantAll && !wantAgents) skipped('AGENTS.md section', 'AGENTS.md', '--agents');
|
|
111
|
+
if (wantCursor) {
|
|
112
|
+
install(
|
|
113
|
+
'.cursor/rules/ivue.mdc',
|
|
114
|
+
`---\ndescription: ${skillDescription}\nglobs:\nalwaysApply: false\n---\n\n${skillBody}`,
|
|
115
|
+
'Cursor rule',
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
if (wantCopilot) {
|
|
119
|
+
install(
|
|
120
|
+
'.github/instructions/ivue.instructions.md',
|
|
121
|
+
`---\napplyTo: '**/*.{ts,tsx,vue}'\n---\n\n${skillBody}`,
|
|
122
|
+
'Copilot instructions',
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
if (wantAgents) {
|
|
126
|
+
// AGENTS.md is shared and user-owned — manage only a marked section.
|
|
127
|
+
const startMarker = '<!-- ivue:skill:start -->';
|
|
128
|
+
const endMarker = '<!-- ivue:skill:end -->';
|
|
129
|
+
const section = `${startMarker}\n<!-- managed by \`npx ivue skill --agents\` — edits inside are overwritten -->\n\n${skillBody}\n${endMarker}`;
|
|
130
|
+
const agentsPath = join(process.cwd(), 'AGENTS.md');
|
|
131
|
+
if (existsSync(agentsPath)) {
|
|
132
|
+
const existing = readFileSync(agentsPath, 'utf8');
|
|
133
|
+
const start = existing.indexOf(startMarker);
|
|
134
|
+
const end = existing.indexOf(endMarker);
|
|
135
|
+
if (start !== -1 && end !== -1) {
|
|
136
|
+
const updated =
|
|
137
|
+
existing.slice(0, start) + section + existing.slice(end + endMarker.length);
|
|
138
|
+
if (updated === existing) {
|
|
139
|
+
console.log(`ivue: AGENTS.md section already up to date (ivue v${version}).`);
|
|
140
|
+
} else {
|
|
141
|
+
writeFileSync(agentsPath, updated);
|
|
142
|
+
console.log(`ivue: AGENTS.md section updated (ivue v${version}).`);
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
writeFileSync(agentsPath, existing.trimEnd() + '\n\n' + section + '\n');
|
|
146
|
+
console.log(`ivue: AGENTS.md section appended (ivue v${version}).`);
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
writeFileSync(agentsPath, section + '\n');
|
|
150
|
+
console.log(`ivue: AGENTS.md created (ivue v${version}).`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { watch, watchEffect, type ExtractPropTypes, type Ref } from 'vue';
|
|
2
|
+
/**
|
|
3
|
+
* Create a reactive class.
|
|
4
|
+
* @param targetClass The class to make reactive.
|
|
5
|
+
* @returns A reactive version of the class (the same class, transformed in place).
|
|
6
|
+
*/
|
|
7
|
+
export declare function Reactive<C extends new (...args: any) => any>(targetClass: C): ReactiveClass<C> & {
|
|
8
|
+
Instance: ReactiveInstance<InstanceType<C>>;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Vue props interface in defineComponent() style.
|
|
12
|
+
*/
|
|
13
|
+
export declare type VuePropsObject = Record<string, {
|
|
14
|
+
type: any;
|
|
15
|
+
default?: any;
|
|
16
|
+
required?: boolean;
|
|
17
|
+
}>;
|
|
18
|
+
/**
|
|
19
|
+
* Vue Props with default properties declared as existing and having values.
|
|
20
|
+
*/
|
|
21
|
+
export declare type VuePropsWithDefaults<T extends VuePropsObject> = {
|
|
22
|
+
[K in keyof T]: {
|
|
23
|
+
type: T[K]['type'];
|
|
24
|
+
default: T[K]['default'];
|
|
25
|
+
required?: boolean;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Determines if the value is a JavaScript Class.
|
|
30
|
+
* Note that class is a class function in JavaScript.
|
|
31
|
+
*
|
|
32
|
+
* @param val Any value
|
|
33
|
+
* @returns boolean If it's a JavaScript Class returns true
|
|
34
|
+
*/
|
|
35
|
+
export declare const isClass: (val: any) => boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Creates props with defaults in defineComponent() style.
|
|
38
|
+
*
|
|
39
|
+
* Merge defaults regular object with Vue types object
|
|
40
|
+
* declared in defineComponent() style.
|
|
41
|
+
*
|
|
42
|
+
* This is made so that the defaults can be declared "as they are"
|
|
43
|
+
* without requiring objects to be function callbacks returning an object.
|
|
44
|
+
*
|
|
45
|
+
* // You don't need to wrap objects in () => ({ nest: { nest :{} } })
|
|
46
|
+
* // You can just delcare them normally.
|
|
47
|
+
* const defaults = {
|
|
48
|
+
* nest: {
|
|
49
|
+
* nest
|
|
50
|
+
* }
|
|
51
|
+
* }
|
|
52
|
+
*
|
|
53
|
+
* This function will create the Vue expected callbacks for Objects, Arrays & Classes
|
|
54
|
+
* but leave primitive properties and functions intact so that
|
|
55
|
+
* the final object is fully defineComponent() style compatible.
|
|
56
|
+
*
|
|
57
|
+
* The default cloner is the native `structuredClone` (zero-dependency, handles
|
|
58
|
+
* plain data, Map/Set/Date/typed arrays, circular refs). For defaults that
|
|
59
|
+
* contain class instances or functions — which `structuredClone` cannot clone —
|
|
60
|
+
* pass a `customCloner` such as lodash `cloneDeep`.
|
|
61
|
+
*
|
|
62
|
+
* @param defaults Regular object of default key -> values
|
|
63
|
+
* @param typedProps Props declared in defineComponent() style with type and possibly required declared, but without default
|
|
64
|
+
* @param customCloner Optional cloner used for object/array defaults (defaults to structuredClone)
|
|
65
|
+
* @returns Props declared in defineComponent() style with all properties having default property declared.
|
|
66
|
+
*/
|
|
67
|
+
export declare const propsWithDefaults: <T extends VuePropsObject>(defaults: Record<string, any>, typedProps: T, customCloner?: ((val: any) => any) | undefined) => VuePropsWithDefaults<T>;
|
|
68
|
+
/**
|
|
69
|
+
* Type Utilities
|
|
70
|
+
*/
|
|
71
|
+
declare type GetterKeys<T> = {
|
|
72
|
+
[K in keyof T]: T[K] extends (...args: any[]) => any ? never : T[K] extends undefined ? never : K;
|
|
73
|
+
}[keyof T];
|
|
74
|
+
declare type GetterReturn<T, K extends keyof T> = T[K] extends (...args: any[]) => any ? never : T[K];
|
|
75
|
+
declare type WritableComputedLike = Ref<any> & {
|
|
76
|
+
set: (...args: any[]) => any;
|
|
77
|
+
};
|
|
78
|
+
declare type IsWritableGetter<R> = R extends Ref<any> ? true : R extends WritableComputedLike ? true : false;
|
|
79
|
+
declare type WritableGetters<T> = {
|
|
80
|
+
[K in GetterKeys<T> as IsWritableGetter<GetterReturn<T, K>> extends true ? K : never]-?: T[K];
|
|
81
|
+
};
|
|
82
|
+
export declare type ReactiveInstance<T> = T & WritableGetters<T> & {
|
|
83
|
+
/** Register a watcher in the instance's lazy effect scope (same signature as Vue `watch`). */
|
|
84
|
+
$watch: typeof watch;
|
|
85
|
+
/** Register a watchEffect in the instance's lazy effect scope (same signature as Vue `watchEffect`). */
|
|
86
|
+
$watchEffect: typeof watchEffect;
|
|
87
|
+
/** Stop the instance's effect scope and drop cached cells. */
|
|
88
|
+
$stopEffects: () => void;
|
|
89
|
+
};
|
|
90
|
+
export declare type ReactiveClass<C extends new (...args: any) => any> = new (...args: ConstructorParameters<C>) => ReactiveInstance<InstanceType<C>>;
|
|
91
|
+
/**
|
|
92
|
+
* Component-authoring type utilities (types only — erased at build time).
|
|
93
|
+
* These complement `propsWithDefaults` for the params/defaults component
|
|
94
|
+
* architecture: object-declared emits, extensible slots, and precise
|
|
95
|
+
* handler-parameter extraction.
|
|
96
|
+
*/
|
|
97
|
+
/** Any JavaScript function of any type. */
|
|
98
|
+
export declare type AnyFn = (...args: any[]) => any;
|
|
99
|
+
/** Convert Record to Union Type. */
|
|
100
|
+
export declare type RecordToUnion<T extends Record<string, any>> = T[keyof T];
|
|
101
|
+
/** Gets object T property by key K. */
|
|
102
|
+
export declare type ValueOf<T extends Record<any, any>, K extends keyof T> = T[K];
|
|
103
|
+
/** Convert Union Type to Intersection Type. */
|
|
104
|
+
export declare type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
105
|
+
/** Prefix keys of an interface T with a prefix P. */
|
|
106
|
+
export declare type PrefixKeys<T, P extends string | undefined = undefined> = {
|
|
107
|
+
[K in Extract<keyof T, string> as P extends string ? `${P}${K}` : K]: T[K];
|
|
108
|
+
};
|
|
109
|
+
/** Extracts object-declared emit validators into the emit-function interface. */
|
|
110
|
+
export declare type ExtractEmitTypes<T extends Record<string, any>> = UnionToIntersection<RecordToUnion<{
|
|
111
|
+
[K in keyof T]: (evt: K, ...args: Parameters<T[K]>) => void;
|
|
112
|
+
}>>;
|
|
113
|
+
/**
|
|
114
|
+
* Extract properties as all-assigned (non-optional) because every one of
|
|
115
|
+
* them carries a default.
|
|
116
|
+
*/
|
|
117
|
+
export declare type ExtractPropDefaultTypes<O> = {
|
|
118
|
+
[K in keyof O]: K extends keyof ExtractPropTypes<O> ? ExtractPropTypes<O>[K] : never;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Extend a slots interface T with prefixed 'before--' & 'after--' slots to
|
|
122
|
+
* create fully extensible wrapped components.
|
|
123
|
+
*/
|
|
124
|
+
export declare type ExtendSlots<T> = PrefixKeys<T, 'before--'> & T & PrefixKeys<T, 'after--'>;
|
|
125
|
+
/** Get function arguments Parameters<F> parameter by index K. */
|
|
126
|
+
export declare type FnParameter<F extends AnyFn, K extends number> = Parameters<F>[K];
|
|
127
|
+
/** Get interface T property K's function arguments as Parameters. */
|
|
128
|
+
export declare type IFnParameters<T extends Record<any, any>, K extends string> = Parameters<Required<Pick<T, K>>[K]>;
|
|
129
|
+
/** Get interface T property P's function parameter by index K. */
|
|
130
|
+
export declare type IFnParameter<T extends Record<any, any>, P extends keyof T, K extends number> = FnParameter<NonNullable<T[P]> extends AnyFn ? NonNullable<T[P]> : never, K>;
|
|
131
|
+
export {};
|
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './Reactive';
|