lopata 0.4.0 → 0.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lopata",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -77,8 +77,8 @@ function isPkcs1RsaKey(data: Uint8Array): boolean {
77
77
  if (data.length < 10 || data[0] !== 0x30) return false
78
78
  let offset = 1
79
79
  // Skip outer SEQUENCE length
80
- if (data[offset] & 0x80) {
81
- offset += 1 + (data[offset] & 0x7f)
80
+ if (data[offset]! & 0x80) {
81
+ offset += 1 + (data[offset]! & 0x7f)
82
82
  } else {
83
83
  offset += 1
84
84
  }
@@ -138,7 +138,7 @@ function wrapPkcs1InPkcs8(pkcs1Key: Uint8Array): Uint8Array {
138
138
  return derWrap(0x30, inner)
139
139
  }
140
140
 
141
- function toUint8Array(data: BufferSource): Uint8Array {
141
+ function toUint8Array(data: ArrayBuffer | ArrayBufferView): Uint8Array {
142
142
  if (data instanceof Uint8Array) return data
143
143
  if (ArrayBuffer.isView(data)) return new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
144
144
  return new Uint8Array(data)
@@ -166,19 +166,13 @@ export function patchGlobalCrypto(): void {
166
166
  // Patch importKey to accept PKCS#1 RSA keys with "pkcs8" format (matching workerd behavior).
167
167
  // Workerd is lenient and auto-wraps PKCS#1 in PKCS#8; Bun/Node native crypto rejects it.
168
168
  const origImportKey = subtle.importKey.bind(subtle)
169
- subtle.importKey = function(
170
- format: KeyFormat,
171
- keyData: BufferSource | JsonWebKey,
172
- algorithm: AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm,
173
- extractable: boolean,
174
- keyUsages: readonly KeyUsage[],
175
- ): Promise<CryptoKey> {
176
- if (format === 'pkcs8' && !(keyData as JsonWebKey).kty) {
177
- const bytes = toUint8Array(keyData as BufferSource)
169
+ subtle.importKey = ((format: string, keyData: unknown, algorithm: unknown, extractable: boolean, keyUsages: readonly string[]) => {
170
+ if (format === 'pkcs8' && typeof keyData === 'object' && keyData !== null && !('kty' in keyData)) {
171
+ const bytes = toUint8Array(keyData as ArrayBuffer | ArrayBufferView)
178
172
  if (isPkcs1RsaKey(bytes)) {
179
- return origImportKey('pkcs8', wrapPkcs1InPkcs8(bytes), algorithm, extractable, keyUsages)
173
+ return (origImportKey as any)('pkcs8', wrapPkcs1InPkcs8(bytes), algorithm, extractable, [...keyUsages])
180
174
  }
181
175
  }
182
- return origImportKey(format, keyData as BufferSource, algorithm, extractable, keyUsages)
183
- }
176
+ return (origImportKey as any)(format, keyData, algorithm, extractable, [...keyUsages])
177
+ }) as typeof subtle.importKey
184
178
  }
@@ -52,6 +52,8 @@ export function devServerPlugin(options: DevServerPluginOptions): Plugin {
52
52
  let currentModule: Record<string, unknown> | null = null
53
53
  // Serializes module reload — prevents concurrent wireClassRefs calls
54
54
  let reloadLock: Promise<void> | null = null
55
+ // Flag set on file change; next request will clear runner cache
56
+ let needsReload = false
55
57
 
56
58
  return {
57
59
  name: 'lopata:dev-server',
@@ -133,7 +135,7 @@ export function devServerPlugin(options: DevServerPluginOptions): Plugin {
133
135
  config,
134
136
  gracePeriodMs: 0,
135
137
  get active() {
136
- return currentModule ? { workerModule: currentModule, env } : null
138
+ return currentModule ? { workerModule: currentModule, env, registry } : null
137
139
  },
138
140
  list() {
139
141
  return []
@@ -165,7 +167,24 @@ export function devServerPlugin(options: DevServerPluginOptions): Plugin {
165
167
  apiMod.setWorkerRegistry(workerRegistry)
166
168
  }
167
169
 
168
- // 5. Set up WebSocket trace streaming on httpServer
170
+ // 5. Track SSR-relevant file changes.
171
+ // Vite's built-in HMR flow invalidates the module graph and sends
172
+ // full-reload events to the runner. However, the runner's async HMR
173
+ // handler can race with incoming requests. We set a flag here and
174
+ // clear the runner's evaluated module cache on the next request,
175
+ // ensuring a clean re-evaluation from the freshly-invalidated module graph.
176
+ server.watcher.on('change', (file) => {
177
+ const ssrEnv = server.environments[options.envName]
178
+ if (!ssrEnv) return
179
+ const normalizedFile = file.replace(/\\/g, '/')
180
+ const mods = ssrEnv.moduleGraph.getModulesByFile(normalizedFile)
181
+ if (mods && mods.size > 0) {
182
+ needsReload = true
183
+ currentModule = null
184
+ }
185
+ })
186
+
187
+ // 6. Set up WebSocket trace streaming on httpServer
169
188
  setupTraceWebSocket(server)
170
189
 
171
190
  // 6. Return middleware callback (post-middleware — runs after framework plugins)
@@ -225,6 +244,19 @@ export function devServerPlugin(options: DevServerPluginOptions): Plugin {
225
244
  // Wait for any in-progress reload before importing
226
245
  if (reloadLock) await reloadLock
227
246
 
247
+ // Clear runner's evaluated module cache when files changed.
248
+ // The module graph is already invalidated by Vite's onFileChange,
249
+ // but the runner caches evaluated modules independently.
250
+ // Using .clear() (like the Cloudflare plugin) wipes all three
251
+ // maps (idToModuleMap, fileToModulesMap, urlToIdModuleMap),
252
+ // forcing a full re-evaluation from the fresh module graph.
253
+ if (needsReload) {
254
+ needsReload = false
255
+ const runner = (ssrEnv as { runner: { evaluatedModules: { clear(): void } } }).runner
256
+ runner.evaluatedModules.clear()
257
+ console.log('[lopata:vite] Cleared runner module cache (file change detected)')
258
+ }
259
+
228
260
  const workerModule = await (ssrEnv as any).runner.import(entrypoint) as Record<string, unknown>
229
261
 
230
262
  // Re-wire class refs when module changes (HMR invalidation)