diff --git a/.changeset/add-dynamodb-state-adapter.md b/.changeset/add-dynamodb-state-adapter.md new file mode 100644 index 00000000..a463183e --- /dev/null +++ b/.changeset/add-dynamodb-state-adapter.md @@ -0,0 +1,5 @@ +--- +"@chat-adapter/state-dynamodb": minor +--- + +Add DynamoDB state adapter for serverless deployments diff --git a/packages/state-dynamodb/package.json b/packages/state-dynamodb/package.json new file mode 100644 index 00000000..1e57a27b --- /dev/null +++ b/packages/state-dynamodb/package.json @@ -0,0 +1,62 @@ +{ + "name": "@chat-adapter/state-dynamodb", + "version": "0.0.0", + "description": "DynamoDB state adapter for chat (production)", + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "test": "vitest run --coverage", + "test:watch": "vitest", + "typecheck": "tsc --noEmit", + "clean": "rm -rf dist" + }, + "dependencies": { + "chat": "workspace:*" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.0.0", + "@aws-sdk/lib-dynamodb": "^3.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vercel/chat.git", + "directory": "packages/state-dynamodb" + }, + "homepage": "https://github.com/vercel/chat#readme", + "bugs": { + "url": "https://github.com/vercel/chat/issues" + }, + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "@aws-sdk/client-dynamodb": "^3.750.0", + "@aws-sdk/lib-dynamodb": "^3.750.0", + "@types/node": "^25.3.2", + "@vitest/coverage-v8": "^4.0.18", + "tsup": "^8.3.5", + "typescript": "^5.7.2", + "vitest": "^4.0.18" + }, + "keywords": [ + "chat", + "state", + "dynamodb", + "production", + "serverless" + ], + "license": "MIT" +} diff --git a/packages/state-dynamodb/src/index.test.ts b/packages/state-dynamodb/src/index.test.ts new file mode 100644 index 00000000..524f62eb --- /dev/null +++ b/packages/state-dynamodb/src/index.test.ts @@ -0,0 +1,789 @@ +import { ConditionalCheckFailedException } from "@aws-sdk/client-dynamodb"; +import type { DynamoDBDocument } from "@aws-sdk/lib-dynamodb"; +import type { Lock, Logger } from "chat"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { createDynamoDBState, DynamoDBStateAdapter } from "./index"; + +function createMockDocClient(): DynamoDBDocument { + return { + put: vi.fn().mockResolvedValue({}), + get: vi.fn().mockResolvedValue({}), + delete: vi.fn().mockResolvedValue({}), + update: vi.fn().mockResolvedValue({}), + query: vi.fn().mockResolvedValue({ Items: [] }), + batchWrite: vi.fn().mockResolvedValue({}), + destroy: vi.fn(), + } as unknown as DynamoDBDocument; +} + +function createMockLogger(): Logger { + return { + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + child: vi.fn().mockReturnThis(), + } as unknown as Logger; +} + +describe("DynamoDBStateAdapter", () => { + it("should export createDynamoDBState function", () => { + expect(typeof createDynamoDBState).toBe("function"); + }); + + it("should export DynamoDBStateAdapter class", () => { + expect(typeof DynamoDBStateAdapter).toBe("function"); + }); + + describe("createDynamoDBState", () => { + it("should create an adapter with default options", () => { + const adapter = createDynamoDBState({ client: createMockDocClient() }); + expect(adapter).toBeInstanceOf(DynamoDBStateAdapter); + }); + + it("should create an adapter with custom options", () => { + const adapter = createDynamoDBState({ + client: createMockDocClient(), + tableName: "custom-table", + keyPrefix: "custom-prefix", + }); + expect(adapter).toBeInstanceOf(DynamoDBStateAdapter); + }); + + it("should create an adapter without a client", () => { + const adapter = createDynamoDBState({ region: "us-east-1" }); + expect(adapter).toBeInstanceOf(DynamoDBStateAdapter); + }); + }); + + describe("ensureConnected", () => { + it("should throw when calling subscribe before connect", async () => { + const adapter = new DynamoDBStateAdapter({ + client: createMockDocClient(), + }); + await expect(adapter.subscribe("thread1")).rejects.toThrow( + "not connected" + ); + }); + + it("should throw when calling acquireLock before connect", async () => { + const adapter = new DynamoDBStateAdapter({ + client: createMockDocClient(), + }); + await expect(adapter.acquireLock("thread1", 5000)).rejects.toThrow( + "not connected" + ); + }); + + it("should throw when calling get before connect", async () => { + const adapter = new DynamoDBStateAdapter({ + client: createMockDocClient(), + }); + await expect(adapter.get("key")).rejects.toThrow("not connected"); + }); + + it("should throw when calling set before connect", async () => { + const adapter = new DynamoDBStateAdapter({ + client: createMockDocClient(), + }); + await expect(adapter.set("key", "value")).rejects.toThrow( + "not connected" + ); + }); + + it("should throw when calling delete before connect", async () => { + const adapter = new DynamoDBStateAdapter({ + client: createMockDocClient(), + }); + await expect(adapter.delete("key")).rejects.toThrow("not connected"); + }); + }); + + describe("with mock client", () => { + let adapter: DynamoDBStateAdapter; + let client: DynamoDBDocument; + let logger: Logger; + + beforeEach(async () => { + client = createMockDocClient(); + logger = createMockLogger(); + adapter = new DynamoDBStateAdapter({ client, logger }); + await adapter.connect(); + }); + + afterEach(async () => { + await adapter.disconnect(); + }); + + describe("connect / disconnect", () => { + it("should be idempotent on connect", async () => { + await adapter.connect(); + await adapter.connect(); + }); + + it("should be idempotent on disconnect", async () => { + await adapter.disconnect(); + await adapter.disconnect(); + }); + }); + + describe("subscriptions", () => { + it("should subscribe by calling put", async () => { + await adapter.subscribe("slack:C123:1234.5678"); + + expect(client.put).toHaveBeenCalledWith( + expect.objectContaining({ + Item: { pk: "chat-sdk#sub#slack:C123:1234.5678", sk: "_" }, + }) + ); + }); + + it("should unsubscribe by calling delete", async () => { + await adapter.unsubscribe("slack:C123:1234.5678"); + + expect(client.delete).toHaveBeenCalledWith( + expect.objectContaining({ + Key: { pk: "chat-sdk#sub#slack:C123:1234.5678", sk: "_" }, + }) + ); + }); + + it("should return true when subscribed", async () => { + vi.mocked(client.get).mockResolvedValue({ + Item: { pk: "chat-sdk#sub#thread1", sk: "_" }, + $metadata: {}, + }); + + const result = await adapter.isSubscribed("thread1"); + expect(result).toBe(true); + }); + + it("should return false when not subscribed", async () => { + vi.mocked(client.get).mockResolvedValue({ $metadata: {} }); + + const result = await adapter.isSubscribed("thread1"); + expect(result).toBe(false); + }); + }); + + describe("locking", () => { + it("should acquire a lock successfully", async () => { + const lock = await adapter.acquireLock("thread1", 5000); + expect(lock).not.toBeNull(); + expect(lock?.threadId).toBe("thread1"); + expect(lock?.token?.startsWith("ddb_")).toBe(true); + expect(lock?.expiresAt).toBeGreaterThan(Date.now()); + }); + + it("should return null when lock is already held", async () => { + vi.mocked(client.put).mockRejectedValue( + new ConditionalCheckFailedException({ + message: "Condition not met", + $metadata: {}, + }) + ); + + const lock = await adapter.acquireLock("thread1", 5000); + expect(lock).toBeNull(); + }); + + it("should release a lock with token check", async () => { + const lock: Lock = { + threadId: "thread1", + token: "ddb_test-token", + expiresAt: Date.now() + 5000, + }; + await adapter.releaseLock(lock); + + expect(client.delete).toHaveBeenCalledWith( + expect.objectContaining({ + ConditionExpression: "#t = :token", + ExpressionAttributeValues: { ":token": "ddb_test-token" }, + }) + ); + }); + + it("should no-op when releasing with wrong token", async () => { + vi.mocked(client.delete).mockRejectedValue( + new ConditionalCheckFailedException({ + message: "Condition not met", + $metadata: {}, + }) + ); + + const lock: Lock = { + threadId: "thread1", + token: "wrong-token", + expiresAt: Date.now() + 5000, + }; + await adapter.releaseLock(lock); + }); + + it("should force-release a lock without token check", async () => { + await adapter.forceReleaseLock("thread1"); + + expect(client.delete).toHaveBeenCalledWith( + expect.not.objectContaining({ + ConditionExpression: expect.anything(), + }) + ); + }); + + it("should return true when lock is extended", async () => { + const lock: Lock = { + threadId: "thread1", + token: "ddb_test-token", + expiresAt: Date.now() + 5000, + }; + const result = await adapter.extendLock(lock, 5000); + expect(result).toBe(true); + }); + + it("should return false when lock extension fails", async () => { + vi.mocked(client.update).mockRejectedValue( + new ConditionalCheckFailedException({ + message: "Condition not met", + $metadata: {}, + }) + ); + + const lock: Lock = { + threadId: "thread1", + token: "ddb_test-token", + expiresAt: Date.now() + 5000, + }; + const result = await adapter.extendLock(lock, 5000); + expect(result).toBe(false); + }); + }); + + describe("cache", () => { + it("should return value on cache hit", async () => { + vi.mocked(client.get).mockResolvedValue({ + Item: { pk: "x", sk: "_", value: { foo: "bar" } }, + $metadata: {}, + }); + + const result = await adapter.get("key"); + expect(result).toEqual({ foo: "bar" }); + }); + + it("should return raw value for non-object types", async () => { + vi.mocked(client.get).mockResolvedValue({ + Item: { pk: "x", sk: "_", value: "plain-string" }, + $metadata: {}, + }); + + const result = await adapter.get("key"); + expect(result).toBe("plain-string"); + }); + + it("should return null on cache miss", async () => { + vi.mocked(client.get).mockResolvedValue({ $metadata: {} }); + + const result = await adapter.get("key"); + expect(result).toBeNull(); + }); + + it("should return null for expired items", async () => { + vi.mocked(client.get).mockResolvedValue({ + Item: { + pk: "x", + sk: "_", + value: { foo: "bar" }, + expiresAtMs: Date.now() - 1000, + }, + $metadata: {}, + }); + + const result = await adapter.get("key"); + expect(result).toBeNull(); + }); + + it("should set a value with correct key format", async () => { + await adapter.set("my-key", { foo: "bar" }); + + expect(client.put).toHaveBeenCalledWith( + expect.objectContaining({ + Item: expect.objectContaining({ + pk: "chat-sdk#cache#my-key", + value: { foo: "bar" }, + }), + }) + ); + }); + + it("should set a value with TTL", async () => { + await adapter.set("key", "value", 5000); + + expect(client.put).toHaveBeenCalledWith( + expect.objectContaining({ + Item: expect.objectContaining({ + expiresAtMs: expect.any(Number), + expiresAt: expect.any(Number), + }), + }) + ); + }); + + it("should return true when setIfNotExists succeeds", async () => { + const result = await adapter.setIfNotExists("key", "value"); + expect(result).toBe(true); + }); + + it("should return false when setIfNotExists finds existing key", async () => { + vi.mocked(client.put).mockRejectedValue( + new ConditionalCheckFailedException({ + message: "Condition not met", + $metadata: {}, + }) + ); + + const result = await adapter.setIfNotExists("key", "value"); + expect(result).toBe(false); + }); + + it("should support setIfNotExists with TTL", async () => { + const result = await adapter.setIfNotExists("key", "value", 5000); + expect(result).toBe(true); + }); + + it("should delete a value without throwing", async () => { + await adapter.delete("key"); + expect(client.delete).toHaveBeenCalled(); + }); + }); + + describe("appendToList / getList", () => { + it("should increment counter and write list entry", async () => { + vi.mocked(client.update).mockResolvedValue({ + Attributes: { seq: 1 }, + $metadata: {}, + }); + + await adapter.appendToList("mylist", { foo: "bar" }); + + expect(client.update).toHaveBeenCalledWith( + expect.objectContaining({ + Key: expect.objectContaining({ + pk: "chat-sdk#list-counter#mylist", + }), + UpdateExpression: "ADD seq :one", + }) + ); + + expect(client.put).toHaveBeenCalledWith( + expect.objectContaining({ + Item: expect.objectContaining({ + pk: "chat-sdk#list#mylist", + sk: "0000000000000001", + value: { foo: "bar" }, + }), + }) + ); + }); + + it("should trim overflow when maxLength is specified", async () => { + vi.mocked(client.update).mockResolvedValue({ + Attributes: { seq: 4 }, + $metadata: {}, + }); + vi.mocked(client.query).mockResolvedValue({ + Items: [ + { sk: "0000000000000001" }, + { sk: "0000000000000002" }, + { sk: "0000000000000003" }, + { sk: "0000000000000004" }, + ], + $metadata: {}, + }); + + await adapter.appendToList("mylist", { id: 4 }, { maxLength: 2 }); + + expect(client.batchWrite).toHaveBeenCalledWith( + expect.objectContaining({ + RequestItems: { + "chat-state": expect.arrayContaining([ + { + DeleteRequest: { + Key: { pk: "chat-sdk#list#mylist", sk: "0000000000000001" }, + }, + }, + { + DeleteRequest: { + Key: { pk: "chat-sdk#list#mylist", sk: "0000000000000002" }, + }, + }, + ]), + }, + }) + ); + }); + + it("should set TTL on list entries when ttlMs is provided", async () => { + vi.mocked(client.update).mockResolvedValue({ + Attributes: { seq: 1 }, + $metadata: {}, + }); + + await adapter.appendToList("mylist", { id: 1 }, { ttlMs: 60000 }); + + expect(client.put).toHaveBeenCalledWith( + expect.objectContaining({ + Item: expect.objectContaining({ + expiresAtMs: expect.any(Number), + expiresAt: expect.any(Number), + }), + }) + ); + }); + + it("should return list items from getList in order", async () => { + vi.mocked(client.query).mockResolvedValue({ + Items: [ + { sk: "0000000000000001", value: { id: 1 } }, + { sk: "0000000000000002", value: { id: 2 } }, + ], + $metadata: {}, + }); + + const result = await adapter.getList("mylist"); + expect(result).toEqual([{ id: 1 }, { id: 2 }]); + }); + + it("should filter expired items in getList", async () => { + vi.mocked(client.query).mockResolvedValue({ + Items: [ + { + sk: "0000000000000001", + value: { id: 1 }, + expiresAtMs: Date.now() - 1000, + }, + { sk: "0000000000000002", value: { id: 2 } }, + ], + $metadata: {}, + }); + + const result = await adapter.getList("mylist"); + expect(result).toEqual([{ id: 2 }]); + }); + + it("should return empty array when no items exist", async () => { + vi.mocked(client.query).mockResolvedValue({ Items: [], $metadata: {} }); + + const result = await adapter.getList("mylist"); + expect(result).toEqual([]); + }); + + it("should handle paginated query results in getList", async () => { + vi.mocked(client.query) + .mockResolvedValueOnce({ + Items: [{ sk: "0000000000000001", value: { id: 1 } }], + LastEvaluatedKey: { pk: "x", sk: "0000000000000001" }, + $metadata: {}, + }) + .mockResolvedValueOnce({ + Items: [{ sk: "0000000000000002", value: { id: 2 } }], + $metadata: {}, + }); + + const result = await adapter.getList("mylist"); + expect(result).toEqual([{ id: 1 }, { id: 2 }]); + }); + }); + + describe("disconnect", () => { + it("should call destroy on owned client", async () => { + const ownedAdapter = createDynamoDBState({ + region: "us-east-1", + logger, + }); + await ownedAdapter.connect(); + const ownedClient = ownedAdapter.getClient(); + vi.spyOn(ownedClient, "destroy").mockImplementation(() => {}); + await ownedAdapter.disconnect(); + expect(ownedClient.destroy).toHaveBeenCalled(); + }); + + it("should not call destroy on externally provided client", async () => { + await adapter.disconnect(); + expect(client.destroy).not.toHaveBeenCalled(); + }); + }); + + describe("trimList logging", () => { + it("should log a warning when batchWrite has unprocessed items", async () => { + vi.mocked(client.update).mockResolvedValue({ + Attributes: { seq: 4 }, + $metadata: {}, + }); + vi.mocked(client.query).mockResolvedValue({ + Items: [ + { sk: "0000000000000001" }, + { sk: "0000000000000002" }, + { sk: "0000000000000003" }, + { sk: "0000000000000004" }, + ], + $metadata: {}, + }); + vi.mocked(client.batchWrite).mockResolvedValue({ + UnprocessedItems: { + "chat-state": [ + { + DeleteRequest: { + Key: { + pk: "chat-sdk#list#mylist", + sk: "0000000000000001", + }, + }, + }, + ], + }, + $metadata: {}, + }); + + await adapter.appendToList("mylist", { id: 4 }, { maxLength: 2 }); + + expect(logger.warn).toHaveBeenCalledWith( + expect.stringContaining("unprocessed deletes") + ); + }); + }); + + describe("appendToList TTL refresh", () => { + it("should refresh TTL on all existing list items via batchWrite", async () => { + vi.mocked(client.update).mockResolvedValue({ + Attributes: { seq: 2 }, + $metadata: {}, + }); + vi.mocked(client.query).mockResolvedValue({ + Items: [ + { sk: "0000000000000001", value: { id: 1 } }, + { sk: "0000000000000002", value: { id: 2 } }, + ], + $metadata: {}, + }); + + await adapter.appendToList("mylist", { id: 2 }, { ttlMs: 60000 }); + + expect(client.batchWrite).toHaveBeenCalledWith( + expect.objectContaining({ + RequestItems: { + "chat-state": expect.arrayContaining([ + { + PutRequest: { + Item: expect.objectContaining({ + sk: "0000000000000001", + value: { id: 1 }, + expiresAtMs: expect.any(Number), + expiresAt: expect.any(Number), + }), + }, + }, + { + PutRequest: { + Item: expect.objectContaining({ + sk: "0000000000000002", + value: { id: 2 }, + expiresAtMs: expect.any(Number), + expiresAt: expect.any(Number), + }), + }, + }, + ]), + }, + }) + ); + }); + + it("should not refresh TTL when ttlMs is not provided", async () => { + vi.mocked(client.update).mockResolvedValue({ + Attributes: { seq: 1 }, + $metadata: {}, + }); + + await adapter.appendToList("mylist", { id: 1 }); + + // Only the counter update, no batchWrite for TTL refresh + expect(client.update).toHaveBeenCalledTimes(1); + expect(client.batchWrite).not.toHaveBeenCalled(); + }); + }); + + describe("getClient", () => { + it("should return the underlying DynamoDB Document client", () => { + expect(adapter.getClient()).toBe(client); + }); + }); + }); + + describe("custom attribute names", () => { + let adapter: DynamoDBStateAdapter; + let client: DynamoDBDocument; + + beforeEach(async () => { + client = createMockDocClient(); + adapter = new DynamoDBStateAdapter({ + client, + pkName: "PK", + skName: "SK", + ttlName: "ttl", + logger: createMockLogger(), + }); + await adapter.connect(); + }); + + afterEach(async () => { + await adapter.disconnect(); + }); + + it("should use custom pk and sk names in subscribe", async () => { + await adapter.subscribe("thread1"); + + expect(client.put).toHaveBeenCalledWith( + expect.objectContaining({ + Item: { PK: "chat-sdk#sub#thread1", SK: "_" }, + }) + ); + }); + + it("should use custom pk and sk names in key lookups", async () => { + vi.mocked(client.get).mockResolvedValue({ + Item: { PK: "chat-sdk#sub#thread1", SK: "_" }, + $metadata: {}, + }); + + const result = await adapter.isSubscribed("thread1"); + expect(result).toBe(true); + expect(client.get).toHaveBeenCalledWith( + expect.objectContaining({ + Key: { PK: "chat-sdk#sub#thread1", SK: "_" }, + ProjectionExpression: "#pk", + ExpressionAttributeNames: { "#pk": "PK" }, + }) + ); + }); + + it("should use custom ttl name in acquireLock", async () => { + const lock = await adapter.acquireLock("thread1", 5000); + expect(lock).not.toBeNull(); + + expect(client.put).toHaveBeenCalledWith( + expect.objectContaining({ + Item: expect.objectContaining({ + PK: expect.any(String), + SK: "_", + ttl: expect.any(Number), + }), + }) + ); + }); + + it("should use custom ttl name in cache set", async () => { + await adapter.set("key", "value", 5000); + + expect(client.put).toHaveBeenCalledWith( + expect.objectContaining({ + Item: expect.objectContaining({ + PK: "chat-sdk#cache#key", + SK: "_", + ttl: expect.any(Number), + }), + }) + ); + }); + + it("should use custom sk name in trimList", async () => { + vi.mocked(client.update).mockResolvedValue({ + Attributes: { seq: 3 }, + $metadata: {}, + }); + vi.mocked(client.query).mockResolvedValue({ + Items: [ + { SK: "0000000000000001" }, + { SK: "0000000000000002" }, + { SK: "0000000000000003" }, + ], + $metadata: {}, + }); + + await adapter.appendToList("mylist", { id: 3 }, { maxLength: 1 }); + + expect(client.batchWrite).toHaveBeenCalledWith( + expect.objectContaining({ + RequestItems: { + "chat-state": expect.arrayContaining([ + { + DeleteRequest: { + Key: { PK: "chat-sdk#list#mylist", SK: "0000000000000001" }, + }, + }, + { + DeleteRequest: { + Key: { PK: "chat-sdk#list#mylist", SK: "0000000000000002" }, + }, + }, + ]), + }, + }) + ); + }); + + it("should return list items with custom key names in getList", async () => { + vi.mocked(client.query).mockResolvedValue({ + Items: [ + { + PK: "chat-sdk#list#mylist", + SK: "0000000000000001", + value: { id: 1 }, + }, + { + PK: "chat-sdk#list#mylist", + SK: "0000000000000002", + value: { id: 2 }, + }, + ], + $metadata: {}, + }); + + const result = await adapter.getList("mylist"); + expect(result).toEqual([{ id: 1 }, { id: 2 }]); + }); + + it("should use custom ttl name in refreshListTtl", async () => { + vi.mocked(client.update).mockResolvedValue({ + Attributes: { seq: 1 }, + $metadata: {}, + }); + vi.mocked(client.query).mockResolvedValue({ + Items: [ + { + PK: "chat-sdk#list#mylist", + SK: "0000000000000001", + value: { id: 1 }, + }, + ], + $metadata: {}, + }); + + await adapter.appendToList("mylist", { id: 1 }, { ttlMs: 60000 }); + + expect(client.batchWrite).toHaveBeenCalledWith( + expect.objectContaining({ + RequestItems: { + "chat-state": expect.arrayContaining([ + { + PutRequest: { + Item: expect.objectContaining({ + ttl: expect.any(Number), + expiresAtMs: expect.any(Number), + }), + }, + }, + ]), + }, + }) + ); + }); + }); +}); diff --git a/packages/state-dynamodb/src/index.ts b/packages/state-dynamodb/src/index.ts new file mode 100644 index 00000000..2f0292d3 --- /dev/null +++ b/packages/state-dynamodb/src/index.ts @@ -0,0 +1,543 @@ +import { + ConditionalCheckFailedException, + DynamoDBClient, +} from "@aws-sdk/client-dynamodb"; +import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb"; +import type { Lock, Logger, StateAdapter } from "chat"; +import { ConsoleLogger } from "chat"; + +const DEFAULT_TABLE_NAME = "chat-state"; +const DEFAULT_KEY_PREFIX = "chat-sdk"; +const DEFAULT_PK_NAME = "pk"; +const DEFAULT_SK_NAME = "sk"; +const DEFAULT_TTL_NAME = "expiresAt"; + +const SEQ_PAD_LENGTH = 16; +const BATCH_WRITE_LIMIT = 25; + +export interface DynamoDBStateSharedOptions { + /** Key prefix for multi-tenancy (default: "chat-sdk") */ + keyPrefix?: string; + /** Logger instance for error reporting */ + logger?: Logger; + /** Partition key attribute name (default: "pk") */ + pkName?: string; + /** Sort key attribute name (default: "sk") */ + skName?: string; + /** DynamoDB table name (default: "chat-state") */ + tableName?: string; + /** TTL attribute name (default: "expiresAt") */ + ttlName?: string; +} + +export interface DynamoDBStateAdapterOptions + extends DynamoDBStateSharedOptions { + /** Custom DynamoDB endpoint (for DynamoDB Local development) */ + endpoint?: string; + /** AWS region (default: from environment) */ + region?: string; +} + +export interface DynamoDBStateClientOptions extends DynamoDBStateSharedOptions { + /** Existing DynamoDBDocument instance */ + client: DynamoDBDocument; +} + +export type CreateDynamoDBStateOptions = + | (Partial & { client?: never }) + | (Partial> & { + client: DynamoDBDocument; + }); + +export class DynamoDBStateAdapter implements StateAdapter { + private readonly docClient: DynamoDBDocument; + private readonly tableName: string; + private readonly keyPrefix: string; + private readonly pkName: string; + private readonly skName: string; + private readonly ttlName: string; + private readonly logger: Logger; + private readonly ownsClient: boolean; + private connected = false; + + constructor( + options: DynamoDBStateAdapterOptions | DynamoDBStateClientOptions + ) { + if ("client" in options && options.client) { + this.docClient = options.client; + this.ownsClient = false; + } else { + const opts = options as DynamoDBStateAdapterOptions; + this.docClient = DynamoDBDocument.from( + new DynamoDBClient({ + region: opts.region, + endpoint: opts.endpoint, + }) + ); + this.ownsClient = true; + } + + this.tableName = options.tableName ?? DEFAULT_TABLE_NAME; + this.keyPrefix = options.keyPrefix ?? DEFAULT_KEY_PREFIX; + this.pkName = options.pkName ?? DEFAULT_PK_NAME; + this.skName = options.skName ?? DEFAULT_SK_NAME; + this.ttlName = options.ttlName ?? DEFAULT_TTL_NAME; + this.logger = options.logger ?? new ConsoleLogger("info").child("dynamodb"); + } + + async connect(): Promise { + this.connected = true; + } + + async disconnect(): Promise { + if (!this.connected) { + return; + } + + if (this.ownsClient) { + this.docClient.destroy(); + } + + this.connected = false; + } + + async subscribe(threadId: string): Promise { + this.ensureConnected(); + + await this.docClient.put({ + TableName: this.tableName, + Item: this.key(this.subKey(threadId), "_"), + }); + } + + async unsubscribe(threadId: string): Promise { + this.ensureConnected(); + + await this.docClient.delete({ + TableName: this.tableName, + Key: this.key(this.subKey(threadId), "_"), + }); + } + + async isSubscribed(threadId: string): Promise { + this.ensureConnected(); + + const result = await this.docClient.get({ + TableName: this.tableName, + Key: this.key(this.subKey(threadId), "_"), + ProjectionExpression: "#pk", + ExpressionAttributeNames: { "#pk": this.pkName }, + }); + + return result.Item !== undefined; + } + + async acquireLock(threadId: string, ttlMs: number): Promise { + this.ensureConnected(); + + const token = generateToken(); + const now = Date.now(); + const expiresAtMs = now + ttlMs; + + try { + await this.docClient.put({ + TableName: this.tableName, + Item: { + ...this.key(this.lockKey(threadId), "_"), + token, + expiresAtMs, + [this.ttlName]: msToSeconds(expiresAtMs), + }, + ConditionExpression: "attribute_not_exists(#pk) OR expiresAtMs <= :now", + ExpressionAttributeNames: { "#pk": this.pkName }, + ExpressionAttributeValues: { ":now": now }, + }); + + return { threadId, token, expiresAt: expiresAtMs }; + } catch (error) { + if (error instanceof ConditionalCheckFailedException) { + return null; + } + throw error; + } + } + + async releaseLock(lock: Lock): Promise { + this.ensureConnected(); + + try { + await this.docClient.delete({ + TableName: this.tableName, + Key: this.key(this.lockKey(lock.threadId), "_"), + ConditionExpression: "#t = :token", + ExpressionAttributeNames: { "#t": "token" }, + ExpressionAttributeValues: { ":token": lock.token }, + }); + } catch (error) { + if (error instanceof ConditionalCheckFailedException) { + return; + } + throw error; + } + } + + async forceReleaseLock(threadId: string): Promise { + this.ensureConnected(); + + await this.docClient.delete({ + TableName: this.tableName, + Key: this.key(this.lockKey(threadId), "_"), + }); + } + + async extendLock(lock: Lock, ttlMs: number): Promise { + this.ensureConnected(); + + const now = Date.now(); + const newExpiresAtMs = now + ttlMs; + + try { + await this.docClient.update({ + TableName: this.tableName, + Key: this.key(this.lockKey(lock.threadId), "_"), + UpdateExpression: "SET expiresAtMs = :newMs, #ttl = :newSec", + ConditionExpression: "#t = :token AND expiresAtMs > :now", + ExpressionAttributeNames: { "#t": "token", "#ttl": this.ttlName }, + ExpressionAttributeValues: { + ":token": lock.token, + ":now": now, + ":newMs": newExpiresAtMs, + ":newSec": msToSeconds(newExpiresAtMs), + }, + }); + + return true; + } catch (error) { + if (error instanceof ConditionalCheckFailedException) { + return false; + } + throw error; + } + } + + async get(key: string): Promise { + this.ensureConnected(); + + const result = await this.docClient.get({ + TableName: this.tableName, + Key: this.key(this.cacheKey(key), "_"), + }); + + if (!result.Item) { + return null; + } + + if ( + result.Item.expiresAtMs !== undefined && + (result.Item.expiresAtMs as number) <= Date.now() + ) { + return null; + } + + return result.Item.value as T; + } + + async set(key: string, value: T, ttlMs?: number): Promise { + this.ensureConnected(); + + const item: Record = { + ...this.key(this.cacheKey(key), "_"), + value, + }; + + if (ttlMs !== undefined) { + const expiresAtMs = Date.now() + ttlMs; + item.expiresAtMs = expiresAtMs; + item[this.ttlName] = msToSeconds(expiresAtMs); + } + + await this.docClient.put({ TableName: this.tableName, Item: item }); + } + + async setIfNotExists( + key: string, + value: unknown, + ttlMs?: number + ): Promise { + this.ensureConnected(); + + const now = Date.now(); + const item: Record = { + ...this.key(this.cacheKey(key), "_"), + value, + }; + + if (ttlMs !== undefined) { + const expiresAtMs = now + ttlMs; + item.expiresAtMs = expiresAtMs; + item[this.ttlName] = msToSeconds(expiresAtMs); + } + + try { + await this.docClient.put({ + TableName: this.tableName, + Item: item, + ConditionExpression: "attribute_not_exists(#pk) OR expiresAtMs <= :now", + ExpressionAttributeNames: { "#pk": this.pkName }, + ExpressionAttributeValues: { ":now": now }, + }); + return true; + } catch (error) { + if (error instanceof ConditionalCheckFailedException) { + return false; + } + throw error; + } + } + + async delete(key: string): Promise { + this.ensureConnected(); + + await this.docClient.delete({ + TableName: this.tableName, + Key: this.key(this.cacheKey(key), "_"), + }); + } + + async appendToList( + key: string, + value: unknown, + options?: { maxLength?: number; ttlMs?: number } + ): Promise { + this.ensureConnected(); + + const counterResult = await this.docClient.update({ + TableName: this.tableName, + Key: this.key(this.listCounterKey(key), "_"), + UpdateExpression: "ADD seq :one", + ExpressionAttributeValues: { ":one": 1 }, + ReturnValues: "ALL_NEW", + }); + + const seq = counterResult.Attributes?.seq as number; + const sk = String(seq).padStart(SEQ_PAD_LENGTH, "0"); + + const item: Record = { + ...this.key(this.listKey(key), sk), + value, + }; + + if (options?.ttlMs !== undefined) { + const expiresAtMs = Date.now() + options.ttlMs; + item.expiresAtMs = expiresAtMs; + item[this.ttlName] = msToSeconds(expiresAtMs); + } + + await this.docClient.put({ TableName: this.tableName, Item: item }); + + if (options?.maxLength) { + await this.trimList(key, options.maxLength); + } + + if (options?.ttlMs !== undefined) { + await this.refreshListTtl(key, options.ttlMs); + } + } + + async getList(key: string): Promise { + this.ensureConnected(); + + const items = await this.queryAllListItems(key); + const now = Date.now(); + + const results: T[] = []; + for (const item of items) { + if ( + item.expiresAtMs !== undefined && + (item.expiresAtMs as number) <= now + ) { + continue; + } + + results.push(item.value as T); + } + + return results; + } + + getClient(): DynamoDBDocument { + return this.docClient; + } + + private subKey(threadId: string): string { + return `${this.keyPrefix}#sub#${threadId}`; + } + + private lockKey(threadId: string): string { + return `${this.keyPrefix}#lock#${threadId}`; + } + + private cacheKey(key: string): string { + return `${this.keyPrefix}#cache#${key}`; + } + + private listKey(key: string): string { + return `${this.keyPrefix}#list#${key}`; + } + + private listCounterKey(key: string): string { + return `${this.keyPrefix}#list-counter#${key}`; + } + + private async queryAllListItems( + key: string + ): Promise[]> { + const items: Record[] = []; + let exclusiveStartKey: Record | undefined; + + do { + const result = await this.docClient.query({ + TableName: this.tableName, + KeyConditionExpression: "#pk = :pk", + ExpressionAttributeNames: { "#pk": this.pkName }, + ExpressionAttributeValues: { ":pk": this.listKey(key) }, + ScanIndexForward: true, + ExclusiveStartKey: exclusiveStartKey, + }); + + if (result.Items) { + items.push(...result.Items); + } + exclusiveStartKey = result.LastEvaluatedKey; + } while (exclusiveStartKey); + + return items; + } + + private async trimList(key: string, maxLength: number): Promise { + const allKeys: string[] = []; + let exclusiveStartKey: Record | undefined; + + do { + const result = await this.docClient.query({ + TableName: this.tableName, + KeyConditionExpression: "#pk = :pk", + ExpressionAttributeNames: { "#pk": this.pkName, "#sk": this.skName }, + ExpressionAttributeValues: { ":pk": this.listKey(key) }, + ProjectionExpression: "#sk", + ScanIndexForward: true, + ExclusiveStartKey: exclusiveStartKey, + }); + + if (result.Items) { + for (const item of result.Items) { + allKeys.push(item[this.skName] as string); + } + } + exclusiveStartKey = result.LastEvaluatedKey; + } while (exclusiveStartKey); + + const overflow = allKeys.length - maxLength; + if (overflow <= 0) { + return; + } + + const keysToDelete = allKeys.slice(0, overflow); + const pkValue = this.listKey(key); + + for (let i = 0; i < keysToDelete.length; i += BATCH_WRITE_LIMIT) { + const batch = keysToDelete.slice(i, i + BATCH_WRITE_LIMIT); + + const result = await this.docClient.batchWrite({ + RequestItems: { + [this.tableName]: batch.map((sk) => ({ + DeleteRequest: { Key: this.key(pkValue, sk) }, + })), + }, + }); + + const unprocessed = result.UnprocessedItems?.[this.tableName]; + if (unprocessed?.length) { + this.logger.warn( + `trimList: ${unprocessed.length} unprocessed deletes for list "${key}"` + ); + } + } + } + + private async refreshListTtl(key: string, ttlMs: number): Promise { + const items = await this.queryAllListItems(key); + const now = Date.now(); + const expiresAtMs = now + ttlMs; + const ttlSeconds = msToSeconds(expiresAtMs); + + for (let i = 0; i < items.length; i += BATCH_WRITE_LIMIT) { + const batch = items.slice(i, i + BATCH_WRITE_LIMIT); + + const result = await this.docClient.batchWrite({ + RequestItems: { + [this.tableName]: batch.map((item) => ({ + PutRequest: { + Item: { ...item, expiresAtMs, [this.ttlName]: ttlSeconds }, + }, + })), + }, + }); + + const unprocessed = result.UnprocessedItems?.[this.tableName]; + if (unprocessed?.length) { + this.logger.warn( + `refreshListTtl: ${unprocessed.length} unprocessed writes for list "${key}"` + ); + } + } + } + + private key(pk: string, sk: string): Record { + return { [this.pkName]: pk, [this.skName]: sk }; + } + + private ensureConnected(): void { + if (!this.connected) { + throw new Error( + "DynamoDBStateAdapter is not connected. Call connect() first." + ); + } + } +} + +function generateToken(): string { + return `ddb_${crypto.randomUUID()}`; +} + +function msToSeconds(ms: number): number { + return Math.floor(ms / 1000); +} + +export function createDynamoDBState( + options: CreateDynamoDBStateOptions = {} +): DynamoDBStateAdapter { + if ("client" in options && options.client) { + return new DynamoDBStateAdapter({ + client: options.client, + tableName: options.tableName, + keyPrefix: options.keyPrefix, + pkName: options.pkName, + skName: options.skName, + ttlName: options.ttlName, + logger: options.logger, + }); + } + + const opts = options as Partial; + return new DynamoDBStateAdapter({ + tableName: opts.tableName, + keyPrefix: opts.keyPrefix, + pkName: opts.pkName, + skName: opts.skName, + ttlName: opts.ttlName, + region: opts.region, + endpoint: opts.endpoint, + logger: opts.logger, + }); +} diff --git a/packages/state-dynamodb/tsconfig.json b/packages/state-dynamodb/tsconfig.json new file mode 100644 index 00000000..8768f5bd --- /dev/null +++ b/packages/state-dynamodb/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "strictNullChecks": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts"] +} diff --git a/packages/state-dynamodb/tsup.config.ts b/packages/state-dynamodb/tsup.config.ts new file mode 100644 index 00000000..0cd92c2e --- /dev/null +++ b/packages/state-dynamodb/tsup.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + format: ["esm"], + dts: true, + clean: true, + sourcemap: true, + external: ["@aws-sdk/client-dynamodb", "@aws-sdk/lib-dynamodb"], +}); diff --git a/packages/state-dynamodb/vitest.config.ts b/packages/state-dynamodb/vitest.config.ts new file mode 100644 index 00000000..edc2d946 --- /dev/null +++ b/packages/state-dynamodb/vitest.config.ts @@ -0,0 +1,14 @@ +import { defineProject } from "vitest/config"; + +export default defineProject({ + test: { + globals: true, + environment: "node", + coverage: { + provider: "v8", + reporter: ["text", "json-summary"], + include: ["src/**/*.ts"], + exclude: ["src/**/*.test.ts"], + }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e123c248..641446f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -559,6 +559,34 @@ importers: specifier: ^4.0.18 version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) + packages/state-dynamodb: + dependencies: + chat: + specifier: workspace:* + version: link:../chat + devDependencies: + '@aws-sdk/client-dynamodb': + specifier: ^3.750.0 + version: 3.1014.0 + '@aws-sdk/lib-dynamodb': + specifier: ^3.750.0 + version: 3.1014.0(@aws-sdk/client-dynamodb@3.1014.0) + '@types/node': + specifier: ^25.3.2 + version: 25.3.2 + '@vitest/coverage-v8': + specifier: ^4.0.18 + version: 4.0.18(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)) + tsup: + specifier: ^8.3.5 + version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3) + typescript: + specifier: ^5.7.2 + version: 5.9.3 + vitest: + specifier: ^4.0.18 + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) + packages/state-ioredis: dependencies: chat: @@ -701,6 +729,143 @@ packages: '@antfu/install-pkg@1.1.0': resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + '@aws-crypto/sha256-browser@5.2.0': + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} + + '@aws-crypto/sha256-js@5.2.0': + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/supports-web-crypto@5.2.0': + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} + + '@aws-crypto/util@5.2.0': + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + + '@aws-sdk/client-dynamodb@3.1014.0': + resolution: {integrity: sha512-AFqO74mg9UITN+H5CdK7ULwPrvty6mlbDT2kwY3HI/piI6DjiwA7Y7wKWtJAFjCa1OLyRRV2/jy1DKBb80Qv8Q==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/core@3.973.23': + resolution: {integrity: sha512-aoJncvD1XvloZ9JLnKqTRL9dBy+Szkryoag9VT+V1TqsuUgIxV9cnBVM/hrDi2vE8bDqLiDR8nirdRcCdtJu0w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-env@3.972.21': + resolution: {integrity: sha512-BkAfKq8Bd4shCtec1usNz//urPJF/SZy14qJyxkSaRJQ/Vv1gVh0VZSTmS7aE6aLMELkFV5wHHrS9ZcdG8Kxsg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-http@3.972.23': + resolution: {integrity: sha512-4XZ3+Gu5DY8/n8zQFHBgcKTF7hWQl42G6CY9xfXVo2d25FM/lYkpmuzhYopYoPL1ITWkJ2OSBQfYEu5JRfHOhA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-ini@3.972.23': + resolution: {integrity: sha512-PZLSmU0JFpNCDFReidBezsgL5ji9jOBry8CnZdw4Jj6d0K2z3Ftnp44NXgADqYx5BLMu/ZHujfeJReaDoV+IwQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-login@3.972.23': + resolution: {integrity: sha512-OmE/pSkbMM3dCj1HdOnZ5kXnKK+R/Yz+kbBugraBecp0pGAs21eEURfQRz+1N2gzIHLVyGIP1MEjk/uSrFsngg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-node@3.972.24': + resolution: {integrity: sha512-9Jwi7aps3AfUicJyF5udYadPypPpCwUZ6BSKr/QjRbVCpRVS1wc+1Q6AEZ/qz8J4JraeRd247pSzyMQSIHVebw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-process@3.972.21': + resolution: {integrity: sha512-nRxbeOJ1E1gVA0lNQezuMVndx+ZcuyaW/RB05pUsznN5BxykSlH6KkZ/7Ca/ubJf3i5N3p0gwNO5zgPSCzj+ww==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-sso@3.972.23': + resolution: {integrity: sha512-APUccADuYPLL0f2htpM8Z4czabSmHOdo4r41W6lKEZdy++cNJ42Radqy6x4TopENzr3hR6WYMyhiuiqtbf/nAA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.972.23': + resolution: {integrity: sha512-H5JNqtIwOu/feInmMMWcK0dL5r897ReEn7n2m16Dd0DPD9gA2Hg8Cq4UDzZ/9OzaLh/uqBM6seixz0U6Fi2Eag==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/dynamodb-codec@3.972.24': + resolution: {integrity: sha512-J4qDdBAV8Gq87B2jnX1y4brRlnlta2lIZma7HfQDlkNYo7abSWF0n8quzK9a0wG7UOMfBDzL5jP+1lt3ufggOQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/endpoint-cache@3.972.4': + resolution: {integrity: sha512-GdASDnWanLnHxKK0hqV97xz23QmfA/C8yGe0PiuEmWiHSe+x+x+mFEj4sXqx9IbfyPncWz8f4EhNwBSG9cgYCg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/lib-dynamodb@3.1014.0': + resolution: {integrity: sha512-erDzDJk1tNPkvTbuWJi/8yelI9M4dA//gOOwpsseZbnzV2OOvgcIExmTxeMpFPWYHXXCJuwo+nOY31V6ba/BVA==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@aws-sdk/client-dynamodb': ^3.1014.0 + + '@aws-sdk/middleware-endpoint-discovery@3.972.8': + resolution: {integrity: sha512-S0oXx1QbSpMDBMJn4P0hOxW8ieGAdRT+G9NbL+ESWkkoCGf9D++fKYD2fyBGtIy88OrP7wgECpXgGLAcGpIj0A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-host-header@3.972.8': + resolution: {integrity: sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-logger@3.972.8': + resolution: {integrity: sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-recursion-detection@3.972.8': + resolution: {integrity: sha512-BnnvYs2ZEpdlmZ2PNlV2ZyQ8j8AEkMTjN79y/YA475ER1ByFYrkVR85qmhni8oeTaJcDqbx364wDpitDAA/wCA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-user-agent@3.972.24': + resolution: {integrity: sha512-dLTWy6IfAMhNiSEvMr07g/qZ54be6pLqlxVblbF6AzafmmGAzMMj8qMoY9B4+YgT+gY9IcuxZslNh03L6PyMCQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/nested-clients@3.996.13': + resolution: {integrity: sha512-ptZ1HF4yYHNJX8cgFF+8NdYO69XJKZn7ft0/ynV3c0hCbN+89fAbrLS+fqniU2tW8o9Kfqhj8FUh+IPXb2Qsuw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/region-config-resolver@3.972.9': + resolution: {integrity: sha512-eQ+dFU05ZRC/lC2XpYlYSPlXtX3VT8sn5toxN2Fv7EXlMoA2p9V7vUBKqHunfD4TRLpxUq8Y8Ol/nCqiv327Ng==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1014.0': + resolution: {integrity: sha512-gHTHNUoaOGNrSWkl32A7wFsU78jlNTlqMccLu0byUk5CysYYXaxNMIonIVr4YcykC7vgtDS5ABuz83giy6fzJA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/types@3.973.6': + resolution: {integrity: sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-dynamodb@3.996.2': + resolution: {integrity: sha512-ddpwaZmjBzcApYN7lgtAXjk+u+GO8fiPsxzuc59UqP+zqdxI1gsenPvkyiHiF9LnYnyRGijz6oN2JylnN561qQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@aws-sdk/client-dynamodb': ^3.1003.0 + + '@aws-sdk/util-endpoints@3.996.5': + resolution: {integrity: sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-locate-window@3.965.5': + resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-user-agent-browser@3.972.8': + resolution: {integrity: sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA==} + + '@aws-sdk/util-user-agent-node@3.973.10': + resolution: {integrity: sha512-E99zeTscCc+pTMfsvnfi6foPpKmdD1cZfOC7/P8UUrjsoQdg9VEWPRD+xdFduKnfPXwcvby58AlO9jwwF6U96g==} + engines: {node: '>=20.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + + '@aws-sdk/xml-builder@3.972.15': + resolution: {integrity: sha512-PxMRlCFNiQnke9YR29vjFQwz4jq+6Q04rOVFeTDR2K7Qpv9h9FOWOxG+zJjageimYbWqE3bTuLjmryWHAWbvaA==} + engines: {node: '>=20.0.0'} + + '@aws/lambda-invoke-store@0.2.4': + resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} + engines: {node: '>=18.0.0'} + '@azure/abort-controller@2.1.2': resolution: {integrity: sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==} engines: {node: '>=18.0.0'} @@ -2588,6 +2753,182 @@ packages: resolution: {integrity: sha512-RoygyteJeFswxDPJjUMESn9dldWVMD2xUcHHd9DenVavSfVC6FeVnSdDerOO7m8LLvw4Q132nQM4hX8JiF7dng==} engines: {node: '>= 18', npm: '>= 8.6.0'} + '@smithy/abort-controller@4.2.12': + resolution: {integrity: sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q==} + engines: {node: '>=18.0.0'} + + '@smithy/config-resolver@4.4.13': + resolution: {integrity: sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg==} + engines: {node: '>=18.0.0'} + + '@smithy/core@3.23.12': + resolution: {integrity: sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.2.12': + resolution: {integrity: sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.3.15': + resolution: {integrity: sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-node@4.2.12': + resolution: {integrity: sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w==} + engines: {node: '>=18.0.0'} + + '@smithy/invalid-dependency@4.2.12': + resolution: {integrity: sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g==} + engines: {node: '>=18.0.0'} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + + '@smithy/is-array-buffer@4.2.2': + resolution: {integrity: sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-content-length@4.2.12': + resolution: {integrity: sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-endpoint@4.4.27': + resolution: {integrity: sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-retry@4.4.44': + resolution: {integrity: sha512-Y1Rav7m5CFRPQyM4CI0koD/bXjyjJu3EQxZZhtLGD88WIrBrQ7kqXM96ncd6rYnojwOo/u9MXu57JrEvu/nLrA==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-serde@4.2.15': + resolution: {integrity: sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-stack@4.2.12': + resolution: {integrity: sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw==} + engines: {node: '>=18.0.0'} + + '@smithy/node-config-provider@4.3.12': + resolution: {integrity: sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.5.0': + resolution: {integrity: sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A==} + engines: {node: '>=18.0.0'} + + '@smithy/property-provider@4.2.12': + resolution: {integrity: sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A==} + engines: {node: '>=18.0.0'} + + '@smithy/protocol-http@5.3.12': + resolution: {integrity: sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-builder@4.2.12': + resolution: {integrity: sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-parser@4.2.12': + resolution: {integrity: sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw==} + engines: {node: '>=18.0.0'} + + '@smithy/service-error-classification@4.2.12': + resolution: {integrity: sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ==} + engines: {node: '>=18.0.0'} + + '@smithy/shared-ini-file-loader@4.4.7': + resolution: {integrity: sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.3.12': + resolution: {integrity: sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw==} + engines: {node: '>=18.0.0'} + + '@smithy/smithy-client@4.12.7': + resolution: {integrity: sha512-q3gqnwml60G44FECaEEsdQMplYhDMZYCtYhMCzadCnRnnHIobZJjegmdoUo6ieLQlPUzvrMdIJUpx6DoPmzANQ==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.13.1': + resolution: {integrity: sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==} + engines: {node: '>=18.0.0'} + + '@smithy/url-parser@4.2.12': + resolution: {integrity: sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-base64@4.3.2': + resolution: {integrity: sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-browser@4.2.2': + resolution: {integrity: sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-node@4.2.3': + resolution: {integrity: sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==} + engines: {node: '>=18.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-buffer-from@4.2.2': + resolution: {integrity: sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==} + engines: {node: '>=18.0.0'} + + '@smithy/util-config-provider@4.2.2': + resolution: {integrity: sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-browser@4.3.43': + resolution: {integrity: sha512-Qd/0wCKMaXxev/z00TvNzGCH2jlKKKxXP1aDxB6oKwSQthe3Og2dMhSayGCnsma1bK/kQX1+X7SMP99t6FgiiQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-node@4.2.47': + resolution: {integrity: sha512-qSRbYp1EQ7th+sPFuVcVO05AE0QH635hycdEXlpzIahqHHf2Fyd/Zl+8v0XYMJ3cgDVPa0lkMefU7oNUjAP+DQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-endpoints@3.3.3': + resolution: {integrity: sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig==} + engines: {node: '>=18.0.0'} + + '@smithy/util-hex-encoding@4.2.2': + resolution: {integrity: sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-middleware@4.2.12': + resolution: {integrity: sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-retry@4.2.12': + resolution: {integrity: sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-stream@4.5.20': + resolution: {integrity: sha512-4yXLm5n/B5SRBR2p8cZ90Sbv4zL4NKsgxdzCzp/83cXw2KxLEumt5p+GAVyRNZgQOSrzXn9ARpO0lUe8XSlSDw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-uri-escape@4.2.2': + resolution: {integrity: sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@4.2.2': + resolution: {integrity: sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-waiter@4.2.13': + resolution: {integrity: sha512-2zdZ9DTHngRtcYxJK1GUDxruNr53kv5W2Lupe0LMU+Imr6ohQg8M2T14MNkj1Y0wS3FFwpgpGQyvuaMF7CiTmQ==} + engines: {node: '>=18.0.0'} + + '@smithy/uuid@1.1.2': + resolution: {integrity: sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==} + engines: {node: '>=18.0.0'} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -3195,6 +3536,9 @@ packages: botframework-streaming@4.23.3: resolution: {integrity: sha512-GMtciQGfZXtAW6syUqFpFJQ2vDyVbpxL3T1DqFzq/GmmkAu7KTZ1zvo7PTww6+IT1kMW0lmL/XZJVq3Rhg4PQA==} + bowser@2.14.1: + resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} + brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} @@ -3786,6 +4130,13 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-xml-builder@1.1.4: + resolution: {integrity: sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==} + + fast-xml-parser@5.5.8: + resolution: {integrity: sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==} + hasBin: true + fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -4770,6 +5121,9 @@ packages: mlly@1.8.0: resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + mnemonist@0.38.3: + resolution: {integrity: sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==} + motion-dom@12.34.0: resolution: {integrity: sha512-Lql3NuEcScRDxTAO6GgUsRHBZOWI/3fnMlkMcH5NftzcN37zJta+bpbMAV9px4Nj057TuvRooMK7QrzMCgtz6Q==} @@ -4885,6 +5239,9 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + obliterator@1.6.1: + resolution: {integrity: sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==} + obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} @@ -4968,6 +5325,10 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-expression-matcher@1.2.0: + resolution: {integrity: sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==} + engines: {node: '>=14.0.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -5492,6 +5853,9 @@ packages: resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} engines: {node: '>=14.16'} + strnum@2.2.1: + resolution: {integrity: sha512-BwRvNd5/QoAtyW1na1y1LsJGQNvRlkde6Q/ipqqEaivoMdV+B1OMOTVdwR+N/cwVUcIt9PYyHmV8HyexCZSupg==} + style-to-js@1.1.21: resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} @@ -6052,6 +6416,373 @@ snapshots: package-manager-detector: 1.6.0 tinyexec: 1.0.2 + '@aws-crypto/sha256-browser@5.2.0': + dependencies: + '@aws-crypto/sha256-js': 5.2.0 + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-locate-window': 3.965.5 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-js@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.6 + tslib: 2.8.1 + + '@aws-crypto/supports-web-crypto@5.2.0': + dependencies: + tslib: 2.8.1 + + '@aws-crypto/util@5.2.0': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-sdk/client-dynamodb@3.1014.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.23 + '@aws-sdk/credential-provider-node': 3.972.24 + '@aws-sdk/dynamodb-codec': 3.972.24 + '@aws-sdk/middleware-endpoint-discovery': 3.972.8 + '@aws-sdk/middleware-host-header': 3.972.8 + '@aws-sdk/middleware-logger': 3.972.8 + '@aws-sdk/middleware-recursion-detection': 3.972.8 + '@aws-sdk/middleware-user-agent': 3.972.24 + '@aws-sdk/region-config-resolver': 3.972.9 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-endpoints': 3.996.5 + '@aws-sdk/util-user-agent-browser': 3.972.8 + '@aws-sdk/util-user-agent-node': 3.973.10 + '@smithy/config-resolver': 4.4.13 + '@smithy/core': 3.23.12 + '@smithy/fetch-http-handler': 5.3.15 + '@smithy/hash-node': 4.2.12 + '@smithy/invalid-dependency': 4.2.12 + '@smithy/middleware-content-length': 4.2.12 + '@smithy/middleware-endpoint': 4.4.27 + '@smithy/middleware-retry': 4.4.44 + '@smithy/middleware-serde': 4.2.15 + '@smithy/middleware-stack': 4.2.12 + '@smithy/node-config-provider': 4.3.12 + '@smithy/node-http-handler': 4.5.0 + '@smithy/protocol-http': 5.3.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.43 + '@smithy/util-defaults-mode-node': 4.2.47 + '@smithy/util-endpoints': 3.3.3 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-retry': 4.2.12 + '@smithy/util-utf8': 4.2.2 + '@smithy/util-waiter': 4.2.13 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/core@3.973.23': + dependencies: + '@aws-sdk/types': 3.973.6 + '@aws-sdk/xml-builder': 3.972.15 + '@smithy/core': 3.23.12 + '@smithy/node-config-provider': 4.3.12 + '@smithy/property-provider': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/signature-v4': 5.3.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + '@smithy/util-base64': 4.3.2 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.972.21': + dependencies: + '@aws-sdk/core': 3.973.23 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.972.23': + dependencies: + '@aws-sdk/core': 3.973.23 + '@aws-sdk/types': 3.973.6 + '@smithy/fetch-http-handler': 5.3.15 + '@smithy/node-http-handler': 4.5.0 + '@smithy/property-provider': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + '@smithy/util-stream': 4.5.20 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.972.23': + dependencies: + '@aws-sdk/core': 3.973.23 + '@aws-sdk/credential-provider-env': 3.972.21 + '@aws-sdk/credential-provider-http': 3.972.23 + '@aws-sdk/credential-provider-login': 3.972.23 + '@aws-sdk/credential-provider-process': 3.972.21 + '@aws-sdk/credential-provider-sso': 3.972.23 + '@aws-sdk/credential-provider-web-identity': 3.972.23 + '@aws-sdk/nested-clients': 3.996.13 + '@aws-sdk/types': 3.973.6 + '@smithy/credential-provider-imds': 4.2.12 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-login@3.972.23': + dependencies: + '@aws-sdk/core': 3.973.23 + '@aws-sdk/nested-clients': 3.996.13 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-node@3.972.24': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.21 + '@aws-sdk/credential-provider-http': 3.972.23 + '@aws-sdk/credential-provider-ini': 3.972.23 + '@aws-sdk/credential-provider-process': 3.972.21 + '@aws-sdk/credential-provider-sso': 3.972.23 + '@aws-sdk/credential-provider-web-identity': 3.972.23 + '@aws-sdk/types': 3.973.6 + '@smithy/credential-provider-imds': 4.2.12 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-process@3.972.21': + dependencies: + '@aws-sdk/core': 3.973.23 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.972.23': + dependencies: + '@aws-sdk/core': 3.973.23 + '@aws-sdk/nested-clients': 3.996.13 + '@aws-sdk/token-providers': 3.1014.0 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-web-identity@3.972.23': + dependencies: + '@aws-sdk/core': 3.973.23 + '@aws-sdk/nested-clients': 3.996.13 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/dynamodb-codec@3.972.24': + dependencies: + '@aws-sdk/core': 3.973.23 + '@smithy/core': 3.23.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + '@smithy/util-base64': 4.3.2 + tslib: 2.8.1 + + '@aws-sdk/endpoint-cache@3.972.4': + dependencies: + mnemonist: 0.38.3 + tslib: 2.8.1 + + '@aws-sdk/lib-dynamodb@3.1014.0(@aws-sdk/client-dynamodb@3.1014.0)': + dependencies: + '@aws-sdk/client-dynamodb': 3.1014.0 + '@aws-sdk/core': 3.973.23 + '@aws-sdk/util-dynamodb': 3.996.2(@aws-sdk/client-dynamodb@3.1014.0) + '@smithy/core': 3.23.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-endpoint-discovery@3.972.8': + dependencies: + '@aws-sdk/endpoint-cache': 3.972.4 + '@aws-sdk/types': 3.973.6 + '@smithy/node-config-provider': 4.3.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-host-header@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-logger@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-recursion-detection@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@aws/lambda-invoke-store': 0.2.4 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-user-agent@3.972.24': + dependencies: + '@aws-sdk/core': 3.973.23 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-endpoints': 3.996.5 + '@smithy/core': 3.23.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-retry': 4.2.12 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.996.13': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.23 + '@aws-sdk/middleware-host-header': 3.972.8 + '@aws-sdk/middleware-logger': 3.972.8 + '@aws-sdk/middleware-recursion-detection': 3.972.8 + '@aws-sdk/middleware-user-agent': 3.972.24 + '@aws-sdk/region-config-resolver': 3.972.9 + '@aws-sdk/types': 3.973.6 + '@aws-sdk/util-endpoints': 3.996.5 + '@aws-sdk/util-user-agent-browser': 3.972.8 + '@aws-sdk/util-user-agent-node': 3.973.10 + '@smithy/config-resolver': 4.4.13 + '@smithy/core': 3.23.12 + '@smithy/fetch-http-handler': 5.3.15 + '@smithy/hash-node': 4.2.12 + '@smithy/invalid-dependency': 4.2.12 + '@smithy/middleware-content-length': 4.2.12 + '@smithy/middleware-endpoint': 4.4.27 + '@smithy/middleware-retry': 4.4.44 + '@smithy/middleware-serde': 4.2.15 + '@smithy/middleware-stack': 4.2.12 + '@smithy/node-config-provider': 4.3.12 + '@smithy/node-http-handler': 4.5.0 + '@smithy/protocol-http': 5.3.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-body-length-node': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.43 + '@smithy/util-defaults-mode-node': 4.2.47 + '@smithy/util-endpoints': 3.3.3 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-retry': 4.2.12 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/region-config-resolver@3.972.9': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/config-resolver': 4.4.13 + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1014.0': + dependencies: + '@aws-sdk/core': 3.973.23 + '@aws-sdk/nested-clients': 3.996.13 + '@aws-sdk/types': 3.973.6 + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/types@3.973.6': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@aws-sdk/util-dynamodb@3.996.2(@aws-sdk/client-dynamodb@3.1014.0)': + dependencies: + '@aws-sdk/client-dynamodb': 3.1014.0 + tslib: 2.8.1 + + '@aws-sdk/util-endpoints@3.996.5': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-endpoints': 3.3.3 + tslib: 2.8.1 + + '@aws-sdk/util-locate-window@3.965.5': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-browser@3.972.8': + dependencies: + '@aws-sdk/types': 3.973.6 + '@smithy/types': 4.13.1 + bowser: 2.14.1 + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-node@3.973.10': + dependencies: + '@aws-sdk/middleware-user-agent': 3.972.24 + '@aws-sdk/types': 3.973.6 + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-config-provider': 4.2.2 + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.972.15': + dependencies: + '@smithy/types': 4.13.1 + fast-xml-parser: 5.5.8 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.2.4': {} + '@azure/abort-controller@2.1.2': dependencies: tslib: 2.8.1 @@ -7960,6 +8691,287 @@ snapshots: transitivePeerDependencies: - debug + '@smithy/abort-controller@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/config-resolver@4.4.13': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-config-provider': 4.2.2 + '@smithy/util-endpoints': 3.3.3 + '@smithy/util-middleware': 4.2.12 + tslib: 2.8.1 + + '@smithy/core@3.23.12': + dependencies: + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-base64': 4.3.2 + '@smithy/util-body-length-browser': 4.2.2 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-stream': 4.5.20 + '@smithy/util-utf8': 4.2.2 + '@smithy/uuid': 1.1.2 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.2.12': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/property-provider': 4.2.12 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.3.15': + dependencies: + '@smithy/protocol-http': 5.3.12 + '@smithy/querystring-builder': 4.2.12 + '@smithy/types': 4.13.1 + '@smithy/util-base64': 4.3.2 + tslib: 2.8.1 + + '@smithy/hash-node@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@smithy/invalid-dependency@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/is-array-buffer@4.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/middleware-content-length@4.2.12': + dependencies: + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/middleware-endpoint@4.4.27': + dependencies: + '@smithy/core': 3.23.12 + '@smithy/middleware-serde': 4.2.15 + '@smithy/node-config-provider': 4.3.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + '@smithy/url-parser': 4.2.12 + '@smithy/util-middleware': 4.2.12 + tslib: 2.8.1 + + '@smithy/middleware-retry@4.4.44': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/service-error-classification': 4.2.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-retry': 4.2.12 + '@smithy/uuid': 1.1.2 + tslib: 2.8.1 + + '@smithy/middleware-serde@4.2.15': + dependencies: + '@smithy/core': 3.23.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/middleware-stack@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/node-config-provider@4.3.12': + dependencies: + '@smithy/property-provider': 4.2.12 + '@smithy/shared-ini-file-loader': 4.4.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/node-http-handler@4.5.0': + dependencies: + '@smithy/abort-controller': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/querystring-builder': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/property-provider@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/protocol-http@5.3.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/querystring-builder@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + '@smithy/util-uri-escape': 4.2.2 + tslib: 2.8.1 + + '@smithy/querystring-parser@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/service-error-classification@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + + '@smithy/shared-ini-file-loader@4.4.7': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/signature-v4@5.3.12': + dependencies: + '@smithy/is-array-buffer': 4.2.2 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-hex-encoding': 4.2.2 + '@smithy/util-middleware': 4.2.12 + '@smithy/util-uri-escape': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@smithy/smithy-client@4.12.7': + dependencies: + '@smithy/core': 3.23.12 + '@smithy/middleware-endpoint': 4.4.27 + '@smithy/middleware-stack': 4.2.12 + '@smithy/protocol-http': 5.3.12 + '@smithy/types': 4.13.1 + '@smithy/util-stream': 4.5.20 + tslib: 2.8.1 + + '@smithy/types@4.13.1': + dependencies: + tslib: 2.8.1 + + '@smithy/url-parser@4.2.12': + dependencies: + '@smithy/querystring-parser': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/util-base64@4.3.2': + dependencies: + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@smithy/util-body-length-browser@4.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/util-body-length-node@4.2.3': + dependencies: + tslib: 2.8.1 + + '@smithy/util-buffer-from@2.2.0': + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-buffer-from@4.2.2': + dependencies: + '@smithy/is-array-buffer': 4.2.2 + tslib: 2.8.1 + + '@smithy/util-config-provider@4.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/util-defaults-mode-browser@4.3.43': + dependencies: + '@smithy/property-provider': 4.2.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/util-defaults-mode-node@4.2.47': + dependencies: + '@smithy/config-resolver': 4.4.13 + '@smithy/credential-provider-imds': 4.2.12 + '@smithy/node-config-provider': 4.3.12 + '@smithy/property-provider': 4.2.12 + '@smithy/smithy-client': 4.12.7 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/util-endpoints@3.3.3': + dependencies: + '@smithy/node-config-provider': 4.3.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/util-hex-encoding@4.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/util-middleware@4.2.12': + dependencies: + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/util-retry@4.2.12': + dependencies: + '@smithy/service-error-classification': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/util-stream@4.5.20': + dependencies: + '@smithy/fetch-http-handler': 5.3.15 + '@smithy/node-http-handler': 4.5.0 + '@smithy/types': 4.13.1 + '@smithy/util-base64': 4.3.2 + '@smithy/util-buffer-from': 4.2.2 + '@smithy/util-hex-encoding': 4.2.2 + '@smithy/util-utf8': 4.2.2 + tslib: 2.8.1 + + '@smithy/util-uri-escape@4.2.2': + dependencies: + tslib: 2.8.1 + + '@smithy/util-utf8@2.3.0': + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-utf8@4.2.2': + dependencies: + '@smithy/util-buffer-from': 4.2.2 + tslib: 2.8.1 + + '@smithy/util-waiter@4.2.13': + dependencies: + '@smithy/abort-controller': 4.2.12 + '@smithy/types': 4.13.1 + tslib: 2.8.1 + + '@smithy/uuid@1.1.2': + dependencies: + tslib: 2.8.1 + '@standard-schema/spec@1.1.0': {} '@streamdown/cjk@1.0.2(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(react@19.2.3)(unified@11.0.5)': @@ -8592,6 +9604,8 @@ snapshots: - bufferutil - utf-8-validate + bowser@2.14.1: {} + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 @@ -9240,6 +10254,16 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-xml-builder@1.1.4: + dependencies: + path-expression-matcher: 1.2.0 + + fast-xml-parser@5.5.8: + dependencies: + fast-xml-builder: 1.1.4 + path-expression-matcher: 1.2.0 + strnum: 2.2.1 + fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -10629,6 +11653,10 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.1 + mnemonist@0.38.3: + dependencies: + obliterator: 1.6.1 + motion-dom@12.34.0: dependencies: motion-utils: 12.29.2 @@ -10719,6 +11747,8 @@ snapshots: object-inspect@1.13.4: {} + obliterator@1.6.1: {} + obug@2.1.1: {} oniguruma-parser@0.12.1: {} @@ -10823,6 +11853,8 @@ snapshots: path-exists@4.0.0: {} + path-expression-matcher@1.2.0: {} + path-key@3.1.1: {} path-scurry@1.11.1: @@ -11508,6 +12540,8 @@ snapshots: strip-json-comments@5.0.3: {} + strnum@2.2.1: {} + style-to-js@1.1.21: dependencies: style-to-object: 1.0.14