@fedify/vocab 2.0.0-dev.241 → 2.0.0-dev.323
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/deno.json +9 -2
- package/dist/actor.test.js +2 -2
- package/dist/{deno-BcC99yBa.js → deno-CpsTU8-l.js} +8 -2
- package/dist/lookup.test.js +13 -11
- package/dist/mod.cjs +1555 -6799
- package/dist/mod.d.cts +1724 -1724
- package/dist/mod.d.ts +1724 -1724
- package/dist/mod.js +870 -6120
- package/dist/type.test.js +1 -1
- package/dist/{vocab-BmxSLhXr.js → vocab-B4nUNXTL.js} +852 -6106
- package/dist/vocab.test.js +2 -1
- package/package.json +9 -7
- package/scripts/codegen.ts +147 -11
- package/src/lookup.test.ts +15 -0
- package/src/lookup.ts +4 -4
- package/src/mod.ts +6 -0
package/dist/vocab.test.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
import { Temporal } from "@js-temporal/polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
|
|
5
|
-
import { Activity, Announce, Collection, Create, CryptographicKey, Follow, Hashtag,
|
|
5
|
+
import { Activity, Announce, Collection, Create, CryptographicKey, Follow, Hashtag, Link, Note, Object as Object$1, OrderedCollectionPage, Person, Place, Question, Source, mockDocumentLoader, test, vocab_exports } from "./vocab-B4nUNXTL.js";
|
|
6
6
|
import { assertInstanceOf } from "./utils-Dm0Onkcz.js";
|
|
7
7
|
import { deepStrictEqual, notDeepStrictEqual, ok, rejects, throws } from "node:assert/strict";
|
|
8
|
+
import { LanguageString, decodeMultibase } from "@fedify/vocab-runtime";
|
|
8
9
|
import { pascalCase } from "es-toolkit";
|
|
9
10
|
import { areAllScalarTypes, loadSchemaFiles } from "@fedify/vocab-tools";
|
|
10
11
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fedify/vocab",
|
|
3
|
-
"version": "2.0.0-dev.
|
|
3
|
+
"version": "2.0.0-dev.323+1d796545",
|
|
4
4
|
"homepage": "https://fedify.dev/",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -47,8 +47,9 @@
|
|
|
47
47
|
"jsonld": "^9.0.0",
|
|
48
48
|
"multicodec": "^3.2.1",
|
|
49
49
|
"pkijs": "^3.3.3",
|
|
50
|
-
"@fedify/
|
|
51
|
-
"@fedify/
|
|
50
|
+
"@fedify/webfinger": "2.0.0-dev.323+1d796545",
|
|
51
|
+
"@fedify/vocab-runtime": "2.0.0-dev.323+1d796545",
|
|
52
|
+
"@fedify/vocab-tools": "2.0.0-dev.323+1d796545"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|
|
54
55
|
"@types/node": "^22.17.0",
|
|
@@ -56,8 +57,7 @@
|
|
|
56
57
|
"fetch-mock": "^12.5.4",
|
|
57
58
|
"tsdown": "^0.12.9",
|
|
58
59
|
"typescript": "^5.9.3",
|
|
59
|
-
"@fedify/fixture": "2.0.0"
|
|
60
|
-
"@fedify/vocab-runtime": "2.0.0-dev.241+58c8126c"
|
|
60
|
+
"@fedify/fixture": "2.0.0"
|
|
61
61
|
},
|
|
62
62
|
"keywords": [
|
|
63
63
|
"Fedify",
|
|
@@ -74,7 +74,9 @@
|
|
|
74
74
|
"build:self": "deno task compile && tsdown",
|
|
75
75
|
"build": "pnpm --filter @fedify/vocab... run build:self",
|
|
76
76
|
"prepublish": "pnpm build",
|
|
77
|
-
"
|
|
78
|
-
"test
|
|
77
|
+
"pretest": "pnpm build",
|
|
78
|
+
"test": "cd dist/ && node --test",
|
|
79
|
+
"pretest:bun": "pnpm build",
|
|
80
|
+
"test:bun": "cd dist/ && bun test --timeout 60000"
|
|
79
81
|
}
|
|
80
82
|
}
|
package/scripts/codegen.ts
CHANGED
|
@@ -1,18 +1,154 @@
|
|
|
1
|
+
import $ from "@david/dax";
|
|
2
|
+
import type { Path } from "@david/dax";
|
|
1
3
|
import { generateVocab } from "@fedify/vocab-tools";
|
|
2
|
-
import { rename } from "node:fs/promises";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
const LOCK_STALE_MS = 5 * 60 * 1000; // 5 minutes
|
|
6
|
+
const LOCK_RETRY_MS = 100;
|
|
7
|
+
const LOCK_TIMEOUT_MS = 60 * 1000; // 1 minute
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get the latest mtime from all YAML files in the schema directory.
|
|
11
|
+
*/
|
|
12
|
+
async function getLatestSourceMtime(schemaDir: Path): Promise<number> {
|
|
13
|
+
let latestMtime = 0;
|
|
14
|
+
for await (const entry of schemaDir.readDir()) {
|
|
15
|
+
if (!entry.isFile) continue;
|
|
16
|
+
if (!entry.name.match(/\.ya?ml$/i)) continue;
|
|
17
|
+
if (entry.name === "schema.yaml") continue;
|
|
18
|
+
const fileStat = await schemaDir.join(entry.name).stat();
|
|
19
|
+
if (fileStat?.mtime && fileStat.mtime.getTime() > latestMtime) {
|
|
20
|
+
latestMtime = fileStat.mtime.getTime();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return latestMtime;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if the generated file is up to date compared to source files.
|
|
28
|
+
*/
|
|
29
|
+
async function isUpToDate(
|
|
30
|
+
schemaDir: Path,
|
|
31
|
+
generatedPath: Path,
|
|
32
|
+
): Promise<boolean> {
|
|
33
|
+
try {
|
|
34
|
+
const [sourceMtime, generatedStat] = await Promise.all([
|
|
35
|
+
getLatestSourceMtime(schemaDir),
|
|
36
|
+
generatedPath.stat(),
|
|
37
|
+
]);
|
|
38
|
+
if (!generatedStat?.mtime) return false;
|
|
39
|
+
return generatedStat.mtime.getTime() >= sourceMtime;
|
|
40
|
+
} catch {
|
|
41
|
+
// If generated file doesn't exist, it's not up to date
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface Lock {
|
|
47
|
+
release(): Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Acquire a directory-based lock. mkdir is atomic on POSIX systems.
|
|
52
|
+
*/
|
|
53
|
+
async function acquireLock(lockPath: Path): Promise<Lock> {
|
|
54
|
+
const startTime = Date.now();
|
|
55
|
+
|
|
56
|
+
while (true) {
|
|
57
|
+
try {
|
|
58
|
+
// Use Deno.mkdir directly because dax's mkdir() is recursive by default
|
|
59
|
+
await Deno.mkdir(lockPath.toString());
|
|
60
|
+
// Write PID and timestamp for stale lock detection
|
|
61
|
+
const infoPath = lockPath.join("info");
|
|
62
|
+
await infoPath.writeJsonPretty({ pid: Deno.pid, timestamp: Date.now() });
|
|
63
|
+
return {
|
|
64
|
+
async release() {
|
|
65
|
+
try {
|
|
66
|
+
await lockPath.remove({ recursive: true });
|
|
67
|
+
} catch {
|
|
68
|
+
// Ignore errors during cleanup
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
} catch (e) {
|
|
73
|
+
if (!(e instanceof Deno.errors.AlreadyExists)) {
|
|
74
|
+
throw e;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check if lock is stale
|
|
78
|
+
try {
|
|
79
|
+
const infoPath = lockPath.join("info");
|
|
80
|
+
const infoStat = await infoPath.stat();
|
|
81
|
+
if (
|
|
82
|
+
infoStat?.mtime &&
|
|
83
|
+
Date.now() - infoStat.mtime.getTime() > LOCK_STALE_MS
|
|
84
|
+
) {
|
|
85
|
+
console.warn("Removing stale lock:", lockPath.toString());
|
|
86
|
+
await lockPath.remove({ recursive: true });
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
// If we can't read the info file, try to remove the lock
|
|
91
|
+
try {
|
|
92
|
+
await lockPath.remove({ recursive: true });
|
|
93
|
+
continue;
|
|
94
|
+
} catch {
|
|
95
|
+
// Ignore
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check timeout
|
|
100
|
+
if (Date.now() - startTime > LOCK_TIMEOUT_MS) {
|
|
101
|
+
throw new Error(`Timeout waiting for lock: ${lockPath}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Wait and retry
|
|
105
|
+
await $.sleep(LOCK_RETRY_MS);
|
|
106
|
+
}
|
|
9
107
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function codegen() {
|
|
111
|
+
const scriptsDir = $.path(import.meta.dirname!);
|
|
112
|
+
const packageDir = scriptsDir.parent()!;
|
|
113
|
+
const schemaDir = packageDir.join("src");
|
|
114
|
+
const realPath = schemaDir.join("vocab.ts");
|
|
115
|
+
const lockPath = packageDir.join(".vocab-codegen.lock");
|
|
116
|
+
|
|
117
|
+
// Acquire lock to prevent concurrent codegen
|
|
118
|
+
const lock = await acquireLock(lockPath);
|
|
119
|
+
try {
|
|
120
|
+
// Check if regeneration is needed (after acquiring lock)
|
|
121
|
+
if (await isUpToDate(schemaDir, realPath)) {
|
|
122
|
+
$.log("vocab.ts is up to date, skipping codegen");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
13
125
|
|
|
14
|
-
|
|
15
|
-
|
|
126
|
+
$.logStep("Generating", "vocab.ts...");
|
|
127
|
+
|
|
128
|
+
// Generate to a temporary file first
|
|
129
|
+
const generatedPath = schemaDir.join(`vocab-${crypto.randomUUID()}.ts`);
|
|
130
|
+
try {
|
|
131
|
+
await generateVocab(schemaDir.toString(), generatedPath.toString());
|
|
132
|
+
await generatedPath.rename(realPath);
|
|
133
|
+
} catch (e) {
|
|
134
|
+
// Clean up temp file on error
|
|
135
|
+
await generatedPath.remove().catch(() => {});
|
|
136
|
+
throw e;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
$.logStep("Formatting", "vocab.ts...");
|
|
140
|
+
await $`deno fmt ${realPath}`;
|
|
141
|
+
|
|
142
|
+
$.logStep("Caching", "vocab.ts...");
|
|
143
|
+
await $`deno cache ${realPath}`;
|
|
144
|
+
|
|
145
|
+
$.logStep("Type checking", "vocab.ts...");
|
|
146
|
+
await $`deno check ${realPath}`;
|
|
147
|
+
|
|
148
|
+
$.logStep("Codegen", "completed successfully");
|
|
149
|
+
} finally {
|
|
150
|
+
await lock.release();
|
|
151
|
+
}
|
|
16
152
|
}
|
|
17
153
|
|
|
18
154
|
if (import.meta.main) {
|
package/src/lookup.test.ts
CHANGED
|
@@ -267,6 +267,21 @@ test("traverseCollection()", {
|
|
|
267
267
|
new Note({ content: "This is a third simple note" }),
|
|
268
268
|
],
|
|
269
269
|
);
|
|
270
|
+
// Inline-paged collection (CollectionPage embedded without id, with next)
|
|
271
|
+
const inlinePagedCollection = await lookupObject(
|
|
272
|
+
"https://example.com/inline-paged-collection",
|
|
273
|
+
options,
|
|
274
|
+
);
|
|
275
|
+
assertInstanceOf(inlinePagedCollection, Collection);
|
|
276
|
+
deepStrictEqual(
|
|
277
|
+
await Array.fromAsync(
|
|
278
|
+
traverseCollection(inlinePagedCollection, options),
|
|
279
|
+
),
|
|
280
|
+
[
|
|
281
|
+
new Note({ content: "Inline first note" }),
|
|
282
|
+
new Note({ content: "Inline second note" }),
|
|
283
|
+
],
|
|
284
|
+
);
|
|
270
285
|
});
|
|
271
286
|
|
|
272
287
|
test("FEP-fe34: lookupObject() cross-origin security", {
|
package/src/lookup.ts
CHANGED
|
@@ -299,14 +299,14 @@ export async function* traverseCollection(
|
|
|
299
299
|
collection: Collection,
|
|
300
300
|
options: TraverseCollectionOptions = {},
|
|
301
301
|
): AsyncIterable<Object | Link> {
|
|
302
|
-
|
|
302
|
+
const interval = Temporal.Duration.from(options.interval ?? { seconds: 0 })
|
|
303
|
+
.total("millisecond");
|
|
304
|
+
let page = await collection.getFirst(options);
|
|
305
|
+
if (page == null) {
|
|
303
306
|
for await (const item of collection.getItems(options)) {
|
|
304
307
|
yield item;
|
|
305
308
|
}
|
|
306
309
|
} else {
|
|
307
|
-
const interval = Temporal.Duration.from(options.interval ?? { seconds: 0 })
|
|
308
|
-
.total("millisecond");
|
|
309
|
-
let page = await collection.getFirst(options);
|
|
310
310
|
while (page != null) {
|
|
311
311
|
for await (const item of page.getItems(options)) {
|
|
312
312
|
yield item;
|
package/src/mod.ts
CHANGED
|
@@ -55,3 +55,9 @@ export * from "./handle.ts";
|
|
|
55
55
|
export * from "./lookup.ts";
|
|
56
56
|
export * from "./type.ts";
|
|
57
57
|
export * from "./vocab.ts";
|
|
58
|
+
export { LanguageString } from "@fedify/vocab-runtime";
|
|
59
|
+
export type {
|
|
60
|
+
DocumentLoader,
|
|
61
|
+
GetUserAgentOptions,
|
|
62
|
+
RemoteDocument,
|
|
63
|
+
} from "@fedify/vocab-runtime";
|