feat(messaging-ui): KernelClientAdapter — direct-IIOS transport for the SDK
Implements MessagingAdapter over @insignia/iios-kernel-client (browser → IIOS,
token-in): listConversations/openThread/history/send/subscribe/typing/markRead
/react, mapping kernel Message/ThreadSummary/annotations onto the SDK's neutral
types. currentActorId comes from the host's session (senderId space), never
inferred from history — the exact bug the conformance suite kills.
- lives in adapters/ (transport boundary intact — core stays transport-free)
- ships from the ./adapters/kernel-client subpath (kernel-client is an optional
peer dep; excluded from the main bundle)
- connectKernelAdapter({serviceUrl, token, currentUserId}) convenience
- passes the shared adapter conformance suite (12 tests) over the real
MessageSocket facade with only the socket.io layer faked
- media/attachments deferred (kernel-client has no presign yet)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,10 @@
|
|||||||
"types": "./dist/adapters/mock.d.ts",
|
"types": "./dist/adapters/mock.d.ts",
|
||||||
"import": "./dist/adapters/mock.js"
|
"import": "./dist/adapters/mock.js"
|
||||||
},
|
},
|
||||||
|
"./adapters/kernel-client": {
|
||||||
|
"types": "./dist/adapters/kernel-client.d.ts",
|
||||||
|
"import": "./dist/adapters/kernel-client.js"
|
||||||
|
},
|
||||||
"./conformance": {
|
"./conformance": {
|
||||||
"types": "./dist/conformance.d.ts",
|
"types": "./dist/conformance.d.ts",
|
||||||
"import": "./dist/conformance.js"
|
"import": "./dist/conformance.js"
|
||||||
@@ -29,9 +33,16 @@
|
|||||||
"test": "vitest run"
|
"test": "vitest run"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": ">=18"
|
"react": ">=18",
|
||||||
|
"@insignia/iios-kernel-client": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@insignia/iios-kernel-client": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@insignia/iios-kernel-client": "workspace:*",
|
||||||
"@testing-library/dom": "^10.4.0",
|
"@testing-library/dom": "^10.4.0",
|
||||||
"@testing-library/react": "^16.1.0",
|
"@testing-library/react": "^16.1.0",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.0.0",
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
// Runs the shared adapter conformance suite against KernelClientAdapter — proving it satisfies
|
||||||
|
// the same contract as the mock, over the REAL MessageSocket facade. Only the lowest socket.io
|
||||||
|
// layer is faked (via kernel-client's own SocketLike seam), so the facade's wire mapping is
|
||||||
|
// exercised for real. No live IIOS required.
|
||||||
|
|
||||||
|
import { MessageSocket } from '@insignia/iios-kernel-client';
|
||||||
|
import type { Message as KernelMessage, SocketLike } from '@insignia/iios-kernel-client';
|
||||||
|
import { runAdapterConformance } from '../conformance';
|
||||||
|
import { KernelClientAdapter, type RestPort } from './kernel-client';
|
||||||
|
|
||||||
|
const ME = 'me';
|
||||||
|
const SEEDED = 'th_seed';
|
||||||
|
|
||||||
|
/** An in-memory stand-in for the /message socket.io namespace: stores messages, echoes 'message'. */
|
||||||
|
function makeFakeSocket(): SocketLike {
|
||||||
|
const threads = new Map<string, KernelMessage[]>([
|
||||||
|
[
|
||||||
|
SEEDED,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: 'seed_1',
|
||||||
|
threadId: SEEDED,
|
||||||
|
senderActorId: 'actor_other',
|
||||||
|
senderId: 'pp_other',
|
||||||
|
senderName: 'Other',
|
||||||
|
content: 'seeded message',
|
||||||
|
createdAt: new Date(0).toISOString(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
const handlers = new Map<string, Set<(...a: unknown[]) => void>>();
|
||||||
|
let seq = 1;
|
||||||
|
|
||||||
|
const fire = (event: string, payload: unknown): void => handlers.get(event)?.forEach((h) => h(payload));
|
||||||
|
|
||||||
|
return {
|
||||||
|
on(event, handler) {
|
||||||
|
if (!handlers.has(event)) handlers.set(event, new Set());
|
||||||
|
handlers.get(event)!.add(handler);
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
off(event, handler) {
|
||||||
|
handlers.get(event)?.delete(handler);
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
emit() {
|
||||||
|
return undefined; // typing/focus — no echo needed for conformance
|
||||||
|
},
|
||||||
|
async emitWithAck(event, payload) {
|
||||||
|
const p = (payload ?? {}) as {
|
||||||
|
threadId: string;
|
||||||
|
content?: string;
|
||||||
|
parentInteractionId?: string;
|
||||||
|
interactionId?: string;
|
||||||
|
type?: string;
|
||||||
|
value?: string;
|
||||||
|
};
|
||||||
|
if (event === 'open_thread') {
|
||||||
|
if (!threads.has(p.threadId)) threads.set(p.threadId, []);
|
||||||
|
return { threadId: p.threadId, status: 'OPEN', history: [...threads.get(p.threadId)!] };
|
||||||
|
}
|
||||||
|
if (event === 'send_message') {
|
||||||
|
const msg: KernelMessage = {
|
||||||
|
id: `m_${seq++}`,
|
||||||
|
threadId: p.threadId,
|
||||||
|
senderActorId: `actor_${ME}`,
|
||||||
|
senderId: ME,
|
||||||
|
senderName: ME,
|
||||||
|
content: p.content ?? '',
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
...(p.parentInteractionId ? { parentInteractionId: p.parentInteractionId } : {}),
|
||||||
|
};
|
||||||
|
if (!threads.has(p.threadId)) threads.set(p.threadId, []);
|
||||||
|
threads.get(p.threadId)!.push(msg);
|
||||||
|
fire('message', msg);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
if (event === 'read') return { ok: true };
|
||||||
|
if (event === 'annotate') {
|
||||||
|
fire('annotation', {
|
||||||
|
threadId: p.threadId,
|
||||||
|
interactionId: p.interactionId,
|
||||||
|
type: p.type,
|
||||||
|
value: p.value,
|
||||||
|
op: 'add',
|
||||||
|
users: [ME],
|
||||||
|
userId: ME,
|
||||||
|
});
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
connect() {
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
disconnect() {
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
connected: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeFakeRest(): RestPort {
|
||||||
|
let seq = 1;
|
||||||
|
return {
|
||||||
|
async listThreads() {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
async createThread() {
|
||||||
|
return { threadId: `th_new_${seq++}` };
|
||||||
|
},
|
||||||
|
async addParticipant() {
|
||||||
|
/* governed server-side; a fake always allows */
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeAdapter(): KernelClientAdapter {
|
||||||
|
const socket = new MessageSocket({ serviceUrl: 'http://iios.test', token: 'tok', autoConnect: false }, makeFakeSocket());
|
||||||
|
return new KernelClientAdapter({ currentUserId: ME, socket, rest: makeFakeRest() });
|
||||||
|
}
|
||||||
|
|
||||||
|
runAdapterConformance({ makeAdapter, seededThreadId: SEEDED, openWith: ['pp_a'] });
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
// Transport adapter: implements MessagingAdapter over @insignia/iios-kernel-client
|
||||||
|
// (browser → IIOS directly, token-in). This is the "plug into any app with a token"
|
||||||
|
// path — the same transport chat-web and support-sdk use.
|
||||||
|
//
|
||||||
|
// It lives in adapters/ (the ONLY layer allowed to import a transport) and ships from
|
||||||
|
// its own subpath, so core UI never pulls socket code it can't use.
|
||||||
|
|
||||||
|
import { MessageSocket, RestClient } from '@insignia/iios-kernel-client';
|
||||||
|
import type {
|
||||||
|
AnnotationEvent,
|
||||||
|
Message as KernelMessage,
|
||||||
|
MessageEvents,
|
||||||
|
OpenThreadResult,
|
||||||
|
ReceiptEvent,
|
||||||
|
ThreadSummary,
|
||||||
|
TypingEvent,
|
||||||
|
} from '@insignia/iios-kernel-client';
|
||||||
|
import type { MessagingAdapter } from '../adapter';
|
||||||
|
import type {
|
||||||
|
Conversation,
|
||||||
|
Membership,
|
||||||
|
Message,
|
||||||
|
MessageEvent,
|
||||||
|
Reaction,
|
||||||
|
SendOpts,
|
||||||
|
Unsubscribe,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
|
/** The slice of MessageSocket the adapter needs — the real facade satisfies it; tests inject a fake. */
|
||||||
|
export interface SocketPort {
|
||||||
|
openThread(threadId?: string, opts?: { membership?: string; creatorRole?: string; subject?: string }): Promise<OpenThreadResult>;
|
||||||
|
sendMessage(threadId: string, content: string, opts?: { parentInteractionId?: string; mentions?: string[] }): Promise<KernelMessage>;
|
||||||
|
react(threadId: string, interactionId: string, value: string): Promise<void>;
|
||||||
|
markRead(threadId: string, interactionId: string): Promise<{ ok: boolean }>;
|
||||||
|
typing(threadId: string): void;
|
||||||
|
on<E extends keyof MessageEvents>(event: E, handler: MessageEvents[E]): () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The slice of RestClient the adapter needs. */
|
||||||
|
export interface RestPort {
|
||||||
|
listThreads(filter?: { metadata?: Record<string, string> }): Promise<ThreadSummary[]>;
|
||||||
|
createThread(opts: { membership?: string; creatorRole?: string; subject?: string; metadata?: Record<string, unknown> }): Promise<{ threadId: string }>;
|
||||||
|
addParticipant(threadId: string, userId: string): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KernelClientAdapterConfig {
|
||||||
|
/**
|
||||||
|
* The current user's id in IIOS's `senderId` space (email/username), from the host's auth —
|
||||||
|
* NEVER inferred from message history. This is exactly `currentActorId()`, and it's the bug the
|
||||||
|
* conformance suite kills: identity comes from the session, not from a message you happened to send.
|
||||||
|
*/
|
||||||
|
currentUserId: string;
|
||||||
|
socket: SocketPort;
|
||||||
|
rest: RestPort;
|
||||||
|
/** Optional opaque metadata filter for the conversation list (e.g. { source: 'crm-messenger' }). */
|
||||||
|
threadFilter?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const REACTION = 'reaction';
|
||||||
|
|
||||||
|
export class KernelClientAdapter implements MessagingAdapter {
|
||||||
|
private readonly me: string;
|
||||||
|
private readonly socket: SocketPort;
|
||||||
|
private readonly rest: RestPort;
|
||||||
|
private readonly threadFilter?: Record<string, string>;
|
||||||
|
|
||||||
|
/** Per-thread UI subscribers. The socket fans server events in; these fan them out. */
|
||||||
|
private readonly listeners = new Map<string, Set<(e: MessageEvent) => void>>();
|
||||||
|
/** Reaction users per message (messageId → emoji → userSet), so an annotation delta becomes a full set. */
|
||||||
|
private readonly reactions = new Map<string, Map<string, Set<string>>>();
|
||||||
|
private readonly joined = new Set<string>();
|
||||||
|
private readonly offs: Array<() => void> = [];
|
||||||
|
|
||||||
|
constructor(cfg: KernelClientAdapterConfig) {
|
||||||
|
this.me = cfg.currentUserId;
|
||||||
|
this.socket = cfg.socket;
|
||||||
|
this.rest = cfg.rest;
|
||||||
|
this.threadFilter = cfg.threadFilter;
|
||||||
|
|
||||||
|
this.offs.push(
|
||||||
|
this.socket.on('message', (m: KernelMessage) => {
|
||||||
|
this.ingestReactions(m);
|
||||||
|
this.emit(m.threadId, { kind: 'message', message: this.toMessage(m) });
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
this.offs.push(
|
||||||
|
this.socket.on('typing', (e: TypingEvent) => this.emit(e.threadId, { kind: 'typing', userId: e.userId })),
|
||||||
|
);
|
||||||
|
this.offs.push(
|
||||||
|
// Receipts carry no threadId, so fan to every open thread; the UI filters by messageId.
|
||||||
|
this.socket.on('receipt', (e: ReceiptEvent) => this.broadcast({ kind: 'receipt', messageId: e.interactionId, actorId: e.actorId })),
|
||||||
|
);
|
||||||
|
this.offs.push(
|
||||||
|
this.socket.on('annotation', (e: AnnotationEvent) => {
|
||||||
|
if (e.type !== REACTION) return;
|
||||||
|
this.setReactionUsers(e.interactionId, e.value, e.users);
|
||||||
|
this.emit(e.threadId, { kind: 'reaction', messageId: e.interactionId, reactions: this.reactionsOf(e.interactionId) });
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentActorId(): string {
|
||||||
|
return this.me;
|
||||||
|
}
|
||||||
|
|
||||||
|
async listConversations(): Promise<Conversation[]> {
|
||||||
|
const threads = await this.rest.listThreads(this.threadFilter ? { metadata: this.threadFilter } : undefined);
|
||||||
|
return threads.map((t) => this.toConversation(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
async openThread(p: { participantIds: string[]; membership?: Membership; subject?: string }): Promise<{ threadId: string }> {
|
||||||
|
const membership: Membership = p.membership ?? (p.participantIds.length === 1 ? 'dm' : 'group');
|
||||||
|
const { threadId } = await this.rest.createThread({
|
||||||
|
membership,
|
||||||
|
creatorRole: membership === 'group' ? 'ADMIN' : 'MEMBER',
|
||||||
|
...(p.subject ? { subject: p.subject } : {}),
|
||||||
|
});
|
||||||
|
// Governance (DM cap, roles) is enforced server-side by IIOS/OPA; a rejected add surfaces up there.
|
||||||
|
for (const id of p.participantIds) await this.rest.addParticipant(threadId, id).catch(() => undefined);
|
||||||
|
return { threadId };
|
||||||
|
}
|
||||||
|
|
||||||
|
async history(threadId: string): Promise<Message[]> {
|
||||||
|
const res = await this.socket.openThread(threadId); // joins the thread, so live events start flowing
|
||||||
|
this.joined.add(threadId);
|
||||||
|
return res.history.map((m) => {
|
||||||
|
this.ingestReactions(m);
|
||||||
|
return this.toMessage(m);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async send(threadId: string, content: string, opts?: SendOpts): Promise<Message> {
|
||||||
|
// Attachments are intentionally not forwarded here: kernel-client exposes no media/presign yet,
|
||||||
|
// and the SDK Attachment carries a display `url`, not a storage `contentRef`. Media is a follow-up
|
||||||
|
// (kernel-client media methods + a contentRef on Attachment). Text + reply threading work today.
|
||||||
|
const m = await this.socket.sendMessage(
|
||||||
|
threadId,
|
||||||
|
content,
|
||||||
|
opts?.parentInteractionId ? { parentInteractionId: opts.parentInteractionId } : undefined,
|
||||||
|
);
|
||||||
|
return this.toMessage(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
async react(threadId: string, messageId: string, emoji: string): Promise<void> {
|
||||||
|
await this.socket.react(threadId, messageId, emoji);
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(threadId: string, cb: (e: MessageEvent) => void): Unsubscribe {
|
||||||
|
if (!this.listeners.has(threadId)) this.listeners.set(threadId, new Set());
|
||||||
|
this.listeners.get(threadId)!.add(cb);
|
||||||
|
if (!this.joined.has(threadId)) {
|
||||||
|
this.joined.add(threadId);
|
||||||
|
void this.socket.openThread(threadId).catch(() => this.joined.delete(threadId));
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
this.listeners.get(threadId)?.delete(cb);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
sendTyping(threadId: string): void {
|
||||||
|
this.socket.typing(threadId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async markRead(threadId: string, messageId: string): Promise<void> {
|
||||||
|
await this.socket.markRead(threadId, messageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Detach socket handlers. Not part of the contract — call on teardown to avoid leaks. */
|
||||||
|
close(): void {
|
||||||
|
for (const off of this.offs) off();
|
||||||
|
this.offs.length = 0;
|
||||||
|
this.listeners.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── mapping ────────────────────────────────────────────────────
|
||||||
|
private toMessage(m: KernelMessage): Message {
|
||||||
|
return {
|
||||||
|
id: m.id,
|
||||||
|
// `senderId` (email/username), NOT the actor id — it matches currentActorId() and is the
|
||||||
|
// reliable "is this mine?" field. Never inferred from history.
|
||||||
|
actorId: m.senderId ?? null,
|
||||||
|
text: m.content ?? '',
|
||||||
|
at: m.createdAt,
|
||||||
|
parentInteractionId: m.parentInteractionId ?? null,
|
||||||
|
reactions: this.reactionsOf(m.id),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private toConversation(t: ThreadSummary): Conversation {
|
||||||
|
const others = t.participants.filter((p) => p !== this.me);
|
||||||
|
const membership: Membership | null = t.membership === 'dm' || t.membership === 'group' ? t.membership : null;
|
||||||
|
return {
|
||||||
|
threadId: t.threadId,
|
||||||
|
title: t.subject?.trim() || others.join(', ') || 'Conversation',
|
||||||
|
subject: t.subject,
|
||||||
|
membership,
|
||||||
|
participants: [...t.participants],
|
||||||
|
unread: t.unread,
|
||||||
|
...(t.lastMessage ? { lastMessage: t.lastMessage } : {}),
|
||||||
|
...(t.lastAt ? { lastAt: t.lastAt } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── reaction state ─────────────────────────────────────────────
|
||||||
|
private ingestReactions(m: KernelMessage): void {
|
||||||
|
for (const a of m.annotations ?? []) {
|
||||||
|
if (a.type === REACTION) this.setReactionUsers(m.id, a.value, a.users);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setReactionUsers(messageId: string, emoji: string, users: string[]): void {
|
||||||
|
let byEmoji = this.reactions.get(messageId);
|
||||||
|
if (!byEmoji) {
|
||||||
|
byEmoji = new Map();
|
||||||
|
this.reactions.set(messageId, byEmoji);
|
||||||
|
}
|
||||||
|
if (users.length === 0) byEmoji.delete(emoji);
|
||||||
|
else byEmoji.set(emoji, new Set(users));
|
||||||
|
}
|
||||||
|
|
||||||
|
private reactionsOf(messageId: string): Reaction[] {
|
||||||
|
const byEmoji = this.reactions.get(messageId);
|
||||||
|
if (!byEmoji) return [];
|
||||||
|
const out: Reaction[] = [];
|
||||||
|
for (const [emoji, users] of byEmoji) {
|
||||||
|
if (users.size > 0) out.push({ emoji, count: users.size, mine: users.has(this.me) });
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── event fan-out ──────────────────────────────────────────────
|
||||||
|
private emit(threadId: string, e: MessageEvent): void {
|
||||||
|
this.listeners.get(threadId)?.forEach((cb) => cb(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
private broadcast(e: MessageEvent): void {
|
||||||
|
for (const set of this.listeners.values()) set.forEach((cb) => cb(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convenience: build an adapter that talks straight to IIOS with a token. */
|
||||||
|
export function connectKernelAdapter(opts: {
|
||||||
|
serviceUrl: string;
|
||||||
|
token: string;
|
||||||
|
currentUserId: string;
|
||||||
|
threadFilter?: Record<string, string>;
|
||||||
|
autoConnect?: boolean;
|
||||||
|
}): KernelClientAdapter {
|
||||||
|
const rest = new RestClient({ serviceUrl: opts.serviceUrl, token: opts.token });
|
||||||
|
const socket = new MessageSocket({ serviceUrl: opts.serviceUrl, token: opts.token, autoConnect: opts.autoConnect ?? true });
|
||||||
|
return new KernelClientAdapter({
|
||||||
|
currentUserId: opts.currentUserId,
|
||||||
|
socket,
|
||||||
|
rest,
|
||||||
|
...(opts.threadFilter ? { threadFilter: opts.threadFilter } : {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -6,9 +6,9 @@ export default defineConfig({
|
|||||||
// entry, never reachable from `index`, because it imports vitest, and bundling
|
// entry, never reachable from `index`, because it imports vitest, and bundling
|
||||||
// that into the main barrel would drag a test runner into every consumer's
|
// that into the main barrel would drag a test runner into every consumer's
|
||||||
// production build.
|
// production build.
|
||||||
entry: ['src/index.ts', 'src/adapters/mock.ts', 'src/conformance.ts'],
|
entry: ['src/index.ts', 'src/adapters/mock.ts', 'src/adapters/kernel-client.ts', 'src/conformance.ts'],
|
||||||
format: ['esm'],
|
format: ['esm'],
|
||||||
dts: true,
|
dts: true,
|
||||||
clean: true,
|
clean: true,
|
||||||
external: ['react', 'react-dom', 'vitest'],
|
external: ['react', 'react-dom', 'vitest', '@insignia/iios-kernel-client'],
|
||||||
});
|
});
|
||||||
|
|||||||
Generated
+14
-11
@@ -328,6 +328,9 @@ importers:
|
|||||||
|
|
||||||
packages/iios-messaging-ui:
|
packages/iios-messaging-ui:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@insignia/iios-kernel-client':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../iios-kernel-client
|
||||||
'@testing-library/dom':
|
'@testing-library/dom':
|
||||||
specifier: ^10.4.0
|
specifier: ^10.4.0
|
||||||
version: 10.4.1
|
version: 10.4.1
|
||||||
@@ -520,6 +523,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-/PZmyAlb2NGWPikRRuiWLdfHQd8Wrx6lX4HqvTcaDhlU43M3T0ud4PH2T3QDp7BzHYY92xtD8iPxX2asg67G1A==}
|
resolution: {integrity: sha512-/PZmyAlb2NGWPikRRuiWLdfHQd8Wrx6lX4HqvTcaDhlU43M3T0ud4PH2T3QDp7BzHYY92xtD8iPxX2asg67G1A==}
|
||||||
engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
|
engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
|
||||||
|
|
||||||
|
'@asamuzakjp/css-color@3.2.0':
|
||||||
|
resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==}
|
||||||
|
|
||||||
'@aws-sdk/checksums@3.1000.18':
|
'@aws-sdk/checksums@3.1000.18':
|
||||||
resolution: {integrity: sha512-IImkbEyXdV6/uaF5r6Wkk+8718mQw1ll83j0a4a30R3JM/rHVFdWAiT4jtJpFjJiIwM/oJ6SxIxr0z2TaQUGqw==}
|
resolution: {integrity: sha512-IImkbEyXdV6/uaF5r6Wkk+8718mQw1ll83j0a4a30R3JM/rHVFdWAiT4jtJpFjJiIwM/oJ6SxIxr0z2TaQUGqw==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
@@ -592,9 +598,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==}
|
resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@asamuzakjp/css-color@3.2.0':
|
|
||||||
resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==}
|
|
||||||
|
|
||||||
'@babel/code-frame@7.29.7':
|
'@babel/code-frame@7.29.7':
|
||||||
resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
|
resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -3677,6 +3680,14 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- chokidar
|
- chokidar
|
||||||
|
|
||||||
|
'@asamuzakjp/css-color@3.2.0':
|
||||||
|
dependencies:
|
||||||
|
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||||
|
'@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
||||||
|
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||||
|
'@csstools/css-tokenizer': 3.0.4
|
||||||
|
lru-cache: 10.4.3
|
||||||
|
|
||||||
'@aws-sdk/checksums@3.1000.18':
|
'@aws-sdk/checksums@3.1000.18':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@aws-sdk/core': 3.975.3
|
'@aws-sdk/core': 3.975.3
|
||||||
@@ -3842,14 +3853,6 @@ snapshots:
|
|||||||
|
|
||||||
'@aws/lambda-invoke-store@0.3.0': {}
|
'@aws/lambda-invoke-store@0.3.0': {}
|
||||||
|
|
||||||
'@asamuzakjp/css-color@3.2.0':
|
|
||||||
dependencies:
|
|
||||||
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
|
||||||
'@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
|
|
||||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
|
||||||
'@csstools/css-tokenizer': 3.0.4
|
|
||||||
lru-cache: 10.4.3
|
|
||||||
|
|
||||||
'@babel/code-frame@7.29.7':
|
'@babel/code-frame@7.29.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/helper-validator-identifier': 7.29.7
|
'@babel/helper-validator-identifier': 7.29.7
|
||||||
|
|||||||
Reference in New Issue
Block a user