diff --git a/packages/schema/README.md b/packages/schema/README.md index 2427256379f..05140dfc5e0 100644 --- a/packages/schema/README.md +++ b/packages/schema/README.md @@ -28,11 +28,7 @@ Its like GraphQL, RPC, or Protobuf... but for anything! -## Usage - -> ⚠️ Private - -This package may currently only be used within EmberData. A public version is coming soon 💜 +--- ## Installation @@ -53,7 +49,7 @@ pnpm install @warp-drive/schema Scaffold the schema for a `User` resource ```no-highlight -npx -p @warp-drive-schema resource user +npx -p @warp-drive/schema resource user ``` Parse Schemas diff --git a/packages/schema/package.json b/packages/schema/package.json index b86f24978b7..3ef737a424e 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -23,9 +23,8 @@ "scripts": { "build:parse": "bun build src/parse.ts --compile --outfile parse", "build:scaffold": "bun build src/scaffold.ts --compile --outfile scaffold", - "build": "bun run build:parse && bun run build:scaffold", - "prepack": "bun run _build", - "_syncPnpm": "bun run sync-dependencies-meta-injected" + "_build": "bun run build:parse && bun run build:scaffold", + "prepack": "bun run _build" }, "files": [ "README.md", @@ -38,15 +37,16 @@ "resource": "./scaffold resource" }, "main": "src/index.ts", - "peerDependencies": { - "typescript": "^5.4.5" - }, - "dependencies": { - "chalk": "^5.3.0" - }, + "peerDependencies": {}, + "dependencies": {}, "devDependencies": { "bun-types": "1.1.3", - "pnpm-sync-dependencies-meta-injected": "0.0.10" + "chalk": "^5.3.0", + "@babel/parser": "^7.24.4", + "@babel/traverse": "^7.24.1", + "@types/babel__parser": "^7.1.1", + "@types/babel__traverse": "^7.20.5", + "typescript": "^5.4.5" }, "engines": { "node": ">= 18.20.2" diff --git a/packages/schema/parse b/packages/schema/parse index 32b77745d47..ba8c2dab16f 100755 Binary files a/packages/schema/parse and b/packages/schema/parse differ diff --git a/packages/schema/scaffold b/packages/schema/scaffold index 55903359ffc..f51010a378c 100755 Binary files a/packages/schema/scaffold and b/packages/schema/scaffold differ diff --git a/packages/schema/src/parse.ts b/packages/schema/src/parse.ts index 5eec2faf4ae..697f9c042dd 100644 --- a/packages/schema/src/parse.ts +++ b/packages/schema/src/parse.ts @@ -1,3 +1,14 @@ -async function main() {} +import { getSchemaConfig } from './parser/steps/get-config'; +import { gatherSchemaFiles } from './parser/steps/gather-schema-files'; +import { compileJSONSchemas } from './parser/compile/json'; + +async function main() { + const config = await getSchemaConfig(); + + const modules = await gatherSchemaFiles(config); + const compiledJson = await compileJSONSchemas(modules); + + console.log(compiledJson); +} await main(); diff --git a/packages/schema/src/parser/compile/json-schema-spec.ts b/packages/schema/src/parser/compile/json-schema-spec.ts new file mode 100644 index 00000000000..0110ed2446f --- /dev/null +++ b/packages/schema/src/parser/compile/json-schema-spec.ts @@ -0,0 +1,568 @@ +import { ObjectValue, PrimitiveValue } from '../utils/extract-json'; + +/** + * A generic "field" that can be used to define + * primitive value fields. + * + * Replaces "attribute" for primitive value fields. + * Can also be used to eject from deep-tracking of + * objects or arrays. + * + * A major difference between "field" and "attribute" + * is that "type" points to a legacy transform on + * "attribute" that a serializer *might* use, while + * "type" points to a new-style transform on "field" + * that a record implmentation *must* use. + */ +export type GenericField = { + kind: 'field'; + name: string; + /** the name of the transform to use, if any */ + type?: string; + /** + * Options to pass to the transform, if any + * + * Must comply to the specific transform's options + * schema. + */ + options?: ObjectValue; +}; + +/** + * Represents a field whose value is the primary + * key of the resource. + * + * This allows any field to serve as the primary + * key while still being able to drive identity + * needs within the system. + * + * This is useful for resources that use for instance + * 'uuid', 'urn' or 'entityUrn' or 'primaryKey' as their + * primary key field instead of 'id'. + */ +export type IdentityField = { + kind: '@id'; + + /** + * The name of the field that serves as the + * primary key for the resource. + */ + name: string; +}; + +/** + * Represents a field whose value is a local + * value that is not stored in the cache, nor + * is it sent to the server. + * + * Local fields can be written to, and their + * value is both memoized and reactive (though + * not deep-tracked). + * + * Because their state is not derived from the cache + * data or the server, they represent a divorced + * uncanonical source of state. + * + * For this reason Local fields should be used sparingly. + * + * In the future, we may choose to only allow our + * own SchemaRecord to utilize them. + * + * Example use cases that drove the creation of local + * fields are states like `isDestroying` and `isDestroyed` + * which are specific to a record instance but not + * stored in the cache. We wanted to be able to drive + * these fields from schema the same as all other fields. + * + * Don't make us regret this decision. + */ +export type LocalField = { + kind: '@local'; + name: string; + /** + * Not currently utilized, we are considering + * allowing transforms to operate on local fields + */ + type?: string; + options?: { defaultValue?: PrimitiveValue }; +}; + +/** + * Represents a field whose value is an object + * with keys pointing to values that are primitive + * values. + * + * If values of the keys are not primitives, or + * if the key/value pairs have well-defined shape, + * use 'schema-object' instead. + */ +export type ObjectField = { + kind: 'object'; + name: string; + + /** + * The name of a transform to pass the entire object + * through before displaying or serializing it. + */ + type?: string; + + /** + * Options to pass to the transform, if any + * + * Must comply to the specific transform's options + * schema. + */ + options?: ObjectValue; +}; + +/** + * Represents a field whose value is an object + * with a well-defined structure described by + * a non-resource schema. + * + * If the object's structure is not well-defined, + * use 'object' instead. + */ +export type SchemaObjectField = { + kind: 'schema-object'; + name: string; + + /** + * The name of the schema that describes the + * structure of the object. + * + * These schemas + */ + type: string; + + // FIXME: would we ever need options here? + options?: ObjectValue; +}; + +/** + * Represents a field whose value is an array + * of primitive values. + * + * If the array's elements are not primitive + * values, use 'schema-array' instead. + */ +export type ArrayField = { + kind: 'array'; + name: string; + + /** + * The name of a transform to pass each item + * in the array through before displaying or + * or serializing it. + */ + type?: string; + + /** + * Options to pass to the transform, if any + * + * Must comply to the specific transform's options + * schema. + */ + options?: ObjectValue; +}; + +/** + * Represents a field whose value is an array + * of objects with a well-defined structure + * described by a non-resource schema. + * + * If the array's elements are not well-defined, + * use 'array' instead. + */ +export type SchemaArrayField = { + kind: 'schema-array'; + name: string; + + /** + * The name of the schema that describes the + * structure of the objects in the array. + */ + type: string; + + // FIXME: would we ever need options here? + options?: ObjectValue; +}; + +/** + * Represents a field whose value is derived + * from other fields in the schema. + * + * The value is read-only, and is not stored + * in the cache, nor is it sent to the server. + * + * Usage of derived fields should be minimized + * to scenarios where the derivation is known + * to be safe. For instance, derivations that + * required fields that are not always loaded + * or that require access to related resources + * that may not be loaded should be avoided. + */ +export type DerivedField = { + kind: 'derived'; + name: string; + + /** + * The name of the derivation to use. + * + * Derivations are functions that take the + * record, options, and the name of the field + * as arguments, and return the derived value. + * + * Derivations are memoized, and are only + * recomputed when the fields they depend on + * change. + * + * Derivations are not stored in the cache, + * and are not sent to the server. + * + * Derivation functions must be explicitly + * registered with the schema service. + */ + type: string; + + /** + * Options to pass to the derivation, if any + * + * Must comply to the specific derivation's + * options schema. + */ + options?: ObjectValue; +}; + +/** + * Represents a field that is a reference to + * another resource. + */ +export type ResourceField = { + kind: 'resource'; + name: string; + + /** + * The name of the resource that this field + * refers to. In the case of a polymorphic + * relationship, this should be the trait + * or abstract type. + */ + type: string; + + /** + * Options for resources are optional. If + * not present, all options are presumed + * to be falsey + */ + options?: { + /** + * Whether the relationship is async + * + * If true, it is expected that the cache + * data for this field will contain a link + * that can be used to fetch the related + * resource when needed. + */ + async?: boolean; + + /** + * The name of the inverse field on the + * related resource that points back to + * this field on this resource to form a + * bidirectional relationship. + * + * If null, the relationship is unidirectional. + */ + inverse?: string | null; + + /** + * If this field is satisfying a polymorphic + * relationship on another resource, then this + * should be set to the trait or abstract type + * that this resource implements. + */ + as?: string; + + /** + * Whether this field is a polymorphic relationship, + * meaning that it can point to multiple types of + * resources so long as they implement the trait + * or abstract type specified in `type`. + */ + polymorphic?: boolean; + }; +}; + +/** + * Represents a field that is a reference to + * a collection of other resources, potentially + * paginate. + */ +export type CollectionField = { + kind: 'collection'; + name: string; + + /** + * The name of the resource that this field + * refers to. In the case of a polymorphic + * relationship, this should be the trait + * or abstract type. + */ + type: string; + + /** + * Options for resources are optional. If + * not present, all options are presumed + * to be falsey + */ + options?: { + /** + * Whether the relationship is async + * + * If true, it is expected that the cache + * data for this field will contain links + * that can be used to fetch the related + * resources when needed. + * + * When false, it is expected that all related + * resources are loaded together with this resource, + * and that the cache data for this field will + * contain the full list of pointers. + * + * When true, it is expected that the relationship + * is paginated. If the relationship is not paginated, + * then the cache data for "page 1" would contain the + * full list of pointers, and loading "page 1" would + * load all related resources. + */ + async?: boolean; + + /** + * The name of the inverse field on the + * related resource that points back to + * this field on this resource to form a + * bidirectional relationship. + * + * If null, the relationship is unidirectional. + */ + inverse?: string | null; + + /** + * If this field is satisfying a polymorphic + * relationship on another resource, then this + * should be set to the trait or abstract type + * that this resource implements. + */ + as?: string; + + /** + * Whether this field is a polymorphic relationship, + * meaning that it can point to multiple types of + * resources so long as they implement the trait + * or abstract type specified in `type`. + */ + polymorphic?: boolean; + }; +}; + +/** + * > [!CAUTION] + * > This Field is LEGACY + * + * A generic "field" that can be used to define + * primitive value fields. + * + * If the field points to an object or array, + * it will not be deep-tracked. + * + * Transforms when defined are legacy transforms + * that a serializer *might* use, but their usage + * is not guaranteed. + */ +export type LegacyAttributeField = { + kind: 'attribute'; + name: string; + /** the name of the transform to use, if any */ + type?: string; + /** + * Options to pass to the transform, if any + * + * Must comply to the specific transform's options + * schema. + */ + options?: ObjectValue; +}; + +/** + * * > [!CAUTION] + * > This Field is LEGACY + * + * Represents a field that is a reference to + * another resource. + * + * This is the legacy version of the `ResourceField` + * type, and is used to represent fields that were + */ +export type LegacyBelongsToField = { + kind: 'belongsTo'; + name: string; + + /** + * The name of the resource that this field + * refers to. In the case of a polymorphic + * relationship, this should be the trait + * or abstract type. + */ + type: string; + + /** + * Options for belongsTo are mandatory. + */ + options: { + /** + * Whether the relationship is async + * + * If true, it is expected that the cache + * data for this field will contain a link + * or a pointer that can be used to fetch + * the related resource when needed. + * + * Pointers are highly discouraged. + */ + async: boolean; + + /** + * The name of the inverse field on the + * related resource that points back to + * this field on this resource to form a + * bidirectional relationship. + * + * If null, the relationship is unidirectional. + */ + inverse: string | null; + + /** + * If this field is satisfying a polymorphic + * relationship on another resource, then this + * should be set to the trait or abstract type + * that this resource implements. + */ + as?: string; + + /** + * Whether this field is a polymorphic relationship, + * meaning that it can point to multiple types of + * resources so long as they implement the trait + * or abstract type specified in `type`. + */ + polymorphic?: boolean; + }; +}; + +/** + * > [!CAUTION] + * > This Field is LEGACY + */ +export type LegacyHasManyField = { + kind: 'hasMany'; + name: string; + type: string; + + /** + * Options for hasMany are mandatory. + */ + options: { + /** + * Whether the relationship is async + * + * If true, it is expected that the cache + * data for this field will contain links + * or pointers that can be used to fetch + * the related resources when needed. + * + * When false, it is expected that all related + * resources are loaded together with this resource, + * and that the cache data for this field will + * contain the full list of pointers. + * + * hasMany relationships do not support pagination. + */ + async: boolean; + + /** + * The name of the inverse field on the + * related resource that points back to + * this field on this resource to form a + * bidirectional relationship. + * + * If null, the relationship is unidirectional. + */ + inverse: string | null; + + /** + * If this field is satisfying a polymorphic + * relationship on another resource, then this + * should be set to the trait or abstract type + * that this resource implements. + */ + as?: string; + + /** + * Whether this field is a polymorphic relationship, + * meaning that it can point to multiple types of + * resources so long as they implement the trait + * or abstract type specified in `type`. + */ + polymorphic?: boolean; + + /** + * When omitted, the cache data for this field will + * clear local state of all changes except for the + * addition of records still in the "new" state any + * time the remote data for this field is updated. + * + * When set to `false`, the cache data for this field + * will instead intelligently commit any changes from + * local state that are present in the remote data, + * leaving any remaining changes in local state still. + */ + resetOnRemoteUpate?: false; + }; +}; + +export type SchemaField = + | GenericField + | IdentityField + | LocalField + | ObjectField + | SchemaObjectField + | ArrayField + | SchemaArrayField + | DerivedField + | ResourceField + | CollectionField + | LegacyAttributeField + | LegacyBelongsToField + | LegacyHasManyField; + +export type Schema = { + '@id': IdentityField | null; + /** + * The name of the schema + * + * For cacheable resources, this should be the + * primary resource type. + * + * For object schemas, this should be the name + * of the object schema. object schemas should + * follow the following guidelines for naming + * + * - for globally shared objects: The pattern `$field:${KlassName}` e.g. `$field:AddressObject` + * - for resource-specific objects: The pattern `$${ResourceKlassName}:$field:${KlassName}` e.g. `$User:$field:ReusableAddress` + * - for inline objects: The pattern `$${ResourceKlassName}.${fieldPath}:$field:anonymous` e.g. `$User.shippingAddress:$field:anonymous` + */ + '@type': string; + traits: string[]; + fields: SchemaField[]; +}; diff --git a/packages/schema/src/parser/compile/json.ts b/packages/schema/src/parser/compile/json.ts new file mode 100644 index 00000000000..2b780dcf16f --- /dev/null +++ b/packages/schema/src/parser/compile/json.ts @@ -0,0 +1,42 @@ +import chalk from 'chalk'; +import { SchemaModule } from '../utils/process-file'; +import { write } from '../utils/utils'; +import { Schema } from './json-schema-spec'; + +export async function compileJSONSchemas(modules: Map) { + const compiled: Schema[] = []; + + for (const [filePath, module] of modules) { + if (module.exports.length === 0) { + write( + `\n\t\t${chalk.bold(chalk.yellow('⚠️ caution: '))} No exported schemas found in ${chalk.bold(chalk.yellow(filePath))}` + ); + } + + if (module.exports.length > 1) { + write( + `\n\t\t${chalk.bold(chalk.red('❌ error: '))} Multiple exported schemas found in ${chalk.bold(chalk.red(filePath))}` + ); + process.exit(1); + } + + const klassSchema = module.exports[0]; + const { FullKlassType, KlassType, fullType } = module.$potentialPrimaryResourceType; + + if (klassSchema.name !== FullKlassType && klassSchema.name !== KlassType) { + write( + `\n\t\t${chalk.bold(chalk.yellow('⚠️ caution: '))} Exported schema ${chalk.bold(klassSchema.name)} in ${fullType} does not seem to match the expected name of ${chalk.bold(FullKlassType)}` + ); + } + + const schema: Partial = { + '@type': fullType, + }; + + // compile traits + + // compile fields + } + + return compiled; +} diff --git a/packages/schema/src/parser/steps/gather-schema-files.ts b/packages/schema/src/parser/steps/gather-schema-files.ts new file mode 100644 index 00000000000..4de48d07c9a --- /dev/null +++ b/packages/schema/src/parser/steps/gather-schema-files.ts @@ -0,0 +1,24 @@ +import chalk from 'chalk'; +import path from 'path'; +import { Glob } from 'bun'; +import { SchemaModule, parseSchemaFile } from '../utils/process-file'; +import { write } from '../utils/utils'; +import { SchemaConfig } from './get-config'; + +export async function gatherSchemaFiles(config: SchemaConfig) { + const { fullSchemaDirectory, relativeSchemaDirectory } = config; + write(`\n\t\tParsing schema files from ${chalk.bold(chalk.cyan(relativeSchemaDirectory))}`); + const modules = new Map(); + + const glob = new Glob(`**/*.ts`); + for await (const filePath of glob.scan(fullSchemaDirectory)) { + write(`\n\t\tParsing ${chalk.bold(chalk.cyan(filePath))}`); + const fullPath = path.join(fullSchemaDirectory, filePath); + const file = Bun.file(fullPath); + const contents = await file.text(); + const schemaModule = await parseSchemaFile(filePath, contents); + modules.set(filePath, schemaModule); + } + + return modules; +} diff --git a/packages/schema/src/parser/steps/get-config.ts b/packages/schema/src/parser/steps/get-config.ts new file mode 100644 index 00000000000..111243147b3 --- /dev/null +++ b/packages/schema/src/parser/steps/get-config.ts @@ -0,0 +1,40 @@ +import chalk from 'chalk'; +import path from 'path'; +import { write } from '../utils/utils'; + +export type SchemaConfig = Awaited>; + +export async function getSchemaConfig() { + const args = Bun.argv.slice(2); + const [schemaPath] = args; + + write( + `\n\t ${chalk.yellow('$')} ${chalk.bold(chalk.greenBright('@warp-drive/') + chalk.magentaBright('schema'))} ${chalk.cyan(chalk.bold('parse'))} ${schemaPath ?? chalk.red('')}` + ); + + if (!schemaPath) { + write(`\n\t${chalk.bold('💥 Error')} Please supply a path to the schema file to parse!\n`); + process.exit(1); + } + + const schemaFile = Bun.file(schemaPath); + const schemaFileExists = await schemaFile.exists(); + + if (!schemaFileExists) { + write(`\n\t${chalk.bold('💥 Error')} ${chalk.white(schemaPath)} does not exist!`); + process.exit(1); + } + + const config = await schemaFile.json(); + const schemaDirectory = path.join(process.cwd(), path.dirname(schemaPath), config.schemas); + const schemaDestination = path.join(process.cwd(), path.dirname(schemaPath), config.dest); + + return { + _config: config, + schemaPath, + relativeSchemaDirectory: path.relative(process.cwd(), schemaDirectory), + relativeSchemaDestination: path.relative(process.cwd(), schemaDestination), + fullSchemaDirectory: schemaDirectory, + fullSchemaDestination: schemaDestination, + }; +} diff --git a/packages/schema/src/parser/utils/extract-json.ts b/packages/schema/src/parser/utils/extract-json.ts new file mode 100644 index 00000000000..b7cbfde0877 --- /dev/null +++ b/packages/schema/src/parser/utils/extract-json.ts @@ -0,0 +1,76 @@ +import { type Node } from '@babel/types'; + +const SerializableTypes = new Set([ + 'StringLiteral', + 'NullLiteral', + 'NumericLiteral', + 'BooleanLiteral', + 'ArrayExpression', + 'ObjectExpression', +]); + +export type PrimitiveValue = string | number | boolean | null; +export interface ObjectValue { + [key: string]: JSONValue; +} +export type ArrayValue = JSONValue[]; +export type JSONValue = PrimitiveValue | ArrayValue | ObjectValue; + +function getJsonValueFromNode(node: Node): JSONValue { + if (!SerializableTypes.has(node.type)) { + console.log(node); + throw new Error(`Values in Schemas must be JSON Serializable. Type ${node.type} is not serializable to JSON.`); + } + + switch (node.type) { + case 'StringLiteral': + return node.value; + case 'BooleanLiteral': + return node.value; + case 'NullLiteral': + return null; + case 'NumericLiteral': + return node.value; + case 'ArrayExpression': + return node.elements.map((element) => { + if (!element) { + throw new Error('Empty array elements like null are not supported.'); + } + if (element.type === 'SpreadElement') { + throw new Error('Spread elements are not supported.'); + } + return getJsonValueFromNode(element); + }); + case 'ObjectExpression': + return extractJSONObject(node); + default: + throw new Error(`Unsupported node type: ${node.type}`); + } +} + +export function extractJSONObject(node: Node): ObjectValue { + if (node.type !== 'ObjectExpression') { + throw new Error('Only ObjectExpression is supported.'); + } + + const properties: ObjectValue = {}; + node.properties.forEach((property) => { + if (property.type !== 'ObjectProperty') { + throw new Error('Only ObjectProperty is supported.'); + } + if (property.key.type !== 'Identifier') { + throw new Error('Only Identifier is supported.'); + } + + if (!SerializableTypes.has(property.value.type)) { + console.log(property); + throw new Error( + `Values in Schemas must be JSON Serializable. Type ${property.value.type} is not serializable to JSON.` + ); + } + + properties[property.key.name] = getJsonValueFromNode(property.value); + }); + + return properties; +} diff --git a/packages/schema/src/parser/utils/process-file.ts b/packages/schema/src/parser/utils/process-file.ts new file mode 100644 index 00000000000..569fb51c253 --- /dev/null +++ b/packages/schema/src/parser/utils/process-file.ts @@ -0,0 +1,268 @@ +import { type TraverseOptions } from '@babel/traverse'; +import { type ClassProperty, type Node } from '@babel/types'; +import { extractJSONObject } from './extract-json'; +import path from 'path'; + +import babel from '@babel/parser'; +import _traverse from '@babel/traverse'; + +// bun compile has a bug where traverse gets unwrapped improperly +// so we have to manually grab the default export +const traverse = (_traverse as unknown as { default: typeof _traverse }).default; + +function normalizeResourceType(fileName: string) { + const dirname = path.dirname(fileName); + const [resourceType] = path.basename(fileName).split('.'); + + const fullType = dirname === '.' ? resourceType : `${dirname}/${resourceType}`; + const matchType = resourceType; + const FullKlassType = fullType + .split('-') + .map((word) => { + return word[0].toUpperCase() + word.slice(1); + }) + .join('') + .split('/') + .map((word) => { + return word[0].toUpperCase() + word.slice(1); + }) + .join(''); + const KlassType = matchType + .split('-') + .map((word) => { + return word[0].toUpperCase() + word.slice(1); + }) + .join(''); + + return { + fullType, + matchType, + KlassType, + FullKlassType, + }; +} + +// TODO do this via import matching +const TypeDecorators = new Set(['createonly', 'optional', 'readonly', 'nullable', 'readonly'] as const); +type TypeDecorator = 'createonly' | 'optional' | 'readonly' | 'nullable' | 'readonly'; + +function isTypeDecorator(decorator: string): decorator is TypeDecorator { + return TypeDecorators.has(decorator as unknown as TypeDecorator); +} + +function numToIndexStr(num: number): string { + switch (num) { + case 0: + return '1st'; + case 1: + return '2nd'; + case 2: + return '3rd'; + default: + return `${num + 1}th`; + } +} + +/** + * Extracts the type signature from a ClassProperty + * + * @typedoc + */ +function extractType(field: ClassProperty) { + if (field.typeAnnotation) { + if (field.typeAnnotation.type !== 'TSTypeAnnotation') { + throw new Error('Only TSTypeAnnotation is supported.'); + } + if (field.typeAnnotation.typeAnnotation.type === 'TSUnionType') { + return field.typeAnnotation.typeAnnotation.types.map((type) => { + return type.type; + }); + } else { + return field.typeAnnotation.typeAnnotation.type; + } + } + return null; +} + +/** + * Extracts the value of the first argument of a decorator + */ +function extractFieldType(node: Node) { + if (!node) { + return null; + } + if (node.type === 'Identifier') { + return node.name; + } + if (node.type === 'StringLiteral') { + return `'${node.value}'`; + } + console.log(node); + throw new Error('Only Identifier is supported.'); +} + +/** + * Extracts the value of the second argument of a decorator, + * which is expected to be an object literal + */ +function extractOptions(node: Node) { + if (!node) { + return null; + } + return extractJSONObject(node); +} + +export type FieldSchema = { + kind: string; + name: string; + type: string | null; + options: {} | null; + typeOptions: { + nullable: boolean; + optional: boolean; + createonly: boolean; + readonly: boolean; + }; + typeInfo: { + uiType: string; + cacheType: string; + }; +}; + +export type Schema = { + name: string; + traits: string[]; + fields: FieldSchema[]; +}; +export type SchemaSource = { + type: 'trait' | 'schema' | 'decorator'; + imported: string; + local: string; + source: string; +}; +export type SchemaModule = { + $potentialPrimaryResourceType: ReturnType; + externals: SchemaSource[]; + internal: Schema[]; + inline: Schema[]; + exports: Schema[]; +}; + +export function buildTraverse(schemModule: SchemaModule): TraverseOptions { + let currentClass: Schema | null = null; + + return { + FunctionDeclaration() { + throw new Error('Functions are not allowed in schemas.'); + }, + VariableDeclaration() { + throw new Error('Variables are not allowed in schemas.'); + }, + + ClassDeclaration: { + enter(path) { + currentClass = {} as Schema; + + // gather name + const id = path.node.id?.name; + if (!id) { + throw new Error('Schemas must have a class name.'); + } + currentClass.name = id!; + + // enforce no extends + if (path.node.superClass) { + throw new Error('Schemas cannot extend from base classes. Use traits.'); + } + + // gather traits + currentClass.traits = + path.node.decorators?.map((decorator) => { + if (decorator.expression.type !== 'Identifier') { + throw new Error('Traits may not have arguments.'); + } + return decorator.expression.name; + }) ?? []; + + // gather fields + currentClass.fields = path.node.body.body.map((field) => { + if (field.type !== 'ClassProperty') { + throw new Error('Schemas may only have fields.'); + } + + if (field.key.type !== 'Identifier') { + throw new Error('Fields must only be string keys.'); + } + + const fieldSchema = { + kind: 'field', + name: field.key.name, + type: null, + options: null, + typeOptions: { + nullable: false, + optional: false, + createonly: false, + readonly: false, + }, + typeInfo: { + optional: field.optional, + uiType: extractType(field), + cacheType: extractType(field), + }, + } as FieldSchema; + + if (field.decorators) { + for (let i = 0; i < field.decorators.length; i++) { + const decorator = field.decorators[i]; + + if (decorator.expression.type === 'Identifier' && isTypeDecorator(decorator.expression.name)) { + fieldSchema.typeOptions[decorator.expression.name] = true; + } else if (i === field.decorators.length - 1) { + if (decorator.expression.type === 'Identifier') { + fieldSchema.kind = decorator.expression.name; + } else if (decorator.expression.type === 'CallExpression') { + if (decorator.expression.callee.type !== 'Identifier') { + throw new Error(`Unable to parse the ${numToIndexStr(i)} field decorator for ${fieldSchema.name}`); + } + fieldSchema.kind = decorator.expression.callee.name; + fieldSchema.type = extractFieldType(decorator.expression.arguments[0]); + fieldSchema.options = extractOptions(decorator.expression.arguments[1]); + } else { + console.log(decorator.expression); + throw new Error(`Unable to parse the ${numToIndexStr(i)} field decorator for ${fieldSchema.name}`); + } + } else { + throw new Error(`Decorators used to declare the field type must be last.`); + } + } + } + + return fieldSchema; + }); + }, + exit(path) { + console.dir(currentClass, { depth: 10 }); + }, + }, + }; +} + +export async function parseSchemaFile(fileName: string, $contents: string): Promise { + const $potentialPrimaryResourceType = normalizeResourceType(fileName); + const ast = babel.parse($contents, { + sourceType: 'module', + plugins: ['classProperties', 'classPrivateProperties', 'classStaticBlock', ['typescript', {}], ['decorators', {}]], + }); + + const context = { + $potentialPrimaryResourceType, + externals: [], + internal: [], + inline: [], + exports: [], + }; + traverse(ast, buildTraverse(context)); + + return context; +} diff --git a/packages/schema/src/parser/utils/utils.ts b/packages/schema/src/parser/utils/utils.ts new file mode 100644 index 00000000000..c3fadefb2ec --- /dev/null +++ b/packages/schema/src/parser/utils/utils.ts @@ -0,0 +1,5 @@ +import chalk from 'chalk'; + +export function write($text: string) { + process.stdout.write(chalk.gray($text)); +} diff --git a/packages/schema/src/scaffold.ts b/packages/schema/src/scaffold.ts index 5eec2faf4ae..1bda24499d3 100644 --- a/packages/schema/src/scaffold.ts +++ b/packages/schema/src/scaffold.ts @@ -1,3 +1,164 @@ -async function main() {} +import chalk from 'chalk'; +import path from 'path'; +import os from 'os'; + +function write($text: string) { + console.log(chalk.gray($text)); +} + +const Scaffolds = ['resource', 'trait', 'field', 'derivation', 'transform']; + +function getRelativePathToRoot($path: string) { + return `~/${path.relative(os.homedir(), $path)}`; +} + +async function loadOrCreateConfig(): Promise & { DID_GENERATE: boolean }> { + const configPath = path.join(process.cwd(), './schema.json'); + const filePointer = Bun.file(configPath); + const fileExists = await filePointer.exists(); + + if (fileExists) { + const config = await filePointer.json(); + config.DID_GENERATE = false; + return config; + } + + const config: Record = { + schemas: './schemas', + dest: './dist', + }; + + write( + `\n\t🔨 Generating new ${chalk.yellow('schema.json')} configuration file in ${chalk.cyan(getRelativePathToRoot(process.cwd()))}` + ); + + await Bun.write(filePointer, JSON.stringify(config, null, 2)); + config.DID_GENERATE = true; + return config as Record & { DID_GENERATE: true }; +} + +function classify($name: string) { + let str = $name + .split('-') + .map((word) => { + return word[0].toUpperCase() + word.slice(1); + }) + .join(''); + str = str + .split('_') + .map((word) => { + return word[0].toUpperCase() + word.slice(1); + }) + .join(''); + str = str[0].toUpperCase() + str.slice(1); + return str; +} + +function singularize($name: string) { + if ($name.endsWith('ies')) { + return $name.slice(0, -3) + 'y'; + } else if ($name.endsWith('es')) { + return $name.slice(0, -2); + } else if ($name.endsWith('s')) { + return $name.slice(0, -1); + } else { + return $name; + } +} + +function generateFirstResource($type: string) { + const className = classify(singularize($type)); + + return `import { collection, createonly, derived, field, optional, readonly, resource, Resource } from '@warp-drive/schema-decorators'; + +@Resource // Resource is a default "Trait" that provides the "id" and "$type" fields used by @warp-drive/schema-record +class ${className} { + // @optional - An optional field is one that may be omitted during create. + // @readonly - A readonly field is one that may never be created or edited. + // @createonly - A createonly field is one that may only be set during create. + + // We use declare to tell TypeScript that this field exists + // We use the declared type to set the "cache" type for the field (what the API returns) + // declare myField: string; + + // We use the field decorator to provide a "Transform" function for the field. + // The transform's return type will be used as the "UI" type for the field. + // e.g. "Date" instead of "string" + // @field('luxon') declare someDateField: string; + + // We use the collection decorator to create a linkage to a collection of other resources + // @collection('comment', { inverse: 'post' }) declare comments: Comment[]; + + // We use the resource decorator to create a linkage to another resource + // if the related resource will not always be present use \`| null\` with the type + // @resource('user', { inverse: 'posts' }) declare author: User; + + // We use the derived decorator to create a field that is derived from other fields + // Note your project can provide its own decorators that can simplify this. + // @derived('concat', { fields: ['firstName', 'lastName'], separator: ' ' }) declare fullName: string; +} + +export { ${className} }; +`; +} + +function generateResource($type: string) { + const className = classify(singularize($type)); + + return `import { Resource } from '@warp-drive/schema-decorators'; + +@Resource +class ${className} { + // ... +} + +export { ${className} }; +`; +} + +async function main() { + const args = Bun.argv.slice(2); + const [resource, name] = args; + + write( + `\n\t $ ${chalk.bold(chalk.greenBright('@warp-drive/') + chalk.magentaBright('schema'))} ${chalk.bold('scaffold')} ${resource ?? chalk.red('')} ${name ?? chalk.red('')}` + ); + + if (!Scaffolds.includes(resource)) { + write(`\n\t${chalk.bold('💥 Error')} ${chalk.white(resource)} is not a valid scaffold.`); + write(`\n\t${chalk.bold('Available Scaffolds')}\n\t\t◆ ${Scaffolds.join(',\n\t\t◆ ')}\n`); + return; + } + + if (!name) { + write(`\n\t${chalk.bold('💥 Error')} Please supply a name for the ${chalk.white(resource)} to scaffold!\n`); + return; + } + + const config = await loadOrCreateConfig(); + const relativeWritePath = + resource === 'resource' ? `${config.schemas}/${name}.ts` : `${config.schemas}/-${resource}s/${name}.ts`; + + const file = Bun.file(path.join(process.cwd(), relativeWritePath)); + const fileExists = await file.exists(); + + if (fileExists) { + write(`\n\t${chalk.bold('💥 Error')} ${chalk.white(relativeWritePath)} already exists! Skipping Scaffold.\n`); + return; + } + + switch (resource) { + case 'resource': + await Bun.write(file, config.DID_GENERATE ? generateFirstResource(name) : generateResource(name)); + break; + default: + break; + } + + write( + `\n\t🔨 Scaffolding new ${chalk.bold(chalk.cyan(name))} ${chalk.bold(chalk.white(resource))} in ${relativeWritePath}...` + ); + console.log(args); +} await main(); diff --git a/packages/schema/tsconfig.json b/packages/schema/tsconfig.json index 3c77dd06054..59f2eccae5e 100644 --- a/packages/schema/tsconfig.json +++ b/packages/schema/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "lib": ["ESNext"], + "lib": ["ESNext", "DOM"], "module": "esnext", "target": "esnext", "moduleResolution": "bundler", @@ -13,6 +13,7 @@ "allowSyntheticDefaultImports": true, "forceConsistentCasingInFileNames": true, "allowJs": true, - "noEmit": true + "noEmit": true, + "types": ["bun-types"] } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ccee2fbfd8d..58e73747267 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,10 +135,10 @@ importers: version: 7.24.1(@babel/core@7.24.4) '@typescript-eslint/eslint-plugin': specifier: ^7.6.0 - version: 7.6.0(@typescript-eslint/parser@7.6.0)(eslint@8.57.0)(typescript@5.4.5) + version: 7.7.0(@typescript-eslint/parser@7.7.0)(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/parser': specifier: ^7.6.0 - version: 7.6.0(eslint@8.57.0)(typescript@5.4.5) + version: 7.7.0(eslint@8.57.0)(typescript@5.4.5) eslint: specifier: ^8.57.0 version: 8.57.0 @@ -147,7 +147,7 @@ importers: version: 9.1.0(eslint@8.57.0) eslint-plugin-import: specifier: ^2.29.1 - version: 2.29.1(@typescript-eslint/parser@7.6.0)(eslint@8.57.0) + version: 2.29.1(@typescript-eslint/parser@7.7.0)(eslint@8.57.0) eslint-plugin-mocha: specifier: ^10.4.2 version: 10.4.2(eslint@8.57.0) @@ -985,7 +985,7 @@ importers: version: 5.3.0 hono: specifier: ^4.2.4 - version: 4.2.4 + version: 4.2.5 pm2: specifier: ^5.3.1 version: 5.3.1 @@ -1767,17 +1767,28 @@ importers: injected: true packages/schema: - dependencies: - chalk: - specifier: ^5.3.0 - version: 5.3.0 devDependencies: + '@babel/parser': + specifier: ^7.24.4 + version: 7.24.4 + '@babel/traverse': + specifier: ^7.24.1 + version: 7.24.1(supports-color@8.1.1) + '@types/babel__parser': + specifier: ^7.1.1 + version: 7.1.1 + '@types/babel__traverse': + specifier: ^7.20.5 + version: 7.20.5 bun-types: specifier: 1.1.3 version: 1.1.3 - pnpm-sync-dependencies-meta-injected: - specifier: 0.0.10 - version: 0.0.10 + chalk: + specifier: ^5.3.0 + version: 5.3.0 + typescript: + specifier: ^5.4.5 + version: 5.4.5 packages/schema-record: dependencies: @@ -4770,7 +4781,6 @@ packages: /@babel/compat-data@7.24.4: resolution: {integrity: sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==} engines: {node: '>=6.9.0'} - dev: true /@babel/core@7.24.4(supports-color@8.1.1): resolution: {integrity: sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==} @@ -4817,15 +4827,6 @@ packages: '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 - /@babel/generator@7.24.1: - resolution: {integrity: sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.0 - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 2.5.2 - /@babel/generator@7.24.4: resolution: {integrity: sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==} engines: {node: '>=6.9.0'} @@ -5047,13 +5048,6 @@ packages: js-tokens: 4.0.0 picocolors: 1.0.0 - /@babel/parser@7.24.1: - resolution: {integrity: sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.24.0 - /@babel/parser@7.24.4: resolution: {integrity: sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==} engines: {node: '>=6.0.0'} @@ -5070,7 +5064,6 @@ packages: '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-plugin-utils': 7.24.0 - dev: true /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.24.1(@babel/core@7.24.4): resolution: {integrity: sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==} @@ -5110,7 +5103,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.24.4(supports-color@8.1.1) - '@babel/helper-create-class-features-plugin': 7.24.1(@babel/core@7.24.4) + '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-proposal-decorators@7.24.1(@babel/core@7.24.4): @@ -5132,7 +5125,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.24.4(supports-color@8.1.1) - '@babel/helper-create-class-features-plugin': 7.24.1(@babel/core@7.24.4) + '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.4): @@ -5152,7 +5145,7 @@ packages: dependencies: '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-create-class-features-plugin': 7.24.1(@babel/core@7.24.4) + '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.4) @@ -5392,7 +5385,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.24.4(supports-color@8.1.1) - '@babel/helper-create-class-features-plugin': 7.24.1(@babel/core@7.24.4) + '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-transform-class-static-block@7.24.1(@babel/core@7.24.4): @@ -5402,7 +5395,7 @@ packages: '@babel/core': ^7.12.0 dependencies: '@babel/core': 7.24.4(supports-color@8.1.1) - '@babel/helper-create-class-features-plugin': 7.24.1(@babel/core@7.24.4) + '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.4) @@ -5416,7 +5409,6 @@ packages: '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.4) - dev: true /@babel/plugin-transform-classes@7.24.1(@babel/core@7.24.4): resolution: {integrity: sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q==} @@ -5702,7 +5694,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.24.4(supports-color@8.1.1) - '@babel/helper-create-class-features-plugin': 7.24.1(@babel/core@7.24.4) + '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 /@babel/plugin-transform-private-property-in-object@7.24.1(@babel/core@7.24.4): @@ -5713,7 +5705,7 @@ packages: dependencies: '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-create-class-features-plugin': 7.24.1(@babel/core@7.24.4) + '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.4) @@ -5807,18 +5799,6 @@ packages: '@babel/core': 7.24.4(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.24.0 - /@babel/plugin-transform-typescript@7.24.1(@babel/core@7.24.4): - resolution: {integrity: sha512-liYSESjX2fZ7JyBFkYG78nfvHlMKE6IpNdTVnxmlYUR+j5ZLsitFbaAE+eJSK2zPPkNWNw4mXL51rQ8WrvdK0w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.4(supports-color@8.1.1) - '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-create-class-features-plugin': 7.24.1(@babel/core@7.24.4) - '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-typescript': 7.24.1(@babel/core@7.24.4) - /@babel/plugin-transform-typescript@7.24.4(@babel/core@7.24.4): resolution: {integrity: sha512-79t3CQ8+oBGk/80SQ8MN3Bs3obf83zJ0YZjDmDaEZN8MqhMI760apl5z6a20kFeMXBwJX99VpKT8CKxEBp5H1g==} engines: {node: '>=6.9.0'} @@ -5870,96 +5850,6 @@ packages: '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 - /@babel/preset-env@7.24.3(@babel/core@7.24.4): - resolution: {integrity: sha512-fSk430k5c2ff8536JcPvPWK4tZDwehWLGlBp0wrsBUjZVdeQV6lePbwKWZaZfK2vnh/1kQX1PzAJWsnBmVgGJA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/compat-data': 7.24.1 - '@babel/core': 7.24.4(supports-color@8.1.1) - '@babel/helper-compilation-targets': 7.23.6 - '@babel/helper-plugin-utils': 7.24.0 - '@babel/helper-validator-option': 7.23.5 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.4) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.4) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.4) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.4) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.4) - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.4) - '@babel/plugin-syntax-import-assertions': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-syntax-import-attributes': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.4) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.4) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.4) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.4) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.4) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.4) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.4) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.4) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.4) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.4) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.24.4) - '@babel/plugin-transform-arrow-functions': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-async-generator-functions': 7.24.3(@babel/core@7.24.4) - '@babel/plugin-transform-async-to-generator': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-block-scoped-functions': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-block-scoping': 7.24.4(@babel/core@7.24.4) - '@babel/plugin-transform-class-properties': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-class-static-block': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-classes': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-computed-properties': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-destructuring': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-dotall-regex': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-duplicate-keys': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-dynamic-import': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-exponentiation-operator': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-export-namespace-from': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-for-of': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-function-name': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-json-strings': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-literals': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-logical-assignment-operators': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-member-expression-literals': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-modules-amd': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-modules-commonjs': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-modules-systemjs': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-modules-umd': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.24.4) - '@babel/plugin-transform-new-target': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-nullish-coalescing-operator': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-numeric-separator': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-object-rest-spread': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-object-super': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-optional-catch-binding': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-optional-chaining': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-parameters': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-private-methods': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-private-property-in-object': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-property-literals': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-regenerator': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-reserved-words': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-shorthand-properties': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-spread': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-sticky-regex': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-template-literals': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-typeof-symbol': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-unicode-escapes': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-unicode-property-regex': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-unicode-regex': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-unicode-sets-regex': 7.24.1(@babel/core@7.24.4) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.24.4) - babel-plugin-polyfill-corejs2: 0.4.10(@babel/core@7.24.4) - babel-plugin-polyfill-corejs3: 0.10.4(@babel/core@7.24.4) - babel-plugin-polyfill-regenerator: 0.6.1(@babel/core@7.24.4) - core-js-compat: 3.36.1 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - /@babel/preset-env@7.24.4(@babel/core@7.24.4): resolution: {integrity: sha512-7Kl6cSmYkak0FK/FXjSEnLJ1N9T/WA2RkMhu17gZ/dsxKJUuTYNIylahPTzqpLyJN4WhDif8X0XK1R8Wsguo/A==} engines: {node: '>=6.9.0'} @@ -6050,7 +5940,6 @@ packages: semver: 6.3.1 transitivePeerDependencies: - supports-color - dev: true /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.24.4): resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} @@ -6103,7 +5992,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.24.2 - '@babel/generator': 7.24.1 + '@babel/generator': 7.24.4 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-function-name': 7.23.0 '@babel/helper-hoist-variables': 7.22.5 @@ -6908,7 +6797,7 @@ packages: resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} dependencies: - '@humanwhocodes/object-schema': 2.0.2 + '@humanwhocodes/object-schema': 2.0.3 debug: 4.3.4(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: @@ -6918,8 +6807,8 @@ packages: resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - /@humanwhocodes/object-schema@2.0.2: - resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} + /@humanwhocodes/object-schema@2.0.3: + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} /@isaacs/cliui@8.0.2: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} @@ -7731,11 +7620,24 @@ packages: resolution: {integrity: sha512-Anitqkl3+KrzcW2k77lRlg/GfLZLWXBuNgbEcIOU6M92yw42vsd3xV/Z/yAHEj8m+KUjL6bWOVOFqX8PFPJ4LA==} dev: true + /@types/babel__parser@7.1.1: + resolution: {integrity: sha512-baSzIb0QQOUQSglfR9gwXVSbHH91YvY00C9Zjq6E7sPdnp8oyPyUsonIj3SF4wUl0s96vR/kyWeVv30gmM/xZw==} + deprecated: Deprecated + dependencies: + '@babel/parser': 7.24.4 + dev: true + + /@types/babel__traverse@7.20.5: + resolution: {integrity: sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==} + dependencies: + '@babel/types': 7.24.0 + dev: true + /@types/body-parser@1.19.5: resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} dependencies: '@types/connect': 3.4.38 - '@types/node': 20.11.30 + '@types/node': 20.12.7 /@types/broccoli-plugin@3.0.0: resolution: {integrity: sha512-f+TcsARR2PovfFRKFdCX0kfH/QoM3ZVD2h1rl2mNvrKO0fq2uBNCBsTU3JanfU4COCt5cXpTfARyUsERlC8vIw==} @@ -7757,7 +7659,7 @@ packages: /@types/connect@3.4.38: resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} dependencies: - '@types/node': 20.11.30 + '@types/node': 20.12.7 /@types/cookie@0.4.1: resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} @@ -7765,7 +7667,7 @@ packages: /@types/cors@2.8.17: resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} dependencies: - '@types/node': 20.11.30 + '@types/node': 20.12.7 /@types/eslint-scope@3.7.7: resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -7785,7 +7687,7 @@ packages: /@types/express-serve-static-core@4.17.43: resolution: {integrity: sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==} dependencies: - '@types/node': 20.11.30 + '@types/node': 20.12.7 '@types/qs': 6.9.14 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -7801,19 +7703,19 @@ packages: /@types/fs-extra@8.1.5: resolution: {integrity: sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==} dependencies: - '@types/node': 20.11.30 + '@types/node': 20.12.7 /@types/glob@7.2.0: resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.11.30 + '@types/node': 20.12.7 /@types/glob@8.1.0: resolution: {integrity: sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==} dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.11.30 + '@types/node': 20.12.7 /@types/http-errors@2.0.4: resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} @@ -7834,7 +7736,7 @@ packages: /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 20.11.30 + '@types/node': 20.12.7 dev: true /@types/mime@1.3.5: @@ -7852,13 +7754,19 @@ packages: /@types/morgan@1.9.9: resolution: {integrity: sha512-iRYSDKVaC6FkGSpEVVIvrRGw0DfJMiQzIn3qr2G5B3C//AWkulhXgaBd7tS9/J79GWSYMTHGs7PfI5b3Y8m+RQ==} dependencies: - '@types/node': 20.11.30 + '@types/node': 20.12.7 dev: true /@types/node@20.11.30: resolution: {integrity: sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==} dependencies: undici-types: 5.26.5 + dev: true + + /@types/node@20.12.7: + resolution: {integrity: sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==} + dependencies: + undici-types: 5.26.5 /@types/qs@6.9.14: resolution: {integrity: sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==} @@ -7876,14 +7784,14 @@ packages: /@types/responselike@1.0.3: resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} dependencies: - '@types/node': 20.11.30 + '@types/node': 20.12.7 dev: true /@types/rimraf@2.0.5: resolution: {integrity: sha512-YyP+VfeaqAyFmXoTh3HChxOQMyjByRMsHU7kc5KOJkSlXudhMhQIALbYV7rHh/l8d2lX3VUQzprrcAgWdRuU8g==} dependencies: '@types/glob': 8.1.0 - '@types/node': 20.11.30 + '@types/node': 20.12.7 /@types/semver@7.5.8: resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} @@ -7892,14 +7800,14 @@ packages: resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} dependencies: '@types/mime': 1.3.5 - '@types/node': 20.11.30 + '@types/node': 20.12.7 /@types/serve-static@1.15.5: resolution: {integrity: sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==} dependencies: '@types/http-errors': 2.0.4 '@types/mime': 3.0.4 - '@types/node': 20.11.30 + '@types/node': 20.12.7 /@types/sizzle@2.3.8: resolution: {integrity: sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==} @@ -7908,7 +7816,7 @@ packages: /@types/ssri@7.1.5: resolution: {integrity: sha512-odD/56S3B51liILSk5aXJlnYt99S6Rt9EFDDqGtJM26rKHApHcwyU/UoYHrzKkdkHMAIquGWCuHtQTbes+FRQw==} dependencies: - '@types/node': 20.11.30 + '@types/node': 20.12.7 /@types/supports-color@8.1.3: resolution: {integrity: sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg==} @@ -7924,7 +7832,7 @@ packages: /@types/ws@8.5.10: resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} dependencies: - '@types/node': 20.11.30 + '@types/node': 20.12.7 dev: true /@types/yargs-parser@21.0.3: @@ -7937,8 +7845,8 @@ packages: '@types/yargs-parser': 21.0.3 dev: true - /@typescript-eslint/eslint-plugin@7.6.0(@typescript-eslint/parser@7.6.0)(eslint@8.57.0)(typescript@5.4.5): - resolution: {integrity: sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A==} + /@typescript-eslint/eslint-plugin@7.7.0(@typescript-eslint/parser@7.7.0)(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-GJWR0YnfrKnsRoluVO3PRb9r5aMZriiMMM/RHj5nnTrBy1/wIgk76XCtCKcnXGjpZQJQRFtGV9/0JJ6n30uwpQ==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: '@typescript-eslint/parser': ^7.0.0 @@ -7949,11 +7857,11 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 7.6.0(eslint@8.57.0)(typescript@5.4.5) - '@typescript-eslint/scope-manager': 7.6.0 - '@typescript-eslint/type-utils': 7.6.0(eslint@8.57.0)(typescript@5.4.5) - '@typescript-eslint/utils': 7.6.0(eslint@8.57.0)(typescript@5.4.5) - '@typescript-eslint/visitor-keys': 7.6.0 + '@typescript-eslint/parser': 7.7.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/scope-manager': 7.7.0 + '@typescript-eslint/type-utils': 7.7.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/utils': 7.7.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 7.7.0 debug: 4.3.4(supports-color@8.1.1) eslint: 8.57.0 graphemer: 1.4.0 @@ -7966,8 +7874,8 @@ packages: - supports-color dev: false - /@typescript-eslint/parser@7.6.0(eslint@8.57.0)(typescript@5.4.5): - resolution: {integrity: sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg==} + /@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-fNcDm3wSwVM8QYL4HKVBggdIPAy9Q41vcvC/GtDobw3c4ndVT3K6cqudUmjHPw8EAp4ufax0o58/xvWaP2FmTg==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 @@ -7976,10 +7884,10 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 7.6.0 - '@typescript-eslint/types': 7.6.0 - '@typescript-eslint/typescript-estree': 7.6.0(typescript@5.4.5) - '@typescript-eslint/visitor-keys': 7.6.0 + '@typescript-eslint/scope-manager': 7.7.0 + '@typescript-eslint/types': 7.7.0 + '@typescript-eslint/typescript-estree': 7.7.0(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 7.7.0 debug: 4.3.4(supports-color@8.1.1) eslint: 8.57.0 typescript: 5.4.5 @@ -7987,16 +7895,16 @@ packages: - supports-color dev: false - /@typescript-eslint/scope-manager@7.6.0: - resolution: {integrity: sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==} + /@typescript-eslint/scope-manager@7.7.0: + resolution: {integrity: sha512-/8INDn0YLInbe9Wt7dK4cXLDYp0fNHP5xKLHvZl3mOT5X17rK/YShXaiNmorl+/U4VKCVIjJnx4Ri5b0y+HClw==} engines: {node: ^18.18.0 || >=20.0.0} dependencies: - '@typescript-eslint/types': 7.6.0 - '@typescript-eslint/visitor-keys': 7.6.0 + '@typescript-eslint/types': 7.7.0 + '@typescript-eslint/visitor-keys': 7.7.0 dev: false - /@typescript-eslint/type-utils@7.6.0(eslint@8.57.0)(typescript@5.4.5): - resolution: {integrity: sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw==} + /@typescript-eslint/type-utils@7.7.0(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-bOp3ejoRYrhAlnT/bozNQi3nio9tIgv3U5C0mVDdZC7cpcQEDZXvq8inrHYghLVwuNABRqrMW5tzAv88Vy77Sg==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 @@ -8005,8 +7913,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 7.6.0(typescript@5.4.5) - '@typescript-eslint/utils': 7.6.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/typescript-estree': 7.7.0(typescript@5.4.5) + '@typescript-eslint/utils': 7.7.0(eslint@8.57.0)(typescript@5.4.5) debug: 4.3.4(supports-color@8.1.1) eslint: 8.57.0 ts-api-utils: 1.3.0(typescript@5.4.5) @@ -8015,13 +7923,13 @@ packages: - supports-color dev: false - /@typescript-eslint/types@7.6.0: - resolution: {integrity: sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==} + /@typescript-eslint/types@7.7.0: + resolution: {integrity: sha512-G01YPZ1Bd2hn+KPpIbrAhEWOn5lQBrjxkzHkWvP6NucMXFtfXoevK82hzQdpfuQYuhkvFDeQYbzXCjR1z9Z03w==} engines: {node: ^18.18.0 || >=20.0.0} dev: false - /@typescript-eslint/typescript-estree@7.6.0(typescript@5.4.5): - resolution: {integrity: sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==} + /@typescript-eslint/typescript-estree@7.7.0(typescript@5.4.5): + resolution: {integrity: sha512-8p71HQPE6CbxIBy2kWHqM1KGrC07pk6RJn40n0DSc6bMOBBREZxSDJ+BmRzc8B5OdaMh1ty3mkuWRg4sCFiDQQ==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: typescript: '*' @@ -8029,8 +7937,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 7.6.0 - '@typescript-eslint/visitor-keys': 7.6.0 + '@typescript-eslint/types': 7.7.0 + '@typescript-eslint/visitor-keys': 7.7.0 debug: 4.3.4(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 @@ -8042,8 +7950,8 @@ packages: - supports-color dev: false - /@typescript-eslint/utils@7.6.0(eslint@8.57.0)(typescript@5.4.5): - resolution: {integrity: sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA==} + /@typescript-eslint/utils@7.7.0(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-LKGAXMPQs8U/zMRFXDZOzmMKgFv3COlxUQ+2NMPhbqgVm6R1w+nU1i4836Pmxu9jZAuIeyySNrN/6Rc657ggig==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 @@ -8051,9 +7959,9 @@ packages: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) '@types/json-schema': 7.0.15 '@types/semver': 7.5.8 - '@typescript-eslint/scope-manager': 7.6.0 - '@typescript-eslint/types': 7.6.0 - '@typescript-eslint/typescript-estree': 7.6.0(typescript@5.4.5) + '@typescript-eslint/scope-manager': 7.7.0 + '@typescript-eslint/types': 7.7.0 + '@typescript-eslint/typescript-estree': 7.7.0(typescript@5.4.5) eslint: 8.57.0 semver: 7.6.0 transitivePeerDependencies: @@ -8061,11 +7969,11 @@ packages: - typescript dev: false - /@typescript-eslint/visitor-keys@7.6.0: - resolution: {integrity: sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==} + /@typescript-eslint/visitor-keys@7.7.0: + resolution: {integrity: sha512-h0WHOj8MhdhY8YWkzIF30R379y0NqyOHExI9N9KCzvmu05EgG4FumeYa3ccfKUSphyWkWQE1ybVrgz/Pbam6YA==} engines: {node: ^18.18.0 || >=20.0.0} dependencies: - '@typescript-eslint/types': 7.6.0 + '@typescript-eslint/types': 7.7.0 eslint-visitor-keys: 3.4.3 dev: false @@ -8892,7 +8800,7 @@ packages: engines: {node: '>= 12.*'} dependencies: '@glimmer/syntax': 0.84.3 - babel-import-util: 2.0.1 + babel-import-util: 2.0.2 /babel-plugin-filter-imports@4.0.0: resolution: {integrity: sha512-jDLlxI8QnfKd7PtieH6pl4tZJzymzfCDCPGdTq/grgbiYAikwDPp/oL0IlFJn0HQjLpcLkyYhPKkUVneRESw5w==} @@ -11048,7 +10956,7 @@ packages: resolution: {integrity: sha512-n3WCgc3PB2t9mLV4SJd87IjTbKeFUGw2sU5eEBYczSmcvj3guX4eMW4XOowlQqGk7I/da6+bwv1ydw1ZtNqWaw==} dependencies: '@babel/plugin-syntax-decorators': 7.24.1(@babel/core@7.24.4) - babel-import-util: 2.0.1 + babel-import-util: 2.0.2 transitivePeerDependencies: - '@babel/core' dev: true @@ -11276,7 +11184,7 @@ packages: '@babel/plugin-proposal-decorators': 7.24.1(@babel/core@7.24.4) '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.24.4) '@babel/plugin-transform-class-static-block': 7.24.1(@babel/core@7.24.4) - '@babel/preset-env': 7.24.3(@babel/core@7.24.4) + '@babel/preset-env': 7.24.4(@babel/core@7.24.4) '@embroider/macros': 1.15.0(@glint/template@1.4.0) '@embroider/shared-internals': 2.5.2 babel-loader: 8.3.0(@babel/core@7.24.4)(webpack@5.91.0) @@ -11320,7 +11228,7 @@ packages: '@babel/plugin-proposal-decorators': 7.24.1(@babel/core@7.24.4) '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.24.4) '@babel/plugin-transform-class-static-block': 7.24.1(@babel/core@7.24.4) - '@babel/preset-env': 7.24.3(@babel/core@7.24.4) + '@babel/preset-env': 7.24.4(@babel/core@7.24.4) '@embroider/macros': 1.15.0(@glint/template@1.4.0) '@embroider/shared-internals': 2.5.2 babel-loader: 8.3.0(@babel/core@7.24.4)(webpack@5.91.0) @@ -11405,8 +11313,8 @@ packages: '@babel/plugin-transform-class-static-block': 7.24.1(@babel/core@7.24.4) '@babel/plugin-transform-modules-amd': 7.24.1(@babel/core@7.24.4) '@babel/plugin-transform-runtime': 7.24.3(@babel/core@7.24.4) - '@babel/plugin-transform-typescript': 7.24.1(@babel/core@7.24.4) - '@babel/preset-env': 7.24.3(@babel/core@7.24.4) + '@babel/plugin-transform-typescript': 7.24.4(@babel/core@7.24.4) + '@babel/preset-env': 7.24.4(@babel/core@7.24.4) '@babel/runtime': 7.12.18 amd-name-resolver: 1.3.1 babel-plugin-debug-macros: 0.3.4(@babel/core@7.24.4) @@ -12003,7 +11911,7 @@ packages: resolution: {integrity: sha512-89oVHVJwmLDvGvAUWgS87KpBoRhy3aZ6U0Ql6HOmU4TrPkyaa8pM0W81wj9cIwjYprcQtN9EwzZMHnq46+oUyw==} engines: {node: 8.* || 10.* || >= 12} dependencies: - '@babel/parser': 7.24.1 + '@babel/parser': 7.24.4 '@babel/traverse': 7.24.1(supports-color@8.1.1) recast: 0.18.10 transitivePeerDependencies: @@ -12259,7 +12167,7 @@ packages: dependencies: '@types/cookie': 0.4.1 '@types/cors': 2.8.17 - '@types/node': 20.11.30 + '@types/node': 20.12.7 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.4.2 @@ -12464,7 +12372,7 @@ packages: - supports-color dev: false - /eslint-module-utils@2.8.1(@typescript-eslint/parser@7.6.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): + /eslint-module-utils@2.8.1(@typescript-eslint/parser@7.7.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} engines: {node: '>=4'} peerDependencies: @@ -12485,7 +12393,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 7.6.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/parser': 7.7.0(eslint@8.57.0)(typescript@5.4.5) debug: 3.2.7 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 @@ -12505,7 +12413,7 @@ packages: eslint-compat-utils: 0.5.0(eslint@8.57.0) dev: false - /eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.6.0)(eslint@8.57.0): + /eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0)(eslint@8.57.0): resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} engines: {node: '>=4'} peerDependencies: @@ -12515,7 +12423,7 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 7.6.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/parser': 7.7.0(eslint@8.57.0)(typescript@5.4.5) array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 @@ -12524,7 +12432,7 @@ packages: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.6.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.7.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -14034,8 +13942,8 @@ packages: dependencies: parse-passwd: 1.0.0 - /hono@4.2.4: - resolution: {integrity: sha512-2T5Ahxh8tT0ISKCrNeA+OIwfD5W4EZ00iIMYBBCuiIivr+sOrZYOphinARSG7wL3nFsY6zkFHMMZbcR9CzEzug==} + /hono@4.2.5: + resolution: {integrity: sha512-uonJD3i/yy005kQ7bPZRVfG3rejYJwyPqBmPoUGijS4UB/qM+YlrZ7xzSWy+ByDu9buGHUG+f+SKzz03Y6V1Kw==} engines: {node: '>=16.0.0'} /hosted-git-info@4.1.0: @@ -14691,7 +14599,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 20.11.30 + '@types/node': 20.12.7 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -17145,6 +17053,7 @@ packages: /right-pad@1.0.1: resolution: {integrity: sha512-bYBjgxmkvTAfgIYy328fmkwhp39v8lwVgWhhrzxPV3yHtcSqyYKe9/XOhvW48UFjATg3VuJbpsp5822ACNvkmw==} engines: {node: '>= 0.10'} + deprecated: Please use String.prototype.padEnd() over this package. /rimraf@2.6.3: resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} @@ -19621,7 +19530,7 @@ packages: '@hono/node-server': 1.11.0 '@warp-drive/core-types': file:packages/core-types(@babel/core@7.24.4)(@glint/template@1.4.0) chalk: 5.3.0 - hono: 4.2.4 + hono: 4.2.5 pm2: 5.3.1 transitivePeerDependencies: - bufferutil diff --git a/tests/schema/output/.gitkeep b/tests/schema/output/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/warp-drive__schema/input/schema.json b/tests/warp-drive__schema/input/schema.json index e69de29bb2d..fd2d32ba289 100644 --- a/tests/warp-drive__schema/input/schema.json +++ b/tests/warp-drive__schema/input/schema.json @@ -0,0 +1,4 @@ +{ + "schemas": "./schemas", + "dest": "../output" +} diff --git a/tests/warp-drive__schema/input/schemas/user-setting.ts b/tests/warp-drive__schema/input/schemas/user-setting.ts new file mode 100644 index 00000000000..1c2c11055e1 --- /dev/null +++ b/tests/warp-drive__schema/input/schemas/user-setting.ts @@ -0,0 +1,44 @@ +import { + collection, + createonly, + derived, + field, + optional, + readonly, + resource, + Resource, +} from '@warp-drive/schema-decorators'; +import { Luxon } from '@warp-drive/schema-transforms'; +import { Comment } from './comment'; +import { User } from './user'; + +@Resource // Resource is a default "Trait" that provides the "id" and "$type" fields used by @warp-drive/schema-record +class UserSetting { + // @optional - An optional field is one that may be omitted during create. + @readonly // - A readonly field is one that may never be created or edited. + // @createonly - A createonly field is one that may only be set during create. + // We use declare to tell TypeScript that this field exists + // We use the declared type to set the "cache" type for the field (what the API returns) + myField?: string | null; + // We use the field decorator to provide a "Transform" function for the field. + // The transform's return type will be used as the "UI" type for the field. + // e.g. "Date" instead of "string" + @field(Luxon, { + inverse: 'post', + value: null, + aNumber: 1, + aThing: [], + anotherThing: {}, + }) + someDateField; + // We use the collection decorator to create a linkage to a collection of other resources + @collection(Comment, { inverse: 'post' }) comments; + // We use the resource decorator to create a linkage to another resource + // if the related resource will not always be present use `| null` with the type + @resource(User, { inverse: 'posts' }) author; + // We use the derived decorator to create a field that is derived from other fields + // Note your project can provide its own decorators that can simplify this. + @derived('concat', { fields: ['firstName', 'lastName'], separator: ' ' }) fullName: string; +} + +export { UserSetting }; diff --git a/tests/warp-drive__schema/input/schemas/user.ts b/tests/warp-drive__schema/input/schemas/user.ts index 07887b47a8e..f99670d3bc8 100644 --- a/tests/warp-drive__schema/input/schemas/user.ts +++ b/tests/warp-drive__schema/input/schemas/user.ts @@ -1,7 +1,8 @@ +import { Resource } from '@warp-drive/schema-decorators'; + +@Resource class User { - declare id: string; - declare firstName: string; - declare lastName: string; + // ... } -export default User; +export { User }; diff --git a/tests/warp-drive__schema/package.json b/tests/warp-drive__schema/package.json index 4004e17eb03..3891da015f8 100644 --- a/tests/warp-drive__schema/package.json +++ b/tests/warp-drive__schema/package.json @@ -16,5 +16,12 @@ }, "scripts": { "_syncPnpm": "bun run sync-dependencies-meta-injected" - } + }, + "engines": { + "node": ">= 18.20.1" + }, + "volta": { + "extends": "../../package.json" + }, + "packageManager": "pnpm@8.15.6" }