@jsenv/core 37.0.5 → 37.1.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.
@@ -6,6 +6,7 @@ import { RUNTIME_COMPAT } from "@jsenv/runtime-compat";
6
6
 
7
7
  import { WEB_URL_CONVERTER } from "../helpers/web_url_converter.js";
8
8
  import { watchSourceFiles } from "../helpers/watch_source_files.js";
9
+ import { createEventEmitter } from "../helpers/event_emitter.js";
9
10
  import { createKitchen } from "../kitchen/kitchen.js";
10
11
  import { getCorePlugins } from "../plugins/plugins.js";
11
12
  import { jsenvPluginServerEventsClientInjection } from "../plugins/server_events/jsenv_plugin_server_events_client_injection.js";
@@ -17,6 +18,7 @@ export const createFileService = ({
17
18
  serverStopCallbacks,
18
19
  serverEventsDispatcher,
19
20
  kitchenCache,
21
+ onKitchenCreated = () => {},
20
22
 
21
23
  sourceDirectoryUrl,
22
24
  sourceMainFilePath,
@@ -32,8 +34,6 @@ export const createFileService = ({
32
34
  supervisor,
33
35
  transpilation,
34
36
  clientAutoreload,
35
- cooldownBetweenFileEvents,
36
- clientServerEventsConfig,
37
37
  cacheControl,
38
38
  ribbon,
39
39
  sourcemaps,
@@ -41,19 +41,32 @@ export const createFileService = ({
41
41
  sourcemapsSourcesContent,
42
42
  outDirectoryUrl,
43
43
  }) => {
44
- const clientFileChangeCallbackList = [];
45
- const clientFilesPruneCallbackList = [];
44
+ if (clientAutoreload === true) {
45
+ clientAutoreload = {};
46
+ }
47
+ if (clientAutoreload === false) {
48
+ clientAutoreload = { enabled: false };
49
+ }
50
+ const clientFileChangeEventEmitter = createEventEmitter();
51
+ const clientFileDereferencedEventEmitter = createEventEmitter();
52
+
53
+ clientAutoreload = {
54
+ enabled: true,
55
+ clientServerEventsConfig: {},
56
+ clientFileChangeEventEmitter,
57
+ clientFileDereferencedEventEmitter,
58
+ ...clientAutoreload,
59
+ };
60
+
46
61
  const stopWatchingSourceFiles = watchSourceFiles(
47
62
  sourceDirectoryUrl,
48
63
  (fileInfo) => {
49
- clientFileChangeCallbackList.forEach((callback) => {
50
- callback(fileInfo);
51
- });
64
+ clientFileChangeEventEmitter.emit(fileInfo);
52
65
  },
53
66
  {
54
67
  sourceFilesConfig,
55
68
  keepProcessAlive: false,
56
- cooldownBetweenFileEvents,
69
+ cooldownBetweenFileEvents: clientAutoreload.cooldownBetweenFileEvents,
57
70
  },
58
71
  );
59
72
  serverStopCallbacks.push(stopWatchingSourceFiles);
@@ -72,10 +85,10 @@ export const createFileService = ({
72
85
  sourceDirectoryUrl,
73
86
  );
74
87
  let kitchen;
75
- clientFileChangeCallbackList.push(({ url }) => {
88
+ clientFileChangeEventEmitter.on(({ url }) => {
76
89
  const urlInfo = kitchen.graph.getUrlInfo(url);
77
90
  if (urlInfo) {
78
- urlInfo.considerModified();
91
+ urlInfo.onModified();
79
92
  }
80
93
  });
81
94
  const clientRuntimeCompat = { [runtimeName]: runtimeVersion };
@@ -111,8 +124,6 @@ export const createFileService = ({
111
124
  transpilation,
112
125
 
113
126
  clientAutoreload,
114
- clientFileChangeCallbackList,
115
- clientFilesPruneCallbackList,
116
127
  cacheControl,
117
128
  ribbon,
118
129
  }),
@@ -126,18 +137,18 @@ export const createFileService = ({
126
137
  ? new URL(`${runtimeName}@${runtimeVersion}/`, outDirectoryUrl)
127
138
  : undefined,
128
139
  });
129
- kitchen.graph.createUrlInfoCallbackRef.current = (urlInfo) => {
140
+ kitchen.graph.urlInfoCreatedEventEmitter.on((urlInfoCreated) => {
130
141
  const { watch } = URL_META.applyAssociations({
131
- url: urlInfo.url,
142
+ url: urlInfoCreated.url,
132
143
  associations: watchAssociations,
133
144
  });
134
- urlInfo.isWatched = watch;
145
+ urlInfoCreated.isWatched = watch;
135
146
  // wehn an url depends on many others, we check all these (like package.json)
136
- urlInfo.isValid = () => {
137
- if (!urlInfo.url.startsWith("file:")) {
147
+ urlInfoCreated.isValid = () => {
148
+ if (!urlInfoCreated.url.startsWith("file:")) {
138
149
  return false;
139
150
  }
140
- if (urlInfo.content === undefined) {
151
+ if (urlInfoCreated.content === undefined) {
141
152
  // urlInfo content is undefined when:
142
153
  // - url info content never fetched
143
154
  // - it is considered as modified because undelying file is watched and got saved
@@ -149,21 +160,20 @@ export const createFileService = ({
149
160
  // file is not watched, check the filesystem
150
161
  let fileContentAsBuffer;
151
162
  try {
152
- fileContentAsBuffer = readFileSync(new URL(urlInfo.url));
163
+ fileContentAsBuffer = readFileSync(new URL(urlInfoCreated.url));
153
164
  } catch (e) {
154
165
  if (e.code === "ENOENT") {
155
- urlInfo.considerModified();
156
- urlInfo.deleteFromGraph();
166
+ urlInfoCreated.onModified();
157
167
  return false;
158
168
  }
159
169
  return false;
160
170
  }
161
171
  const fileContentEtag = bufferToEtag(fileContentAsBuffer);
162
- if (fileContentEtag !== urlInfo.originalContentEtag) {
163
- urlInfo.considerModified();
172
+ if (fileContentEtag !== urlInfoCreated.originalContentEtag) {
173
+ urlInfoCreated.onModified();
164
174
  // restore content to be able to compare it again later
165
- urlInfo.kitchen.urlInfoTransformer.setContent(
166
- urlInfo,
175
+ urlInfoCreated.kitchen.urlInfoTransformer.setContent(
176
+ urlInfoCreated,
167
177
  String(fileContentAsBuffer),
168
178
  {
169
179
  contentEtag: fileContentEtag,
@@ -172,23 +182,24 @@ export const createFileService = ({
172
182
  return false;
173
183
  }
174
184
  }
175
- for (const implicitUrl of urlInfo.implicitUrlSet) {
176
- const implicitUrlInfo = kitchen.graph.getUrlInfo(implicitUrl);
185
+ for (const implicitUrl of urlInfoCreated.implicitUrlSet) {
186
+ const implicitUrlInfo = urlInfoCreated.graph.getUrlInfo(implicitUrl);
177
187
  if (implicitUrlInfo && !implicitUrlInfo.isValid()) {
178
188
  return false;
179
189
  }
180
190
  }
181
191
  return true;
182
192
  };
183
- };
184
- kitchen.graph.pruneUrlInfoCallbackRef.current = (
185
- prunedUrlInfo,
186
- lastReferenceFromOther,
187
- ) => {
188
- clientFilesPruneCallbackList.forEach((callback) => {
189
- callback(prunedUrlInfo, lastReferenceFromOther);
190
- });
191
- };
193
+ });
194
+ kitchen.graph.urlInfoDereferencedEventEmitter.on(
195
+ (urlInfoDereferenced, lastReferenceFromOther) => {
196
+ clientFileDereferencedEventEmitter.emit(
197
+ urlInfoDereferenced,
198
+ lastReferenceFromOther,
199
+ );
200
+ },
201
+ );
202
+
192
203
  serverStopCallbacks.push(() => {
193
204
  kitchen.pluginController.callHooks("destroy", kitchen.context);
194
205
  });
@@ -221,12 +232,15 @@ export const createFileService = ({
221
232
  });
222
233
  // "pushPlugin" so that event source client connection can be put as early as possible in html
223
234
  kitchen.pluginController.pushPlugin(
224
- jsenvPluginServerEventsClientInjection(clientServerEventsConfig),
235
+ jsenvPluginServerEventsClientInjection(
236
+ clientAutoreload.clientServerEventsConfig,
237
+ ),
225
238
  );
226
239
  }
227
240
  }
228
241
 
229
242
  kitchenCache.set(runtimeId, kitchen);
243
+ onKitchenCreated(kitchen);
230
244
  return kitchen;
231
245
  };
232
246
 
@@ -244,31 +258,28 @@ export const createFileService = ({
244
258
  if (responseFromPlugin) {
245
259
  return responseFromPlugin;
246
260
  }
247
- let reference;
248
- const parentUrl = inferParentFromRequest(request, sourceDirectoryUrl);
249
- if (parentUrl) {
250
- reference = kitchen.graph.inferReference(request.resource, parentUrl);
251
- }
261
+ const { referer } = request.headers;
262
+ const parentUrl = referer
263
+ ? WEB_URL_CONVERTER.asFileUrl(referer, {
264
+ origin: request.origin,
265
+ rootDirectoryUrl: sourceDirectoryUrl,
266
+ })
267
+ : sourceDirectoryUrl;
268
+ let reference = kitchen.graph.inferReference(request.resource, parentUrl);
252
269
  if (!reference) {
253
- let parentUrlInfo;
254
- if (parentUrl) {
255
- parentUrlInfo = kitchen.graph.getUrlInfo(parentUrl);
256
- }
257
- if (!parentUrlInfo) {
258
- parentUrlInfo = kitchen.graph.rootUrlInfo;
259
- }
260
- reference = parentUrlInfo.dependencies.createResolveAndFinalize({
261
- trace: { message: parentUrl || sourceDirectoryUrl },
262
- type: "http_request",
263
- specifier: request.resource,
264
- });
270
+ reference =
271
+ kitchen.graph.rootUrlInfo.dependencies.createResolveAndFinalize({
272
+ trace: { message: parentUrl },
273
+ type: "http_request",
274
+ specifier: request.resource,
275
+ });
265
276
  }
266
277
  const urlInfo = reference.urlInfo;
267
278
  const ifNoneMatch = request.headers["if-none-match"];
268
279
  const urlInfoTargetedByCache = urlInfo.findParentIfInline() || urlInfo;
269
280
 
270
281
  try {
271
- if (ifNoneMatch) {
282
+ if (!urlInfo.error && ifNoneMatch) {
272
283
  const [clientOriginalContentEtag, clientContentEtag] =
273
284
  ifNoneMatch.split("_");
274
285
  if (
@@ -316,7 +327,7 @@ export const createFileService = ({
316
327
  }),
317
328
  ...urlInfo.headers,
318
329
  "content-type": urlInfo.contentType,
319
- "content-length": Buffer.byteLength(urlInfo.content),
330
+ "content-length": urlInfo.contentLength,
320
331
  },
321
332
  body: urlInfo.content,
322
333
  timing: urlInfo.timing,
@@ -355,7 +366,7 @@ export const createFileService = ({
355
366
  statusMessage: originalError.message,
356
367
  headers: {
357
368
  "content-type": urlInfo.contentType,
358
- "content-length": Buffer.byteLength(urlInfo.content),
369
+ "content-length": urlInfo.contentLength,
359
370
  "cache-control": "no-store",
360
371
  },
361
372
  body: urlInfo.content,
@@ -402,26 +413,6 @@ export const createFileService = ({
402
413
  statusText: e.reason,
403
414
  statusMessage: e.stack,
404
415
  };
405
- } finally {
406
- // remove http_request when there is other references keeping url info alive
407
- if (
408
- reference.type === "http_request" &&
409
- reference.urlInfo.referenceFromOthersSet.size > 1
410
- ) {
411
- reference.remove();
412
- }
413
416
  }
414
417
  };
415
418
  };
416
-
417
- const inferParentFromRequest = (request, sourceDirectoryUrl) => {
418
- const { referer } = request.headers;
419
- if (!referer) {
420
- return null;
421
- }
422
- const refererUrl = referer;
423
- return WEB_URL_CONVERTER.asFileUrl(refererUrl, {
424
- origin: request.origin,
425
- rootDirectoryUrl: sourceDirectoryUrl,
426
- });
427
- };
@@ -44,8 +44,6 @@ export const startDevServer = async ({
44
44
 
45
45
  sourceFilesConfig,
46
46
  clientAutoreload = true,
47
- cooldownBetweenFileEvents,
48
- clientServerEventsConfig = {},
49
47
 
50
48
  // runtimeCompat is the runtimeCompat for the build
51
49
  // when specified, dev server use it to warn in case
@@ -61,6 +59,7 @@ export const startDevServer = async ({
61
59
  cacheControl = true,
62
60
  ribbon = true,
63
61
  // toolbar = false,
62
+ onKitchenCreated = () => {},
64
63
 
65
64
  sourcemaps = "inline",
66
65
  sourcemapsSourcesProtocol,
@@ -185,6 +184,7 @@ export const startDevServer = async ({
185
184
  serverStopCallbacks,
186
185
  serverEventsDispatcher,
187
186
  kitchenCache,
187
+ onKitchenCreated,
188
188
 
189
189
  sourceDirectoryUrl,
190
190
  sourceMainFilePath,
@@ -200,8 +200,6 @@ export const startDevServer = async ({
200
200
  supervisor,
201
201
  transpilation,
202
202
  clientAutoreload,
203
- cooldownBetweenFileEvents,
204
- clientServerEventsConfig,
205
203
  cacheControl,
206
204
  ribbon,
207
205
  sourcemaps,
@@ -0,0 +1,18 @@
1
+ export const createEventEmitter = () => {
2
+ const callbackSet = new Set();
3
+ const on = (callback) => {
4
+ callbackSet.add(callback);
5
+ return () => {
6
+ callbackSet.delete(callback);
7
+ };
8
+ };
9
+ const off = (callback) => {
10
+ callbackSet.delete(callback);
11
+ };
12
+ const emit = (...args) => {
13
+ callbackSet.forEach((callback) => {
14
+ callback(...args);
15
+ });
16
+ };
17
+ return { on, off, emit };
18
+ };
@@ -484,12 +484,10 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
484
484
  // "cooked" hook
485
485
  pluginController.callHooks("cooked", urlInfo, (cookedReturnValue) => {
486
486
  if (typeof cookedReturnValue === "function") {
487
- const prevCallback = graph.pruneUrlInfoCallbackRef.current;
488
- graph.pruneUrlInfoCallbackRef.current(
489
- (prunedUrlInfo, lastReferenceFromOther) => {
490
- prevCallback();
491
- if (prunedUrlInfo === urlInfo.url) {
492
- graph.pruneUrlInfoCallbackRef.current = prevCallback;
487
+ const removeCallback = urlInfo.graph.urlInfoDereferencedEventEmitter.on(
488
+ (urlInfoDereferenced, lastReferenceFromOther) => {
489
+ if (urlInfoDereferenced === urlInfo) {
490
+ removeCallback();
493
491
  cookedReturnValue(lastReferenceFromOther.urlInfo);
494
492
  }
495
493
  },
@@ -463,6 +463,10 @@ const createReference = ({
463
463
  return implicitReference;
464
464
  };
465
465
 
466
+ reference.gotInlined = () => {
467
+ return !reference.isInline && reference.next && reference.next.isInline;
468
+ };
469
+
466
470
  reference.remove = () => removeDependency(reference);
467
471
 
468
472
  // Object.preventExtensions(reference) // useful to ensure all properties are declared here
@@ -557,7 +561,6 @@ const canAddOrRemoveReference = (reference) => {
557
561
  const applyDependencyRemovalEffects = (reference) => {
558
562
  const { ownerUrlInfo } = reference;
559
563
  const { referenceToOthersSet } = ownerUrlInfo;
560
-
561
564
  if (reference.isImplicit && !reference.isInline) {
562
565
  let hasAnOtherImplicitRef = false;
563
566
  for (const referenceToOther of referenceToOthersSet) {
@@ -589,8 +592,29 @@ const applyDependencyRemovalEffects = (reference) => {
589
592
  const referencedUrlInfo = reference.urlInfo;
590
593
  referencedUrlInfo.referenceFromOthersSet.delete(reference);
591
594
 
592
- const firstReferenceFromOther =
593
- referencedUrlInfo.getFirstReferenceFromOther();
595
+ let firstReferenceFromOther;
596
+ for (const referenceFromOther of referencedUrlInfo.referenceFromOthersSet) {
597
+ if (referenceFromOther.urlInfo !== referencedUrlInfo) {
598
+ continue;
599
+ }
600
+ // Here we want to know if the file is referenced by an other file.
601
+ // So we want to ignore reference that are created by other means:
602
+ // - "http_request"
603
+ // This type of reference is created when client request a file
604
+ // that we don't know yet
605
+ // 1. reference(s) to this file are not yet discovered
606
+ // 2. there is no reference to this file
607
+ if (referenceFromOther.type === "http_request") {
608
+ continue;
609
+ }
610
+ if (referenceFromOther.gotInlined()) {
611
+ // the url info was inlined, an other reference is required
612
+ // to consider the non-inlined urlInfo as used
613
+ continue;
614
+ }
615
+ firstReferenceFromOther = referenceFromOther;
616
+ break;
617
+ }
594
618
  if (firstReferenceFromOther) {
595
619
  // either applying new ref should override old ref
596
620
  // or we should first remove effects before adding new ones
@@ -601,11 +625,8 @@ const applyDependencyRemovalEffects = (reference) => {
601
625
  }
602
626
  return false;
603
627
  }
604
- if (reference.type !== "http_request") {
605
- referencedUrlInfo.deleteFromGraph(reference);
606
- return true;
607
- }
608
- return false;
628
+ referencedUrlInfo.onDereferenced(reference);
629
+ return true;
609
630
  };
610
631
 
611
632
  const traceFromUrlSite = (urlSite) => {
@@ -25,6 +25,15 @@ export const defineGettersOnPropertiesDerivedFromOriginalContent = (
25
25
  };
26
26
 
27
27
  export const defineGettersOnPropertiesDerivedFromContent = (urlInfo) => {
28
+ const contentLengthDescriptor = Object.getOwnPropertyDescriptor(
29
+ urlInfo,
30
+ "contentLength",
31
+ );
32
+ if (contentLengthDescriptor.value === undefined) {
33
+ defineVolatileGetter(urlInfo, "contentLength", () => {
34
+ return Buffer.byteLength(urlInfo.content);
35
+ });
36
+ }
28
37
  const contentAstDescriptor = Object.getOwnPropertyDescriptor(
29
38
  urlInfo,
30
39
  "contentAst",
@@ -4,8 +4,10 @@ import {
4
4
  injectQueryParamsIntoSpecifier,
5
5
  } from "@jsenv/urls";
6
6
 
7
+ import { createEventEmitter } from "../../helpers/event_emitter.js";
7
8
  import { urlSpecifierEncoding } from "./url_specifier_encoding.js";
8
9
  import { createDependencies } from "./references.js";
10
+ import { GRAPH_VISITOR } from "./url_graph_visitor.js";
9
11
 
10
12
  export const createUrlGraph = ({
11
13
  rootDirectoryUrl,
@@ -13,8 +15,8 @@ export const createUrlGraph = ({
13
15
  name = "anonymous",
14
16
  }) => {
15
17
  const urlGraph = {};
16
- const createUrlInfoCallbackRef = { current: () => {} };
17
- const pruneUrlInfoCallbackRef = { current: () => {} };
18
+ const urlInfoCreatedEventEmitter = createEventEmitter();
19
+ const urlInfoDereferencedEventEmitter = createEventEmitter();
18
20
 
19
21
  const urlInfoMap = new Map();
20
22
  const hasUrlInfo = (key) => {
@@ -35,27 +37,7 @@ export const createUrlGraph = ({
35
37
  }
36
38
  return null;
37
39
  };
38
- const deleteUrlInfo = (url, lastReferenceFromOther) => {
39
- const urlInfo = urlInfoMap.get(url);
40
- if (urlInfo) {
41
- urlInfo.kitchen.urlInfoTransformer.resetContent(urlInfo);
42
- urlInfoMap.delete(url);
43
- urlInfo.modifiedTimestamp = Date.now();
44
- if (lastReferenceFromOther && !urlInfo.isInline) {
45
- pruneUrlInfoCallbackRef.current(urlInfo, lastReferenceFromOther);
46
- }
47
- urlInfo.referenceToOthersSet.forEach((referenceToOther) => {
48
- referenceToOther.remove();
49
- });
50
- if (urlInfo.searchParams.size > 0) {
51
- const urlWithoutSearch = asUrlWithoutSearch(urlInfo.url);
52
- const urlInfoWithoutSearch = getUrlInfo(urlWithoutSearch);
53
- if (urlInfoWithoutSearch) {
54
- urlInfoWithoutSearch.searchParamVariantSet.delete(urlInfo);
55
- }
56
- }
57
- }
58
- };
40
+
59
41
  const addUrlInfo = (urlInfo) => {
60
42
  urlInfo.graph = urlGraph;
61
43
  urlInfo.kitchen = kitchen;
@@ -72,7 +54,7 @@ export const createUrlGraph = ({
72
54
  const context = Object.create(ownerContext);
73
55
  referencedUrlInfo = createUrlInfo(referencedUrl, context);
74
56
  addUrlInfo(referencedUrlInfo);
75
- createUrlInfoCallbackRef.current(referencedUrlInfo);
57
+ urlInfoCreatedEventEmitter.emit(referencedUrlInfo);
76
58
  }
77
59
  if (referencedUrlInfo.searchParams.size > 0 && !kitchen.context.shape) {
78
60
  // A resource is represented by a url.
@@ -149,17 +131,16 @@ export const createUrlGraph = ({
149
131
  Object.assign(urlGraph, {
150
132
  name,
151
133
  rootUrlInfo,
152
- createUrlInfoCallbackRef,
153
- pruneUrlInfoCallbackRef,
154
134
 
155
135
  urlInfoMap,
156
136
  reuseOrCreateUrlInfo,
157
137
  hasUrlInfo,
158
138
  getUrlInfo,
159
- deleteUrlInfo,
160
139
  getEntryPoints,
161
140
 
162
141
  inferReference,
142
+ urlInfoCreatedEventEmitter,
143
+ urlInfoDereferencedEventEmitter,
163
144
 
164
145
  toObject: () => {
165
146
  const data = {};
@@ -197,6 +178,7 @@ const createUrlInfo = (url, context) => {
197
178
  context,
198
179
  error: null,
199
180
  modifiedTimestamp: 0,
181
+ dereferencedTimestamp: 0,
200
182
  originalContentEtag: null,
201
183
  contentEtag: null,
202
184
  isWatched: false,
@@ -221,6 +203,7 @@ const createUrlInfo = (url, context) => {
221
203
  originalContentAst: undefined,
222
204
  content: undefined,
223
205
  contentAst: undefined,
206
+ contentLength: undefined,
224
207
  contentFinalized: false,
225
208
 
226
209
  sourcemap: null,
@@ -247,37 +230,24 @@ const createUrlInfo = (url, context) => {
247
230
  urlInfo.searchParams = new URL(url).searchParams;
248
231
 
249
232
  urlInfo.dependencies = createDependencies(urlInfo);
250
- urlInfo.getFirstReferenceFromOther = ({ ignoreWeak } = {}) => {
251
- for (const referenceFromOther of urlInfo.referenceFromOthersSet) {
252
- if (referenceFromOther.url === urlInfo.url) {
253
- if (
254
- !referenceFromOther.isInline &&
255
- referenceFromOther.next &&
256
- referenceFromOther.next.isInline
257
- ) {
258
- // the url info was inlined, an other reference is required
259
- // to consider the non-inlined urlInfo as used
260
- continue;
261
- }
262
- if (ignoreWeak && referenceFromOther.isWeak) {
263
- // weak reference don't count as using the url
264
- continue;
265
- }
266
- return referenceFromOther;
267
- }
268
- }
269
- return null;
270
- };
271
233
  urlInfo.isUsed = () => {
272
234
  if (urlInfo.isRoot) {
273
235
  return true;
274
236
  }
275
- // if (urlInfo.type === "sourcemap") {
276
- // return true;
277
- // }
278
- // check if there is a strong reference to this urlInfo
279
- if (urlInfo.getFirstReferenceFromOther({ ignoreWeak: true })) {
280
- return true;
237
+ for (const referenceFromOther of urlInfo.referenceFromOthersSet) {
238
+ if (referenceFromOther.urlInfo !== urlInfo) {
239
+ continue;
240
+ }
241
+ if (referenceFromOther.isWeak) {
242
+ // weak reference don't count as using the url
243
+ continue;
244
+ }
245
+ if (referenceFromOther.gotInlined()) {
246
+ // the url info was inlined, an other reference is required
247
+ // to consider the non-inlined urlInfo as used
248
+ continue;
249
+ }
250
+ return referenceFromOther.ownerUrlInfo.isUsed();
281
251
  }
282
252
  // nothing uses this url anymore
283
253
  // - versioning update inline content
@@ -297,6 +267,9 @@ const createUrlInfo = (url, context) => {
297
267
  }
298
268
  return null;
299
269
  };
270
+ urlInfo.findDependent = (callback) => {
271
+ return GRAPH_VISITOR.findDependent(urlInfo, callback);
272
+ };
300
273
  urlInfo.isSearchParamVariantOf = (otherUrlInfo) => {
301
274
  if (urlInfo.searchParams.size === 0) {
302
275
  return false;
@@ -367,7 +340,7 @@ const createUrlInfo = (url, context) => {
367
340
  reference.next = referenceWithoutSearchParam;
368
341
  return referenceWithoutSearchParam.urlInfo;
369
342
  };
370
- urlInfo.considerModified = ({ modifiedTimestamp = Date.now() } = {}) => {
343
+ urlInfo.onModified = ({ modifiedTimestamp = Date.now() } = {}) => {
371
344
  const visitedSet = new Set();
372
345
  const iterate = (urlInfo) => {
373
346
  if (visitedSet.has(urlInfo)) {
@@ -388,9 +361,29 @@ const createUrlInfo = (url, context) => {
388
361
  };
389
362
  iterate(urlInfo);
390
363
  };
391
- urlInfo.deleteFromGraph = (reference) => {
392
- urlInfo.graph.deleteUrlInfo(urlInfo.url, reference);
364
+ urlInfo.onDereferenced = (lastReferenceFromOther) => {
365
+ urlInfo.dereferencedTimestamp = Date.now();
366
+ urlInfo.graph.urlInfoDereferencedEventEmitter.emit(
367
+ urlInfo,
368
+ lastReferenceFromOther,
369
+ );
393
370
  };
371
+
372
+ // not used for now
373
+ // urlInfo.deleteFromGraph = () => {
374
+ // urlInfo.kitchen.urlInfoTransformer.resetContent(urlInfo);
375
+ // urlInfo.graph.urlInfoMap.delete(url);
376
+ // urlInfo.referenceToOthersSet.forEach((referenceToOther) => {
377
+ // referenceToOther.remove();
378
+ // });
379
+ // if (urlInfo.searchParams.size > 0) {
380
+ // const urlWithoutSearch = asUrlWithoutSearch(urlInfo.url);
381
+ // const urlInfoWithoutSearch = urlInfo.graph.getUrlInfo(urlWithoutSearch);
382
+ // if (urlInfoWithoutSearch) {
383
+ // urlInfoWithoutSearch.searchParamVariantSet.delete(urlInfo);
384
+ // }
385
+ // }
386
+ // };
394
387
  urlInfo.cook = (customContext) => {
395
388
  return urlInfo.context.cook(urlInfo, customContext);
396
389
  };