diff --git a/.vscode/settings.json b/.vscode/settings.json index 57e0af2..08e6ef8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,9 @@ { "rust-analyzer.cargo.target": "wasm32-unknown-unknown", "rust-analyzer.cargo.features": [], + "rust-analyzer.cargo.extraEnv": { + "PROTOC":"/opt/homebrew/bin/protoc" + }, "protoc": { "options": ["-I/${workspaceRoot}/confidence-resolver/protos"] } diff --git a/confidence-resolver/protos/confidence/flags/admin/v1/resolver.proto b/confidence-resolver/protos/confidence/flags/admin/v1/resolver.proto index 8e44a1f..5657e71 100644 --- a/confidence-resolver/protos/confidence/flags/admin/v1/resolver.proto +++ b/confidence-resolver/protos/confidence/flags/admin/v1/resolver.proto @@ -56,6 +56,8 @@ message ResolverStateUriResponse { string signed_uri = 1; // At what time the state uri expires google.protobuf.Timestamp expire_time = 2; + // The account the referenced state belongs to + string account = 3; } // Request to get the resolver state for the whole account diff --git a/openfeature-provider/js/.editorconfig b/openfeature-provider/js/.editorconfig new file mode 100644 index 0000000..1ed453a --- /dev/null +++ b/openfeature-provider/js/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +[*.{js,json,yml}] +charset = utf-8 +indent_style = space +indent_size = 2 diff --git a/openfeature-provider/js/.gitattributes b/openfeature-provider/js/.gitattributes new file mode 100644 index 0000000..af3ad12 --- /dev/null +++ b/openfeature-provider/js/.gitattributes @@ -0,0 +1,4 @@ +/.yarn/** linguist-vendored +/.yarn/releases/* binary +/.yarn/plugins/**/* binary +/.pnp.* binary linguist-generated diff --git a/openfeature-provider/js/.gitignore b/openfeature-provider/js/.gitignore new file mode 100644 index 0000000..85bc4ad --- /dev/null +++ b/openfeature-provider/js/.gitignore @@ -0,0 +1,10 @@ +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions +node_modules/ +src/proto/ +dist/ +.env.test \ No newline at end of file diff --git a/openfeature-provider/js/.yarnrc.yml b/openfeature-provider/js/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/openfeature-provider/js/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/openfeature-provider/js/README.md b/openfeature-provider/js/README.md new file mode 100644 index 0000000..242d300 --- /dev/null +++ b/openfeature-provider/js/README.md @@ -0,0 +1,146 @@ +## @spotify-confidence/openfeature-server-provider-local + +OpenFeature provider for the Spotify Confidence resolver (local mode, powered by WebAssembly). It periodically fetches resolver state, evaluates flags locally, and flushes evaluation logs to the Confidence backend. + +### Features +- Local flag evaluation via WASM (no per-eval network calls) +- Automatic state refresh and batched flag log flushing +- Pluggable `fetch` with retries, timeouts and routing +- Optional logging using `debug` + +### Requirements +- Node.js 18+ (built-in `fetch`) or provide a compatible `fetch` +- WebAssembly support (Node 18+/modern browsers) + +--- + +## Installation + +```bash +yarn add @spotify-confidence/openfeature-server-provider-local + +# Optional: enable logs by installing the peer dependency +yarn add debug +``` + +Notes: +- `debug` is an optional peer. Install it if you want logs. Without it, logging is a no-op. +- Types and bundling are ESM-first; Node is supported, and a browser build is provided for modern bundlers. + +--- + +## Quick start (Node) + +```ts +import { OpenFeature } from '@openfeature/server-sdk'; +import { createConfidenceServerProvider } from '@spotify-confidence/openfeature-server-provider-local'; + +const provider = createConfidenceServerProvider({ + flagClientSecret: process.env.CONFIDENCE_FLAG_CLIENT_SECRET!, + apiClientId: process.env.CONFIDENCE_API_CLIENT_ID!, + apiClientSecret: process.env.CONFIDENCE_API_CLIENT_SECRET!, + // initializeTimeout?: number + // flushInterval?: number + // fetch?: typeof fetch (Node <18 or custom transport) +}); + +// Wait for the provider to be ready (fetches initial resolver state) +await OpenFeature.setProviderAndWait(provider); + +const client = OpenFeature.getClient(); + +// Evaluate a boolean flag +const details = await client.getBooleanDetails('my-flag', false, { targetingKey: 'user-123' }); +console.log(details.value, details.reason); + +// Evaluate a nested value from an object flag using dot-path +// e.g. flag key "experiments" with payload { groupA: { ratio: 0.5 } } +const ratio = await client.getNumberValue('experiments.groupA.ratio', 0, { targetingKey: 'user-123' }); + +// On shutdown, flush any pending logs +await provider.onClose(); +``` + +--- + +## Options + +- `flagClientSecret` (string, required): The flag client secret used during evaluation. +- `apiClientId` (string, required): OAuth client ID for Confidence IAM. +- `apiClientSecret` (string, required): OAuth client secret for Confidence IAM. +- `initializeTimeout` (number, optional): Max ms to wait for initial state fetch. Defaults to 30_000. +- `flushInterval` (number, optional): Interval in ms for sending evaluation logs. Defaults to 10_000. +- `fetch` (optional): Custom `fetch` implementation. Required for Node < 18; for Node 18+ you can omit. + +The provider periodically: +- Refreshes resolver state (default every 30s) +- Flushes flag evaluation logs to the backend + +--- + +## Logging (optional) + +Logging uses the `debug` library if present; otherwise, all log calls are no-ops. + +Namespaces: +- Core: `cnfd:*` +- Fetch/middleware: `cnfd:fetch:*` (e.g. retries, auth renewals, request summaries) + +Enable logs: + +- Node: +```bash +DEBUG=cnfd:* node app.js +# or narrower +DEBUG=cnfd:info,cnfd:error,cnfd:fetch:* node app.js +``` + +- Browser (in DevTools console): +```js +localStorage.debug = 'cnfd:*'; +``` + +Install `debug` if you haven’t: + +```bash +yarn add debug +``` + +--- + +## WebAssembly asset notes + +- Node: the WASM (`confidence_resolver.wasm`) is resolved from the installed package automatically; no extra config needed. +- Browser: the ESM build resolves the WASM via `new URL('confidence_resolver.wasm', import.meta.url)` so modern bundlers (Vite/Rollup/Webpack 5 asset modules) will include it. If your bundler does not, configure it to treat the `.wasm` file as a static asset. + +--- + +## Using in browsers + +The package exports a browser ESM build that compiles the WASM via streaming and uses the global `fetch`. Integrate it with your OpenFeature SDK variant for the web similarly to Node, then register the provider before evaluation. Credentials must be available to the runtime (e.g. through your app’s configuration layer). + +--- + +## Testing + +- You can inject a custom `fetch` via the `fetch` option to stub network behavior in tests. +- The provider batches logs; call `await provider.onClose()` in tests to flush them deterministically. + +--- + +## Troubleshooting + +- Provider stuck in NOT_READY/ERROR: + - Verify `apiClientId`/`apiClientSecret` and `flagClientSecret` are correct. + - Ensure outbound access to Confidence endpoints and GCS. + - Enable `DEBUG=cnfd:*` for more detail. + +- No logs appear: + - Install `debug` and enable the appropriate namespaces. + - Check that your environment variable/`localStorage.debug` is set before your app initializes the provider. + +--- + +## License + +See the root `LICENSE`. diff --git a/openfeature-provider/js/package.json b/openfeature-provider/js/package.json new file mode 100644 index 0000000..744581e --- /dev/null +++ b/openfeature-provider/js/package.json @@ -0,0 +1,61 @@ +{ + "name": "@spotify-confidence/openfeature-server-provider-local", + "version": "0.0.0", + "private": true, + "description": "Spotify Confidence Open Feature provider", + "type": "module", + "files": [ + "dist" + ], + "main": "./dist/index.node.js", + "module": "./dist/index.browser.js", + "types": "./dist/index.node.d.ts", + "exports": { + ".": { + "node": { + "types": "./dist/index.node.d.ts", + "default": "./dist/index.node.js" + }, + "browser": { + "types": "./dist/index.browser.d.ts", + "default": "./dist/index.browser.js" + }, + "default": { + "types": "./dist/index.node.d.ts", + "default": "./dist/index.node.js" + } + }, + "./package.json": "./package.json" + }, + "scripts": { + "build": "tsdown", + "dev": "tsdown --watch", + "test": "vitest", + "proto:gen": "rm -rf src/proto && mkdir -p src/proto && protoc --plugin=node_modules/.bin/protoc-gen-ts_proto --ts_proto_opt useOptionals=messages --ts_proto_opt esModuleInterop=true --ts_proto_out src/proto -Iproto api.proto messages.proto" + }, + "dependencies": { + "@bufbuild/protobuf": "^2.9.0" + }, + "devDependencies": { + "@openfeature/core": "^1.9.0", + "@openfeature/server-sdk": "^1.19.0", + "@types/debug": "^4", + "@types/node": "^24.0.1", + "@vitest/coverage-v8": "^3.2.4", + "debug": "^4.4.3", + "dotenv": "^17.2.2", + "rolldown": "1.0.0-beta.38", + "ts-proto": "^2.7.3", + "tsdown": "latest", + "vitest": "^3.2.4" + }, + "peerDependencies": { + "debug": "^4.4.3" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + }, + "packageManager": "yarn@4.6.0" +} diff --git a/openfeature-provider/js/proto/api.proto b/openfeature-provider/js/proto/api.proto new file mode 100644 index 0000000..0d2f5c8 --- /dev/null +++ b/openfeature-provider/js/proto/api.proto @@ -0,0 +1,92 @@ +syntax = "proto3"; + +import "google/protobuf/struct.proto"; +import "google/protobuf/timestamp.proto"; + +message ResolveFlagsRequest { + // If non-empty, the specific flags are resolved, otherwise all flags + // available to the client will be resolved. + repeated string flags = 1; + + // An object that contains data used in the flag resolve. For example, + // the targeting key e.g. the id of the randomization unit, other attributes + // like country or version that are used for targeting. + google.protobuf.Struct evaluation_context = 2; + + // Credentials for the client. It is used to identify the client and find + // the flags that are available to it. + string client_secret = 3; + + // Determines whether the flags should be applied directly as part of the + // resolve, or delayed until `ApplyFlag` is called. A flag is typically + // applied when it is used, if this occurs much later than the resolve, then + // `apply` should likely be set to false. + bool apply = 4; + + // Information about the SDK used to initiate the request. + // Sdk sdk = 5; +} + +message ResolveFlagsResponse { + // The list of all flags that could be resolved. Note: if any flag was + // archived it will not be included in this list. + repeated ResolvedFlag resolved_flags = 1; + + // An opaque token that is used when `apply` is set to false in `ResolveFlags`. + // When `apply` is set to false, the token must be passed to `ApplyFlags`. + bytes resolve_token = 2; + + // Unique identifier for this particular resolve request. + string resolve_id = 3; +} + + +message ResolvedFlag { + // The id of the flag that as resolved. + string flag = 1; + + // The id of the resolved variant has the format `flags/abc/variants/xyz`. + string variant = 2; + + // The value corresponding to the variant. It will always be a json object, + // for example `{ "color": "red", "size": 12 }`. + google.protobuf.Struct value = 3; + + // The schema of the value that was returned. For example: + // ``` + // { + // "schema": { + // "color": { "stringSchema": {} }, + // "size": { "intSchema": {} } + // } + // } + // ``` + // types.v1.FlagSchema.StructFlagSchema flag_schema = 4; + + // The reason to why the flag could be resolved or not. + ResolveReason reason = 5; +} + + +enum ResolveReason { + // Unspecified enum. + RESOLVE_REASON_UNSPECIFIED = 0; + // The flag was successfully resolved because one rule matched. + RESOLVE_REASON_MATCH = 1; + // The flag could not be resolved because no rule matched. + RESOLVE_REASON_NO_SEGMENT_MATCH = 2; + // The flag could not be resolved because the matching rule had no variant + // that could be assigned. + RESOLVE_REASON_NO_TREATMENT_MATCH = 3 [deprecated = true]; + // The flag could not be resolved because it was archived. + RESOLVE_REASON_FLAG_ARCHIVED = 4; + // The flag could not be resolved because the targeting key field was invalid + RESOLVE_REASON_TARGETING_KEY_ERROR = 5; + // Unknown error occurred during the resolve + RESOLVE_REASON_ERROR = 6; +} + +message SetResolverStateRequest { + bytes state = 1; + string account_id = 2; +} diff --git a/openfeature-provider/js/proto/messages.proto b/openfeature-provider/js/proto/messages.proto new file mode 100644 index 0000000..191f941 --- /dev/null +++ b/openfeature-provider/js/proto/messages.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +message Void {} + +message Request { + bytes data = 1; +} + +message Response { + oneof result { + bytes data = 1; + string error = 2; + } +} diff --git a/openfeature-provider/js/src/ConfidenceServerProviderLocal.e2e.test.ts b/openfeature-provider/js/src/ConfidenceServerProviderLocal.e2e.test.ts new file mode 100644 index 0000000..1f541a0 --- /dev/null +++ b/openfeature-provider/js/src/ConfidenceServerProviderLocal.e2e.test.ts @@ -0,0 +1,98 @@ +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { OpenFeature } from '@openfeature/server-sdk'; +import { ConfidenceServerProviderLocal } from './ConfidenceServerProviderLocal'; +import { readFileSync } from 'node:fs'; +import { WasmResolver } from './WasmResolver'; + +const { + CONFIDENCE_API_CLIENT_ID, + CONFIDENCE_API_CLIENT_SECRET, +} = requireEnv('CONFIDENCE_API_CLIENT_ID', 'CONFIDENCE_API_CLIENT_SECRET'); + +const moduleBytes = readFileSync(__dirname + '/../../../wasm/confidence_resolver.wasm'); +const module = new WebAssembly.Module(moduleBytes); +const resolver = await WasmResolver.load(module); +const confidenceProvider = new ConfidenceServerProviderLocal(resolver, { + flagClientSecret: 'RxDVTrXvc6op1XxiQ4OaR31dKbJ39aYV', + apiClientId: CONFIDENCE_API_CLIENT_ID, + apiClientSecret: CONFIDENCE_API_CLIENT_SECRET +}); + +describe('ConfidenceServerProvider E2E tests', () => { + beforeAll( async () => { + + await OpenFeature.setProviderAndWait(confidenceProvider); + OpenFeature.setContext({ + targetingKey: 'test-a', // control + }); + }); + + afterAll(() => OpenFeature.close()) + + it('should resolve a boolean e2e', async () => { + const client = OpenFeature.getClient(); + + expect(await client.getBooleanValue('web-sdk-e2e-flag.bool', true)).toBeFalsy(); + }); + + it('should resolve an int', async () => { + const client = OpenFeature.getClient(); + + expect(await client.getNumberValue('web-sdk-e2e-flag.int', 10)).toEqual(3); + }); + + it('should resolve a double', async () => { + const client = OpenFeature.getClient(); + + expect(await client.getNumberValue('web-sdk-e2e-flag.double', 10)).toEqual(3.5); + }); + + it('should resolve a string', async () => { + const client = OpenFeature.getClient(); + + expect(await client.getStringValue('web-sdk-e2e-flag.str', 'default')).toEqual('control'); + }); + + it('should resolve a struct', async () => { + const client = OpenFeature.getClient(); + const expectedObject = { + int: 4, + str: 'obj control', + bool: false, + double: 3.6, + ['obj-obj']: {}, + }; + + expect(await client.getObjectValue('web-sdk-e2e-flag.obj', {})).toEqual(expectedObject); + }); + + it('should resolve a sub value from a struct', async () => { + const client = OpenFeature.getClient(); + + expect(await client.getBooleanValue('web-sdk-e2e-flag.obj.bool', true)).toBeFalsy(); + }); + + it('should resolve a sub value from a struct with details with resolve token for client side apply call', async () => { + const client = OpenFeature.getClient(); + const expectedObject = { + flagKey: 'web-sdk-e2e-flag.obj.double', + reason: 'MATCH', + variant: 'flags/web-sdk-e2e-flag/variants/control', + flagMetadata: {}, + value: 3.6, + }; + + expect(await client.getNumberDetails('web-sdk-e2e-flag.obj.double', 1)).toEqual(expectedObject); + }); +}); + +function requireEnv(...names:N): Record { + return names.reduce((acc, name) => { + const value = process.env[name]; + if(!value) throw new Error(`Missing environment variable ${name}`) + return { + ...acc, + [name]: value + }; + }, {}) as Record; +} \ No newline at end of file diff --git a/openfeature-provider/js/src/ConfidenceServerProviderLocal.test.ts b/openfeature-provider/js/src/ConfidenceServerProviderLocal.test.ts new file mode 100644 index 0000000..200a64c --- /dev/null +++ b/openfeature-provider/js/src/ConfidenceServerProviderLocal.test.ts @@ -0,0 +1,149 @@ +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, MockedFunction, MockedObject, vi } from 'vitest'; +import { AccessToken, LocalResolver, ResolveStateUri } from './LocalResolver'; +import { ConfidenceServerProviderLocal, DEFAULT_STATE_INTERVAL } from './ConfidenceServerProviderLocal'; +import { abortableSleep, TimeUnit } from './util'; + + +const mockedWasmResolver:MockedObject = { + resolveFlags: vi.fn(), + setResolverState: vi.fn(), + flushLogs: vi.fn().mockReturnValue(new Uint8Array(100)) +} + +type Endpoint = (req:Request)=>Promise +type MockedEndpoint = MockedFunction & { + latency: number, + status: number | string, + reset: () => void +} + +function createEndpointMock(payloadFactory:(req:Request) => BodyInit | null): MockedEndpoint { + const self = Object.assign(vi.fn(), { + reset(this:MockedEndpoint) { + this.latency = 0; + this.status = 200; + this.mockImplementation(async (req) => { + await abortableSleep(this.latency, req.signal); + if(typeof this.status === 'string') { + throw new Error(this.status); + } + const body = this.status === 200 ? payloadFactory(req) : null; + return new Response(body, { status: this.status }); + }); + } + }) as MockedEndpoint; + return self; +} +const tokenEndpoint = createEndpointMock(() => JSON.stringify({ + accessToken: ``, + expiresIn: 3600 // 1 hour +} satisfies AccessToken)); + +const stateUriEndpoint = createEndpointMock(() => JSON.stringify({ + signedUri: 'https://storage.googleapis.com/state', + account: '' +} satisfies ResolveStateUri)) + +const stateEndpoint = createEndpointMock(() => new Uint8Array(100)); +const flushEndpoint = createEndpointMock(() => null); + +const endpointMocks = [tokenEndpoint, stateUriEndpoint, stateEndpoint, flushEndpoint]; + +const mockedFetch:MockedFunction = vi.fn(async (input, init) => { + const req = new Request(input, init); + switch(req.url) { + case 'https://iam.confidence.dev/v1/oauth/token': + return tokenEndpoint(req); + case 'https://resolver.confidence.dev/v1/flagLogs:write': + return flushEndpoint(req); + case 'https://flags.confidence.dev/v1/resolverState:resolverStateUri': + return stateUriEndpoint(req); + case 'https://storage.googleapis.com/state': + return stateEndpoint(req); + } + return new Response(null, { + status: 404, + statusText: 'Not found' + }) +}); + +let provider:ConfidenceServerProviderLocal; + +vi.useFakeTimers(); + +beforeEach(() => { + vi.clearAllMocks(); + vi.clearAllTimers(); + vi.setSystemTime(0); + provider = new ConfidenceServerProviderLocal(mockedWasmResolver, { + flagClientSecret:'flagClientSecret', + apiClientId: 'apiClientId', + apiClientSecret: 'apiClientSecret', + fetch: mockedFetch + }); + endpointMocks.forEach(em => em.reset()) + +}) + +afterEach(() => { +}) + +describe('good conditions', () => { + it('makes some requests', async () => { + const asyncAssertions:Promise[] = []; + + asyncAssertions.push( + expect(provider.initialize()).resolves.toBeUndefined() + ); + + await vi.advanceTimersByTimeAsync(TimeUnit.HOUR + TimeUnit.SECOND) + + // the token ttl is one hour, since it renews at 80% of ttl, it will be fetched twice + expect(tokenEndpoint).toBeCalledTimes(2); + // since we fetch state every 30s we should fetch 120 times, but we also do an initial fetch in initialize + expect(stateUriEndpoint).toBeCalledTimes(121); + expect(stateEndpoint).toBeCalledTimes(121); + // flush is called every 10s so 360 times in an hour + expect(flushEndpoint).toBeCalledTimes(360); + vi.clearAllMocks(); + + asyncAssertions.push( + expect(provider.onClose()).resolves.toBeUndefined() + ); + + await vi.runAllTimersAsync(); + + // close does a final flush + expect(flushEndpoint).toBeCalledTimes(1); + + await Promise.all(asyncAssertions); + + }) +}) + +describe('no network', () => { + + beforeEach(() => { + // stateEndpoint.status = 'No network' + endpointMocks.forEach(em => { + em.status = 'No network' + }); + }); + + it('initialize throws after timeout', async () => { + const asyncAssertions:Promise[] = []; + + asyncAssertions.push( + expect(provider.initialize()).rejects.toThrow() + ); + + while(provider.status === 'NOT_READY' && Date.now() < TimeUnit.HOUR) { + await vi.advanceTimersToNextTimerAsync() + } + expect(Date.now()).toBe(DEFAULT_STATE_INTERVAL); + + await Promise.all(asyncAssertions); + }) + + +}) \ No newline at end of file diff --git a/openfeature-provider/js/src/ConfidenceServerProviderLocal.ts b/openfeature-provider/js/src/ConfidenceServerProviderLocal.ts new file mode 100644 index 0000000..03471b0 --- /dev/null +++ b/openfeature-provider/js/src/ConfidenceServerProviderLocal.ts @@ -0,0 +1,314 @@ +import type { + ErrorCode, + EvaluationContext, + JsonValue, + Provider, + ProviderMetadata, + ProviderStatus, + ResolutionDetails, + ResolutionReason, +} from '@openfeature/server-sdk'; +import { ResolveReason } from './proto/api'; +import { Fetch, FetchMiddleware, withAuth, withLogging, withResponse, withRetry, withRouter, withStallTimeout, withTimeout } from './fetch'; +import { scheduleWithFixedInterval, timeoutSignal, TimeUnit } from './util'; +import { AccessToken, LocalResolver, ResolveStateUri } from './LocalResolver'; + +export const DEFAULT_STATE_INTERVAL = 30_000; +export const DEFAULT_FLUSH_INTERVAL = 10_000; +export interface ProviderOptions { + flagClientSecret:string, + apiClientId:string, + apiClientSecret:string, + initializeTimeout?:number, + flushInterval?:number, + fetch?: typeof fetch, +} + +/** + * OpenFeature Provider for Confidence Server SDK (Local Mode) + * @public + */ +export class ConfidenceServerProviderLocal implements Provider { + /** Static data about the provider */ + readonly metadata: ProviderMetadata = { + name: 'ConfidenceServerProviderLocal', + }; + /** Current status of the provider. Can be READY, NOT_READY, ERROR, STALE and FATAL. */ + status = 'NOT_READY' as ProviderStatus; + + private readonly main = new AbortController(); + private readonly fetch:Fetch; + private readonly flushInterval:number; + private stateEtag:string | null = null; + + + // TODO Maybe pass in a resolver factory, so that we can initialize it in initialize and transition to fatal if not. + constructor(private resolver:LocalResolver, private options:ProviderOptions) { + // TODO better error handling + // TODO validate options + this.flushInterval = options.flushInterval ?? DEFAULT_FLUSH_INTERVAL; + const withConfidenceAuth = withAuth(async () => { + const { accessToken, expiresIn } = await this.fetchToken(); + return [accessToken, new Date(Date.now() + 1000*expiresIn)] + }, this.main.signal); + + const withFastRetry = FetchMiddleware.compose( + withRetry({ + maxAttempts: Infinity, + baseInterval: 300, + maxInterval: 5*TimeUnit.SECOND + }), + withTimeout(5*TimeUnit.SECOND) + ); + + this.fetch = Fetch.create([ + withRouter({ + 'https://iam.confidence.dev/v1/oauth/token': [ + withFastRetry + ], + 'https://storage.googleapis.com/*':[ + withRetry({ + maxAttempts: Infinity, + baseInterval: 500, + maxInterval: DEFAULT_STATE_INTERVAL, + }), + withStallTimeout(500) + ], + 'https://flags.confidence.dev/*|https://resolver.confidence.dev/*':[ + withConfidenceAuth, + withRouter({ + '*/v1/resolverState:resolverStateUri':[ + withFastRetry, + ], + '*/v1/flagLogs:write':[ + withRetry({ + maxAttempts: 3, + baseInterval: 500, + }), + withTimeout(5*TimeUnit.SECOND) + ] + }), + ], + // non-configured requests + '*': [withResponse((url) => { throw new Error(`Unknown route ${url}`)})] + }), + withLogging() + ], options.fetch ?? fetch); + } + + async initialize(context?: EvaluationContext): Promise { + // TODO validate options and switch to fatal. + const signal = this.main.signal; + const initialUpdateSignal = AbortSignal.any([signal, timeoutSignal(this.options.initializeTimeout ?? DEFAULT_STATE_INTERVAL)]); + try { + // TODO set schedulers irrespective of failure + // TODO if 403 here, + await this.updateState(initialUpdateSignal); + scheduleWithFixedInterval(signal => this.flush(signal), this.flushInterval, { maxConcurrent: 3, signal }); + // TODO Better with fixed delay so we don't do a double fetch when we're behind. Alt, skip if in progress + scheduleWithFixedInterval(signal => this.updateState(signal), DEFAULT_STATE_INTERVAL, { signal }); + this.status = 'READY' as ProviderStatus; + } catch(e:unknown) { + this.status = 'ERROR' as ProviderStatus; + // TODO should we swallow this? + throw e; + } + } + + onClose(): Promise { + this.main.abort(); + return this.flush(); + } + + // TODO test unknown flagClientSecret + evaluate(flagKey: string, defaultValue: T, context: EvaluationContext): ResolutionDetails { + + const [flagName, ...path] = flagKey.split('.') + const { resolvedFlags: [flag]} = this.resolver.resolveFlags({ + flags: [`flags/${flagName}`], + evaluationContext: ConfidenceServerProviderLocal.convertEvaluationContext(context), + apply: true, + clientSecret: this.options.flagClientSecret + }); + if(!flag) { + return { + value: defaultValue, + reason: 'ERROR', + errorCode: 'FLAG_NOT_FOUND' as ErrorCode + } + } + if(flag.reason != ResolveReason.RESOLVE_REASON_MATCH) { + return { + value: defaultValue, + reason: ConfidenceServerProviderLocal.convertReason(flag.reason), + } + } + let value:unknown = flag.value; + for(const step of path) { + if(typeof value !== 'object' || value === null || !hasKey(value, step)) { + return { + value: defaultValue, + reason: 'ERROR', + errorCode: 'TYPE_MISMATCH' as ErrorCode + } + } + value = value[step]; + } + if(!isAssignableTo(value, defaultValue)) { + return { + value: defaultValue, + reason: 'ERROR', + errorCode: 'TYPE_MISMATCH' as ErrorCode + } + } + return { + value, + reason: 'MATCH', + variant: flag.variant + }; + } + + async updateState(signal?:AbortSignal):Promise { + const { signedUri, account } = await this.fetchResolveStateUri(signal); + const headers = new Headers() + if(this.stateEtag) { + headers.set('If-None-Match', this.stateEtag); + } + const resp = await this.fetch(signedUri, { headers, signal }); + if(resp.status === 304) { + // not changed + return; + } + if(!resp.ok) { + throw new Error(`Failed to fetch state: ${resp.status} ${resp.statusText}`); + } + this.stateEtag = resp.headers.get('etag'); + const state = new Uint8Array(await resp.arrayBuffer()); + this.resolver.setResolverState({ + accountId: account, + state + }) + } + + async flush(signal?:AbortSignal):Promise { + const writeFlagLogRequest = this.resolver.flushLogs(); + if(writeFlagLogRequest.length == 0) { + // nothing to send + return; + } + await this.fetch('https://resolver.confidence.dev/v1/flagLogs:write', { + method: 'post', + signal, + headers: { + 'Content-Type': 'application/x-protobuf', + }, + body: writeFlagLogRequest as Uint8Array + }); + } + + private async fetchResolveStateUri(signal?: AbortSignal):Promise { + const resp = await this.fetch('https://flags.confidence.dev/v1/resolverState:resolverStateUri', { signal }); + if(!resp.ok) { + throw new Error('Failed to get resolve state url'); + } + return resp.json(); + } + + private async fetchToken():Promise { + const resp = await this.fetch('https://iam.confidence.dev/v1/oauth/token', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + clientId: this.options.apiClientId, + clientSecret: this.options.apiClientSecret, + grantType: 'client_credentials' + }) + }) + if(!resp.ok) { + throw new Error('Failed to fetch access token'); + } + return resp.json(); + } + + private static convertReason(reason:ResolveReason):ResolutionReason { + switch(reason) { + case ResolveReason.RESOLVE_REASON_ERROR: + return 'ERROR'; + case ResolveReason.RESOLVE_REASON_FLAG_ARCHIVED: + return 'FLAG_ARCHIVED'; + case ResolveReason.RESOLVE_REASON_MATCH: + return 'MATCH'; + case ResolveReason.RESOLVE_REASON_NO_SEGMENT_MATCH: + return 'NO_SEGMENT_MATCH'; + case ResolveReason.RESOLVE_REASON_TARGETING_KEY_ERROR: + return 'TARGETING_KEY_ERROR'; + case ResolveReason.RESOLVE_REASON_NO_TREATMENT_MATCH: + return 'NO_TREATMENT_MATCH'; + default: + return 'UNSPECIFIED' + } + } + + private static convertEvaluationContext({ targetingKey:targeting_key, ...rest}:EvaluationContext): { [key: string]: any } { + return { + targeting_key, ...rest + } + } + + /** Resolves with an evaluation of a Boolean flag */ + resolveBooleanEvaluation( + flagKey: string, + defaultValue: boolean, + context: EvaluationContext, + ): Promise> { + return Promise.resolve(this.evaluate(flagKey, defaultValue, context)); + } + /** Resolves with an evaluation of a Numbers flag */ + resolveNumberEvaluation( + flagKey: string, + defaultValue: number, + context: EvaluationContext, + ): Promise> { + return Promise.resolve(this.evaluate(flagKey, defaultValue, context)); + } + /** Resolves with an evaluation of an Object flag */ + resolveObjectEvaluation( + flagKey: string, + defaultValue: T, + context: EvaluationContext, + ): Promise> { + return Promise.resolve(this.evaluate(flagKey, defaultValue, context)); + } + /** Resolves with an evaluation of a String flag */ + resolveStringEvaluation( + flagKey: string, + defaultValue: string, + context: EvaluationContext, + ): Promise> { + return Promise.resolve(this.evaluate(flagKey, defaultValue, context)); + } +} + +function hasKey(obj:object, key:K): obj is { [P in K]: unknown } { + return key in obj; +} + +function isAssignableTo(value:unknown, schema:T): value is T { + if(typeof schema !== typeof value) return false; + if(typeof value === 'object' && typeof schema === 'object') { + if(schema === null) return value === null; + if(Array.isArray(schema)) { + if(!Array.isArray(value)) return false; + if(schema.length == 0) return true; + return value.every(item => isAssignableTo(item, schema[0])); + } + for(const [key, schemaValue] of Object.entries(schema)) { + if(!hasKey(value!, key)) return false; + if(!isAssignableTo(value[key], schemaValue)) return false; + } + } + return true; +} + diff --git a/openfeature-provider/js/src/LocalResolver.ts b/openfeature-provider/js/src/LocalResolver.ts new file mode 100644 index 0000000..664e5fa --- /dev/null +++ b/openfeature-provider/js/src/LocalResolver.ts @@ -0,0 +1,18 @@ +import type { ResolveFlagsRequest, ResolveFlagsResponse, SetResolverStateRequest } from "./proto/api" + +export interface LocalResolver { + resolveFlags(request: ResolveFlagsRequest): ResolveFlagsResponse + setResolverState(request: SetResolverStateRequest):void + flushLogs():Uint8Array +} + +export interface AccessToken { + accessToken: string, + /// lifetime seconds + expiresIn: number +} + +export interface ResolveStateUri { + signedUri:string, + account: string, +} diff --git a/openfeature-provider/js/src/WasmResolver.test.ts b/openfeature-provider/js/src/WasmResolver.test.ts new file mode 100644 index 0000000..b81293a --- /dev/null +++ b/openfeature-provider/js/src/WasmResolver.test.ts @@ -0,0 +1,95 @@ +import { beforeEach, describe, expect, it, test } from 'vitest'; +import { WasmResolver } from './WasmResolver'; +import { readFileSync } from 'node:fs'; +import { ResolveReason } from './proto/api'; +import { spawnSync } from 'node:child_process'; +import { error } from 'node:console'; +import { stderr } from 'node:process'; + +const moduleBytes = readFileSync(__dirname + '/../../../wasm/confidence_resolver.wasm'); +const stateBytes = readFileSync(__dirname + '/../../../wasm/resolver_state.pb'); + +const CLIENT_SECRET = 'mkjJruAATQWjeY7foFIWfVAcBWnci2YF'; + +let wasmResolver: WasmResolver; +beforeEach(async () => { + wasmResolver = await WasmResolver.load(new WebAssembly.Module(moduleBytes)); +}); + +it('should fail to resolve without state', () => { + expect(() => { + wasmResolver.resolveFlags({ flags: [], clientSecret: 'xyz', apply: false }); + }).toThrowError('Resolver state not set'); +}); + +describe('with state', () => { + beforeEach(() => { + wasmResolver.setResolverState({ state: stateBytes, accountId: 'confidence-test' }); + }); + + it('should resolve flags', () => { + try { + const resp = wasmResolver.resolveFlags({ + flags: ['flags/tutorial-feature'], + clientSecret: CLIENT_SECRET, + apply: true, + evaluationContext: { + targeting_key: 'tutorial_visitor', + visitor_id: 'tutorial_visitor', + }, + }); + + expect(resp).toMatchObject({ + resolvedFlags: [ + { + reason: ResolveReason.RESOLVE_REASON_MATCH, + }, + ], + }); + } catch (e) { + console.log('yo', e); + } + }); + + describe('flushLogs', () => { + + it('should be empty before any resolve', () => { + const logs = wasmResolver.flushLogs(); + expect(logs.length).toBe(0); + }) + + it('should contain logs after a resolve', () => { + wasmResolver.resolveFlags({ + flags: ['flags/tutorial-feature'], + clientSecret: CLIENT_SECRET, + apply: true, + evaluationContext: { + targeting_key: 'tutorial_visitor', + visitor_id: 'tutorial_visitor', + }, + }); + + const decoded = decodeBuffer(wasmResolver.flushLogs()); + + expect(decoded).contains('flag_assigned'); + expect(decoded).contains('client_resolve_info'); + expect(decoded).contains('flag_resolve_info'); + }) + }) +}); + + +function decodeBuffer(input:Uint8Array):string { + const res = spawnSync('protoc',[ + `-I${__dirname}/../../../confidence-resolver/protos`, + `--decode=confidence.flags.resolver.v1.WriteFlagLogsRequest`, + `confidence/flags/resolver/v1/internal_api.proto` + ], { input, encoding: 'utf8' }); + if(res.error) { + throw res.error; + } + if(res.status !== 0) { + throw new Error(res.stderr) + } + return res.stdout; +} \ No newline at end of file diff --git a/openfeature-provider/js/src/WasmResolver.ts b/openfeature-provider/js/src/WasmResolver.ts new file mode 100644 index 0000000..8361e5e --- /dev/null +++ b/openfeature-provider/js/src/WasmResolver.ts @@ -0,0 +1,92 @@ +import { BinaryWriter } from '@bufbuild/protobuf/wire'; +import { Request, Response, Void } from './proto/messages'; +import { Timestamp } from './proto/google/protobuf/timestamp'; +import { ResolveFlagsRequest, ResolveFlagsResponse, SetResolverStateRequest } from './proto/api'; +import { LocalResolver } from './LocalResolver'; + +type Codec = { + encode(message: T): BinaryWriter; + decode(input: Uint8Array): T; +}; + +export class WasmResolver implements LocalResolver { + private exports: any; + private imports: any; + + private constructor() { + this.imports = { + wasm_msg: { + wasm_msg_host_current_time: () => { + const ptr = this.transferRequest({ seconds: Date.now(), nanos: 0 }, Timestamp); + return ptr; + }, + }, + }; + } + + resolveFlags(request: ResolveFlagsRequest): ResolveFlagsResponse { + const reqPtr = this.transferRequest(request, ResolveFlagsRequest); + const resPtr = this.exports.wasm_msg_guest_resolve(reqPtr); + return this.consumeResponse(resPtr, ResolveFlagsResponse); + } + + setResolverState(request: SetResolverStateRequest): void { + const reqPtr = this.transferRequest(request, SetResolverStateRequest); + const resPtr = this.exports.wasm_msg_guest_set_resolver_state(reqPtr); + this.consumeResponse(resPtr, Void); + } + + flushLogs():Uint8Array { + const resPtr = this.exports.wasm_msg_guest_flush_logs(0); + const {data, error} = this.consume(resPtr, Response); + if(error) { + throw new Error(error); + } + return data!; + } + + private transferRequest(value: T, codec: Codec): number { + const data = codec.encode(value).finish(); + return this.transfer({ data }, Request); + } + + private consumeResponse(ptr: number, codec: Codec): T { + const { data, error }: Response = this.consume(ptr, Response); + if (error) { + throw new Error(error); + } + return codec.decode(data!); + } + + private transfer(data: T, codec: Codec): number { + const encoded = codec.encode(data).finish(); + const ptr = this.exports.wasm_msg_alloc(encoded.length); + this.viewBuffer(ptr).set(encoded); + return ptr; + } + + private consume(ptr: number, codec: Codec): T { + const data = this.viewBuffer(ptr); + // we need this defensive copy cause codec.decode might returns views into the buffer + const res = codec.decode(data.slice()); + this.free(ptr); + return res; + } + + private viewBuffer(ptr: number): Uint8Array { + const size = new DataView(this.exports.memory.buffer).getUint32(ptr - 4, true); + const data = new Uint8Array(this.exports.memory.buffer, ptr, size - 4); + return data; + } + + private free(ptr: number) { + this.exports.wasm_msg_free(ptr); + } + + static async load(module: WebAssembly.Module): Promise { + const wasmResolver = new WasmResolver(); + const instance = await WebAssembly.instantiate(module, wasmResolver.imports); + wasmResolver.exports = instance.exports; + return wasmResolver; + } +} diff --git a/openfeature-provider/js/src/defines.d.ts b/openfeature-provider/js/src/defines.d.ts new file mode 100644 index 0000000..3094a59 --- /dev/null +++ b/openfeature-provider/js/src/defines.d.ts @@ -0,0 +1,13 @@ +export {}; + +declare global { + /** + * If blocks guarded by __ASSERT__ will be folded away in builds. + */ + const __ASSERT__:boolean; + + /** + * True when running tests, false otherwise. + */ + const __TEST__:boolean; +} \ No newline at end of file diff --git a/openfeature-provider/js/src/env.ts b/openfeature-provider/js/src/env.ts new file mode 100644 index 0000000..b981be1 --- /dev/null +++ b/openfeature-provider/js/src/env.ts @@ -0,0 +1,3 @@ + + +export const isDev = process?.env?.NODE_ENV !== 'production'; \ No newline at end of file diff --git a/openfeature-provider/js/src/fetch.test.ts b/openfeature-provider/js/src/fetch.test.ts new file mode 100644 index 0000000..68ae743 --- /dev/null +++ b/openfeature-provider/js/src/fetch.test.ts @@ -0,0 +1,246 @@ +import { describe, it, expect, vi, beforeEach, afterEach, MockedFunction } from 'vitest'; +import { Fetch, FetchMiddleware, withAuth, withRetry, withTimeout, withRouter } from './fetch'; + +// Helpers +function mockedSink(status:number) : MockedFunction +function mockedSink(error:string) : MockedFunction +function mockedSink(impl:Fetch) : MockedFunction +function mockedSink(cfg:Fetch | number | string) : MockedFunction +{ + let impl:Fetch; + if(typeof cfg === 'number') { + impl = async () => new Response(null, { status: cfg }); + } + else if(typeof cfg === 'string') { + impl = () => { throw new Error(cfg) } + } + else { + impl = cfg; + } + return vi.fn(impl) +} + +function textStream(text:string): ReadableStream { + const encoder = new TextEncoder(); + return new ReadableStream({ + start(controller) { + controller.enqueue(encoder.encode(text)); + controller.close(); + } + }); +} + +async function bodyText(body:BodyInit | null | undefined): Promise { + if(body == null) return ''; + const res = new Response(body); + return await res.text(); +} + +describe('fetch middlewares', () => { + + describe('Fetch.create composition', () => { + it('wraps middlewares right-to-left', async () => { + const order:string[] = []; + const m1:FetchMiddleware = next => async (url, init) => { + order.push('m1-pre'); + const r = await next(url, init); + order.push('m1-post'); + return r; + } + const m2:FetchMiddleware = next => async (url, init) => { + order.push('m2-pre'); + const r = await next(url, init); + order.push('m2-post'); + return r; + } + const sink = mockedSink(200); + const f = Fetch.create([m1, m2], sink); + const resp = await f('http://example.test'); + expect(resp.status).toBe(200); + expect(order).toEqual(['m1-pre','m2-pre','m2-post','m1-post']); + }); + }); + + describe('withTimeout', () => { + it('aborts the request after timeout (or skips if unsupported)', async () => { + const sink = mockedSink((_url, init) => new Promise((_resolve, reject) => { + init?.signal?.addEventListener('abort', () => reject(init.signal?.reason)); + })); + const f = Fetch.create([withTimeout(15)], sink); + const p = f('http://example.test'); + const guard = new Promise((_r, reject) => setTimeout(() => reject(new Error('timeout waiting for abort')), 500)); + await expect(Promise.race([p as Promise, guard])).rejects.toBeDefined(); + }); + }); + + describe('withRetry', () => { + it('retries on 5xx and eventually succeeds', async () => { + const calls:number[] = []; + const sink = mockedSink(async () => { + calls.push(Date.now()); + return calls.length === 1 + ? new Response(null, { status: 500 }) + : new Response(null, { status: 200 }); + }); + const f = Fetch.create([withRetry({ baseInterval: 1, jitter: 0 })], sink); + const resp = await f('http://retry.test'); + expect(resp.status).toBe(200); + }); + + it('uses Retry-After header when provided (HTTP-date small delta)', async () => { + let attempt = 0; + const sink = mockedSink(async () => { + attempt++; + if(attempt === 1) { + const soon = new Date(Date.now() + 20).toUTCString(); + return new Response(null, { status: 503, headers: { 'Retry-After': soon } as any }); + } + return new Response(null, { status: 200 }); + }); + const f = Fetch.create([withRetry({ baseInterval: 1, maxInterval: 10_000, jitter: 0 })], sink); + const resp = await f('http://retry-after.test'); + expect(resp.status).toBe(200); + }); + + it('replays a ReadableStream body across retries', async () => { + const seen:string[] = []; + let attempt = 0; + const sink = mockedSink(async (_url, init) => { + seen.push(await bodyText(init?.body ?? null)); + attempt++; + return attempt === 1 + ? new Response(null, { status: 500 }) + : new Response(null, { status: 200 }); + }); + const f = Fetch.create([withRetry({ baseInterval: 1, jitter: 0 })], sink); + const body = textStream('hello'); + const resp = await f('http://body.test', { method: 'POST', body }); + expect(resp.status).toBe(200); + expect(seen).toEqual(['hello','hello']); + }); + + it('aborts during backoff if signal aborted', async () => { + let attempt = 0; + const sink = mockedSink(async () => { + attempt++; + return attempt === 1 + ? new Response(null, { status: 500 }) + : new Response(null, { status: 200 }); + }); + const f = Fetch.create([withRetry({ baseInterval: 10, jitter: 0 })], sink); + const c = new AbortController(); + const p = f('http://abort.test', { signal: c.signal }); + // abort before advancing time into the backoff sleep + c.abort(new Error('boom')); + await expect(p).rejects.toBeDefined(); + }); + }); + + describe('withAuth', () => { + it('adds Authorization header and retries once on 401 with renewed token', async () => { + const calls:{ auth?:string }[] = []; + let nextStatus = 401; + const sink = mockedSink(async (_url, init) => { + calls.push({ auth: (init?.headers instanceof Headers) ? init.headers.get('Authorization') ?? undefined : new Headers(init?.headers as any).get('Authorization') ?? undefined }); + const r = new Response(null, { status: nextStatus }); + nextStatus = 200; + return r; + }); + + let tokenGen = 0; + const tokenProvider = async () => { + tokenGen += 1; + return [`t${tokenGen}`, new Date(Date.now() + 60_000)] as [string, Date]; + } + + const f = Fetch.create([withAuth(tokenProvider)], sink); + const resp = await f('http://auth.test'); + expect(resp.status).toBe(200); + expect(calls.map(c => c.auth)).toEqual(['Bearer t1','Bearer t2']); + }); + }); + + describe('withRouter', () => { + it('matches simple trailing * (prefix match)', async () => { + const sink = mockedSink(200); + const routed = Fetch.create([ + withRouter({ + 'https://api.example.com/v1/items/*': [next => next], + }) + ], sink); + const ok = await routed('https://api.example.com/v1/items/123'); + expect(ok.status).toBe(200); + }); + + it('matches across segments via anchored regex', async () => { + const sink = mockedSink(200); + const routed = Fetch.create([ + withRouter({ + '^https://api\\.example\\.com/.*/metrics$': [next => next], + }) + ], sink); + const ok = await routed('https://api.example.com/a/b/metrics'); + expect(ok.status).toBe(200); + }); + + it('matches exactly one segment via anchored regex', async () => { + const sink = mockedSink(200); + const routed = Fetch.create([ + withRouter({ + '^https://api\\.example\\.com/v1/[^/]+/metrics$': [next => next], + }) + ], sink); + const ok = await routed('https://api.example.com/v1/users/metrics'); + expect(ok.status).toBe(200); + }); + + it('matches zero or more segments via anchored regex', async () => { + const sink = mockedSink(200); + const routed = Fetch.create([ + withRouter({ + '^https://api\\.example\\.com(?:/[^/]+)*/metrics/[^/]+$': [next => next], + }) + ], sink); + const ok1 = await routed('https://api.example.com/metrics/x'); + const ok2 = await routed('https://api.example.com/a/b/metrics/x'); + expect(ok1.status).toBe(200); + expect(ok2.status).toBe(200); + }); + + it('supports leading single * (suffix match) and regex alternative', async () => { + const sink = mockedSink(200); + const routed = Fetch.create([ + withRouter({ + '*/health': [next => next], + '^https://[^/]+/v1/[^/]+$': [next => next], + }) + ], sink); + const ok1 = await routed('https://service/foo/health'); + const ok2 = await routed('https://x.example/v1/y'); + expect(ok1.status).toBe(200); + expect(ok2.status).toBe(200); + }); + + it('supports catch-all *', async () => { + const sink = mockedSink(200); + const routed = Fetch.create([ + withRouter({ '*': [next => next] }) + ], sink); + const res = await routed('https://anything.example/path'); + expect(res.status).toBe(200); + }); + + it('falls through to sink on no match', async () => { + const route = mockedSink('err') + const sink = mockedSink(200); + const routed = Fetch.create([ + withRouter({ 'https://api.example.com/v1/*': [ next => route] }) + ], sink); + const res = await routed('https://other.example.com/v1/a'); + expect(sink).toHaveBeenCalledTimes(1); + expect(route).not.toBeCalled(); + }); + }); + + +}); diff --git a/openfeature-provider/js/src/fetch.ts b/openfeature-provider/js/src/fetch.ts new file mode 100644 index 0000000..1eff09b --- /dev/null +++ b/openfeature-provider/js/src/fetch.ts @@ -0,0 +1,282 @@ +import { logger as rootLogger, Logger } from "./logger"; +import { portableSetTimeout, abortableSleep, abortablePromise } from "./util"; + +const logger = rootLogger.getLogger('fetch'); + +export type Fetch = (url:string, init?: RequestInit) => Promise; + +export namespace Fetch { + + export function create(middleware:FetchMiddleware[], sink:Fetch = fetch):Fetch { + return middleware.reduceRight((next, middleware) => middleware(next), sink); + } +} + +export type FetchMiddleware = (next:Fetch) => Fetch; + +export namespace FetchMiddleware { + + export function compose(outer:FetchMiddleware, inner:FetchMiddleware):FetchMiddleware { + return next => outer(inner(next)) + } +} + +export function withTimeout(timeoutMs:number) : FetchMiddleware { + return next => (url, init = {}) => { + const signal = timeoutSignal(timeoutMs, init.signal); + return next(url, { ...init, signal }); + } +} + +export function withStallTimeout(stallTimeoutMs:number) : FetchMiddleware { + return next => async (url, init = {}) => { + const ac = new AbortController(); + const signal = init.signal ? AbortSignal.any([ac.signal, init.signal]) : ac.signal; + const abort = () => { + ac.abort(new Error(`This operation timed out after not receiving data for ${stallTimeoutMs}ms`)); + } + let timeoutId = portableSetTimeout(abort, stallTimeoutMs); + + + const resp = await next(url, { ...init, signal }); + clearTimeout(timeoutId); + if(resp.body) { + timeoutId = portableSetTimeout(abort, stallTimeoutMs); + + const touch = () => { + clearTimeout(timeoutId); + timeoutId = portableSetTimeout(abort, stallTimeoutMs); + } + const [body, copy] = resp.body.tee(); + consumeReadableStream(copy, touch); + + return new Response(body, { + status: resp.status, + statusText: resp.statusText, + headers: resp.headers, + }); + } + return resp; + } +} + +export function withRetry(opts?:{ + maxAttempts?: number; + baseInterval?: number; + maxInterval?: number; + abortAtInterval?: boolean, + backoff?: number; + jitter?: number; +}) : FetchMiddleware { + const maxAttempts = opts?.maxAttempts ?? 6; + const baseInterval = opts?.baseInterval ?? 250; + const maxInterval = opts?.maxInterval ?? 30_000; + const backoff = opts?.backoff ?? 2; + const jitter = opts?.jitter ?? (__TEST__ ? 0 : 0.1); + + return next => async (url, { body, signal, ...init} = {}) => { + const cloneBody = await bodyRepeater(body); + let attempts = 0; + let deadline = 0; + + const calculateDeadline = ():number => { + const jitterFactor = 1 + 2 * Math.random() * jitter - jitter; + const delay = jitterFactor * Math.min(maxInterval, baseInterval * Math.pow(backoff, attempts)); + return Date.now() + delay; + } + const onSuccess = async (resp:Response) => { + const { status, statusText } = resp; + if(status !== 408 && status !== 429 && status < 500 || attempts >= maxAttempts) { + return resp; + } + logger.debug('withRetry %s failed attempt %d with %d %s', url, attempts - 1, status, statusText); + const serverDelay = parseRetryAfter(resp.headers.get('Retry-After'), baseInterval, maxInterval); + + await abortableSleep(serverDelay ?? (deadline - Date.now()), signal); + return doTry(); + } + const onError = async (error:unknown) => { + logger.debug('withRetry %s failed attempt %d with %s', url, attempts - 1, error); + if(signal?.aborted || attempts >= maxAttempts) { + throw error; + } + await abortableSleep(deadline - Date.now(), signal); + return doTry(); + } + const doTry = ():Promise => { + let attemptSignal = signal; + deadline = calculateDeadline(); + attempts++; + if(opts?.abortAtInterval) { + attemptSignal = timeoutSignal(deadline - Date.now(), signal); + } + return next(url, { body: cloneBody(), signal:attemptSignal, ...init}).then(onSuccess, onError) + }; + + return doTry(); + } +} + +export function withAuth(tokenProvider: () => Promise<[token:string, expiry?:Date]>, signal?:AbortSignal): FetchMiddleware { + + let renewTimeout = 0; + let current:Promise | null = null; + + signal?.addEventListener('abort', () => { + clearTimeout(renewTimeout); + }) + + const renewToken = () => { + logger.debug("withAuth renewing token"); + clearTimeout(renewTimeout); + current = tokenProvider().then(([token, expiry]) => { + logger.debug("withAuth renew success %s", expiry && (expiry.valueOf() - Date.now())); + if(expiry) { + const ttl = expiry.valueOf() - Date.now(); + renewTimeout = portableSetTimeout(renewToken, 0.8*ttl); + } + return token; + }).catch(e => { + current = null; + throw e; + }); + } + + const fetchWithToken = async (fetch:Fetch, url:string, init:RequestInit) => { + const token = await abortablePromise(current!, init.signal); + const headers = new Headers(init.headers); + headers.set('Authorization', `Bearer ${token}`); + return fetch(url, { ...init, headers }) + } + + return next => async (url, init = {}) => { + const bodyClone = await bodyRepeater(init.body); + if(!current) { + renewToken(); + } + const currentBeforeFetch = current; + let resp = await fetchWithToken(next, url, { ...init, body: bodyClone() }); + if(resp.status === 401) { + // there might be a race of multiple simultaneous 401 + if(current === currentBeforeFetch) { + renewToken(); + } + // do one quick retry on 401 + resp = await fetchWithToken(next, url, { ...init, body: bodyClone() }); + } + return resp; + } +} + +export function withRouter(routes:Record):FetchMiddleware { + // Simplified DSL over full URL string: + // - Anchored regex when pattern starts with ^ and ends with $ + // - '*' alone => match all + // - Leading single '*' => suffix match + // - Trailing single '*' => prefix match + // - Otherwise exact match (any other '*' usage is unsupported) + const hasOnlyOneStar = (s:string) => (s.split('*').length - 1) === 1; + const compile = (pattern:string):((url:string)=>boolean) => { + if(pattern.length >= 2 && pattern[0] === '^' && pattern[pattern.length-1] === '$') { + const rx = new RegExp(pattern); + return url => rx.test(url); + } + if(pattern === '*') { + return _ => true; + } + if(pattern.includes('|')) { + const predicates = pattern.split('|').map(compile); + return url => predicates.some(pred => pred(url)) + } + if(pattern.startsWith('*') && hasOnlyOneStar(pattern)) { + const suffix = pattern.slice(1); + return url => url.endsWith(suffix); + } + if(pattern.endsWith('*') && hasOnlyOneStar(pattern)) { + const prefix = pattern.slice(0, -1); + return url => url.startsWith(prefix); + } + if(pattern.includes('*')) { + throw new Error(`withRouter unsupported pattern "${pattern}". Only single leading or trailing * (or * alone) supported.`) + } + return url => url === pattern; + }; + const preCompiled = Object.entries(routes) + .map(([pattern, middlewares]):[(url:string)=>boolean, FetchMiddleware[]] => { + const predicate = compile(pattern); + return [predicate, middlewares]; + }) + + return next => { + + const table = preCompiled + .map(([predicate, middlewares]):[(url:string)=>boolean, Fetch] => { + const fetch = Fetch.create(middlewares, next); + return [predicate, fetch]; + }); + + return async (url, init = {}) => { + const match = table.find(([pred]) => pred(url)); + if(!match) { + logger.info('withRouter no route matched %s, falling through', url); + return next(url, init); + } + return match[1](url, init); + } + } +} + +export function withResponse(factory:(url:string, init?:RequestInit) => Promise): FetchMiddleware { + return _next => factory; +} + +const fetchLogger = logger; +export function withLogging(logger:Logger = fetchLogger):FetchMiddleware { + return next => async (url, init) => { + const start = Date.now(); + const resp = await next(url, init); + const duration = Date.now() - start; + logger.info('%s %s (%i) %dms', (init?.method ?? 'get').toUpperCase(), url.split('?', 1)[0], resp.status, duration); + return resp; + } +} + +async function bodyRepeater(body:T): Promise<() => T> { + if(body instanceof ReadableStream) { + // TODO this case could be made a little more efficient by body.tee, + // but we don't use ReadableStreams, so low prio + const blob = await new Response(body).blob(); + return () => blob.stream() as T + } + return () => body; +} + +function parseRetryAfter(retryAfterValue:string | null, min = 0, max = Number.MAX_SAFE_INTEGER):number | undefined { + if(retryAfterValue) { + let delay = Number(retryAfterValue)*1000; + if(Number.isNaN(delay)) { + delay = Date.parse(retryAfterValue) - Date.now(); + } + if(Number.isFinite(delay) && delay > 0) { + return Math.max(min, Math.min(delay, max)); + } + } + return undefined; +} + +async function consumeReadableStream(stream:ReadableStream, onData:(chunk:Uint8Array) => void, onEnd?:(err?:unknown) => void):Promise { + try { + for await(const chunk of stream) { + onData(chunk); + } + onEnd?.(); + } catch(e:unknown) { + onEnd?.(e); + } +} + +function timeoutSignal(delay:number, signal?:AbortSignal | null):AbortSignal { + const ac = new AbortController(); + portableSetTimeout(() => ac.abort(new Error(`Operation timed out after ${delay}ms`)), delay); + return signal ? AbortSignal.any([signal, ac.signal]) : ac.signal; +} \ No newline at end of file diff --git a/openfeature-provider/js/src/index.browser.ts b/openfeature-provider/js/src/index.browser.ts new file mode 100644 index 0000000..6374e61 --- /dev/null +++ b/openfeature-provider/js/src/index.browser.ts @@ -0,0 +1,11 @@ +import { ConfidenceServerProviderLocal, ProviderOptions } from './ConfidenceServerProviderLocal'; +import { WasmResolver } from './WasmResolver'; + +const wasmUrl = new URL('confidence_resolver.wasm', import.meta.url); + +const module = await WebAssembly.compileStreaming(fetch(wasmUrl)); +const resolver = await WasmResolver.load(module); + +export function createConfidenceServerProvider(options:ProviderOptions):ConfidenceServerProviderLocal { + return new ConfidenceServerProviderLocal(resolver, options) +} \ No newline at end of file diff --git a/openfeature-provider/js/src/index.node.ts b/openfeature-provider/js/src/index.node.ts new file mode 100644 index 0000000..1da0fcf --- /dev/null +++ b/openfeature-provider/js/src/index.node.ts @@ -0,0 +1,13 @@ +import fs from 'node:fs/promises' +import { ConfidenceServerProviderLocal, ProviderOptions } from './ConfidenceServerProviderLocal'; +import { WasmResolver } from './WasmResolver'; + +const wasmPath = require.resolve('./confidence_resolver.wasm'); +const buffer = await fs.readFile(wasmPath); + +const module = await WebAssembly.compile(buffer as BufferSource); +const resolver = await WasmResolver.load(module); + +export function createConfidenceServerProvider(options:ProviderOptions):ConfidenceServerProviderLocal { + return new ConfidenceServerProviderLocal(resolver, options) +} \ No newline at end of file diff --git a/openfeature-provider/js/src/logger.ts b/openfeature-provider/js/src/logger.ts new file mode 100644 index 0000000..14824af --- /dev/null +++ b/openfeature-provider/js/src/logger.ts @@ -0,0 +1,73 @@ +const NOOP_LOG_FN = () => {}; +export type LogFn = (msg:string, ...rest:any[]) => void; + +type Debug = (typeof import('debug'))['default']; + +const debugBackend = loadDebug(); + +export interface Logger { + debug(msg:string, ...args:any[]):void; + info(msg:string, ...args:any[]):void; + warn(msg:string, ...args:any[]):void; + error(msg:string, ...args:any[]):void; + + readonly name:string; + + getLogger(name:string):Logger; +} + +class LoggerImpl implements Logger { + private readonly childLoggers = new Map(); + + debug: LogFn = NOOP_LOG_FN; + info: LogFn = NOOP_LOG_FN; + warn: LogFn = NOOP_LOG_FN; + error: LogFn = NOOP_LOG_FN; + + + constructor(readonly name:string) { + this.configure(); + } + + async configure() { + // TODO we should queue messages logged before configure is done + const debug = await debugBackend; + if(!debug) return; + const debugFn = this.debug = debug(this.name + ":debug") + const infoFn = this.info = debug(this.name + ":info") + const warnFn = this.warn = debug(this.name + ":warn") + const errorFn = this.error = debug(this.name + ":error"); + + switch(true) { + case debugFn.enabled: + infoFn.enabled = true; + case infoFn.enabled: + warnFn.enabled = true; + case warnFn.enabled: + errorFn.enabled = true; + } + } + + getLogger(name: string): Logger { + let child = this.childLoggers.get(name); + if(!child) { + child = new LoggerImpl(this.name + ":" + name); + this.childLoggers.set(name, child); + } + return child; + } +} + +export const logger = new LoggerImpl('cnfd'); + + +async function loadDebug():Promise { + try { + const { default:debug } = await import('debug'); + return debug; + } + catch(e) { + // debug not available + return null; + } +} diff --git a/openfeature-provider/js/src/util.ts b/openfeature-provider/js/src/util.ts new file mode 100644 index 0000000..5940e07 --- /dev/null +++ b/openfeature-provider/js/src/util.ts @@ -0,0 +1,130 @@ +import {logger} from "./logger"; + +export const enum TimeUnit { + SECOND = 1000, + MINUTE = 1000*60, + HOUR = 1000*60*60, + DAY = 1000*60*60*24, +} +export function scheduleWithFixedDelay(operation:(signal?:AbortSignal) => unknown, delayMs:number):() => void { + const ac = new AbortController(); + let nextRunTimeoutId = 0; + + const run = async () => { + try { + await operation(ac.signal); + } catch(e:unknown) { + logger.warn('scheduleWithFixedDelay failure:', e); + } + nextRunTimeoutId = portableSetTimeout(run, delayMs); + } + + nextRunTimeoutId = portableSetTimeout(run, delayMs); + return () => { + clearTimeout(nextRunTimeoutId); + ac.abort(); + } +} + +export function scheduleWithFixedInterval(operation:(signal?:AbortSignal) => unknown, intervalMs:number, opt: { maxConcurrent?:number, signal?:AbortSignal } = {}):() => void { + const maxConcurrent = opt.maxConcurrent ?? 1; + const ac = new AbortController(); + let nextRunTimeoutId = 0; + let lastRunTime = 0; + let concurrent = 0; + + if(__ASSERT__) { + if(!Number.isInteger(maxConcurrent) || maxConcurrent < 1) { + throw new Error(`maxConcurrent must be an integer greater than zero, was ${maxConcurrent}`); + } + } + + const run = async () => { + lastRunTime = Date.now(); + nextRunTimeoutId = portableSetTimeout(run, intervalMs); + if(concurrent >= maxConcurrent) { + return; + } + concurrent++; + try { + await operation(ac.signal); + } catch(e:unknown) { + logger.warn('scheduleWithFixedInterval failure:', e); + } + concurrent--; + const timeSinceLast = Date.now() - lastRunTime; + if(timeSinceLast > intervalMs && nextRunTimeoutId != 0) { + clearTimeout(nextRunTimeoutId); + run(); + } + } + + nextRunTimeoutId = portableSetTimeout(run, intervalMs); + + const stop = () => { + clearTimeout(nextRunTimeoutId); + nextRunTimeoutId = 0; + ac.abort(); + }; + opt.signal?.addEventListener('abort', stop); + return stop; +} + +export function timeoutSignal(milliseconds:number):AbortSignal { + const ac = new AbortController(); + portableSetTimeout(() => { + ac.abort(new Error('Timeout')); + // ac.abort('Timeout'); + }, milliseconds, { unref: false }) + return ac.signal; +} + +export function portableSetTimeout(callback:() => void, milliseconds:number, opt:{ unref?:boolean } = {}):number { + const timeout:unknown = setTimeout(callback, milliseconds); + if(opt.unref && typeof timeout === 'object' && timeout !== null && 'unref' in timeout && typeof timeout.unref === 'function') { + timeout.unref(); + } + return Number(timeout); +} + +export function abortableSleep(milliseconds:number, signal?:AbortSignal | null):Promise { + if(signal?.aborted) { + return Promise.reject(signal.reason) + } + if(milliseconds <= 0) { + return Promise.resolve() + } + if(milliseconds === Infinity) { + return signal ? promiseSignal(signal) : new Promise(() => {}); + } + return new Promise((resolve, reject) => { + let timeout:number; + const onTimeout = () => { + cleanup(); + resolve(); + } + const onAbort = () => { + cleanup(); + reject(signal?.reason); + } + const cleanup = () => { + clearTimeout(timeout); + signal?.removeEventListener('abort', onAbort); + } + + signal?.addEventListener('abort', onAbort); + timeout = portableSetTimeout(onTimeout, milliseconds); + }) +} + +export function promiseSignal(signal:AbortSignal):Promise { + if(signal.aborted) { + return Promise.reject(signal.reason); + } + return new Promise((_,reject) => { + signal.addEventListener('abort', () => { reject(signal.aborted) }, { once: true }); + }) +} +export function abortablePromise(promise:Promise, signal?: AbortSignal | null | undefined):Promise { + return signal ? Promise.race([promise, promiseSignal(signal)]) : promise; +} diff --git a/openfeature-provider/js/tsconfig.json b/openfeature-provider/js/tsconfig.json new file mode 100644 index 0000000..c76221d --- /dev/null +++ b/openfeature-provider/js/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "types": ["node"] + }, + "include": ["src/**/*"], +} diff --git a/openfeature-provider/js/tsdown.config.ts b/openfeature-provider/js/tsdown.config.ts new file mode 100644 index 0000000..a019aba --- /dev/null +++ b/openfeature-provider/js/tsdown.config.ts @@ -0,0 +1,30 @@ +import { defineConfig } from 'tsdown' + + +const base = defineConfig({ + minify: 'dce-only', + dts: { + oxc: true, + }, + define: { + __ASSERT__: 'false', + __TEST__: 'false', + }, + external: ['@bufbuild/protobuf/wire'], + // inputOptions: { + // moduleTypes: { + // '.wasm':'asset' + // } + // }, +}); + +export default defineConfig([{ + entry: './src/index.node.ts', + platform: 'node', + copy: ['../../wasm/confidence_resolver.wasm'], + ...base +},{ + entry: './src/index.browser.ts', + platform: 'browser', + ...base +}]) diff --git a/openfeature-provider/js/vitest.config.ts b/openfeature-provider/js/vitest.config.ts new file mode 100644 index 0000000..99d5dc1 --- /dev/null +++ b/openfeature-provider/js/vitest.config.ts @@ -0,0 +1,34 @@ +import { defineConfig } from 'vitest/config' +import { config, parse } from 'dotenv'; +import { existsSync, readFileSync } from 'fs'; + + +export default defineConfig({ + define: { + __TEST__:'true', + __ASSERT__:'true', + }, + test: { + environment: 'node', + globals: false, + include: ['src/**/*.{test,spec}.ts'], + silent: false, + watch: false, + env: { + ...readEnv('.env.test') + } + } +}) + +function readEnv(file):Record { + try { + const buf = readFileSync(file); + return parse(buf); + } catch(e) { + if(e.code === 'ENOENT') { + console.log('could not find', file); + return {}; + } + throw e; + } +} \ No newline at end of file diff --git a/openfeature-provider/js/yarn.lock b/openfeature-provider/js/yarn.lock new file mode 100644 index 0000000..a2e2177 --- /dev/null +++ b/openfeature-provider/js/yarn.lock @@ -0,0 +1,2618 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@ampproject/remapping@npm:^2.3.0": + version: 2.3.0 + resolution: "@ampproject/remapping@npm:2.3.0" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/81d63cca5443e0f0c72ae18b544cc28c7c0ec2cea46e7cb888bb0e0f411a1191d0d6b7af798d54e30777d8d1488b2ec0732aac2be342d3d7d3ffd271c6f489ed + languageName: node + linkType: hard + +"@babel/generator@npm:^7.28.3": + version: 7.28.3 + resolution: "@babel/generator@npm:7.28.3" + dependencies: + "@babel/parser": "npm:^7.28.3" + "@babel/types": "npm:^7.28.2" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10c0/0ff58bcf04f8803dcc29479b547b43b9b0b828ec1ee0668e92d79f9e90f388c28589056637c5ff2fd7bcf8d153c990d29c448d449d852bf9d1bc64753ca462bc + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-string-parser@npm:7.27.1" + checksum: 10c0/8bda3448e07b5583727c103560bcf9c4c24b3c1051a4c516d4050ef69df37bb9a4734a585fe12725b8c2763de0a265aa1e909b485a4e3270b7cfd3e4dbe4b602 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-identifier@npm:7.27.1" + checksum: 10c0/c558f11c4871d526498e49d07a84752d1800bf72ac0d3dad100309a2eaba24efbf56ea59af5137ff15e3a00280ebe588560534b0e894a4750f8b1411d8f78b84 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.25.4, @babel/parser@npm:^7.28.0, @babel/parser@npm:^7.28.3, @babel/parser@npm:^7.28.4": + version: 7.28.4 + resolution: "@babel/parser@npm:7.28.4" + dependencies: + "@babel/types": "npm:^7.28.4" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/58b239a5b1477ac7ed7e29d86d675cc81075ca055424eba6485872626db2dc556ce63c45043e5a679cd925e999471dba8a3ed4864e7ab1dbf64306ab72c52707 + languageName: node + linkType: hard + +"@babel/types@npm:^7.25.4, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4": + version: 7.28.4 + resolution: "@babel/types@npm:7.28.4" + dependencies: + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + checksum: 10c0/ac6f909d6191319e08c80efbfac7bd9a25f80cc83b43cd6d82e7233f7a6b9d6e7b90236f3af7400a3f83b576895bcab9188a22b584eb0f224e80e6d4e95f4517 + languageName: node + linkType: hard + +"@bcoe/v8-coverage@npm:^1.0.2": + version: 1.0.2 + resolution: "@bcoe/v8-coverage@npm:1.0.2" + checksum: 10c0/1eb1dc93cc17fb7abdcef21a6e7b867d6aa99a7ec88ec8207402b23d9083ab22a8011213f04b2cf26d535f1d22dc26139b7929e6c2134c254bd1e14ba5e678c3 + languageName: node + linkType: hard + +"@bufbuild/protobuf@npm:^2.0.0": + version: 2.8.0 + resolution: "@bufbuild/protobuf@npm:2.8.0" + checksum: 10c0/970faf0b58fd10c3c650224fda51f471b0f2005222fb33b43e6f8894478bc905b5a3bc0577cd7539afd7d2493fa6a6e588b89e67bdabd2750acf26cb40da237f + languageName: node + linkType: hard + +"@bufbuild/protobuf@npm:^2.9.0": + version: 2.9.0 + resolution: "@bufbuild/protobuf@npm:2.9.0" + checksum: 10c0/fe46723c12204c00ff1f4eefb3636a7e7ad9e3c87736ed92e9de77aad6f29edae9b903a1517bb0cd8d1b24da46934ab1ae7acebc04c151b8f3a6151b7583f20f + languageName: node + linkType: hard + +"@emnapi/core@npm:^1.5.0": + version: 1.5.0 + resolution: "@emnapi/core@npm:1.5.0" + dependencies: + "@emnapi/wasi-threads": "npm:1.1.0" + tslib: "npm:^2.4.0" + checksum: 10c0/52ba3485277706d92fa27d92b37e5b4f6ef0742c03ed68f8096f294c6bfa30f0752c82d4c2bfa14bff4dc30d63c9f71a8f9fb64a92743d00807d9e468fafd5ff + languageName: node + linkType: hard + +"@emnapi/runtime@npm:^1.5.0": + version: 1.5.0 + resolution: "@emnapi/runtime@npm:1.5.0" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/a85c9fc4e3af49cbe41e5437e5be2551392a931910cd0a5b5d3572532786927810c9cc1db11b232ec8f9657b33d4e6f7c4f985f1a052917d7cd703b5b2a20faa + languageName: node + linkType: hard + +"@emnapi/wasi-threads@npm:1.1.0": + version: 1.1.0 + resolution: "@emnapi/wasi-threads@npm:1.1.0" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/e6d54bf2b1e64cdd83d2916411e44e579b6ae35d5def0dea61a3c452d9921373044dff32a8b8473ae60c80692bdc39323e98b96a3f3d87ba6886b24dd0ef7ca1 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/aix-ppc64@npm:0.25.9" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/android-arm64@npm:0.25.9" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/android-arm@npm:0.25.9" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/android-x64@npm:0.25.9" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/darwin-arm64@npm:0.25.9" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/darwin-x64@npm:0.25.9" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/freebsd-arm64@npm:0.25.9" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/freebsd-x64@npm:0.25.9" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/linux-arm64@npm:0.25.9" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/linux-arm@npm:0.25.9" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/linux-ia32@npm:0.25.9" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/linux-loong64@npm:0.25.9" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/linux-mips64el@npm:0.25.9" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/linux-ppc64@npm:0.25.9" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/linux-riscv64@npm:0.25.9" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/linux-s390x@npm:0.25.9" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/linux-x64@npm:0.25.9" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/netbsd-arm64@npm:0.25.9" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/netbsd-x64@npm:0.25.9" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/openbsd-arm64@npm:0.25.9" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/openbsd-x64@npm:0.25.9" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/openharmony-arm64@npm:0.25.9" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/sunos-x64@npm:0.25.9" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/win32-arm64@npm:0.25.9" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/win32-ia32@npm:0.25.9" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.25.9": + version: 0.25.9 + resolution: "@esbuild/win32-x64@npm:0.25.9" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@isaacs/cliui@npm:^8.0.2": + version: 8.0.2 + resolution: "@isaacs/cliui@npm:8.0.2" + dependencies: + string-width: "npm:^5.1.2" + string-width-cjs: "npm:string-width@^4.2.0" + strip-ansi: "npm:^7.0.1" + strip-ansi-cjs: "npm:strip-ansi@^6.0.1" + wrap-ansi: "npm:^8.1.0" + wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" + checksum: 10c0/b1bf42535d49f11dc137f18d5e4e63a28c5569de438a221c369483731e9dac9fb797af554e8bf02b6192d1e5eba6e6402cf93900c3d0ac86391d00d04876789e + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 + languageName: node + linkType: hard + +"@istanbuljs/schema@npm:^0.1.2": + version: 0.1.3 + resolution: "@istanbuljs/schema@npm:0.1.3" + checksum: 10c0/61c5286771676c9ca3eb2bd8a7310a9c063fb6e0e9712225c8471c582d157392c88f5353581c8c9adbe0dff98892317d2fdfc56c3499aa42e0194405206a963a + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.13 + resolution: "@jridgewell/gen-mapping@npm:0.3.13" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/9a7d65fb13bd9aec1fbab74cda08496839b7e2ceb31f5ab922b323e94d7c481ce0fc4fd7e12e2610915ed8af51178bdc61e168e92a8c8b8303b030b03489b13b + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0, @jridgewell/sourcemap-codec@npm:^1.5.5": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.28, @jridgewell/trace-mapping@npm:^0.3.30": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 + languageName: node + linkType: hard + +"@napi-rs/wasm-runtime@npm:^1.0.5": + version: 1.0.5 + resolution: "@napi-rs/wasm-runtime@npm:1.0.5" + dependencies: + "@emnapi/core": "npm:^1.5.0" + "@emnapi/runtime": "npm:^1.5.0" + "@tybys/wasm-util": "npm:^0.10.1" + checksum: 10c0/8d29299933c57b6ead61f46fad5c3dfabc31e1356bbaf25c3a8ae57be0af0db0006a808f2c1bb16e28925e027f20e0856550dac94e015f56dd6ed53b38f9a385 + languageName: node + linkType: hard + +"@npmcli/agent@npm:^3.0.0": + version: 3.0.0 + resolution: "@npmcli/agent@npm:3.0.0" + dependencies: + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^10.0.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10c0/efe37b982f30740ee77696a80c196912c274ecd2cb243bc6ae7053a50c733ce0f6c09fda085145f33ecf453be19654acca74b69e81eaad4c90f00ccffe2f9271 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/fs@npm:4.0.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10c0/c90935d5ce670c87b6b14fab04a965a3b8137e585f8b2a6257263bd7f97756dd736cb165bb470e5156a9e718ecd99413dccc54b1138c1a46d6ec7cf325982fe5 + languageName: node + linkType: hard + +"@openfeature/core@npm:^1.9.0": + version: 1.9.0 + resolution: "@openfeature/core@npm:1.9.0" + checksum: 10c0/957ee82fff323850e5255e46eb91222b2ec364fce7e8dd8f1277e0b24c0f9464714f9793a9492ede5b5c4f1457f3bfec885a86122d3ee01bfe678ba6b6ddbc08 + languageName: node + linkType: hard + +"@openfeature/server-sdk@npm:^1.19.0": + version: 1.19.0 + resolution: "@openfeature/server-sdk@npm:1.19.0" + peerDependencies: + "@openfeature/core": ^1.9.0 + checksum: 10c0/3b0f2b63123323403d6b93326799c20a2f4ce80f8f39e834857b0685a3beb7a084f1ff316a469cbe006f69b9a4ff75deddf9fabd7ea8e25517c99081d0bfba8c + languageName: node + linkType: hard + +"@oxc-project/types@npm:=0.89.0": + version: 0.89.0 + resolution: "@oxc-project/types@npm:0.89.0" + checksum: 10c0/2e971397f32d28aef443b0863f915eb56bacb0d94ced10da0d8366e076c1a8f9284cbe107f03ad709b7ac65cd2ddfe4c13f754ddf214001f39f079126fa40622 + languageName: node + linkType: hard + +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 10c0/5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd + languageName: node + linkType: hard + +"@quansync/fs@npm:^0.1.5": + version: 0.1.5 + resolution: "@quansync/fs@npm:0.1.5" + dependencies: + quansync: "npm:^0.2.11" + checksum: 10c0/c7f8f654499240be450b23c308a484de87bebcd0a0c8291c1afda8908a4aafafe7bc1b50e43bed0ac82ec53712505be2fa71db60e992d9353fd8ac6e664bc157 + languageName: node + linkType: hard + +"@rolldown/binding-android-arm64@npm:1.0.0-beta.38": + version: 1.0.0-beta.38 + resolution: "@rolldown/binding-android-arm64@npm:1.0.0-beta.38" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-arm64@npm:1.0.0-beta.38": + version: 1.0.0-beta.38 + resolution: "@rolldown/binding-darwin-arm64@npm:1.0.0-beta.38" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-x64@npm:1.0.0-beta.38": + version: 1.0.0-beta.38 + resolution: "@rolldown/binding-darwin-x64@npm:1.0.0-beta.38" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-freebsd-x64@npm:1.0.0-beta.38": + version: 1.0.0-beta.38 + resolution: "@rolldown/binding-freebsd-x64@npm:1.0.0-beta.38" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-beta.38": + version: 1.0.0-beta.38 + resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-beta.38" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-gnu@npm:1.0.0-beta.38": + version: 1.0.0-beta.38 + resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.0.0-beta.38" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-musl@npm:1.0.0-beta.38": + version: 1.0.0-beta.38 + resolution: "@rolldown/binding-linux-arm64-musl@npm:1.0.0-beta.38" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-gnu@npm:1.0.0-beta.38": + version: 1.0.0-beta.38 + resolution: "@rolldown/binding-linux-x64-gnu@npm:1.0.0-beta.38" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-musl@npm:1.0.0-beta.38": + version: 1.0.0-beta.38 + resolution: "@rolldown/binding-linux-x64-musl@npm:1.0.0-beta.38" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-openharmony-arm64@npm:1.0.0-beta.38": + version: 1.0.0-beta.38 + resolution: "@rolldown/binding-openharmony-arm64@npm:1.0.0-beta.38" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-wasm32-wasi@npm:1.0.0-beta.38": + version: 1.0.0-beta.38 + resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.0-beta.38" + dependencies: + "@napi-rs/wasm-runtime": "npm:^1.0.5" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@rolldown/binding-win32-arm64-msvc@npm:1.0.0-beta.38": + version: 1.0.0-beta.38 + resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.0.0-beta.38" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-win32-ia32-msvc@npm:1.0.0-beta.38": + version: 1.0.0-beta.38 + resolution: "@rolldown/binding-win32-ia32-msvc@npm:1.0.0-beta.38" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rolldown/binding-win32-x64-msvc@npm:1.0.0-beta.38": + version: 1.0.0-beta.38 + resolution: "@rolldown/binding-win32-x64-msvc@npm:1.0.0-beta.38" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/pluginutils@npm:1.0.0-beta.38": + version: 1.0.0-beta.38 + resolution: "@rolldown/pluginutils@npm:1.0.0-beta.38" + checksum: 10c0/8353ec2528349f79e27d1a3193806725b85830da334e935cbb606d88c1177c58ea6519c578e4e93e5f677f5b22aecb8738894dbed14603e14b6bffe3facf1002 + languageName: node + linkType: hard + +"@rollup/rollup-android-arm-eabi@npm:4.50.2": + version: 4.50.2 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.50.2" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-android-arm64@npm:4.50.2": + version: 4.50.2 + resolution: "@rollup/rollup-android-arm64@npm:4.50.2" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-arm64@npm:4.50.2": + version: 4.50.2 + resolution: "@rollup/rollup-darwin-arm64@npm:4.50.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-x64@npm:4.50.2": + version: 4.50.2 + resolution: "@rollup/rollup-darwin-x64@npm:4.50.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-arm64@npm:4.50.2": + version: 4.50.2 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.50.2" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-x64@npm:4.50.2": + version: 4.50.2 + resolution: "@rollup/rollup-freebsd-x64@npm:4.50.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.50.2": + version: 4.50.2 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.50.2" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-musleabihf@npm:4.50.2": + version: 4.50.2 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.50.2" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.50.2": + version: 4.50.2 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.50.2" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-musl@npm:4.50.2": + version: 4.50.2 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.50.2" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-gnu@npm:4.50.2": + version: 4.50.2 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.50.2" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-gnu@npm:4.50.2": + version: 4.50.2 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.50.2" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.50.2": + version: 4.50.2 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.50.2" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-musl@npm:4.50.2": + version: 4.50.2 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.50.2" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-s390x-gnu@npm:4.50.2": + version: 4.50.2 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.50.2" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.50.2": + version: 4.50.2 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.50.2" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-musl@npm:4.50.2": + version: 4.50.2 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.50.2" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-openharmony-arm64@npm:4.50.2": + version: 4.50.2 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.50.2" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.50.2": + version: 4.50.2 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.50.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-ia32-msvc@npm:4.50.2": + version: 4.50.2 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.50.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-msvc@npm:4.50.2": + version: 4.50.2 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.50.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@spotify-confidence/openfeature-server-provider-local@workspace:.": + version: 0.0.0-use.local + resolution: "@spotify-confidence/openfeature-server-provider-local@workspace:." + dependencies: + "@bufbuild/protobuf": "npm:^2.9.0" + "@openfeature/core": "npm:^1.9.0" + "@openfeature/server-sdk": "npm:^1.19.0" + "@types/debug": "npm:^4" + "@types/node": "npm:^24.0.1" + "@vitest/coverage-v8": "npm:^3.2.4" + debug: "npm:^4.4.3" + dotenv: "npm:^17.2.2" + rolldown: "npm:1.0.0-beta.38" + ts-proto: "npm:^2.7.3" + tsdown: "npm:latest" + vitest: "npm:^3.2.4" + peerDependencies: + debug: ^4.4.3 + peerDependenciesMeta: + debug: + optional: true + languageName: unknown + linkType: soft + +"@tybys/wasm-util@npm:^0.10.1": + version: 0.10.1 + resolution: "@tybys/wasm-util@npm:0.10.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/b255094f293794c6d2289300c5fbcafbb5532a3aed3a5ffd2f8dc1828e639b88d75f6a376dd8f94347a44813fd7a7149d8463477a9a49525c8b2dcaa38c2d1e8 + languageName: node + linkType: hard + +"@types/chai@npm:^5.2.2": + version: 5.2.2 + resolution: "@types/chai@npm:5.2.2" + dependencies: + "@types/deep-eql": "npm:*" + checksum: 10c0/49282bf0e8246800ebb36f17256f97bd3a8c4fb31f92ad3c0eaa7623518d7e87f1eaad4ad206960fcaf7175854bdff4cb167e4fe96811e0081b4ada83dd533ec + languageName: node + linkType: hard + +"@types/debug@npm:^4": + version: 4.1.12 + resolution: "@types/debug@npm:4.1.12" + dependencies: + "@types/ms": "npm:*" + checksum: 10c0/5dcd465edbb5a7f226e9a5efd1f399c6172407ef5840686b73e3608ce135eeca54ae8037dcd9f16bdb2768ac74925b820a8b9ecc588a58ca09eca6acabe33e2f + languageName: node + linkType: hard + +"@types/deep-eql@npm:*": + version: 4.0.2 + resolution: "@types/deep-eql@npm:4.0.2" + checksum: 10c0/bf3f811843117900d7084b9d0c852da9a044d12eb40e6de73b552598a6843c21291a8a381b0532644574beecd5e3491c5ff3a0365ab86b15d59862c025384844 + languageName: node + linkType: hard + +"@types/estree@npm:1.0.8, @types/estree@npm:^1.0.0": + version: 1.0.8 + resolution: "@types/estree@npm:1.0.8" + checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 + languageName: node + linkType: hard + +"@types/ms@npm:*": + version: 2.1.0 + resolution: "@types/ms@npm:2.1.0" + checksum: 10c0/5ce692ffe1549e1b827d99ef8ff71187457e0eb44adbae38fdf7b9a74bae8d20642ee963c14516db1d35fa2652e65f47680fdf679dcbde52bbfadd021f497225 + languageName: node + linkType: hard + +"@types/node@npm:^24.0.1": + version: 24.5.0 + resolution: "@types/node@npm:24.5.0" + dependencies: + undici-types: "npm:~7.12.0" + checksum: 10c0/c5beff68481e2cc667279a1478b34a1cfd048dbff914219cb5888967938d134907836b6c4d6d141dc862489cb09ef28f7d446c7a3b475181fd126c0fcd2916fa + languageName: node + linkType: hard + +"@vitest/coverage-v8@npm:^3.2.4": + version: 3.2.4 + resolution: "@vitest/coverage-v8@npm:3.2.4" + dependencies: + "@ampproject/remapping": "npm:^2.3.0" + "@bcoe/v8-coverage": "npm:^1.0.2" + ast-v8-to-istanbul: "npm:^0.3.3" + debug: "npm:^4.4.1" + istanbul-lib-coverage: "npm:^3.2.2" + istanbul-lib-report: "npm:^3.0.1" + istanbul-lib-source-maps: "npm:^5.0.6" + istanbul-reports: "npm:^3.1.7" + magic-string: "npm:^0.30.17" + magicast: "npm:^0.3.5" + std-env: "npm:^3.9.0" + test-exclude: "npm:^7.0.1" + tinyrainbow: "npm:^2.0.0" + peerDependencies: + "@vitest/browser": 3.2.4 + vitest: 3.2.4 + peerDependenciesMeta: + "@vitest/browser": + optional: true + checksum: 10c0/cae3e58d81d56e7e1cdecd7b5baab7edd0ad9dee8dec9353c52796e390e452377d3f04174d40b6986b17c73241a5e773e422931eaa8102dcba0605ff24b25193 + languageName: node + linkType: hard + +"@vitest/expect@npm:3.2.4": + version: 3.2.4 + resolution: "@vitest/expect@npm:3.2.4" + dependencies: + "@types/chai": "npm:^5.2.2" + "@vitest/spy": "npm:3.2.4" + "@vitest/utils": "npm:3.2.4" + chai: "npm:^5.2.0" + tinyrainbow: "npm:^2.0.0" + checksum: 10c0/7586104e3fd31dbe1e6ecaafb9a70131e4197dce2940f727b6a84131eee3decac7b10f9c7c72fa5edbdb68b6f854353bd4c0fa84779e274207fb7379563b10db + languageName: node + linkType: hard + +"@vitest/mocker@npm:3.2.4": + version: 3.2.4 + resolution: "@vitest/mocker@npm:3.2.4" + dependencies: + "@vitest/spy": "npm:3.2.4" + estree-walker: "npm:^3.0.3" + magic-string: "npm:^0.30.17" + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + checksum: 10c0/f7a4aea19bbbf8f15905847ee9143b6298b2c110f8b64789224cb0ffdc2e96f9802876aa2ca83f1ec1b6e1ff45e822abb34f0054c24d57b29ab18add06536ccd + languageName: node + linkType: hard + +"@vitest/pretty-format@npm:3.2.4, @vitest/pretty-format@npm:^3.2.4": + version: 3.2.4 + resolution: "@vitest/pretty-format@npm:3.2.4" + dependencies: + tinyrainbow: "npm:^2.0.0" + checksum: 10c0/5ad7d4278e067390d7d633e307fee8103958806a419ca380aec0e33fae71b44a64415f7a9b4bc11635d3c13d4a9186111c581d3cef9c65cc317e68f077456887 + languageName: node + linkType: hard + +"@vitest/runner@npm:3.2.4": + version: 3.2.4 + resolution: "@vitest/runner@npm:3.2.4" + dependencies: + "@vitest/utils": "npm:3.2.4" + pathe: "npm:^2.0.3" + strip-literal: "npm:^3.0.0" + checksum: 10c0/e8be51666c72b3668ae3ea348b0196656a4a5adb836cb5e270720885d9517421815b0d6c98bfdf1795ed02b994b7bfb2b21566ee356a40021f5bf4f6ed4e418a + languageName: node + linkType: hard + +"@vitest/snapshot@npm:3.2.4": + version: 3.2.4 + resolution: "@vitest/snapshot@npm:3.2.4" + dependencies: + "@vitest/pretty-format": "npm:3.2.4" + magic-string: "npm:^0.30.17" + pathe: "npm:^2.0.3" + checksum: 10c0/f8301a3d7d1559fd3d59ed51176dd52e1ed5c2d23aa6d8d6aa18787ef46e295056bc726a021698d8454c16ed825ecba163362f42fa90258bb4a98cfd2c9424fc + languageName: node + linkType: hard + +"@vitest/spy@npm:3.2.4": + version: 3.2.4 + resolution: "@vitest/spy@npm:3.2.4" + dependencies: + tinyspy: "npm:^4.0.3" + checksum: 10c0/6ebf0b4697dc238476d6b6a60c76ba9eb1dd8167a307e30f08f64149612fd50227682b876420e4c2e09a76334e73f72e3ebf0e350714dc22474258292e202024 + languageName: node + linkType: hard + +"@vitest/utils@npm:3.2.4": + version: 3.2.4 + resolution: "@vitest/utils@npm:3.2.4" + dependencies: + "@vitest/pretty-format": "npm:3.2.4" + loupe: "npm:^3.1.4" + tinyrainbow: "npm:^2.0.0" + checksum: 10c0/024a9b8c8bcc12cf40183c246c244b52ecff861c6deb3477cbf487ac8781ad44c68a9c5fd69f8c1361878e55b97c10d99d511f2597f1f7244b5e5101d028ba64 + languageName: node + linkType: hard + +"abbrev@npm:^3.0.0": + version: 3.0.1 + resolution: "abbrev@npm:3.0.1" + checksum: 10c0/21ba8f574ea57a3106d6d35623f2c4a9111d9ee3e9a5be47baed46ec2457d2eac46e07a5c4a60186f88cb98abbe3e24f2d4cca70bc2b12f1692523e2209a9ccf + languageName: node + linkType: hard + +"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": + version: 7.1.4 + resolution: "agent-base@npm:7.1.4" + checksum: 10c0/c2c9ab7599692d594b6a161559ada307b7a624fa4c7b03e3afdb5a5e31cd0e53269115b620fcab024c5ac6a6f37fa5eb2e004f076ad30f5f7e6b8b671f7b35fe + languageName: node + linkType: hard + +"ansi-regex@npm:^5.0.1": + version: 5.0.1 + resolution: "ansi-regex@npm:5.0.1" + checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 + languageName: node + linkType: hard + +"ansi-regex@npm:^6.0.1": + version: 6.2.2 + resolution: "ansi-regex@npm:6.2.2" + checksum: 10c0/05d4acb1d2f59ab2cf4b794339c7b168890d44dda4bf0ce01152a8da0213aca207802f930442ce8cd22d7a92f44907664aac6508904e75e038fa944d2601b30f + languageName: node + linkType: hard + +"ansi-styles@npm:^4.0.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: "npm:^2.0.1" + checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 + languageName: node + linkType: hard + +"ansi-styles@npm:^6.1.0": + version: 6.2.3 + resolution: "ansi-styles@npm:6.2.3" + checksum: 10c0/23b8a4ce14e18fb854693b95351e286b771d23d8844057ed2e7d083cd3e708376c3323707ec6a24365f7d7eda3ca00327fe04092e29e551499ec4c8b7bfac868 + languageName: node + linkType: hard + +"ansis@npm:^4.0.0, ansis@npm:^4.1.0": + version: 4.1.0 + resolution: "ansis@npm:4.1.0" + checksum: 10c0/df62d017a7791babdaf45b93f930d2cfd6d1dab5568b610735c11434c9a5ef8f513740e7cfd80bcbc3530fc8bd892b88f8476f26621efc251230e53cbd1a2c24 + languageName: node + linkType: hard + +"assertion-error@npm:^2.0.1": + version: 2.0.1 + resolution: "assertion-error@npm:2.0.1" + checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8 + languageName: node + linkType: hard + +"ast-kit@npm:^2.1.2": + version: 2.1.2 + resolution: "ast-kit@npm:2.1.2" + dependencies: + "@babel/parser": "npm:^7.28.0" + pathe: "npm:^2.0.3" + checksum: 10c0/7034c2d98de971cd689f5e098837c08f4f1b96a4fab14ab8d54ddc3b877e5e677f6851bef7e1625f0c714196f85ba2a0417446afe571ae34db1a4e34d788b25c + languageName: node + linkType: hard + +"ast-v8-to-istanbul@npm:^0.3.3": + version: 0.3.5 + resolution: "ast-v8-to-istanbul@npm:0.3.5" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.30" + estree-walker: "npm:^3.0.3" + js-tokens: "npm:^9.0.1" + checksum: 10c0/6796d2e79dc82302543f8109a6d75944278903cee6269b46df4a7d923c289754f1c97390df48536657741d387046e11dbedcda8ce2e6441bcbe26f8586a6d715 + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee + languageName: node + linkType: hard + +"birpc@npm:^2.5.0": + version: 2.5.0 + resolution: "birpc@npm:2.5.0" + checksum: 10c0/8caed5ad86b71e0b4af6a1c5e8ed006f451d3b378ce52c2fa613fe68f15bb3df1357ad69f7fb0251e4261f39b2926995e34307ac06397f993665b16ba569dc54 + languageName: node + linkType: hard + +"brace-expansion@npm:^2.0.1": + version: 2.0.2 + resolution: "brace-expansion@npm:2.0.2" + dependencies: + balanced-match: "npm:^1.0.0" + checksum: 10c0/6d117a4c793488af86b83172deb6af143e94c17bc53b0b3cec259733923b4ca84679d506ac261f4ba3c7ed37c46018e2ff442f9ce453af8643ecd64f4a54e6cf + languageName: node + linkType: hard + +"cac@npm:^6.7.14": + version: 6.7.14 + resolution: "cac@npm:6.7.14" + checksum: 10c0/4ee06aaa7bab8981f0d54e5f5f9d4adcd64058e9697563ce336d8a3878ed018ee18ebe5359b2430eceae87e0758e62ea2019c3f52ae6e211b1bd2e133856cd10 + languageName: node + linkType: hard + +"cacache@npm:^19.0.1": + version: 19.0.1 + resolution: "cacache@npm:19.0.1" + dependencies: + "@npmcli/fs": "npm:^4.0.0" + fs-minipass: "npm:^3.0.0" + glob: "npm:^10.2.2" + lru-cache: "npm:^10.0.1" + minipass: "npm:^7.0.3" + minipass-collect: "npm:^2.0.1" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + p-map: "npm:^7.0.2" + ssri: "npm:^12.0.0" + tar: "npm:^7.4.3" + unique-filename: "npm:^4.0.0" + checksum: 10c0/01f2134e1bd7d3ab68be851df96c8d63b492b1853b67f2eecb2c37bb682d37cb70bb858a16f2f0554d3c0071be6dfe21456a1ff6fa4b7eed996570d6a25ffe9c + languageName: node + linkType: hard + +"case-anything@npm:^2.1.13": + version: 2.1.13 + resolution: "case-anything@npm:2.1.13" + checksum: 10c0/b02ffa51d7d58b9a32df7b40973836e16afad131eae7d343e64cb3ca7be57a936bf3d6c9d57a7aa242cf2f545d9a33990b755e93bcac2517761d77773a4a6a30 + languageName: node + linkType: hard + +"chai@npm:^5.2.0": + version: 5.3.3 + resolution: "chai@npm:5.3.3" + dependencies: + assertion-error: "npm:^2.0.1" + check-error: "npm:^2.1.1" + deep-eql: "npm:^5.0.1" + loupe: "npm:^3.1.0" + pathval: "npm:^2.0.0" + checksum: 10c0/b360fd4d38861622e5010c2f709736988b05c7f31042305fa3f4e9911f6adb80ccfb4e302068bf8ed10e835c2e2520cba0f5edc13d878b886987e5aa62483f53 + languageName: node + linkType: hard + +"check-error@npm:^2.1.1": + version: 2.1.1 + resolution: "check-error@npm:2.1.1" + checksum: 10c0/979f13eccab306cf1785fa10941a590b4e7ea9916ea2a4f8c87f0316fc3eab07eabefb6e587424ef0f88cbcd3805791f172ea739863ca3d7ce2afc54641c7f0e + languageName: node + linkType: hard + +"chokidar@npm:^4.0.3": + version: 4.0.3 + resolution: "chokidar@npm:4.0.3" + dependencies: + readdirp: "npm:^4.0.1" + checksum: 10c0/a58b9df05bb452f7d105d9e7229ac82fa873741c0c40ddcc7bb82f8a909fbe3f7814c9ebe9bc9a2bef9b737c0ec6e2d699d179048ef06ad3ec46315df0ebe6ad + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: "npm:~1.1.4" + checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 + languageName: node + linkType: hard + +"color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.6": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.1.1, debug@npm:^4.3.4, debug@npm:^4.4.1, debug@npm:^4.4.3": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 + languageName: node + linkType: hard + +"deep-eql@npm:^5.0.1": + version: 5.0.2 + resolution: "deep-eql@npm:5.0.2" + checksum: 10c0/7102cf3b7bb719c6b9c0db2e19bf0aa9318d141581befe8c7ce8ccd39af9eaa4346e5e05adef7f9bd7015da0f13a3a25dcfe306ef79dc8668aedbecb658dd247 + languageName: node + linkType: hard + +"defu@npm:^6.1.4": + version: 6.1.4 + resolution: "defu@npm:6.1.4" + checksum: 10c0/2d6cc366262dc0cb8096e429368e44052fdf43ed48e53ad84cc7c9407f890301aa5fcb80d0995abaaf842b3949f154d060be4160f7a46cb2bc2f7726c81526f5 + languageName: node + linkType: hard + +"detect-libc@npm:^1.0.3": + version: 1.0.3 + resolution: "detect-libc@npm:1.0.3" + bin: + detect-libc: ./bin/detect-libc.js + checksum: 10c0/4da0deae9f69e13bc37a0902d78bf7169480004b1fed3c19722d56cff578d16f0e11633b7fbf5fb6249181236c72e90024cbd68f0b9558ae06e281f47326d50d + languageName: node + linkType: hard + +"diff@npm:^8.0.2": + version: 8.0.2 + resolution: "diff@npm:8.0.2" + checksum: 10c0/abfb387f033e089df3ec3be960205d17b54df8abf0924d982a7ced3a94c557a4e6cbff2e78b121f216b85f466b3d8d041673a386177c311aaea41459286cc9bc + languageName: node + linkType: hard + +"dotenv@npm:^17.2.2": + version: 17.2.2 + resolution: "dotenv@npm:17.2.2" + checksum: 10c0/be66513504590aff6eccb14167625aed9bd42ce80547f4fe5d195860211971a7060949b57108dfaeaf90658f79e40edccd3f233f0a978bff507b5b1565ae162b + languageName: node + linkType: hard + +"dprint-node@npm:^1.0.8": + version: 1.0.8 + resolution: "dprint-node@npm:1.0.8" + dependencies: + detect-libc: "npm:^1.0.3" + checksum: 10c0/39c1f8511833226cde773129afc5862dfd05babe062375e6b1f5824e221a5743a4d9c48626f32f7c2080113566270fe80521a50acb9029a20a2e80a3cd5e4106 + languageName: node + linkType: hard + +"dts-resolver@npm:^2.1.2": + version: 2.1.2 + resolution: "dts-resolver@npm:2.1.2" + peerDependencies: + oxc-resolver: ">=11.0.0" + peerDependenciesMeta: + oxc-resolver: + optional: true + checksum: 10c0/521986fc9a7922e972c5d603bc2a2e1e2a0d7aa4902533947e2d63362d3ac6ac5b6ca22a75e82ee1ff7a3de9480eb050b1a584f3d2c653b3fe8091413f99f69f + languageName: node + linkType: hard + +"eastasianwidth@npm:^0.2.0": + version: 0.2.0 + resolution: "eastasianwidth@npm:0.2.0" + checksum: 10c0/26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39 + languageName: node + linkType: hard + +"emoji-regex@npm:^8.0.0": + version: 8.0.0 + resolution: "emoji-regex@npm:8.0.0" + checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 + languageName: node + linkType: hard + +"emoji-regex@npm:^9.2.2": + version: 9.2.2 + resolution: "emoji-regex@npm:9.2.2" + checksum: 10c0/af014e759a72064cf66e6e694a7fc6b0ed3d8db680427b021a89727689671cefe9d04151b2cad51dbaf85d5ba790d061cd167f1cf32eb7b281f6368b3c181639 + languageName: node + linkType: hard + +"empathic@npm:^2.0.0": + version: 2.0.0 + resolution: "empathic@npm:2.0.0" + checksum: 10c0/7d3b14b04a93b35c47bcc950467ec914fd241cd9acc0269b0ea160f13026ec110f520c90fae64720fde72cc1757b57f3f292fb606617b7fccac1f4d008a76506 + languageName: node + linkType: hard + +"encoding@npm:^0.1.13": + version: 0.1.13 + resolution: "encoding@npm:0.1.13" + dependencies: + iconv-lite: "npm:^0.6.2" + checksum: 10c0/36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"err-code@npm:^2.0.2": + version: 2.0.3 + resolution: "err-code@npm:2.0.3" + checksum: 10c0/b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66 + languageName: node + linkType: hard + +"es-module-lexer@npm:^1.7.0": + version: 1.7.0 + resolution: "es-module-lexer@npm:1.7.0" + checksum: 10c0/4c935affcbfeba7fb4533e1da10fa8568043df1e3574b869385980de9e2d475ddc36769891936dbb07036edb3c3786a8b78ccf44964cd130dedc1f2c984b6c7b + languageName: node + linkType: hard + +"esbuild@npm:^0.25.0": + version: 0.25.9 + resolution: "esbuild@npm:0.25.9" + dependencies: + "@esbuild/aix-ppc64": "npm:0.25.9" + "@esbuild/android-arm": "npm:0.25.9" + "@esbuild/android-arm64": "npm:0.25.9" + "@esbuild/android-x64": "npm:0.25.9" + "@esbuild/darwin-arm64": "npm:0.25.9" + "@esbuild/darwin-x64": "npm:0.25.9" + "@esbuild/freebsd-arm64": "npm:0.25.9" + "@esbuild/freebsd-x64": "npm:0.25.9" + "@esbuild/linux-arm": "npm:0.25.9" + "@esbuild/linux-arm64": "npm:0.25.9" + "@esbuild/linux-ia32": "npm:0.25.9" + "@esbuild/linux-loong64": "npm:0.25.9" + "@esbuild/linux-mips64el": "npm:0.25.9" + "@esbuild/linux-ppc64": "npm:0.25.9" + "@esbuild/linux-riscv64": "npm:0.25.9" + "@esbuild/linux-s390x": "npm:0.25.9" + "@esbuild/linux-x64": "npm:0.25.9" + "@esbuild/netbsd-arm64": "npm:0.25.9" + "@esbuild/netbsd-x64": "npm:0.25.9" + "@esbuild/openbsd-arm64": "npm:0.25.9" + "@esbuild/openbsd-x64": "npm:0.25.9" + "@esbuild/openharmony-arm64": "npm:0.25.9" + "@esbuild/sunos-x64": "npm:0.25.9" + "@esbuild/win32-arm64": "npm:0.25.9" + "@esbuild/win32-ia32": "npm:0.25.9" + "@esbuild/win32-x64": "npm:0.25.9" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/aaa1284c75fcf45c82f9a1a117fe8dc5c45628e3386bda7d64916ae27730910b51c5aec7dd45a6ba19256be30ba2935e64a8f011a3f0539833071e06bf76d5b3 + languageName: node + linkType: hard + +"estree-walker@npm:^3.0.3": + version: 3.0.3 + resolution: "estree-walker@npm:3.0.3" + dependencies: + "@types/estree": "npm:^1.0.0" + checksum: 10c0/c12e3c2b2642d2bcae7d5aa495c60fa2f299160946535763969a1c83fc74518ffa9c2cd3a8b69ac56aea547df6a8aac25f729a342992ef0bbac5f1c73e78995d + languageName: node + linkType: hard + +"expect-type@npm:^1.2.1": + version: 1.2.2 + resolution: "expect-type@npm:1.2.2" + checksum: 10c0/6019019566063bbc7a690d9281d920b1a91284a4a093c2d55d71ffade5ac890cf37a51e1da4602546c4b56569d2ad2fc175a2ccee77d1ae06cb3af91ef84f44b + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.2 + resolution: "exponential-backoff@npm:3.1.2" + checksum: 10c0/d9d3e1eafa21b78464297df91f1776f7fbaa3d5e3f7f0995648ca5b89c069d17055033817348d9f4a43d1c20b0eab84f75af6991751e839df53e4dfd6f22e844 + languageName: node + linkType: hard + +"fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f + languageName: node + linkType: hard + +"foreground-child@npm:^3.1.0": + version: 3.3.1 + resolution: "foreground-child@npm:3.3.1" + dependencies: + cross-spawn: "npm:^7.0.6" + signal-exit: "npm:^4.0.1" + checksum: 10c0/8986e4af2430896e65bc2788d6679067294d6aee9545daefc84923a0a4b399ad9c7a3ea7bd8c0b2b80fdf4a92de4c69df3f628233ff3224260e9c1541a9e9ed3 + languageName: node + linkType: hard + +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 + languageName: node + linkType: hard + +"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"get-tsconfig@npm:^4.10.1": + version: 4.10.1 + resolution: "get-tsconfig@npm:4.10.1" + dependencies: + resolve-pkg-maps: "npm:^1.0.0" + checksum: 10c0/7f8e3dabc6a49b747920a800fb88e1952fef871cdf51b79e98db48275a5de6cdaf499c55ee67df5fa6fe7ce65f0063e26de0f2e53049b408c585aa74d39ffa21 + languageName: node + linkType: hard + +"glob@npm:^10.2.2, glob@npm:^10.4.1": + version: 10.4.5 + resolution: "glob@npm:10.4.5" + dependencies: + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^3.1.2" + minimatch: "npm:^9.0.4" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^1.11.1" + bin: + glob: dist/esm/bin.mjs + checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e + languageName: node + linkType: hard + +"graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 + languageName: node + linkType: hard + +"hookable@npm:^5.5.3": + version: 5.5.3 + resolution: "hookable@npm:5.5.3" + checksum: 10c0/275f4cc84d27f8d48c5a5cd5685b6c0fea9291be9deea5bff0cfa72856ed566abde1dcd8cb1da0f9a70b4da3d7ec0d60dc3554c4edbba647058cc38816eced3d + languageName: node + linkType: hard + +"html-escaper@npm:^2.0.0": + version: 2.0.2 + resolution: "html-escaper@npm:2.0.2" + checksum: 10c0/208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.1.1": + version: 4.2.0 + resolution: "http-cache-semantics@npm:4.2.0" + checksum: 10c0/45b66a945cf13ec2d1f29432277201313babf4a01d9e52f44b31ca923434083afeca03f18417f599c9ab3d0e7b618ceb21257542338b57c54b710463b4a53e37 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.0": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: "npm:^7.1.0" + debug: "npm:^4.3.4" + checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.1": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:4" + checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac + languageName: node + linkType: hard + +"iconv-lite@npm:^0.6.2": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 + languageName: node + linkType: hard + +"ip-address@npm:^10.0.1": + version: 10.0.1 + resolution: "ip-address@npm:10.0.1" + checksum: 10c0/1634d79dae18394004775cb6d699dc46b7c23df6d2083164025a2b15240c1164fccde53d0e08bd5ee4fc53913d033ab6b5e395a809ad4b956a940c446e948843 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^3.0.0": + version: 3.0.0 + resolution: "is-fullwidth-code-point@npm:3.0.0" + checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d + languageName: node + linkType: hard + +"isexe@npm:^3.1.1": + version: 3.1.1 + resolution: "isexe@npm:3.1.1" + checksum: 10c0/9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7 + languageName: node + linkType: hard + +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.2": + version: 3.2.2 + resolution: "istanbul-lib-coverage@npm:3.2.2" + checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b + languageName: node + linkType: hard + +"istanbul-lib-report@npm:^3.0.0, istanbul-lib-report@npm:^3.0.1": + version: 3.0.1 + resolution: "istanbul-lib-report@npm:3.0.1" + dependencies: + istanbul-lib-coverage: "npm:^3.0.0" + make-dir: "npm:^4.0.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/84323afb14392de8b6a5714bd7e9af845cfbd56cfe71ed276cda2f5f1201aea673c7111901227ee33e68e4364e288d73861eb2ed48f6679d1e69a43b6d9b3ba7 + languageName: node + linkType: hard + +"istanbul-lib-source-maps@npm:^5.0.6": + version: 5.0.6 + resolution: "istanbul-lib-source-maps@npm:5.0.6" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.23" + debug: "npm:^4.1.1" + istanbul-lib-coverage: "npm:^3.0.0" + checksum: 10c0/ffe75d70b303a3621ee4671554f306e0831b16f39ab7f4ab52e54d356a5d33e534d97563e318f1333a6aae1d42f91ec49c76b6cd3f3fb378addcb5c81da0255f + languageName: node + linkType: hard + +"istanbul-reports@npm:^3.1.7": + version: 3.2.0 + resolution: "istanbul-reports@npm:3.2.0" + dependencies: + html-escaper: "npm:^2.0.0" + istanbul-lib-report: "npm:^3.0.0" + checksum: 10c0/d596317cfd9c22e1394f22a8d8ba0303d2074fe2e971887b32d870e4b33f8464b10f8ccbe6847808f7db485f084eba09e6c2ed706b3a978e4b52f07085b8f9bc + languageName: node + linkType: hard + +"jackspeak@npm:^3.1.2": + version: 3.4.3 + resolution: "jackspeak@npm:3.4.3" + dependencies: + "@isaacs/cliui": "npm:^8.0.2" + "@pkgjs/parseargs": "npm:^0.11.0" + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9 + languageName: node + linkType: hard + +"jiti@npm:^2.5.1": + version: 2.5.1 + resolution: "jiti@npm:2.5.1" + bin: + jiti: lib/jiti-cli.mjs + checksum: 10c0/f0a38d7d8842cb35ffe883038166aa2d52ffd21f1a4fc839ae4076ea7301c22a1f11373f8fc52e2667de7acde8f3e092835620dd6f72a0fbe9296b268b0874bb + languageName: node + linkType: hard + +"js-tokens@npm:^9.0.1": + version: 9.0.1 + resolution: "js-tokens@npm:9.0.1" + checksum: 10c0/68dcab8f233dde211a6b5fd98079783cbcd04b53617c1250e3553ee16ab3e6134f5e65478e41d82f6d351a052a63d71024553933808570f04dbf828d7921e80e + languageName: node + linkType: hard + +"jsesc@npm:^3.0.2": + version: 3.1.0 + resolution: "jsesc@npm:3.1.0" + bin: + jsesc: bin/jsesc + checksum: 10c0/531779df5ec94f47e462da26b4cbf05eb88a83d9f08aac2ba04206508fc598527a153d08bd462bae82fc78b3eaa1a908e1a4a79f886e9238641c4cdefaf118b1 + languageName: node + linkType: hard + +"loupe@npm:^3.1.0, loupe@npm:^3.1.4": + version: 3.2.1 + resolution: "loupe@npm:3.2.1" + checksum: 10c0/910c872cba291309664c2d094368d31a68907b6f5913e989d301b5c25f30e97d76d77f23ab3bf3b46d0f601ff0b6af8810c10c31b91d2c6b2f132809ca2cc705 + languageName: node + linkType: hard + +"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": + version: 10.4.3 + resolution: "lru-cache@npm:10.4.3" + checksum: 10c0/ebd04fbca961e6c1d6c0af3799adcc966a1babe798f685bb84e6599266599cd95d94630b10262f5424539bc4640107e8a33aa28585374abf561d30d16f4b39fb + languageName: node + linkType: hard + +"magic-string@npm:^0.30.17, magic-string@npm:^0.30.19": + version: 0.30.19 + resolution: "magic-string@npm:0.30.19" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10c0/db23fd2e2ee98a1aeb88a4cdb2353137fcf05819b883c856dd79e4c7dfb25151e2a5a4d5dbd88add5e30ed8ae5c51bcf4accbc6becb75249d924ec7b4fbcae27 + languageName: node + linkType: hard + +"magicast@npm:^0.3.5": + version: 0.3.5 + resolution: "magicast@npm:0.3.5" + dependencies: + "@babel/parser": "npm:^7.25.4" + "@babel/types": "npm:^7.25.4" + source-map-js: "npm:^1.2.0" + checksum: 10c0/a6cacc0a848af84f03e3f5bda7b0de75e4d0aa9ddce5517fd23ed0f31b5ddd51b2d0ff0b7e09b51f7de0f4053c7a1107117edda6b0732dca3e9e39e6c5a68c64 + languageName: node + linkType: hard + +"make-dir@npm:^4.0.0": + version: 4.0.0 + resolution: "make-dir@npm:4.0.0" + dependencies: + semver: "npm:^7.5.3" + checksum: 10c0/69b98a6c0b8e5c4fe9acb61608a9fbcfca1756d910f51e5dbe7a9e5cfb74fca9b8a0c8a0ffdf1294a740826c1ab4871d5bf3f62f72a3049e5eac6541ddffed68 + languageName: node + linkType: hard + +"make-fetch-happen@npm:^14.0.3": + version: 14.0.3 + resolution: "make-fetch-happen@npm:14.0.3" + dependencies: + "@npmcli/agent": "npm:^3.0.0" + cacache: "npm:^19.0.1" + http-cache-semantics: "npm:^4.1.1" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^4.0.0" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + negotiator: "npm:^1.0.0" + proc-log: "npm:^5.0.0" + promise-retry: "npm:^2.0.1" + ssri: "npm:^12.0.0" + checksum: 10c0/c40efb5e5296e7feb8e37155bde8eb70bc57d731b1f7d90e35a092fde403d7697c56fb49334d92d330d6f1ca29a98142036d6480a12681133a0a1453164cb2f0 + languageName: node + linkType: hard + +"minimatch@npm:^9.0.4": + version: 9.0.5 + resolution: "minimatch@npm:9.0.5" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed + languageName: node + linkType: hard + +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e + languageName: node + linkType: hard + +"minipass-fetch@npm:^4.0.0": + version: 4.0.1 + resolution: "minipass-fetch@npm:4.0.1" + dependencies: + encoding: "npm:^0.1.13" + minipass: "npm:^7.0.3" + minipass-sized: "npm:^1.0.3" + minizlib: "npm:^3.0.1" + dependenciesMeta: + encoding: + optional: true + checksum: 10c0/a3147b2efe8e078c9bf9d024a0059339c5a09c5b1dded6900a219c218cc8b1b78510b62dae556b507304af226b18c3f1aeb1d48660283602d5b6586c399eed5c + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.5 + resolution: "minipass-flush@npm:1.0.5" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 + languageName: node + linkType: hard + +"minipass-sized@npm:^1.0.3": + version: 1.0.3 + resolution: "minipass-sized@npm:1.0.3" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/298f124753efdc745cfe0f2bdfdd81ba25b9f4e753ca4a2066eb17c821f25d48acea607dfc997633ee5bf7b6dfffb4eee4f2051eb168663f0b99fad2fa4829cb + languageName: node + linkType: hard + +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c + languageName: node + linkType: hard + +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": + version: 7.1.2 + resolution: "minipass@npm:7.1.2" + checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 + languageName: node + linkType: hard + +"minizlib@npm:^3.0.1": + version: 3.0.2 + resolution: "minizlib@npm:3.0.2" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/9f3bd35e41d40d02469cb30470c55ccc21cae0db40e08d1d0b1dff01cc8cc89a6f78e9c5d2b7c844e485ec0a8abc2238111213fdc5b2038e6d1012eacf316f78 + languageName: node + linkType: hard + +"mkdirp@npm:^3.0.1": + version: 3.0.1 + resolution: "mkdirp@npm:3.0.1" + bin: + mkdirp: dist/cjs/src/bin.js + checksum: 10c0/9f2b975e9246351f5e3a40dcfac99fcd0baa31fbfab615fe059fb11e51f10e4803c63de1f384c54d656e4db31d000e4767e9ef076a22e12a641357602e31d57d + languageName: node + linkType: hard + +"ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"nanoid@npm:^3.3.11": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b + languageName: node + linkType: hard + +"negotiator@npm:^1.0.0": + version: 1.0.0 + resolution: "negotiator@npm:1.0.0" + checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 11.4.2 + resolution: "node-gyp@npm:11.4.2" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^14.0.3" + nopt: "npm:^8.0.0" + proc-log: "npm:^5.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.4.3" + tinyglobby: "npm:^0.2.12" + which: "npm:^5.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/0bfd3e96770ed70f07798d881dd37b4267708966d868a0e585986baac487d9cf5831285579fd629a83dc4e434f53e6416ce301097f2ee464cb74d377e4d8bdbe + languageName: node + linkType: hard + +"nopt@npm:^8.0.0": + version: 8.1.0 + resolution: "nopt@npm:8.1.0" + dependencies: + abbrev: "npm:^3.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/62e9ea70c7a3eb91d162d2c706b6606c041e4e7b547cbbb48f8b3695af457dd6479904d7ace600856bf923dd8d1ed0696f06195c8c20f02ac87c1da0e1d315ef + languageName: node + linkType: hard + +"p-map@npm:^7.0.2": + version: 7.0.3 + resolution: "p-map@npm:7.0.3" + checksum: 10c0/46091610da2b38ce47bcd1d8b4835a6fa4e832848a6682cf1652bc93915770f4617afc844c10a77d1b3e56d2472bb2d5622353fa3ead01a7f42b04fc8e744a5c + languageName: node + linkType: hard + +"package-json-from-dist@npm:^1.0.0": + version: 1.0.1 + resolution: "package-json-from-dist@npm:1.0.1" + checksum: 10c0/62ba2785eb655fec084a257af34dbe24292ab74516d6aecef97ef72d4897310bc6898f6c85b5cd22770eaa1ce60d55a0230e150fb6a966e3ecd6c511e23d164b + languageName: node + linkType: hard + +"path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c + languageName: node + linkType: hard + +"path-scurry@npm:^1.11.1": + version: 1.11.1 + resolution: "path-scurry@npm:1.11.1" + dependencies: + lru-cache: "npm:^10.2.0" + minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" + checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d + languageName: node + linkType: hard + +"pathe@npm:^2.0.3": + version: 2.0.3 + resolution: "pathe@npm:2.0.3" + checksum: 10c0/c118dc5a8b5c4166011b2b70608762e260085180bb9e33e80a50dcdb1e78c010b1624f4280c492c92b05fc276715a4c357d1f9edc570f8f1b3d90b6839ebaca1 + languageName: node + linkType: hard + +"pathval@npm:^2.0.0": + version: 2.0.1 + resolution: "pathval@npm:2.0.1" + checksum: 10c0/460f4709479fbf2c45903a65655fc8f0a5f6d808f989173aeef5fdea4ff4f303dc13f7870303999add60ec49d4c14733895c0a869392e9866f1091fa64fd7581 + languageName: node + linkType: hard + +"picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 + languageName: node + linkType: hard + +"picomatch@npm:^4.0.2, picomatch@npm:^4.0.3": + version: 4.0.3 + resolution: "picomatch@npm:4.0.3" + checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2 + languageName: node + linkType: hard + +"postcss@npm:^8.5.6": + version: 8.5.6 + resolution: "postcss@npm:8.5.6" + dependencies: + nanoid: "npm:^3.3.11" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/5127cc7c91ed7a133a1b7318012d8bfa112da9ef092dddf369ae699a1f10ebbd89b1b9f25f3228795b84585c72aabd5ced5fc11f2ba467eedf7b081a66fad024 + languageName: node + linkType: hard + +"proc-log@npm:^5.0.0": + version: 5.0.0 + resolution: "proc-log@npm:5.0.0" + checksum: 10c0/bbe5edb944b0ad63387a1d5b1911ae93e05ce8d0f60de1035b218cdcceedfe39dbd2c697853355b70f1a090f8f58fe90da487c85216bf9671f9499d1a897e9e3 + languageName: node + linkType: hard + +"promise-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "promise-retry@npm:2.0.1" + dependencies: + err-code: "npm:^2.0.2" + retry: "npm:^0.12.0" + checksum: 10c0/9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96 + languageName: node + linkType: hard + +"quansync@npm:^0.2.11": + version: 0.2.11 + resolution: "quansync@npm:0.2.11" + checksum: 10c0/cb9a1f8ebce074069f2f6a78578873ffedd9de9f6aa212039b44c0870955c04a71c3b1311b5d97f8ac2f2ec476de202d0a5c01160cb12bc0a11b7ef36d22ef56 + languageName: node + linkType: hard + +"readdirp@npm:^4.0.1": + version: 4.1.2 + resolution: "readdirp@npm:4.1.2" + checksum: 10c0/60a14f7619dec48c9c850255cd523e2717001b0e179dc7037cfa0895da7b9e9ab07532d324bfb118d73a710887d1e35f79c495fa91582784493e085d18c72c62 + languageName: node + linkType: hard + +"resolve-pkg-maps@npm:^1.0.0": + version: 1.0.0 + resolution: "resolve-pkg-maps@npm:1.0.0" + checksum: 10c0/fb8f7bbe2ca281a73b7ef423a1cbc786fb244bd7a95cbe5c3fba25b27d327150beca8ba02f622baea65919a57e061eb5005204daa5f93ed590d9b77463a567ab + languageName: node + linkType: hard + +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe + languageName: node + linkType: hard + +"rolldown-plugin-dts@npm:^0.16.5": + version: 0.16.5 + resolution: "rolldown-plugin-dts@npm:0.16.5" + dependencies: + "@babel/generator": "npm:^7.28.3" + "@babel/parser": "npm:^7.28.4" + "@babel/types": "npm:^7.28.4" + ast-kit: "npm:^2.1.2" + birpc: "npm:^2.5.0" + debug: "npm:^4.4.1" + dts-resolver: "npm:^2.1.2" + get-tsconfig: "npm:^4.10.1" + magic-string: "npm:^0.30.19" + peerDependencies: + "@ts-macro/tsc": ^0.3.6 + "@typescript/native-preview": ">=7.0.0-dev.20250601.1" + rolldown: ^1.0.0-beta.9 + typescript: ^5.0.0 + vue-tsc: ~3.0.3 + peerDependenciesMeta: + "@ts-macro/tsc": + optional: true + "@typescript/native-preview": + optional: true + typescript: + optional: true + vue-tsc: + optional: true + checksum: 10c0/f15ba5ef752cb56db9289d71d62f371a06a04159d266efd7d19f7ff4a961730f4a2367f4f80d59322fbf6d2f3d5c7b36a2f3412c36e2cc49101976f5720939c8 + languageName: node + linkType: hard + +"rolldown@npm:1.0.0-beta.38, rolldown@npm:latest": + version: 1.0.0-beta.38 + resolution: "rolldown@npm:1.0.0-beta.38" + dependencies: + "@oxc-project/types": "npm:=0.89.0" + "@rolldown/binding-android-arm64": "npm:1.0.0-beta.38" + "@rolldown/binding-darwin-arm64": "npm:1.0.0-beta.38" + "@rolldown/binding-darwin-x64": "npm:1.0.0-beta.38" + "@rolldown/binding-freebsd-x64": "npm:1.0.0-beta.38" + "@rolldown/binding-linux-arm-gnueabihf": "npm:1.0.0-beta.38" + "@rolldown/binding-linux-arm64-gnu": "npm:1.0.0-beta.38" + "@rolldown/binding-linux-arm64-musl": "npm:1.0.0-beta.38" + "@rolldown/binding-linux-x64-gnu": "npm:1.0.0-beta.38" + "@rolldown/binding-linux-x64-musl": "npm:1.0.0-beta.38" + "@rolldown/binding-openharmony-arm64": "npm:1.0.0-beta.38" + "@rolldown/binding-wasm32-wasi": "npm:1.0.0-beta.38" + "@rolldown/binding-win32-arm64-msvc": "npm:1.0.0-beta.38" + "@rolldown/binding-win32-ia32-msvc": "npm:1.0.0-beta.38" + "@rolldown/binding-win32-x64-msvc": "npm:1.0.0-beta.38" + "@rolldown/pluginutils": "npm:1.0.0-beta.38" + ansis: "npm:^4.0.0" + dependenciesMeta: + "@rolldown/binding-android-arm64": + optional: true + "@rolldown/binding-darwin-arm64": + optional: true + "@rolldown/binding-darwin-x64": + optional: true + "@rolldown/binding-freebsd-x64": + optional: true + "@rolldown/binding-linux-arm-gnueabihf": + optional: true + "@rolldown/binding-linux-arm64-gnu": + optional: true + "@rolldown/binding-linux-arm64-musl": + optional: true + "@rolldown/binding-linux-x64-gnu": + optional: true + "@rolldown/binding-linux-x64-musl": + optional: true + "@rolldown/binding-openharmony-arm64": + optional: true + "@rolldown/binding-wasm32-wasi": + optional: true + "@rolldown/binding-win32-arm64-msvc": + optional: true + "@rolldown/binding-win32-ia32-msvc": + optional: true + "@rolldown/binding-win32-x64-msvc": + optional: true + bin: + rolldown: bin/cli.mjs + checksum: 10c0/5c77fe444585d9d06c2383dbb0480e9ef4c8c565c8f87119f8ee8d7f9e63df749eb24f3342c66273510635fd32aedda7cbad94f4c40c9ffed2e9e76097c0961e + languageName: node + linkType: hard + +"rollup@npm:^4.43.0": + version: 4.50.2 + resolution: "rollup@npm:4.50.2" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.50.2" + "@rollup/rollup-android-arm64": "npm:4.50.2" + "@rollup/rollup-darwin-arm64": "npm:4.50.2" + "@rollup/rollup-darwin-x64": "npm:4.50.2" + "@rollup/rollup-freebsd-arm64": "npm:4.50.2" + "@rollup/rollup-freebsd-x64": "npm:4.50.2" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.50.2" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.50.2" + "@rollup/rollup-linux-arm64-gnu": "npm:4.50.2" + "@rollup/rollup-linux-arm64-musl": "npm:4.50.2" + "@rollup/rollup-linux-loong64-gnu": "npm:4.50.2" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.50.2" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.50.2" + "@rollup/rollup-linux-riscv64-musl": "npm:4.50.2" + "@rollup/rollup-linux-s390x-gnu": "npm:4.50.2" + "@rollup/rollup-linux-x64-gnu": "npm:4.50.2" + "@rollup/rollup-linux-x64-musl": "npm:4.50.2" + "@rollup/rollup-openharmony-arm64": "npm:4.50.2" + "@rollup/rollup-win32-arm64-msvc": "npm:4.50.2" + "@rollup/rollup-win32-ia32-msvc": "npm:4.50.2" + "@rollup/rollup-win32-x64-msvc": "npm:4.50.2" + "@types/estree": "npm:1.0.8" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-freebsd-arm64": + optional: true + "@rollup/rollup-freebsd-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-loong64-gnu": + optional: true + "@rollup/rollup-linux-ppc64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-musl": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-openharmony-arm64": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10c0/5415d0a5ae6f37fa5f10997b3c5cff20c2ea6bd1636db90e59672969a4f83b29f6168bf9dd26c1276c2e37e1d55674472758da90cbc46c8b08ada5d0ec60eb9b + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + +"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.7.2": + version: 7.7.2 + resolution: "semver@npm:7.7.2" + bin: + semver: bin/semver.js + checksum: 10c0/aca305edfbf2383c22571cb7714f48cadc7ac95371b4b52362fb8eeffdfbc0de0669368b82b2b15978f8848f01d7114da65697e56cd8c37b0dab8c58e543f9ea + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: "npm:^3.0.0" + checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 + languageName: node + linkType: hard + +"siginfo@npm:^2.0.0": + version: 2.0.0 + resolution: "siginfo@npm:2.0.0" + checksum: 10c0/3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34 + languageName: node + linkType: hard + +"signal-exit@npm:^4.0.1": + version: 4.1.0 + resolution: "signal-exit@npm:4.1.0" + checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 + languageName: node + linkType: hard + +"smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.5 + resolution: "socks-proxy-agent@npm:8.0.5" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + socks: "npm:^2.8.3" + checksum: 10c0/5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6 + languageName: node + linkType: hard + +"socks@npm:^2.8.3": + version: 2.8.7 + resolution: "socks@npm:2.8.7" + dependencies: + ip-address: "npm:^10.0.1" + smart-buffer: "npm:^4.2.0" + checksum: 10c0/2805a43a1c4bcf9ebf6e018268d87b32b32b06fbbc1f9282573583acc155860dc361500f89c73bfbb157caa1b4ac78059eac0ef15d1811eb0ca75e0bdadbc9d2 + languageName: node + linkType: hard + +"source-map-js@npm:^1.2.0, source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + +"ssri@npm:^12.0.0": + version: 12.0.0 + resolution: "ssri@npm:12.0.0" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/caddd5f544b2006e88fa6b0124d8d7b28208b83c72d7672d5ade44d794525d23b540f3396108c4eb9280dcb7c01f0bef50682f5b4b2c34291f7c5e211fd1417d + languageName: node + linkType: hard + +"stackback@npm:0.0.2": + version: 0.0.2 + resolution: "stackback@npm:0.0.2" + checksum: 10c0/89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983 + languageName: node + linkType: hard + +"std-env@npm:^3.9.0": + version: 3.9.0 + resolution: "std-env@npm:3.9.0" + checksum: 10c0/4a6f9218aef3f41046c3c7ecf1f98df00b30a07f4f35c6d47b28329bc2531eef820828951c7d7b39a1c5eb19ad8a46e3ddfc7deb28f0a2f3ceebee11bab7ba50 + languageName: node + linkType: hard + +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0": + version: 4.2.3 + resolution: "string-width@npm:4.2.3" + dependencies: + emoji-regex: "npm:^8.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + strip-ansi: "npm:^6.0.1" + checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b + languageName: node + linkType: hard + +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": + version: 5.1.2 + resolution: "string-width@npm:5.1.2" + dependencies: + eastasianwidth: "npm:^0.2.0" + emoji-regex: "npm:^9.2.2" + strip-ansi: "npm:^7.0.1" + checksum: 10c0/ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca + languageName: node + linkType: hard + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: "npm:^5.0.1" + checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 + languageName: node + linkType: hard + +"strip-ansi@npm:^7.0.1": + version: 7.1.2 + resolution: "strip-ansi@npm:7.1.2" + dependencies: + ansi-regex: "npm:^6.0.1" + checksum: 10c0/0d6d7a023de33368fd042aab0bf48f4f4077abdfd60e5393e73c7c411e85e1b3a83507c11af2e656188511475776215df9ca589b4da2295c9455cc399ce1858b + languageName: node + linkType: hard + +"strip-literal@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-literal@npm:3.0.0" + dependencies: + js-tokens: "npm:^9.0.1" + checksum: 10c0/d81657f84aba42d4bbaf2a677f7e7f34c1f3de5a6726db8bc1797f9c0b303ba54d4660383a74bde43df401cf37cce1dff2c842c55b077a4ceee11f9e31fba828 + languageName: node + linkType: hard + +"supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 + languageName: node + linkType: hard + +"tar@npm:^7.4.3": + version: 7.4.3 + resolution: "tar@npm:7.4.3" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.0.1" + mkdirp: "npm:^3.0.1" + yallist: "npm:^5.0.0" + checksum: 10c0/d4679609bb2a9b48eeaf84632b6d844128d2412b95b6de07d53d8ee8baf4ca0857c9331dfa510390a0727b550fd543d4d1a10995ad86cdf078423fbb8d99831d + languageName: node + linkType: hard + +"test-exclude@npm:^7.0.1": + version: 7.0.1 + resolution: "test-exclude@npm:7.0.1" + dependencies: + "@istanbuljs/schema": "npm:^0.1.2" + glob: "npm:^10.4.1" + minimatch: "npm:^9.0.4" + checksum: 10c0/6d67b9af4336a2e12b26a68c83308c7863534c65f27ed4ff7068a56f5a58f7ac703e8fc80f698a19bb154fd8f705cdf7ec347d9512b2c522c737269507e7b263 + languageName: node + linkType: hard + +"tinybench@npm:^2.9.0": + version: 2.9.0 + resolution: "tinybench@npm:2.9.0" + checksum: 10c0/c3500b0f60d2eb8db65250afe750b66d51623057ee88720b7f064894a6cb7eb93360ca824a60a31ab16dab30c7b1f06efe0795b352e37914a9d4bad86386a20c + languageName: node + linkType: hard + +"tinyexec@npm:^0.3.2": + version: 0.3.2 + resolution: "tinyexec@npm:0.3.2" + checksum: 10c0/3efbf791a911be0bf0821eab37a3445c2ba07acc1522b1fa84ae1e55f10425076f1290f680286345ed919549ad67527d07281f1c19d584df3b74326909eb1f90 + languageName: node + linkType: hard + +"tinyexec@npm:^1.0.1": + version: 1.0.1 + resolution: "tinyexec@npm:1.0.1" + checksum: 10c0/e1ec3c8194a0427ce001ba69fd933d0c957e2b8994808189ed8020d3e0c01299aea8ecf0083cc514ecbf90754695895f2b5c0eac07eb2d0c406f7d4fbb8feade + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.14, tinyglobby@npm:^0.2.15": + version: 0.2.15 + resolution: "tinyglobby@npm:0.2.15" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.3" + checksum: 10c0/869c31490d0d88eedb8305d178d4c75e7463e820df5a9b9d388291daf93e8b1eb5de1dad1c1e139767e4269fe75f3b10d5009b2cc14db96ff98986920a186844 + languageName: node + linkType: hard + +"tinypool@npm:^1.1.1": + version: 1.1.1 + resolution: "tinypool@npm:1.1.1" + checksum: 10c0/bf26727d01443061b04fa863f571016950888ea994ba0cd8cba3a1c51e2458d84574341ab8dbc3664f1c3ab20885c8cf9ff1cc4b18201f04c2cde7d317fff69b + languageName: node + linkType: hard + +"tinyrainbow@npm:^2.0.0": + version: 2.0.0 + resolution: "tinyrainbow@npm:2.0.0" + checksum: 10c0/c83c52bef4e0ae7fb8ec6a722f70b5b6fa8d8be1c85792e829f56c0e1be94ab70b293c032dc5048d4d37cfe678f1f5babb04bdc65fd123098800148ca989184f + languageName: node + linkType: hard + +"tinyspy@npm:^4.0.3": + version: 4.0.3 + resolution: "tinyspy@npm:4.0.3" + checksum: 10c0/0a92a18b5350945cc8a1da3a22c9ad9f4e2945df80aaa0c43e1b3a3cfb64d8501e607ebf0305e048e3c3d3e0e7f8eb10cea27dc17c21effb73e66c4a3be36373 + languageName: node + linkType: hard + +"tree-kill@npm:^1.2.2": + version: 1.2.2 + resolution: "tree-kill@npm:1.2.2" + bin: + tree-kill: cli.js + checksum: 10c0/7b1b7c7f17608a8f8d20a162e7957ac1ef6cd1636db1aba92f4e072dc31818c2ff0efac1e3d91064ede67ed5dc57c565420531a8134090a12ac10cf792ab14d2 + languageName: node + linkType: hard + +"ts-poet@npm:^6.12.0": + version: 6.12.0 + resolution: "ts-poet@npm:6.12.0" + dependencies: + dprint-node: "npm:^1.0.8" + checksum: 10c0/605ac770055259b618ba657b88995f7851d8a6106d9c335e71c8442be51a9b8a87c501aa7ab4babb43700a32ff47dc1cd877f78892c15166ded87c311c878735 + languageName: node + linkType: hard + +"ts-proto-descriptors@npm:2.0.0": + version: 2.0.0 + resolution: "ts-proto-descriptors@npm:2.0.0" + dependencies: + "@bufbuild/protobuf": "npm:^2.0.0" + checksum: 10c0/a4f47a6db7de6b328a5b22bb0bed2a0dac929c28002567613db7980e0a8392b9148530e432a602a8c6b2cfda9909be9568a2e5c545f5624bb5d5eba52031c059 + languageName: node + linkType: hard + +"ts-proto@npm:^2.7.3": + version: 2.7.7 + resolution: "ts-proto@npm:2.7.7" + dependencies: + "@bufbuild/protobuf": "npm:^2.0.0" + case-anything: "npm:^2.1.13" + ts-poet: "npm:^6.12.0" + ts-proto-descriptors: "npm:2.0.0" + bin: + protoc-gen-ts_proto: protoc-gen-ts_proto + checksum: 10c0/fa0daf0b6d635ab87c950c4a1c79722d80cd3d3800e950a6dd36aad68cec4c39a2bb66b96f45c8fe0302672dbb571ffa5a6baa0a05702642e729a382efdc0dbc + languageName: node + linkType: hard + +"tsdown@npm:latest": + version: 0.15.1 + resolution: "tsdown@npm:0.15.1" + dependencies: + ansis: "npm:^4.1.0" + cac: "npm:^6.7.14" + chokidar: "npm:^4.0.3" + debug: "npm:^4.4.1" + diff: "npm:^8.0.2" + empathic: "npm:^2.0.0" + hookable: "npm:^5.5.3" + rolldown: "npm:latest" + rolldown-plugin-dts: "npm:^0.16.5" + semver: "npm:^7.7.2" + tinyexec: "npm:^1.0.1" + tinyglobby: "npm:^0.2.15" + tree-kill: "npm:^1.2.2" + unconfig: "npm:^7.3.3" + peerDependencies: + "@arethetypeswrong/core": ^0.18.1 + publint: ^0.3.0 + typescript: ^5.0.0 + unplugin-lightningcss: ^0.4.0 + unplugin-unused: ^0.5.0 + peerDependenciesMeta: + "@arethetypeswrong/core": + optional: true + publint: + optional: true + typescript: + optional: true + unplugin-lightningcss: + optional: true + unplugin-unused: + optional: true + bin: + tsdown: dist/run.mjs + checksum: 10c0/10aadb974df6bebb28dd952c880c283e92bdf29192044c6995f96b1ccddd0d7d3a26b89d855351381c62a55d462fad79a68000659ce0de4d423efee23b6e13f2 + languageName: node + linkType: hard + +"tslib@npm:^2.4.0": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 + languageName: node + linkType: hard + +"unconfig@npm:^7.3.3": + version: 7.3.3 + resolution: "unconfig@npm:7.3.3" + dependencies: + "@quansync/fs": "npm:^0.1.5" + defu: "npm:^6.1.4" + jiti: "npm:^2.5.1" + quansync: "npm:^0.2.11" + checksum: 10c0/7c1b0688ce7ba36a92cfeb36f248a61b86e27807b25a4504acc3e0fbf19a217fc74ba80fe45e3205def7648666de51d2b28551e61c86d1c54dcb8e129a011e58 + languageName: node + linkType: hard + +"undici-types@npm:~7.12.0": + version: 7.12.0 + resolution: "undici-types@npm:7.12.0" + checksum: 10c0/326e455bbc0026db1d6b81c76a1cf10c63f7e2f9821db2e24fdc258f482814e5bfa8481f8910d07c68e305937c5c049610fdc441c5e8b7bb0daca7154fb8a306 + languageName: node + linkType: hard + +"unique-filename@npm:^4.0.0": + version: 4.0.0 + resolution: "unique-filename@npm:4.0.0" + dependencies: + unique-slug: "npm:^5.0.0" + checksum: 10c0/38ae681cceb1408ea0587b6b01e29b00eee3c84baee1e41fd5c16b9ed443b80fba90c40e0ba69627e30855570a34ba8b06702d4a35035d4b5e198bf5a64c9ddc + languageName: node + linkType: hard + +"unique-slug@npm:^5.0.0": + version: 5.0.0 + resolution: "unique-slug@npm:5.0.0" + dependencies: + imurmurhash: "npm:^0.1.4" + checksum: 10c0/d324c5a44887bd7e105ce800fcf7533d43f29c48757ac410afd42975de82cc38ea2035c0483f4de82d186691bf3208ef35c644f73aa2b1b20b8e651be5afd293 + languageName: node + linkType: hard + +"vite-node@npm:3.2.4": + version: 3.2.4 + resolution: "vite-node@npm:3.2.4" + dependencies: + cac: "npm:^6.7.14" + debug: "npm:^4.4.1" + es-module-lexer: "npm:^1.7.0" + pathe: "npm:^2.0.3" + vite: "npm:^5.0.0 || ^6.0.0 || ^7.0.0-0" + bin: + vite-node: vite-node.mjs + checksum: 10c0/6ceca67c002f8ef6397d58b9539f80f2b5d79e103a18367288b3f00a8ab55affa3d711d86d9112fce5a7fa658a212a087a005a045eb8f4758947dd99af2a6c6b + languageName: node + linkType: hard + +"vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0": + version: 7.1.5 + resolution: "vite@npm:7.1.5" + dependencies: + esbuild: "npm:^0.25.0" + fdir: "npm:^6.5.0" + fsevents: "npm:~2.3.3" + picomatch: "npm:^4.0.3" + postcss: "npm:^8.5.6" + rollup: "npm:^4.43.0" + tinyglobby: "npm:^0.2.15" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + jiti: ">=1.21.0" + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/782d2f20c25541b26d1fb39bef5f194149caff39dc25b7836e25f049ca919f2e2ce186bddb21f3f20f6195354b3579ec637a8ca08d65b117f8b6f81e3e730a9c + languageName: node + linkType: hard + +"vitest@npm:^3.2.4": + version: 3.2.4 + resolution: "vitest@npm:3.2.4" + dependencies: + "@types/chai": "npm:^5.2.2" + "@vitest/expect": "npm:3.2.4" + "@vitest/mocker": "npm:3.2.4" + "@vitest/pretty-format": "npm:^3.2.4" + "@vitest/runner": "npm:3.2.4" + "@vitest/snapshot": "npm:3.2.4" + "@vitest/spy": "npm:3.2.4" + "@vitest/utils": "npm:3.2.4" + chai: "npm:^5.2.0" + debug: "npm:^4.4.1" + expect-type: "npm:^1.2.1" + magic-string: "npm:^0.30.17" + pathe: "npm:^2.0.3" + picomatch: "npm:^4.0.2" + std-env: "npm:^3.9.0" + tinybench: "npm:^2.9.0" + tinyexec: "npm:^0.3.2" + tinyglobby: "npm:^0.2.14" + tinypool: "npm:^1.1.1" + tinyrainbow: "npm:^2.0.0" + vite: "npm:^5.0.0 || ^6.0.0 || ^7.0.0-0" + vite-node: "npm:3.2.4" + why-is-node-running: "npm:^2.3.0" + peerDependencies: + "@edge-runtime/vm": "*" + "@types/debug": ^4.1.12 + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + "@vitest/browser": 3.2.4 + "@vitest/ui": 3.2.4 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/debug": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + bin: + vitest: vitest.mjs + checksum: 10c0/5bf53ede3ae6a0e08956d72dab279ae90503f6b5a05298a6a5e6ef47d2fd1ab386aaf48fafa61ed07a0ebfe9e371772f1ccbe5c258dd765206a8218bf2eb79eb + languageName: node + linkType: hard + +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: "npm:^2.0.0" + bin: + node-which: ./bin/node-which + checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f + languageName: node + linkType: hard + +"which@npm:^5.0.0": + version: 5.0.0 + resolution: "which@npm:5.0.0" + dependencies: + isexe: "npm:^3.1.1" + bin: + node-which: bin/which.js + checksum: 10c0/e556e4cd8b7dbf5df52408c9a9dd5ac6518c8c5267c8953f5b0564073c66ed5bf9503b14d876d0e9c7844d4db9725fb0dcf45d6e911e17e26ab363dc3965ae7b + languageName: node + linkType: hard + +"why-is-node-running@npm:^2.3.0": + version: 2.3.0 + resolution: "why-is-node-running@npm:2.3.0" + dependencies: + siginfo: "npm:^2.0.0" + stackback: "npm:0.0.2" + bin: + why-is-node-running: cli.js + checksum: 10c0/1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054 + languageName: node + linkType: hard + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da + languageName: node + linkType: hard + +"wrap-ansi@npm:^8.1.0": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" + dependencies: + ansi-styles: "npm:^6.1.0" + string-width: "npm:^5.0.1" + strip-ansi: "npm:^7.0.1" + checksum: 10c0/138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60 + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 + languageName: node + linkType: hard diff --git a/wasm-msg/src/sync.rs b/wasm-msg/src/sync.rs index 87174cf..3cd49b7 100644 --- a/wasm-msg/src/sync.rs +++ b/wasm-msg/src/sync.rs @@ -8,7 +8,11 @@ where Req: prost::Message + Default, Res: prost::Message, { - let request = message::consume_request::(ptr); + let request = if ptr.is_null() { + Req::default() + } else { + message::consume_request::(ptr) + }; let result = handler(request); message::transfer_response(result) } diff --git a/wasm/node-host/src/index.ts b/wasm/node-host/src/index.ts index ba72028..74a122c 100644 --- a/wasm/node-host/src/index.ts +++ b/wasm/node-host/src/index.ts @@ -22,9 +22,8 @@ const wasmModule = new WebAssembly.Module(wasmBuffer); const api = new ApiBuilder() .guest('set_resolver_state', SetResolverStateRequest, Void, false) + .guestRaw('flush_logs') .guest('resolve', ResolveFlagsRequest, ResolveFlagsResponse, false) - .host('log_resolve', Void, Void, false, () => ({})) - .host('log_assign', Void, Void, false, () => ({})) .host('current_time', Void, Timestamp, false, () => { const now = Date.now(); return { seconds: Math.floor(now / 1000), nanos: (now % 1000) * 1000000 } @@ -40,7 +39,7 @@ api.set_resolver_state({ { const resp = api.resolve({ clientSecret: 'mkjJruAATQWjeY7foFIWfVAcBWnci2YF', - apply: false, + apply: true, evaluationContext: { targeting_key: 'tutorial_visitor', visitor_id: 'tutorial_visitor', @@ -71,5 +70,8 @@ api.set_resolver_state({ } } console.log(`tutorial-feature verified: reason=RESOLVE_REASON_MATCH variant=${rf.variant} title=${titleVal}`); + + const buf = api.flush_logs(new Uint8Array(0)); + fs.writeFileSync('events.bin', buf); } // Done: single flag verified above \ No newline at end of file