@getstrata/core 0.5.20 → 0.5.24

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.
@@ -1,7 +1,6 @@
1
1
  // @bun
2
2
  // ../../src/core/queue/jobRegistry.ts
3
3
  class JobRegistry {
4
- constructor() {}
5
4
  factories = new Map;
6
5
  instances = new WeakMap;
7
6
  register(name, factory) {
@@ -25,7 +24,17 @@ class JobRegistry {
25
24
  return [...this.factories.keys()];
26
25
  }
27
26
  }
28
- var jobRegistry = new JobRegistry;
27
+ var JOB_REGISTRY_KEY = Symbol.for("@getstrata/jobRegistry");
28
+ function readSharedJobRegistry() {
29
+ const globalRegistry = globalThis[JOB_REGISTRY_KEY];
30
+ if (globalRegistry) {
31
+ return globalRegistry;
32
+ }
33
+ const registry = new JobRegistry;
34
+ globalThis[JOB_REGISTRY_KEY] = registry;
35
+ return registry;
36
+ }
37
+ var jobRegistry = readSharedJobRegistry();
29
38
  export {
30
39
  jobRegistry,
31
40
  JobRegistry
@@ -1,6 +1,12 @@
1
1
  // @bun
2
- // ../../src/bootstrap/config.ts
2
+ // ../../src/core/contracts/serviceTokens.ts
3
+ var CORE_CONFIG_TOKEN = "core.config";
4
+ var CORE_CACHE_TOKEN = "core.cache";
5
+ var CORE_QUEUE_TOKEN = "core.queue";
6
+ var CORE_POLICY_GATE_TOKEN = "core.policyGate";
7
+ var CORE_AUTH_TOKEN = "core.auth";
3
8
  var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
9
+ // ../../src/bootstrap/config.ts
4
10
  var DEFAULT_QUEUE_DRIVER = "sync";
5
11
 
6
12
  // ../../src/config/queue.ts
@@ -12,7 +18,6 @@ var queueConfig = {
12
18
 
13
19
  // ../../src/core/queue/jobRegistry.ts
14
20
  class JobRegistry {
15
- constructor() {}
16
21
  factories = new Map;
17
22
  instances = new WeakMap;
18
23
  register(name, factory) {
@@ -36,7 +41,17 @@ class JobRegistry {
36
41
  return [...this.factories.keys()];
37
42
  }
38
43
  }
39
- var jobRegistry = new JobRegistry;
44
+ var JOB_REGISTRY_KEY = Symbol.for("@getstrata/jobRegistry");
45
+ function readSharedJobRegistry() {
46
+ const globalRegistry = globalThis[JOB_REGISTRY_KEY];
47
+ if (globalRegistry) {
48
+ return globalRegistry;
49
+ }
50
+ const registry = new JobRegistry;
51
+ globalThis[JOB_REGISTRY_KEY] = registry;
52
+ return registry;
53
+ }
54
+ var jobRegistry = readSharedJobRegistry();
40
55
 
41
56
  // ../../src/core/queue/jobRunner.ts
42
57
  async function runQueueJob(envelope, failedJobs) {
@@ -1911,7 +1911,6 @@ var failedJobService_default = FailedJobService;
1911
1911
 
1912
1912
  // ../../src/core/queue/jobRegistry.ts
1913
1913
  class JobRegistry {
1914
- constructor() {}
1915
1914
  factories = new Map;
1916
1915
  instances = new WeakMap;
1917
1916
  register(name, factory) {
@@ -1935,10 +1934,26 @@ class JobRegistry {
1935
1934
  return [...this.factories.keys()];
1936
1935
  }
1937
1936
  }
1938
- var jobRegistry = new JobRegistry;
1937
+ var JOB_REGISTRY_KEY = Symbol.for("@getstrata/jobRegistry");
1938
+ function readSharedJobRegistry() {
1939
+ const globalRegistry = globalThis[JOB_REGISTRY_KEY];
1940
+ if (globalRegistry) {
1941
+ return globalRegistry;
1942
+ }
1943
+ const registry = new JobRegistry;
1944
+ globalThis[JOB_REGISTRY_KEY] = registry;
1945
+ return registry;
1946
+ }
1947
+ var jobRegistry = readSharedJobRegistry();
1939
1948
 
1940
- // ../../src/bootstrap/config.ts
1949
+ // ../../src/core/contracts/serviceTokens.ts
1950
+ var CORE_CONFIG_TOKEN = "core.config";
1951
+ var CORE_CACHE_TOKEN = "core.cache";
1952
+ var CORE_QUEUE_TOKEN = "core.queue";
1953
+ var CORE_POLICY_GATE_TOKEN = "core.policyGate";
1954
+ var CORE_AUTH_TOKEN = "core.auth";
1941
1955
  var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
1956
+ // ../../src/bootstrap/config.ts
1942
1957
  var DEFAULT_QUEUE_DRIVER = "sync";
1943
1958
 
1944
1959
  // ../../src/config/queue.ts
@@ -2,8 +2,14 @@
2
2
  // ../../src/core/queue/queueMetrics.ts
3
3
  var {RedisClient: RedisClient2 } = globalThis.Bun;
4
4
 
5
- // ../../src/bootstrap/config.ts
5
+ // ../../src/core/contracts/serviceTokens.ts
6
+ var CORE_CONFIG_TOKEN = "core.config";
7
+ var CORE_CACHE_TOKEN = "core.cache";
8
+ var CORE_QUEUE_TOKEN = "core.queue";
9
+ var CORE_POLICY_GATE_TOKEN = "core.policyGate";
10
+ var CORE_AUTH_TOKEN = "core.auth";
6
11
  var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
12
+ // ../../src/bootstrap/config.ts
7
13
  var DEFAULT_QUEUE_DRIVER = "sync";
8
14
 
9
15
  // ../../src/config/queue.ts
@@ -1925,7 +1931,6 @@ var failedJobService_default = FailedJobService;
1925
1931
 
1926
1932
  // ../../src/core/queue/jobRegistry.ts
1927
1933
  class JobRegistry {
1928
- constructor() {}
1929
1934
  factories = new Map;
1930
1935
  instances = new WeakMap;
1931
1936
  register(name, factory) {
@@ -1949,7 +1954,17 @@ class JobRegistry {
1949
1954
  return [...this.factories.keys()];
1950
1955
  }
1951
1956
  }
1952
- var jobRegistry = new JobRegistry;
1957
+ var JOB_REGISTRY_KEY = Symbol.for("@getstrata/jobRegistry");
1958
+ function readSharedJobRegistry() {
1959
+ const globalRegistry = globalThis[JOB_REGISTRY_KEY];
1960
+ if (globalRegistry) {
1961
+ return globalRegistry;
1962
+ }
1963
+ const registry = new JobRegistry;
1964
+ globalThis[JOB_REGISTRY_KEY] = registry;
1965
+ return registry;
1966
+ }
1967
+ var jobRegistry = readSharedJobRegistry();
1953
1968
 
1954
1969
  // ../../src/core/queue/jobRunner.ts
1955
1970
  async function runQueueJob(envelope, failedJobs) {
@@ -2156,368 +2171,6 @@ function createQueueWorker(redisUrl, failedJobs = createFailedJobService()) {
2156
2171
  return new QueueWorker(redisUrl, failedJobs);
2157
2172
  }
2158
2173
 
2159
- // ../../src/core/jobs/dispatchWebhookJob.ts
2160
- import { createHmac } from "crypto";
2161
-
2162
- // ../../src/config/app.ts
2163
- var appConfig = {
2164
- name: "WorkHub",
2165
- env: process.env.APP_ENV ?? "local",
2166
- debug: (process.env.APP_DEBUG ?? "true") !== "false",
2167
- url: process.env.APP_URL ?? "http://localhost:3000",
2168
- apiPrefix: process.env.API_PREFIX ?? "/api/v1"
2169
- };
2170
-
2171
- // ../../src/core/queue/index.ts
2172
- class Job {
2173
- maxAttempts;
2174
- backoffMs;
2175
- priority;
2176
- }
2177
-
2178
- // ../../src/core/security/safeUrl.ts
2179
- import { lookup as dnsLookupImpl } from "dns/promises";
2180
- var dnsLookup = dnsLookupImpl;
2181
- var BLOCKED_HOSTNAMES = new Set([
2182
- "localhost",
2183
- "127.0.0.1",
2184
- "0.0.0.0",
2185
- "::1",
2186
- "metadata.google.internal"
2187
- ]);
2188
- function isPrivateIpv4(hostname) {
2189
- const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
2190
- if (!match) {
2191
- return false;
2192
- }
2193
- const octets = match.slice(1, 5).map((part) => Number.parseInt(part, 10));
2194
- if (octets.some((octet) => octet < 0 || octet > 255)) {
2195
- return true;
2196
- }
2197
- const [a = 0, b = 0] = octets;
2198
- if (a === 10) {
2199
- return true;
2200
- }
2201
- if (a === 127) {
2202
- return true;
2203
- }
2204
- if (a === 0) {
2205
- return true;
2206
- }
2207
- if (a === 169 && b === 254) {
2208
- return true;
2209
- }
2210
- if (a === 172 && b >= 16 && b <= 31) {
2211
- return true;
2212
- }
2213
- if (a === 192 && b === 168) {
2214
- return true;
2215
- }
2216
- return false;
2217
- }
2218
- function isBlockedHostname(hostname) {
2219
- const normalized = hostname.trim().toLowerCase();
2220
- if (normalized.length === 0) {
2221
- return true;
2222
- }
2223
- if (BLOCKED_HOSTNAMES.has(normalized)) {
2224
- return true;
2225
- }
2226
- if (normalized.endsWith(".local") || normalized.endsWith(".internal")) {
2227
- return true;
2228
- }
2229
- if (normalized.includes(":")) {
2230
- return true;
2231
- }
2232
- return isPrivateIpv4(normalized);
2233
- }
2234
- function assertSafeOutboundUrl(rawUrl, options = {}) {
2235
- let parsed;
2236
- try {
2237
- parsed = new URL(rawUrl);
2238
- } catch {
2239
- throw new BadRequestError("Webhook URL is invalid.");
2240
- }
2241
- if (parsed.protocol !== "https:" && !(options.allowHttp && parsed.protocol === "http:")) {
2242
- throw new BadRequestError("Webhook URL must use HTTPS.");
2243
- }
2244
- if (parsed.username || parsed.password) {
2245
- throw new BadRequestError("Webhook URL must not include credentials.");
2246
- }
2247
- if (isBlockedHostname(parsed.hostname)) {
2248
- throw new BadRequestError("Webhook URL targets a blocked host.");
2249
- }
2250
- return parsed;
2251
- }
2252
- function isBlockedIpAddress(address) {
2253
- return isBlockedHostname(address.trim().toLowerCase());
2254
- }
2255
- async function assertSafeOutboundUrlResolved(rawUrl, options = {}) {
2256
- const parsed = assertSafeOutboundUrl(rawUrl, options);
2257
- if (options.resolveDns === false) {
2258
- return parsed;
2259
- }
2260
- const hostname = parsed.hostname.trim().toLowerCase();
2261
- const results = await dnsLookup(hostname, { all: true, verbatim: true });
2262
- if (results.some((result) => isBlockedIpAddress(result.address))) {
2263
- throw new BadRequestError("Webhook URL targets a blocked host.");
2264
- }
2265
- return parsed;
2266
- }
2267
- function setDnsLookupForTests(lookupFn) {
2268
- dnsLookup = lookupFn;
2269
- }
2270
- function resetDnsLookupForTests() {
2271
- dnsLookup = dnsLookupImpl;
2272
- }
2273
-
2274
- // ../../src/core/security/safeFetch.ts
2275
- var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
2276
- async function safeFetch(input, init = {}, options = {}) {
2277
- const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
2278
- const maxRedirects = options.maxRedirects ?? 0;
2279
- const resolveDns = options.resolveDns ?? appConfig.env === "production";
2280
- const urlOptions = { allowHttp: options.allowHttp, resolveDns };
2281
- const controller = new AbortController;
2282
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
2283
- try {
2284
- let currentUrl = (await assertSafeOutboundUrlResolved(input, urlOptions)).toString();
2285
- let redirectCount = 0;
2286
- while (true) {
2287
- const response = await fetch(currentUrl, {
2288
- ...init,
2289
- signal: controller.signal,
2290
- redirect: "manual"
2291
- });
2292
- if (response.status >= 300 && response.status < 400) {
2293
- const location = response.headers.get("location");
2294
- if (!location || redirectCount >= maxRedirects) {
2295
- return response;
2296
- }
2297
- currentUrl = (await assertSafeOutboundUrlResolved(new URL(location, currentUrl).toString(), urlOptions)).toString();
2298
- redirectCount += 1;
2299
- continue;
2300
- }
2301
- return response;
2302
- }
2303
- } finally {
2304
- clearTimeout(timeout);
2305
- }
2306
- }
2307
-
2308
- // ../../src/core/jobs/dispatchWebhookJob.ts
2309
- class DispatchWebhookJob extends Job {
2310
- maxAttempts = 3;
2311
- backoffMs = 2000;
2312
- async handle(payload) {
2313
- const rows = await repositoryConnection`
2314
- SELECT id, url, secret
2315
- FROM webhook
2316
- WHERE id = ${payload.webhookId} AND active = TRUE
2317
- LIMIT 1
2318
- `;
2319
- const webhook = rows[0];
2320
- if (!webhook) {
2321
- return;
2322
- }
2323
- const body = JSON.stringify({ event: payload.event, payload: payload.payload });
2324
- const signature = createHmac("sha256", webhook.secret).update(body).digest("hex");
2325
- assertSafeOutboundUrl(webhook.url, { allowHttp: appConfig.env !== "production" });
2326
- let responseStatus = null;
2327
- let errorMessage = null;
2328
- try {
2329
- const response = await safeFetch(webhook.url, {
2330
- method: "POST",
2331
- headers: {
2332
- "content-type": "application/json",
2333
- "x-workhub-signature": signature
2334
- },
2335
- body
2336
- }, { allowHttp: appConfig.env !== "production" });
2337
- responseStatus = response.status;
2338
- if (!response.ok) {
2339
- throw new Error(`Webhook delivery failed with status ${response.status}.`);
2340
- }
2341
- } catch (error) {
2342
- errorMessage = error instanceof Error ? error.message : String(error);
2343
- await repositoryConnection`
2344
- INSERT INTO webhook_delivery (webhook_id, event, payload, response_status, error)
2345
- VALUES (
2346
- ${webhook.id},
2347
- ${payload.event},
2348
- ${JSON.stringify(payload.payload)}::jsonb,
2349
- ${responseStatus},
2350
- ${errorMessage}
2351
- )
2352
- `;
2353
- throw error instanceof Error ? error : new Error(errorMessage);
2354
- }
2355
- await repositoryConnection`
2356
- INSERT INTO webhook_delivery (webhook_id, event, payload, response_status)
2357
- VALUES (
2358
- ${webhook.id},
2359
- ${payload.event},
2360
- ${JSON.stringify(payload.payload)}::jsonb,
2361
- ${responseStatus}
2362
- )
2363
- `;
2364
- }
2365
- }
2366
- var dispatchWebhookJob_default = DispatchWebhookJob;
2367
-
2368
- // ../../src/core/jobs/invalidateCacheTagsJob.ts
2369
- class InvalidateCacheTagsJob extends Job {
2370
- cache;
2371
- constructor(cache) {
2372
- super();
2373
- this.cache = cache;
2374
- }
2375
- async handle(payload) {
2376
- await this.cache.tags(...payload.tags).flush();
2377
- }
2378
- }
2379
- var invalidateCacheTagsJob_default = InvalidateCacheTagsJob;
2380
-
2381
- // ../../src/core/logging/logger.ts
2382
- class Logger {
2383
- channel;
2384
- constructor(channel = "app") {
2385
- this.channel = channel;
2386
- }
2387
- write(level, message, context = {}) {
2388
- const entry = {
2389
- level,
2390
- channel: this.channel,
2391
- message,
2392
- timestamp: new Date().toISOString(),
2393
- ...context
2394
- };
2395
- const line = JSON.stringify(entry);
2396
- if (level === "error") {
2397
- console.error(line);
2398
- return;
2399
- }
2400
- console.log(line);
2401
- }
2402
- debug(message, context) {
2403
- this.write("debug", message, context);
2404
- }
2405
- info(message, context) {
2406
- this.write("info", message, context);
2407
- }
2408
- warn(message, context) {
2409
- this.write("warn", message, context);
2410
- }
2411
- error(message, context) {
2412
- this.write("error", message, context);
2413
- }
2414
- }
2415
- var appLogger = new Logger("app");
2416
-
2417
- // ../../src/bootstrap/contracts.ts
2418
- class ServiceContainer {
2419
- services = new Map;
2420
- singletonFactories = new Map;
2421
- bindings = new Map;
2422
- set(key, value) {
2423
- this.singletonFactories.delete(key);
2424
- this.bindings.delete(key);
2425
- this.services.set(key, value);
2426
- return value;
2427
- }
2428
- singleton(key, factory) {
2429
- this.bindings.delete(key);
2430
- this.services.delete(key);
2431
- this.singletonFactories.set(key, factory);
2432
- }
2433
- bind(key, factory) {
2434
- this.singletonFactories.delete(key);
2435
- this.services.delete(key);
2436
- this.bindings.set(key, factory);
2437
- }
2438
- get(key) {
2439
- if (this.services.has(key)) {
2440
- return this.services.get(key);
2441
- }
2442
- const singletonFactory = this.singletonFactories.get(key);
2443
- if (singletonFactory) {
2444
- const value = singletonFactory(this);
2445
- this.services.set(key, value);
2446
- return value;
2447
- }
2448
- const binding = this.bindings.get(key);
2449
- if (binding) {
2450
- return binding(this);
2451
- }
2452
- throw new Error(`Service "${key}" is not registered.`);
2453
- }
2454
- resolve(key) {
2455
- return this.get(key);
2456
- }
2457
- has(key) {
2458
- return this.services.has(key) || this.singletonFactories.has(key) || this.bindings.has(key);
2459
- }
2460
- }
2461
-
2462
- class ConfigStore {
2463
- values = new Map;
2464
- set(key, value) {
2465
- this.values.set(key, value);
2466
- return value;
2467
- }
2468
- get(key) {
2469
- return this.values.get(key);
2470
- }
2471
- require(key) {
2472
- if (!this.values.has(key)) {
2473
- throw new Error(`Config key "${key}" is not defined.`);
2474
- }
2475
- return this.values.get(key);
2476
- }
2477
- has(key) {
2478
- return this.values.has(key);
2479
- }
2480
- }
2481
- function getRequiredDependency(dependencies, key) {
2482
- const dependency = dependencies[key];
2483
- if (dependency === undefined) {
2484
- throw new Error(`Required dependency "${key}" is not registered.`);
2485
- }
2486
- return dependency;
2487
- }
2488
-
2489
- // ../../src/bootstrap/applicationRegistry.ts
2490
- var APPLICATION_CONTEXT_KEY = Symbol.for("@getstrata/applicationContext");
2491
- var activeContext;
2492
- function readStoredApplicationContext() {
2493
- if (activeContext) {
2494
- return activeContext;
2495
- }
2496
- const globalContext = globalThis[APPLICATION_CONTEXT_KEY];
2497
- if (globalContext) {
2498
- activeContext = globalContext;
2499
- }
2500
- return activeContext;
2501
- }
2502
- function requireActiveApplicationContext() {
2503
- const context = readStoredApplicationContext();
2504
- if (!context) {
2505
- throw new Error("The application context has not been bootstrapped.");
2506
- }
2507
- return context;
2508
- }
2509
- function resolveApplicationCache() {
2510
- return getRequiredDependency(requireActiveApplicationContext().dependencies, "cache");
2511
- }
2512
-
2513
- // ../../src/bootstrap/queue/defaultJobs.ts
2514
- function registerDefaultJobs() {
2515
- jobRegistry.register("cache.invalidate-tags", () => {
2516
- return new invalidateCacheTagsJob_default(resolveApplicationCache());
2517
- });
2518
- jobRegistry.register("webhook.dispatch", () => new dispatchWebhookJob_default);
2519
- }
2520
-
2521
2174
  // ../../src/core/queue/createAppQueue.ts
2522
2175
  var FAILED_JOB_SERVICE_TOKEN = "core.failedJobs";
2523
2176
  function createAppQueue(driver, redisUrl, failedJobs = createFailedJobService(), registerJobs) {
@@ -0,0 +1 @@
1
+ export * from "../../index.js";
@@ -44,8 +44,14 @@ function htmlResponse(html, init = {}) {
44
44
  function isHtmxRequest(request) {
45
45
  return request.headers.get("HX-Request") === "true";
46
46
  }
47
- // ../../src/bootstrap/config.ts
47
+ // ../../src/core/contracts/serviceTokens.ts
48
+ var CORE_CONFIG_TOKEN = "core.config";
49
+ var CORE_CACHE_TOKEN = "core.cache";
50
+ var CORE_QUEUE_TOKEN = "core.queue";
51
+ var CORE_POLICY_GATE_TOKEN = "core.policyGate";
52
+ var CORE_AUTH_TOKEN = "core.auth";
48
53
  var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
54
+ // ../../src/bootstrap/config.ts
49
55
  var DEFAULT_QUEUE_DRIVER = "sync";
50
56
 
51
57
  // ../../src/core/auth/oauth/oidcProvider.ts
@@ -2,7 +2,6 @@
2
2
  * Stable framework surface for application modules and future package extraction.
3
3
  * Import from `@getstrata/core` (workspace) or `src/framework/public-api`.
4
4
  */
5
- export { resolveApplicationAuth, resolveApplicationCache, resolveApplicationConfig, resolveApplicationDependencies, resolveApplicationLogger, resolveApplicationPolicyGate, resolveApplicationQueue, setActiveApplicationContext, } from "../bootstrap/applicationRegistry.ts";
6
5
  export type { ServiceProvider } from "../bootstrap/contracts.ts";
7
6
  export { ConfigStore, resolveService, ServiceContainer, } from "../bootstrap/contracts.ts";
8
7
  export type { AdminColumn, AdminColumnType, AdminResource, AdminResourceDefinition, AdminResourceHandlers, } from "../core/admin/index.ts";
@@ -20,7 +19,6 @@ export { default as MembershipService, resolveMembershipService, } from "../core
20
19
  export { Policy, PolicyGate } from "../core/auth/policy.ts";
21
20
  export { createScimAuthMiddleware } from "../core/auth/scimAuthMiddleware.ts";
22
21
  export { type CacheDriver, type CreateCacheStoreOptions, createCacheStore, } from "../core/cache/createCacheStore.ts";
23
- export { cacheTagsForModelWrite, discoverModelTableNames } from "../core/cache/modelCacheTags.ts";
24
22
  export { default as CacheRepository } from "../core/cache/repository.ts";
25
23
  export { CACHE_TAGS } from "../core/cache/tags.ts";
26
24
  export type { DatabaseConnection } from "../core/database/baseRepository.ts";
@@ -96,6 +94,7 @@ export { AsyncQueue, createQueue, Job, SyncQueue } from "../core/queue/index.ts"
96
94
  export { createFailedJobService, createProductionQueue, createQueueWorker, createTrackedJob, FailedJobRepository, FailedJobService, jobRegistry, QueueWorker, RedisQueue, ResilientQueue, runQueueJob, } from "../core/queue/publicQueue.ts";
97
95
  export type { QueueMetricsSnapshot } from "../core/queue/queueMetrics.ts";
98
96
  export { collectQueueMetrics } from "../core/queue/queueMetrics.ts";
97
+ export { resolveApplicationAuth, resolveApplicationCache, resolveApplicationConfig, resolveApplicationDependencies, resolveApplicationLogger, resolveApplicationPolicyGate, resolveApplicationQueue, setActiveApplicationContext, } from "../core/runtime/applicationRegistry.ts";
99
98
  export type { ScheduledTask } from "../core/scheduler/schedule.ts";
100
99
  export { appSchedule, runDueScheduledTasks, Schedule } from "../core/scheduler/schedule.ts";
101
100
  export { guestCanViewResource, isPublicReadsEnabled } from "../core/security/publicReads.ts";