@stdout-design/core 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as isZodObject, n as PropValidationError, o as validateProps, r as PropValidationIssue, s as zodToJsonSchemaShape, t as PropSchema } from "./validation-BUNZnc-5.mjs";
1
+ import { a as validateProps, i as isZodObject, n as PropValidationError, o as zodToJsonSchemaShape, t as PropSchema } from "./validation-MigzRuji.mjs";
2
2
  import { z } from "zod";
3
3
  import { fromJsx } from "takumi-js/helpers/jsx";
4
4
  import { ComponentType } from "react";
@@ -42,8 +42,13 @@ declare const studioConfigSchema: z.ZodObject<{
42
42
  description: z.ZodOptional<z.ZodString>;
43
43
  }, z.core.$strip>>;
44
44
  }, z.core.$strip>;
45
+ declare const parseStudioConfig: (raw: unknown) => {
46
+ config: StudioConfig;
47
+ } | {
48
+ issues: string;
49
+ };
45
50
  //#endregion
46
- //#region src/shared/render.d.ts
51
+ //#region src/engine/render.d.ts
47
52
  interface CompiledTemplate {
48
53
  node: Node;
49
54
  stylesheets: string[];
@@ -85,7 +90,7 @@ declare const logInfo: (message: string, context?: Record<string, unknown>) => v
85
90
  declare const logWarn: (message: string, context?: Record<string, unknown>) => void;
86
91
  declare const logError: (code: ErrorCode, message: string, context?: Record<string, unknown>) => void;
87
92
  //#endregion
88
- //#region src/node/cache-keys.d.ts
93
+ //#region src/cache/cache-keys.d.ts
89
94
  /**
90
95
  * Cache key derivation, shared by every component that needs to name a
91
96
  * cache entry consistently. Pulled out of the old monolithic RenderCache
@@ -108,7 +113,7 @@ interface PixelCacheKeyInput {
108
113
  declare const createCompileCacheKey: (input: CompileCacheKeyInput) => string;
109
114
  declare const createPixelCacheKey: (input: PixelCacheKeyInput) => string;
110
115
  //#endregion
111
- //#region src/node/render-cache.d.ts
116
+ //#region src/cache/render-cache.d.ts
112
117
  interface CacheStats {
113
118
  compiled: {
114
119
  entries: number;
@@ -176,7 +181,7 @@ declare class RenderCache {
176
181
  private requireEviction;
177
182
  }
178
183
  //#endregion
179
- //#region src/node/takumi-types-shim.d.ts
184
+ //#region src/engine/takumi-types-shim.d.ts
180
185
  /**
181
186
  * Type-only shim for `@takumi-rs/core` (as re-exported by `takumi-js/node`).
182
187
  *
@@ -276,7 +281,7 @@ interface MeasuredNode {
276
281
  runs: MeasuredTextRun[];
277
282
  }
278
283
  //#endregion
279
- //#region src/node/renderer.d.ts
284
+ //#region src/engine/renderer.d.ts
280
285
  /**
281
286
  * Preload a font once and reuse it across many renders without re-passing
282
287
  * its bytes on every call. Optional -- per Takumi's v2 guidance, passing
@@ -317,35 +322,51 @@ declare const measureTemplate: (template: CompiledTemplate, options?: RenderOpti
317
322
  */
318
323
  declare const renderAutoSized: (template: CompiledTemplate, options?: RenderOptions, signal?: AbortSignal) => Promise<RenderOutput>;
319
324
  //#endregion
320
- //#region src/batch/types.d.ts
321
- type OutputFormat = "webp" | "png" | "jpeg" | "ico" | "raw";
322
- interface MatrixCell {
323
- rowIndex: number;
324
- rowKey: string;
325
- locale: string;
326
- preset: Pick<Preset, "id" | "width" | "height">;
327
- props: Record<string, unknown>;
325
+ //#region src/project/project-paths.d.ts
326
+ interface ProjectPaths {
327
+ configPath: string;
328
+ defaultCacheDir: string;
329
+ localesDir: string;
330
+ templatesDir: string;
331
+ }
332
+ declare const resolveProjectPaths: (rootDir: string) => ProjectPaths;
333
+ //#endregion
334
+ //#region src/cache/open-cache.d.ts
335
+ interface OpenCacheOptions {
336
+ cacheDir?: string;
337
+ maxSizeMB?: number;
328
338
  }
329
- interface RenderOneInput {
330
- compiledTemplate: CompiledTemplate;
331
- templateId: string;
332
- contentHash: string;
339
+ declare const openCache: (rootDir: string, options?: OpenCacheOptions) => Promise<RenderCache>;
340
+ //#endregion
341
+ //#region src/assets/assets-resolver.d.ts
342
+ interface AssetsForLocale {
343
+ fonts: Font[];
344
+ fontFamilies?: string[];
345
+ lang: string | undefined;
333
346
  props: Record<string, unknown>;
334
- width: number;
335
- height: number;
336
- locale?: string;
337
- format?: OutputFormat;
338
- cache: RenderCache;
339
- outDir: string;
340
- filename: string;
341
- signal?: AbortSignal;
342
- renderOptions?: Pick<RenderOptions, "fonts" | "fontFamilies" | "lang"> & Record<string, unknown>;
343
347
  }
344
- interface RenderOneOutput {
345
- outputPath: string;
346
- cacheHit: boolean;
347
- durationMs: number;
348
+ interface FontResolutionResult {
349
+ fonts: Font[];
350
+ fontFamilies: string[];
348
351
  }
352
+ interface AssetsResolverOptions {
353
+ fetchFonts?: (localeId: string) => Promise<FontResolutionResult | null>;
354
+ localeData?: Record<string, unknown>;
355
+ loadLocaleData?: (locale: string) => Promise<Record<string, unknown>>;
356
+ preResolvedFonts?: Font[];
357
+ preResolvedFontFamilies?: string[];
358
+ }
359
+ declare const resolveAssetsForLocale: (localeId: string, props: Record<string, unknown>, options?: AssetsResolverOptions) => Promise<AssetsForLocale>;
360
+ //#endregion
361
+ //#region src/batch/loaders.d.ts
362
+ interface LoadedTemplate {
363
+ module: TemplateModule;
364
+ contentHash: string;
365
+ }
366
+ declare const importTemplateForBatch: (rootDir: string, componentPath: string, templateId: string) => Promise<LoadedTemplate>;
367
+ declare const loadConfig: (rootDir: string) => Promise<StudioConfig>;
368
+ //#endregion
369
+ //#region src/batch/types.d.ts
349
370
  interface ManifestEntry {
350
371
  rowIndex: number;
351
372
  locale: string;
@@ -366,49 +387,9 @@ interface Manifest {
366
387
  }[];
367
388
  }
368
389
  //#endregion
369
- //#region src/batch/matrix.d.ts
370
- interface MatrixLocaleInput {
371
- id: string;
372
- data?: Record<string, unknown>;
373
- }
374
- interface DataRow {
375
- key?: string;
376
- id?: string;
377
- slug?: string;
378
- [key: string]: unknown;
379
- }
380
- interface MatrixInput {
381
- rows: DataRow[];
382
- locales: MatrixLocaleInput[];
383
- presets: Pick<Preset, "id" | "width" | "height">[];
384
- baseProps?: Record<string, unknown>;
385
- }
386
- /**
387
- * Expands rows × locales × presets into a flat array of matrix cells.
388
- * Locale data is merged into props during expansion so each cell carries
389
- * fully resolved props.
390
- */
391
- declare const expandMatrix: (input: MatrixInput) => MatrixCell[];
392
- //#endregion
393
- //#region src/batch/render-one.d.ts
394
- declare const renderOne: (input: RenderOneInput) => Promise<RenderOneOutput>;
395
- //#endregion
396
- //#region src/batch/naming.d.ts
397
- interface NamingOptions {
398
- templateId: string;
399
- locale: string;
400
- presetId: string;
401
- rowKey: string;
402
- format?: string;
403
- }
404
- declare const generateOutputFilename: (options: NamingOptions) => string;
405
- //#endregion
406
- //#region src/batch/manifest.d.ts
407
- declare const writeManifest: (filePath: string, manifest: Manifest) => Promise<void>;
408
- //#endregion
409
- //#region src/batch/single.d.ts
390
+ //#region src/orchestrate/single.d.ts
410
391
  interface SingleInput {
411
- component: ComponentType<any>;
392
+ component: ComponentType<Record<string, unknown>>;
412
393
  propsSchema: PropSchema;
413
394
  props: Record<string, unknown>;
414
395
  preset: {
@@ -424,6 +405,55 @@ interface SingleOutput {
424
405
  }
425
406
  declare const renderComponent: (input: SingleInput) => Promise<SingleOutput>;
426
407
  //#endregion
408
+ //#region src/orchestrate/types.d.ts
409
+ type OutputFormat = "webp" | "png" | "jpeg" | "ico" | "raw";
410
+ interface OrchestrateRenderInput {
411
+ component?: ComponentType<Record<string, unknown>>;
412
+ compiledTemplate?: CompiledTemplate;
413
+ templateContentHash: string;
414
+ templateId: string;
415
+ props: Record<string, unknown>;
416
+ propsSchema?: PropSchema;
417
+ locale?: string;
418
+ loadLocaleData?: (locale: string) => Promise<Record<string, unknown>>;
419
+ fonts?: Font[];
420
+ fontFamilies?: string[];
421
+ width: number;
422
+ height: number;
423
+ cache: RenderCache;
424
+ format?: OutputFormat;
425
+ signal?: AbortSignal;
426
+ }
427
+ interface OrchestrateRenderResult {
428
+ bytes: Buffer;
429
+ width: number;
430
+ height: number;
431
+ format: string;
432
+ cacheHit: boolean;
433
+ durationMs: number;
434
+ }
435
+ interface OrchestrateMeasureInput {
436
+ component?: ComponentType<Record<string, unknown>>;
437
+ compiledTemplate?: CompiledTemplate;
438
+ templateContentHash: string;
439
+ templateId: string;
440
+ props: Record<string, unknown>;
441
+ propsSchema?: PropSchema;
442
+ locale?: string;
443
+ loadLocaleData?: (locale: string) => Promise<Record<string, unknown>>;
444
+ cache: RenderCache;
445
+ signal?: AbortSignal;
446
+ }
447
+ interface OrchestrateMeasureResult {
448
+ width: number;
449
+ height: number;
450
+ durationMs: number;
451
+ }
452
+ //#endregion
453
+ //#region src/orchestrate/orchestrate.d.ts
454
+ declare const orchestrateRender: (input: OrchestrateRenderInput) => Promise<OrchestrateRenderResult>;
455
+ declare const orchestrateMeasure: (input: OrchestrateMeasureInput) => Promise<OrchestrateMeasureResult>;
456
+ //#endregion
427
457
  //#region src/batch/batch.d.ts
428
458
  interface BatchInput {
429
459
  rootDir: string;
@@ -444,17 +474,4 @@ interface BatchOutput {
444
474
  }
445
475
  declare const runBatch: (input: BatchInput) => Promise<BatchOutput>;
446
476
  //#endregion
447
- //#region src/batch/loaders.d.ts
448
- interface LoadedTemplate {
449
- module: TemplateModule;
450
- contentHash: string;
451
- }
452
- declare const loadConfig: (rootDir: string) => Promise<StudioConfig>;
453
- declare const importTemplateForBatch: (rootDir: string, componentPath: string, templateId: string) => Promise<LoadedTemplate>;
454
- declare const parseDataFile: (filePath: string) => Promise<Record<string, unknown>[]>;
455
- declare const resolveLocales: (rootDir: string, localeCodes: string[], templateId: string) => Promise<{
456
- id: string;
457
- data?: Record<string, unknown>;
458
- }[]>;
459
- //#endregion
460
- export { AppError, type BatchInput, type BatchOutput, type CacheStats, type CompileCacheKeyInput, type CompiledTemplate, type DataRow, ErrorCode, type ErrorCode as ErrorCodeType, type Font, type LoadedTemplate, type LogLevel, type Manifest, type ManifestEntry, type MatrixCell, type MatrixInput, type MatrixLocaleInput, type NamingOptions, type PixelCacheKeyInput, type Preset, PropValidationError, type PropValidationIssue, RenderCache, type RenderCacheOptions, type RenderOneInput, type RenderOneOutput, type RenderOptions, type RenderOutput, type SingleInput, type SingleOutput, type StudioConfig, type TemplateEntry, type TemplateModule, compileTemplate, expandMatrix, generateOutputFilename, importTemplateForBatch, isZodObject, loadConfig, logDebug, logError, logInfo, logWarn, measureTemplate, parseDataFile, registerFont, renderAutoSized, renderComponent, renderOne, renderToPixels, resolveLocales, runBatch, studioConfigSchema, validateProps, writeManifest, zodToJsonSchemaShape };
477
+ export { AppError, type AssetsForLocale, type AssetsResolverOptions, type BatchInput, type BatchOutput, type CacheStats, type CompiledTemplate, ErrorCode, type ErrorCode as ErrorCodeType, type FontResolutionResult, type LogLevel, type Manifest, type ManifestEntry, type OpenCacheOptions, type OrchestrateMeasureInput, type OrchestrateMeasureResult, type OrchestrateRenderInput, type OrchestrateRenderResult, PropValidationError, RenderCache, type RenderCacheOptions, type RenderOutput, type SingleInput, type SingleOutput, type StudioConfig, type TemplateModule, compileTemplate, importTemplateForBatch, isZodObject, loadConfig, logDebug, logError, logInfo, logWarn, measureTemplate, openCache, orchestrateMeasure, orchestrateRender, parseStudioConfig, registerFont, renderAutoSized, renderComponent, renderToPixels, resolveAssetsForLocale, resolveProjectPaths, runBatch, studioConfigSchema, validateProps, zodToJsonSchemaShape };
package/dist/index.mjs CHANGED
@@ -12,9 +12,9 @@ import { Database } from "bun:sqlite";
12
12
  import { Kysely, sql } from "kysely";
13
13
  import { BunSqliteDialect } from "kysely-bun-sqlite";
14
14
  import { Renderer } from "takumi-js/node";
15
- import { createElement } from "react";
16
15
  import { googleFonts } from "@takumi-rs/helpers";
17
16
  import { pathToFileURL } from "node:url";
17
+ import { createElement } from "react";
18
18
  //#region src/shared/config-schema.ts
19
19
  const templateEntrySchema = z.object({
20
20
  componentPath: z.string().min(1, "componentPath is required"),
@@ -33,8 +33,13 @@ const studioConfigSchema = z.object({
33
33
  presets: z.array(presetSchema),
34
34
  templates: z.record(z.string(), templateEntrySchema)
35
35
  });
36
+ const parseStudioConfig = (raw) => {
37
+ const parsed = studioConfigSchema.safeParse(raw);
38
+ if (!parsed.success) return { issues: parsed.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ") };
39
+ return { config: parsed.data };
40
+ };
36
41
  //#endregion
37
- //#region src/shared/render.ts
42
+ //#region src/engine/render.ts
38
43
  const compileTemplate = async (element, options) => {
39
44
  const result = await fromJsx(element, options);
40
45
  return {
@@ -94,11 +99,11 @@ const logError = (code, message, context) => {
94
99
  console.error(`[${timestamp()}] ERROR [${code}] ${message}${stringifyContext(context)}`);
95
100
  };
96
101
  //#endregion
97
- //#region src/node/cache-keys.ts
102
+ //#region src/cache/cache-keys.ts
98
103
  const createCompileCacheKey = (input) => createHash("sha256").update(`${input.templateId}\u0000${input.templateContentHash}\u0000${input.propsJSON}`).digest("hex").slice(0, 16);
99
104
  const createPixelCacheKey = (input) => createHash("sha256").update(`${input.templateContentHash}\u0000${input.propsJSON}\u0000${input.width}x${input.height}\u0000${input.format ?? "png"}`).digest("hex").slice(0, 32);
100
105
  //#endregion
101
- //#region src/node/compile-cache.ts
106
+ //#region src/cache/compile-cache.ts
102
107
  /**
103
108
  * CompileCache: in-memory compile-result cache.
104
109
  *
@@ -176,7 +181,7 @@ var CompileCache = class {
176
181
  }
177
182
  };
178
183
  //#endregion
179
- //#region src/node/concurrency-limiter.ts
184
+ //#region src/cache/concurrency-limiter.ts
180
185
  /**
181
186
  * Bounds how many concurrent operations (writes/reads through FsStore)
182
187
  * run at once. Exists to cap the "N concurrent large-buffer renders spike
@@ -210,7 +215,7 @@ var ConcurrencyLimiter = class {
210
215
  }
211
216
  };
212
217
  //#endregion
213
- //#region src/node/eviction-coordinator.ts
218
+ //#region src/cache/eviction-coordinator.ts
214
219
  const DEFAULT_DEBOUNCE_MS = 250;
215
220
  /**
216
221
  * Coordinates eviction across both time (debouncing) and processes
@@ -296,7 +301,7 @@ var EvictionCoordinator = class {
296
301
  }
297
302
  };
298
303
  //#endregion
299
- //#region src/node/fs-store.ts
304
+ //#region src/cache/fs-store.ts
300
305
  const defaultFsPrimitives = {
301
306
  existsSync,
302
307
  readFile,
@@ -415,7 +420,7 @@ var FsStore = class {
415
420
  }
416
421
  };
417
422
  //#endregion
418
- //#region src/node/metadata-store.ts
423
+ //#region src/cache/metadata-store.ts
419
424
  const BUSY_TIMEOUT_MS = 5e3;
420
425
  const toRow = (r) => ({
421
426
  contentHash: r.content_hash,
@@ -598,7 +603,7 @@ var MetadataStore = class {
598
603
  }
599
604
  };
600
605
  //#endregion
601
- //#region src/node/render-cache.ts
606
+ //#region src/cache/render-cache.ts
602
607
  const DEFAULT_MAX_SIZE_MB = 500;
603
608
  /**
604
609
  * Content-addressed two-stage render cache -- coordinator.
@@ -781,7 +786,7 @@ var RenderCache = class {
781
786
  }
782
787
  };
783
788
  //#endregion
784
- //#region src/node/takumi-types-shim.ts
789
+ //#region src/engine/takumi-types-shim.ts
785
790
  /**
786
791
  * Type-only shim for `@takumi-rs/core` (as re-exported by `takumi-js/node`).
787
792
  *
@@ -821,7 +826,7 @@ var RenderCache = class {
821
826
  */
822
827
  const Renderer$1 = Renderer;
823
828
  //#endregion
824
- //#region src/node/renderer.ts
829
+ //#region src/engine/renderer.ts
825
830
  /**
826
831
  * Renderer lifecycle -- v2.
827
832
  *
@@ -911,175 +916,108 @@ const renderAutoSized = async (template, options, signal) => {
911
916
  }, options, signal);
912
917
  };
913
918
  //#endregion
914
- //#region src/batch/matrix.ts
915
- const mergeLocale = (props, localeData) => {
916
- if (!localeData) return props;
917
- const merged = { ...props };
918
- for (const [key, value] of Object.entries(localeData)) if (Array.isArray(value) && Array.isArray(merged[key])) merged[key] = value;
919
- else if (typeof value === "string" && typeof merged[key] === "string") merged[key] = value;
920
- return merged;
921
- };
922
- /**
923
- * Expands rows × locales × presets into a flat array of matrix cells.
924
- * Locale data is merged into props during expansion so each cell carries
925
- * fully resolved props.
926
- */
927
- const expandMatrix = (input) => {
928
- const { rows, locales, presets, baseProps = {} } = input;
929
- if (rows.length === 0) return [];
930
- const cells = [];
931
- for (const [rowIndex, row] of rows.entries()) {
932
- const rowKey = String(row.key ?? row.id ?? row.slug ?? String(rowIndex));
933
- const rowProps = {
934
- ...baseProps,
935
- ...row
936
- };
937
- delete rowProps.key;
938
- delete rowProps.id;
939
- delete rowProps.slug;
940
- for (const locale of locales) {
941
- const localeProps = mergeLocale(rowProps, locale.data);
942
- for (const preset of presets) cells.push({
943
- locale: locale.id,
944
- preset: {
945
- height: preset.height,
946
- id: preset.id,
947
- width: preset.width
948
- },
949
- props: {
950
- ...localeProps,
951
- locale: locale.id
952
- },
953
- rowIndex,
954
- rowKey
955
- });
956
- }
957
- }
958
- return cells;
959
- };
919
+ //#region src/project/project-paths.ts
920
+ const resolveProjectPaths = (rootDir) => ({
921
+ configPath: path.resolve(rootDir, "studio.config.ts"),
922
+ defaultCacheDir: path.resolve(rootDir, ".studio-cache"),
923
+ localesDir: path.resolve(rootDir, "locales"),
924
+ templatesDir: path.resolve(rootDir, "templates")
925
+ });
960
926
  //#endregion
961
- //#region src/batch/utils.ts
962
- const ensureDir = async (dir) => {
963
- if (!existsSync(dir)) await mkdir(dir, { recursive: true });
927
+ //#region src/cache/open-cache.ts
928
+ const openCache = async (rootDir, options) => {
929
+ const cache = new RenderCache({
930
+ cacheDir: options?.cacheDir ?? resolveProjectPaths(rootDir).defaultCacheDir,
931
+ maxSizeMB: options?.maxSizeMB
932
+ });
933
+ await cache.init();
934
+ return cache;
964
935
  };
965
- const tryWriteFile = async (filePath, data) => {
936
+ //#endregion
937
+ //#region src/assets/assets-resolver.ts
938
+ const isFontDescriptor = (f) => typeof f === "object" && !(f instanceof Uint8Array);
939
+ const defaultFetchFonts = async (localeId) => {
966
940
  try {
967
- await writeFile(filePath, data);
941
+ const fonts = await googleFonts([{
942
+ name: "Noto Sans Arabic",
943
+ weight: [400, 700]
944
+ }]);
945
+ const fontFamilies = fonts.filter(isFontDescriptor).map((f) => String(f.name ?? "")).filter(Boolean);
946
+ logDebug("googleFonts resolved per locale", {
947
+ count: fonts.length,
948
+ localeId,
949
+ names: fonts.filter(isFontDescriptor).map((f) => f.name)
950
+ });
951
+ return {
952
+ fontFamilies,
953
+ fonts
954
+ };
968
955
  } catch (error) {
969
- logWarn("Failed to write output file", {
956
+ logDebug("googleFonts failed for locale", {
970
957
  error: String(error),
971
- filePath
958
+ localeId
972
959
  });
960
+ return null;
973
961
  }
974
962
  };
975
- //#endregion
976
- //#region src/batch/render-one.ts
977
- const renderOne = async (input) => {
978
- const start = performance.now();
979
- const { compiledTemplate, contentHash, props, width, height, format = "png", locale, cache, outDir, filename, signal, renderOptions } = input;
980
- const propsJSON = JSON.stringify(props);
981
- const pixelKey = RenderCache.createPixelCacheKey({
982
- format,
983
- height,
984
- propsJSON,
985
- templateContentHash: contentHash,
986
- width
987
- });
988
- const cached = await cache.getPixels(pixelKey);
989
- if (cached) {
990
- const outputPath = `${outDir}/${filename}`;
991
- await ensureDir(outDir);
992
- await tryWriteFile(outputPath, cached);
993
- return {
994
- cacheHit: true,
995
- durationMs: performance.now() - start,
996
- outputPath
997
- };
998
- }
999
- const lang = locale?.startsWith("ar") ? "ar" : void 0;
1000
- logDebug("renderOne -> renderToPixels", {
1001
- fontFamilies: renderOptions?.fontFamilies,
1002
- fontNames: renderOptions?.fonts?.map((f) => typeof f === "object" && "name" in f ? f.name : "raw"),
1003
- fontsCount: renderOptions?.fonts?.length ?? 0,
1004
- lang,
1005
- locale
1006
- });
1007
- const output = await renderToPixels(compiledTemplate, {
1008
- height,
1009
- width
1010
- }, {
1011
- fontFamilies: renderOptions?.fontFamilies,
1012
- fonts: renderOptions?.fonts,
1013
- format,
1014
- lang
1015
- }, signal);
1016
- await cache.setPixels(pixelKey, output.bytes, width, height, format);
1017
- const outputPath = `${outDir}/${filename}`;
1018
- await ensureDir(outDir);
1019
- await tryWriteFile(outputPath, output.bytes);
963
+ const mergeLocaleProps = (props, localeData) => {
964
+ if (!localeData) return { ...props };
965
+ const merged = { ...props };
966
+ for (const [key, value] of Object.entries(localeData)) if (Array.isArray(value) && Array.isArray(merged[key])) merged[key] = value;
967
+ else if (typeof value === "string" && typeof merged[key] === "string") merged[key] = value;
968
+ return merged;
969
+ };
970
+ const classifyLocale = (localeId) => {
971
+ const isArabic = localeId.startsWith("ar");
1020
972
  return {
1021
- cacheHit: false,
1022
- durationMs: performance.now() - start,
1023
- outputPath
973
+ lang: isArabic ? "ar" : void 0,
974
+ needsArabic: isArabic
1024
975
  };
1025
976
  };
1026
- //#endregion
1027
- //#region src/batch/naming.ts
1028
- const generateOutputFilename = (options) => {
1029
- const { templateId, locale, presetId, rowKey, format = "png" } = options;
1030
- return `${templateId}.${locale}.${presetId}.${rowKey}.${format}`;
1031
- };
1032
- //#endregion
1033
- //#region src/batch/manifest.ts
1034
- const writeManifest = async (filePath, manifest) => {
1035
- await mkdir(path.dirname(filePath), { recursive: true });
1036
- await writeFile(filePath, JSON.stringify(manifest, null, 2));
1037
- };
1038
- //#endregion
1039
- //#region src/batch/single.ts
1040
- const renderComponent = async (input) => {
1041
- const { component, format = "png", outDir = "./out", preset, props, propsSchema } = input;
1042
- const { bytes } = await renderToPixels(await compileTemplate(createElement(component, validateProps(propsSchema, props))), {
1043
- height: preset.height,
1044
- width: preset.width
1045
- }, { format });
1046
- const outputPath = `${outDir}/${generateOutputFilename({
1047
- locale: "default",
1048
- presetId: preset.id,
1049
- rowKey: "single",
1050
- templateId: "single"
1051
- })}`;
1052
- await ensureDir(outDir);
1053
- await Bun.write(outputPath, bytes);
1054
- return { outputPath };
1055
- };
1056
- //#endregion
1057
- //#region src/batch/loaders.ts
1058
- const loadConfig = async (rootDir) => {
1059
- const configPath = path.resolve(rootDir, "studio.config.ts");
1060
- try {
1061
- await readFile(configPath, "utf-8");
977
+ const fontCache = /* @__PURE__ */ new Map();
978
+ const resolveAssetsForLocale = async (localeId, props, options) => {
979
+ let localeData = options?.localeData;
980
+ if (options?.loadLocaleData) try {
981
+ localeData = await options.loadLocaleData(localeId);
1062
982
  } catch {
1063
- throw new AppError(ErrorCode.CONFIG_NOT_FOUND, `No studio.config.ts found in ${rootDir}.`);
983
+ localeData = {};
1064
984
  }
1065
- logDebug("Loading studio.config.ts", { path: configPath });
1066
- const mod = await import(pathToFileURL(configPath).href);
1067
- const raw = mod.default ?? mod;
1068
- const parsed = studioConfigSchema.safeParse(raw);
1069
- if (!parsed.success) {
1070
- const issues = parsed.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
1071
- throw new AppError(ErrorCode.CONFIG_INVALID, `studio.config.ts is invalid: ${issues}`);
985
+ const mergedProps = mergeLocaleProps(props, localeData);
986
+ const { lang, needsArabic } = classifyLocale(localeId);
987
+ let fonts = [];
988
+ let fontFamilies;
989
+ if (options?.preResolvedFonts) {
990
+ fonts = options.preResolvedFonts;
991
+ fontFamilies = options.preResolvedFontFamilies;
992
+ } else if (needsArabic) {
993
+ const fetch = options?.fetchFonts ?? defaultFetchFonts;
994
+ const cached = fontCache.get(localeId);
995
+ let resolution;
996
+ if (cached) resolution = await cached;
997
+ else {
998
+ const promise = fetch(localeId);
999
+ fontCache.set(localeId, promise);
1000
+ resolution = await promise;
1001
+ }
1002
+ if (resolution) ({fontFamilies, fonts} = resolution);
1072
1003
  }
1073
- return parsed.data;
1004
+ return {
1005
+ fontFamilies,
1006
+ fonts,
1007
+ lang,
1008
+ props: mergedProps
1009
+ };
1074
1010
  };
1075
- const importTemplateForBatch = async (rootDir, componentPath, templateId) => {
1076
- const fullPath = path.resolve(rootDir, `${componentPath}.tsx`);
1011
+ //#endregion
1012
+ //#region src/batch/loaders.ts
1013
+ const templateCache = /* @__PURE__ */ new Map();
1014
+ const loadTemplateForBatch = async (fullPath, templateId) => {
1077
1015
  let content;
1078
1016
  try {
1079
1017
  content = await readFile(fullPath, "utf-8");
1080
1018
  } catch {
1081
1019
  throw new AppError(ErrorCode.TEMPLATE_LOAD_FAILED, `Template file not found: ${fullPath}`, {
1082
- componentPath,
1020
+ componentPath: fullPath,
1083
1021
  templateId
1084
1022
  });
1085
1023
  }
@@ -1097,6 +1035,32 @@ const importTemplateForBatch = async (rootDir, componentPath, templateId) => {
1097
1035
  module: mod
1098
1036
  };
1099
1037
  };
1038
+ const importTemplateForBatch = async (rootDir, componentPath, templateId) => {
1039
+ const fullPath = path.resolve(rootDir, `${componentPath}.tsx`);
1040
+ const cached = templateCache.get(fullPath);
1041
+ if (cached) return cached;
1042
+ const promise = loadTemplateForBatch(fullPath, templateId);
1043
+ templateCache.set(fullPath, promise);
1044
+ try {
1045
+ return await promise;
1046
+ } catch {
1047
+ templateCache.delete(fullPath);
1048
+ throw await promise.catch((error) => error);
1049
+ }
1050
+ };
1051
+ const loadConfig = async (rootDir) => {
1052
+ const { configPath } = resolveProjectPaths(rootDir);
1053
+ try {
1054
+ await readFile(configPath, "utf-8");
1055
+ } catch {
1056
+ throw new AppError(ErrorCode.CONFIG_NOT_FOUND, `No studio.config.ts found in ${rootDir}.`);
1057
+ }
1058
+ logDebug("Loading studio.config.ts", { path: configPath });
1059
+ const mod = await import(pathToFileURL(configPath).href);
1060
+ const result = parseStudioConfig(mod.default ?? mod);
1061
+ if ("issues" in result) throw new AppError(ErrorCode.CONFIG_INVALID, `studio.config.ts is invalid: ${result.issues}`);
1062
+ return result.config;
1063
+ };
1100
1064
  const parseDataFile = async (filePath) => {
1101
1065
  const content = await readFile(filePath, "utf-8");
1102
1066
  if (filePath.endsWith(".json")) {
@@ -1116,8 +1080,9 @@ const parseDataFile = async (filePath) => {
1116
1080
  };
1117
1081
  const resolveLocales = async (rootDir, localeCodes, templateId) => {
1118
1082
  if (localeCodes.length === 0) return [{ id: "default" }];
1083
+ const { localesDir } = resolveProjectPaths(rootDir);
1119
1084
  return await Promise.all(localeCodes.map(async (code) => {
1120
- const localePath = path.resolve(rootDir, `locales/${code}.json`);
1085
+ const localePath = path.resolve(localesDir, `${code}.json`);
1121
1086
  try {
1122
1087
  const content = await readFile(localePath, "utf-8");
1123
1088
  return {
@@ -1130,36 +1095,211 @@ const resolveLocales = async (rootDir, localeCodes, templateId) => {
1130
1095
  }));
1131
1096
  };
1132
1097
  //#endregion
1133
- //#region src/batch/batch.ts
1134
- const isFontDescriptor = (f) => typeof f === "object" && !(f instanceof Uint8Array);
1135
- const resolveArabicFonts = async (_locales) => {
1136
- let fonts = [];
1137
- let fontFamilies = [];
1138
- try {
1139
- fonts = await googleFonts([{
1140
- name: "Noto Sans Arabic",
1141
- weight: [400, 700]
1142
- }]);
1143
- fontFamilies = fonts.filter(isFontDescriptor).map((f) => String(f.name ?? "")).filter(Boolean);
1144
- logDebug("googleFonts resolved", {
1145
- count: fonts.length,
1146
- hasData: fonts.filter(isFontDescriptor).map((f) => typeof f.data),
1147
- names: fonts.filter(isFontDescriptor).map((f) => f.name)
1148
- });
1149
- } catch (error) {
1150
- logDebug("googleFonts failed, using direct URL fallback", { error: String(error) });
1151
- fonts = [{
1152
- data: /* @__PURE__ */ new Uint8Array(0),
1153
- name: "Noto Sans Arabic",
1154
- weight: 400
1155
- }];
1156
- fontFamilies = ["Noto Sans Arabic"];
1098
+ //#region src/batch/naming.ts
1099
+ const generateOutputFilename = (options) => {
1100
+ const { templateId, locale, presetId, rowKey, format = "png" } = options;
1101
+ return `${templateId}.${locale}.${presetId}.${rowKey}.${format}`;
1102
+ };
1103
+ //#endregion
1104
+ //#region src/orchestrate/orchestrate.ts
1105
+ const prepPipeline = async (input) => {
1106
+ const { compiledTemplate: preCompiled, component, templateContentHash, templateId, props, propsSchema, locale, loadLocaleData, preResolvedFonts, preResolvedFontFamilies, cache } = input;
1107
+ const { fonts: resolvedFonts, fontFamilies: resolvedFontFamilies, lang: resolvedLang, props: merged } = await resolveAssetsForLocale(locale, propsSchema ? validateProps(propsSchema, props) : { ...props }, {
1108
+ loadLocaleData,
1109
+ preResolvedFontFamilies,
1110
+ preResolvedFonts
1111
+ });
1112
+ const propsJSON = JSON.stringify(merged);
1113
+ const compileKey = RenderCache.createCompileCacheKey({
1114
+ propsJSON,
1115
+ templateContentHash,
1116
+ templateId
1117
+ });
1118
+ let compiled = preCompiled;
1119
+ if (!compiled) {
1120
+ if (!component) throw new Error("Must provide either compiledTemplate or component");
1121
+ const cachedCompile = cache.getCompiled(compileKey);
1122
+ if (cachedCompile) compiled = cachedCompile;
1123
+ else {
1124
+ compiled = await compileTemplate(createElement(component, merged));
1125
+ cache.setCompiledWithContentHash(compileKey, templateContentHash, compiled);
1126
+ }
1157
1127
  }
1158
1128
  return {
1159
- fontFamilies,
1160
- fonts
1129
+ compileKey,
1130
+ compiled,
1131
+ mergedProps: merged,
1132
+ propsJSON,
1133
+ resolvedFontFamilies,
1134
+ resolvedFonts,
1135
+ resolvedLang
1136
+ };
1137
+ };
1138
+ const orchestrateRender = async (input) => {
1139
+ const start = performance.now();
1140
+ const { compiledTemplate, component, templateContentHash, templateId, props, propsSchema, locale: localeId, loadLocaleData, fonts: preResolvedFonts, fontFamilies: preResolvedFontFamilies, width, height, cache, format = "png", signal } = input;
1141
+ const prep = await prepPipeline({
1142
+ cache,
1143
+ compiledTemplate,
1144
+ component,
1145
+ loadLocaleData,
1146
+ locale: localeId ?? "default",
1147
+ preResolvedFontFamilies,
1148
+ preResolvedFonts,
1149
+ props,
1150
+ propsSchema,
1151
+ templateContentHash,
1152
+ templateId
1153
+ });
1154
+ const pixelKey = RenderCache.createPixelCacheKey({
1155
+ format,
1156
+ height,
1157
+ propsJSON: prep.propsJSON,
1158
+ templateContentHash,
1159
+ width
1160
+ });
1161
+ const cached = await cache.getPixels(pixelKey);
1162
+ if (cached) return {
1163
+ bytes: cached,
1164
+ cacheHit: true,
1165
+ durationMs: performance.now() - start,
1166
+ format,
1167
+ height,
1168
+ width
1169
+ };
1170
+ const output = await renderToPixels(prep.compiled, {
1171
+ height,
1172
+ width
1173
+ }, {
1174
+ fontFamilies: (prep.resolvedFontFamilies ?? []).length > 0 ? prep.resolvedFontFamilies : void 0,
1175
+ fonts: prep.resolvedFonts.length > 0 ? prep.resolvedFonts : void 0,
1176
+ format,
1177
+ lang: prep.resolvedLang
1178
+ }, signal);
1179
+ await cache.setPixels(pixelKey, output.bytes, width, height, format);
1180
+ return {
1181
+ bytes: output.bytes,
1182
+ cacheHit: false,
1183
+ durationMs: performance.now() - start,
1184
+ format,
1185
+ height: output.height,
1186
+ width: output.width
1161
1187
  };
1162
1188
  };
1189
+ const orchestrateMeasure = async (input) => {
1190
+ const start = performance.now();
1191
+ const { compiledTemplate, component, templateContentHash, templateId, props, propsSchema, locale: localeId, loadLocaleData, cache, signal } = input;
1192
+ const prep = await prepPipeline({
1193
+ cache,
1194
+ compiledTemplate,
1195
+ component,
1196
+ loadLocaleData,
1197
+ locale: localeId ?? "default",
1198
+ preResolvedFontFamilies: void 0,
1199
+ preResolvedFonts: void 0,
1200
+ props,
1201
+ propsSchema,
1202
+ templateContentHash,
1203
+ templateId
1204
+ });
1205
+ const measured = await measureTemplate(prep.compiled, {
1206
+ fontFamilies: (prep.resolvedFontFamilies ?? []).length > 0 ? prep.resolvedFontFamilies : void 0,
1207
+ fonts: prep.resolvedFonts.length > 0 ? prep.resolvedFonts : void 0,
1208
+ lang: prep.resolvedLang
1209
+ }, signal);
1210
+ return {
1211
+ durationMs: performance.now() - start,
1212
+ height: measured.height,
1213
+ width: measured.width
1214
+ };
1215
+ };
1216
+ //#endregion
1217
+ //#region src/orchestrate/utils.ts
1218
+ const ensureDir = async (dir) => {
1219
+ if (!existsSync(dir)) await mkdir(dir, { recursive: true });
1220
+ };
1221
+ const tryWriteFile = async (filePath, data) => {
1222
+ try {
1223
+ await writeFile(filePath, data);
1224
+ } catch (error) {
1225
+ logWarn("Failed to write output file", {
1226
+ error: String(error),
1227
+ filePath
1228
+ });
1229
+ }
1230
+ };
1231
+ //#endregion
1232
+ //#region src/orchestrate/single.ts
1233
+ const noop = () => {};
1234
+ const noopCache = {
1235
+ getCompiled: () => null,
1236
+ getPixels: () => null,
1237
+ setCompiledWithContentHash: noop,
1238
+ setPixels: noop
1239
+ };
1240
+ const renderComponent = async (input) => {
1241
+ const { component, format = "png", outDir = "./out", preset, props, propsSchema } = input;
1242
+ const result = await orchestrateRender({
1243
+ cache: noopCache,
1244
+ component,
1245
+ format,
1246
+ height: preset.height,
1247
+ props,
1248
+ propsSchema,
1249
+ templateContentHash: "single",
1250
+ templateId: "single",
1251
+ width: preset.width
1252
+ });
1253
+ const outputPath = `${outDir}/${generateOutputFilename({
1254
+ locale: "default",
1255
+ presetId: preset.id,
1256
+ rowKey: "single",
1257
+ templateId: "single"
1258
+ })}`;
1259
+ await ensureDir(outDir);
1260
+ await Bun.write(outputPath, result.bytes);
1261
+ return { outputPath };
1262
+ };
1263
+ //#endregion
1264
+ //#region src/batch/manifest.ts
1265
+ const writeManifest = async (filePath, manifest) => {
1266
+ await mkdir(path.dirname(filePath), { recursive: true });
1267
+ await writeFile(filePath, JSON.stringify(manifest, null, 2));
1268
+ };
1269
+ //#endregion
1270
+ //#region src/batch/matrix.ts
1271
+ const expandMatrix = (input) => {
1272
+ const { rows, locales, presets, baseProps = {} } = input;
1273
+ if (rows.length === 0) return [];
1274
+ const cells = [];
1275
+ for (const [rowIndex, row] of rows.entries()) {
1276
+ const rowKey = String(row.key ?? row.id ?? row.slug ?? String(rowIndex));
1277
+ const rowProps = {
1278
+ ...baseProps,
1279
+ ...row
1280
+ };
1281
+ delete rowProps.key;
1282
+ delete rowProps.id;
1283
+ delete rowProps.slug;
1284
+ for (const localeId of locales) for (const preset of presets) cells.push({
1285
+ locale: localeId,
1286
+ preset: {
1287
+ height: preset.height,
1288
+ id: preset.id,
1289
+ width: preset.width
1290
+ },
1291
+ props: {
1292
+ ...rowProps,
1293
+ locale: localeId
1294
+ },
1295
+ rowIndex,
1296
+ rowKey
1297
+ });
1298
+ }
1299
+ return cells;
1300
+ };
1301
+ //#endregion
1302
+ //#region src/batch/batch.ts
1163
1303
  const runBatch = async (input) => {
1164
1304
  const start = performance.now();
1165
1305
  const { baseProps = {}, dataFile, failFast = false, cacheDir: cacheDirOverride, locales: filterLocales, outDir: outDirOverride, presets: filterPresets, rootDir, rows: inlineRows, templateId } = input;
@@ -1177,63 +1317,48 @@ const runBatch = async (input) => {
1177
1317
  width: p.width
1178
1318
  };
1179
1319
  });
1180
- const locales = await resolveLocales(rootDir, filterLocales ?? config.locales ?? [], templateId);
1320
+ const resolvedLocales = await resolveLocales(rootDir, filterLocales ?? config.locales ?? [], templateId);
1321
+ const localeDataMap = new Map(resolvedLocales.map((l) => [l.id, l.data ?? {}]));
1322
+ const loadLocaleData = (code) => Promise.resolve(localeDataMap.get(code) ?? {});
1323
+ const rows = dataFile ? await parseDataFile(path.resolve(rootDir, dataFile)) : inlineRows ?? [];
1181
1324
  const cells = expandMatrix({
1182
1325
  baseProps,
1183
- locales,
1326
+ locales: resolvedLocales.map((l) => l.id),
1184
1327
  presets,
1185
- rows: dataFile ? await parseDataFile(path.resolve(rootDir, dataFile)) : inlineRows ?? []
1328
+ rows
1186
1329
  });
1187
1330
  const outDir = path.resolve(rootDir, outDirOverride ?? config.outDir ?? "./out");
1188
- const cache = new RenderCache({ cacheDir: cacheDirOverride ?? path.resolve(rootDir, ".studio-cache") });
1189
- await cache.init();
1190
- const arabicLocales = locales.filter((l) => l.id.startsWith("ar"));
1191
- logDebug("Arabic locale detection", {
1192
- arabicCount: arabicLocales.length,
1193
- localeIds: locales.map((l) => l.id)
1194
- });
1195
- let fonts = [];
1196
- let fontFamilies;
1197
- if (arabicLocales.length > 0) {
1198
- const { fonts: resolvedFonts, fontFamilies: resolvedFamilies } = await resolveArabicFonts(locales);
1199
- fonts = resolvedFonts;
1200
- fontFamilies = resolvedFamilies;
1201
- }
1331
+ const cache = await openCache(rootDir, { cacheDir: cacheDirOverride });
1202
1332
  const succeeded = [];
1203
1333
  const failed = [];
1204
- const renderOptions = fonts.length > 0 ? {
1205
- fontFamilies,
1206
- fonts
1207
- } : void 0;
1208
- logDebug("renderOptions before loop", {
1209
- fontFamilies,
1210
- fontsCount: fonts.length,
1211
- hasOptions: renderOptions !== void 0
1212
- });
1213
1334
  try {
1214
1335
  for (const cell of cells) try {
1215
- const result = await renderOne({
1336
+ const filename = generateOutputFilename({
1337
+ locale: cell.locale,
1338
+ presetId: cell.preset.id,
1339
+ rowKey: cell.rowKey,
1340
+ templateId
1341
+ });
1342
+ const result = await orchestrateRender({
1216
1343
  cache,
1217
- compiledTemplate: await compileTemplate(createElement(Component, cell.props)),
1218
- contentHash,
1219
- filename: generateOutputFilename({
1220
- locale: cell.locale,
1221
- presetId: cell.preset.id,
1222
- rowKey: cell.rowKey,
1223
- templateId
1224
- }),
1344
+ component: Component,
1345
+ format: "png",
1225
1346
  height: cell.preset.height,
1347
+ loadLocaleData,
1226
1348
  locale: cell.locale,
1227
- outDir,
1228
1349
  props: cell.props,
1229
- renderOptions,
1350
+ propsSchema: module.propsSchema,
1351
+ templateContentHash: contentHash,
1230
1352
  templateId,
1231
1353
  width: cell.preset.width
1232
1354
  });
1355
+ const outputPath = `${outDir}/${filename}`;
1356
+ await ensureDir(outDir);
1357
+ await tryWriteFile(outputPath, result.bytes);
1233
1358
  succeeded.push({
1234
1359
  cacheHit: result.cacheHit,
1235
1360
  locale: cell.locale,
1236
- outputPath: result.outputPath,
1361
+ outputPath,
1237
1362
  preset: cell.preset.id,
1238
1363
  rowIndex: cell.rowIndex
1239
1364
  });
@@ -1263,4 +1388,4 @@ const runBatch = async (input) => {
1263
1388
  };
1264
1389
  };
1265
1390
  //#endregion
1266
- export { AppError, ErrorCode, PropValidationError, RenderCache, compileTemplate, expandMatrix, generateOutputFilename, importTemplateForBatch, isZodObject, loadConfig, logDebug, logError, logInfo, logWarn, measureTemplate, parseDataFile, registerFont, renderAutoSized, renderComponent, renderOne, renderToPixels, resolveLocales, runBatch, studioConfigSchema, validateProps, writeManifest, zodToJsonSchemaShape };
1391
+ export { AppError, ErrorCode, PropValidationError, RenderCache, compileTemplate, importTemplateForBatch, isZodObject, loadConfig, logDebug, logError, logInfo, logWarn, measureTemplate, openCache, orchestrateMeasure, orchestrateRender, parseStudioConfig, registerFont, renderAutoSized, renderComponent, renderToPixels, resolveAssetsForLocale, resolveProjectPaths, runBatch, studioConfigSchema, validateProps, zodToJsonSchemaShape };
package/dist/schema.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { i as defineSchema, t as PropSchema } from "./validation-BUNZnc-5.mjs";
1
+ import { r as defineSchema, t as PropSchema } from "./validation-MigzRuji.mjs";
2
2
  export { type PropSchema, defineSchema };
@@ -36,4 +36,4 @@ declare const validateProps: <T extends PropSchema>(schema: T, props: unknown) =
36
36
  declare const isZodObject: (schema: unknown) => schema is z.ZodObject<Record<string, z.ZodTypeAny>>;
37
37
  declare const zodToJsonSchemaShape: (schema: z.ZodTypeAny) => Record<string, unknown>;
38
38
  //#endregion
39
- export { isZodObject as a, defineSchema as i, PropValidationError as n, validateProps as o, PropValidationIssue as r, zodToJsonSchemaShape as s, PropSchema as t };
39
+ export { validateProps as a, isZodObject as i, PropValidationError as n, zodToJsonSchemaShape as o, defineSchema as r, PropSchema as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stdout-design/core",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Core rendering engine for stdout-design — compile TSX templates to pixel-perfect images via Takumi",
5
5
  "keywords": [
6
6
  "design",
@@ -49,32 +49,33 @@
49
49
  "build": "tsdown",
50
50
  "dev": "tsdown --watch",
51
51
  "test": "bun test",
52
+ "test:integration": "bun test ./src/__tests__/full-pipeline.integration.ts",
52
53
  "check-types": "tsc --noEmit",
53
54
  "check-types:tsgo": "tsgo --noEmit",
54
55
  "lint": "ultracite check"
55
56
  },
56
57
  "dependencies": {
57
- "@takumi-rs/core": "^2.0.0",
58
- "@takumi-rs/helpers": "^2.0.0",
58
+ "@takumi-rs/core": "^2.1.1",
59
+ "@takumi-rs/helpers": "^2.1.1",
59
60
  "csv-parse": "^7.0.1",
60
61
  "kysely": "^0.29.3",
61
62
  "kysely-bun-sqlite": "^0.4.0",
62
- "p-limit": "^7.0.0",
63
+ "p-limit": "^7.3.0",
63
64
  "proper-lockfile": "^4.1.2",
64
65
  "react": "^19.2.7",
65
- "takumi-js": "^2.0.0",
66
+ "takumi-js": "^2.1.1",
66
67
  "write-file-atomic": "^8.0.0",
67
68
  "zod": "^4.4.3"
68
69
  },
69
70
  "devDependencies": {
70
71
  "@sinonjs/fake-timers": "^15.4.0",
71
72
  "@stdout-design/config": "workspace:*",
72
- "@takumi-rs/wasm": "^2.0.0",
73
+ "@takumi-rs/wasm": "^2.1.1",
73
74
  "@types/proper-lockfile": "^4.1.4",
74
75
  "@types/react": "^19.2.17",
75
76
  "@types/sinonjs__fake-timers": "^15.0.1",
76
77
  "@types/write-file-atomic": "^4.0.3",
77
- "tsdown": "^0.22.4",
78
- "typescript": "^7"
78
+ "tsdown": "^0.22.5",
79
+ "typescript": "^7.0.2"
79
80
  }
80
81
  }