@nifrajs/content 5.4.0 → 5.5.0
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/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/src/fs.ts +12 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @nifrajs/content
|
|
2
2
|
|
|
3
|
+
## 5.5.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 6ca916c: A collection with `watch` on re-reads a file that was replaced in place with the same size and modification time, as a deploy or sync tool does when it preserves times. Its cache recognised a file by size and mtime alone, so `get()` and `all()` kept serving the old content until either changed. The cache now also compares the file's device, inode and change time.
|
|
8
|
+
|
|
3
9
|
## 5.4.0
|
|
4
10
|
|
|
5
11
|
No changes in this release.
|
package/package.json
CHANGED
package/src/fs.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* gives you a typed, validated API over them. Build/server-time (Bun/Node/Deno); for the edge, bake the
|
|
4
4
|
* entries at build (the manifest approach `@nifrajs/web/fs` uses for routes) - tracked as a follow-up.
|
|
5
5
|
*/
|
|
6
|
-
import type {
|
|
6
|
+
import type { BigIntStats } from "node:fs"
|
|
7
7
|
import { readdir, readFile, stat as statPath } from "node:fs/promises"
|
|
8
8
|
import { resolve, sep } from "node:path"
|
|
9
9
|
import { type Entry, type InferSchema, parseEntry, type StandardSchemaV1 } from "./index.ts"
|
|
@@ -51,6 +51,12 @@ const assertSafeExtensions = (extensions: readonly string[]): void => {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
// File identity and ctime as well as mtime and size: an atomic replace that keeps both the size and
|
|
55
|
+
// the mtime (`touch -r`, a sync tool preserving times) still swaps the inode and moves the ctime.
|
|
56
|
+
// BigInt stats, because a 64-bit file id (Windows) and a nanosecond time do not fit a double.
|
|
57
|
+
const stampOf = (stat: BigIntStats): string =>
|
|
58
|
+
`${stat.dev}:${stat.ino}:${stat.ctimeNs}:${stat.mtimeNs}:${stat.size}`
|
|
59
|
+
|
|
54
60
|
const insideRoot = (root: string, file: string): boolean =>
|
|
55
61
|
file !== root && file.startsWith(`${root}${sep}`)
|
|
56
62
|
|
|
@@ -109,8 +115,8 @@ export function defineCollection<S extends StandardSchemaV1>(
|
|
|
109
115
|
return Promise.all(
|
|
110
116
|
files.map(async (file) => {
|
|
111
117
|
const path = resolve(root, file)
|
|
112
|
-
const stat = await statPath(path)
|
|
113
|
-
return { file, path, stamp:
|
|
118
|
+
const stat = await statPath(path, { bigint: true })
|
|
119
|
+
return { file, path, stamp: stampOf(stat) }
|
|
114
120
|
}),
|
|
115
121
|
)
|
|
116
122
|
}
|
|
@@ -148,16 +154,16 @@ export function defineCollection<S extends StandardSchemaV1>(
|
|
|
148
154
|
for (const ext of extensions) {
|
|
149
155
|
const file = resolve(root, `${slug}${ext}`)
|
|
150
156
|
if (!insideRoot(root, file)) return null
|
|
151
|
-
let stat:
|
|
157
|
+
let stat: BigIntStats
|
|
152
158
|
try {
|
|
153
|
-
stat = await statPath(file)
|
|
159
|
+
stat = await statPath(file, { bigint: true })
|
|
154
160
|
} catch {
|
|
155
161
|
fileCache.delete(file)
|
|
156
162
|
continue // Not this extension; try the next configured one.
|
|
157
163
|
}
|
|
158
164
|
if (!stat.isFile()) continue
|
|
159
165
|
// Outside the try: a file that exists but fails to parse is an error, not a missing entry.
|
|
160
|
-
return await readEntry(file, slug,
|
|
166
|
+
return await readEntry(file, slug, stampOf(stat))
|
|
161
167
|
}
|
|
162
168
|
return null
|
|
163
169
|
},
|