diff --git a/.circleci/config.yml b/.circleci/config.yml index 2a69edbcd..ca20e048f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -28,6 +28,9 @@ workflows: - test-viewer: filters: *filters-allow-all + - test-objectsender: + filters: *filters-allow-all + - test-ui-components: filters: *filters-allow-all @@ -58,6 +61,7 @@ workflows: - deployment-testing-approval - test-frontend-2 - test-viewer + - test-objectsender - test-server - docker-build-server - docker-build-frontend @@ -77,6 +81,7 @@ workflows: - deployment-testing-approval - test-frontend-2 - test-viewer + - test-objectsender - test-server - docker-build-server - docker-build-frontend @@ -169,6 +174,7 @@ workflows: - publish-approval - test-frontend-2 - test-viewer + - test-objectsender - test-server - docker-publish-frontend: @@ -181,6 +187,7 @@ workflows: - publish-approval - test-frontend-2 - test-viewer + - test-objectsender - test-server - docker-publish-frontend-2: @@ -193,6 +200,7 @@ workflows: - publish-approval - test-frontend-2 - test-viewer + - test-objectsender - test-server - docker-publish-webhooks: @@ -205,6 +213,7 @@ workflows: - publish-approval - test-frontend-2 - test-viewer + - test-objectsender - test-server - docker-publish-file-imports: @@ -217,6 +226,7 @@ workflows: - publish-approval - test-frontend-2 - test-viewer + - test-objectsender - test-server - docker-publish-previews: @@ -229,6 +239,7 @@ workflows: - publish-approval - test-frontend-2 - test-viewer + - test-objectsender - test-server - docker-publish-test-container: @@ -241,6 +252,7 @@ workflows: - publish-approval - test-frontend-2 - test-viewer + - test-objectsender - test-server - docker-publish-monitor-container: @@ -253,6 +265,7 @@ workflows: - publish-approval - test-frontend-2 - test-viewer + - test-objectsender - test-server - docker-publish-docker-compose-ingress: @@ -304,6 +317,7 @@ workflows: - test-ui-components - test-frontend-2 - test-viewer + - test-objectsender - publish-viewer-sandbox-cloudflare-pages: filters: *filters-publish @@ -568,6 +582,41 @@ jobs: command: yarn test working_directory: 'packages/viewer' + test-objectsender: + docker: &docker-node-browsers-image + - image: cimg/node:18.19.0-browsers + resource_class: large + steps: + - checkout + - restore_cache: + name: Restore Yarn Package Cache + keys: + - yarn-packages-server-{{ checksum "yarn.lock" }} + - run: + name: Install Dependencies + command: yarn + + - run: + name: Install Dependencies v2 (.node files missing bug) + command: yarn + + - save_cache: + name: Save Yarn Package Cache + key: yarn-packages-server-{{ checksum "yarn.lock" }} + paths: + - .yarn/cache + - .yarn/unplugged + + - run: + name: Build public packages + command: yarn build:public + - run: + name: Run tests + command: yarn test:ci + working_directory: 'packages/objectsender' + - store_artifacts: + path: 'packages/objectsender/coverage' + test-ui-components: docker: *docker-node-browsers-image resource_class: xlarge diff --git a/packages/objectsender/.gitignore b/packages/objectsender/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/packages/objectsender/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/packages/objectsender/eslint.config.mjs b/packages/objectsender/eslint.config.mjs new file mode 100644 index 000000000..a872627ed --- /dev/null +++ b/packages/objectsender/eslint.config.mjs @@ -0,0 +1,49 @@ +import { baseConfigs, globals, getESMDirname } from '../../eslint.config.mjs' +import tseslint from 'typescript-eslint' + +/** + * @type {Array} + */ +const configs = [ + ...baseConfigs, + { + files: ['examples/browser/**/*.{ts,js}'], + languageOptions: { + globals: { + ...globals.browser + } + } + }, + ...tseslint.configs.recommendedTypeChecked.map((c) => ({ + ...c, + files: [...(c.files || []), '**/*.ts', '**/*.d.ts'] + })), + { + files: ['**/*.ts', '**/*.d.ts'], + languageOptions: { + parserOptions: { + tsconfigRootDir: getESMDirname(import.meta.url), + project: './tsconfig.eslint.json' + } + }, + rules: { + '@typescript-eslint/restrict-template-expressions': 'off' + } + }, + { + files: ['**/*.d.ts'], + rules: { + '@typescript-eslint/no-explicit-any': 'off' + } + }, + { + files: ['vite.config.ts'], + languageOptions: { + globals: { + ...globals.node + } + } + } +] + +export default configs diff --git a/packages/objectsender/index.html b/packages/objectsender/index.html new file mode 100644 index 000000000..c2a2a456d --- /dev/null +++ b/packages/objectsender/index.html @@ -0,0 +1,104 @@ + + + + + + + @speckle/objectsender example + + + +
+

Object Sender Test

+

All the magic is in the console.

+

This will send a random speckle object to the server to flex things around.

+
+

How does this work?

+
+import { send, Base } from '@speckle/objectsender'
+
+const testObject = new Base({
+  prop: value, 
+  '@detachedValue': new Base({...}),
+  '@detachedArray': [...Array(100).fill(0).map( _ => new Base({...}))],
+  '@(10)chunkedArr': [...Array(100).fill(0)]
+})
+
+const { hash } = await send(testObject, { serverUrl, projectId, token }) 
+      
+

For more info check src/examples/browser/main.ts

+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+resulting link will be displayed here (note, nothing will be visible in the viewer)
+
+ + + diff --git a/packages/objectsender/package.json b/packages/objectsender/package.json new file mode 100644 index 000000000..d57c5f3c2 --- /dev/null +++ b/packages/objectsender/package.json @@ -0,0 +1,71 @@ +{ + "name": "@speckle/objectsender", + "version": "1.0.1", + "description": "Simple API helper to serialize and send objects to the server.", + "type": "module", + "main": "dist/objectsender.cjs", + "module": "dist/objectsender.js", + "homepage": "https://speckle.systems", + "types": "./dist/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/specklesystems/speckle-server.git", + "directory": "packages/objectsender" + }, + "engines": { + "node": ">=18.0.0" + }, + "scripts": { + "lint": "eslint .", + "dev": "vite", + "example": "yarn dev", + "build": "tsc && vite build", + "prepack": "yarn build", + "test": "vitest", + "test:ci": "vitest --run --coverage" + }, + "keywords": [ + "speckle", + "aec", + "speckle api" + ], + "author": "AEC Systems", + "license": "Apache-2.0", + "dependencies": { + "@speckle/shared": "workspace:^", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21" + }, + "devDependencies": { + "@types/lodash": "^4.17.5", + "@types/lodash-es": "^4.17.12", + "@typescript-eslint/eslint-plugin": "^7.12.0", + "@typescript-eslint/parser": "^7.12.0", + "@vitest/coverage-v8": "^1.6.0", + "eslint": "^9.4.0", + "eslint-config-prettier": "^9.1.0", + "prettier": "^3.3.2", + "typescript": "^5.2.2", + "vite": "^5.2.0", + "vite-plugin-dts": "^3.9.1", + "vitest": "^1.6.0" + }, + "exports": { + ".": { + "import": { + "default": "./dist/objectsender.js", + "types": "./dist/index.d.ts" + }, + "require": { + "default": "./dist/objectsender.cjs", + "types": "./dist/index.d.ts" + } + } + }, + "imports": { + "#lodash": { + "require": "lodash", + "import": "lodash-es" + } + } +} diff --git a/packages/objectsender/public/vite.svg b/packages/objectsender/public/vite.svg new file mode 100644 index 000000000..e7b8dfb1b --- /dev/null +++ b/packages/objectsender/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/objectsender/readme.md b/packages/objectsender/readme.md new file mode 100644 index 000000000..de3d8e36c --- /dev/null +++ b/packages/objectsender/readme.md @@ -0,0 +1,13 @@ +# objectsender + +Utility for sending objects to a Speckle server instance + +## Testing around / playground + +Run `yarn example` in the app, and open up the URL outputted in the console + +## Development + +For development and/or the example app you can run a dev server in dev mode that will run the util in the browser: `yarn dev` + +To do a real build that will build the library run `yarn build`. diff --git a/packages/objectsender/src/examples/browser/main.ts b/packages/objectsender/src/examples/browser/main.ts new file mode 100644 index 000000000..d4c40dda7 --- /dev/null +++ b/packages/objectsender/src/examples/browser/main.ts @@ -0,0 +1,120 @@ +import { send, Base, type SendResult } from '../../index' +import { times } from '#lodash' + +interface ExampleAppWindow extends Window { + send: typeof import('../../index').send + loadData: () => Promise +} + +const appWindow = window as unknown as ExampleAppWindow + +appWindow.send = send + +const setInputValue = ( + key: string, + value: string, + options?: Partial<{ valueKey: 'value' | 'textContent' }> +) => { + const { valueKey = 'value' } = options || {} + const input = document.getElementById(key) as HTMLInputElement | undefined + if (!input) { + console.error("Unexpectedly couldn't find core input: " + key) + return + } + + input[valueKey] = value +} + +const getInputValue = (key: string) => { + const input = document.getElementById(key) as HTMLInputElement | undefined + if (!input) { + throw new Error("Unexpectedly couldn't find core input: " + key) + } + + return input.value +} + +appWindow.onload = () => { + const serverUrl = localStorage.getItem('serverUrl') + const apiToken = localStorage.getItem('apiToken') + const projectId = localStorage.getItem('projectId') + + if (serverUrl) { + setInputValue('serverUrl', serverUrl) + } + if (apiToken) { + setInputValue('apiToken', apiToken) + } + if (projectId) { + setInputValue('projectId', projectId) + } +} + +appWindow.loadData = async () => { + const serverUrl = getInputValue('serverUrl') + const apiToken = getInputValue('apiToken') + const projectId = getInputValue('projectId') + + localStorage.setItem('serverUrl', serverUrl) + localStorage.setItem('projectId', projectId) + localStorage.setItem('apiToken', apiToken) + + setInputValue('result', '...', { valueKey: 'textContent' }) + + const obj = generateTestObject() + let res: SendResult | undefined = undefined + + try { + res = await send(obj, { + serverUrl, + projectId, + token: apiToken + }) + } catch (e) { + const msg = e instanceof Error ? e.message : JSON.stringify(e) + setInputValue('result', msg, { valueKey: 'textContent' }) + throw e + } + + const objectUrl = new URL(`/projects/${projectId}/models/${res.hash}`, serverUrl) + const objectLink = objectUrl.toString() + console.log(objectLink) + + setInputValue('result', objectLink, { valueKey: 'textContent' }) +} + +function generateTestObject() { + return new Base({ + start: 'end', + primitiveArray: Array(100).fill(1), + normalObject: { + hello: 'world', + how: 'are', + you: '?', + inner: { + pasta: 'pesto', + qux: 'mux' + } + }, + '@detachedValue': new RandomFoo({ + '@nestedDetachedValue_1': new RandomFoo({ + '@nestedDetachedValue_2': new RandomFoo({ + '@nestedDetachedValue_3': new RandomFoo() + }) + }) + }), + '@detachedArray': [ + ...Array(100) + .fill(0) + .map(() => new RandomFoo({ bar: 'baz baz baz' })) + ], + '@(10)chunkedArr': times(100, () => 42) + }) +} + +class RandomFoo extends Base { + constructor(props?: Record) { + super(props) + this.noise = Math.random().toString(16) + } +} diff --git a/packages/objectsender/src/index.ts b/packages/objectsender/src/index.ts new file mode 100644 index 000000000..0d856db93 --- /dev/null +++ b/packages/objectsender/src/index.ts @@ -0,0 +1,51 @@ +import { Serializer } from './utils/Serializer' +import { ServerTransport } from './transports/ServerTransport' +import { Base } from './utils/Base' +export { Base } + +export type SendParams = { + serverUrl?: string + projectId: string + token: string + logger?: { + log: (message: unknown) => void + error: (message: unknown) => void + } +} + +export type SendResult = { + hash: string + traversed: Record +} + +/** + * Decomposes, serializes and sends to a speckle server a given object. Note, for objects to be detached, they need to have a 'speckle_type' property. + * @param object object to decompose, serialise and send to speckle + * @param parameters: server url, project id and token + * @returns the hash of the root object and the value of the root object + */ +export const send = async ( + object: Base, + { + serverUrl = 'https://app.speckle.systems', + projectId, + token, + logger = console + }: SendParams +) => { + const t0 = performance.now() + logger?.log('Starting to send') + const transport = new ServerTransport(serverUrl, projectId, token) + const serializer = new Serializer(transport) + + let result: SendResult + try { + result = await serializer.write(object) + } finally { + transport.dispose() + serializer.dispose() + } + const t1 = performance.now() + logger.log(`Finished sending in ${(t1 - t0) / 1000}s.`) + return result +} diff --git a/packages/objectsender/src/transports/ITransport.ts b/packages/objectsender/src/transports/ITransport.ts new file mode 100644 index 000000000..1c249652e --- /dev/null +++ b/packages/objectsender/src/transports/ITransport.ts @@ -0,0 +1,12 @@ +export interface ITransport { + /** + * Instructs the transport to write this object to its storage layer. + * @param serialisedObject + * @param size approximate objects size + */ + write(serialisedObject: string, size: number): Promise + /** + * Flushes the buffer ensuring it is persisted to its storage layer. + */ + flush(): Promise +} diff --git a/packages/objectsender/src/transports/ServerTransport.ts b/packages/objectsender/src/transports/ServerTransport.ts new file mode 100644 index 000000000..6591d0f9e --- /dev/null +++ b/packages/objectsender/src/transports/ServerTransport.ts @@ -0,0 +1,82 @@ +import { ITransport } from './ITransport' +import { IDisposable } from '../utils/IDisposable' +import { retry, timeoutAt } from '@speckle/shared' + +/** + * Basic object sender to a speckle server + */ +export class ServerTransport implements ITransport, IDisposable { + #buffer: string[] + #maxSize: number + #currSize: number + #serverUrl: string + #projectId: string + #authToken: string + #flushRetryCount: number + #flushTimeout: number + + constructor( + serverUrl: string, + projectId: string, + authToken: string, + options?: Partial<{ + maxSize: number + flushRetryCount: number + flushTimeout: number + }> + ) { + this.#maxSize = options?.maxSize || 200_000 + this.#flushRetryCount = options?.flushRetryCount || 3 + this.#flushTimeout = options?.flushTimeout || 2 * 60 * 1000 + + this.#currSize = 0 + this.#serverUrl = serverUrl + this.#projectId = projectId + this.#authToken = authToken + this.#buffer = [] + } + + async write(serialisedObject: string, size: number) { + this.#buffer.push(serialisedObject) + this.#currSize += size + if (this.#currSize < this.#maxSize) return // return fast + await this.flush() // block until we send objects + } + + async flush() { + if (this.#buffer.length === 0) return + + const formData = new FormData() + const concat = '[' + this.#buffer.join(',') + ']' + formData.append('object-batch', new Blob([concat], { type: 'application/json' })) + const url = new URL(`/objects/${this.#projectId}`, this.#serverUrl) + const res = await retry( + async () => + await Promise.race([ + fetch(url, { + method: 'POST', + headers: { Authorization: `Bearer ${this.#authToken}` }, + body: formData + }), + timeoutAt(this.#flushTimeout, 'Object sender flush timed out') + ]), + this.#flushRetryCount, + (i) => { + return i * 1000 + } + ) + + if (res.status !== 201) { + throw new Error( + `Unexpected error when sending data. Expected status 200, got ${res.status}` + ) + } + + this.#buffer = [] + this.#currSize = 0 + } + + dispose() { + this.#buffer = [] + } +} diff --git a/packages/objectsender/src/utils/Base.ts b/packages/objectsender/src/utils/Base.ts new file mode 100644 index 000000000..602b449f0 --- /dev/null +++ b/packages/objectsender/src/utils/Base.ts @@ -0,0 +1,15 @@ +/* eslint-disable camelcase */ +/** + * Basic 'Base'-like object from .NET. It will create a 'speckle_type' prop that defaults to the class' name. This can be overriden by providing yourself a 'speckle_type' property in the props argument of the constructor. + */ +export class Base implements Record { + speckle_type: string + constructor(props?: Record) { + this.speckle_type = this.constructor.name + + if (props) { + for (const key in props) this[key] = props[key] + } + } + [x: string]: unknown +} diff --git a/packages/objectsender/src/utils/IDisposable.ts b/packages/objectsender/src/utils/IDisposable.ts new file mode 100644 index 000000000..43ec39923 --- /dev/null +++ b/packages/objectsender/src/utils/IDisposable.ts @@ -0,0 +1,3 @@ +export interface IDisposable { + dispose: () => void +} diff --git a/packages/objectsender/src/utils/Serializer.ts b/packages/objectsender/src/utils/Serializer.ts new file mode 100644 index 000000000..e263e416e --- /dev/null +++ b/packages/objectsender/src/utils/Serializer.ts @@ -0,0 +1,248 @@ +/* eslint-disable camelcase */ +import { SHA1 } from './Sha1' +import { ITransport } from '../transports/ITransport' +import { Base } from './Base' +import { IDisposable } from './IDisposable' +import { isObjectLike, get } from '#lodash' + +type BasicSpeckleObject = Record & { + speckle_type: string +} + +const isSpeckleObject = (obj: unknown): obj is BasicSpeckleObject => + isObjectLike(obj) && !!get(obj, 'speckle_type') + +export class Serializer implements IDisposable { + chunkSize: number + detachLineage: boolean[] + lineage: string[] + familyTree: Record> + closureTable: Record + transport: ITransport | null + uniqueId: number + hashingFunction: (s: string) => string + + constructor( + transport: ITransport, + chunkSize: number = 1000, + hashingFunction: (s: string) => string = SHA1 + ) { + this.chunkSize = chunkSize + this.detachLineage = [true] // first ever call is always detached + this.lineage = [] + this.familyTree = {} + this.closureTable = {} + this.transport = transport + this.uniqueId = 0 + this.hashingFunction = hashingFunction || SHA1 + } + + async write(obj: Base) { + return await this.#traverse(obj, true) + } + + async #traverse(obj: Record, root: boolean) { + const temporaryId = `${this.uniqueId++}-obj` + this.lineage.push(temporaryId) + + const traversed = { speckle_type: obj.speckle_type || 'Base' } as Record< + string, + unknown + > + + for (const propKey in obj) { + const value = obj[propKey] + // 0. skip some props + if (!value || propKey === 'id' || propKey.startsWith('_')) continue + + // 1. primitives (numbers, bools, strings) + if (typeof value !== 'object') { + traversed[propKey] = value + continue + } + + const isDetachedProp = propKey.startsWith('@') + + // 2. chunked arrays + const isArray = Array.isArray(value) + const isChunked = isArray ? propKey.match(/^@\((\d*)\)/) : false // chunk syntax + if (isArray && isChunked && value.length !== 0 && typeof value[0] !== 'object') { + const chunkSize = isChunked[1] !== '' ? parseInt(isChunked[1]) : this.chunkSize + const chunkRefs = [] + + let chunk = new DataChunk() + let count = 0 + for (const el of value) { + if (count === chunkSize) { + chunkRefs.push(await this.#handleChunk(chunk)) + chunk = new DataChunk() + count = 0 + } + chunk.data.push(el) + count++ + } + + if (chunk.data.length !== 0) chunkRefs.push(await this.#handleChunk(chunk)) + traversed[propKey.replace(isChunked[0], '')] = chunkRefs // strip chunk syntax + continue + } + + // 3. speckle objects + if ((value as Record).speckle_type) { + const child = (await this.#traverseValue({ + value, + isDetached: isDetachedProp + })) as { + id: string + } + traversed[propKey] = isDetachedProp ? this.#detachHelper(child.id) : child + continue + } + + // 4. other objects (dicts/maps, lists) + traversed[propKey] = await this.#traverseValue({ + value, + isDetached: isDetachedProp + }) + } + // We've finished going through all the properties of this object, now let's perform the last rites + const detached = this.detachLineage.pop() + const parent = this.lineage.pop() as string + + if (this.familyTree[parent]) { + const closure = {} as Record + + Object.entries(this.familyTree[parent]).forEach(([ref, depth]) => { + closure[ref] = depth - this.detachLineage.length + }) + + traversed['totalChildrenCount'] = Object.keys(closure).length + + if (traversed['totalChildrenCount']) { + traversed['__closure'] = closure + } + } + + const { hash, serializedObject, size } = this.#generateId(traversed) + traversed.id = hash + + // Pop it in + if ((detached || root) && this.transport) { + await this.transport.write(serializedObject, size) + } + + // We've reached the end, let's flush + if (root && this.transport) { + await this.transport.flush() + } + + return { hash, traversed } + } + + async #traverseValue({ + value, + isDetached = false + }: { + value: unknown + isDetached?: boolean + }): Promise { + // 1. primitives + if (typeof value !== 'object') return value + + // 2. arrays + if (Array.isArray(value)) { + const arr = value as unknown[] + // 2.1 empty arrays + if (arr.length === 0) return value as unknown + + // 2.2 primitive arrays + if (typeof arr[0] !== 'object') return arr + + // 2.3. non-primitive non-detached arrays + if (!isDetached) { + return Promise.all( + value.map(async (el) => await this.#traverseValue({ value: el })) + ) + } + + // 2.4 non-primitive detached arrays + const detachedList = [] as unknown[] + for (const el of value) { + if (isSpeckleObject(el)) { + this.detachLineage.push(isDetached) + const { hash } = await this.#traverse(el, false) + detachedList.push(this.#detachHelper(hash)) + } else { + detachedList.push(await this.#traverseValue({ value: el, isDetached })) + } + } + return detachedList + } + + // 3. dicts + if (!(value as { speckle_type?: string }).speckle_type) return value + + // 4. base objects + if ((value as { speckle_type?: string }).speckle_type) { + this.detachLineage.push(isDetached) + const res = await this.#traverse(value as Record, false) + return res.traversed + } + + throw new Error(`Unsupported type '${typeof value}': ${value}.`) + } + + #detachHelper(refHash: string) { + this.lineage.forEach((parent) => { + if (!this.familyTree[parent]) this.familyTree[parent] = {} + + if ( + !this.familyTree[parent][refHash] || + this.familyTree[parent][refHash] > this.detachLineage.length + ) { + this.familyTree[parent][refHash] = this.detachLineage.length + } + }) + return { + referencedId: refHash, + speckle_type: 'reference' + } + } + + async #handleChunk(chunk: DataChunk) { + this.detachLineage.push(true) + const { hash } = await this.#traverse( + chunk as unknown as Record, + false + ) + return this.#detachHelper(hash) + } + + #generateId(obj: Record) { + const s = JSON.stringify(obj) + const h = this.hashingFunction(s) + const f = s.substring(0, 1) + `"id":"${h}",` + s.substring(1) + return { + hash: SHA1(s), + serializedObject: f, + size: s.length // approx, good enough as we're just limiting artificially batch sizes based on this + } + } + + dispose() { + this.detachLineage = [] + this.lineage = [] + this.familyTree = {} + this.closureTable = {} + this.transport = null + } +} + +class DataChunk { + speckle_type: 'Speckle.Core.Models.DataChunk' + data: unknown[] + constructor() { + this.data = [] + this.speckle_type = 'Speckle.Core.Models.DataChunk' + } +} diff --git a/packages/objectsender/src/utils/Sha1.spec.ts b/packages/objectsender/src/utils/Sha1.spec.ts new file mode 100644 index 000000000..e7498fc56 --- /dev/null +++ b/packages/objectsender/src/utils/Sha1.spec.ts @@ -0,0 +1,15 @@ +import { expect, describe, it } from 'vitest' +import { SHA1 } from './Sha1' + +describe('SHA1 encryption', () => { + it.each([ + ['le speckle', '67413ddfa55bab1b735d4d90bf5be7f5fafbcdfb'], + [ + 'the quick brown fox jumped over the lazy dog? i think', + 'b724fbdc205bae3b1d7511304ec4b576af563f93' + ], + ['1', '356a192b7913b04c54574d18c28d46e6395428ab'] + ])('SHA1(%s) should return %s', (input, expected) => { + expect(SHA1(input)).toBe(expected) + }) +}) diff --git a/packages/objectsender/src/utils/Sha1.ts b/packages/objectsender/src/utils/Sha1.ts new file mode 100644 index 000000000..8adf91fc6 --- /dev/null +++ b/packages/objectsender/src/utils/Sha1.ts @@ -0,0 +1,138 @@ +/* eslint-disable camelcase */ +/** + * Basic hashing function, to avoid dependencies and crazy build processes + * @param msg + * @returns + */ +export function SHA1(msg: string) { + function rotate_left(n: number, s: number) { + const t4 = (n << s) | (n >>> (32 - s)) + return t4 + } + function cvt_hex(val: number) { + let str = '' + let i + let v + for (i = 7; i >= 0; i--) { + v = (val >>> (i * 4)) & 0x0f + str += v.toString(16) + } + return str + } + function Utf8Encode(string: string) { + string = string.replace(/\r\n/g, '\n') + let utftext = '' + for (let n = 0; n < string.length; n++) { + const c = string.charCodeAt(n) + if (c < 128) { + utftext += String.fromCharCode(c) + } else if (c > 127 && c < 2048) { + utftext += String.fromCharCode((c >> 6) | 192) + utftext += String.fromCharCode((c & 63) | 128) + } else { + utftext += String.fromCharCode((c >> 12) | 224) + utftext += String.fromCharCode(((c >> 6) & 63) | 128) + utftext += String.fromCharCode((c & 63) | 128) + } + } + return utftext + } + let blockstart + let i, j + const W = new Array(80) + let H0 = 0x67452301 + let H1 = 0xefcdab89 + let H2 = 0x98badcfe + let H3 = 0x10325476 + let H4 = 0xc3d2e1f0 + let A, B, C, D, E + let temp + msg = Utf8Encode(msg) + const msg_len = msg.length + const word_array = [] as unknown[] + for (i = 0; i < msg_len - 3; i += 4) { + j = + (msg.charCodeAt(i) << 24) | + (msg.charCodeAt(i + 1) << 16) | + (msg.charCodeAt(i + 2) << 8) | + msg.charCodeAt(i + 3) + word_array.push(j) + } + switch (msg_len % 4) { + case 0: + i = 0x080000000 + break + case 1: + i = (msg.charCodeAt(msg_len - 1) << 24) | 0x0800000 + break + case 2: + i = + (msg.charCodeAt(msg_len - 2) << 24) | + (msg.charCodeAt(msg_len - 1) << 16) | + 0x08000 + break + case 3: + i = + (msg.charCodeAt(msg_len - 3) << 24) | + (msg.charCodeAt(msg_len - 2) << 16) | + (msg.charCodeAt(msg_len - 1) << 8) | + 0x80 + break + } + word_array.push(i) + while (word_array.length % 16 !== 14) word_array.push(0) + word_array.push(msg_len >>> 29) + word_array.push((msg_len << 3) & 0x0ffffffff) + for (blockstart = 0; blockstart < word_array.length; blockstart += 16) { + for (i = 0; i < 16; i++) W[i] = word_array[blockstart + i] + for (i = 16; i <= 79; i++) + W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1) + A = H0 + B = H1 + C = H2 + D = H3 + E = H4 + for (i = 0; i <= 19; i++) { + temp = + (rotate_left(A, 5) + ((B & C) | (~B & D)) + E + W[i] + 0x5a827999) & 0x0ffffffff + E = D + D = C + C = rotate_left(B, 30) + B = A + A = temp + } + for (i = 20; i <= 39; i++) { + temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ed9eba1) & 0x0ffffffff + E = D + D = C + C = rotate_left(B, 30) + B = A + A = temp + } + for (i = 40; i <= 59; i++) { + temp = + (rotate_left(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[i] + 0x8f1bbcdc) & + 0x0ffffffff + E = D + D = C + C = rotate_left(B, 30) + B = A + A = temp + } + for (i = 60; i <= 79; i++) { + temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0xca62c1d6) & 0x0ffffffff + E = D + D = C + C = rotate_left(B, 30) + B = A + A = temp + } + H0 = (H0 + A) & 0x0ffffffff + H1 = (H1 + B) & 0x0ffffffff + H2 = (H2 + C) & 0x0ffffffff + H3 = (H3 + D) & 0x0ffffffff + H4 = (H4 + E) & 0x0ffffffff + } + const h = cvt_hex(H0) + cvt_hex(H1) + cvt_hex(H2) + cvt_hex(H3) + cvt_hex(H4) + return h.toLowerCase().substring(0, 40) +} diff --git a/packages/objectsender/src/vite-env.d.ts b/packages/objectsender/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/packages/objectsender/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/objectsender/tsconfig.eslint.json b/packages/objectsender/tsconfig.eslint.json new file mode 100644 index 000000000..fb5d0dd47 --- /dev/null +++ b/packages/objectsender/tsconfig.eslint.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "dist", "coverage"], + "include": ["src", "vite.config.ts", "vitest.config.ts"] +} diff --git a/packages/objectsender/tsconfig.json b/packages/objectsender/tsconfig.json new file mode 100644 index 000000000..75abdef26 --- /dev/null +++ b/packages/objectsender/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/packages/objectsender/vite.config.ts b/packages/objectsender/vite.config.ts new file mode 100644 index 000000000..3d525bdd2 --- /dev/null +++ b/packages/objectsender/vite.config.ts @@ -0,0 +1,21 @@ +/// +import pkg from './package.json' +import { defineConfig } from 'vite' +import { resolve } from 'path' +import dts from 'vite-plugin-dts' + +export default defineConfig({ + build: { + lib: { + entry: resolve(__dirname, './src/index.ts'), + name: 'objectsender', + fileName: 'objectsender', + formats: ['es', 'cjs'] + }, + sourcemap: true, + rollupOptions: { + external: Object.keys(pkg.dependencies || {}) + } + }, + plugins: [dts()] +}) diff --git a/packages/objectsender/vitest.config.ts b/packages/objectsender/vitest.config.ts new file mode 100644 index 000000000..a16d6ad90 --- /dev/null +++ b/packages/objectsender/vitest.config.ts @@ -0,0 +1,9 @@ +import { coverageConfigDefaults, defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + coverage: { + exclude: ['**/src/examples/**', ...coverageConfigDefaults.exclude] + } + } +}) diff --git a/workspace.code-workspace b/workspace.code-workspace index 117dc48e9..e94d40e71 100644 --- a/workspace.code-workspace +++ b/workspace.code-workspace @@ -46,7 +46,11 @@ }, { "path": "packages/objectloader", - "name": "🌐 objectloader" + "name": "⬇️ objectloader" + }, + { + "path": "packages/objectsender", + "name": "⬆️ objectsender" }, { "path": "packages/shared", @@ -134,6 +138,6 @@ "graphql.vscode-graphql-syntax" ], // List of extensions recommended by VS Code that should not be recommended for users of this workspace. - "unwantedRecommendations": ["octref.vetur", "vscode.typescript-language-features"] + "unwantedRecommendations": ["octref.vetur"] } } diff --git a/yarn.lock b/yarn.lock index 96079d3ec..22a3e8d42 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29,6 +29,16 @@ __metadata: languageName: node linkType: hard +"@ampproject/remapping@npm:^2.2.1": + version: 2.3.0 + resolution: "@ampproject/remapping@npm:2.3.0" + dependencies: + "@jridgewell/gen-mapping": ^0.3.5 + "@jridgewell/trace-mapping": ^0.3.24 + checksum: d3ad7b89d973df059c4e8e6d7c972cbeb1bb2f18f002a3bd04ae0707da214cb06cc06929b65aa2313b9347463df2914772298bae8b1d7973f246bb3f2ab3e8f0 + languageName: node + linkType: hard + "@antfu/install-pkg@npm:^0.1.1": version: 0.1.1 resolution: "@antfu/install-pkg@npm:0.1.1" @@ -4692,7 +4702,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.24.7": +"@babel/parser@npm:^7.24.4, @babel/parser@npm:^7.24.7": version: 7.24.7 resolution: "@babel/parser@npm:7.24.7" bin: @@ -13275,7 +13285,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": +"@jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": version: 0.3.25 resolution: "@jridgewell/trace-mapping@npm:0.3.25" dependencies: @@ -13446,6 +13456,17 @@ __metadata: languageName: node linkType: hard +"@microsoft/api-extractor-model@npm:7.28.13": + version: 7.28.13 + resolution: "@microsoft/api-extractor-model@npm:7.28.13" + dependencies: + "@microsoft/tsdoc": 0.14.2 + "@microsoft/tsdoc-config": ~0.16.1 + "@rushstack/node-core-library": 4.0.2 + checksum: 0ac8236c670da4fe831a2b2fee94b4c9e4b881ba04e3c7807774db3f47e87c8d62dd5a5fd9b9a3f8bd56aa9ce2d75307338558c83063a912f230824e08f15e89 + languageName: node + linkType: hard + "@microsoft/api-extractor-model@npm:7.28.2": version: 7.28.2 resolution: "@microsoft/api-extractor-model@npm:7.28.2" @@ -13457,6 +13478,29 @@ __metadata: languageName: node linkType: hard +"@microsoft/api-extractor@npm:7.43.0": + version: 7.43.0 + resolution: "@microsoft/api-extractor@npm:7.43.0" + dependencies: + "@microsoft/api-extractor-model": 7.28.13 + "@microsoft/tsdoc": 0.14.2 + "@microsoft/tsdoc-config": ~0.16.1 + "@rushstack/node-core-library": 4.0.2 + "@rushstack/rig-package": 0.5.2 + "@rushstack/terminal": 0.10.0 + "@rushstack/ts-command-line": 4.19.1 + lodash: ~4.17.15 + minimatch: ~3.0.3 + resolve: ~1.22.1 + semver: ~7.5.4 + source-map: ~0.6.1 + typescript: 5.4.2 + bin: + api-extractor: bin/api-extractor + checksum: 7015bbd529782209f0d8e5728ae7699707ea60a696a60b2bc688ec88fec62430e82ac77629e59fc40d3b287ea0135a1050509c7436de648bb656e882c97fae3c + languageName: node + linkType: hard + "@microsoft/api-extractor@npm:^7.38.0": version: 7.38.3 resolution: "@microsoft/api-extractor@npm:7.38.3" @@ -15574,6 +15618,25 @@ __metadata: languageName: node linkType: hard +"@rushstack/node-core-library@npm:4.0.2": + version: 4.0.2 + resolution: "@rushstack/node-core-library@npm:4.0.2" + dependencies: + fs-extra: ~7.0.1 + import-lazy: ~4.0.0 + jju: ~1.4.0 + resolve: ~1.22.1 + semver: ~7.5.4 + z-schema: ~5.0.2 + peerDependencies: + "@types/node": "*" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 318cbe9c17514dd18948d63fc170eeea5167d877734385e2351ba844ebb96f3b1cfced8d73ed2a0fdb89eb7013367be6e1f9409ae0ede71fe6e35cfdb6bf8e5b + languageName: node + linkType: hard + "@rushstack/rig-package@npm:0.5.1": version: 0.5.1 resolution: "@rushstack/rig-package@npm:0.5.1" @@ -15584,6 +15647,31 @@ __metadata: languageName: node linkType: hard +"@rushstack/rig-package@npm:0.5.2": + version: 0.5.2 + resolution: "@rushstack/rig-package@npm:0.5.2" + dependencies: + resolve: ~1.22.1 + strip-json-comments: ~3.1.1 + checksum: cdfbca218d85f0daf865b308a8f1ad572835e465b64a2efa74e8ee0025d31bb7da8d77a5f2c34af7fd8993eb0e317ea14cce14557dcbe0fe3041020d4141704b + languageName: node + linkType: hard + +"@rushstack/terminal@npm:0.10.0": + version: 0.10.0 + resolution: "@rushstack/terminal@npm:0.10.0" + dependencies: + "@rushstack/node-core-library": 4.0.2 + supports-color: ~8.1.1 + peerDependencies: + "@types/node": "*" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 1a62b2bd26f501608ae0c482b4597c4266752501e6ca61ffa7a00344b396382dbb5a8a6766dd93af9b5aecfbcf861c065f00b3c70461b48ec168feefc9515d9f + languageName: node + linkType: hard + "@rushstack/ts-command-line@npm:4.17.1": version: 4.17.1 resolution: "@rushstack/ts-command-line@npm:4.17.1" @@ -15596,6 +15684,18 @@ __metadata: languageName: node linkType: hard +"@rushstack/ts-command-line@npm:4.19.1": + version: 4.19.1 + resolution: "@rushstack/ts-command-line@npm:4.19.1" + dependencies: + "@rushstack/terminal": 0.10.0 + "@types/argparse": 1.0.38 + argparse: ~1.0.9 + string-argv: ~0.3.1 + checksum: db9ee56563e6c628fe7203153213a7db92a9aeed509f36a0c87882d9ccc46bdd6474ef870b1fc1d68f7980676b30e327bd1b6ef5e529847bbe60f5de49e81b2a + languageName: node + linkType: hard + "@sentry/core@npm:6.19.7": version: 6.19.7 resolution: "@sentry/core@npm:6.19.7" @@ -16774,6 +16874,28 @@ __metadata: languageName: unknown linkType: soft +"@speckle/objectsender@workspace:packages/objectsender": + version: 0.0.0-use.local + resolution: "@speckle/objectsender@workspace:packages/objectsender" + dependencies: + "@speckle/shared": "workspace:^" + "@types/lodash": ^4.17.5 + "@types/lodash-es": ^4.17.12 + "@typescript-eslint/eslint-plugin": ^7.12.0 + "@typescript-eslint/parser": ^7.12.0 + "@vitest/coverage-v8": ^1.6.0 + eslint: ^9.4.0 + eslint-config-prettier: ^9.1.0 + lodash: ^4.17.21 + lodash-es: ^4.17.21 + prettier: ^3.3.2 + typescript: ^5.2.2 + vite: ^5.2.0 + vite-plugin-dts: ^3.9.1 + vitest: ^1.6.0 + languageName: unknown + linkType: soft + "@speckle/preview-service@workspace:packages/preview-service": version: 0.0.0-use.local resolution: "@speckle/preview-service@workspace:packages/preview-service" @@ -19736,6 +19858,13 @@ __metadata: languageName: node linkType: hard +"@types/lodash@npm:^4.17.5": + version: 4.17.5 + resolution: "@types/lodash@npm:4.17.5" + checksum: 3c9bb15772509f0ecb40428531863dbc3f064f2bf34bbccc2ce2b2923c69fb0868aec7e357b1d97fd0d7f7e435a014ea5c1adef8a64715529887179c97a5a823 + languageName: node + linkType: hard + "@types/long@npm:^4.0.0": version: 4.0.2 resolution: "@types/long@npm:4.0.2" @@ -20907,6 +21036,29 @@ __metadata: languageName: node linkType: hard +"@vitest/coverage-v8@npm:^1.6.0": + version: 1.6.0 + resolution: "@vitest/coverage-v8@npm:1.6.0" + dependencies: + "@ampproject/remapping": ^2.2.1 + "@bcoe/v8-coverage": ^0.2.3 + debug: ^4.3.4 + istanbul-lib-coverage: ^3.2.2 + istanbul-lib-report: ^3.0.1 + istanbul-lib-source-maps: ^5.0.4 + istanbul-reports: ^3.1.6 + magic-string: ^0.30.5 + magicast: ^0.3.3 + picocolors: ^1.0.0 + std-env: ^3.5.0 + strip-literal: ^2.0.0 + test-exclude: ^6.0.0 + peerDependencies: + vitest: 1.6.0 + checksum: f5f29ec8768bc221f01f0183f7b326fc88113c6f1cdf215713f4ee00a47d01fd3ce446d1cd38935a005caab17f560490b351927e7e939669d39dbdf5ddf29360 + languageName: node + linkType: hard + "@vitest/expect@npm:1.3.1": version: 1.3.1 resolution: "@vitest/expect@npm:1.3.1" @@ -20929,6 +21081,17 @@ __metadata: languageName: node linkType: hard +"@vitest/expect@npm:1.6.0": + version: 1.6.0 + resolution: "@vitest/expect@npm:1.6.0" + dependencies: + "@vitest/spy": 1.6.0 + "@vitest/utils": 1.6.0 + chai: ^4.3.10 + checksum: f3a9959ea387622297efed9e3689fd405044a813df5d5923302eaaea831e250d8d6a0ccd44fb387a95c19963242695ed803afc7c46ae06c48a8e06f194951984 + languageName: node + linkType: hard + "@vitest/runner@npm:1.4.0": version: 1.4.0 resolution: "@vitest/runner@npm:1.4.0" @@ -20940,6 +21103,17 @@ __metadata: languageName: node linkType: hard +"@vitest/runner@npm:1.6.0": + version: 1.6.0 + resolution: "@vitest/runner@npm:1.6.0" + dependencies: + "@vitest/utils": 1.6.0 + p-limit: ^5.0.0 + pathe: ^1.1.1 + checksum: 2dcd953477d5effc051376e35a7f2c2b28abbe07c54e61157c9a6d6f01c880e079592c959397b3a55471423256ab91709c150881a33632558b81b1e251a0bf9c + languageName: node + linkType: hard + "@vitest/snapshot@npm:1.4.0": version: 1.4.0 resolution: "@vitest/snapshot@npm:1.4.0" @@ -20951,6 +21125,17 @@ __metadata: languageName: node linkType: hard +"@vitest/snapshot@npm:1.6.0": + version: 1.6.0 + resolution: "@vitest/snapshot@npm:1.6.0" + dependencies: + magic-string: ^0.30.5 + pathe: ^1.1.1 + pretty-format: ^29.7.0 + checksum: c4249fbf3ce310de86a19529a0a5c10b1bde4d8d8a678029c632335969b86cbdbf51cedc20d5e9c9328afee834d13cec1b8de5d0fd58139bf8e2dd8dcd0797f4 + languageName: node + linkType: hard + "@vitest/spy@npm:1.3.1": version: 1.3.1 resolution: "@vitest/spy@npm:1.3.1" @@ -20969,6 +21154,15 @@ __metadata: languageName: node linkType: hard +"@vitest/spy@npm:1.6.0": + version: 1.6.0 + resolution: "@vitest/spy@npm:1.6.0" + dependencies: + tinyspy: ^2.2.0 + checksum: 0201975232255e1197f70fc6b23a1ff5e606138a5b96598fff06077d5b747705391013ee98f951affcfd8f54322e4ae1416200393248bb6a9c794f4ef663a066 + languageName: node + linkType: hard + "@vitest/ui@npm:^1.4.0": version: 1.4.0 resolution: "@vitest/ui@npm:1.4.0" @@ -21010,6 +21204,18 @@ __metadata: languageName: node linkType: hard +"@vitest/utils@npm:1.6.0": + version: 1.6.0 + resolution: "@vitest/utils@npm:1.6.0" + dependencies: + diff-sequences: ^29.6.3 + estree-walker: ^3.0.3 + loupe: ^2.3.7 + pretty-format: ^29.7.0 + checksum: a4749533a48e7e4bbc8eafee0fee0e9a0d4eaa4910fbdb490d34e16f8ebcce59a2b38529b9e6b4578e3b4510ea67b29384c93165712b0a19f2e71946922d2c56 + languageName: node + linkType: hard + "@volar/language-core@npm:1.10.1, @volar/language-core@npm:~1.10.0": version: 1.10.1 resolution: "@volar/language-core@npm:1.10.1" @@ -21028,6 +21234,15 @@ __metadata: languageName: node linkType: hard +"@volar/language-core@npm:1.11.1, @volar/language-core@npm:~1.11.1": + version: 1.11.1 + resolution: "@volar/language-core@npm:1.11.1" + dependencies: + "@volar/source-map": 1.11.1 + checksum: 7f98fbeb96ff1093dbaa47e790575a98d1fd2103d9bb1598ec7b0ae787fc6af2ffcea12fdea0f0a4e057f38f6ee3a60bd54f2af3985159319021771f79df9451 + languageName: node + linkType: hard + "@volar/language-core@npm:1.4.0-alpha.4": version: 1.4.0-alpha.4 resolution: "@volar/language-core@npm:1.4.0-alpha.4" @@ -21073,6 +21288,15 @@ __metadata: languageName: node linkType: hard +"@volar/source-map@npm:1.11.1, @volar/source-map@npm:~1.11.1": + version: 1.11.1 + resolution: "@volar/source-map@npm:1.11.1" + dependencies: + muggle-string: ^0.3.1 + checksum: 1ec1034432ee51a0afe187ba9158292dd607a90d01120ee8a36cf27f5d464da5282c8fe7b0de82f52f45474a840c63eba666254c5c21ca5466dc02d0c95cd147 + languageName: node + linkType: hard + "@volar/source-map@npm:1.4.0-alpha.4": version: 1.4.0-alpha.4 resolution: "@volar/source-map@npm:1.4.0-alpha.4" @@ -21128,6 +21352,16 @@ __metadata: languageName: node linkType: hard +"@volar/typescript@npm:~1.11.1": + version: 1.11.1 + resolution: "@volar/typescript@npm:1.11.1" + dependencies: + "@volar/language-core": 1.11.1 + path-browserify: ^1.0.1 + checksum: 0db2fc32db133e493f05dbafd248560a6d4e5b071a0d80422c67b1875bd36980c113915d876a83e855d55c2880b2e7b9f04f803ce3504a4d6fafcc0b801c621b + languageName: node + linkType: hard + "@volar/typescript@npm:~2.1.3": version: 2.1.6 resolution: "@volar/typescript@npm:2.1.6" @@ -21959,6 +22193,28 @@ __metadata: languageName: node linkType: hard +"@vue/language-core@npm:1.8.27, @vue/language-core@npm:^1.8.27": + version: 1.8.27 + resolution: "@vue/language-core@npm:1.8.27" + dependencies: + "@volar/language-core": ~1.11.1 + "@volar/source-map": ~1.11.1 + "@vue/compiler-dom": ^3.3.0 + "@vue/shared": ^3.3.0 + computeds: ^0.0.1 + minimatch: ^9.0.3 + muggle-string: ^0.3.1 + path-browserify: ^1.0.1 + vue-template-compiler: ^2.7.14 + peerDependencies: + typescript: "*" + peerDependenciesMeta: + typescript: + optional: true + checksum: 8660c05319be8dc5daacc2cd929171434215d29f3ad5bfbe0038d1967db05b8bf640286b25f338845cc1e3890b4aaa239ac9e8cb832cc8a50a5bbdff31b2edd1 + languageName: node + linkType: hard + "@vue/language-core@npm:1.8.8": version: 1.8.8 resolution: "@vue/language-core@npm:1.8.8" @@ -29384,7 +29640,7 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0, esbuild@npm:^0.20.2": +"esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0, esbuild@npm:^0.20.1, esbuild@npm:^0.20.2": version: 0.20.2 resolution: "esbuild@npm:0.20.2" dependencies: @@ -34836,6 +35092,13 @@ __metadata: languageName: node linkType: hard +"istanbul-lib-coverage@npm:^3.2.2": + version: 3.2.2 + resolution: "istanbul-lib-coverage@npm:3.2.2" + checksum: 2367407a8d13982d8f7a859a35e7f8dd5d8f75aae4bb5484ede3a9ea1b426dc245aff28b976a2af48ee759fdd9be374ce2bd2669b644f31e76c5f46a2e29a831 + languageName: node + linkType: hard + "istanbul-lib-hook@npm:^3.0.0": version: 3.0.0 resolution: "istanbul-lib-hook@npm:3.0.0" @@ -34909,6 +35172,17 @@ __metadata: languageName: node linkType: hard +"istanbul-lib-report@npm:^3.0.1": + version: 3.0.1 + resolution: "istanbul-lib-report@npm:3.0.1" + dependencies: + istanbul-lib-coverage: ^3.0.0 + make-dir: ^4.0.0 + supports-color: ^7.1.0 + checksum: fd17a1b879e7faf9bb1dc8f80b2a16e9f5b7b8498fe6ed580a618c34df0bfe53d2abd35bf8a0a00e628fb7405462576427c7df20bbe4148d19c14b431c974b21 + languageName: node + linkType: hard + "istanbul-lib-source-maps@npm:^4.0.0": version: 4.0.1 resolution: "istanbul-lib-source-maps@npm:4.0.1" @@ -34920,6 +35194,17 @@ __metadata: languageName: node linkType: hard +"istanbul-lib-source-maps@npm:^5.0.4": + version: 5.0.4 + resolution: "istanbul-lib-source-maps@npm:5.0.4" + dependencies: + "@jridgewell/trace-mapping": ^0.3.23 + debug: ^4.1.1 + istanbul-lib-coverage: ^3.0.0 + checksum: f34550c75b957312787eaa86a6c115b269f090905c0128f8acdb553a8d9b6562581be0e7c0b68247d97172fdfd6445517cbe46651f984453cce3c517b2e931ad + languageName: node + linkType: hard + "istanbul-reports@npm:^3.0.2": version: 3.1.4 resolution: "istanbul-reports@npm:3.1.4" @@ -34940,6 +35225,16 @@ __metadata: languageName: node linkType: hard +"istanbul-reports@npm:^3.1.6": + version: 3.1.7 + resolution: "istanbul-reports@npm:3.1.7" + dependencies: + html-escaper: ^2.0.0 + istanbul-lib-report: ^3.0.0 + checksum: 2072db6e07bfbb4d0eb30e2700250636182398c1af811aea5032acb219d2080f7586923c09fa194029efd6b92361afb3dcbe1ebcc3ee6651d13340f7c6c4ed95 + languageName: node + linkType: hard + "iterall@npm:^1.2.1, iterall@npm:^1.3.0": version: 1.3.0 resolution: "iterall@npm:1.3.0" @@ -38020,6 +38315,17 @@ __metadata: languageName: node linkType: hard +"magicast@npm:^0.3.3": + version: 0.3.4 + resolution: "magicast@npm:0.3.4" + dependencies: + "@babel/parser": ^7.24.4 + "@babel/types": ^7.24.0 + source-map-js: ^1.2.0 + checksum: 9cc84b8424d2c9b03533c16abec5b29f3897619a07714232c602382660867140858f54f482da2b9968ba4b89e198e66578f483415256c4eb2656ae7265bbb2de + languageName: node + linkType: hard + "make-dir@npm:^2.0.0, make-dir@npm:^2.1.0": version: 2.1.0 resolution: "make-dir@npm:2.1.0" @@ -38039,6 +38345,15 @@ __metadata: languageName: node linkType: hard +"make-dir@npm:^4.0.0": + version: 4.0.0 + resolution: "make-dir@npm:4.0.0" + dependencies: + semver: ^7.5.3 + checksum: bf0731a2dd3aab4db6f3de1585cea0b746bb73eb5a02e3d8d72757e376e64e6ada190b1eddcde5b2f24a81b688a9897efd5018737d05e02e2a671dda9cff8a8a + languageName: node + linkType: hard + "make-error@npm:^1.1.1, make-error@npm:^1.3.6": version: 1.3.6 resolution: "make-error@npm:1.3.6" @@ -38618,7 +38933,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:~3.0.4": +"minimatch@npm:~3.0.3, minimatch@npm:~3.0.4": version: 3.0.8 resolution: "minimatch@npm:3.0.8" dependencies: @@ -43933,6 +44248,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.4.38": + version: 8.4.38 + resolution: "postcss@npm:8.4.38" + dependencies: + nanoid: ^3.3.7 + picocolors: ^1.0.0 + source-map-js: ^1.2.0 + checksum: 649f9e60a763ca4b5a7bbec446a069edf07f057f6d780a5a0070576b841538d1ecf7dd888f2fbfd1f76200e26c969e405aeeae66332e6927dbdc8bdcb90b9451 + languageName: node + linkType: hard + "postgres-array@npm:~2.0.0": version: 2.0.0 resolution: "postgres-array@npm:2.0.0" @@ -47512,6 +47838,13 @@ __metadata: languageName: node linkType: hard +"source-map-js@npm:^1.2.0": + version: 1.2.0 + resolution: "source-map-js@npm:1.2.0" + checksum: 791a43306d9223792e84293b00458bf102a8946e7188f3db0e4e22d8d530b5f80a4ce468eb5ec0bf585443ad55ebbd630bf379c98db0b1f317fd902500217f97 + languageName: node + linkType: hard + "source-map-support@npm:0.5.13": version: 0.5.13 resolution: "source-map-support@npm:0.5.13" @@ -48507,7 +48840,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:8.1.1, supports-color@npm:^8.0.0, supports-color@npm:^8.1.0, supports-color@npm:^8.1.1": +"supports-color@npm:8.1.1, supports-color@npm:^8.0.0, supports-color@npm:^8.1.0, supports-color@npm:^8.1.1, supports-color@npm:~8.1.1": version: 8.1.1 resolution: "supports-color@npm:8.1.1" dependencies: @@ -49248,6 +49581,13 @@ __metadata: languageName: node linkType: hard +"tinypool@npm:^0.8.3": + version: 0.8.4 + resolution: "tinypool@npm:0.8.4" + checksum: d40c40e062d5eeae85dadc39294dde6bc7b9a7a7cf0c972acbbe5a2b42491dfd4c48381c1e48bbe02aff4890e63de73d115b2e7de2ce4c81356aa5e654a43caf + languageName: node + linkType: hard + "tinyspy@npm:^2.2.0": version: 2.2.1 resolution: "tinyspy@npm:2.2.1" @@ -51329,6 +51669,21 @@ __metadata: languageName: node linkType: hard +"vite-node@npm:1.6.0": + version: 1.6.0 + resolution: "vite-node@npm:1.6.0" + dependencies: + cac: ^6.7.14 + debug: ^4.3.4 + pathe: ^1.1.1 + picocolors: ^1.0.0 + vite: ^5.0.0 + bin: + vite-node: vite-node.mjs + checksum: ce111c5c7a4cf65b722baa15cbc065b7bfdbf1b65576dd6372995f6a72b2b93773ec5df59f6c5f08cfe1284806597b44b832efcea50d5971102428159ff4379f + languageName: node + linkType: hard + "vite-node@npm:^0.33.0": version: 0.33.0 resolution: "vite-node@npm:0.33.0" @@ -51467,6 +51822,27 @@ __metadata: languageName: node linkType: hard +"vite-plugin-dts@npm:^3.9.1": + version: 3.9.1 + resolution: "vite-plugin-dts@npm:3.9.1" + dependencies: + "@microsoft/api-extractor": 7.43.0 + "@rollup/pluginutils": ^5.1.0 + "@vue/language-core": ^1.8.27 + debug: ^4.3.4 + kolorist: ^1.8.0 + magic-string: ^0.30.8 + vue-tsc: ^1.8.27 + peerDependencies: + typescript: "*" + vite: "*" + peerDependenciesMeta: + vite: + optional: true + checksum: e048b509995ae4ed883aa4cc2397d8ab7929865b70ba3710ed853d5d37de1c7f49389f7e2dc89404a729624f4d2738dc9616450f868eef8e2861ebc6673099e5 + languageName: node + linkType: hard + "vite-plugin-inspect@npm:^0.7.15": version: 0.7.16 resolution: "vite-plugin-inspect@npm:0.7.16" @@ -51633,6 +52009,46 @@ __metadata: languageName: node linkType: hard +"vite@npm:^5.2.0": + version: 5.2.13 + resolution: "vite@npm:5.2.13" + dependencies: + esbuild: ^0.20.1 + fsevents: ~2.3.3 + postcss: ^8.4.38 + rollup: ^4.13.0 + peerDependencies: + "@types/node": ^18.0.0 || >=20.0.0 + less: "*" + lightningcss: ^1.21.0 + sass: "*" + stylus: "*" + sugarss: "*" + terser: ^5.4.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + bin: + vite: bin/vite.js + checksum: 79620413e45804494bedb81f2b78b48ca1d26fb026894a114a2f40cb1a3ad9b6783e777bc8969a51f8711e168b4db93a9563a5af027c80eecf000ef9b675a3be + languageName: node + linkType: hard + "vite@npm:~4.3.9": version: 4.3.9 resolution: "vite@npm:4.3.9" @@ -51720,6 +52136,56 @@ __metadata: languageName: node linkType: hard +"vitest@npm:^1.6.0": + version: 1.6.0 + resolution: "vitest@npm:1.6.0" + dependencies: + "@vitest/expect": 1.6.0 + "@vitest/runner": 1.6.0 + "@vitest/snapshot": 1.6.0 + "@vitest/spy": 1.6.0 + "@vitest/utils": 1.6.0 + acorn-walk: ^8.3.2 + chai: ^4.3.10 + debug: ^4.3.4 + execa: ^8.0.1 + local-pkg: ^0.5.0 + magic-string: ^0.30.5 + pathe: ^1.1.1 + picocolors: ^1.0.0 + std-env: ^3.5.0 + strip-literal: ^2.0.0 + tinybench: ^2.5.1 + tinypool: ^0.8.3 + vite: ^5.0.0 + vite-node: 1.6.0 + why-is-node-running: ^2.2.2 + peerDependencies: + "@edge-runtime/vm": "*" + "@types/node": ^18.0.0 || >=20.0.0 + "@vitest/browser": 1.6.0 + "@vitest/ui": 1.6.0 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + bin: + vitest: vitest.mjs + checksum: a9b9b97e5685d630e5d8d221e6d6cd2e1e9b5b2dd61e82042839ef11549c8d2d780cf696307de406dce804bf41c1219398cb20b4df570b3b47ad1e53af6bfe51 + languageName: node + linkType: hard + "void-elements@npm:^3.1.0": version: 3.1.0 resolution: "void-elements@npm:3.1.0" @@ -52168,6 +52634,21 @@ __metadata: languageName: node linkType: hard +"vue-tsc@npm:^1.8.27": + version: 1.8.27 + resolution: "vue-tsc@npm:1.8.27" + dependencies: + "@volar/typescript": ~1.11.1 + "@vue/language-core": 1.8.27 + semver: ^7.5.4 + peerDependencies: + typescript: "*" + bin: + vue-tsc: bin/vue-tsc.js + checksum: 98c2986df01000a3245b5f08b9db35d0ead4f46fb12f4fe771257b4aa61aa4c26dda359aaa0e6c484a6240563d5188aaa6ed312dd37cc2315922d5e079260001 + languageName: node + linkType: hard + "vue-tsc@npm:^1.8.8": version: 1.8.8 resolution: "vue-tsc@npm:1.8.8"