diff --git a/.eslintrc.js b/.eslintrc.js index 4f6b9b8..8171dd1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,37 +1,38 @@ module.exports = { env: { node: true }, parser: "@typescript-eslint/parser", plugins: ["@typescript-eslint", "prettier"], extends: [ "eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended-requiring-type-checking", "plugin:prettier/recommended", ], ignorePatterns: [".eslintrc.js", "src/**/*.test.ts", "@types/*"], parserOptions: { sourceType: "module", ecmaFeatures: { modules: true, }, project: ["./tsconfig.json"], }, rules: { "@typescript-eslint/require-await": 0, "no-await-in-loop": 2, + "@typescript-eslint/no-unused-vars": ["warn", { args: "none" }], }, settings: { jsdoc: { mode: "typescript" } }, overrides: [ { files: ["*.subtest.ts", "*.test.ts"], rules: { "@typescript-eslint/no-unsafe-member-access": 0, "prefer-const": 0, "@typescript-eslint/no-unsafe-call": 0, "@typescript-eslint/no-unsafe-return": 0, "@typescript-eslint/no-unsafe-assignment": 0, "no-await-in-loop": 1, // sometimes it's easier to debug when requests run sequentially }, }, ], }; diff --git a/src/component-arguments/list.ts b/src/component-arguments/list.ts index 6a3ddc1..1a72ea3 100644 --- a/src/component-arguments/list.ts +++ b/src/component-arguments/list.ts @@ -1,78 +1,79 @@ import { is, predicates } from "@sealcode/ts-predicates"; import { JDDContext } from ".."; import { MaybePromise } from "../utils/util-types"; import { ComponentArgument } from "./component-argument"; export class List extends ComponentArgument> { constructor( public item_type: ComponentArgument, public example_count: number | null = null ) { super(); item_type.parent_argument = this; } getTypeName() { return "list"; } getEmptyValue() { return []; } getExampleCount() { if (this.example_count === null) { return Math.floor(Math.random() * 5); } else { return this.example_count; } } async getExampleValue(context: JDDContext) { if (this.example_values.length) { return super.getExampleValue(context); } else { const count = this.getExampleCount(); const result = [] as Array>; for (let i = 0; i < count; i++) { result.push(this.item_type.getExampleValue(context)); } return (await Promise.all(result)) as Array; } } countWords(value: Array): number { return value.reduce( (acc, item) => acc + this.item_type.countWords(item), 0 ); } async parseFormInput( context: JDDContext, input: unknown, arg_name: string ): Promise { if ( !is(input, predicates.array(predicates.object)) && !is(input, predicates.object) ) { throw new Error(`$.${arg_name} is not a list or object`); } const values = Array.isArray(input) ? input : Object.values(input); let array_result: Array = await Promise.all( values.map(async (value, index) => { const result = await this.item_type.parseFormInput( context, value, `${arg_name}[${index}]` ); return result; }) ); if (this.item_type.getTypeName() != "list") { array_result = array_result.flat() as T[]; } - return array_result.filter((e) => e !== null) as T[]; + const result = array_result.filter((e) => e !== null) as T[]; + return result; } } diff --git a/src/component.ts b/src/component.ts index df98254..c27de0d 100644 --- a/src/component.ts +++ b/src/component.ts @@ -1,56 +1,68 @@ import { FlatTemplatable } from "tempstream"; import { ComponentArgument, ExtractStructuredComponentArgumentsValues, JDDContext, } from "."; export interface ComponentConstructor< A extends Record> = Record< string, ComponentArgument > > { new (): Component; } +export type EarlyAsset = ( + | { type: "script" | "style"; url: string; integrity?: string } + | { type: "script" | "style"; content: string } +) & { identity: string }; // identity key will be used for deduplication + export abstract class Component< ArgumentsT extends Record> = Record< string, ComponentArgument > > { abstract getArguments(): ArgumentsT; abstract toHTML( args: ExtractStructuredComponentArgumentsValues, context: JDDContext ): FlatTemplatable; + async getEarlyAssets( + _args: ExtractStructuredComponentArgumentsValues, + _context: JDDContext + ): Promise { + return []; + } + countWords(args: Record): number { return Object.entries(args).reduce((acc, [arg_name, value]) => { const arg = this.getArguments()[arg_name]; if (!arg) { console.warn( `Arguemnt ${arg_name} was not found in the component` ); return acc + 0; } return acc + arg.countWords(value); }, 0); } async getExampleValues( context: JDDContext ): Promise> { return Object.fromEntries( await Promise.all( Object.entries(this.getArguments()).map( async ([key, value]) => [ key, await value.getExampleValue(context), ] ) ) ) as ExtractStructuredComponentArgumentsValues; } } diff --git a/src/index.ts b/src/index.ts index b7935a7..a271dc6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,55 +1,120 @@ import { tempstream } from "tempstream"; import { JDDContext } from "./jdd-context"; import { Registry } from "./registry"; +import { EarlyAsset } from "./component"; +import { hasField } from "@sealcode/ts-predicates"; export * from "./component"; export * from "./component-arguments/component-argument"; export * as ComponentArguments from "./component-arguments/component-arguments"; export * from "./component-arguments/component-arguments"; // exporting this also as root elements to make it easier to auto-import those export * from "./jdd-context"; export * from "./registry"; export { insert_nbsp } from "./utils/insert_nbsp"; export type JDDocument = Array<{ component_name: string; args: Record; }>; export function countWords(registry: Registry, document: JDDocument): number { return document.reduce((acc, { component_name, args }) => { const component = registry.get(component_name); if (!component) { console.warn( "Component not found in the registry: " + component_name ); return acc + 0; } return acc + component.countWords(args); }, 0); } +export async function renderEarlyAssets( + registry: Registry, + document: JDDocument, + context: JDDContext +) { + const early_assets = ( + await Promise.all( + document.map(async ({ component_name, args }) => { + const component = registry.get(component_name); + if (!component) { + console.warn( + "Component not found in the registry: " + component_name + ); + return []; + } + for (const arg_name in component?.getArguments()) { + if (!Object.prototype.hasOwnProperty.call(args, arg_name)) { + args[arg_name] = component + ?.getArguments() + [arg_name]?.getEmptyValue(); + } + } + return await component.getEarlyAssets(args, context); + }) + ) + ).flat(); + const deduplicated_assets: Record = {}; + for (const asset of early_assets) { + deduplicated_assets[asset.identity] = asset; + } + return Object.values(deduplicated_assets) + .map((asset) => { + if (asset.type == "script") { + if (hasField("url", asset)) { + return /* HTML */ ``; + } else { + return /* HTML */ `