From d0ad46e49e33aa1d6f6779ba15d69565556af5cc Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Feb 2026 16:56:07 +0000 Subject: [PATCH 01/19] feat: migrate to InfluxDB 3 Core with influxdb3-javascript client Phase 1 of InfluxDB 3 migration: - Replace @influxdata/influxdb-client v1.24.0 with @influxdata/influxdb3-client v2.0.0 - Remove @influxdata/influxdb-client-apis (AuthorizationsAPI not available in v3) - Update environment variables: - INFLUX_URL -> INFLUX_HOST (port 8181) - INFLUX_BUCKET -> INFLUX_DATABASE - INFLUX_BUCKET_AUTH -> INFLUX_DATABASE_AUTH - Remove INFLUX_ORG (not used in InfluxDB 3) - Convert all Flux queries to SQL (Flux not supported in InfluxDB 3) - Add shared client utility (lib/influxdb.js) with: - createClient() for InfluxDB 3 client instantiation - query() helper for SQL queries - write() helper for line protocol writes - generateDeviceToken() for app-level device auth - Redesign device authorization for InfluxDB 3 Core: - Use application-level tokens instead of InfluxDB-native tokens - Store device credentials in deviceauth table - Enterprise upgrade path: use resource tokens for per-device permissions Breaking changes: - Measurement queries must use SQL instead of Flux - Device tokens are now application-level (not InfluxDB-native) https://claude.ai/code/session_01Pga27ES6JQsJo1joSVZMJY --- .env.development | 13 ++- lib/influxdb.js | 84 +++++++++++++ package.json | 3 +- pages/api/devices/[[...deviceParams]].js | 54 +++++++-- pages/api/devices/_devices.js | 121 ++++++++++++------- pages/api/devices/create.js | 143 +++++++++++++---------- pages/api/measurements/index.js | 66 ++++++++--- 7 files changed, 348 insertions(+), 136 deletions(-) create mode 100644 lib/influxdb.js diff --git a/.env.development b/.env.development index 3846cd4..4a6a9da 100644 --- a/.env.development +++ b/.env.development @@ -1,5 +1,12 @@ # Development environment non-secret defaults +# InfluxDB 3 Core configuration -INFLUX_URL=http://localhost:8086 -INFLUX_BUCKET=iot_center -INFLUX_BUCKET_AUTH=iot_center_devices \ No newline at end of file +# InfluxDB 3 server URL (default port is 8181) +INFLUX_HOST=http://localhost:8181 + +# Database names (equivalent to v2 buckets) +INFLUX_DATABASE=iot_center +INFLUX_DATABASE_AUTH=iot_center_devices + +# Token should be set in .env.local (not committed to git) +# INFLUX_TOKEN=your_admin_token_here \ No newline at end of file diff --git a/lib/influxdb.js b/lib/influxdb.js new file mode 100644 index 0000000..8ea9f1b --- /dev/null +++ b/lib/influxdb.js @@ -0,0 +1,84 @@ +import { InfluxDBClient } from '@influxdata/influxdb3-client' + +/** + * Creates a new InfluxDB 3 client instance. + * Always call client.close() when done to release resources. + * + * @returns {InfluxDBClient} A new InfluxDB client instance + */ +export function createClient() { + return new InfluxDBClient({ + host: process.env.INFLUX_HOST, + token: process.env.INFLUX_TOKEN, + }) +} + +/** + * Environment configuration for InfluxDB 3 + */ +export const config = { + get host() { + return process.env.INFLUX_HOST + }, + get token() { + return process.env.INFLUX_TOKEN + }, + get database() { + return process.env.INFLUX_DATABASE + }, + get databaseAuth() { + return process.env.INFLUX_DATABASE_AUTH + }, +} + +/** + * Executes a SQL query and returns results as an array. + * + * @param {string} sql - The SQL query to execute + * @param {string} database - The database to query + * @returns {Promise} Array of row objects + */ +export async function query(sql, database) { + const client = createClient() + try { + const results = [] + for await (const row of client.query(sql, database)) { + results.push(row) + } + return results + } finally { + await client.close() + } +} + +/** + * Writes line protocol data to InfluxDB. + * + * @param {string|Array} lines - Line protocol string(s) to write + * @param {string} database - The database to write to + */ +export async function write(lines, database) { + const client = createClient() + try { + const data = Array.isArray(lines) ? lines.join('\n') : lines + await client.write(data, database) + } finally { + await client.close() + } +} + +/** + * Generates a secure application-level token for device authentication. + * In InfluxDB 3 Core, we use application-level tokens stored in the database + * rather than InfluxDB-native authorization tokens. + * + * @returns {string} A secure random token string + */ +export function generateDeviceToken() { + // Generate a secure random token using crypto + const bytes = new Uint8Array(32) + crypto.getRandomValues(bytes) + return 'iot_' + Array.from(bytes) + .map(b => b.toString(16).padStart(2, '0')) + .join('') +} diff --git a/package.json b/package.json index 7ca0f6f..0418a0f 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,7 @@ "start": "next start" }, "dependencies": { - "@influxdata/influxdb-client": "^1.24.0", - "@influxdata/influxdb-client-apis": "^1.24.0", + "@influxdata/influxdb3-client": "^2.0.0", "next": "^14.2.35", "postcss": "^8.4.31", "react": "^18.2.0", diff --git a/pages/api/devices/[[...deviceParams]].js b/pages/api/devices/[[...deviceParams]].js index 2474d52..020d308 100644 --- a/pages/api/devices/[[...deviceParams]].js +++ b/pages/api/devices/[[...deviceParams]].js @@ -1,30 +1,62 @@ import { getMeasurements } from '../measurements' import { getDevices } from './_devices' +/** + * API handler for device-related endpoints: + * + * GET /api/devices - List all registered devices + * GET /api/devices/:deviceId - Get a specific device + * POST /api/devices/:deviceId/measurements - Query measurements for a device + * + * Note: For measurement queries, the `query` parameter must be a SQL query. + * Flux queries are not supported in InfluxDB 3. + * + * Example SQL query for measurements: + * SELECT * FROM home WHERE room = 'Kitchen' ORDER BY time DESC LIMIT 100 + */ export default async function handler(req, res) { try { - const {deviceParams} = req.query + const { deviceParams } = req.query let deviceId = undefined let path = [] - if(Array.isArray(deviceParams)) { + + if (Array.isArray(deviceParams)) { [deviceId, ...path] = deviceParams } - if(Array.isArray(path) && path[0] === 'measurements') { - const {query} = req.body - if(query) { - const data = await getMeasurements(query) - res.status(200).send(data) + + // Handle measurement queries: POST /api/devices/:deviceId/measurements + if (Array.isArray(path) && path[0] === 'measurements') { + if (req.method !== 'POST') { + return res.status(405).json({ error: 'Method not allowed. Use POST for measurement queries.' }) + } + + const { query } = req.body || {} + if (!query) { + return res.status(400).json({ + error: 'Missing query parameter', + hint: 'Provide a SQL query in the request body. Flux is not supported in InfluxDB 3.', + example: "SELECT * FROM home WHERE time >= now() - INTERVAL '1 hour' ORDER BY time DESC", + }) } + + const data = await getMeasurements(query) + res.status(200).send(data) return } - + + // Handle device listing/retrieval: GET /api/devices or GET /api/devices/:deviceId + if (req.method !== 'GET') { + return res.status(405).json({ error: 'Method not allowed' }) + } + const devices = await getDevices(deviceId) res.status(200).json( Object.values(devices) - .filter((x) => x.deviceId && x.key) // ignore deleted or unknown devices + .filter((x) => x.deviceId && x.key) // Ignore deleted or unknown devices .sort((a, b) => a.deviceId.localeCompare(b.deviceId)) ) - } catch(err) { - res.status(500).json({ error: `failed to load data: ${err}` }) + } catch (err) { + console.error('Device API error:', err) + res.status(500).json({ error: `Failed to load data: ${err.message || err}` }) } } diff --git a/pages/api/devices/_devices.js b/pages/api/devices/_devices.js index 587dad4..e6d7e15 100644 --- a/pages/api/devices/_devices.js +++ b/pages/api/devices/_devices.js @@ -1,48 +1,83 @@ -import { InfluxDB } from '@influxdata/influxdb-client' -import { flux } from '@influxdata/influxdb-client' +import { query, config } from '../../../lib/influxdb' -const INFLUX_ORG = process.env.INFLUX_ORG -const INFLUX_BUCKET_AUTH = process.env.INFLUX_BUCKET_AUTH -const influxdb = new InfluxDB({url: process.env.INFLUX_URL, token: process.env.INFLUX_TOKEN}) +/** + * Gets devices or a particular device when deviceId is specified. + * Tokens are not returned unless deviceId is specified. + * It can also return devices with empty/unknown key - such devices + * can be ignored (no valid authorization is associated). + * + * @param {string} [deviceId] - Optional deviceId to filter by + * @returns {Promise>} + */ +export async function getDevices(deviceId) { + const database = config.databaseAuth + + // Build SQL query + // When deviceId is specified, return all fields including token + // Otherwise, exclude token field for security + let sql + if (deviceId !== undefined) { + // Get specific device with token + sql = ` + SELECT time, deviceId, key, token + FROM deviceauth + WHERE deviceId = '${escapeString(deviceId)}' + ORDER BY time DESC + LIMIT 1 + ` + } else { + // Get all devices without tokens + sql = ` + SELECT time, deviceId, key + FROM deviceauth + ORDER BY time DESC + ` + } + + const rows = await query(sql, database) + + // Transform rows into devices object keyed by deviceId + const devices = {} + + for (const row of rows) { + const id = row.deviceId + if (!id) { + continue + } + + // If we already have this device, only update if this row is newer + if (devices[id]) { + const existingTime = new Date(devices[id].updatedAt).getTime() + const rowTime = new Date(row.time).getTime() + if (rowTime <= existingTime) { + continue + } + } + + devices[id] = { + deviceId: id, + key: row.key, + updatedAt: row.time, + } + + // Only include token when querying specific device + if (deviceId !== undefined && row.token) { + devices[id].token = row.token + } + } + + return devices +} /** - * Gets devices or a particular device when deviceId is specified. Tokens - * are not returned unless deviceId is specified. It can also return devices - * with empty/unknown key, such devices can be ignored (InfluxDB authorization is not associated). - * @param deviceId optional deviceId - * @returns promise with an Record. + * Escapes a string for use in SQL queries to prevent SQL injection. + * @param {string} str - The string to escape + * @returns {string} The escaped string */ - export async function getDevices(deviceId) { - const queryApi = influxdb.getQueryApi(INFLUX_ORG) - const deviceFilter = - deviceId !== undefined - ? flux` and r.deviceId == "${deviceId}"` - : flux` and r._field != "token"` - const fluxQuery = flux`from(bucket:${INFLUX_BUCKET_AUTH}) - |> range(start: 0) - |> filter(fn: (r) => r._measurement == "deviceauth"${deviceFilter}) - |> last()` - const devices = {} - - return await new Promise((resolve, reject) => { - queryApi.queryRows(fluxQuery, { - next(row, tableMeta) { - const o = tableMeta.toObject(row) - const deviceId = o.deviceId - if (!deviceId) { - return - } - const device = devices[deviceId] || (devices[deviceId] = {deviceId}) - device[o._field] = o._value - if (!device.updatedAt || device.updatedAt < o._time) { - device.updatedAt = o._time - } - }, - error: reject, - complete() { - resolve(devices) - }, - }) - }) +function escapeString(str) { + if (typeof str !== 'string') { + return str } - \ No newline at end of file + // Escape single quotes by doubling them + return str.replace(/'/g, "''") +} diff --git a/pages/api/devices/create.js b/pages/api/devices/create.js index 711ba59..369a016 100644 --- a/pages/api/devices/create.js +++ b/pages/api/devices/create.js @@ -1,74 +1,99 @@ -import { InfluxDB } from '@influxdata/influxdb-client' import { getDevices } from './_devices' -import { AuthorizationsAPI, BucketsAPI } from '@influxdata/influxdb-client-apis' -import { Point } from '@influxdata/influxdb-client' - -const INFLUX_ORG = process.env.INFLUX_ORG -const INFLUX_BUCKET_AUTH = process.env.INFLUX_BUCKET_AUTH -const INFLUX_BUCKET = process.env.INFLUX_BUCKET - -const influxdb = new InfluxDB({url: process.env.INFLUX_URL, token: process.env.INFLUX_TOKEN}) +import { write, config, generateDeviceToken } from '../../../lib/influxdb' export default async function handler(req, res) { + if (req.method !== 'POST') { + return res.status(405).json({ error: 'Method not allowed' }) + } + try { const deviceId = JSON.parse(req.body)?.deviceId - const devices = await createDevice(deviceId) - res.send(200) - } catch(err) { - res.status(500).json({ error: `failed to load data: ${err}` }) + + if (!deviceId) { + return res.status(400).json({ error: 'deviceId is required' }) + } + + const result = await createDevice(deviceId) + res.status(200).json(result) + } catch (err) { + console.error('Device creation error:', err) + res.status(500).json({ error: `Failed to create device: ${err.message || err}` }) } } -/** Creates an authorization for a deviceId and writes it to a bucket */ +/** + * Creates a new device with an application-level authentication token. + * + * In InfluxDB 3 Core, we use application-level tokens stored in the database + * rather than InfluxDB-native authorization tokens (which require Enterprise + * resource tokens for per-device permissions). + * + * @param {string} deviceId - The unique device identifier + * @returns {Promise<{deviceId: string, key: string, message: string}>} + */ async function createDevice(deviceId) { - let device = (await getDevices(deviceId)) || {} - let authorizationValid = !!Object.values(device)[0]?.key - if(authorizationValid) { - console.log(JSON.stringify(device)) - return Promise.reject('This device ID is already registered and has an authorization.') - } else { - console.log(`createDeviceAuthorization: deviceId=${deviceId}`) - const authorization = await createAuthorization(deviceId) - const writeApi = influxdb.getWriteApi(INFLUX_ORG, INFLUX_BUCKET_AUTH, 'ms', { - batchSize: 2, - }) - const point = new Point('deviceauth') - .tag('deviceId', deviceId) - .stringField('key', authorization.id) - .stringField('token', authorization.token) - writeApi.writePoint(point) - await writeApi.close() - return + // Check if device already exists + const existingDevices = await getDevices(deviceId) + const existingDevice = Object.values(existingDevices)[0] + + if (existingDevice?.key) { + throw new Error('This device ID is already registered and has an authorization.') + } + + console.log(`createDevice: deviceId=${deviceId}`) + + // Generate application-level token for the device + const deviceToken = generateDeviceToken() + const deviceKey = `device_${deviceId}_${Date.now()}` + + // Write device auth record using line protocol + // Table: deviceauth + // Tags: deviceId + // Fields: key, token + const lineProtocol = `deviceauth,deviceId=${escapeTagValue(deviceId)} key="${escapeFieldValue(deviceKey)}",token="${escapeFieldValue(deviceToken)}"` + + await write(lineProtocol, config.databaseAuth) + + console.log(`Device created: ${deviceId}`) + + return { + deviceId, + key: deviceKey, + token: deviceToken, + database: config.database, + host: config.host, + message: 'Device registered successfully. Use the provided token for device authentication.', } } - /** - * Creates an authorization for a supplied deviceId - * @param {string} deviceId client identifier - * @returns {import('@influxdata/influxdb-client-apis').Authorization} promise with authorization or an error +/** + * Escapes a tag value for line protocol. + * Tags cannot contain spaces, commas, or equals signs without escaping. + * @param {string} value - The tag value to escape + * @returns {string} The escaped tag value */ - async function createAuthorization(deviceId) { - const authorizationsAPI = new AuthorizationsAPI(influxdb) - const bucketsAPI = new BucketsAPI(influxdb) - const DESC_PREFIX = 'IoTCenterDevice: ' +function escapeTagValue(value) { + if (typeof value !== 'string') { + return value + } + return value + .replace(/\\/g, '\\\\') + .replace(/ /g, '\\ ') + .replace(/,/g, '\\,') + .replace(/=/g, '\\=') +} - const buckets = await bucketsAPI.getBuckets({name: INFLUX_BUCKET, orgID: INFLUX_ORG}) - const bucketId = buckets.buckets[0]?.id - - return await authorizationsAPI.postAuthorizations({ - body: { - orgID: INFLUX_ORG, - description: DESC_PREFIX + deviceId, - permissions: [ - { - action: 'read', - resource: {type: 'buckets', id: bucketId, orgID: INFLUX_ORG}, - }, - { - action: 'write', - resource: {type: 'buckets', id: bucketId, orgID: INFLUX_ORG}, - }, - ], - }, - }) +/** + * Escapes a string field value for line protocol. + * String fields are wrapped in quotes and need backslash and quote escaping. + * @param {string} value - The field value to escape + * @returns {string} The escaped field value + */ +function escapeFieldValue(value) { + if (typeof value !== 'string') { + return value } + return value + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') +} diff --git a/pages/api/measurements/index.js b/pages/api/measurements/index.js index e5ad2e9..64efdf5 100644 --- a/pages/api/measurements/index.js +++ b/pages/api/measurements/index.js @@ -1,21 +1,51 @@ -import { InfluxDB } from '@influxdata/influxdb-client' +import { query, config } from '../../../lib/influxdb' -const INFLUX_ORG = process.env.INFLUX_ORG -const influxdb = new InfluxDB({url: process.env.INFLUX_URL, token: process.env.INFLUX_TOKEN}) +/** + * Executes a SQL query against the InfluxDB 3 database and returns results. + * + * Note: In InfluxDB 3, Flux is not supported. All queries must use SQL or InfluxQL. + * This function accepts SQL queries and returns results as CSV-formatted text + * for backward compatibility with the original API response format. + * + * @param {string} sqlQuery - The SQL query to execute + * @returns {Promise} CSV-formatted query results + */ +export async function getMeasurements(sqlQuery) { + const database = config.database + const rows = await query(sqlQuery, database) -export async function getMeasurements(fluxQuery) { - const queryApi = influxdb.getQueryApi(INFLUX_ORG) + if (rows.length === 0) { + return '' + } - return await new Promise((resolve, reject) => { - let result = '' - queryApi.queryLines(fluxQuery, { - next(line) { - result = result.concat(`${line}\n`) - }, - error: reject, - complete() { - resolve(result) - }, - }) - }) - } \ No newline at end of file + // Convert results to CSV format for backward compatibility + const columns = Object.keys(rows[0]) + const header = columns.join(',') + const dataRows = rows.map(row => + columns.map(col => formatCsvValue(row[col])).join(',') + ) + + return [header, ...dataRows].join('\n') + '\n' +} + +/** + * Formats a value for CSV output. + * @param {any} value - The value to format + * @returns {string} The formatted CSV value + */ +function formatCsvValue(value) { + if (value === null || value === undefined) { + return '' + } + if (typeof value === 'string') { + // Escape quotes and wrap in quotes if contains comma, quote, or newline + if (value.includes(',') || value.includes('"') || value.includes('\n')) { + return `"${value.replace(/"/g, '""')}"` + } + return value + } + if (value instanceof Date) { + return value.toISOString() + } + return String(value) +} From 2431ad93f55604ddabc1d9690d3454c0a9304f3d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 9 Feb 2026 19:13:08 +0000 Subject: [PATCH 02/19] fix: use Point class from influxdb3-client and fix Node.js crypto - Import Point class from @influxdata/influxdb3-client and re-export - Use Point.measurement().setTag().setStringField() for type-safe writes - Fix crypto: use Node.js randomBytes instead of Web Crypto API - Remove manual line protocol escaping (Point handles it internally) https://claude.ai/code/session_01Pga27ES6JQsJo1joSVZMJY --- lib/influxdb.js | 14 ++++++------ pages/api/devices/create.js | 43 ++++++------------------------------- 2 files changed, 14 insertions(+), 43 deletions(-) diff --git a/lib/influxdb.js b/lib/influxdb.js index 8ea9f1b..460d406 100644 --- a/lib/influxdb.js +++ b/lib/influxdb.js @@ -1,4 +1,8 @@ -import { InfluxDBClient } from '@influxdata/influxdb3-client' +import { InfluxDBClient, Point } from '@influxdata/influxdb3-client' +import { randomBytes } from 'crypto' + +// Re-export Point class for use in other modules +export { Point } /** * Creates a new InfluxDB 3 client instance. @@ -75,10 +79,6 @@ export async function write(lines, database) { * @returns {string} A secure random token string */ export function generateDeviceToken() { - // Generate a secure random token using crypto - const bytes = new Uint8Array(32) - crypto.getRandomValues(bytes) - return 'iot_' + Array.from(bytes) - .map(b => b.toString(16).padStart(2, '0')) - .join('') + // Generate a secure random token using Node.js crypto + return 'iot_' + randomBytes(32).toString('hex') } diff --git a/pages/api/devices/create.js b/pages/api/devices/create.js index 369a016..3b264c8 100644 --- a/pages/api/devices/create.js +++ b/pages/api/devices/create.js @@ -1,5 +1,5 @@ import { getDevices } from './_devices' -import { write, config, generateDeviceToken } from '../../../lib/influxdb' +import { write, config, generateDeviceToken, Point } from '../../../lib/influxdb' export default async function handler(req, res) { if (req.method !== 'POST') { @@ -46,13 +46,16 @@ async function createDevice(deviceId) { const deviceToken = generateDeviceToken() const deviceKey = `device_${deviceId}_${Date.now()}` - // Write device auth record using line protocol + // Write device auth record using Point class // Table: deviceauth // Tags: deviceId // Fields: key, token - const lineProtocol = `deviceauth,deviceId=${escapeTagValue(deviceId)} key="${escapeFieldValue(deviceKey)}",token="${escapeFieldValue(deviceToken)}"` + const point = Point.measurement('deviceauth') + .setTag('deviceId', deviceId) + .setStringField('key', deviceKey) + .setStringField('token', deviceToken) - await write(lineProtocol, config.databaseAuth) + await write(point.toLineProtocol(), config.databaseAuth) console.log(`Device created: ${deviceId}`) @@ -65,35 +68,3 @@ async function createDevice(deviceId) { message: 'Device registered successfully. Use the provided token for device authentication.', } } - -/** - * Escapes a tag value for line protocol. - * Tags cannot contain spaces, commas, or equals signs without escaping. - * @param {string} value - The tag value to escape - * @returns {string} The escaped tag value - */ -function escapeTagValue(value) { - if (typeof value !== 'string') { - return value - } - return value - .replace(/\\/g, '\\\\') - .replace(/ /g, '\\ ') - .replace(/,/g, '\\,') - .replace(/=/g, '\\=') -} - -/** - * Escapes a string field value for line protocol. - * String fields are wrapped in quotes and need backslash and quote escaping. - * @param {string} value - The field value to escape - * @returns {string} The escaped field value - */ -function escapeFieldValue(value) { - if (typeof value !== 'string') { - return value - } - return value - .replace(/\\/g, '\\\\') - .replace(/"/g, '\\"') -} From 41c7e1a804c38bfc72f96aec1d39b950dbd3e1cf Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Mon, 9 Feb 2026 17:22:13 -0600 Subject: [PATCH 03/19] Apply jstirnaman review suggestions for InfluxDB 3 migration (#20) - Remove redundant note about SQL queries in device endpoint comments - Simplify hint text for missing query parameter - Update device token storage comment to clarify Enterprise vs Core approaches - Clarify SQL/InfluxQL query requirement in measurements documentation --- pages/api/devices/[[...deviceParams]].js | 95 ++++++++++---------- pages/api/devices/create.js | 110 +++++++++++------------ pages/api/measurements/index.js | 54 ++++++----- 3 files changed, 125 insertions(+), 134 deletions(-) diff --git a/pages/api/devices/[[...deviceParams]].js b/pages/api/devices/[[...deviceParams]].js index 020d308..0bbfcce 100644 --- a/pages/api/devices/[[...deviceParams]].js +++ b/pages/api/devices/[[...deviceParams]].js @@ -1,5 +1,5 @@ -import { getMeasurements } from '../measurements' -import { getDevices } from './_devices' +import { getMeasurements } from '../measurements'; +import { getDevices } from './_devices'; /** * API handler for device-related endpoints: @@ -8,55 +8,54 @@ import { getDevices } from './_devices' * GET /api/devices/:deviceId - Get a specific device * POST /api/devices/:deviceId/measurements - Query measurements for a device * - * Note: For measurement queries, the `query` parameter must be a SQL query. - * Flux queries are not supported in InfluxDB 3. + * * Example SQL query for measurements: * SELECT * FROM home WHERE room = 'Kitchen' ORDER BY time DESC LIMIT 100 */ export default async function handler(req, res) { - try { - const { deviceParams } = req.query - let deviceId = undefined - let path = [] - - if (Array.isArray(deviceParams)) { - [deviceId, ...path] = deviceParams - } - - // Handle measurement queries: POST /api/devices/:deviceId/measurements - if (Array.isArray(path) && path[0] === 'measurements') { - if (req.method !== 'POST') { - return res.status(405).json({ error: 'Method not allowed. Use POST for measurement queries.' }) - } - - const { query } = req.body || {} - if (!query) { - return res.status(400).json({ - error: 'Missing query parameter', - hint: 'Provide a SQL query in the request body. Flux is not supported in InfluxDB 3.', - example: "SELECT * FROM home WHERE time >= now() - INTERVAL '1 hour' ORDER BY time DESC", - }) - } - - const data = await getMeasurements(query) - res.status(200).send(data) - return - } - - // Handle device listing/retrieval: GET /api/devices or GET /api/devices/:deviceId - if (req.method !== 'GET') { - return res.status(405).json({ error: 'Method not allowed' }) - } - - const devices = await getDevices(deviceId) - res.status(200).json( - Object.values(devices) - .filter((x) => x.deviceId && x.key) // Ignore deleted or unknown devices - .sort((a, b) => a.deviceId.localeCompare(b.deviceId)) - ) - } catch (err) { - console.error('Device API error:', err) - res.status(500).json({ error: `Failed to load data: ${err.message || err}` }) - } + try { + const { deviceParams } = req.query; + let deviceId = undefined; + let path = []; + + if (Array.isArray(deviceParams)) { + [deviceId, ...path] = deviceParams; + } + + // Handle measurement queries: POST /api/devices/:deviceId/measurements + if (Array.isArray(path) && path[0] === 'measurements') { + if (req.method !== 'POST') { + return res.status(405).json({ error: 'Method not allowed. Use POST for measurement queries.' }); + } + + const { query } = req.body || {}; + if (!query) { + return res.status(400).json({ + error: 'Missing query parameter', + hint: 'Provide a SQL query in the request body.', + example: "SELECT * FROM home WHERE time >= now() - INTERVAL '1 hour' ORDER BY time DESC", + }); + } + + const data = await getMeasurements(query); + res.status(200).send(data); + return; + } + + // Handle device listing/retrieval: GET /api/devices or GET /api/devices/:deviceId + if (req.method !== 'GET') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + const devices = await getDevices(deviceId); + res.status(200).json( + Object.values(devices) + .filter((x) => x.deviceId && x.key) // Ignore deleted or unknown devices + .sort((a, b) => a.deviceId.localeCompare(b.deviceId)), + ); + } catch (err) { + console.error('Device API error:', err); + res.status(500).json({ error: `Failed to load data: ${err.message || err}` }); + } } diff --git a/pages/api/devices/create.js b/pages/api/devices/create.js index 369a016..218e950 100644 --- a/pages/api/devices/create.js +++ b/pages/api/devices/create.js @@ -1,69 +1,69 @@ -import { getDevices } from './_devices' -import { write, config, generateDeviceToken } from '../../../lib/influxdb' +import { getDevices } from './_devices'; +import { write, config, generateDeviceToken } from '../../../lib/influxdb'; export default async function handler(req, res) { - if (req.method !== 'POST') { - return res.status(405).json({ error: 'Method not allowed' }) - } + if (req.method !== 'POST') { + return res.status(405).json({ error: 'Method not allowed' }); + } - try { - const deviceId = JSON.parse(req.body)?.deviceId + try { + const deviceId = JSON.parse(req.body)?.deviceId; - if (!deviceId) { - return res.status(400).json({ error: 'deviceId is required' }) - } + if (!deviceId) { + return res.status(400).json({ error: 'deviceId is required' }); + } - const result = await createDevice(deviceId) - res.status(200).json(result) - } catch (err) { - console.error('Device creation error:', err) - res.status(500).json({ error: `Failed to create device: ${err.message || err}` }) - } + const result = await createDevice(deviceId); + res.status(200).json(result); + } catch (err) { + console.error('Device creation error:', err); + res.status(500).json({ error: `Failed to create device: ${err.message || err}` }); + } } /** * Creates a new device with an application-level authentication token. * - * In InfluxDB 3 Core, we use application-level tokens stored in the database - * rather than InfluxDB-native authorization tokens (which require Enterprise - * resource tokens for per-device permissions). + * We store device tokens in the database. + * With InfluxDB 3 Enterprise, we can use fine-grained database tokens. + * With InfluxDB 3 Core, you can use application-level admin tokens. Core doesn't provide fine-grained tokens. * * @param {string} deviceId - The unique device identifier * @returns {Promise<{deviceId: string, key: string, message: string}>} */ async function createDevice(deviceId) { - // Check if device already exists - const existingDevices = await getDevices(deviceId) - const existingDevice = Object.values(existingDevices)[0] + // Check if device already exists + const existingDevices = await getDevices(deviceId); + const existingDevice = Object.values(existingDevices)[0]; - if (existingDevice?.key) { - throw new Error('This device ID is already registered and has an authorization.') - } + if (existingDevice?.key) { + throw new Error('This device ID is already registered and has an authorization.'); + } - console.log(`createDevice: deviceId=${deviceId}`) + console.log(`createDevice: deviceId=${deviceId}`); - // Generate application-level token for the device - const deviceToken = generateDeviceToken() - const deviceKey = `device_${deviceId}_${Date.now()}` + // Generate application-level token for the device + const deviceToken = generateDeviceToken(); + const deviceKey = `device_${deviceId}_${Date.now()}`; - // Write device auth record using line protocol - // Table: deviceauth - // Tags: deviceId - // Fields: key, token - const lineProtocol = `deviceauth,deviceId=${escapeTagValue(deviceId)} key="${escapeFieldValue(deviceKey)}",token="${escapeFieldValue(deviceToken)}"` + // Write device auth record using line protocol + // Table: deviceauth + // Tags: deviceId + // Fields: key, token + const lineProtocol = `deviceauth,deviceId=${escapeTagValue(deviceId)} key="${escapeFieldValue(deviceKey)}",token="${escapeFieldValue(deviceToken)}"`; - await write(lineProtocol, config.databaseAuth) + await write(lineProtocol, config.databaseAuth); - console.log(`Device created: ${deviceId}`) + console.log(`Device created: ${deviceId}`); - return { - deviceId, - key: deviceKey, - token: deviceToken, - database: config.database, - host: config.host, - message: 'Device registered successfully. Use the provided token for device authentication.', - } + return { + deviceId, + key: deviceKey, + token: deviceToken, + database: config.database, + host: config.host, + message: 'Device registered successfully. Use the provided token for device authentication.', + }; } /** @@ -73,14 +73,10 @@ async function createDevice(deviceId) { * @returns {string} The escaped tag value */ function escapeTagValue(value) { - if (typeof value !== 'string') { - return value - } - return value - .replace(/\\/g, '\\\\') - .replace(/ /g, '\\ ') - .replace(/,/g, '\\,') - .replace(/=/g, '\\=') + if (typeof value !== 'string') { + return value; + } + return value.replace(/\\/g, '\\\\').replace(/ /g, '\\ ').replace(/,/g, '\\,').replace(/=/g, '\\='); } /** @@ -90,10 +86,8 @@ function escapeTagValue(value) { * @returns {string} The escaped field value */ function escapeFieldValue(value) { - if (typeof value !== 'string') { - return value - } - return value - .replace(/\\/g, '\\\\') - .replace(/"/g, '\\"') + if (typeof value !== 'string') { + return value; + } + return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); } diff --git a/pages/api/measurements/index.js b/pages/api/measurements/index.js index 64efdf5..b2e3a1b 100644 --- a/pages/api/measurements/index.js +++ b/pages/api/measurements/index.js @@ -1,9 +1,9 @@ -import { query, config } from '../../../lib/influxdb' +import { query, config } from '../../../lib/influxdb'; /** * Executes a SQL query against the InfluxDB 3 database and returns results. * - * Note: In InfluxDB 3, Flux is not supported. All queries must use SQL or InfluxQL. + * Note: In InfluxDB 3, use SQL or InfluxQL to query data. * This function accepts SQL queries and returns results as CSV-formatted text * for backward compatibility with the original API response format. * @@ -11,21 +11,19 @@ import { query, config } from '../../../lib/influxdb' * @returns {Promise} CSV-formatted query results */ export async function getMeasurements(sqlQuery) { - const database = config.database - const rows = await query(sqlQuery, database) + const database = config.database; + const rows = await query(sqlQuery, database); - if (rows.length === 0) { - return '' - } + if (rows.length === 0) { + return ''; + } - // Convert results to CSV format for backward compatibility - const columns = Object.keys(rows[0]) - const header = columns.join(',') - const dataRows = rows.map(row => - columns.map(col => formatCsvValue(row[col])).join(',') - ) + // Convert results to CSV format for backward compatibility + const columns = Object.keys(rows[0]); + const header = columns.join(','); + const dataRows = rows.map((row) => columns.map((col) => formatCsvValue(row[col])).join(',')); - return [header, ...dataRows].join('\n') + '\n' + return [header, ...dataRows].join('\n') + '\n'; } /** @@ -34,18 +32,18 @@ export async function getMeasurements(sqlQuery) { * @returns {string} The formatted CSV value */ function formatCsvValue(value) { - if (value === null || value === undefined) { - return '' - } - if (typeof value === 'string') { - // Escape quotes and wrap in quotes if contains comma, quote, or newline - if (value.includes(',') || value.includes('"') || value.includes('\n')) { - return `"${value.replace(/"/g, '""')}"` - } - return value - } - if (value instanceof Date) { - return value.toISOString() - } - return String(value) + if (value === null || value === undefined) { + return ''; + } + if (typeof value === 'string') { + // Escape quotes and wrap in quotes if contains comma, quote, or newline + if (value.includes(',') || value.includes('"') || value.includes('\n')) { + return `"${value.replace(/"/g, '""')}"`; + } + return value; + } + if (value instanceof Date) { + return value.toISOString(); + } + return String(value); } From 07c514aa1020717fc93adf153d9120d90fc3be95 Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Mon, 9 Feb 2026 22:25:13 -0600 Subject: [PATCH 04/19] Update pages/api/devices/create.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pages/api/devices/create.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/api/devices/create.js b/pages/api/devices/create.js index 218e950..3b3c11f 100644 --- a/pages/api/devices/create.js +++ b/pages/api/devices/create.js @@ -29,7 +29,7 @@ export default async function handler(req, res) { * With InfluxDB 3 Core, you can use application-level admin tokens. Core doesn't provide fine-grained tokens. * * @param {string} deviceId - The unique device identifier - * @returns {Promise<{deviceId: string, key: string, message: string}>} + * @returns {Promise<{deviceId: string, key: string, token: string, database: string, host: string, message: string}>} */ async function createDevice(deviceId) { // Check if device already exists From 85b5b40b12504a2c2d522c4ef27da56ccf82a342 Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Mon, 9 Feb 2026 22:25:36 -0600 Subject: [PATCH 05/19] Update pages/api/devices/create.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pages/api/devices/create.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pages/api/devices/create.js b/pages/api/devices/create.js index 3b3c11f..24878e9 100644 --- a/pages/api/devices/create.js +++ b/pages/api/devices/create.js @@ -7,8 +7,16 @@ export default async function handler(req, res) { } try { - const deviceId = JSON.parse(req.body)?.deviceId; + let body = req.body; + if (typeof body === 'string') { + try { + body = JSON.parse(body); + } catch (parseErr) { + return res.status(400).json({ error: 'Invalid JSON in request body' }); + } + } + const deviceId = body?.deviceId; if (!deviceId) { return res.status(400).json({ error: 'deviceId is required' }); } From f9cf64a2668d9faea876134fe641b9f085e6c7f7 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Feb 2026 04:36:56 +0000 Subject: [PATCH 06/19] fix: address Copilot security review issues and add tests Security fixes: - Add deviceId validation (alphanumeric, hyphens, underscores only) - Fix JSON parsing to handle both string and pre-parsed body - Remove token exposure from public GET endpoints - Add SQL query validation (SELECT only, block DROP/DELETE/UPDATE) - Add query length limit (2000 chars) to prevent abuse - Block multi-statement SQL injection attempts Code quality: - Add getDevices() options parameter for internal token access - Add validateQuery() helper with blocked patterns - Update API documentation to reflect SELECT-only queries Tests: - Add Jest test suite with node-mocks-http - Test device creation with valid/invalid deviceIds - Test device listing without token exposure - Test SQL injection prevention - Test query validation (DROP, DELETE, UPDATE blocked) https://claude.ai/code/session_01Pga27ES6JQsJo1joSVZMJY --- __tests__/api.test.js | 283 +++++++++++++++++++++++ package.json | 7 +- pages/api/devices/[[...deviceParams]].js | 51 +++- pages/api/devices/_devices.js | 23 +- pages/api/devices/create.js | 15 +- 5 files changed, 362 insertions(+), 17 deletions(-) create mode 100644 __tests__/api.test.js diff --git a/__tests__/api.test.js b/__tests__/api.test.js new file mode 100644 index 0000000..2a25aca --- /dev/null +++ b/__tests__/api.test.js @@ -0,0 +1,283 @@ +/** + * API Endpoint Tests for iot-api-js + * + * These tests verify the behavior of the IoT API endpoints. + * Run with: npm test (after adding test script to package.json) + * + * Note: These tests mock the InfluxDB client to run without a database. + */ + +import { createMocks } from 'node-mocks-http' + +// Mock the influxdb module before importing handlers +jest.mock('../lib/influxdb', () => ({ + query: jest.fn(), + write: jest.fn(), + config: { + host: 'http://localhost:8181', + token: 'test-token', + database: 'iot_center', + databaseAuth: 'iot_center_devices', + }, + generateDeviceToken: jest.fn(() => 'iot_mock_token_12345'), + Point: { + measurement: jest.fn(() => ({ + setTag: jest.fn().mockReturnThis(), + setStringField: jest.fn().mockReturnThis(), + toLineProtocol: jest.fn(() => 'deviceauth,deviceId=test-device key="test-key",token="test-token"'), + })), + }, +})) + +import createHandler from '../pages/api/devices/create' +import devicesHandler from '../pages/api/devices/[[...deviceParams]]' +import { query, write } from '../lib/influxdb' + +describe('POST /api/devices/create', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + test('creates a new device with valid deviceId', async () => { + query.mockResolvedValue([]) // No existing device + write.mockResolvedValue(undefined) + + const { req, res } = createMocks({ + method: 'POST', + body: { deviceId: 'sensor-001' }, + }) + + await createHandler(req, res) + + expect(res._getStatusCode()).toBe(200) + const data = JSON.parse(res._getData()) + expect(data.deviceId).toBe('sensor-001') + expect(data.token).toBeDefined() + expect(data.message).toContain('registered successfully') + }) + + test('rejects invalid deviceId with special characters', async () => { + const { req, res } = createMocks({ + method: 'POST', + body: { deviceId: 'sensor' }, + }) + + await createHandler(req, res) + + expect(res._getStatusCode()).toBe(400) + const data = JSON.parse(res._getData()) + expect(data.error).toContain('Invalid deviceId format') + }) + + test('rejects deviceId with newlines (injection attempt)', async () => { + const { req, res } = createMocks({ + method: 'POST', + body: { deviceId: 'sensor\nmalicious,tag=evil field=1' }, + }) + + await createHandler(req, res) + + expect(res._getStatusCode()).toBe(400) + }) + + test('rejects missing deviceId', async () => { + const { req, res } = createMocks({ + method: 'POST', + body: {}, + }) + + await createHandler(req, res) + + expect(res._getStatusCode()).toBe(400) + const data = JSON.parse(res._getData()) + expect(data.error).toContain('deviceId is required') + }) + + test('rejects non-POST methods', async () => { + const { req, res } = createMocks({ + method: 'GET', + }) + + await createHandler(req, res) + + expect(res._getStatusCode()).toBe(405) + }) + + test('rejects duplicate device registration', async () => { + query.mockResolvedValue([ + { deviceId: 'existing-device', key: 'existing-key', time: new Date() }, + ]) + + const { req, res } = createMocks({ + method: 'POST', + body: { deviceId: 'existing-device' }, + }) + + await createHandler(req, res) + + expect(res._getStatusCode()).toBe(500) + const data = JSON.parse(res._getData()) + expect(data.error).toContain('already registered') + }) +}) + +describe('GET /api/devices', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + test('lists all devices without tokens', async () => { + query.mockResolvedValue([ + { deviceId: 'device-1', key: 'key-1', time: new Date() }, + { deviceId: 'device-2', key: 'key-2', time: new Date() }, + ]) + + const { req, res } = createMocks({ + method: 'GET', + query: { deviceParams: [] }, + }) + + await devicesHandler(req, res) + + expect(res._getStatusCode()).toBe(200) + const data = JSON.parse(res._getData()) + expect(data).toHaveLength(2) + // Verify tokens are NOT exposed + expect(data[0].token).toBeUndefined() + expect(data[1].token).toBeUndefined() + }) + + test('returns specific device without token', async () => { + query.mockResolvedValue([ + { deviceId: 'device-1', key: 'key-1', time: new Date() }, + ]) + + const { req, res } = createMocks({ + method: 'GET', + query: { deviceParams: ['device-1'] }, + }) + + await devicesHandler(req, res) + + expect(res._getStatusCode()).toBe(200) + const data = JSON.parse(res._getData()) + expect(data[0].deviceId).toBe('device-1') + // Token should NOT be exposed even for specific device + expect(data[0].token).toBeUndefined() + }) +}) + +describe('POST /api/devices/:deviceId/measurements', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + test('executes valid SELECT query', async () => { + query.mockResolvedValue([ + { time: new Date(), room: 'Kitchen', temp: 22.5 }, + ]) + + const { req, res } = createMocks({ + method: 'POST', + query: { deviceParams: ['device-1', 'measurements'] }, + body: { query: 'SELECT * FROM home LIMIT 10' }, + }) + + await devicesHandler(req, res) + + expect(res._getStatusCode()).toBe(200) + }) + + test('rejects DROP TABLE query', async () => { + const { req, res } = createMocks({ + method: 'POST', + query: { deviceParams: ['device-1', 'measurements'] }, + body: { query: 'DROP TABLE home' }, + }) + + await devicesHandler(req, res) + + expect(res._getStatusCode()).toBe(400) + const data = JSON.parse(res._getData()) + expect(data.error).toContain('Only SELECT queries are allowed') + }) + + test('rejects DELETE query', async () => { + const { req, res } = createMocks({ + method: 'POST', + query: { deviceParams: ['device-1', 'measurements'] }, + body: { query: 'DELETE FROM home WHERE 1=1' }, + }) + + await devicesHandler(req, res) + + expect(res._getStatusCode()).toBe(400) + }) + + test('rejects UPDATE query', async () => { + const { req, res } = createMocks({ + method: 'POST', + query: { deviceParams: ['device-1', 'measurements'] }, + body: { query: "UPDATE home SET temp=0 WHERE room='Kitchen'" }, + }) + + await devicesHandler(req, res) + + expect(res._getStatusCode()).toBe(400) + }) + + test('rejects multi-statement injection', async () => { + const { req, res } = createMocks({ + method: 'POST', + query: { deviceParams: ['device-1', 'measurements'] }, + body: { query: 'SELECT * FROM home; DROP TABLE home' }, + }) + + await devicesHandler(req, res) + + expect(res._getStatusCode()).toBe(400) + const data = JSON.parse(res._getData()) + expect(data.error).toContain('blocked operations') + }) + + test('rejects excessively long queries', async () => { + const longQuery = 'SELECT * FROM home WHERE ' + 'x=1 OR '.repeat(500) + + const { req, res } = createMocks({ + method: 'POST', + query: { deviceParams: ['device-1', 'measurements'] }, + body: { query: longQuery }, + }) + + await devicesHandler(req, res) + + expect(res._getStatusCode()).toBe(400) + const data = JSON.parse(res._getData()) + expect(data.error).toContain('maximum length') + }) + + test('rejects missing query parameter', async () => { + const { req, res } = createMocks({ + method: 'POST', + query: { deviceParams: ['device-1', 'measurements'] }, + body: {}, + }) + + await devicesHandler(req, res) + + expect(res._getStatusCode()).toBe(400) + const data = JSON.parse(res._getData()) + expect(data.error).toContain('Missing query parameter') + }) + + test('rejects GET method for measurements', async () => { + const { req, res } = createMocks({ + method: 'GET', + query: { deviceParams: ['device-1', 'measurements'] }, + }) + + await devicesHandler(req, res) + + expect(res._getStatusCode()).toBe(405) + }) +}) diff --git a/package.json b/package.json index b4ca286..427e361 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,8 @@ "scripts": { "dev": "next dev", "build": "next build", - "start": "next start" + "start": "next start", + "test": "jest" }, "dependencies": { "@influxdata/influxdb3-client": "^2.0.0", @@ -12,6 +13,10 @@ "react": "^18.2.0", "react-dom": "^18.2.0" }, + "devDependencies": { + "jest": "^29.7.0", + "node-mocks-http": "^1.14.1" + }, "resolutions": { "postcss": "^8.4.31" } diff --git a/pages/api/devices/[[...deviceParams]].js b/pages/api/devices/[[...deviceParams]].js index 020d308..34fe338 100644 --- a/pages/api/devices/[[...deviceParams]].js +++ b/pages/api/devices/[[...deviceParams]].js @@ -1,14 +1,53 @@ import { getMeasurements } from '../measurements' import { getDevices } from './_devices' +// Maximum query length to prevent abuse +const MAX_QUERY_LENGTH = 2000 + +// Blocked SQL keywords (case-insensitive) +const BLOCKED_PATTERNS = [ + /\b(DROP|DELETE|UPDATE|INSERT|ALTER|CREATE|TRUNCATE|GRANT|REVOKE)\b/i, + /\b(EXEC|EXECUTE|CALL)\b/i, + /;\s*\w/i, // Multiple statements +] + +/** + * Validates a SQL query for safety. + * Only allows SELECT queries without dangerous operations. + * Note: This is basic validation for a sample app - production apps + * should use parameterized queries and proper authorization. + */ +function validateQuery(query) { + if (typeof query !== 'string') { + return { valid: false, error: 'Query must be a string' } + } + + if (query.length > MAX_QUERY_LENGTH) { + return { valid: false, error: `Query exceeds maximum length of ${MAX_QUERY_LENGTH} characters` } + } + + const trimmed = query.trim() + if (!trimmed.toUpperCase().startsWith('SELECT')) { + return { valid: false, error: 'Only SELECT queries are allowed' } + } + + for (const pattern of BLOCKED_PATTERNS) { + if (pattern.test(query)) { + return { valid: false, error: 'Query contains blocked operations' } + } + } + + return { valid: true } +} + /** * API handler for device-related endpoints: * * GET /api/devices - List all registered devices * GET /api/devices/:deviceId - Get a specific device - * POST /api/devices/:deviceId/measurements - Query measurements for a device + * POST /api/devices/:deviceId/measurements - Query measurements (SELECT only) * - * Note: For measurement queries, the `query` parameter must be a SQL query. + * Note: For measurement queries, the `query` parameter must be a SELECT SQL query. * Flux queries are not supported in InfluxDB 3. * * Example SQL query for measurements: @@ -34,11 +73,17 @@ export default async function handler(req, res) { if (!query) { return res.status(400).json({ error: 'Missing query parameter', - hint: 'Provide a SQL query in the request body. Flux is not supported in InfluxDB 3.', + hint: 'Provide a SQL SELECT query in the request body. Flux is not supported in InfluxDB 3.', example: "SELECT * FROM home WHERE time >= now() - INTERVAL '1 hour' ORDER BY time DESC", }) } + // Validate query before execution + const validation = validateQuery(query) + if (!validation.valid) { + return res.status(400).json({ error: validation.error }) + } + const data = await getMeasurements(query) res.status(200).send(data) return diff --git a/pages/api/devices/_devices.js b/pages/api/devices/_devices.js index e6d7e15..67c82a0 100644 --- a/pages/api/devices/_devices.js +++ b/pages/api/devices/_devices.js @@ -2,31 +2,30 @@ import { query, config } from '../../../lib/influxdb' /** * Gets devices or a particular device when deviceId is specified. - * Tokens are not returned unless deviceId is specified. - * It can also return devices with empty/unknown key - such devices - * can be ignored (no valid authorization is associated). + * Tokens are NEVER returned via public API to prevent unauthorized access. * * @param {string} [deviceId] - Optional deviceId to filter by + * @param {Object} [options] - Query options + * @param {boolean} [options.includeToken=false] - Include token (internal use only) * @returns {Promise>} */ -export async function getDevices(deviceId) { +export async function getDevices(deviceId, options = {}) { + const { includeToken = false } = options const database = config.databaseAuth - // Build SQL query - // When deviceId is specified, return all fields including token - // Otherwise, exclude token field for security + // Build SQL query - only include token field for internal verification let sql if (deviceId !== undefined) { - // Get specific device with token + const tokenField = includeToken ? ', token' : '' sql = ` - SELECT time, deviceId, key, token + SELECT time, deviceId, key${tokenField} FROM deviceauth WHERE deviceId = '${escapeString(deviceId)}' ORDER BY time DESC LIMIT 1 ` } else { - // Get all devices without tokens + // Get all devices - never include tokens in list view sql = ` SELECT time, deviceId, key FROM deviceauth @@ -60,8 +59,8 @@ export async function getDevices(deviceId) { updatedAt: row.time, } - // Only include token when querying specific device - if (deviceId !== undefined && row.token) { + // Only include token for internal calls that explicitly request it + if (includeToken && row.token) { devices[id].token = row.token } } diff --git a/pages/api/devices/create.js b/pages/api/devices/create.js index 3b264c8..8902a13 100644 --- a/pages/api/devices/create.js +++ b/pages/api/devices/create.js @@ -1,18 +1,31 @@ import { getDevices } from './_devices' import { write, config, generateDeviceToken, Point } from '../../../lib/influxdb' +// Valid deviceId pattern: alphanumeric, hyphens, underscores, 1-64 chars +const DEVICE_ID_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/ + export default async function handler(req, res) { if (req.method !== 'POST') { return res.status(405).json({ error: 'Method not allowed' }) } try { - const deviceId = JSON.parse(req.body)?.deviceId + // Handle both pre-parsed objects and JSON strings + const body = typeof req.body === 'string' ? JSON.parse(req.body) : req.body + const deviceId = body?.deviceId if (!deviceId) { return res.status(400).json({ error: 'deviceId is required' }) } + // Validate deviceId format to prevent injection attacks + if (!DEVICE_ID_PATTERN.test(deviceId)) { + return res.status(400).json({ + error: 'Invalid deviceId format', + hint: 'deviceId must be 1-64 characters, alphanumeric with hyphens and underscores only', + }) + } + const result = await createDevice(deviceId) res.status(200).json(result) } catch (err) { From d885cd8581a932119c069935de872e62083768cb Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Feb 2026 15:42:48 +0000 Subject: [PATCH 07/19] docs: update README for InfluxDB 3 Core migration - Add sample application disclaimer in GitHub callout format - Update all references from InfluxDB v2 to InfluxDB 3 Core - Update setup instructions with InfluxDB 3 CLI commands - Update environment variable documentation (INFLUX_HOST, INFLUX_DATABASE) - Update troubleshooting section for database errors - Add links to InfluxDB 3 documentation and JavaScript client https://claude.ai/code/session_01Pga27ES6JQsJo1joSVZMJY --- README.md | 84 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 0df96a6..96434f6 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,62 @@ # iot-api-js -This example project provides a Node.js REST API server that interacts with the InfluxDB v2 HTTP API. -The project uses the [Next.js](https://nextjs.org/) framework and the [InfluxDB v2 API client library for JavaScript](https://docs.influxdata.com/influxdb/v2/api-guide/client-libraries/nodejs/) to demonstrate how to build an app that collects, stores, and queries IoT device data. +> [!WARNING] +> #### ⚠️ Sample Application Notice +> This is a **reference implementation** for learning purposes. It demonstrates InfluxDB 3 client library usage patterns but is **not production-ready**. +> +> **Not included:** Authentication/authorization, rate limiting, comprehensive error handling, input sanitization for all edge cases, secure credential management, high availability, or production logging. +> +> **Before deploying to production**, implement proper security controls, follow your organization's security guidelines, and conduct a thorough security review. + +This example project provides a Node.js REST API server that interacts with InfluxDB 3 Core. +The project uses the [Next.js](https://nextjs.org/) framework and the [InfluxDB 3 JavaScript client library](https://github.com/InfluxCommunity/influxdb3-js) to demonstrate how to build an app that collects, stores, and queries IoT device data. After you have set up and run your `iot-api-js` API server, you can consume your API using the [iot-api-ui](https://github.com/influxdata/iot-api-ui) standalone React frontend. ## Features -This application demonstrates how you can use InfluxDB client libraries to do the following: +This application demonstrates how you can use the InfluxDB 3 client library to do the following: -- Create and manage InfluxDB authorizations (API tokens and permissions). -- Write and query device metadata in InfluxDB. -- Write and query telemetry data in InfluxDB. -- Generate data visualizations with the InfluxDB Giraffe library. +- Register IoT devices with application-level tokens. +- Write and query device metadata in InfluxDB 3. +- Write and query telemetry data using SQL queries. +- Explore data using the InfluxDB 3 SQL query interface. ## Tutorial and support -To learn how to build this app from scratch, follow the [InfluxDB v2 OSS tutorial](https://docs.influxdata.com/influxdb/v2/api-guide/tutorials/nodejs/) or [InfluxDB Cloud tutorial](https://docs.influxdata.com/influxdb/cloud/api-guide/tutorials/nodejs/). -The app is an adaptation of [InfluxData IoT Center](https://github.com/bonitoo-io/iot-center-v2), simplified to accompany the IoT Starter tutorial. +This app is an adaptation of [InfluxData IoT Center](https://github.com/bonitoo-io/iot-center-v2), simplified to demonstrate InfluxDB 3 Core integration patterns. -For help, refer to the tutorials and InfluxDB documentation or use the following resources: +For help, refer to the InfluxDB 3 documentation or use the following resources: +- [InfluxDB 3 Core Documentation](https://docs.influxdata.com/influxdb3/core/) +- [InfluxDB 3 JavaScript Client](https://github.com/InfluxCommunity/influxdb3-js) - [InfluxData Community](https://community.influxdata.com/) - [InfluxDB Community Slack](https://influxdata.com/slack) -To report a problem, submit an issue to this repo or to the [`influxdata/docs-v2` repo](https://github.com/influxdata/docs-v2/issues). +To report a problem, submit an issue to this repo. ## Get started -### Set up InfluxDB prerequisites +### Set up InfluxDB 3 Core -Follow the tutorial instructions to setup your InfluxDB organization, API token, and buckets: +1. Install and start InfluxDB 3 Core following the [installation guide](https://docs.influxdata.com/influxdb3/core/get-started/setup/). -- [Set up InfluxDB OSS v2 prerequisites](https://docs.influxdata.com/influxdb/v2/api-guide/tutorials/nodejs/#set-up-influxdb) -- [Set up InfluxDB Cloud v2 prerequisites](https://docs.influxdata.com/influxdb/cloud/api-guide/tutorials/nodejs/#set-up-influxdb) +2. Create the required databases: + + ```bash + # Create database for telemetry data + influxdb3 create database iot_center + + # Create database for device authentication + influxdb3 create database iot_center_devices + ``` + +3. Create an admin token for the API server: + + ```bash + influxdb3 create token --admin + ``` + + Save the token value for the next step. Next, [clone and run the API server](#clone-and-run-the-api-server). @@ -50,16 +74,19 @@ Next, [clone and run the API server](#clone-and-run-the-api-server). ```bash # Local environment secrets - INFLUX_TOKEN=INFLUXDB_ALL_ACCESS_TOKEN - INFLUX_ORG=INFLUXDB_ORG_ID + INFLUX_TOKEN=YOUR_ADMIN_TOKEN ``` - Replace the following: + Replace **`YOUR_ADMIN_TOKEN`** with the admin token you created in the previous step. - - **`INFLUXDB_ALL_ACCESS_TOKEN`** with your InfluxDB **All Access** token. - - **`INFLUXDB_ORG_ID`** with your InfluxDB organization ID. +4. If you need to adjust the default host or database names, edit the settings in `.env.development` or set them in `.env.local` (to override `.env.development`): -4. If you need to adjust the default URL or bucket names to match your InfluxDB instance, edit the settings in `.env.development` or set them in `.env.local` (to override `.env.development`). + ```bash + # Default settings (can be overridden in .env.local) + INFLUX_HOST=http://localhost:8181 + INFLUX_DATABASE=iot_center + INFLUX_DATABASE_AUTH=iot_center_devices + ``` 5. If you haven't already, follow the [Node.js installation instructions](https://nodejs.org/) to install `node` for your operating system. 6. To check the installed `node` version, enter the following command in your terminal: @@ -100,23 +127,26 @@ Next, [clone and run the API server](#clone-and-run-the-api-server). ## Troubleshoot -### Error: could not find bucket +### Error: could not find database ```json -{"error":"failed to load data: HttpError: failed to initialize execute state: could not find bucket \"iot_center_devices\""} +{"error":"failed to load data: database \"iot_center_devices\" not found"} ``` -Solution: [create buckets](#set-up-influxdb-prerequisites) or adjust the defaults in `.env.development` to match your InfluxDB instance. +Solution: [create the databases](#set-up-influxdb-3-core) or adjust the defaults in `.env.development` to match your InfluxDB instance. ## Learn More -### InfluxDB +### InfluxDB 3 -- Develop with the InfluxDB API for [OSS v2](https://docs.influxdata.com/influxdb/v2/api-guide/) or [Cloud v2](https://docs.influxdata.com/influxdb/cloud/api-guide/). +- [InfluxDB 3 Core Documentation](https://docs.influxdata.com/influxdb3/core/) +- [InfluxDB 3 Enterprise Documentation](https://docs.influxdata.com/influxdb3/enterprise/) +- [InfluxDB 3 JavaScript Client](https://github.com/InfluxCommunity/influxdb3-js) +- [Query data with SQL](https://docs.influxdata.com/influxdb3/core/query-data/sql/) ### Next.js -To learn more about Next.js, see following resources: +To learn more about Next.js, see the following resources: - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. From f6c3e041b58c2a777cce93bebd1c124e82652276 Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Tue, 10 Feb 2026 10:33:51 -0600 Subject: [PATCH 08/19] chore: add Claude Code configuration and Docker Compose for InfluxDB 3 - Add .claude/settings.json with project permissions - Add .claude/skills/run-tests/SKILL.md for test workflow - Add CLAUDE.md and AGENTS.md instruction files - Add .github/INSTRUCTIONS.md navigation guide - Add compose.yaml for InfluxDB 3 Core local development - Update .gitignore to exclude test/.influxdb3/ data --- .claude/settings.json | 30 +++++ .claude/skills/run-tests/SKILL.md | 195 ++++++++++++++++++++++++++++++ .github/INSTRUCTIONS.md | 52 ++++++++ .gitignore | 3 + AGENTS.md | 173 ++++++++++++++++++++++++++ CLAUDE.md | 100 +++++++++++++++ compose.yaml | 74 ++++++++++++ 7 files changed, 627 insertions(+) create mode 100644 .claude/settings.json create mode 100644 .claude/skills/run-tests/SKILL.md create mode 100644 .github/INSTRUCTIONS.md create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 compose.yaml diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..1e6d473 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,30 @@ +{ + "permissions": { + "allow": [ + "Bash(npm:*)", + "Bash(yarn:*)", + "Bash(npx:*)", + "Bash(node:*)", + "Bash(curl:*)", + "Bash(gh:*)", + "Bash(jq:*)", + "Bash(mkdir:*)", + "Bash(docker:*)", + "Bash(docker compose:*)", + "Edit", + "Read", + "Write", + "Grep", + "Glob", + "LS" + ], + "deny": [ + "Read(./.env)", + "Read(./.env.*)", + "Read(./secrets/**)" + ], + "ask": [ + "Bash(git push:*)" + ] + } +} diff --git a/.claude/skills/run-tests/SKILL.md b/.claude/skills/run-tests/SKILL.md new file mode 100644 index 0000000..0b3b60d --- /dev/null +++ b/.claude/skills/run-tests/SKILL.md @@ -0,0 +1,195 @@ +--- +name: run-tests +description: Run API tests against InfluxDB 3 Core. Handles service initialization, database setup, and test execution. +author: InfluxData +version: "1.0" +--- + +# Run Tests Skill + +## Purpose + +This skill guides running the IoT API test suite against a local InfluxDB 3 Core instance. It covers service setup, database creation, and test execution. + +## Quick Reference + +| Task | Command | +|------|---------| +| Start InfluxDB 3 | `docker compose up -d influxdb3-core` | +| Check status | `curl -i http://localhost:8181/health` | +| Run tests | `yarn test` | +| View logs | `docker logs influxdb3-core` | + +## Complete Setup Workflow + +### 1. Initialize InfluxDB 3 Core + +```bash +# Create required directories +mkdir -p test/.influxdb3/core/data test/.influxdb3/core/plugins + +# Generate admin token (first time only) +openssl rand -hex 32 > test/.influxdb3/core/.token +chmod 600 test/.influxdb3/core/.token + +# Start the container +docker compose up -d influxdb3-core + +# Wait for healthy status +docker compose ps +``` + +### 2. Create Databases + +```bash +# Get the token +TOKEN=$(cat test/.influxdb3/core/.token) + +# Create main database +curl -X POST "http://localhost:8181/api/v3/configure/database" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"db": "iot_center"}' + +# Create auth database +curl -X POST "http://localhost:8181/api/v3/configure/database" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"db": "iot_center_auth"}' + +# Verify databases exist +curl "http://localhost:8181/api/v3/configure/database?format=json" \ + -H "Authorization: Bearer $TOKEN" +``` + +### 3. Configure Environment + +Create `.env.local` if it doesn't exist: + +```bash +cat > .env.local << EOF +INFLUX_HOST=http://localhost:8181 +INFLUX_TOKEN=$(cat test/.influxdb3/core/.token) +INFLUX_DATABASE=iot_center +INFLUX_DATABASE_AUTH=iot_center_auth +EOF +``` + +### 4. Run Tests + +```bash +# Install dependencies (if needed) +yarn + +# Run the test suite +yarn test + +# Run with verbose output +yarn test --verbose + +# Run specific test file +yarn test __tests__/api.test.js +``` + +## Troubleshooting + +### Container Won't Start + +**Symptom:** Container exits immediately + +**Check:** +```bash +# View logs +docker logs influxdb3-core + +# Verify directories exist +ls -la test/.influxdb3/core/ + +# Verify token file exists +cat test/.influxdb3/core/.token +``` + +**Common fixes:** +- Create missing directories: `mkdir -p test/.influxdb3/core/data test/.influxdb3/core/plugins` +- Generate token: `openssl rand -hex 32 > test/.influxdb3/core/.token` + +### 401 Unauthorized + +**Symptom:** API calls return 401 + +**Check:** +```bash +# Verify token matches +echo "Token in file: $(cat test/.influxdb3/core/.token)" +echo "Token in .env.local: $(grep INFLUX_TOKEN .env.local)" + +# Test with token directly +curl -i http://localhost:8181/api/v3/configure/database \ + -H "Authorization: Bearer $(cat test/.influxdb3/core/.token)" +``` + +### Database Not Found + +**Symptom:** Tests fail with "database not found" + +**Fix:** Create the required databases: +```bash +TOKEN=$(cat test/.influxdb3/core/.token) +curl -X POST "http://localhost:8181/api/v3/configure/database" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"db": "iot_center"}' +curl -X POST "http://localhost:8181/api/v3/configure/database" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"db": "iot_center_auth"}' +``` + +### Port Already in Use + +**Symptom:** "port is already allocated" + +**Fix:** +```bash +# Find what's using the port +lsof -i :8181 + +# Stop existing container +docker compose down +``` + +## Clean Slate + +To start fresh: + +```bash +# Stop and remove containers +docker compose down + +# Remove data (WARNING: deletes all data) +rm -rf test/.influxdb3/core/data/* + +# Regenerate token +openssl rand -hex 32 > test/.influxdb3/core/.token + +# Start fresh +docker compose up -d influxdb3-core +``` + +## Test Configuration + +The test suite uses these environment variables: + +| Variable | Default | Description | +|----------|---------|-------------| +| `INFLUX_HOST` | `http://localhost:8181` | InfluxDB 3 API URL | +| `INFLUX_TOKEN` | (from `.env.local`) | Admin token | +| `INFLUX_DATABASE` | `iot_center` | Main data database | +| `INFLUX_DATABASE_AUTH` | `iot_center_auth` | Device auth database | + +## Related Files + +- **Docker Compose**: `compose.yaml` +- **Test suite**: `__tests__/api.test.js` +- **Environment defaults**: `.env.development` +- **Local overrides**: `.env.local` (gitignored) diff --git a/.github/INSTRUCTIONS.md b/.github/INSTRUCTIONS.md new file mode 100644 index 0000000..a99b0d6 --- /dev/null +++ b/.github/INSTRUCTIONS.md @@ -0,0 +1,52 @@ +# AI Instructions Navigation Guide + +This repository has multiple instruction files for different AI tools and use cases. + +## Quick Navigation + +| If you are... | Use this file | Purpose | +|---------------|---------------|---------| +| **GitHub Copilot** | [../AGENTS.md](../AGENTS.md) | Development patterns, code examples | +| **Claude, ChatGPT, Gemini** | [../AGENTS.md](../AGENTS.md) | Comprehensive development guide | +| **Claude with MCP** | [../CLAUDE.md](../CLAUDE.md) | Quick reference with skill pointers | + +## File Organization + +``` +iot-api-js/ +├── .claude/ +│ ├── settings.json # Claude permissions +│ └── skills/ +│ └── run-tests/SKILL.md # Test execution workflow +├── .github/ +│ └── INSTRUCTIONS.md # THIS FILE - Navigation guide +├── AGENTS.md # Comprehensive AI assistant guide +├── CLAUDE.md # Claude with MCP quick reference +└── README.md # User-facing documentation +``` + +## What's in Each File + +**[../CLAUDE.md](../CLAUDE.md)** - Quick reference for Claude: +- Project overview +- Quick commands +- Structure summary +- Links to skills + +**[../AGENTS.md](../AGENTS.md)** - Comprehensive guide: +- Architecture diagram +- Development workflow +- Code patterns and examples +- Common tasks +- Style guidelines + +**[../README.md](../README.md)** - User documentation: +- Setup instructions +- API usage +- Troubleshooting + +## Getting Started + +1. **New to the repository?** Start with [../README.md](../README.md) +2. **Using AI assistants?** Read [../AGENTS.md](../AGENTS.md) +3. **Using Claude with MCP?** Check [../CLAUDE.md](../CLAUDE.md) and [../.claude/](../.claude/) diff --git a/.gitignore b/.gitignore index 20fccdd..c0a1c0c 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,6 @@ yarn-error.log* .env.development.local .env.test.local .env.production.local + +# InfluxDB 3 local data +test/.influxdb3/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..3fa345d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,173 @@ +# IoT API JS - AI Assistant Guide + +> **For general AI assistants (Claude, ChatGPT, Gemini, etc.)** +> +> This guide provides instructions for AI assistants helping with the IoT API sample application. +> +> **Other instruction resources**: +> - [CLAUDE.md](CLAUDE.md) - For Claude with MCP +> - [.github/INSTRUCTIONS.md](.github/INSTRUCTIONS.md) - Navigation guide +> - [README.md](README.md) - User-facing documentation + +## Project Purpose + +This is a **sample application** for InfluxData documentation tutorials. It demonstrates: +- Building REST APIs that interact with InfluxDB 3 +- Device registration with application-level authentication +- Writing and querying time-series IoT data +- Using the `@influxdata/influxdb3-client` JavaScript library + +**Target audience**: Developers learning to build IoT applications with InfluxDB 3. + +## Architecture + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Frontend UI │────▶│ Next.js API │────▶│ InfluxDB 3 │ +│ (iot-api-ui) │ │ (this repo) │ │ Core │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ Two databases: │ + │ - iot_center │ + │ - iot_center_ │ + │ auth │ + └─────────────────┘ +``` + +## Key Files + +| File | Purpose | +|------|---------| +| `lib/influxdb.js` | InfluxDB client factory and helpers | +| `pages/api/devices/create.js` | Device registration endpoint | +| `pages/api/devices/_devices.js` | Shared device query logic | +| `pages/api/devices/[[...deviceParams]].js` | Device CRUD operations | +| `pages/api/measurements/index.js` | Telemetry query endpoint | +| `__tests__/api.test.js` | API integration tests | + +## Development Workflow + +### Prerequisites + +1. Node.js 18+ and Yarn +2. Docker (for InfluxDB 3 Core) +3. Environment variables in `.env.local` + +### Local Development + +```bash +# 1. Start InfluxDB 3 Core +docker compose up -d influxdb3-core + +# 2. Get the generated token +cat test/.influxdb3/core/.token + +# 3. Configure .env.local with the token +echo "INFLUX_TOKEN=$(cat test/.influxdb3/core/.token)" >> .env.local + +# 4. Install dependencies and start +yarn +yarn dev -p 5200 + +# 5. Test the API +curl http://localhost:5200/api/devices +``` + +### Running Tests + +```bash +# Ensure InfluxDB 3 is running +docker compose up -d influxdb3-core + +# Run test suite +yarn test +``` + +## Code Patterns + +### SQL Queries (InfluxDB 3) + +InfluxDB 3 uses SQL instead of Flux: + +```javascript +// Query devices +const sql = ` + SELECT time, deviceId, key + FROM deviceauth + WHERE deviceId = '${escapeString(deviceId)}' + ORDER BY time DESC + LIMIT 1 +` +const rows = await query(sql, database) +``` + +### Writing Data with Points + +```javascript +import { Point } from '@influxdata/influxdb3-client' + +const point = Point.measurement('deviceauth') + .setTag('deviceId', deviceId) + .setStringField('key', deviceKey) + .setStringField('token', deviceToken) + +await write(point.toLineProtocol(), database) +``` + +### Client Lifecycle + +Always close clients after use: + +```javascript +const client = createClient() +try { + // Use client... +} finally { + await client.close() +} +``` + +## Common Tasks + +### Adding a New Endpoint + +1. Create file in `pages/api/` following Next.js conventions +2. Import helpers from `lib/influxdb.js` +3. Add input validation (see `DEVICE_ID_PATTERN` example) +4. Add tests in `__tests__/` + +### Modifying Database Schema + +Data is stored as time-series measurements: +- `deviceauth` - Device registration (tags: deviceId; fields: key, token) +- Custom measurements for telemetry + +### Debugging + +```bash +# Check InfluxDB 3 logs +docker logs influxdb3-core + +# Query directly with curl +curl -X POST "http://localhost:8181/api/v3/query_sql" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"db": "iot_center", "q": "SELECT * FROM deviceauth"}' +``` + +## Style Guidelines + +- Use ES modules (`import`/`export`) +- Validate all user input before database operations +- Never expose tokens in API responses +- Use descriptive error messages +- Follow existing patterns in the codebase + +## Related Resources + +- [InfluxDB 3 Core Documentation](https://docs.influxdata.com/influxdb3/core/) +- [influxdb3-javascript Client](https://github.com/InfluxCommunity/influxdb3-js) +- [IoT Starter Tutorial](https://docs.influxdata.com/influxdb/v2/api-guide/tutorials/nodejs/) +- [iot-api-ui Frontend](https://github.com/influxdata/iot-api-ui) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..7dbf2f6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,100 @@ +# IoT API JS - Claude Instructions + +> **For Claude with MCP** +> +> This is a Next.js REST API server demonstrating InfluxDB 3 integration for IoT device data. +> +> **Instruction resources**: +> - [AGENTS.md](AGENTS.md) - For general AI assistants +> - [.github/INSTRUCTIONS.md](.github/INSTRUCTIONS.md) - Navigation guide +> - [.claude/](.claude/) - Claude configuration (settings, skills) + +## Project Overview + +This sample application demonstrates how to build an IoT data collection API using: +- **Next.js** - React framework for the API server +- **InfluxDB 3 Core** - Time-series database +- **@influxdata/influxdb3-client** - Official JavaScript client + +## Quick Commands + +| Task | Command | +|------|---------| +| Install dependencies | `yarn` | +| Start dev server | `yarn dev -p 5200` | +| Run tests | `yarn test` | +| Start InfluxDB 3 | `docker compose up -d influxdb3-core` | + +## Project Structure + +``` +iot-api-js/ +├── lib/ +│ └── influxdb.js # InfluxDB client helpers +├── pages/api/ +│ ├── devices/ # Device CRUD endpoints +│ │ ├── create.js # POST /api/devices/create +│ │ ├── _devices.js # Shared device queries +│ │ └── [[...deviceParams]].js # GET/DELETE /api/devices +│ └── measurements/ # Telemetry endpoints +│ └── index.js # GET /api/measurements +├── __tests__/ # API tests +├── compose.yaml # InfluxDB 3 Core container +└── .env.development # Default config (committed) +``` + +## Environment Configuration + +Copy `.env.development` values to `.env.local` and customize: + +```bash +INFLUX_HOST=http://localhost:8181 +INFLUX_TOKEN=your-token +INFLUX_DATABASE=iot_center +INFLUX_DATABASE_AUTH=iot_center_auth +``` + +## Testing + +Run the test suite against a local InfluxDB 3 Core instance: + +```bash +# Start InfluxDB 3 Core +docker compose up -d influxdb3-core + +# Run tests +yarn test +``` + +See `.claude/skills/run-tests/SKILL.md` for detailed testing workflow. + +## API Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/api/devices` | GET | List all devices | +| `/api/devices/:id` | GET | Get device by ID | +| `/api/devices/create` | POST | Create new device | +| `/api/devices/:id` | DELETE | Delete device | +| `/api/measurements` | GET | Query device telemetry | + +## Key Patterns + +### InfluxDB 3 Client Usage + +```javascript +import { createClient, query, write } from '../../../lib/influxdb' + +// Query with SQL +const results = await query('SELECT * FROM deviceauth', database) + +// Write line protocol +await write('measurement,tag=value field=1', database) +``` + +### Application-Level Tokens + +This app uses application-managed tokens stored in the database (not InfluxDB-native auth tokens). Tokens are: +- Generated via `generateDeviceToken()` in `lib/influxdb.js` +- Stored in the `deviceauth` measurement +- Never exposed via public API responses diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..b8902f8 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,74 @@ +# Docker Compose for IoT API JS +# Provides InfluxDB 3 Core for local development and testing. +name: iot-api-js + +secrets: + influxdb3-core-token: + file: test/.influxdb3/core/.token + +services: + # ============================================================================ + # InfluxDB 3 Core + # ============================================================================ + # Local development instance with file-based storage. + # + # USAGE: + # docker compose up -d influxdb3-core + # + # FIRST-TIME SETUP: + # 1. Create directories: + # mkdir -p test/.influxdb3/core/data test/.influxdb3/core/plugins + # + # 2. Generate token: + # openssl rand -hex 32 > test/.influxdb3/core/.token + # chmod 600 test/.influxdb3/core/.token + # + # 3. Start service: + # docker compose up -d influxdb3-core + # + # 4. Create databases: + # TOKEN=$(cat test/.influxdb3/core/.token) + # curl -X POST "http://localhost:8181/api/v3/configure/database" \ + # -H "Authorization: Bearer $TOKEN" \ + # -H "Content-Type: application/json" \ + # -d '{"db": "iot_center"}' + # curl -X POST "http://localhost:8181/api/v3/configure/database" \ + # -H "Authorization: Bearer $TOKEN" \ + # -H "Content-Type: application/json" \ + # -d '{"db": "iot_center_auth"}' + # + # ENDPOINTS: + # - API: http://localhost:8181 + # - Health: http://localhost:8181/health + # - Ping: http://localhost:8181/ping + # ============================================================================ + influxdb3-core: + container_name: influxdb3-core + image: influxdb:3-core + pull_policy: always + ports: + - 8181:8181 + command: + - influxdb3 + - serve + - --node-id=node0 + - --object-store=file + - --data-dir=/var/lib/influxdb3/data + - --plugin-dir=/var/lib/influxdb3/plugins + - --admin-token-file=/run/secrets/influxdb3-core-token + - --log-filter=info + volumes: + - type: bind + source: test/.influxdb3/core/data + target: /var/lib/influxdb3/data + - type: bind + source: test/.influxdb3/core/plugins + target: /var/lib/influxdb3/plugins + secrets: + - influxdb3-core-token + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8181/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s From 4b74cc5edc3acc7634901cc09795b8d5794c242a Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Tue, 10 Feb 2026 11:19:46 -0600 Subject: [PATCH 09/19] Update CLAUDE.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 7dbf2f6..ed1d085 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,7 +51,7 @@ Copy `.env.development` values to `.env.local` and customize: INFLUX_HOST=http://localhost:8181 INFLUX_TOKEN=your-token INFLUX_DATABASE=iot_center -INFLUX_DATABASE_AUTH=iot_center_auth +INFLUX_DATABASE_AUTH=iot_center_devices ``` ## Testing From 7b98aaa11eedf2d500bd0b4bf70e4954f556993c Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Tue, 10 Feb 2026 11:20:03 -0600 Subject: [PATCH 10/19] Update compose.yaml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose.yaml b/compose.yaml index b8902f8..a311e08 100644 --- a/compose.yaml +++ b/compose.yaml @@ -35,7 +35,7 @@ services: # curl -X POST "http://localhost:8181/api/v3/configure/database" \ # -H "Authorization: Bearer $TOKEN" \ # -H "Content-Type: application/json" \ - # -d '{"db": "iot_center_auth"}' + # -d '{"db": "iot_center_devices"}' # # ENDPOINTS: # - API: http://localhost:8181 From 49f0d9628f31ccb9fb68ec7d9b09162da4a6e3ca Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Tue, 10 Feb 2026 11:20:20 -0600 Subject: [PATCH 11/19] Update AGENTS.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- AGENTS.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3fa345d..2c96a12 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,12 +28,12 @@ This is a **sample application** for InfluxData documentation tutorials. It demo └─────────────────┘ └─────────────────┘ └─────────────────┘ │ ▼ - ┌─────────────────┐ - │ Two databases: │ - │ - iot_center │ - │ - iot_center_ │ - │ auth │ - └─────────────────┘ + ┌─────────────────────┐ + │ Two databases: │ + │ - iot_center │ + │ - iot_center │ + │ - iot_center_devices│ + └─────────────────────┘ ``` ## Key Files From 1f093ea76433a1a8c6bfee1d90bc36c02556cfd1 Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Tue, 10 Feb 2026 11:20:30 -0600 Subject: [PATCH 12/19] Update AGENTS.md --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 2c96a12..b68b307 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -89,7 +89,7 @@ yarn test ### SQL Queries (InfluxDB 3) -InfluxDB 3 uses SQL instead of Flux: +Query InfluxDB 3 using SQL: ```javascript // Query devices From 14d4f71154430306ebe9e5991de28622ec17db3a Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Tue, 10 Feb 2026 11:20:48 -0600 Subject: [PATCH 13/19] Update CLAUDE.md --- CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index ed1d085..2c3461c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -94,7 +94,7 @@ await write('measurement,tag=value field=1', database) ### Application-Level Tokens -This app uses application-managed tokens stored in the database (not InfluxDB-native auth tokens). Tokens are: +This app uses application-managed tokens stored in the database. Tokens are: - Generated via `generateDeviceToken()` in `lib/influxdb.js` - Stored in the `deviceauth` measurement - Never exposed via public API responses From a2c59379fee9b3b16be5e9b016592cd4c7a56959 Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Tue, 10 Feb 2026 11:21:00 -0600 Subject: [PATCH 14/19] Update AGENTS.md --- AGENTS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index b68b307..112d77b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -87,6 +87,8 @@ yarn test ## Code Patterns +Use SQL or InfluxQL to query data. + ### SQL Queries (InfluxDB 3) Query InfluxDB 3 using SQL: From c77d106c0276be61052410342552c0875b71ec4a Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Tue, 10 Feb 2026 15:24:29 -0600 Subject: [PATCH 15/19] chore: add .worktrees to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index c0a1c0c..372a633 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,6 @@ yarn-error.log* # InfluxDB 3 local data test/.influxdb3/ + +# Git worktrees +.worktrees/ From dcdfb2615f76f2e94d16123036911829c64fd4d6 Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Tue, 17 Feb 2026 16:57:14 -0600 Subject: [PATCH 16/19] fix: remove duplicate deviceId declaration in create.js The handler had `const deviceId` declared twice (lines 15 and 17), causing a SyntaxError that prevented the endpoint from working. Also adds missing Jest/Babel configuration files required to run tests. --- babel.config.js | 5 + jest.config.js | 13 + package.json | 3 + pages/api/devices/create.js | 4 +- yarn.lock | 3166 ++++++++++++++++++++++++++++++++++- 5 files changed, 3169 insertions(+), 22 deletions(-) create mode 100644 babel.config.js create mode 100644 jest.config.js diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..2862f69 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,5 @@ +module.exports = { + presets: [ + ['@babel/preset-env', { targets: { node: 'current' } }], + ], +}; diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..fb4eae3 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,13 @@ +module.exports = { + testEnvironment: 'node', + transform: { + '^.+\\.js$': 'babel-jest', + }, + transformIgnorePatterns: [ + 'node_modules/(?!(@influxdata)/)', + ], + testPathIgnorePatterns: [ + '/node_modules/', + '/.worktrees/', + ], +}; diff --git a/package.json b/package.json index 427e361..5377bab 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,9 @@ "react-dom": "^18.2.0" }, "devDependencies": { + "@babel/core": "^7.29.0", + "@babel/preset-env": "^7.29.0", + "babel-jest": "29.7.0", "jest": "^29.7.0", "node-mocks-http": "^1.14.1" }, diff --git a/pages/api/devices/create.js b/pages/api/devices/create.js index 324aba2..d779fb5 100644 --- a/pages/api/devices/create.js +++ b/pages/api/devices/create.js @@ -13,9 +13,7 @@ export default async function handler(req, res) { // Handle both pre-parsed objects and JSON strings const body = typeof req.body === 'string' ? JSON.parse(req.body) : req.body const deviceId = body?.deviceId - - const deviceId = body?.deviceId; - if (!deviceId) { + if (!deviceId) { return res.status(400).json({ error: 'deviceId is required' }); } diff --git a/yarn.lock b/yarn.lock index 3280d7f..6f3822f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,905 @@ # yarn lockfile v1 +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c" + integrity sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw== + dependencies: + "@babel/helper-validator-identifier" "^7.28.5" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.28.6", "@babel/compat-data@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.0.tgz#00d03e8c0ac24dd9be942c5370990cbe1f17d88d" + integrity sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg== + +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.0.tgz#5286ad785df7f79d656e88ce86e650d16ca5f322" + integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helpers" "^7.28.6" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.29.0" + "@babel/types" "^7.29.0" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.29.0", "@babel/generator@^7.7.2": + version "7.29.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.1.tgz#d09876290111abbb00ef962a7b83a5307fba0d50" + integrity sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw== + dependencies: + "@babel/parser" "^7.29.0" + "@babel/types" "^7.29.0" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/helper-annotate-as-pure@^7.27.1", "@babel/helper-annotate-as-pure@^7.27.3": + version "7.27.3" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz#f31fd86b915fc4daf1f3ac6976c59be7084ed9c5" + integrity sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg== + dependencies: + "@babel/types" "^7.27.3" + +"@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz#32c4a3f41f12ed1532179b108a4d746e105c2b25" + integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA== + dependencies: + "@babel/compat-data" "^7.28.6" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-create-class-features-plugin@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz#611ff5482da9ef0db6291bcd24303400bca170fb" + integrity sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-member-expression-to-functions" "^7.28.5" + "@babel/helper-optimise-call-expression" "^7.27.1" + "@babel/helper-replace-supers" "^7.28.6" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + "@babel/traverse" "^7.28.6" + semver "^6.3.1" + +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.27.1", "@babel/helper-create-regexp-features-plugin@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz#7c1ddd64b2065c7f78034b25b43346a7e19ed997" + integrity sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + regexpu-core "^6.3.1" + semver "^6.3.1" + +"@babel/helper-define-polyfill-provider@^0.6.6": + version "0.6.6" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz#714dfe33d8bd710f556df59953720f6eeb6c1a14" + integrity sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA== + dependencies: + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + debug "^4.4.3" + lodash.debounce "^4.0.8" + resolve "^1.22.11" + +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + +"@babel/helper-member-expression-to-functions@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz#f3e07a10be37ed7a63461c63e6929575945a6150" + integrity sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg== + dependencies: + "@babel/traverse" "^7.28.5" + "@babel/types" "^7.28.5" + +"@babel/helper-module-imports@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz#60632cbd6ffb70b22823187201116762a03e2d5c" + integrity sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw== + dependencies: + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e" + integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA== + dependencies: + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-validator-identifier" "^7.28.5" + "@babel/traverse" "^7.28.6" + +"@babel/helper-optimise-call-expression@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz#c65221b61a643f3e62705e5dd2b5f115e35f9200" + integrity sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw== + dependencies: + "@babel/types" "^7.27.1" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.28.6", "@babel/helper-plugin-utils@^7.8.0": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz#6f13ea251b68c8532e985fd532f28741a8af9ac8" + integrity sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug== + +"@babel/helper-remap-async-to-generator@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz#4601d5c7ce2eb2aea58328d43725523fcd362ce6" + integrity sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.1" + "@babel/helper-wrap-function" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@babel/helper-replace-supers@^7.27.1", "@babel/helper-replace-supers@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz#94aa9a1d7423a00aead3f204f78834ce7d53fe44" + integrity sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.28.5" + "@babel/helper-optimise-call-expression" "^7.27.1" + "@babel/traverse" "^7.28.6" + +"@babel/helper-skip-transparent-expression-wrappers@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz#62bb91b3abba8c7f1fec0252d9dbea11b3ee7a56" + integrity sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" + integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helper-wrap-function@^7.27.1": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz#4e349ff9222dab69a93a019cc296cdd8442e279a" + integrity sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ== + dependencies: + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/helpers@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.6.tgz#fca903a313ae675617936e8998b814c415cbf5d7" + integrity sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw== + dependencies: + "@babel/template" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.28.6", "@babel/parser@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.0.tgz#669ef345add7d057e92b7ed15f0bac07611831b6" + integrity sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww== + dependencies: + "@babel/types" "^7.29.0" + +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz#fbde57974707bbfa0376d34d425ff4fa6c732421" + integrity sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/traverse" "^7.28.5" + +"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz#43f70a6d7efd52370eefbdf55ae03d91b293856d" + integrity sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz#beb623bd573b8b6f3047bd04c32506adc3e58a72" + integrity sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz#e134a5479eb2ba9c02714e8c1ebf1ec9076124fd" + integrity sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + "@babel/plugin-transform-optional-chaining" "^7.27.1" + +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz#0e8289cec28baaf05d54fd08d81ae3676065f69f" + integrity sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/traverse" "^7.28.6" + +"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": + version "7.21.0-placeholder-for-preset-env.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" + integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-assertions@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz#ae9bc1923a6ba527b70104dd2191b0cd872c8507" + integrity sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-syntax-import-attributes@^7.24.7", "@babel/plugin-syntax-import-attributes@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz#b71d5914665f60124e133696f17cd7669062c503" + integrity sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz#f8ca28bbd84883b5fea0e447c635b81ba73997ee" + integrity sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz#c7b2ddf1d0a811145b1de800d1abd146af92e3a2" + integrity sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" + integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-arrow-functions@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz#6e2061067ba3ab0266d834a9f94811196f2aba9a" + integrity sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-async-generator-functions@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz#63ed829820298f0bf143d5a4a68fb8c06ffd742f" + integrity sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/helper-remap-async-to-generator" "^7.27.1" + "@babel/traverse" "^7.29.0" + +"@babel/plugin-transform-async-to-generator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz#bd97b42237b2d1bc90d74bcb486c39be5b4d7e77" + integrity sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g== + dependencies: + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/helper-remap-async-to-generator" "^7.27.1" + +"@babel/plugin-transform-block-scoped-functions@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz#558a9d6e24cf72802dd3b62a4b51e0d62c0f57f9" + integrity sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-block-scoping@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz#e1ef5633448c24e76346125c2534eeb359699a99" + integrity sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-class-properties@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz#d274a4478b6e782d9ea987fda09bdb6d28d66b72" + integrity sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-class-static-block@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz#1257491e8259c6d125ac4d9a6f39f9d2bf3dba70" + integrity sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-classes@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz#8f6fb79ba3703978e701ce2a97e373aae7dda4b7" + integrity sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-globals" "^7.28.0" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/helper-replace-supers" "^7.28.6" + "@babel/traverse" "^7.28.6" + +"@babel/plugin-transform-computed-properties@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz#936824fc71c26cb5c433485776d79c8e7b0202d2" + integrity sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/template" "^7.28.6" + +"@babel/plugin-transform-destructuring@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz#b8402764df96179a2070bb7b501a1586cf8ad7a7" + integrity sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/traverse" "^7.28.5" + +"@babel/plugin-transform-dotall-regex@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz#def31ed84e0fb6e25c71e53c124e7b76a4ab8e61" + integrity sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-duplicate-keys@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz#f1fbf628ece18e12e7b32b175940e68358f546d1" + integrity sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz#8014b8a6cfd0e7b92762724443bf0d2400f26df1" + integrity sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-dynamic-import@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz#4c78f35552ac0e06aa1f6e3c573d67695e8af5a4" + integrity sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-explicit-resource-management@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz#dd6788f982c8b77e86779d1d029591e39d9d8be7" + integrity sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-transform-destructuring" "^7.28.5" + +"@babel/plugin-transform-exponentiation-operator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz#5e477eb7eafaf2ab5537a04aaafcf37e2d7f1091" + integrity sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-export-namespace-from@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz#71ca69d3471edd6daa711cf4dfc3400415df9c23" + integrity sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-for-of@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz#bc24f7080e9ff721b63a70ac7b2564ca15b6c40a" + integrity sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + +"@babel/plugin-transform-function-name@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz#4d0bf307720e4dce6d7c30fcb1fd6ca77bdeb3a7" + integrity sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ== + dependencies: + "@babel/helper-compilation-targets" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/traverse" "^7.27.1" + +"@babel/plugin-transform-json-strings@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz#4c8c15b2dc49e285d110a4cf3dac52fd2dfc3038" + integrity sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-literals@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz#baaefa4d10a1d4206f9dcdda50d7d5827bb70b24" + integrity sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-logical-assignment-operators@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz#53028a3d77e33c50ef30a8fce5ca17065936e605" + integrity sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-member-expression-literals@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz#37b88ba594d852418e99536f5612f795f23aeaf9" + integrity sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-modules-amd@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz#a4145f9d87c2291fe2d05f994b65dba4e3e7196f" + integrity sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA== + dependencies: + "@babel/helper-module-transforms" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-modules-commonjs@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz#c0232e0dfe66a734cc4ad0d5e75fc3321b6fdef1" + integrity sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA== + dependencies: + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-modules-systemjs@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz#e458a95a17807c415924106a3ff188a3b8dee964" + integrity sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ== + dependencies: + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/helper-validator-identifier" "^7.28.5" + "@babel/traverse" "^7.29.0" + +"@babel/plugin-transform-modules-umd@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz#63f2cf4f6dc15debc12f694e44714863d34cd334" + integrity sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w== + dependencies: + "@babel/helper-module-transforms" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz#a26cd51e09c4718588fc4cce1c5d1c0152102d6a" + integrity sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-new-target@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz#259c43939728cad1706ac17351b7e6a7bea1abeb" + integrity sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-nullish-coalescing-operator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz#9bc62096e90ab7a887f3ca9c469f6adec5679757" + integrity sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-numeric-separator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz#1310b0292762e7a4a335df5f580c3320ee7d9e9f" + integrity sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-object-rest-spread@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz#fdd4bc2d72480db6ca42aed5c051f148d7b067f7" + integrity sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA== + dependencies: + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/plugin-transform-destructuring" "^7.28.5" + "@babel/plugin-transform-parameters" "^7.27.7" + "@babel/traverse" "^7.28.6" + +"@babel/plugin-transform-object-super@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz#1c932cd27bf3874c43a5cac4f43ebf970c9871b5" + integrity sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + "@babel/helper-replace-supers" "^7.27.1" + +"@babel/plugin-transform-optional-catch-binding@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz#75107be14c78385978201a49c86414a150a20b4c" + integrity sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-optional-chaining@^7.27.1", "@babel/plugin-transform-optional-chaining@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz#926cf150bd421fc8362753e911b4a1b1ce4356cd" + integrity sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + +"@babel/plugin-transform-parameters@^7.27.7": + version "7.27.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz#1fd2febb7c74e7d21cf3b05f7aebc907940af53a" + integrity sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-private-methods@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz#c76fbfef3b86c775db7f7c106fff544610bdb411" + integrity sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-private-property-in-object@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz#4fafef1e13129d79f1d75ac180c52aafefdb2811" + integrity sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.27.3" + "@babel/helper-create-class-features-plugin" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-property-literals@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz#07eafd618800591e88073a0af1b940d9a42c6424" + integrity sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-regenerator@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz#dec237cec1b93330876d6da9992c4abd42c9d18b" + integrity sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-regexp-modifiers@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz#7ef0163bd8b4a610481b2509c58cf217f065290b" + integrity sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-reserved-words@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz#40fba4878ccbd1c56605a4479a3a891ac0274bb4" + integrity sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-shorthand-properties@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz#532abdacdec87bfee1e0ef8e2fcdee543fe32b90" + integrity sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-spread@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz#40a2b423f6db7b70f043ad027a58bcb44a9757b6" + integrity sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" + +"@babel/plugin-transform-sticky-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz#18984935d9d2296843a491d78a014939f7dcd280" + integrity sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-template-literals@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz#1a0eb35d8bb3e6efc06c9fd40eb0bcef548328b8" + integrity sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-typeof-symbol@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz#70e966bb492e03509cf37eafa6dcc3051f844369" + integrity sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-unicode-escapes@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz#3e3143f8438aef842de28816ece58780190cf806" + integrity sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-unicode-property-regex@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz#63a7a6c21a0e75dae9b1861454111ea5caa22821" + integrity sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-transform-unicode-regex@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz#25948f5c395db15f609028e370667ed8bae9af97" + integrity sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.27.1" + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-unicode-sets-regex@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz#924912914e5df9fe615ec472f88ff4788ce04d4e" + integrity sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.28.5" + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/preset-env@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.29.0.tgz#c55db400c515a303662faaefd2d87e796efa08d0" + integrity sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w== + dependencies: + "@babel/compat-data" "^7.29.0" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-plugin-utils" "^7.28.6" + "@babel/helper-validator-option" "^7.27.1" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.28.5" + "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.27.1" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.27.1" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.27.1" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.28.6" + "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" + "@babel/plugin-syntax-import-assertions" "^7.28.6" + "@babel/plugin-syntax-import-attributes" "^7.28.6" + "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.27.1" + "@babel/plugin-transform-async-generator-functions" "^7.29.0" + "@babel/plugin-transform-async-to-generator" "^7.28.6" + "@babel/plugin-transform-block-scoped-functions" "^7.27.1" + "@babel/plugin-transform-block-scoping" "^7.28.6" + "@babel/plugin-transform-class-properties" "^7.28.6" + "@babel/plugin-transform-class-static-block" "^7.28.6" + "@babel/plugin-transform-classes" "^7.28.6" + "@babel/plugin-transform-computed-properties" "^7.28.6" + "@babel/plugin-transform-destructuring" "^7.28.5" + "@babel/plugin-transform-dotall-regex" "^7.28.6" + "@babel/plugin-transform-duplicate-keys" "^7.27.1" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.29.0" + "@babel/plugin-transform-dynamic-import" "^7.27.1" + "@babel/plugin-transform-explicit-resource-management" "^7.28.6" + "@babel/plugin-transform-exponentiation-operator" "^7.28.6" + "@babel/plugin-transform-export-namespace-from" "^7.27.1" + "@babel/plugin-transform-for-of" "^7.27.1" + "@babel/plugin-transform-function-name" "^7.27.1" + "@babel/plugin-transform-json-strings" "^7.28.6" + "@babel/plugin-transform-literals" "^7.27.1" + "@babel/plugin-transform-logical-assignment-operators" "^7.28.6" + "@babel/plugin-transform-member-expression-literals" "^7.27.1" + "@babel/plugin-transform-modules-amd" "^7.27.1" + "@babel/plugin-transform-modules-commonjs" "^7.28.6" + "@babel/plugin-transform-modules-systemjs" "^7.29.0" + "@babel/plugin-transform-modules-umd" "^7.27.1" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.29.0" + "@babel/plugin-transform-new-target" "^7.27.1" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.28.6" + "@babel/plugin-transform-numeric-separator" "^7.28.6" + "@babel/plugin-transform-object-rest-spread" "^7.28.6" + "@babel/plugin-transform-object-super" "^7.27.1" + "@babel/plugin-transform-optional-catch-binding" "^7.28.6" + "@babel/plugin-transform-optional-chaining" "^7.28.6" + "@babel/plugin-transform-parameters" "^7.27.7" + "@babel/plugin-transform-private-methods" "^7.28.6" + "@babel/plugin-transform-private-property-in-object" "^7.28.6" + "@babel/plugin-transform-property-literals" "^7.27.1" + "@babel/plugin-transform-regenerator" "^7.29.0" + "@babel/plugin-transform-regexp-modifiers" "^7.28.6" + "@babel/plugin-transform-reserved-words" "^7.27.1" + "@babel/plugin-transform-shorthand-properties" "^7.27.1" + "@babel/plugin-transform-spread" "^7.28.6" + "@babel/plugin-transform-sticky-regex" "^7.27.1" + "@babel/plugin-transform-template-literals" "^7.27.1" + "@babel/plugin-transform-typeof-symbol" "^7.27.1" + "@babel/plugin-transform-unicode-escapes" "^7.27.1" + "@babel/plugin-transform-unicode-property-regex" "^7.28.6" + "@babel/plugin-transform-unicode-regex" "^7.27.1" + "@babel/plugin-transform-unicode-sets-regex" "^7.28.6" + "@babel/preset-modules" "0.1.6-no-external-plugins" + babel-plugin-polyfill-corejs2 "^0.4.15" + babel-plugin-polyfill-corejs3 "^0.14.0" + babel-plugin-polyfill-regenerator "^0.6.6" + core-js-compat "^3.48.0" + semver "^6.3.1" + +"@babel/preset-modules@0.1.6-no-external-plugins": + version "0.1.6-no-external-plugins" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" + integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/template@^7.28.6", "@babel/template@^7.3.3": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.28.6.tgz#0e7e56ecedb78aeef66ce7972b082fce76a23e57" + integrity sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/parser" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.5", "@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a" + integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/types" "^7.29.0" + debug "^4.3.1" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.5", "@babel/types@^7.28.6", "@babel/types@^7.29.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7" + integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + "@emnapi/runtime@^1.7.0": version "1.8.1" resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.8.1.tgz#550fa7e3c0d49c5fb175a116e8cd70614f9a22a5" @@ -9,6 +908,24 @@ dependencies: tslib "^2.4.0" +"@grpc/grpc-js@^1.9.9": + version "1.14.3" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.14.3.tgz#4c9b817a900ae4020ddc28515ae4b52c78cfb8da" + integrity sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA== + dependencies: + "@grpc/proto-loader" "^0.8.0" + "@js-sdsl/ordered-map" "^4.4.2" + +"@grpc/proto-loader@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.8.0.tgz#b6c324dd909c458a0e4aa9bfd3d69cf78a4b9bd8" + integrity sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ== + dependencies: + lodash.camelcase "^4.3.0" + long "^5.0.0" + protobufjs "^7.5.3" + yargs "^17.7.2" + "@img/colour@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@img/colour/-/colour-1.0.0.tgz#d2fabb223455a793bf3bf9c70de3d28526aa8311" @@ -156,15 +1073,264 @@ resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz#a81ffb00e69267cd0a1d626eaedb8a8430b2b2f8" integrity sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw== -"@influxdata/influxdb-client-apis@^1.24.0": - version "1.33.2" - resolved "https://registry.yarnpkg.com/@influxdata/influxdb-client-apis/-/influxdb-client-apis-1.33.2.tgz#792f4ae3930588cee6ac20c1192bf8c38dbd0765" - integrity sha512-W6x9TOAQ3AUx0RBCrCibDhSvMqN50lxJmElc3rHn7+R/9Zi35oczu8r9YMkyNlzWnksu+dcyKr8/xLv28Ot4hw== +"@influxdata/influxdb3-client@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@influxdata/influxdb3-client/-/influxdb3-client-2.0.0.tgz#8b1cfeb57c14ba4927bbd7bd32a0284b3df4c4fb" + integrity sha512-ZS+6CeHsCZ1mJjvryNvCcivY3FVqYMDiohYZzk4H/0Vvk/nCNBZ6DOT2GCOG5KsC3NXs1JcL27lJnDSwadpPEQ== + dependencies: + "@grpc/grpc-js" "^1.9.9" + "@protobuf-ts/grpc-transport" "^2.9.1" + "@protobuf-ts/grpcweb-transport" "^2.9.1" + "@protobuf-ts/runtime-rpc" "^2.9.1" + apache-arrow "^19.0.0" + grpc-web "^2.0.2" + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" -"@influxdata/influxdb-client@^1.24.0": - version "1.33.2" - resolved "https://registry.yarnpkg.com/@influxdata/influxdb-client/-/influxdb-client-1.33.2.tgz#c68cfcf592e4e042361003143fbab99461410172" - integrity sha512-RT5SxH+grHAazo/YK3UTuWK/frPWRM0N7vkrCUyqVprDgQzlLP+bSK4ak2Jv3QVF/pazTnsxWjvtKZdwskV5Xw== +"@js-sdsl/ordered-map@^4.4.2": + version "4.4.2" + resolved "https://registry.yarnpkg.com/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz#9299f82874bab9e4c7f9c48d865becbfe8d6907c" + integrity sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw== "@next/env@16.1.5": version "16.1.5" @@ -211,38 +1377,1354 @@ resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.5.tgz#92c0161dec3c466b120a312199a376d029b92ce2" integrity sha512-7is37HJTNQGhjPpQbkKjKEboHYQnCgpVt/4rBrrln0D9nderNxZ8ZWs8w1fAtzUx7wEyYjQ+/13myFgFj6K2Ng== -"@swc/helpers@0.5.15": - version "0.5.15" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.15.tgz#79efab344c5819ecf83a43f3f9f811fc84b516d7" - integrity sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g== +"@protobuf-ts/grpc-transport@^2.9.1": + version "2.11.1" + resolved "https://registry.yarnpkg.com/@protobuf-ts/grpc-transport/-/grpc-transport-2.11.1.tgz#310b681a9172b4207c9bb588c53ab4a58f712299" + integrity sha512-l6wrcFffY+tuNnuyrNCkRM8hDIsAZVLA8Mn7PKdVyYxITosYh60qW663p9kL6TWXYuDCL3oxH8ih3vLKTDyhtg== dependencies: - tslib "^2.8.0" + "@protobuf-ts/runtime" "^2.11.1" + "@protobuf-ts/runtime-rpc" "^2.11.1" -baseline-browser-mapping@^2.8.3: - version "2.9.19" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz#3e508c43c46d961eb4d7d2e5b8d1dd0f9ee4f488" - integrity sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg== +"@protobuf-ts/grpcweb-transport@^2.9.1": + version "2.11.1" + resolved "https://registry.yarnpkg.com/@protobuf-ts/grpcweb-transport/-/grpcweb-transport-2.11.1.tgz#63a8a0d6043851fec14de4b091121377d2549683" + integrity sha512-1W4utDdvOB+RHMFQ0soL4JdnxjXV+ddeGIUg08DvZrA8Ms6k5NN6GBFU2oHZdTOcJVpPrDJ02RJlqtaoCMNBtw== + dependencies: + "@protobuf-ts/runtime" "^2.11.1" + "@protobuf-ts/runtime-rpc" "^2.11.1" + +"@protobuf-ts/runtime-rpc@^2.11.1", "@protobuf-ts/runtime-rpc@^2.9.1": + version "2.11.1" + resolved "https://registry.yarnpkg.com/@protobuf-ts/runtime-rpc/-/runtime-rpc-2.11.1.tgz#a6eb2f384bceae8d23a01d0b0e37faf0af36c179" + integrity sha512-4CqqUmNA+/uMz00+d3CYKgElXO9VrEbucjnBFEjqI4GuDrEQ32MaI3q+9qPBvIGOlL4PmHXrzM32vBPWRhQKWQ== + dependencies: + "@protobuf-ts/runtime" "^2.11.1" + +"@protobuf-ts/runtime@^2.11.1": + version "2.11.1" + resolved "https://registry.yarnpkg.com/@protobuf-ts/runtime/-/runtime-2.11.1.tgz#ee2bf2fac6e2d8deac0ca63471a77481548e5553" + integrity sha512-KuDaT1IfHkugM2pyz+FwiY80ejWrkH1pAtOBOZFuR6SXEFTsnb/jiQWQ1rCIrcKx2BtyxnxW6BWwsVSA/Ie+WQ== + +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + +"@sinclair/typebox@^0.27.8": + version "0.27.10" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.10.tgz#beefe675f1853f73676aecc915b2bd2ac98c4fc6" + integrity sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA== + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@swc/helpers@0.5.15": + version "0.5.15" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.15.tgz#79efab344c5819ecf83a43f3f9f811fc84b516d7" + integrity sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g== + dependencies: + tslib "^2.8.0" + +"@swc/helpers@^0.5.11": + version "0.5.18" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.18.tgz#feeeabea0d10106ee25aaf900165df911ab6d3b1" + integrity sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ== + dependencies: + tslib "^2.8.0" + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" + integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" + integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== + dependencies: + "@babel/types" "^7.28.2" + +"@types/command-line-args@^5.2.3": + version "5.2.3" + resolved "https://registry.yarnpkg.com/@types/command-line-args/-/command-line-args-5.2.3.tgz#553ce2fd5acf160b448d307649b38ffc60d39639" + integrity sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw== + +"@types/command-line-usage@^5.0.4": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@types/command-line-usage/-/command-line-usage-5.0.4.tgz#374e4c62d78fbc5a670a0f36da10235af879a0d5" + integrity sha512-BwR5KP3Es/CSht0xqBcUXS3qCAUVXwpRKsV2+arxeb65atasuXG9LykC9Ab10Cw3s2raH92ZqOeILaQbsB2ACg== + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/node@*", "@types/node@>=13.7.0": + version "25.2.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.2.3.tgz#9c18245be768bdb4ce631566c7da303a5c99a7f8" + integrity sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ== + dependencies: + undici-types "~7.16.0" + +"@types/node@^20.13.0": + version "20.19.33" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.33.tgz#ac8364c623b72d43125f0e7dd722bbe968f0c65e" + integrity sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw== + dependencies: + undici-types "~6.21.0" + +"@types/stack-utils@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.35" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.35.tgz#07013e46aa4d7d7d50a49e15604c1c5340d4eb24" + integrity sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg== + dependencies: + "@types/yargs-parser" "*" + +accepts@^1.3.7: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +anymatch@^3.0.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +apache-arrow@^19.0.0: + version "19.0.1" + resolved "https://registry.yarnpkg.com/apache-arrow/-/apache-arrow-19.0.1.tgz#aeac6922102e3274f57d97ec72da69d20e55b15c" + integrity sha512-APmMLzS4qbTivLrPdQXexGM4JRr+0g62QDaobzEvip/FdQIrv2qLy0mD5Qdmw4buydtVJgbFeKR8f59I6PPGDg== + dependencies: + "@swc/helpers" "^0.5.11" + "@types/command-line-args" "^5.2.3" + "@types/command-line-usage" "^5.0.4" + "@types/node" "^20.13.0" + command-line-args "^6.0.1" + command-line-usage "^7.0.1" + flatbuffers "^24.3.25" + json-bignum "^0.0.3" + tslib "^2.6.2" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +array-back@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-6.2.2.tgz#f567d99e9af88a6d3d2f9dfcc21db6f9ba9fd157" + integrity sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw== + +babel-jest@29.7.0, babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-plugin-polyfill-corejs2@^0.4.15: + version "0.4.15" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz#808fa349686eea4741807cfaaa2aa3aa57ce120a" + integrity sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw== + dependencies: + "@babel/compat-data" "^7.28.6" + "@babel/helper-define-polyfill-provider" "^0.6.6" + semver "^6.3.1" + +babel-plugin-polyfill-corejs3@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.0.tgz#65b06cda48d6e447e1e926681f5a247c6ae2b9cf" + integrity sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.6" + core-js-compat "^3.48.0" + +babel-plugin-polyfill-regenerator@^0.6.6: + version "0.6.6" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz#69f5dd263cab933c42fe5ea05e83443b374bd4bf" + integrity sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz#20730d6cdc7dda5d89401cab10ac6a32067acde6" + integrity sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +baseline-browser-mapping@^2.8.3, baseline-browser-mapping@^2.9.0: + version "2.9.19" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz#3e508c43c46d961eb4d7d2e5b8d1dd0f9ee4f488" + integrity sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg== + +brace-expansion@^1.1.7: + version "1.1.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.24.0, browserslist@^4.28.1: + version "4.28.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" + integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== + dependencies: + baseline-browser-mapping "^2.9.0" + caniuse-lite "^1.0.30001759" + electron-to-chromium "^1.5.263" + node-releases "^2.0.27" + update-browserslist-db "^1.2.0" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001579: version "1.0.30001618" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001618.tgz#fad74fa006aef0f01e8e5c0a5540c74d8d36ec6f" integrity sha512-p407+D1tIkDvsEAPS22lJxLQQaG8OTBEqo0KhzfABGk0TU4juBNDSfH0hyAp/HRyx+M8L17z/ltyhxh27FTfQg== +caniuse-lite@^1.0.30001759: + version "1.0.30001769" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz#1ad91594fad7dc233777c2781879ab5409f7d9c2" + integrity sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg== + +chalk-template@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/chalk-template/-/chalk-template-0.4.0.tgz#692c034d0ed62436b9062c1707fadcd0f753204b" + integrity sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg== + dependencies: + chalk "^4.1.2" + +chalk@^4.0.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cjs-module-lexer@^1.0.0: + version "1.4.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" + integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== + client-only@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz#cc1f01eb8d02298cbc9a437c74c70ab4e5210b80" + integrity sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +command-line-args@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-6.0.1.tgz#cbd1efb4f72b285dbd54bde9a8585c2d9694b070" + integrity sha512-Jr3eByUjqyK0qd8W0SGFW1nZwqCaNCtbXjRo2cRJC1OYxWl3MZ5t1US3jq+cO4sPavqgw4l9BMGX0CBe+trepg== + dependencies: + array-back "^6.2.2" + find-replace "^5.0.2" + lodash.camelcase "^4.3.0" + typical "^7.2.0" + +command-line-usage@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-7.0.3.tgz#6bce992354f6af10ecea2b631bfdf0c8b3bfaea3" + integrity sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q== + dependencies: + array-back "^6.2.2" + chalk-template "^0.4.0" + table-layout "^4.1.0" + typical "^7.1.1" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +content-disposition@^0.5.3: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +core-js-compat@^3.48.0: + version "3.48.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.48.0.tgz#7efbe1fc1cbad44008190462217cc5558adaeaa6" + integrity sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q== + dependencies: + browserslist "^4.28.1" + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +cross-spawn@^7.0.3: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.4.3: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +dedent@^1.0.0: + version "1.7.1" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.1.tgz#364661eea3d73f3faba7089214420ec2f8f13e15" + integrity sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg== + +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +depd@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== + detect-libc@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== -"js-tokens@^3.0.0 || ^4.0.0": +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +electron-to-chromium@^1.5.263: + version "1.5.286" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz#142be1ab5e1cd5044954db0e5898f60a4960384e" + integrity sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +error-ex@^1.3.1: + version "1.3.4" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414" + integrity sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ== + dependencies: + is-arrayish "^0.2.1" + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-replace@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-5.0.2.tgz#fe27ff0be05975aef6fc679c1139bbabea564e26" + integrity sha512-Y45BAiE3mz2QsrN2fb5QEtO4qb44NcS7en/0y9PEVsg351HsLeVclP8QPMH79Le9sH3rs5RSwJu99W0WPZO43Q== + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +flatbuffers@^24.3.25: + version "24.12.23" + resolved "https://registry.yarnpkg.com/flatbuffers/-/flatbuffers-24.12.23.tgz#6eea59d2bcda0c5d59bcacefd6216348b3086883" + integrity sha512-dLVCAISd5mhls514keQzmEG6QHmUUsNuWsb4tFafIUwvvgDjXhtfAYSKOzt5SWOy+qByV5pbsDZ+Vb7HUOBEdA== + +fresh@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +grpc-web@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/grpc-web/-/grpc-web-2.0.2.tgz#416164eb747bab5c3054fd58f73b21814f325d83" + integrity sha512-bvBP0/d5jyVM3eGxxffJhRLAPpH6eXhJeUzBT+bSIEgUKkG4a/BotEimVeW3phP0WLnsJnkRl8uQdRf2yDLaVA== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-core-module@^2.16.1: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-instrument@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz#cb4535162b5784aa623cee21a7252cf2c807ac93" + integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + +jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +js-yaml@^3.13.1: + version "3.14.2" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.2.tgz#77485ce1dd7f33c061fd1b16ecea23b55fcb04b0" + integrity sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsesc@^3.0.2, jsesc@~3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +json-bignum@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/json-bignum/-/json-bignum-0.0.3.tgz#41163b50436c773d82424dbc20ed70db7604b8d7" + integrity sha512-2WHyXj3OfHSgNyuzDbSxI1w2jgw5gkWSWhS7Qg4bWXx1nLk3jnbwfUeS0PSba3IzpTUWdHxBieELUzXRjQB2zg== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== + +long@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" + integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== + loose-envify@^1.1.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -250,11 +2732,104 @@ loose-envify@^1.1.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +merge-descriptors@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" + integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +methods@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +micromatch@^4.0.4: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@^1.3.4: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4, minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + nanoid@^3.3.7: version "3.3.8" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + next@^16.1.5: version "16.1.5" resolved "https://registry.yarnpkg.com/next/-/next-16.1.5.tgz#95c9bc91bfb1bb0d3f2d441fdbb550bf18939edf" @@ -277,11 +2852,146 @@ next@^16.1.5: "@next/swc-win32-x64-msvc" "16.1.5" sharp "^0.34.4" +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-mocks-http@^1.14.1: + version "1.17.2" + resolved "https://registry.yarnpkg.com/node-mocks-http/-/node-mocks-http-1.17.2.tgz#e947d9b94defb13e3775414a8200c848f6b2fc74" + integrity sha512-HVxSnjNzE9NzoWMx9T9z4MLqwMpLwVvA0oVZ+L+gXskYXEJ6tFn3Kx4LargoB6ie7ZlCLplv7QbWO6N+MysWGA== + dependencies: + accepts "^1.3.7" + content-disposition "^0.5.3" + depd "^1.1.0" + fresh "^0.5.2" + merge-descriptors "^1.0.1" + methods "^1.1.2" + mime "^1.3.4" + parseurl "^1.3.3" + range-parser "^1.2.0" + type-is "^1.6.18" + +node-releases@^2.0.27: + version "2.0.27" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e" + integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parseurl@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + picocolors@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" + integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + postcss@8.4.31, postcss@^8.4.31: version "8.4.38" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" @@ -291,6 +3001,51 @@ postcss@8.4.31, postcss@^8.4.31: picocolors "^1.0.0" source-map-js "^1.2.0" +pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +protobufjs@^7.5.3: + version "7.5.4" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.4.tgz#885d31fe9c4b37f25d1bb600da30b1c5b37d286a" + integrity sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + +range-parser@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + react-dom@^18.2.0: version "18.3.1" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" @@ -299,6 +3054,11 @@ react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.2" +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + react@^18.2.0: version "18.3.1" resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" @@ -306,6 +3066,78 @@ react@^18.2.0: dependencies: loose-envify "^1.1.0" +regenerate-unicode-properties@^10.2.2: + version "10.2.2" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz#aa113812ba899b630658c7623466be71e1f86f66" + integrity sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g== + dependencies: + regenerate "^1.4.2" + +regenerate@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + +regexpu-core@^6.3.1: + version "6.4.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-6.4.0.tgz#3580ce0c4faedef599eccb146612436b62a176e5" + integrity sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA== + dependencies: + regenerate "^1.4.2" + regenerate-unicode-properties "^10.2.2" + regjsgen "^0.8.0" + regjsparser "^0.13.0" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.2.1" + +regjsgen@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.8.0.tgz#df23ff26e0c5b300a6470cad160a9d090c3a37ab" + integrity sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q== + +regjsparser@^0.13.0: + version "0.13.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.13.0.tgz#01f8351335cf7898d43686bc74d2dd71c847ecc0" + integrity sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q== + dependencies: + jsesc "~3.1.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" + integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== + +resolve@^1.20.0, resolve@^1.22.11: + version "1.22.11" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262" + integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== + dependencies: + is-core-module "^2.16.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +safe-buffer@5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + scheduler@^0.23.2: version "0.23.2" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" @@ -313,6 +3145,16 @@ scheduler@^0.23.2: dependencies: loose-envify "^1.1.0" +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.5.3, semver@^7.5.4: + version "7.7.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" + integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== + semver@^7.7.3: version "7.7.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" @@ -352,11 +3194,102 @@ sharp@^0.34.4: "@img/sharp-win32-ia32" "0.34.5" "@img/sharp-win32-x64" "0.34.5" +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + source-map-js@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + styled-jsx@5.1.6: version "5.1.6" resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.6.tgz#83b90c077e6c6a80f7f5e8781d0f311b2fe41499" @@ -364,12 +3297,207 @@ styled-jsx@5.1.6: dependencies: client-only "0.0.1" +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +table-layout@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-4.1.1.tgz#0f72965de1a5c0c1419c9ba21cae4e73a2f73a42" + integrity sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA== + dependencies: + array-back "^6.2.2" + wordwrapjs "^5.1.0" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + tslib@^2.4.0: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== -tslib@^2.8.0: +tslib@^2.6.2, tslib@^2.8.0: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-is@^1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typical@^7.1.1, typical@^7.2.0: + version "7.3.0" + resolved "https://registry.yarnpkg.com/typical/-/typical-7.3.0.tgz#930376be344228709f134613911fa22aa09617a4" + integrity sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +undici-types@~7.16.0: + version "7.16.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" + integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== + +unicode-canonical-property-names-ecmascript@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz#cb3173fe47ca743e228216e4a3ddc4c84d628cc2" + integrity sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg== + +unicode-match-property-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" + integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== + dependencies: + unicode-canonical-property-names-ecmascript "^2.0.0" + unicode-property-aliases-ecmascript "^2.0.0" + +unicode-match-property-value-ecmascript@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz#65a7adfad8574c219890e219285ce4c64ed67eaa" + integrity sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg== + +unicode-property-aliases-ecmascript@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz#301d4f8a43d2b75c97adfad87c9dd5350c9475d1" + integrity sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ== + +update-browserslist-db@^1.2.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" + integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wordwrapjs@^5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-5.1.1.tgz#bfd1eb426f0f7eec73b7df32cf7df1f618bfb3a9" + integrity sha512-0yweIbkINJodk27gX9LBGMzyQdBDan3s/dEAiwBOj+Mf0PPyWL6/rikalkv8EeD0E8jm4o5RXEOrFTP3NXbhJg== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.3.1, yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From 4e968f2f5a93fcc1b60ff88d460f45d19ddff38c Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Thu, 26 Feb 2026 23:32:33 -0600 Subject: [PATCH 17/19] docs: refactor AI instructions into modular docs/ structure - Move detailed documentation to docs/ directory: - architecture.md: system design, project structure, schema - development.md: setup, testing, debugging - api-reference.md: endpoint documentation - code-patterns.md: InfluxDB client usage - contributing.md: style guidelines, adding endpoints - Simplify CLAUDE.md and AGENTS.md to concise TOCs - Update .github/INSTRUCTIONS.md with links to docs/ --- .github/INSTRUCTIONS.md | 57 ++++++------- AGENTS.md | 177 +++++----------------------------------- CLAUDE.md | 120 +++++++-------------------- docs/api-reference.md | 115 ++++++++++++++++++++++++++ docs/architecture.md | 65 +++++++++++++++ docs/code-patterns.md | 134 ++++++++++++++++++++++++++++++ docs/contributing.md | 83 +++++++++++++++++++ docs/development.md | 82 +++++++++++++++++++ 8 files changed, 555 insertions(+), 278 deletions(-) create mode 100644 docs/api-reference.md create mode 100644 docs/architecture.md create mode 100644 docs/code-patterns.md create mode 100644 docs/contributing.md create mode 100644 docs/development.md diff --git a/.github/INSTRUCTIONS.md b/.github/INSTRUCTIONS.md index a99b0d6..bccbd88 100644 --- a/.github/INSTRUCTIONS.md +++ b/.github/INSTRUCTIONS.md @@ -4,49 +4,44 @@ This repository has multiple instruction files for different AI tools and use ca ## Quick Navigation -| If you are... | Use this file | Purpose | -|---------------|---------------|---------| -| **GitHub Copilot** | [../AGENTS.md](../AGENTS.md) | Development patterns, code examples | -| **Claude, ChatGPT, Gemini** | [../AGENTS.md](../AGENTS.md) | Comprehensive development guide | -| **Claude with MCP** | [../CLAUDE.md](../CLAUDE.md) | Quick reference with skill pointers | +| If you are... | Start here | +|---------------|------------| +| **GitHub Copilot** | [../AGENTS.md](../AGENTS.md) | +| **Claude, ChatGPT, Gemini** | [../AGENTS.md](../AGENTS.md) | +| **Claude with MCP** | [../CLAUDE.md](../CLAUDE.md) | ## File Organization ``` iot-api-js/ +├── docs/ # Detailed documentation +│ ├── architecture.md # System design, project structure +│ ├── development.md # Setup, testing, debugging +│ ├── api-reference.md # API endpoints +│ ├── code-patterns.md # InfluxDB client usage +│ └── contributing.md # Style guidelines ├── .claude/ -│ ├── settings.json # Claude permissions -│ └── skills/ -│ └── run-tests/SKILL.md # Test execution workflow +│ ├── settings.json # Claude permissions +│ └── skills/run-tests/SKILL.md # Test execution workflow ├── .github/ -│ └── INSTRUCTIONS.md # THIS FILE - Navigation guide -├── AGENTS.md # Comprehensive AI assistant guide -├── CLAUDE.md # Claude with MCP quick reference -└── README.md # User-facing documentation +│ └── INSTRUCTIONS.md # THIS FILE +├── AGENTS.md # AI assistant quick reference +├── CLAUDE.md # Claude with MCP quick reference +└── README.md # User-facing documentation ``` -## What's in Each File +## Documentation -**[../CLAUDE.md](../CLAUDE.md)** - Quick reference for Claude: -- Project overview -- Quick commands -- Structure summary -- Links to skills - -**[../AGENTS.md](../AGENTS.md)** - Comprehensive guide: -- Architecture diagram -- Development workflow -- Code patterns and examples -- Common tasks -- Style guidelines - -**[../README.md](../README.md)** - User documentation: -- Setup instructions -- API usage -- Troubleshooting +| Topic | Location | +|-------|----------| +| System architecture | [../docs/architecture.md](../docs/architecture.md) | +| Development setup | [../docs/development.md](../docs/development.md) | +| API endpoints | [../docs/api-reference.md](../docs/api-reference.md) | +| Code patterns | [../docs/code-patterns.md](../docs/code-patterns.md) | +| Contributing | [../docs/contributing.md](../docs/contributing.md) | ## Getting Started 1. **New to the repository?** Start with [../README.md](../README.md) -2. **Using AI assistants?** Read [../AGENTS.md](../AGENTS.md) +2. **Using AI assistants?** Read [../AGENTS.md](../AGENTS.md) then explore [../docs/](../docs/) 3. **Using Claude with MCP?** Check [../CLAUDE.md](../CLAUDE.md) and [../.claude/](../.claude/) diff --git a/AGENTS.md b/AGENTS.md index 112d77b..22497c1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,40 +1,17 @@ # IoT API JS - AI Assistant Guide -> **For general AI assistants (Claude, ChatGPT, Gemini, etc.)** -> -> This guide provides instructions for AI assistants helping with the IoT API sample application. -> -> **Other instruction resources**: -> - [CLAUDE.md](CLAUDE.md) - For Claude with MCP -> - [.github/INSTRUCTIONS.md](.github/INSTRUCTIONS.md) - Navigation guide -> - [README.md](README.md) - User-facing documentation +Sample application for InfluxData tutorials demonstrating REST APIs with InfluxDB 3 Core. -## Project Purpose +**Target audience:** Developers learning to build IoT applications with InfluxDB 3. -This is a **sample application** for InfluxData documentation tutorials. It demonstrates: -- Building REST APIs that interact with InfluxDB 3 -- Device registration with application-level authentication -- Writing and querying time-series IoT data -- Using the `@influxdata/influxdb3-client` JavaScript library +## Quick Reference -**Target audience**: Developers learning to build IoT applications with InfluxDB 3. - -## Architecture - -``` -┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ -│ Frontend UI │────▶│ Next.js API │────▶│ InfluxDB 3 │ -│ (iot-api-ui) │ │ (this repo) │ │ Core │ -└─────────────────┘ └─────────────────┘ └─────────────────┘ - │ - ▼ - ┌─────────────────────┐ - │ Two databases: │ - │ - iot_center │ - │ - iot_center │ - │ - iot_center_devices│ - └─────────────────────┘ -``` +| Task | Command | +|------|---------| +| Install | `yarn` | +| Dev server | `yarn dev -p 5200` | +| Run tests | `yarn test` | +| Start InfluxDB | `docker compose up -d influxdb3-core` | ## Key Files @@ -44,132 +21,20 @@ This is a **sample application** for InfluxData documentation tutorials. It demo | `pages/api/devices/create.js` | Device registration endpoint | | `pages/api/devices/_devices.js` | Shared device query logic | | `pages/api/devices/[[...deviceParams]].js` | Device CRUD operations | -| `pages/api/measurements/index.js` | Telemetry query endpoint | | `__tests__/api.test.js` | API integration tests | -## Development Workflow - -### Prerequisites - -1. Node.js 18+ and Yarn -2. Docker (for InfluxDB 3 Core) -3. Environment variables in `.env.local` - -### Local Development - -```bash -# 1. Start InfluxDB 3 Core -docker compose up -d influxdb3-core - -# 2. Get the generated token -cat test/.influxdb3/core/.token - -# 3. Configure .env.local with the token -echo "INFLUX_TOKEN=$(cat test/.influxdb3/core/.token)" >> .env.local - -# 4. Install dependencies and start -yarn -yarn dev -p 5200 - -# 5. Test the API -curl http://localhost:5200/api/devices -``` - -### Running Tests - -```bash -# Ensure InfluxDB 3 is running -docker compose up -d influxdb3-core - -# Run test suite -yarn test -``` - -## Code Patterns - -Use SQL or InfluxQL to query data. - -### SQL Queries (InfluxDB 3) - -Query InfluxDB 3 using SQL: - -```javascript -// Query devices -const sql = ` - SELECT time, deviceId, key - FROM deviceauth - WHERE deviceId = '${escapeString(deviceId)}' - ORDER BY time DESC - LIMIT 1 -` -const rows = await query(sql, database) -``` - -### Writing Data with Points - -```javascript -import { Point } from '@influxdata/influxdb3-client' - -const point = Point.measurement('deviceauth') - .setTag('deviceId', deviceId) - .setStringField('key', deviceKey) - .setStringField('token', deviceToken) - -await write(point.toLineProtocol(), database) -``` - -### Client Lifecycle - -Always close clients after use: - -```javascript -const client = createClient() -try { - // Use client... -} finally { - await client.close() -} -``` - -## Common Tasks - -### Adding a New Endpoint - -1. Create file in `pages/api/` following Next.js conventions -2. Import helpers from `lib/influxdb.js` -3. Add input validation (see `DEVICE_ID_PATTERN` example) -4. Add tests in `__tests__/` - -### Modifying Database Schema - -Data is stored as time-series measurements: -- `deviceauth` - Device registration (tags: deviceId; fields: key, token) -- Custom measurements for telemetry - -### Debugging - -```bash -# Check InfluxDB 3 logs -docker logs influxdb3-core - -# Query directly with curl -curl -X POST "http://localhost:8181/api/v3/query_sql" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"db": "iot_center", "q": "SELECT * FROM deviceauth"}' -``` - -## Style Guidelines +## Documentation -- Use ES modules (`import`/`export`) -- Validate all user input before database operations -- Never expose tokens in API responses -- Use descriptive error messages -- Follow existing patterns in the codebase +| Topic | Location | +|-------|----------| +| System architecture | [docs/architecture.md](docs/architecture.md) | +| Development setup | [docs/development.md](docs/development.md) | +| API endpoints | [docs/api-reference.md](docs/api-reference.md) | +| Code patterns | [docs/code-patterns.md](docs/code-patterns.md) | +| Contributing | [docs/contributing.md](docs/contributing.md) | -## Related Resources +## Other Resources -- [InfluxDB 3 Core Documentation](https://docs.influxdata.com/influxdb3/core/) -- [influxdb3-javascript Client](https://github.com/InfluxCommunity/influxdb3-js) -- [IoT Starter Tutorial](https://docs.influxdata.com/influxdb/v2/api-guide/tutorials/nodejs/) -- [iot-api-ui Frontend](https://github.com/influxdata/iot-api-ui) +- [CLAUDE.md](CLAUDE.md) - For Claude with MCP +- [README.md](README.md) - User-facing documentation +- [.github/INSTRUCTIONS.md](.github/INSTRUCTIONS.md) - Navigation guide diff --git a/CLAUDE.md b/CLAUDE.md index 2c3461c..2ac573d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,100 +1,38 @@ # IoT API JS - Claude Instructions -> **For Claude with MCP** -> -> This is a Next.js REST API server demonstrating InfluxDB 3 integration for IoT device data. -> -> **Instruction resources**: -> - [AGENTS.md](AGENTS.md) - For general AI assistants -> - [.github/INSTRUCTIONS.md](.github/INSTRUCTIONS.md) - Navigation guide -> - [.claude/](.claude/) - Claude configuration (settings, skills) +Next.js REST API demonstrating InfluxDB 3 Core integration for IoT device data. -## Project Overview - -This sample application demonstrates how to build an IoT data collection API using: -- **Next.js** - React framework for the API server -- **InfluxDB 3 Core** - Time-series database -- **@influxdata/influxdb3-client** - Official JavaScript client - -## Quick Commands +## Quick Reference | Task | Command | |------|---------| -| Install dependencies | `yarn` | -| Start dev server | `yarn dev -p 5200` | +| Install | `yarn` | +| Dev server | `yarn dev -p 5200` | | Run tests | `yarn test` | -| Start InfluxDB 3 | `docker compose up -d influxdb3-core` | - -## Project Structure - -``` -iot-api-js/ -├── lib/ -│ └── influxdb.js # InfluxDB client helpers -├── pages/api/ -│ ├── devices/ # Device CRUD endpoints -│ │ ├── create.js # POST /api/devices/create -│ │ ├── _devices.js # Shared device queries -│ │ └── [[...deviceParams]].js # GET/DELETE /api/devices -│ └── measurements/ # Telemetry endpoints -│ └── index.js # GET /api/measurements -├── __tests__/ # API tests -├── compose.yaml # InfluxDB 3 Core container -└── .env.development # Default config (committed) -``` - -## Environment Configuration - -Copy `.env.development` values to `.env.local` and customize: - -```bash -INFLUX_HOST=http://localhost:8181 -INFLUX_TOKEN=your-token -INFLUX_DATABASE=iot_center -INFLUX_DATABASE_AUTH=iot_center_devices -``` - -## Testing - -Run the test suite against a local InfluxDB 3 Core instance: - -```bash -# Start InfluxDB 3 Core -docker compose up -d influxdb3-core +| Start InfluxDB | `docker compose up -d influxdb3-core` | -# Run tests -yarn test -``` +## Key Files -See `.claude/skills/run-tests/SKILL.md` for detailed testing workflow. - -## API Endpoints - -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/api/devices` | GET | List all devices | -| `/api/devices/:id` | GET | Get device by ID | -| `/api/devices/create` | POST | Create new device | -| `/api/devices/:id` | DELETE | Delete device | -| `/api/measurements` | GET | Query device telemetry | - -## Key Patterns - -### InfluxDB 3 Client Usage - -```javascript -import { createClient, query, write } from '../../../lib/influxdb' - -// Query with SQL -const results = await query('SELECT * FROM deviceauth', database) - -// Write line protocol -await write('measurement,tag=value field=1', database) -``` - -### Application-Level Tokens - -This app uses application-managed tokens stored in the database. Tokens are: -- Generated via `generateDeviceToken()` in `lib/influxdb.js` -- Stored in the `deviceauth` measurement -- Never exposed via public API responses +| File | Purpose | +|------|---------| +| `lib/influxdb.js` | Client factory and query/write helpers | +| `pages/api/devices/create.js` | Device registration | +| `pages/api/devices/_devices.js` | Shared device queries | +| `pages/api/devices/[[...deviceParams]].js` | Device CRUD + measurements | + +## Documentation + +| Topic | Location | +|-------|----------| +| System architecture | [docs/architecture.md](docs/architecture.md) | +| Development setup | [docs/development.md](docs/development.md) | +| API endpoints | [docs/api-reference.md](docs/api-reference.md) | +| Code patterns | [docs/code-patterns.md](docs/code-patterns.md) | +| Contributing | [docs/contributing.md](docs/contributing.md) | +| Testing workflow | [.claude/skills/run-tests/SKILL.md](.claude/skills/run-tests/SKILL.md) | + +## Other Resources + +- [AGENTS.md](AGENTS.md) - Guide for general AI assistants +- [.github/INSTRUCTIONS.md](.github/INSTRUCTIONS.md) - Navigation guide +- [.claude/](.claude/) - Claude Code settings and skills diff --git a/docs/api-reference.md b/docs/api-reference.md new file mode 100644 index 0000000..8669793 --- /dev/null +++ b/docs/api-reference.md @@ -0,0 +1,115 @@ +# API Reference + +## Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/api/devices` | GET | List all devices | +| `/api/devices/:id` | GET | Get device by ID | +| `/api/devices/create` | POST | Register a new device | +| `/api/devices/:id/measurements` | POST | Query device telemetry | + +## Device Endpoints + +### List Devices + +```http +GET /api/devices +``` + +**Response:** `200 OK` +```json +[ + { "deviceId": "sensor-001", "key": "device_sensor-001_1234567890", "updatedAt": "2024-01-01T00:00:00Z" }, + { "deviceId": "sensor-002", "key": "device_sensor-002_1234567891", "updatedAt": "2024-01-01T00:00:01Z" } +] +``` + +### Get Device + +```http +GET /api/devices/:deviceId +``` + +**Response:** `200 OK` +```json +[ + { "deviceId": "sensor-001", "key": "device_sensor-001_1234567890", "updatedAt": "2024-01-01T00:00:00Z" } +] +``` + +### Create Device + +```http +POST /api/devices/create +Content-Type: application/json + +{ "deviceId": "sensor-001" } +``` + +**Validation:** +- `deviceId` required, 1-64 characters +- Alphanumeric, hyphens, underscores only +- Pattern: `^[a-zA-Z0-9_-]{1,64}$` + +**Response:** `200 OK` +```json +{ + "deviceId": "sensor-001", + "key": "device_sensor-001_1234567890", + "token": "iot_abc123...", + "database": "iot_center", + "host": "http://localhost:8181", + "message": "Device registered successfully." +} +``` + +**Errors:** +- `400` - Invalid or missing deviceId +- `500` - Device already exists (should be 409) + +## Measurement Endpoints + +### Query Measurements + +```http +POST /api/devices/:deviceId/measurements +Content-Type: application/json + +{ "query": "SELECT * FROM home WHERE time >= now() - INTERVAL '1 hour' ORDER BY time DESC" } +``` + +**Query Validation:** +- Must be a SELECT statement +- Max 2000 characters +- Blocked: DROP, DELETE, UPDATE, INSERT, ALTER, CREATE, TRUNCATE, GRANT, REVOKE, EXEC, EXECUTE, CALL +- No multi-statement queries (`;` followed by another statement) + +**Response:** `200 OK` with `Content-Type: application/csv` +```csv +time,room,temp,humidity +2024-01-01T00:00:00Z,Kitchen,22.5,45.0 +2024-01-01T00:01:00Z,Kitchen,22.6,44.8 +``` + +**Errors:** +- `400` - Missing or invalid query +- `405` - Method not allowed (use POST) + +## Error Responses + +All errors return JSON: + +```json +{ + "error": "Error message", + "hint": "Optional helpful suggestion" +} +``` + +## Security Notes + +- Device tokens are **never** returned in GET responses +- Tokens are only provided once during device creation +- All SQL queries are validated before execution +- DeviceId format is strictly validated to prevent injection diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..4f06848 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,65 @@ +# Architecture + +## System Overview + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Frontend UI │────▶│ Next.js API │────▶│ InfluxDB 3 │ +│ (iot-api-ui) │ │ (this repo) │ │ Core │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +This sample application demonstrates building REST APIs with InfluxDB 3 Core for IoT data collection. + +## Project Structure + +``` +iot-api-js/ +├── lib/ +│ └── influxdb.js # InfluxDB client factory and helpers +├── pages/api/ +│ ├── devices/ +│ │ ├── create.js # POST /api/devices/create +│ │ ├── _devices.js # Shared device query logic +│ │ └── [[...deviceParams]].js # GET /api/devices, POST measurements +│ └── measurements/ +│ └── index.js # Shared query function (not a route) +├── __tests__/ +│ └── api.test.js # API integration tests +├── docs/ # Documentation +├── .claude/ # Claude Code configuration +│ ├── settings.json +│ └── skills/ +├── compose.yaml # InfluxDB 3 Core container +├── .env.development # Default config (committed) +└── .env.local # Local overrides (gitignored) +``` + +## Databases + +The application uses two InfluxDB databases: + +| Database | Purpose | Measurements | +|----------|---------|--------------| +| `iot_center` | Device telemetry data | Custom per device | +| `iot_center_devices` | Device registration | `deviceauth` | + +### Device Auth Schema + +The `deviceauth` measurement stores device credentials: + +| Field | Type | Description | +|-------|------|-------------| +| `time` | timestamp | Registration time | +| `deviceId` | tag | Unique device identifier | +| `key` | field (string) | Device key | +| `token` | field (string) | Application-level auth token | + +## Technology Stack + +| Component | Technology | +|-----------|------------| +| API Framework | Next.js 16 (Pages Router) | +| Database | InfluxDB 3 Core | +| Client Library | @influxdata/influxdb3-client | +| Testing | Jest with mocks | diff --git a/docs/code-patterns.md b/docs/code-patterns.md new file mode 100644 index 0000000..d884137 --- /dev/null +++ b/docs/code-patterns.md @@ -0,0 +1,134 @@ +# Code Patterns + +## InfluxDB 3 Client + +### Client Factory + +Always use the helper functions from `lib/influxdb.js`: + +```javascript +import { query, write, createClient, config } from '../../../lib/influxdb' +``` + +### Querying with SQL + +```javascript +const sql = ` + SELECT time, deviceId, key + FROM deviceauth + WHERE deviceId = '${escapeString(deviceId)}' + ORDER BY time DESC + LIMIT 1 +` +const rows = await query(sql, config.databaseAuth) +``` + +### Writing Data with Points + +```javascript +import { Point } from '@influxdata/influxdb3-client' + +const point = Point.measurement('deviceauth') + .setTag('deviceId', deviceId) + .setStringField('key', deviceKey) + .setStringField('token', deviceToken) + +await write(point.toLineProtocol(), config.databaseAuth) +``` + +### Client Lifecycle + +The helpers manage client lifecycle automatically. If using `createClient()` directly: + +```javascript +const client = createClient() +try { + // Use client... +} finally { + await client.close() +} +``` + +## Input Validation + +### DeviceId Pattern + +```javascript +const DEVICE_ID_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/ + +if (!DEVICE_ID_PATTERN.test(deviceId)) { + return res.status(400).json({ + error: 'Invalid deviceId format', + hint: 'deviceId must be 1-64 characters, alphanumeric with hyphens and underscores only', + }) +} +``` + +### SQL Query Validation + +```javascript +const BLOCKED_PATTERNS = [ + /\b(DROP|DELETE|UPDATE|INSERT|ALTER|CREATE|TRUNCATE|GRANT|REVOKE)\b/i, + /\b(EXEC|EXECUTE|CALL)\b/i, + /;\s*\w/i, // Multiple statements +] + +function validateQuery(query) { + if (query.length > 2000) return { valid: false, error: 'Query too long' } + if (!query.trim().toUpperCase().startsWith('SELECT')) { + return { valid: false, error: 'Only SELECT queries allowed' } + } + for (const pattern of BLOCKED_PATTERNS) { + if (pattern.test(query)) return { valid: false, error: 'Blocked operation' } + } + return { valid: true } +} +``` + +## API Handler Pattern + +```javascript +export default async function handler(req, res) { + if (req.method !== 'POST') { + return res.status(405).json({ error: 'Method not allowed' }) + } + + try { + const body = typeof req.body === 'string' ? JSON.parse(req.body) : req.body + // Validate input... + // Process request... + res.status(200).json(result) + } catch (err) { + console.error('Handler error:', err) + res.status(500).json({ error: `Operation failed: ${err.message}` }) + } +} +``` + +## Security Patterns + +### Token Protection + +Never expose tokens in API responses: + +```javascript +// In _devices.js +export async function getDevices(deviceId, options = {}) { + const { includeToken = false } = options // Default: no token + + // Only include token field for internal verification + const tokenField = includeToken ? ', token' : '' + // ... +} +``` + +### SQL Escaping + +```javascript +function escapeString(str) { + if (typeof str !== 'string') return str + return str.replace(/'/g, "''") +} +``` + +Note: Prefer validating input format over escaping when possible. diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..be4d7b1 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,83 @@ +# Contributing + +## Style Guidelines + +- Use ES modules (`import`/`export`) +- Validate all user input before database operations +- Never expose tokens in API responses +- Use descriptive error messages with hints +- Follow existing patterns in the codebase + +## Adding a New Endpoint + +1. Create file in `pages/api/` following Next.js conventions +2. Import helpers from `lib/influxdb.js` +3. Add input validation (see `DEVICE_ID_PATTERN` example) +4. Add tests in `__tests__/` +5. Update [API Reference](api-reference.md) + +### Example Structure + +```javascript +import { query, config } from '../../../lib/influxdb' + +export default async function handler(req, res) { + if (req.method !== 'GET') { + return res.status(405).json({ error: 'Method not allowed' }) + } + + try { + // Validate input + // Execute query + // Return response + res.status(200).json(result) + } catch (err) { + console.error('Error:', err) + res.status(500).json({ error: err.message }) + } +} +``` + +## Testing + +### Writing Tests + +Tests use Jest with mocked InfluxDB client: + +```javascript +jest.mock('../lib/influxdb', () => ({ + query: jest.fn(), + write: jest.fn(), + config: { /* test config */ }, +})) + +test('creates device', async () => { + query.mockResolvedValue([]) + write.mockResolvedValue(undefined) + + const { req, res } = createMocks({ + method: 'POST', + body: { deviceId: 'test-device' }, + }) + + await handler(req, res) + + expect(res._getStatusCode()).toBe(200) +}) +``` + +### Test Coverage + +Ensure tests cover: +- Happy path +- Input validation (invalid format, missing fields) +- Security (injection attempts, blocked operations) +- Error handling +- HTTP method validation + +## Related Resources + +- [InfluxDB 3 Core Documentation](https://docs.influxdata.com/influxdb3/core/) +- [influxdb3-js Client](https://github.com/InfluxCommunity/influxdb3-js) +- [IoT Starter Tutorial](https://docs.influxdata.com/influxdb/v2/api-guide/tutorials/nodejs/) +- [iot-api-ui Frontend](https://github.com/influxdata/iot-api-ui) diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..adf1efa --- /dev/null +++ b/docs/development.md @@ -0,0 +1,82 @@ +# Development Guide + +## Prerequisites + +- Node.js 18+ and Yarn +- Docker (for InfluxDB 3 Core) + +## Quick Start + +```bash +# 1. Start InfluxDB 3 Core +docker compose up -d influxdb3-core + +# 2. Get the generated token +cat test/.influxdb3/core/.token + +# 3. Configure environment +echo "INFLUX_TOKEN=$(cat test/.influxdb3/core/.token)" >> .env.local + +# 4. Install and run +yarn +yarn dev -p 5200 + +# 5. Test the API +curl http://localhost:5200/api/devices +``` + +## Environment Variables + +Copy `.env.development` to `.env.local` and customize: + +```bash +INFLUX_HOST=http://localhost:8181 +INFLUX_TOKEN=your-token +INFLUX_DATABASE=iot_center +INFLUX_DATABASE_AUTH=iot_center_devices +``` + +## Running Tests + +```bash +# Run mocked unit tests (no database required) +yarn test + +# Run with verbose output +yarn test --verbose +``` + +For integration testing with a live database, see [.claude/skills/run-tests/SKILL.md](../.claude/skills/run-tests/SKILL.md). + +## Debugging + +### Check InfluxDB 3 Logs + +```bash +docker logs influxdb3-core +``` + +### Query Database Directly + +```bash +TOKEN=$(cat test/.influxdb3/core/.token) + +curl -X POST "http://localhost:8181/api/v3/query_sql" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"db": "iot_center", "q": "SELECT * FROM deviceauth"}' +``` + +### Health Check + +```bash +curl http://localhost:8181/health +``` + +## Common Issues + +| Issue | Solution | +|-------|----------| +| Port 8181 in use | `docker compose down` then restart | +| 401 Unauthorized | Verify token in `.env.local` matches `test/.influxdb3/core/.token` | +| Database not found | Create databases via InfluxDB API (see skill docs) | From 70b321b8c47bf100fe37633cc5dcb3eeb9a92e46 Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Fri, 27 Feb 2026 00:33:24 -0600 Subject: [PATCH 18/19] docs: add migration design for InfluxDB 3 Core with caching, plugins, and Enterprise Covers architecture, LVC/DVC caching, Processing Engine WAL flush plugin, optional Enterprise support via separate code paths, data flow, file change summary, and testing strategy. --- ...6-02-27-influxdb3-core-migration-design.md | 357 ++++++++++++++++++ 1 file changed, 357 insertions(+) create mode 100644 docs/plans/2026-02-27-influxdb3-core-migration-design.md diff --git a/docs/plans/2026-02-27-influxdb3-core-migration-design.md b/docs/plans/2026-02-27-influxdb3-core-migration-design.md new file mode 100644 index 0000000..5647512 --- /dev/null +++ b/docs/plans/2026-02-27-influxdb3-core-migration-design.md @@ -0,0 +1,357 @@ +# InfluxDB 3 Core Migration Design + +Standalone demo app for InfluxDB 3 Core (with optional Enterprise support), +used as a tutorial reference on the InfluxData documentation site. + +## Architecture + +``` +┌─────────────────┐ ┌──────────────────────────────┐ ┌──────────────────────┐ +│ Frontend UI │────>│ Next.js API (this repo) │────>│ InfluxDB 3 Core │ +│ (iot-api-ui) │ │ │ │ or Enterprise │ +└─────────────────┘ │ pages/api/ │ │ │ + │ ├── devices/create.js │ │ Databases: │ + │ ├── devices/[[...params]].js│ │ ├── iot_center │ + │ └── devices/:id/status [NEW]│ │ └── iot_center_devices + │ │ │ │ + │ lib/ │ │ Caches: │ + │ ├── influxdb.js (existing) │ │ ├── LVC: deviceStatus + │ └── enterprise.js [NEW] │ │ └── DVC: deviceList │ + └──────────────────────────────┘ │ │ + │ Plugins: │ + │ └── sensor_guard.py │ + └──────────────────────┘ +``` + +### What stays the same + +- Two databases: `iot_center` (telemetry), `iot_center_devices` (device auth) +- `lib/influxdb.js` as the client factory with `query()`, `write()`, `Point` +- Existing API routes for device CRUD and measurements +- Pages Router, Jest tests, Docker Compose + +### What's new + +- LVC on `iot_center` for fast device status queries +- DVC on `iot_center_devices` for fast device enumeration +- Processing Engine plugin (Python) for data validation on write +- `lib/enterprise.js` for Enterprise-only features +- New API path for device status via LVC +- Enterprise service in compose.yaml (Docker profile-gated) + +### Technology + +| Component | Technology | +|-----------|------------| +| API Framework | Next.js 16 (Pages Router) | +| Database | InfluxDB 3 Core / Enterprise | +| Client Library | @influxdata/influxdb3-client v2.x | +| Testing | Jest with mocks | +| Plugin Runtime | Python (Processing Engine) | + +## Caching + +### Last Value Cache (LVC) — Device Status + +Caches the most recent sensor reading per device for sub-10ms dashboard queries. + +**Setup (manual CLI):** + +```sh +influxdb3 create last_cache \ + --database iot_center \ + --table sensor_data \ + --key-columns deviceId \ + --value-columns temperature,humidity,pressure \ + --count 1 \ + --ttl 30mins \ + --token $TOKEN \ + deviceStatus +``` + +**Query pattern:** + +```sql +SELECT * FROM last_cache('sensor_data', 'deviceStatus') +WHERE deviceId = '...' +``` + +### Distinct Value Cache (DVC) — Device Listing + +Caches distinct `deviceId` values for sub-30ms device enumeration. + +**Setup (manual CLI):** + +```sh +influxdb3 create distinct_cache \ + --database iot_center_devices \ + --table deviceauth \ + --columns deviceId \ + --max-cardinality 10000 \ + --max-age 24h \ + --token $TOKEN \ + deviceList +``` + +**Query pattern:** + +```sql +SELECT * FROM distinct_cache('deviceauth', 'deviceList') +``` + +### Graceful degradation + +Both cache queries catch errors and fall back to regular SQL. The app works +without caches configured -- they are a performance optimization, not a +requirement. Tutorial flow: "it works without caches, now let's make it fast." + +## Processing Engine Plugin + +### Trigger type: WAL flush + +A WAL flush trigger fires when sensor data is written. This is the most natural +fit for an IoT pipeline -- validate and enrich data inline without external +services. + +### Plugin: `sensor_guard.py` + +Validates incoming sensor data on write: + +- Receives batches of written rows from the `sensor_data` table +- Validates ranges (temperature -50 to 150, humidity 0 to 100) +- Writes out-of-range readings to a `sensor_alerts` table with original values + plus an `alert_type` tag +- Logs warnings for rejected data + +```python +def process_writes(influxdb3_local, table_batches, args=None): + for table_batch in table_batches: + if table_batch["table_name"] != "sensor_data": + continue + for row in table_batch["rows"]: + device_id = row.get("deviceId", "unknown") + temp = row.get("temperature") + humidity = row.get("humidity") + + alerts = [] + if temp is not None and not (-50 <= temp <= 150): + alerts.append("temperature_out_of_range") + if humidity is not None and not (0 <= humidity <= 100): + alerts.append("humidity_out_of_range") + + for alert_type in alerts: + line = ( + f'sensor_alerts,deviceId={device_id},' + f'alert_type={alert_type} ' + f'temperature={temp},humidity={humidity}' + ) + influxdb3_local.write(line) + influxdb3_local.info( + f"Alert: {alert_type} for device {device_id}" + ) +``` + +**Trigger setup (manual CLI):** + +```sh +influxdb3 create trigger \ + --database iot_center \ + --plugin sensor_guard.py \ + --trigger-spec "table:sensor_data" \ + --token $TOKEN \ + sensorGuardTrigger +``` + +### Why not other trigger types? + +- **Scheduled**: Could work for deadman alerting, but adds complexity without + teaching much beyond what WAL flush shows. Possible follow-up. +- **HTTP request**: The app already has Next.js API routes. A competing HTTP + endpoint inside InfluxDB would confuse the tutorial. + +## Enterprise Support + +### Configuration + +Environment variable `INFLUX_EDITION` controls which edition the app targets. +Defaults to `core`. + +### `lib/enterprise.js` + +Provides three Enterprise-specific features: + +1. **Fine-grained database tokens**: Enterprise supports read/write tokens + scoped to specific databases. Instead of application-level tokens stored in + `deviceauth`, Enterprise issues real database tokens per device. + +2. **Table-level retention**: Enterprise supports per-table retention periods. + Exposes a helper to set retention on `sensor_data` independently from the + database default. + +3. **Historical queries**: Core limits queries to recent data (~72 hours + uncompacted). Enterprise compacts data and enables historical range queries. + Provides a helper that removes the time-range guardrails. + +### Integration pattern + +Existing routes gain small conditional branches: + +```javascript +if (config.edition === 'enterprise') { + const { createDatabaseToken } = await import('../../../lib/enterprise.js') + // Enterprise token creation +} else { + // Core: generate app-level token +} +``` + +### Docker Compose + +Enterprise service behind a profile (does not start by default): + +```yaml +influxdb3-enterprise: + container_name: influxdb3-enterprise + image: influxdb:3-enterprise + profiles: ["enterprise"] + ports: + - 8181:8181 +``` + +Users run `docker compose --profile enterprise up -d` to use Enterprise. + +### What Enterprise does NOT change + +- Same database names, table schemas, API routes +- Same LVC and DVC (both work on Enterprise) +- Same Processing Engine plugins (same API) +- Tutorial flow: "build with Core first, then here's what Enterprise adds" + +## Data Flow + +### Route Map + +| Method | Route | Behavior | New? | +|--------|-------|----------|------| +| POST | `/api/devices/create` | Register device, generate token | Enterprise: database tokens | +| GET | `/api/devices` | List all devices | DVC fast path | +| GET | `/api/devices/:deviceId` | Get specific device | No change | +| GET | `/api/devices/:deviceId/status` | Latest readings | NEW (LVC) | +| POST | `/api/devices/:deviceId/measurements` | Query sensor data | Enterprise: no time limit | + +### Write flow (with Processing Engine) + +``` +Client writes sensor data + | + v +POST /api/devices/:deviceId/measurements + | + v +lib/influxdb.js write() --> InfluxDB 3 + | + v +WAL flush triggers sensor_guard.py + | + |-- Valid data --> stored in sensor_data + | LVC updated automatically + | + +-- Out-of-range --> alert written to sensor_alerts +``` + +### Read flow (with caches) + +``` +GET /api/devices + | + v +_devices.js --> distinct_cache('deviceauth', 'deviceList') + | <30ms response + v +Return device list + + +GET /api/devices/:deviceId/status + | + v +[[...deviceParams]].js --> last_cache('sensor_data', 'deviceStatus') + | <10ms response + v +Return latest readings +``` + +## File Changes + +### New files + +| File | Purpose | +|------|---------| +| `lib/enterprise.js` | Enterprise-only helpers | +| `plugins/sensor_guard.py` | WAL flush plugin for sensor validation | + +### Modified files + +| File | Changes | +|------|---------| +| `lib/influxdb.js` | Add `config.edition` getter | +| `pages/api/devices/[[...deviceParams]].js` | Add `status` path, Enterprise conditional | +| `pages/api/devices/_devices.js` | DVC fast path with fallback | +| `pages/api/devices/create.js` | Enterprise conditional for database tokens | +| `compose.yaml` | Add Enterprise service behind profile | +| `.env.development` | Add `INFLUX_EDITION=core` | +| `__tests__/api.test.js` | Tests for status endpoint, cache fallback | +| `docs/architecture.md` | Update with caching, plugins, Enterprise | +| `README.md` | Setup steps for caches, plugin, Enterprise | + +### Unchanged files + +| File | Why | +|------|-----| +| `pages/api/measurements/index.js` | Shared query helper, no changes | +| `package.json` | No new dependencies | +| `jest.config.js` | Test config stays the same | + +### Scope + +- 2 new files +- ~8 modified files +- 0 new npm dependencies +- 1 Python file (plugin, not a Node dependency) + +## Testing + +### Jest tests (mock-based) + +**Status endpoint (LVC):** +- Returns latest reading from LVC +- Returns null when no readings exist +- Falls back to regular query on cache error +- Returns 405 for non-GET methods + +**Device listing (DVC):** +- Uses DVC when available +- Falls back to full table scan on cache error +- Results match same shape regardless of code path + +**Enterprise conditionals:** +- `INFLUX_EDITION=enterprise` triggers database token flow +- `INFLUX_EDITION=core` (default) uses app-level token logic + +### Plugin testing (manual) + +The Processing Engine plugin runs inside InfluxDB, not in Node.js. +Manual verification documented in README: + +1. Write an out-of-range sensor value +2. Query `sensor_alerts` to confirm the alert appeared + +### Error handling + +**Cache unavailable:** Catch, log warning, fall back to standard SQL. + +**Plugin failures:** Logged server-side by Processing Engine. Do not block +writes. No app-side handling needed. + +**Enterprise on Core:** If `INFLUX_EDITION=enterprise` but server is Core, +Enterprise API calls return clear HTTP errors (401/404). App passes these +through with context message. From 492ae7e018231e4c8295349a5cc88b8371f6f7c5 Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Fri, 27 Feb 2026 00:36:29 -0600 Subject: [PATCH 19/19] docs: add implementation plan for caching, plugins, and Enterprise Eight bite-sized tasks with exact file paths, code, test steps, and commit points. Covers config, DVC, LVC, plugin, Enterprise module, Docker Compose, and documentation updates. --- ...026-02-27-influxdb3-core-migration-plan.md | 893 ++++++++++++++++++ 1 file changed, 893 insertions(+) create mode 100644 docs/plans/2026-02-27-influxdb3-core-migration-plan.md diff --git a/docs/plans/2026-02-27-influxdb3-core-migration-plan.md b/docs/plans/2026-02-27-influxdb3-core-migration-plan.md new file mode 100644 index 0000000..61d2f6f --- /dev/null +++ b/docs/plans/2026-02-27-influxdb3-core-migration-plan.md @@ -0,0 +1,893 @@ +# InfluxDB 3 Core Migration Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Add LVC/DVC caching, a Processing Engine plugin, and optional Enterprise support to the existing InfluxDB 3 Core demo app. + +**Architecture:** Incremental enhancement of the existing Next.js Pages Router app. New features are additive — a new `/status` API path, DVC fast-path in device listing, a Python plugin file, and a separate Enterprise module. Cache queries degrade gracefully to standard SQL. + +**Tech Stack:** Next.js 16 (Pages Router), @influxdata/influxdb3-client v2.x, Jest, Python (Processing Engine plugin) + +--- + +### Task 1: Add `edition` to config + +Add the `INFLUX_EDITION` getter to the shared config object so all modules can check which edition is active. + +**Files:** +- Modify: `lib/influxdb.js:23-36` +- Test: `__tests__/api.test.js:13-30` (update mock) + +**Step 1: Update the mock in the test file** + +In `__tests__/api.test.js`, add `edition` to the mocked config object: + +```javascript +jest.mock('../lib/influxdb', () => ({ + query: jest.fn(), + write: jest.fn(), + config: { + host: 'http://localhost:8181', + token: 'test-token', + database: 'iot_center', + databaseAuth: 'iot_center_devices', + edition: 'core', + }, + generateDeviceToken: jest.fn(() => 'iot_mock_token_12345'), + Point: { + measurement: jest.fn(() => ({ + setTag: jest.fn().mockReturnThis(), + setStringField: jest.fn().mockReturnThis(), + toLineProtocol: jest.fn(() => 'deviceauth,deviceId=test-device key="test-key",token="test-token"'), + })), + }, +})) +``` + +**Step 2: Run tests to verify nothing broke** + +Run: `yarn test` +Expected: All existing tests pass (the new `edition` field is additive) + +**Step 3: Add the `edition` getter to `lib/influxdb.js`** + +Add after the `databaseAuth` getter at line 35: + +```javascript + get edition() { + return process.env.INFLUX_EDITION || 'core' + }, +``` + +The full config object becomes: + +```javascript +export const config = { + get host() { + return process.env.INFLUX_HOST + }, + get token() { + return process.env.INFLUX_TOKEN + }, + get database() { + return process.env.INFLUX_DATABASE + }, + get databaseAuth() { + return process.env.INFLUX_DATABASE_AUTH + }, + get edition() { + return process.env.INFLUX_EDITION || 'core' + }, +} +``` + +**Step 4: Run tests to verify** + +Run: `yarn test` +Expected: All tests pass + +**Step 5: Commit** + +```bash +git add lib/influxdb.js __tests__/api.test.js +git commit -m "feat: add edition getter to influxdb config" +``` + +--- + +### Task 2: DVC fast path for device listing + +Add distinct value cache query to `getDevices()` with graceful fallback. + +**Files:** +- Modify: `pages/api/devices/_devices.js` +- Test: `__tests__/api.test.js` + +**Step 1: Write the failing test for DVC fast path** + +Add to `__tests__/api.test.js` after the existing `GET /api/devices` describe block: + +```javascript +describe('GET /api/devices (DVC fast path)', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + test('falls back to full query when DVC query fails', async () => { + // First call: DVC query fails (cache not configured) + // Second call: fallback full query succeeds + query + .mockRejectedValueOnce(new Error('cache not found')) + .mockResolvedValueOnce([ + { deviceId: 'device-1', key: 'key-1', time: new Date() }, + ]) + + const { req, res } = createMocks({ + method: 'GET', + query: { deviceParams: [] }, + }) + + await devicesHandler(req, res) + + expect(res._getStatusCode()).toBe(200) + const data = JSON.parse(res._getData()) + expect(data).toHaveLength(1) + expect(data[0].deviceId).toBe('device-1') + // query was called twice: DVC attempt + fallback + expect(query).toHaveBeenCalledTimes(2) + }) +}) +``` + +**Step 2: Run test to verify it fails** + +Run: `yarn test` +Expected: FAIL — the current `getDevices()` calls `query` only once, so `toHaveBeenCalledTimes(2)` fails. + +**Step 3: Implement DVC fast path in `_devices.js`** + +Replace the `else` branch (list all devices) in `getDevices()`. The full function becomes: + +```javascript +export async function getDevices(deviceId, options = {}) { + const { includeToken = false } = options + const database = config.databaseAuth + + let rows + + if (deviceId !== undefined) { + const tokenField = includeToken ? ', token' : '' + const sql = ` + SELECT time, deviceId, key${tokenField} + FROM deviceauth + WHERE deviceId = '${escapeString(deviceId)}' + ORDER BY time DESC + LIMIT 1 + ` + rows = await query(sql, database) + } else { + // Try DVC for fast device enumeration, fall back to full scan + rows = await queryDevicesDvc(database) + } + + const devices = {} + + for (const row of rows) { + const id = row.deviceId + if (!id) { + continue + } + + if (devices[id]) { + const existingTime = new Date(devices[id].updatedAt).getTime() + const rowTime = new Date(row.time).getTime() + if (rowTime <= existingTime) { + continue + } + } + + devices[id] = { + deviceId: id, + key: row.key, + updatedAt: row.time, + } + + if (includeToken && row.token) { + devices[id].token = row.token + } + } + + return devices +} + +/** + * Queries devices using the Distinct Value Cache for fast enumeration. + * Falls back to a full table scan if the DVC is not configured. + */ +async function queryDevicesDvc(database) { + try { + return await query( + "SELECT * FROM distinct_cache('deviceauth', 'deviceList')", + database + ) + } catch { + console.warn('DVC not available, falling back to full device query') + return await query( + 'SELECT time, deviceId, key FROM deviceauth ORDER BY time DESC', + database + ) + } +} +``` + +**Step 4: Run tests to verify** + +Run: `yarn test` +Expected: All tests pass, including the new DVC fallback test + +**Step 5: Commit** + +```bash +git add pages/api/devices/_devices.js __tests__/api.test.js +git commit -m "feat: add DVC fast path for device listing with fallback" +``` + +--- + +### Task 3: LVC device status endpoint + +Add `GET /api/devices/:deviceId/status` that queries the Last Value Cache. + +**Files:** +- Modify: `pages/api/devices/[[...deviceParams]].js` +- Test: `__tests__/api.test.js` + +**Step 1: Write the failing tests** + +Add to `__tests__/api.test.js`: + +```javascript +describe('GET /api/devices/:deviceId/status', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + test('returns latest reading from LVC', async () => { + query.mockResolvedValue([ + { deviceId: 'sensor-001', temperature: 22.5, humidity: 45.0, time: '2026-02-27T10:00:00Z' }, + ]) + + const { req, res } = createMocks({ + method: 'GET', + query: { deviceParams: ['sensor-001', 'status'] }, + }) + + await devicesHandler(req, res) + + expect(res._getStatusCode()).toBe(200) + const data = JSON.parse(res._getData()) + expect(data.deviceId).toBe('sensor-001') + expect(data.temperature).toBe(22.5) + }) + + test('returns null when no readings exist', async () => { + query.mockResolvedValue([]) + + const { req, res } = createMocks({ + method: 'GET', + query: { deviceParams: ['sensor-999', 'status'] }, + }) + + await devicesHandler(req, res) + + expect(res._getStatusCode()).toBe(200) + const data = JSON.parse(res._getData()) + expect(data).toBeNull() + }) + + test('falls back to regular query when LVC fails', async () => { + query + .mockRejectedValueOnce(new Error('cache not found')) + .mockResolvedValueOnce([ + { deviceId: 'sensor-001', temperature: 22.5, humidity: 45.0, time: '2026-02-27T10:00:00Z' }, + ]) + + const { req, res } = createMocks({ + method: 'GET', + query: { deviceParams: ['sensor-001', 'status'] }, + }) + + await devicesHandler(req, res) + + expect(res._getStatusCode()).toBe(200) + expect(query).toHaveBeenCalledTimes(2) + }) + + test('rejects non-GET methods', async () => { + const { req, res } = createMocks({ + method: 'POST', + query: { deviceParams: ['sensor-001', 'status'] }, + }) + + await devicesHandler(req, res) + + expect(res._getStatusCode()).toBe(405) + }) +}) +``` + +**Step 2: Run tests to verify they fail** + +Run: `yarn test` +Expected: FAIL — `status` path is not handled yet, so requests fall through to the device listing handler. + +**Step 3: Add status handler to `[[...deviceParams]].js`** + +Add this block after the `deviceId` / `path` parsing (after line 64), before the measurements handler: + +```javascript + // Handle device status: GET /api/devices/:deviceId/status + if (Array.isArray(path) && path[0] === 'status') { + if (req.method !== 'GET') { + return res.status(405).json({ error: 'Method not allowed. Use GET for device status.' }) + } + const data = await getDeviceStatus(deviceId) + return res.status(200).json(data) + } +``` + +Add the imports at the top of the file. Update the existing import line: + +```javascript +import { query, config } from '../../../lib/influxdb' +``` + +Add the `getDeviceStatus` function at the bottom of the file (before the closing): + +```javascript +/** + * Gets the latest sensor readings for a device using the Last Value Cache. + * Falls back to a regular query if the LVC is not configured. + * + * @param {string} deviceId - The device to get status for + * @returns {Promise} Latest readings or null + */ +async function getDeviceStatus(deviceId) { + const escaped = deviceId.replace(/'/g, "''") + try { + const rows = await query( + `SELECT * FROM last_cache('sensor_data', 'deviceStatus') WHERE deviceId = '${escaped}'`, + config.database + ) + return rows[0] || null + } catch { + console.warn('LVC not available, falling back to regular query') + const rows = await query( + `SELECT * FROM sensor_data WHERE deviceId = '${escaped}' ORDER BY time DESC LIMIT 1`, + config.database + ) + return rows[0] || null + } +} +``` + +The full imports at the top of the file become: + +```javascript +import { getMeasurements } from '../measurements' +import { getDevices } from './_devices' +import { query, config } from '../../../lib/influxdb' +``` + +**Step 4: Run tests to verify** + +Run: `yarn test` +Expected: All tests pass + +**Step 5: Commit** + +```bash +git add pages/api/devices/[[...deviceParams]].js __tests__/api.test.js +git commit -m "feat: add device status endpoint with LVC and fallback" +``` + +--- + +### Task 4: Processing Engine plugin + +Create the `sensor_guard.py` WAL flush plugin. + +**Files:** +- Create: `plugins/sensor_guard.py` + +**Step 1: Create the plugins directory** + +```bash +mkdir -p plugins +``` + +**Step 2: Write the plugin** + +Create `plugins/sensor_guard.py`: + +```python +""" +Sensor Guard Plugin — validates incoming sensor data on WAL flush. + +Trigger type: WAL flush (table:sensor_data) +Database: iot_center + +Checks temperature and humidity ranges. Out-of-range readings are written +to the sensor_alerts table with the original values and an alert_type tag. + +Setup: + influxdb3 create trigger \ + --database iot_center \ + --plugin sensor_guard.py \ + --trigger-spec "table:sensor_data" \ + --token $TOKEN \ + sensorGuardTrigger +""" + +TEMP_MIN = -50 +TEMP_MAX = 150 +HUMIDITY_MIN = 0 +HUMIDITY_MAX = 100 + + +def process_writes(influxdb3_local, table_batches, args=None): + for table_batch in table_batches: + if table_batch["table_name"] != "sensor_data": + continue + + for row in table_batch["rows"]: + device_id = row.get("deviceId", "unknown") + temp = row.get("temperature") + humidity = row.get("humidity") + + alerts = [] + if temp is not None and not (TEMP_MIN <= temp <= TEMP_MAX): + alerts.append("temperature_out_of_range") + if humidity is not None and not (HUMIDITY_MIN <= humidity <= HUMIDITY_MAX): + alerts.append("humidity_out_of_range") + + for alert_type in alerts: + line = ( + f"sensor_alerts,deviceId={device_id}," + f"alert_type={alert_type} " + f"temperature={temp},humidity={humidity}" + ) + influxdb3_local.write(line) + influxdb3_local.info( + f"Alert: {alert_type} for device {device_id}" + ) +``` + +**Step 3: Commit** + +```bash +git add plugins/sensor_guard.py +git commit -m "feat: add sensor_guard Processing Engine plugin" +``` + +--- + +### Task 5: Enterprise module + +Create `lib/enterprise.js` with Enterprise-specific helpers. + +**Files:** +- Create: `lib/enterprise.js` +- Test: `__tests__/api.test.js` + +**Step 1: Write the failing test** + +Add to `__tests__/api.test.js`: + +```javascript +describe('Enterprise module', () => { + test('exports expected functions', async () => { + const enterprise = await import('../lib/enterprise') + expect(typeof enterprise.createDatabaseToken).toBe('function') + expect(typeof enterprise.setTableRetention).toBe('function') + expect(typeof enterprise.isEnterprise).toBe('function') + }) +}) +``` + +**Step 2: Run test to verify it fails** + +Run: `yarn test` +Expected: FAIL — `lib/enterprise` does not exist + +**Step 3: Create `lib/enterprise.js`** + +```javascript +import { config } from './influxdb' + +/** + * Checks if the app is configured for InfluxDB 3 Enterprise. + * @returns {boolean} + */ +export function isEnterprise() { + return config.edition === 'enterprise' +} + +/** + * Creates a fine-grained database token via the Enterprise API. + * Enterprise supports read/write tokens scoped to specific databases. + * + * @param {string} description - Token description + * @param {string} database - Database to scope the token to + * @param {string[]} permissions - Permissions array, e.g. ['read', 'write'] + * @returns {Promise<{id: string, token: string}>} + */ +export async function createDatabaseToken(description, database, permissions) { + const res = await fetch(`${config.host}/api/v3/configure/token`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${config.token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + description, + permissions: permissions.map((p) => ({ + resource_type: 'database', + resource_name: database, + action: p, + })), + }), + }) + + if (!res.ok) { + const body = await res.text() + throw new Error(`Failed to create database token: ${res.status} ${body}`) + } + + return res.json() +} + +/** + * Sets a retention period on a specific table (Enterprise only). + * Core only supports database-level retention. + * + * @param {string} database - Database name + * @param {string} table - Table name + * @param {string} retention - Retention period, e.g. '30d', '24h' + */ +export async function setTableRetention(database, table, retention) { + const res = await fetch( + `${config.host}/api/v3/configure/table/retention_period`, + { + method: 'POST', + headers: { + 'Authorization': `Bearer ${config.token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ db: database, table, retention_period: retention }), + } + ) + + if (!res.ok) { + const body = await res.text() + throw new Error(`Failed to set table retention: ${res.status} ${body}`) + } +} +``` + +**Step 4: Run tests to verify** + +Run: `yarn test` +Expected: All tests pass + +**Step 5: Commit** + +```bash +git add lib/enterprise.js __tests__/api.test.js +git commit -m "feat: add Enterprise module with token and retention helpers" +``` + +--- + +### Task 6: Enterprise conditional in device creation + +Wire the Enterprise database token flow into `create.js`. + +**Files:** +- Modify: `pages/api/devices/create.js` +- Test: `__tests__/api.test.js` + +**Step 1: Write the failing test** + +Add to `__tests__/api.test.js`: + +```javascript +describe('POST /api/devices/create (Enterprise)', () => { + const originalEdition = process.env.INFLUX_EDITION + + afterEach(() => { + process.env.INFLUX_EDITION = originalEdition || '' + jest.clearAllMocks() + jest.restoreAllMocks() + }) + + test('uses app-level token on Core (default)', async () => { + process.env.INFLUX_EDITION = 'core' + query.mockResolvedValue([]) + write.mockResolvedValue(undefined) + + const { req, res } = createMocks({ + method: 'POST', + body: { deviceId: 'core-device' }, + }) + + await createHandler(req, res) + + expect(res._getStatusCode()).toBe(200) + const data = JSON.parse(res._getData()) + // Core uses app-level token (starts with iot_) + expect(data.token).toMatch(/^iot_/) + }) +}) +``` + +**Step 2: Run test to verify it passes** + +Run: `yarn test` +Expected: PASS — this test documents existing Core behavior. It should already pass before any changes. + +**Step 3: Update `create.js` with Enterprise conditional** + +Modify the `createDevice` function in `pages/api/devices/create.js`. Add `config` to the import (it's already imported). Add the Enterprise branch: + +```javascript +async function createDevice(deviceId) { + const existingDevices = await getDevices(deviceId) + const existingDevice = Object.values(existingDevices)[0] + + if (existingDevice?.key) { + throw new Error('This device ID is already registered and has an authorization.') + } + + console.log(`createDevice: deviceId=${deviceId}`) + + if (config.edition === 'enterprise') { + // Enterprise: create a fine-grained database token via the API + const { createDatabaseToken } = await import('../../../lib/enterprise.js') + const tokenResult = await createDatabaseToken( + `Device token for ${deviceId}`, + config.database, + ['read', 'write'] + ) + + const point = Point.measurement('deviceauth') + .setTag('deviceId', deviceId) + .setStringField('key', tokenResult.id) + .setStringField('token', tokenResult.token) + + await write(point.toLineProtocol(), config.databaseAuth) + + console.log(`Device created (Enterprise): ${deviceId}`) + + return { + deviceId, + key: tokenResult.id, + token: tokenResult.token, + database: config.database, + host: config.host, + message: 'Device registered with Enterprise database token.', + } + } + + // Core: generate application-level token + const deviceToken = generateDeviceToken() + const deviceKey = `device_${deviceId}_${Date.now()}` + + const point = Point.measurement('deviceauth') + .setTag('deviceId', deviceId) + .setStringField('key', deviceKey) + .setStringField('token', deviceToken) + + await write(point.toLineProtocol(), config.databaseAuth) + + console.log(`Device created: ${deviceId}`) + + return { + deviceId, + key: deviceKey, + token: deviceToken, + database: config.database, + host: config.host, + message: 'Device registered successfully. Use the provided token for device authentication.', + } +} +``` + +**Step 4: Run tests to verify** + +Run: `yarn test` +Expected: All tests pass + +**Step 5: Commit** + +```bash +git add pages/api/devices/create.js __tests__/api.test.js +git commit -m "feat: add Enterprise database token flow to device creation" +``` + +--- + +### Task 7: Enterprise service in Docker Compose + +Add the Enterprise service behind a Docker Compose profile. + +**Files:** +- Modify: `compose.yaml` + +**Step 1: Add Enterprise service and secrets** + +Append to `compose.yaml` after the Core service, and add the Enterprise secret to the `secrets` section: + +Add to `secrets:` section: + +```yaml + influxdb3-enterprise-token: + file: test/.influxdb3/enterprise/.token +``` + +Add new service after the `influxdb3-core` service: + +```yaml + # ============================================================================ + # InfluxDB 3 Enterprise (optional) + # ============================================================================ + # Commercial edition with compaction, historical queries, and fine-grained + # tokens. Only starts when the 'enterprise' profile is active. + # + # USAGE: + # docker compose --profile enterprise up -d influxdb3-enterprise + # + # FIRST-TIME SETUP: + # 1. Create directories: + # mkdir -p test/.influxdb3/enterprise/data test/.influxdb3/enterprise/plugins + # + # 2. Generate token: + # openssl rand -hex 32 > test/.influxdb3/enterprise/.token + # chmod 600 test/.influxdb3/enterprise/.token + # + # 3. Start service: + # docker compose --profile enterprise up -d influxdb3-enterprise + # + # 4. Create databases (same as Core): + # TOKEN=$(cat test/.influxdb3/enterprise/.token) + # curl -X POST "http://localhost:8181/api/v3/configure/database" \ + # -H "Authorization: Bearer $TOKEN" \ + # -H "Content-Type: application/json" \ + # -d '{"db": "iot_center"}' + # curl -X POST "http://localhost:8181/api/v3/configure/database" \ + # -H "Authorization: Bearer $TOKEN" \ + # -H "Content-Type: application/json" \ + # -d '{"db": "iot_center_devices"}' + # ============================================================================ + influxdb3-enterprise: + container_name: influxdb3-enterprise + image: influxdb:3-enterprise + profiles: ["enterprise"] + pull_policy: always + ports: + - 8181:8181 + command: + - influxdb3 + - serve + - --node-id=node0 + - --object-store=file + - --data-dir=/var/lib/influxdb3/data + - --plugin-dir=/var/lib/influxdb3/plugins + - --admin-token-file=/run/secrets/influxdb3-enterprise-token + - --log-filter=info + volumes: + - type: bind + source: test/.influxdb3/enterprise/data + target: /var/lib/influxdb3/data + - type: bind + source: test/.influxdb3/enterprise/plugins + target: /var/lib/influxdb3/plugins + secrets: + - influxdb3-enterprise-token + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8181/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s +``` + +**Step 2: Verify compose file syntax** + +Run: `docker compose config --quiet` +Expected: No errors + +**Step 3: Commit** + +```bash +git add compose.yaml +git commit -m "feat: add Enterprise service to Docker Compose behind profile" +``` + +--- + +### Task 8: Update documentation + +Update architecture docs and README with caching, plugin, and Enterprise setup steps. + +**Files:** +- Modify: `docs/architecture.md` +- Modify: `README.md` + +**Step 1: Update `docs/architecture.md`** + +Add these sections after the existing "Technology Stack" table: + +```markdown +## Caching + +The application uses two InfluxDB 3 in-memory caches for fast queries: + +| Cache | Type | Database | Table | Purpose | +|-------|------|----------|-------|---------| +| `deviceStatus` | Last Value Cache | `iot_center` | `sensor_data` | Sub-10ms latest readings per device | +| `deviceList` | Distinct Value Cache | `iot_center_devices` | `deviceauth` | Sub-30ms device enumeration | + +Both caches are optional. The app falls back to standard SQL queries if caches are not configured. + +## Processing Engine + +The `plugins/sensor_guard.py` plugin validates incoming sensor data on WAL flush: + +- Checks temperature range (-50 to 150) and humidity range (0 to 100) +- Writes out-of-range readings to a `sensor_alerts` table +- Trigger type: WAL flush on `sensor_data` table + +## Enterprise Support + +Set `INFLUX_EDITION=enterprise` to enable Enterprise features: + +| Feature | Core | Enterprise | +|---------|------|------------| +| Auth tokens | Application-level (stored in DB) | Fine-grained database tokens | +| Data retention | Database-level only | Per-table retention periods | +| Historical queries | ~72 hours (uncompacted) | Unlimited (compacted) | +| Caches (LVC/DVC) | Yes | Yes | +| Processing Engine | Yes | Yes | +``` + +**Step 2: Update `README.md`** + +Add setup sections for caches, plugin, and Enterprise after the existing setup instructions. The exact content depends on the current README structure — add sections for: + +- **Set up caches** — CLI commands for creating the LVC and DVC +- **Set up the Processing Engine plugin** — CLI commands to install and create the trigger +- **Enterprise setup** — How to run with Enterprise using Docker Compose profiles +- **API endpoints** — Updated table including the new `/status` endpoint + +**Step 3: Commit** + +```bash +git add docs/architecture.md README.md +git commit -m "docs: add caching, plugin, and Enterprise setup instructions" +``` + +--- + +## Task Summary + +| Task | Description | New files | Modified files | +|------|-------------|-----------|---------------| +| 1 | Add `edition` to config | — | `lib/influxdb.js`, `__tests__/api.test.js` | +| 2 | DVC fast path for device listing | — | `_devices.js`, `__tests__/api.test.js` | +| 3 | LVC device status endpoint | — | `[[...deviceParams]].js`, `__tests__/api.test.js` | +| 4 | Processing Engine plugin | `plugins/sensor_guard.py` | — | +| 5 | Enterprise module | `lib/enterprise.js` | `__tests__/api.test.js` | +| 6 | Enterprise conditional in create | — | `create.js`, `__tests__/api.test.js` | +| 7 | Enterprise Docker Compose service | — | `compose.yaml` | +| 8 | Documentation updates | — | `docs/architecture.md`, `README.md` |