weifuwu 0.80.0 → 0.81.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/components/index.js +12 -12
- package/dist/index.js +219 -8
- package/dist/ui-dom/Confirm.d.ts +1 -1
- package/dist/ui-dom/Notification.d.ts +1 -1
- package/dist/ui-dom/Toast.d.ts +1 -1
- package/dist/ui-dom/hooks/chat.d.ts +1 -1
- package/dist/ui-dom/hooks/external.d.ts +1 -1
- package/dist/ui-dom/index.d.ts +1 -1
- package/dist/ui-dom/index.js +9 -8
- package/dist/ui-dom/middleware/api.d.ts +3 -0
- package/dist/ui-dom/middleware/auth.d.ts +2 -0
- package/dist/ui-dom/store.d.ts +1 -1
- package/dist/ui-dom/types.d.ts +1 -1
- package/dist/ui-dom/use-chat.d.ts +1 -1
- package/dist/ui-dom/vdom2/audit.d.ts +1 -1
- package/dist/ui-dom/vdom2/mount.d.ts +1 -1
- package/dist/ui-dom/vdom2/trace.d.ts +1 -1
- package/dist/ui-dom/vdom2/transform.d.ts +1 -1
- package/dist/user/index.d.ts +85 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1466,6 +1466,8 @@ function redirect(url, status = 302) {
|
|
|
1466
1466
|
// src/user/index.ts
|
|
1467
1467
|
var USERS_TABLE = "_weifuwu_users";
|
|
1468
1468
|
var SESSIONS_TABLE = "_weifuwu_sessions";
|
|
1469
|
+
var APP_TABLE = "_weifuwu_apps";
|
|
1470
|
+
var MEMBER_TABLE = "_weifuwu_app_members";
|
|
1469
1471
|
function normalizeEmail(email2) {
|
|
1470
1472
|
return email2.trim().toLowerCase();
|
|
1471
1473
|
}
|
|
@@ -1484,24 +1486,54 @@ function userSystem(options) {
|
|
|
1484
1486
|
const accessTtlSeconds = options.accessTtlSeconds ?? 3600;
|
|
1485
1487
|
const refreshTtlDays = options.refreshTtlDays ?? 30;
|
|
1486
1488
|
const prefix = options.prefix ?? "/api/auth";
|
|
1489
|
+
const INVITE_TTL_SECONDS = 7 * 24 * 3600;
|
|
1487
1490
|
let currentUser = null;
|
|
1488
1491
|
async function findUserById(id) {
|
|
1489
1492
|
const rows = await sql.query.from(USERS_TABLE).select("id", "email", "name", "role", "tenant").where({ id }).run();
|
|
1490
1493
|
return rows.length ? rows[0] : null;
|
|
1491
1494
|
}
|
|
1492
|
-
async function
|
|
1495
|
+
async function findMemberRole(appId, userId) {
|
|
1496
|
+
const rows = await sql.query.from(MEMBER_TABLE).select("role").where({ app_id: appId, user_id: userId }).run();
|
|
1497
|
+
return rows.length ? String(rows[0].role) : null;
|
|
1498
|
+
}
|
|
1499
|
+
async function findAppIdBySlug(slug) {
|
|
1500
|
+
const rows = await sql.query.from(APP_TABLE).select("id").where({ slug }).run();
|
|
1501
|
+
return rows.length ? String(rows[0].id) : null;
|
|
1502
|
+
}
|
|
1503
|
+
async function listAppsFor(userId) {
|
|
1504
|
+
const rows = await sql.query.from(MEMBER_TABLE).select("app_id", "role").where({ user_id: userId }).run();
|
|
1505
|
+
if (!rows.length) return [];
|
|
1506
|
+
const out = [];
|
|
1507
|
+
for (const r of rows) {
|
|
1508
|
+
const apps = await sql.query.from(APP_TABLE).select("id", "slug", "name").where({ id: String(r.app_id) }).run();
|
|
1509
|
+
if (apps.length) {
|
|
1510
|
+
out.push({ id: String(apps[0].id), slug: String(apps[0].slug), name: String(apps[0].name), role: String(r.role) });
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
return out;
|
|
1514
|
+
}
|
|
1515
|
+
async function issueSession(user, session) {
|
|
1493
1516
|
const token = signToken(
|
|
1494
|
-
{
|
|
1517
|
+
{
|
|
1518
|
+
sub: user.id,
|
|
1519
|
+
...session?.appId ? { appId: session.appId, role: session.role } : {},
|
|
1520
|
+
...!session?.appId && user.tenant ? { tenantId: user.tenant } : {}
|
|
1521
|
+
},
|
|
1495
1522
|
secret,
|
|
1496
1523
|
accessTtlSeconds
|
|
1497
1524
|
);
|
|
1498
1525
|
const refreshToken = generateRefreshToken();
|
|
1499
1526
|
const expiresAt = new Date(Date.now() + refreshTtlDays * 24 * 3600 * 1e3);
|
|
1500
|
-
await sql.query.insert(SESSIONS_TABLE).values({
|
|
1527
|
+
await sql.query.insert(SESSIONS_TABLE).values({
|
|
1528
|
+
token_hash: hashRefreshToken(refreshToken),
|
|
1529
|
+
user_id: user.id,
|
|
1530
|
+
expires_at: expiresAt,
|
|
1531
|
+
...session?.appId ? { app_id: session.appId } : {}
|
|
1532
|
+
}).run();
|
|
1501
1533
|
return { token, refreshToken };
|
|
1502
1534
|
}
|
|
1503
1535
|
async function consumeRefreshToken(refreshToken) {
|
|
1504
|
-
const rows = await sql.query.from(`${SESSIONS_TABLE} s`).select("s.user_id", "s.expires_at").where({ "s.token_hash": hashRefreshToken(refreshToken), "s.revoked_at": { isNull: true } }).run();
|
|
1536
|
+
const rows = await sql.query.from(`${SESSIONS_TABLE} s`).select("s.user_id", "s.expires_at", "s.app_id").where({ "s.token_hash": hashRefreshToken(refreshToken), "s.revoked_at": { isNull: true } }).run();
|
|
1505
1537
|
if (!rows.length) throw new HttpError("Invalid refresh token", 401);
|
|
1506
1538
|
const row = rows[0];
|
|
1507
1539
|
if (new Date(row.expires_at) < /* @__PURE__ */ new Date()) {
|
|
@@ -1509,12 +1541,13 @@ function userSystem(options) {
|
|
|
1509
1541
|
}
|
|
1510
1542
|
const user = await findUserById(row.user_id);
|
|
1511
1543
|
if (!user) throw new HttpError("User not found", 401);
|
|
1512
|
-
return user;
|
|
1544
|
+
return { user, appId: row.app_id ? String(row.app_id) : void 0 };
|
|
1513
1545
|
}
|
|
1514
1546
|
const mw = (async (req, ctx, next) => {
|
|
1515
1547
|
currentUser = null;
|
|
1516
1548
|
const sessionPayload = {};
|
|
1517
1549
|
let payloadTenantId;
|
|
1550
|
+
let payloadAppId;
|
|
1518
1551
|
const authHeader = req.headers.get("authorization");
|
|
1519
1552
|
if (authHeader?.startsWith("Bearer ")) {
|
|
1520
1553
|
const payload = verifyToken(authHeader.slice(7), secret);
|
|
@@ -1526,9 +1559,11 @@ function userSystem(options) {
|
|
|
1526
1559
|
}
|
|
1527
1560
|
sessionPayload.userId = String(payload.sub);
|
|
1528
1561
|
payloadTenantId = payload.tenantId;
|
|
1562
|
+
payloadAppId = payload.appId;
|
|
1529
1563
|
}
|
|
1530
1564
|
}
|
|
1531
1565
|
ctx.user = currentUser;
|
|
1566
|
+
if (payloadAppId != null) ctx.appId = String(payloadAppId);
|
|
1532
1567
|
if (payloadTenantId != null) ctx.tenantId = String(payloadTenantId);
|
|
1533
1568
|
ctx.auth = {
|
|
1534
1569
|
// ── 会话 payload 字段(来自 token,应用层签发时决定带什么) ──
|
|
@@ -1547,6 +1582,7 @@ function userSystem(options) {
|
|
|
1547
1582
|
}).returning("id", "email", "name", "role", "tenant").run();
|
|
1548
1583
|
const user = rows[0];
|
|
1549
1584
|
const session = await issueSession(user);
|
|
1585
|
+
currentUser = user;
|
|
1550
1586
|
return { ...session, user };
|
|
1551
1587
|
},
|
|
1552
1588
|
async login(email2, password) {
|
|
@@ -1563,7 +1599,9 @@ function userSystem(options) {
|
|
|
1563
1599
|
tenant: row.tenant
|
|
1564
1600
|
};
|
|
1565
1601
|
const session = await issueSession(user);
|
|
1566
|
-
|
|
1602
|
+
const apps = await listAppsFor(user.id);
|
|
1603
|
+
currentUser = user;
|
|
1604
|
+
return { ...session, user, apps };
|
|
1567
1605
|
},
|
|
1568
1606
|
async logout(refreshToken) {
|
|
1569
1607
|
await sql.query.update(SESSIONS_TABLE).set({ revoked_at: sql.raw`now()` }).where({ token_hash: hashRefreshToken(refreshToken) }).run();
|
|
@@ -1579,6 +1617,115 @@ function userSystem(options) {
|
|
|
1579
1617
|
},
|
|
1580
1618
|
createToken(type, payload, opts) {
|
|
1581
1619
|
return signToken({ type, ...payload }, secret, opts.ttlSeconds);
|
|
1620
|
+
},
|
|
1621
|
+
// ── 应用三层模型 ──
|
|
1622
|
+
async createApp(input) {
|
|
1623
|
+
if (!currentUser) throw new HttpError("Unauthorized", 401);
|
|
1624
|
+
if (!input.slug || !input.name) throw new HttpError("slug and name are required", 400);
|
|
1625
|
+
const slug = input.slug.trim().toLowerCase();
|
|
1626
|
+
const rows = await sql.query.insert(APP_TABLE).values({
|
|
1627
|
+
slug,
|
|
1628
|
+
name: input.name,
|
|
1629
|
+
owner_user_id: currentUser.id,
|
|
1630
|
+
open_registration: input.openRegistration ?? false
|
|
1631
|
+
}).returning("id", "slug", "name", "owner_user_id", "open_registration").run();
|
|
1632
|
+
const appInfo = rows[0];
|
|
1633
|
+
await sql.query.insert(MEMBER_TABLE).values({ app_id: appInfo.id, user_id: currentUser.id, role: "owner", invited_by: currentUser.id }).run();
|
|
1634
|
+
return appInfo;
|
|
1635
|
+
},
|
|
1636
|
+
async registerInApp(input) {
|
|
1637
|
+
if (!input.email || !input.password) throw new HttpError("email and password are required", 400);
|
|
1638
|
+
if (input.password.length < 8) throw new HttpError("password must be at least 8 characters", 400);
|
|
1639
|
+
const appRows = await sql.query.from(APP_TABLE).select("id", "open_registration").where({ slug: input.appSlug }).run();
|
|
1640
|
+
if (!appRows.length) throw new HttpError("Application not found", 404);
|
|
1641
|
+
const appRow = appRows[0];
|
|
1642
|
+
const appId = String(appRow.id);
|
|
1643
|
+
const email2 = normalizeEmail(input.email);
|
|
1644
|
+
let role = "member";
|
|
1645
|
+
if (!appRow.open_registration) {
|
|
1646
|
+
if (!input.inviteToken) throw new HttpError("Registration closed for this application", 403);
|
|
1647
|
+
const inv = verifyToken(input.inviteToken, secret);
|
|
1648
|
+
if (!inv || inv.type !== "app-invite" || inv.appId !== appId) {
|
|
1649
|
+
throw new HttpError("Invalid invite", 403);
|
|
1650
|
+
}
|
|
1651
|
+
if (inv.email && String(inv.email) !== email2) throw new HttpError("Invite email mismatch", 403);
|
|
1652
|
+
if (typeof inv.role === "string" && inv.role !== "owner") role = inv.role;
|
|
1653
|
+
}
|
|
1654
|
+
let user;
|
|
1655
|
+
const existing = await sql.query.from(USERS_TABLE).select("id", "email", "name", "role", "tenant").where({ email: email2 }).run();
|
|
1656
|
+
if (existing.length) {
|
|
1657
|
+
user = existing[0];
|
|
1658
|
+
} else {
|
|
1659
|
+
const passwordHash = await hashPassword(input.password);
|
|
1660
|
+
const rows = await sql.query.insert(USERS_TABLE).values({
|
|
1661
|
+
email: email2,
|
|
1662
|
+
password_hash: passwordHash,
|
|
1663
|
+
name: input.name ?? null,
|
|
1664
|
+
role: null,
|
|
1665
|
+
tenant: null
|
|
1666
|
+
}).returning("id", "email", "name", "role", "tenant").run();
|
|
1667
|
+
user = rows[0];
|
|
1668
|
+
}
|
|
1669
|
+
const member = await findMemberRole(appId, user.id);
|
|
1670
|
+
if (!member) {
|
|
1671
|
+
await sql.query.insert(MEMBER_TABLE).values({ app_id: appId, user_id: user.id, role, invited_by: currentUser?.id ?? null }).run();
|
|
1672
|
+
}
|
|
1673
|
+
const session = await issueSession(user, { appId, role });
|
|
1674
|
+
currentUser = user;
|
|
1675
|
+
return { ...session, user };
|
|
1676
|
+
},
|
|
1677
|
+
async loginApp(appSlug, email2, password) {
|
|
1678
|
+
const appRows = await sql.query.from(APP_TABLE).select("id").where({ slug: appSlug }).run();
|
|
1679
|
+
if (!appRows.length) throw new HttpError("Application not found", 404);
|
|
1680
|
+
const appId = String(appRows[0].id);
|
|
1681
|
+
const rows = await sql.query.from(USERS_TABLE).select("id", "email", "password_hash", "name", "role", "tenant").where({ email: normalizeEmail(email2) }).run();
|
|
1682
|
+
const row = rows[0];
|
|
1683
|
+
if (!row) throw new HttpError("Invalid email or password", 401);
|
|
1684
|
+
const valid = await verifyPassword(password, String(row.password_hash));
|
|
1685
|
+
if (!valid) throw new HttpError("Invalid email or password", 401);
|
|
1686
|
+
const role = await findMemberRole(appId, String(row.id));
|
|
1687
|
+
if (!role) throw new HttpError("Not a member of this application", 401);
|
|
1688
|
+
const user = {
|
|
1689
|
+
id: String(row.id),
|
|
1690
|
+
email: String(row.email),
|
|
1691
|
+
name: row.name,
|
|
1692
|
+
role: row.role,
|
|
1693
|
+
tenant: row.tenant
|
|
1694
|
+
};
|
|
1695
|
+
const session = await issueSession(user, { appId, role });
|
|
1696
|
+
currentUser = user;
|
|
1697
|
+
return { ...session, user };
|
|
1698
|
+
},
|
|
1699
|
+
async createInvite(appId, opts) {
|
|
1700
|
+
if (!currentUser) throw new HttpError("Unauthorized", 401);
|
|
1701
|
+
const role = await findMemberRole(appId, currentUser.id);
|
|
1702
|
+
if (role !== "owner") throw new HttpError("Owner only", 403);
|
|
1703
|
+
const inviteRole = opts.role && opts.role !== "owner" ? opts.role : "member";
|
|
1704
|
+
const inviteToken = signToken(
|
|
1705
|
+
{ type: "app-invite", appId, ...opts.email ? { email: normalizeEmail(opts.email) } : {}, role: inviteRole },
|
|
1706
|
+
secret,
|
|
1707
|
+
INVITE_TTL_SECONDS
|
|
1708
|
+
);
|
|
1709
|
+
return { inviteToken };
|
|
1710
|
+
},
|
|
1711
|
+
async addMember(appId, email2, role) {
|
|
1712
|
+
if (!currentUser) throw new HttpError("Unauthorized", 401);
|
|
1713
|
+
const callerRole = await findMemberRole(appId, currentUser.id);
|
|
1714
|
+
if (callerRole !== "owner") throw new HttpError("Owner only", 403);
|
|
1715
|
+
const target = await sql.query.from(USERS_TABLE).select("id").where({ email: normalizeEmail(email2) }).run();
|
|
1716
|
+
if (!target.length) throw new HttpError("User not found \u2014 invite them to register first", 400);
|
|
1717
|
+
const memberRole = role && role !== "owner" ? role : "member";
|
|
1718
|
+
await sql.query.insert(MEMBER_TABLE).values({ app_id: appId, user_id: String(target[0].id), role: memberRole, invited_by: currentUser.id }).run();
|
|
1719
|
+
},
|
|
1720
|
+
async listMyApps() {
|
|
1721
|
+
if (!currentUser) throw new HttpError("Unauthorized", 401);
|
|
1722
|
+
return listAppsFor(currentUser.id);
|
|
1723
|
+
},
|
|
1724
|
+
async requireApp(appId) {
|
|
1725
|
+
if (!currentUser) throw new HttpError("Unauthorized", 401);
|
|
1726
|
+
const role = await findMemberRole(appId, currentUser.id);
|
|
1727
|
+
if (!role) throw new HttpError("Not a member of this application", 403);
|
|
1728
|
+
return { userId: currentUser.id, appId, role };
|
|
1582
1729
|
}
|
|
1583
1730
|
};
|
|
1584
1731
|
return next(req, ctx);
|
|
@@ -1601,11 +1748,33 @@ function userSystem(options) {
|
|
|
1601
1748
|
CREATE TABLE IF NOT EXISTS ${SESSIONS_TABLE} (
|
|
1602
1749
|
token_hash TEXT PRIMARY KEY,
|
|
1603
1750
|
user_id UUID NOT NULL REFERENCES ${USERS_TABLE}(id) ON DELETE CASCADE,
|
|
1751
|
+
app_id TEXT,
|
|
1604
1752
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
1605
1753
|
revoked_at TIMESTAMPTZ,
|
|
1606
1754
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
1607
1755
|
)
|
|
1608
1756
|
`);
|
|
1757
|
+
await sql.unsafe(`ALTER TABLE ${SESSIONS_TABLE} ADD COLUMN IF NOT EXISTS app_id TEXT`);
|
|
1758
|
+
await sql.unsafe(`
|
|
1759
|
+
CREATE TABLE IF NOT EXISTS ${APP_TABLE} (
|
|
1760
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1761
|
+
slug TEXT NOT NULL UNIQUE,
|
|
1762
|
+
name TEXT NOT NULL,
|
|
1763
|
+
owner_user_id UUID NOT NULL REFERENCES ${USERS_TABLE}(id) ON DELETE CASCADE,
|
|
1764
|
+
open_registration BOOLEAN NOT NULL DEFAULT false,
|
|
1765
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
1766
|
+
)
|
|
1767
|
+
`);
|
|
1768
|
+
await sql.unsafe(`
|
|
1769
|
+
CREATE TABLE IF NOT EXISTS ${MEMBER_TABLE} (
|
|
1770
|
+
app_id UUID NOT NULL REFERENCES ${APP_TABLE}(id) ON DELETE CASCADE,
|
|
1771
|
+
user_id UUID NOT NULL REFERENCES ${USERS_TABLE}(id) ON DELETE CASCADE,
|
|
1772
|
+
role TEXT NOT NULL DEFAULT 'member',
|
|
1773
|
+
invited_by UUID,
|
|
1774
|
+
joined_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
1775
|
+
PRIMARY KEY (app_id, user_id)
|
|
1776
|
+
)
|
|
1777
|
+
`);
|
|
1609
1778
|
};
|
|
1610
1779
|
mw.routes = (app, routeOpts) => {
|
|
1611
1780
|
const p = routeOpts?.prefix ?? prefix;
|
|
@@ -1630,15 +1799,57 @@ function userSystem(options) {
|
|
|
1630
1799
|
if (!excluded.has("refresh")) app.post(`${p}/refresh`, async (req, ctx) => {
|
|
1631
1800
|
const body = await req.json().catch(() => ({}));
|
|
1632
1801
|
if (!body.refreshToken) throw new HttpError("refreshToken is required", 400);
|
|
1633
|
-
const user = await consumeRefreshToken(body.refreshToken);
|
|
1802
|
+
const { user, appId } = await consumeRefreshToken(body.refreshToken);
|
|
1634
1803
|
await ctx.auth.logout(body.refreshToken);
|
|
1635
|
-
const session = await issueSession(user);
|
|
1804
|
+
const session = await issueSession(user, appId ? { appId } : void 0);
|
|
1636
1805
|
return ok({ ...session, user });
|
|
1637
1806
|
});
|
|
1638
1807
|
if (!excluded.has("me")) app.get(`${p}/me`, async (_req, ctx) => {
|
|
1639
1808
|
if (!ctx.user) throw new HttpError("Unauthorized", 401);
|
|
1640
1809
|
return ok(ctx.user);
|
|
1641
1810
|
});
|
|
1811
|
+
if (!excluded.has("apps")) {
|
|
1812
|
+
app.post(`${p}/apps`, async (req, ctx) => {
|
|
1813
|
+
const body = await req.json().catch(() => ({}));
|
|
1814
|
+
const appInfo = await ctx.auth.createApp(body);
|
|
1815
|
+
return created({ app: appInfo });
|
|
1816
|
+
});
|
|
1817
|
+
app.get(`${p}/apps`, async (_req, ctx) => {
|
|
1818
|
+
const apps = await ctx.auth.listMyApps();
|
|
1819
|
+
return ok({ apps });
|
|
1820
|
+
});
|
|
1821
|
+
app.post(`${p}/apps/:appSlug/members`, async (req, ctx) => {
|
|
1822
|
+
const body = await req.json().catch(() => ({}));
|
|
1823
|
+
const appId = await findAppIdBySlug(ctx.params.appSlug);
|
|
1824
|
+
if (!appId) throw new HttpError("Application not found", 404);
|
|
1825
|
+
await ctx.auth.addMember(appId, body.email ?? "", body.role);
|
|
1826
|
+
return created({ ok: true });
|
|
1827
|
+
});
|
|
1828
|
+
app.post(`${p}/apps/:appSlug/invites`, async (req, ctx) => {
|
|
1829
|
+
const body = await req.json().catch(() => ({}));
|
|
1830
|
+
const appId = await findAppIdBySlug(ctx.params.appSlug);
|
|
1831
|
+
if (!appId) throw new HttpError("Application not found", 404);
|
|
1832
|
+
const result = await ctx.auth.createInvite(appId, { email: body.email, role: body.role });
|
|
1833
|
+
return created(result);
|
|
1834
|
+
});
|
|
1835
|
+
app.post(`${p}/apps/:appSlug/login`, async (req, ctx) => {
|
|
1836
|
+
const body = await req.json().catch(() => ({}));
|
|
1837
|
+
if (!body.email || !body.password) return badRequest("email and password are required");
|
|
1838
|
+
const result = await ctx.auth.loginApp(ctx.params.appSlug, body.email, body.password);
|
|
1839
|
+
return ok(result);
|
|
1840
|
+
});
|
|
1841
|
+
app.post(`${p}/apps/:appSlug/register`, async (req, ctx) => {
|
|
1842
|
+
const body = await req.json().catch(() => ({}));
|
|
1843
|
+
const result = await ctx.auth.registerInApp({
|
|
1844
|
+
appSlug: ctx.params.appSlug,
|
|
1845
|
+
email: body.email ?? "",
|
|
1846
|
+
password: body.password ?? "",
|
|
1847
|
+
name: body.name,
|
|
1848
|
+
inviteToken: body.inviteToken
|
|
1849
|
+
});
|
|
1850
|
+
return created(result);
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1642
1853
|
};
|
|
1643
1854
|
return mw;
|
|
1644
1855
|
}
|
package/dist/ui-dom/Confirm.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* weifuwu/ui-dom — Confirm(命令式中间件)
|
|
3
3
|
*
|
|
4
|
-
* v1 退役(design
|
|
4
|
+
* v1 退役(design 归档):实现已收敛到 components/Confirm(vdom 引擎,
|
|
5
5
|
* mountCommand + render-only)。本文件保留为公开 API 兼容入口。
|
|
6
6
|
*/
|
|
7
7
|
export { Confirm, confirm } from '../components/Confirm/Confirm.ts';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* weifuwu/ui-dom — Notification(命令式中间件)
|
|
3
3
|
*
|
|
4
|
-
* v1 退役(design
|
|
4
|
+
* v1 退役(design 归档):实现已收敛到 components/Notification(vdom 引擎,
|
|
5
5
|
* mountCommand + render-only)。本文件保留为公开 API 兼容入口。
|
|
6
6
|
*/
|
|
7
7
|
export { Notification, notification } from '../components/Notification/Notification.ts';
|
package/dist/ui-dom/Toast.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* weifuwu/ui-dom — Toast(命令式中间件)
|
|
3
3
|
*
|
|
4
|
-
* v1 退役(design
|
|
4
|
+
* v1 退役(design 归档):实现已收敛到 components/Toast(vdom 引擎,
|
|
5
5
|
* mountCommand + render-only)。本文件保留为公开 API 兼容入口。
|
|
6
6
|
*/
|
|
7
7
|
export { Toast, toast } from '../components/Toast/Toast.ts';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* hooks/chat — AI 对话会话(useChat)
|
|
3
3
|
*
|
|
4
|
-
* 会话语义 + 工具调用内嵌 + HITL 审批。render-only 方案(design
|
|
4
|
+
* 会话语义 + 工具调用内嵌 + HITL 审批。render-only 方案(design 归档):
|
|
5
5
|
* state 是**普通对象**(不再挂组件 $),变化 → notify → 订阅者重渲染;
|
|
6
6
|
* handle 带 subscribe——共享会话的子组件用 ctx.ui.useExternal(handle) 订阅。
|
|
7
7
|
*/
|
package/dist/ui-dom/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* weifuwu/ui-dom — 前端路由 + 渲染运行时(v2 vdom 引擎,render-only)
|
|
3
3
|
*
|
|
4
|
-
* 渲染运行时 = vdom(第 2 代引擎,design
|
|
4
|
+
* 渲染运行时 = vdom(第 2 代引擎,design 归档):
|
|
5
5
|
* - 渲染触发唯一原语 ctx.ui.render()(无 $ / dirty——无自动渲染)
|
|
6
6
|
* - 共享状态:createStore + ctx.ui.useExternal
|
|
7
7
|
* - 命令式挂载:mountCommand/unmountCommand(弹窗中间件在 components 各组件内部)
|