@boon4681/giri 0.0.2-alpha-8 → 0.0.3-alpha-1

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.ts CHANGED
@@ -82,6 +82,12 @@ interface BuildGiriAppOptions {
82
82
  services?: Services;
83
83
  /** Files that changed since last build — only these are purged from require.cache before loading. */
84
84
  dirty?: Set<string>;
85
+ /** Defer route-module evaluation until the adapter first reads its runtime fields. */
86
+ lazy?: boolean;
87
+ /** The caller owns a persistent TypeScript loader for lazy route evaluation. */
88
+ loaderRegistered?: boolean;
89
+ /** The caller owns a persistent project-alias resolver for lazy route evaluation. */
90
+ aliasResolverRegistered?: boolean;
85
91
  }
86
92
  interface BuiltGiriApp<App> {
87
93
  app: App;
package/dist/index.js CHANGED
@@ -45,13 +45,7 @@ var init_es5 = __esm({
45
45
  });
46
46
 
47
47
  // src/generator/schema/program.ts
48
- function leanOptions(options) {
49
- return {
50
- ...options,
51
- types: []
52
- };
53
- }
54
- function createSchemaProgram(paths, routeFiles, programOptions = {}) {
48
+ function createSchemaProgram(paths, routeFiles) {
55
49
  let options = { ...DEFAULT_OPTIONS };
56
50
  const configPath = import_typescript2.default.findConfigFile(paths.cwd, import_typescript2.default.sys.fileExists, "tsconfig.json");
57
51
  if (configPath) {
@@ -64,9 +58,6 @@ function createSchemaProgram(paths, routeFiles, programOptions = {}) {
64
58
  options = { ...parsed.options, noEmit: true };
65
59
  }
66
60
  }
67
- if (programOptions.lean) {
68
- options = leanOptions(options);
69
- }
70
61
  return import_typescript2.default.createProgram(routeFiles, options);
71
62
  }
72
63
  var import_typescript2, DEFAULT_OPTIONS;
@@ -1108,8 +1099,11 @@ async function buildGiriApp(config, options = {}) {
1108
1099
  const routes = await scanRoutes(paths.routesDir);
1109
1100
  const app = config.adapter.createApp();
1110
1101
  ensureGiriAliasResolver(paths.outDir);
1111
- const { unregister } = await safeRegister();
1112
- const unregisterAliasResolver = registerAliasResolver(config.alias, paths.cwd);
1102
+ if (options.lazy && (!options.loaderRegistered || !options.aliasResolverRegistered)) {
1103
+ throw new Error("Lazy route loading requires persistent loader and alias registrations.");
1104
+ }
1105
+ const loader = options.loaderRegistered ? void 0 : await safeRegister();
1106
+ const unregisterAliasResolver = options.aliasResolverRegistered ? void 0 : registerAliasResolver(config.alias, paths.cwd);
1113
1107
  try {
1114
1108
  const dirty = options.dirty;
1115
1109
  const forceReload = dirty === void 0;
@@ -1121,7 +1115,7 @@ async function buildGiriApp(config, options = {}) {
1121
1115
  }
1122
1116
  return sharedCache.get(file);
1123
1117
  };
1124
- for (const route of routes) {
1118
+ const runtimeFor = (route) => {
1125
1119
  const routeModule = loadModule(route.file, isDirty(route.file));
1126
1120
  if (typeof routeModule.handle !== "function") {
1127
1121
  throw new Error(`${route.file} must export a named handle function.`);
@@ -1130,7 +1124,7 @@ async function buildGiriApp(config, options = {}) {
1130
1124
  (file) => normalizeMiddleware(loadShared(file).middleware, file)
1131
1125
  );
1132
1126
  const verbMiddleware = normalizeMiddleware(routeModule.middleware, route.file);
1133
- config.adapter.register(app, {
1127
+ return {
1134
1128
  method: route.method,
1135
1129
  path: route.path,
1136
1130
  handle: routeModule.handle,
@@ -1138,19 +1132,45 @@ async function buildGiriApp(config, options = {}) {
1138
1132
  input: routeInput(routeModule, route.file),
1139
1133
  services: options.services,
1140
1134
  cookieSecret: config.cookieSecret
1135
+ };
1136
+ };
1137
+ for (const route of routes) {
1138
+ if (!options.lazy) {
1139
+ config.adapter.register(app, runtimeFor(route));
1140
+ continue;
1141
+ }
1142
+ let runtime;
1143
+ const getRuntime = () => {
1144
+ runtime ??= runtimeFor(route);
1145
+ return runtime;
1146
+ };
1147
+ config.adapter.register(app, {
1148
+ method: route.method,
1149
+ path: route.path,
1150
+ get handle() {
1151
+ return getRuntime().handle;
1152
+ },
1153
+ get middleware() {
1154
+ return getRuntime().middleware;
1155
+ },
1156
+ get input() {
1157
+ return getRuntime().input;
1158
+ },
1159
+ services: options.services,
1160
+ cookieSecret: config.cookieSecret
1141
1161
  });
1142
1162
  }
1143
1163
  } finally {
1144
- unregisterAliasResolver();
1145
- unregister();
1164
+ unregisterAliasResolver?.();
1165
+ loader?.unregister();
1146
1166
  }
1147
1167
  return { app, routes, paths };
1148
1168
  }
1149
1169
 
1150
1170
  // src/generator/sync.ts
1151
- var import_node_fs7 = require("fs");
1152
- var import_promises3 = require("fs/promises");
1153
- var import_node_path11 = require("path");
1171
+ var import_node_fs8 = require("fs");
1172
+ var import_promises4 = require("fs/promises");
1173
+ var import_node_path12 = require("path");
1154
1174
 
1155
1175
  // src/generator/app-types.ts
1156
1176
  var import_node_fs4 = require("fs");
@@ -2097,6 +2117,103 @@ async function writeTsConfig(paths, config) {
2097
2117
  });
2098
2118
  }
2099
2119
 
2120
+ // src/generator/cache.ts
2121
+ var import_node_crypto = require("crypto");
2122
+ var import_node_fs7 = require("fs");
2123
+ var import_promises3 = require("fs/promises");
2124
+ var import_node_path11 = require("path");
2125
+ var import_tinyglobby2 = require("tinyglobby");
2126
+ var CACHE_VERSION = 1;
2127
+ var SYNC_CACHE_NAME = ".sync-cache.json";
2128
+ function stableConfig(config) {
2129
+ const alias = Object.entries(config.alias ?? {}).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => [key, Array.isArray(value) ? [...value] : value]);
2130
+ return { alias, outDir: config.outDir ?? ".giri" };
2131
+ }
2132
+ async function syncFingerprint(config, paths) {
2133
+ const outRelative = slash((0, import_node_path11.relative)(paths.cwd, paths.outDir));
2134
+ const ignore = ["**/node_modules/**", "**/.git/**"];
2135
+ if (outRelative && !outRelative.startsWith("..")) {
2136
+ ignore.push(`${outRelative}/**`);
2137
+ }
2138
+ const files = await (0, import_tinyglobby2.glob)([
2139
+ "src/**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs,json}",
2140
+ "giri.config.{ts,js,mts,cts,mjs,cjs}",
2141
+ "tsconfig*.json",
2142
+ "package.json",
2143
+ "package-lock.json",
2144
+ "npm-shrinkwrap.json",
2145
+ "yarn.lock",
2146
+ "pnpm-lock.yaml",
2147
+ "bun.lock",
2148
+ "bun.lockb"
2149
+ ], {
2150
+ cwd: paths.cwd,
2151
+ absolute: false,
2152
+ onlyFiles: true,
2153
+ dot: true,
2154
+ ignore
2155
+ });
2156
+ const hash = (0, import_node_crypto.createHash)("sha256");
2157
+ hash.update(JSON.stringify(stableConfig(config)));
2158
+ for (const file of files.sort()) {
2159
+ hash.update("\0");
2160
+ hash.update(slash(file));
2161
+ hash.update("\0");
2162
+ hash.update(await (0, import_promises3.readFile)((0, import_node_path11.resolve)(paths.cwd, file)));
2163
+ }
2164
+ return hash.digest("hex");
2165
+ }
2166
+ function cachePath(paths) {
2167
+ return (0, import_node_path11.join)(paths.outDir, SYNC_CACHE_NAME);
2168
+ }
2169
+ function serializePath(paths, file) {
2170
+ return slash((0, import_node_path11.relative)(paths.cwd, file));
2171
+ }
2172
+ function deserializePath(paths, file) {
2173
+ return slash((0, import_node_path11.resolve)(paths.cwd, file.split("/").join(import_node_path11.sep)));
2174
+ }
2175
+ function serializeMap(paths, values) {
2176
+ return [...values].map(([file, value]) => [serializePath(paths, file), value]);
2177
+ }
2178
+ function deserializeMap(paths, values) {
2179
+ return new Map(values.map(([file, value]) => [deserializePath(paths, file), value]));
2180
+ }
2181
+ async function readSyncCache(paths, fingerprint) {
2182
+ const file = cachePath(paths);
2183
+ if (!(0, import_node_fs7.existsSync)(file)) {
2184
+ return void 0;
2185
+ }
2186
+ try {
2187
+ const cache = JSON.parse(await (0, import_promises3.readFile)(file, "utf8"));
2188
+ if (cache.version !== CACHE_VERSION || cache.fingerprint !== fingerprint) {
2189
+ return void 0;
2190
+ }
2191
+ return {
2192
+ responsesByFile: deserializeMap(paths, cache.data.responsesByFile),
2193
+ inputsByFile: deserializeMap(paths, cache.data.inputsByFile),
2194
+ securityByFile: deserializeMap(paths, cache.data.securityByFile),
2195
+ hiddenFiles: new Set(cache.data.hiddenFiles.map((entry) => deserializePath(paths, entry))),
2196
+ openapiByFile: deserializeMap(paths, cache.data.openapiByFile)
2197
+ };
2198
+ } catch {
2199
+ return void 0;
2200
+ }
2201
+ }
2202
+ async function writeSyncCache(paths, fingerprint, data) {
2203
+ const cache = {
2204
+ version: CACHE_VERSION,
2205
+ fingerprint,
2206
+ data: {
2207
+ responsesByFile: serializeMap(paths, data.responsesByFile),
2208
+ inputsByFile: serializeMap(paths, data.inputsByFile),
2209
+ securityByFile: serializeMap(paths, data.securityByFile),
2210
+ hiddenFiles: [...data.hiddenFiles].map((file) => serializePath(paths, file)),
2211
+ openapiByFile: serializeMap(paths, data.openapiByFile)
2212
+ }
2213
+ };
2214
+ await writeJson(cachePath(paths), cache);
2215
+ }
2216
+
2100
2217
  // src/generator/sync.ts
2101
2218
  async function typeFolders(paths, routes) {
2102
2219
  const verbsByDir = /* @__PURE__ */ new Map();
@@ -2123,57 +2240,17 @@ async function extractResponses(paths, routes) {
2123
2240
  try {
2124
2241
  const { createSchemaProgram: createSchemaProgram2, extractRouteResponses: extractRouteResponses2 } = await Promise.resolve().then(() => (init_schema(), schema_exports));
2125
2242
  const files = [...new Set(routes.map((route) => route.file))];
2126
- const appTypes = (0, import_node_path11.join)(paths.outDir, "types", "app.d.ts");
2127
- const roots = (0, import_node_fs7.existsSync)(appTypes) ? [...files, appTypes] : files;
2128
- const program = createSchemaProgram2(paths, roots, { lean: true });
2129
- const fallbackFiles = [];
2243
+ const appTypes = (0, import_node_path12.join)(paths.outDir, "types", "app.d.ts");
2244
+ const roots = (0, import_node_fs8.existsSync)(appTypes) ? [...files, appTypes] : files;
2245
+ const program = createSchemaProgram2(paths, roots);
2130
2246
  for (const file of files) {
2131
- const responses = extractRouteResponses2(program, file);
2132
- byFile.set(file, responses);
2133
- if (hasLooseResponseSchema(responses)) {
2134
- fallbackFiles.push(file);
2135
- }
2136
- }
2137
- if (fallbackFiles.length > 0) {
2138
- const fullProgram = createSchemaProgram2(
2139
- paths,
2140
- (0, import_node_fs7.existsSync)(appTypes) ? [...fallbackFiles, appTypes] : fallbackFiles
2141
- );
2142
- for (const file of fallbackFiles) {
2143
- byFile.set(file, extractRouteResponses2(fullProgram, file));
2144
- }
2247
+ byFile.set(file, extractRouteResponses2(program, file));
2145
2248
  }
2146
2249
  } catch (error) {
2147
2250
  console.warn(`giri: skipped response schema generation (${error.message}).`);
2148
2251
  }
2149
2252
  return byFile;
2150
2253
  }
2151
- function isLooseSchema(value) {
2152
- if (!value || typeof value !== "object") {
2153
- return false;
2154
- }
2155
- const schema = value;
2156
- const keys = Object.keys(schema);
2157
- if (keys.length === 0) {
2158
- return true;
2159
- }
2160
- if (typeof schema.$ref === "string") {
2161
- return false;
2162
- }
2163
- if (Array.isArray(schema.anyOf) && schema.anyOf.some(isLooseSchema)) {
2164
- return true;
2165
- }
2166
- if (schema.items && isLooseSchema(schema.items)) {
2167
- return true;
2168
- }
2169
- if (schema.properties && typeof schema.properties === "object") {
2170
- return Object.values(schema.properties).some(isLooseSchema);
2171
- }
2172
- return false;
2173
- }
2174
- function hasLooseResponseSchema(responses) {
2175
- return responses.responses.some((response) => isLooseSchema(response.schema));
2176
- }
2177
2254
  async function extractMeta(config, paths, routes) {
2178
2255
  const inputsByFile = /* @__PURE__ */ new Map();
2179
2256
  const securityByFile = /* @__PURE__ */ new Map();
@@ -2206,28 +2283,44 @@ async function extractMeta(config, paths, routes) {
2206
2283
  async function syncProject(config, options = {}) {
2207
2284
  const paths = resolveGiriPaths(config, options.cwd);
2208
2285
  assertSafeOutDir(paths);
2209
- const hadOutDir = (0, import_node_fs7.existsSync)(paths.outDir);
2286
+ const hadOutDir = (0, import_node_fs8.existsSync)(paths.outDir);
2210
2287
  const routes = await scanRoutes(paths.routesDir);
2211
2288
  const folders = await typeFolders(paths, routes);
2212
- await (0, import_promises3.mkdir)(paths.outDir, { recursive: true });
2289
+ const fingerprint = await syncFingerprint(config, paths);
2290
+ const cached = await readSyncCache(paths, fingerprint);
2291
+ const generatedFiles = [
2292
+ (0, import_node_path12.join)(paths.outDir, "tsconfig.json"),
2293
+ (0, import_node_path12.join)(paths.outDir, "manifest.json"),
2294
+ (0, import_node_path12.join)(paths.outDir, "openapi.json"),
2295
+ (0, import_node_path12.join)(paths.outDir, "routes.d.ts"),
2296
+ (0, import_node_path12.join)(paths.outDir, "types", "app.d.ts"),
2297
+ ...folders.map((folder) => typeFilePath(paths, folder.dir))
2298
+ ];
2299
+ if (cached && generatedFiles.every(import_node_fs8.existsSync)) {
2300
+ return { paths, routes, folders, data: cached };
2301
+ }
2302
+ await (0, import_promises4.mkdir)(paths.outDir, { recursive: true });
2213
2303
  await writeParamTypes(paths, folders);
2214
2304
  await writeRouteTypes(paths, routes);
2215
2305
  await writeAppTypes(paths);
2216
2306
  await writeTsConfig(paths, config);
2217
- const responsesByFile = await extractResponses(paths, routes);
2218
- const { inputsByFile, securityByFile, hiddenFiles, openapiByFile } = await extractMeta(config, paths, routes);
2219
- const data = { responsesByFile, inputsByFile, securityByFile, hiddenFiles, openapiByFile };
2307
+ const data = cached ?? {
2308
+ responsesByFile: await extractResponses(paths, routes),
2309
+ ...await extractMeta(config, paths, routes)
2310
+ };
2220
2311
  await writeManifest(paths, routes, data);
2221
2312
  await writeOpenApi(paths, routes, data);
2313
+ await writeSyncCache(paths, fingerprint, data);
2222
2314
  if (hadOutDir) {
2223
2315
  await pruneDir(
2224
2316
  paths.outDir,
2225
2317
  /* @__PURE__ */ new Set([
2226
- (0, import_node_path11.join)(paths.outDir, "tsconfig.json"),
2227
- (0, import_node_path11.join)(paths.outDir, "manifest.json"),
2228
- (0, import_node_path11.join)(paths.outDir, "openapi.json"),
2229
- (0, import_node_path11.join)(paths.outDir, "routes.d.ts"),
2230
- (0, import_node_path11.join)(paths.outDir, "types", "app.d.ts"),
2318
+ (0, import_node_path12.join)(paths.outDir, "tsconfig.json"),
2319
+ (0, import_node_path12.join)(paths.outDir, "manifest.json"),
2320
+ (0, import_node_path12.join)(paths.outDir, "openapi.json"),
2321
+ (0, import_node_path12.join)(paths.outDir, "routes.d.ts"),
2322
+ (0, import_node_path12.join)(paths.outDir, SYNC_CACHE_NAME),
2323
+ (0, import_node_path12.join)(paths.outDir, "types", "app.d.ts"),
2231
2324
  ...folders.map((folder) => typeFilePath(paths, folder.dir))
2232
2325
  ])
2233
2326
  );
@@ -2236,27 +2329,33 @@ async function syncProject(config, options = {}) {
2236
2329
  }
2237
2330
 
2238
2331
  // src/generator/watch.ts
2239
- var import_node_fs8 = require("fs");
2332
+ var import_node_fs10 = require("fs");
2333
+ var import_node_path15 = require("path");
2334
+
2335
+ // src/loader/import-graph.ts
2336
+ var import_node_fs9 = require("fs");
2240
2337
  var import_node_path13 = require("path");
2338
+ var import_typescript5 = __toESM(require("typescript"));
2339
+ var import_tinyglobby3 = require("tinyglobby");
2241
2340
 
2242
2341
  // src/loader/module-loader.ts
2243
- var import_node_path12 = require("path");
2342
+ var import_node_path14 = require("path");
2244
2343
 
2245
2344
  // src/lifecycle.ts
2246
- var import_node_fs9 = require("fs");
2247
- var import_node_path14 = require("path");
2345
+ var import_node_fs11 = require("fs");
2346
+ var import_node_path16 = require("path");
2248
2347
  var MAIN_EXTENSIONS2 = ["ts", "tsx", "mts", "cts", "js", "jsx", "mjs", "cjs"];
2249
2348
  function resolveMainFile(cwd) {
2250
2349
  for (const ext of MAIN_EXTENSIONS2) {
2251
- const file = (0, import_node_path14.join)(cwd, "src", `main.${ext}`);
2252
- if ((0, import_node_fs9.existsSync)(file)) {
2350
+ const file = (0, import_node_path16.join)(cwd, "src", `main.${ext}`);
2351
+ if ((0, import_node_fs11.existsSync)(file)) {
2253
2352
  return file;
2254
2353
  }
2255
2354
  }
2256
2355
  return void 0;
2257
2356
  }
2258
2357
  async function loadLifecycle(cwd = process.cwd()) {
2259
- const file = resolveMainFile((0, import_node_path14.resolve)(cwd));
2358
+ const file = resolveMainFile((0, import_node_path16.resolve)(cwd));
2260
2359
  if (!file) {
2261
2360
  return {};
2262
2361
  }