@luckystack/core 0.9.2 → 0.10.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 +18 -1
- package/CLAUDE.md +6 -4
- package/dist/client.js.map +1 -1
- package/dist/eslint/index.js +63 -14
- package/dist/eslint/index.js.map +1 -1
- package/dist/index.d.ts +68 -6
- package/dist/index.js +156 -73
- package/dist/index.js.map +1 -1
- package/docs/redis-adapter.md +4 -1
- package/docs/socket-bootstrap.md +25 -2
- package/package.json +1 -1
package/dist/eslint/index.js
CHANGED
|
@@ -107,6 +107,52 @@ var rule2 = {
|
|
|
107
107
|
};
|
|
108
108
|
var no_direct_prisma_import_in_components_default = rule2;
|
|
109
109
|
|
|
110
|
+
// src/eslint/rules/no-local-socket-enumeration.ts
|
|
111
|
+
var ADAPTER_LOCAL_MAPS = /* @__PURE__ */ new Set(["rooms", "sids"]);
|
|
112
|
+
var ENUMERATING_MEMBERS = /* @__PURE__ */ new Set(["values", "keys", "entries", "forEach", "size"]);
|
|
113
|
+
var isNamedMember = (node, name) => node.type === "MemberExpression" && !node.computed && node.property.type === "Identifier" && node.property.name === name;
|
|
114
|
+
var isLocalSocketsMap = (node) => isNamedMember(node, "sockets") && isNamedMember(node.object, "sockets");
|
|
115
|
+
var rule3 = {
|
|
116
|
+
meta: {
|
|
117
|
+
type: "problem",
|
|
118
|
+
docs: {
|
|
119
|
+
description: "Disallow enumerating the Socket.io server's local socket maps (`.adapter.rooms`, `.adapter.sids`, iterating `io.sockets.sockets`). They only see THIS instance; use `getRoomSockets(...)` or `io.in(room).fetchSockets()`."
|
|
120
|
+
},
|
|
121
|
+
messages: {
|
|
122
|
+
localAdapterMap: "`.adapter.{{name}}` only sees sockets connected to THIS instance \u2014 on a multi-instance deployment every other instance is invisible here. Use `getRoomSockets(room, { userId })` from `@luckystack/core` or `await io.in(room).fetchSockets()`. For deliberate per-instance work use `getIoInstance({ raw: true })` and add `// eslint-disable-next-line luckystack/no-local-socket-enumeration -- <why>`.",
|
|
123
|
+
localSocketEnumeration: "Enumerating `io.sockets.sockets` (`.{{name}}`) only sees sockets connected to THIS instance \u2014 on a multi-instance deployment every other instance is invisible here. Use `getRoomSockets(room, { userId })` from `@luckystack/core` or `await io.in(room).fetchSockets()`. For deliberate per-instance work use `getIoInstance({ raw: true })` and add `// eslint-disable-next-line luckystack/no-local-socket-enumeration -- <why>`."
|
|
124
|
+
},
|
|
125
|
+
schema: []
|
|
126
|
+
},
|
|
127
|
+
create(context) {
|
|
128
|
+
const reportEnumeration = (node, name) => {
|
|
129
|
+
context.report({ node, messageId: "localSocketEnumeration", data: { name } });
|
|
130
|
+
};
|
|
131
|
+
return {
|
|
132
|
+
MemberExpression(node) {
|
|
133
|
+
if (node.computed || node.property.type !== "Identifier") return;
|
|
134
|
+
const name = node.property.name;
|
|
135
|
+
if (ADAPTER_LOCAL_MAPS.has(name) && isNamedMember(node.object, "adapter")) {
|
|
136
|
+
context.report({ node, messageId: "localAdapterMap", data: { name } });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (ENUMERATING_MEMBERS.has(name) && isLocalSocketsMap(node.object)) {
|
|
140
|
+
reportEnumeration(node, name);
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
//? (c) `for (const s of <Y>.sockets.sockets)`
|
|
144
|
+
ForOfStatement(node) {
|
|
145
|
+
if (isLocalSocketsMap(node.right)) reportEnumeration(node.right, "for...of");
|
|
146
|
+
},
|
|
147
|
+
//? (d) `[...<Y>.sockets.sockets]`
|
|
148
|
+
SpreadElement(node) {
|
|
149
|
+
if (isLocalSocketsMap(node.argument)) reportEnumeration(node.argument, "...spread");
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
var no_local_socket_enumeration_default = rule3;
|
|
155
|
+
|
|
110
156
|
// src/eslint/rules/no-raw-fetch-in-src.ts
|
|
111
157
|
var extractStaticUrlText = (urlNode) => {
|
|
112
158
|
if (!urlNode || typeof urlNode !== "object") return null;
|
|
@@ -124,7 +170,7 @@ var targetsFrameworkRoute = (urlNode) => {
|
|
|
124
170
|
if (/^(?:\|)?\/auth\//.test(text)) return false;
|
|
125
171
|
return /(?:^|[^A-Za-z])\/(?:api|sync)\//.test(text);
|
|
126
172
|
};
|
|
127
|
-
var
|
|
173
|
+
var rule4 = {
|
|
128
174
|
meta: {
|
|
129
175
|
type: "problem",
|
|
130
176
|
docs: {
|
|
@@ -151,10 +197,10 @@ var rule3 = {
|
|
|
151
197
|
};
|
|
152
198
|
}
|
|
153
199
|
};
|
|
154
|
-
var no_raw_fetch_in_src_default =
|
|
200
|
+
var no_raw_fetch_in_src_default = rule4;
|
|
155
201
|
|
|
156
202
|
// src/eslint/rules/no-raw-try-catch.ts
|
|
157
|
-
var
|
|
203
|
+
var rule5 = {
|
|
158
204
|
meta: {
|
|
159
205
|
type: "problem",
|
|
160
206
|
docs: {
|
|
@@ -175,7 +221,7 @@ var rule4 = {
|
|
|
175
221
|
};
|
|
176
222
|
}
|
|
177
223
|
};
|
|
178
|
-
var no_raw_try_catch_default =
|
|
224
|
+
var no_raw_try_catch_default = rule5;
|
|
179
225
|
|
|
180
226
|
// src/eslint/rules/no-unsafe-api-wrappers.ts
|
|
181
227
|
var SUSPECT_PARAM_NAMES = /* @__PURE__ */ new Set(["name", "version", "route", "apiName"]);
|
|
@@ -212,7 +258,7 @@ var isSuspectFunction = (node) => {
|
|
|
212
258
|
if (!hasSuspectStringParam) return false;
|
|
213
259
|
return containsApiRequestCall(node.body);
|
|
214
260
|
};
|
|
215
|
-
var
|
|
261
|
+
var rule6 = {
|
|
216
262
|
meta: {
|
|
217
263
|
type: "problem",
|
|
218
264
|
docs: {
|
|
@@ -240,7 +286,7 @@ var rule5 = {
|
|
|
240
286
|
};
|
|
241
287
|
}
|
|
242
288
|
};
|
|
243
|
-
var no_unsafe_api_wrappers_default =
|
|
289
|
+
var no_unsafe_api_wrappers_default = rule6;
|
|
244
290
|
|
|
245
291
|
// src/eslint/rules/no-unsafe-sync-wrappers.ts
|
|
246
292
|
var SUSPECT_PARAM_NAMES2 = /* @__PURE__ */ new Set(["name", "version", "route", "syncName"]);
|
|
@@ -277,7 +323,7 @@ var isSuspect = (node) => {
|
|
|
277
323
|
if (!hasSuspectStringParam) return false;
|
|
278
324
|
return containsSuspectCall(node.body);
|
|
279
325
|
};
|
|
280
|
-
var
|
|
326
|
+
var rule7 = {
|
|
281
327
|
meta: {
|
|
282
328
|
type: "problem",
|
|
283
329
|
docs: {
|
|
@@ -303,10 +349,10 @@ var rule6 = {
|
|
|
303
349
|
};
|
|
304
350
|
}
|
|
305
351
|
};
|
|
306
|
-
var no_unsafe_sync_wrappers_default =
|
|
352
|
+
var no_unsafe_sync_wrappers_default = rule7;
|
|
307
353
|
|
|
308
354
|
// src/eslint/rules/prefer-luckystack-confirm.ts
|
|
309
|
-
var
|
|
355
|
+
var rule8 = {
|
|
310
356
|
meta: {
|
|
311
357
|
type: "suggestion",
|
|
312
358
|
docs: {
|
|
@@ -332,10 +378,10 @@ var rule7 = {
|
|
|
332
378
|
};
|
|
333
379
|
}
|
|
334
380
|
};
|
|
335
|
-
var prefer_luckystack_confirm_default =
|
|
381
|
+
var prefer_luckystack_confirm_default = rule8;
|
|
336
382
|
|
|
337
383
|
// src/eslint/rules/prefer-luckystack-dropdown.ts
|
|
338
|
-
var
|
|
384
|
+
var rule9 = {
|
|
339
385
|
meta: {
|
|
340
386
|
type: "suggestion",
|
|
341
387
|
docs: {
|
|
@@ -358,10 +404,10 @@ var rule8 = {
|
|
|
358
404
|
return listener;
|
|
359
405
|
}
|
|
360
406
|
};
|
|
361
|
-
var prefer_luckystack_dropdown_default =
|
|
407
|
+
var prefer_luckystack_dropdown_default = rule9;
|
|
362
408
|
|
|
363
409
|
// src/eslint/rules/prefer-luckystack-notify.ts
|
|
364
|
-
var
|
|
410
|
+
var rule10 = {
|
|
365
411
|
meta: {
|
|
366
412
|
type: "suggestion",
|
|
367
413
|
docs: {
|
|
@@ -387,7 +433,7 @@ var rule9 = {
|
|
|
387
433
|
};
|
|
388
434
|
}
|
|
389
435
|
};
|
|
390
|
-
var prefer_luckystack_notify_default =
|
|
436
|
+
var prefer_luckystack_notify_default = rule10;
|
|
391
437
|
|
|
392
438
|
// src/eslint/index.ts
|
|
393
439
|
var allRules = {
|
|
@@ -395,6 +441,7 @@ var allRules = {
|
|
|
395
441
|
"no-raw-fetch-in-src": no_raw_fetch_in_src_default,
|
|
396
442
|
"no-unsafe-api-wrappers": no_unsafe_api_wrappers_default,
|
|
397
443
|
"no-unsafe-sync-wrappers": no_unsafe_sync_wrappers_default,
|
|
444
|
+
"no-local-socket-enumeration": no_local_socket_enumeration_default,
|
|
398
445
|
"prefer-luckystack-dropdown": prefer_luckystack_dropdown_default,
|
|
399
446
|
"prefer-luckystack-confirm": prefer_luckystack_confirm_default,
|
|
400
447
|
"prefer-luckystack-notify": prefer_luckystack_notify_default,
|
|
@@ -412,6 +459,8 @@ var ruleSeverities = {
|
|
|
412
459
|
"luckystack/no-raw-fetch-in-src": hasApi || hasCore ? "error" : "off",
|
|
413
460
|
"luckystack/no-unsafe-api-wrappers": hasApi ? "error" : "off",
|
|
414
461
|
"luckystack/no-unsafe-sync-wrappers": hasSync ? "error" : "off",
|
|
462
|
+
// Local Socket.io maps (`.adapter.rooms`, iterating `io.sockets.sockets`) only see one instance.
|
|
463
|
+
"luckystack/no-local-socket-enumeration": hasCore || hasSync ? "error" : "off",
|
|
415
464
|
// Warnings — enabled by default per framework owner's preference.
|
|
416
465
|
"luckystack/prefer-luckystack-dropdown": "warn",
|
|
417
466
|
"luckystack/prefer-luckystack-confirm": "warn",
|
package/dist/eslint/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/eslint/internal/hasPackage.ts","../../src/eslint/rules/no-arbitrary-tailwind-color.ts","../../src/eslint/rules/no-direct-prisma-import-in-components.ts","../../src/eslint/rules/no-raw-fetch-in-src.ts","../../src/eslint/rules/no-raw-try-catch.ts","../../src/eslint/rules/no-unsafe-api-wrappers.ts","../../src/eslint/rules/no-unsafe-sync-wrappers.ts","../../src/eslint/rules/prefer-luckystack-confirm.ts","../../src/eslint/rules/prefer-luckystack-dropdown.ts","../../src/eslint/rules/prefer-luckystack-notify.ts","../../src/eslint/index.ts"],"sourcesContent":["//? Peer-dep presence probe used to gate package-dependent eslint rules.\n//? Resolves against `process.cwd()` so the CONSUMER's node_modules drives\n//? activation, not this package's own resolution graph.\n//?\n//? Implementation: tries `require.resolve` first (works for CJS-compatible\n//? packages), falls back to an fs-based `node_modules/<name>/package.json`\n//? check for pure-ESM packages whose `exports` map omits the `require`\n//? condition (which is the case for the @luckystack/* packages themselves).\n\nimport { existsSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\n\nconst localRequire = createRequire(import.meta.url);\n\nconst checkOnDisk = (name: string): boolean => {\n const segments = name.startsWith('@')\n ? name.split('/').slice(0, 2)\n : [name.split('/')[0] ?? name];\n return existsSync(path.join(process.cwd(), 'node_modules', ...segments, 'package.json'));\n};\n\nexport const hasPackage = (name: string): boolean => {\n try {\n localRequire.resolve(name, { paths: [process.cwd()] });\n return true;\n } catch {\n return checkOnDisk(name);\n }\n};\n","//? Rule: warn on arbitrary Tailwind color values (`text-[#fff]`,\n//? `bg-[#abc123]`, `border-[#000]`) inside className strings. The framework\n//? mandates colors from `src/index.css` `@theme` block (rule 14). Arbitrary\n//? colors break dark-mode auto-switching.\n\nimport type { Rule } from 'eslint';\n\nimport type { EslintRule } from '../internal/ruleTypes.js';\n\nconst ARBITRARY_COLOR_PATTERN = /\\b(?:text|bg|border|ring|fill|stroke|from|to|via|outline|decoration|accent|caret|placeholder|divide)-\\[#[0-9a-fA-F]{3,8}\\]/;\n\n//? This rule reads JSX AST nodes (`JSXAttribute`, `JSXExpressionContainer`,\n//? `TemplateLiteral` quasis) that eslint-plugin-react injects into the visitor\n//? map at runtime but that eslint's stock `Rule.Node` type doesn't model. The\n//? objects ARE real eslint nodes at runtime — `context.report({ node })` only\n//? needs `{ type, loc, range, ... }`, all present. This guard narrows an\n//? `unknown` to `Rule.Node` in one place so the call sites stay cast-free and\n//? the previous file-wide `as unknown as Rule.Node` double-casts are gone.\nconst asRuleNode = (node: object): Rule.Node => node as Rule.Node;\n\n//? JSX AST nodes (JSXAttribute, JSXExpressionContainer, …) are not part of\n//? eslint's stock estree AST. eslint-plugin-react extends the visitor map at\n//? runtime, but the stock `Rule.RuleListener` type does not know about them,\n//? so we describe the shapes we read inline. The actual eslint runtime gives\n//? us the right object; this is purely a TS-side annotation.\ntype ClassNameValueShape =\n | { type: 'Literal'; value: unknown }\n | { type: 'JSXExpressionContainer'; expression: ExpressionShape }\n | { type: string };\n\ntype ExpressionShape =\n | { type: 'Literal'; value: unknown }\n | { type: 'TemplateLiteral'; quasis: readonly { value: { cooked?: string; raw: string } }[] }\n | { type: string };\n\ninterface JsxAttributeShape {\n name: { type: string; name?: string };\n value?: ClassNameValueShape | null;\n}\n\nconst reportIfArbitrary = (\n context: Rule.RuleContext,\n node: Rule.Node,\n value: string,\n): void => {\n if (ARBITRARY_COLOR_PATTERN.test(value)) {\n context.report({ node, messageId: 'useThemeToken' });\n }\n};\n\nconst rule: EslintRule = {\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Disallow arbitrary Tailwind color values (`text-[#…]`, `bg-[#…]`, …). Use tokens from `src/index.css` `@theme` block.',\n },\n messages: {\n useThemeToken:\n 'Do not use arbitrary Tailwind color values like `text-[#…]`. Use a token from `src/index.css` `@theme` block (e.g. `bg-primary`, `text-title`, `border-divider`). Arbitrary colors break dark-mode auto-switching.',\n },\n schema: [],\n },\n create(context) {\n const listener: Rule.RuleListener = {\n JSXAttribute(rawNode: unknown): void {\n const node = rawNode as JsxAttributeShape & Rule.Node;\n if (node.name.type !== 'JSXIdentifier' || node.name.name !== 'className') return;\n const value = node.value;\n if (!value) return;\n if (value.type === 'Literal' && typeof (value as { value?: unknown }).value === 'string') {\n reportIfArbitrary(context, asRuleNode(value), (value as { value: string }).value);\n return;\n }\n if (value.type === 'JSXExpressionContainer') {\n const expression = (value as { expression: ExpressionShape }).expression;\n if (expression.type === 'TemplateLiteral') {\n const quasis = (expression as { quasis: readonly { value: { cooked?: string; raw: string } }[] }).quasis;\n for (const quasi of quasis) {\n reportIfArbitrary(\n context,\n asRuleNode(quasi),\n quasi.value.cooked ?? quasi.value.raw,\n );\n }\n } else if (expression.type === 'Literal' && typeof (expression as { value?: unknown }).value === 'string') {\n reportIfArbitrary(\n context,\n asRuleNode(expression),\n (expression as { value: string }).value,\n );\n }\n }\n },\n };\n return listener;\n },\n};\n\nexport default rule;\n","//? Rule: warn on `import … from '@prisma/client'` outside of allowed\n//? boundaries. Convention: app code re-exports Prisma model types through\n//? `src/_types/<ModelName>.ts` so swapping providers / extending types\n//? stays centralised. Direct imports leak Prisma details into UI code.\n//?\n//? Allowed paths: `src/_types/**`, `server/**`, and any file ending in\n//? `Adapter.ts` (legitimate adapter implementations).\n\nimport type { EslintRule } from '../internal/ruleTypes.js';\n\nconst isAllowedPath = (filename: string): boolean => {\n const normalised = filename.replaceAll('\\\\', '/');\n if (normalised.includes('/src/_types/')) return true;\n if (normalised.includes('/server/')) return true;\n if (normalised.endsWith('Adapter.ts') || normalised.endsWith('Adapter.tsx')) return true;\n if (normalised.includes('/packages/')) return true;\n //? Project-root config files (the LuckyStack `config.ts` convention) and\n //? any `*.config.ts` are framework boundaries — Prisma types there are\n //? expected and not a \"leaking into component code\" concern.\n if (normalised.endsWith('/config.ts') || normalised.endsWith('.config.ts')) return true;\n if (normalised.includes('/prisma/')) return true;\n return false;\n};\n\nconst rule: EslintRule = {\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Re-export Prisma model types through `src/_types/<Model>.ts` rather than importing `@prisma/client` directly in components.',\n },\n messages: {\n reExportThroughTypes:\n 'Do not import from `@prisma/client` here. Re-export the type through `src/_types/<ModelName>.ts` and import that instead — keeps Prisma details out of component code.',\n },\n schema: [],\n },\n create(context) {\n if (isAllowedPath(context.filename)) return {};\n return {\n ImportDeclaration(node) {\n if (node.source.value === '@prisma/client') {\n context.report({ node, messageId: 'reExportThroughTypes' });\n }\n },\n };\n },\n};\n\nexport default rule;\n","//? Rule: forbid direct `fetch(...)` calls to framework `/api/*` or\n//? `/sync/*` routes. The framework provides typed transport helpers\n//? `apiRequest` (for `_api/` routes) and `syncRequest` (for `_sync/`\n//? routes) which preserve route/version generics + auth/CSRF/validation.\n//?\n//? The rule is deliberately narrow:\n//? - Fires when the URL is a literal / template containing `/api/` or\n//? `/sync/` (framework route patterns).\n//? - Skips URLs whose PATH STARTS with `/auth/` — the framework's auth\n//? endpoints (`/auth/api/credentials`, OAuth callbacks) are HTTP-only\n//? and intentionally use raw fetch (cookies, headers, redirects). The\n//? start-of-path check (not `.includes`) means a consumer's typed\n//? route under an `auth/` page folder (URL `/api/auth/...`) still\n//? fires correctly.\n//? - Skips external URLs (starts with `http://`, `https://`, `//`) —\n//? consumers fetching third-party APIs are fine.\n//? - Skips dynamic URLs (variable, function call, etc.) — be permissive\n//? rather than flag-by-default; consumers can opt in via comments.\n//?\n//? Gated by hasPackage('@luckystack/api') OR hasPackage('@luckystack/core')\n//? at the config-composition layer.\n\nimport type { EslintRule } from '../internal/ruleTypes.js';\n\ninterface AstNodeShape {\n type?: string;\n value?: unknown;\n quasis?: { value?: { raw?: string; cooked?: string } }[];\n}\n\nconst extractStaticUrlText = (urlNode: unknown): string | null => {\n if (!urlNode || typeof urlNode !== 'object') return null;\n const n = urlNode as AstNodeShape;\n if (n.type === 'Literal' && typeof n.value === 'string') return n.value;\n if (n.type === 'TemplateLiteral' && Array.isArray(n.quasis)) {\n return n.quasis.map((q) => q.value?.cooked ?? q.value?.raw ?? '').join('|');\n }\n return null;\n};\n\nconst targetsFrameworkRoute = (urlNode: unknown): boolean => {\n const text = extractStaticUrlText(urlNode);\n if (text === null) return false;\n if (/^(https?:)?\\/\\//.test(text)) return false;\n //? Skip only when the path STARTS with `/auth/` (framework's special\n //? endpoints). The leading `|` accounts for `extractStaticUrlText`'s\n //? template-literal join when the first quasi is empty, e.g.\n //? `${backendUrl}/auth/api/credentials` joins to `|/auth/api/credentials`.\n if (/^(?:\\|)?\\/auth\\//.test(text)) return false;\n return /(?:^|[^A-Za-z])\\/(?:api|sync)\\//.test(text);\n};\n\nconst rule: EslintRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Disallow raw `fetch(...)` to framework `/api/*` and `/sync/*` routes. Use `apiRequest` / `syncRequest` instead.',\n },\n messages: {\n useApiRequest:\n 'Do not call `fetch` directly for framework `/api/*` routes. Use `apiRequest({ name, version, data })` for typed inference, or `httpFetch(...)` from `@luckystack/core` if you need a raw same-origin request.',\n useSyncRequest:\n 'Do not call `fetch` directly for framework `/sync/*` routes. Use `syncRequest({ name, version, data, receiver })` instead.',\n },\n schema: [],\n },\n create(context) {\n return {\n CallExpression(node) {\n const callee = node.callee;\n const isFetchCall =\n (callee.type === 'Identifier' && callee.name === 'fetch') ||\n (callee.type === 'MemberExpression' &&\n callee.object.type === 'Identifier' &&\n callee.object.name === 'window' &&\n callee.property.type === 'Identifier' &&\n callee.property.name === 'fetch');\n if (!isFetchCall) return;\n const url = node.arguments[0];\n if (!targetsFrameworkRoute(url)) return;\n const text = extractStaticUrlText(url) ?? '';\n const messageId = text.includes('/sync/') ? 'useSyncRequest' : 'useApiRequest';\n context.report({ node, messageId });\n },\n };\n },\n};\n\nexport default rule;\n","//? Rule: forbid raw `try { … } catch` inside src/server/shared code.\n//? The framework provides `tryCatch` from @luckystack/core (and the API/sync\n//? handler `functions.tryCatch` injection) that handles Sentry capture +\n//? normalised tuple returns. Raw try/catch bypasses that.\n\nimport type { EslintRule } from '../internal/ruleTypes.js';\n\nconst rule: EslintRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Disallow raw `try { … } catch` blocks — use the framework `tryCatch` helper instead.',\n },\n messages: {\n useTryCatch:\n 'Do not use raw try/catch. Import `tryCatch` from `@luckystack/core` (client) or `server/functions/tryCatch` (server), or use the injected `functions.tryCatch` in API / sync handlers.',\n },\n schema: [],\n },\n create(context) {\n return {\n TryStatement(node) {\n //? Only fire when an actual catch clause is present. `try { } finally { }`\n //? is a different construct (cleanup, not error handling) and the\n //? `tryCatch` helper is not a substitute.\n if (node.handler) {\n context.report({ node, messageId: 'useTryCatch' });\n }\n },\n };\n },\n};\n\nexport default rule;\n","//? Rule: forbid local wrappers around `apiRequest` whose parameter types\n//? erase the route/version generics. The framework's type system relies on\n//? literal `name`/`version` strings to infer `serverOutput` / `clientOutput`.\n//? A wrapper like:\n//?\n//? const unsafeApi = (name: string, version: string, data: unknown) =>\n//? apiRequest({ name: name as any, ... });\n//?\n//? destroys that inference. Always call `apiRequest` directly with route\n//? literals, or write a wrapper that preserves the generics.\n//?\n//? Gated by hasPackage('@luckystack/api') at the config-composition layer.\n\n//? Double-cast + any-spread disabled file-wide: bridges `Rule.Node` to specific\n//? AST shapes; the spread iterates over `Object.entries(node)` whose values are\n//? typed as `any` because eslint's AST shape varies by node kind.\n/* eslint-disable no-restricted-syntax, @typescript-eslint/no-unsafe-argument */\n\nimport type { EslintRule } from '../internal/ruleTypes.js';\n\nconst SUSPECT_PARAM_NAMES = new Set(['name', 'version', 'route', 'apiName']);\n\n//? AST keys that point to parents or non-AST metadata. Including them in\n//? a recursive descent causes infinite loops because eslint adds `parent`\n//? pointers during traversal.\nconst SKIP_KEYS = new Set(['parent', 'loc', 'range', 'tokens', 'comments']);\n\nconst containsApiRequestCall = (body: unknown): boolean => {\n if (!body || typeof body !== 'object') return false;\n const visited = new WeakSet<object>();\n const stack: unknown[] = [body];\n while (stack.length > 0) {\n const node = stack.pop() as Record<string, unknown> | null;\n if (!node || typeof node !== 'object') continue;\n if (visited.has(node)) continue;\n visited.add(node);\n const callee = (node as { type?: string; callee?: { type?: string; name?: string } }).callee;\n if (\n (node as { type?: string }).type === 'CallExpression' &&\n callee?.type === 'Identifier' &&\n (callee.name === 'apiRequest' || callee.name === 'syncRequest')\n ) {\n return true;\n }\n for (const [key, value] of Object.entries(node)) {\n if (SKIP_KEYS.has(key)) continue;\n if (Array.isArray(value)) stack.push(...value);\n else if (value && typeof value === 'object') stack.push(value);\n }\n }\n return false;\n};\n\nconst isSuspectFunction = (node: {\n params?: { type?: string; name?: string; typeAnnotation?: { typeAnnotation?: { type?: string } } }[];\n body?: unknown;\n}): boolean => {\n if (!node.params || node.params.length === 0) return false;\n const hasSuspectStringParam = node.params.some((p) => {\n if (p.type !== 'Identifier' || !p.name) return false;\n if (!SUSPECT_PARAM_NAMES.has(p.name)) return false;\n const annotation = p.typeAnnotation?.typeAnnotation?.type;\n return annotation === 'TSStringKeyword';\n });\n if (!hasSuspectStringParam) return false;\n return containsApiRequestCall(node.body);\n};\n\nconst rule: EslintRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Disallow local wrappers around `apiRequest` / `syncRequest` whose `name`/`version` params are typed as plain `string` — this erases the generic route/version inference.',\n },\n messages: {\n unsafeWrapper:\n 'This wrapper around `apiRequest` / `syncRequest` accepts `{{paramName}}: string` and erases the route/version generic inference. Call the framework helper directly with literal route names, or type the parameter as a literal union (e.g. `name: \\'examples/getUserData\\'`).',\n },\n schema: [],\n },\n create(context) {\n return {\n FunctionDeclaration(node) {\n if (isSuspectFunction(node)) {\n const suspect = (node.params as { name?: string }[]).find((p) => p.name && SUSPECT_PARAM_NAMES.has(p.name));\n context.report({ node, messageId: 'unsafeWrapper', data: { paramName: suspect?.name ?? 'name' } });\n }\n },\n ArrowFunctionExpression(node) {\n if (isSuspectFunction(node)) {\n const suspect = (node.params as { name?: string }[]).find((p) => p.name && SUSPECT_PARAM_NAMES.has(p.name));\n context.report({ node, messageId: 'unsafeWrapper', data: { paramName: suspect?.name ?? 'name' } });\n }\n },\n };\n },\n};\n\nexport default rule;\n","//? Rule: same shape as no-unsafe-api-wrappers, but targeting wrappers\n//? around `syncRequest` and `upsertSyncEventCallback`. Gated by\n//? hasPackage('@luckystack/sync') at the config-composition layer.\n//?\n//? Note: the api-wrappers rule already covers the `syncRequest` call site;\n//? this rule additionally catches `upsertSyncEventCallback` wrappers and\n//? exists as a distinct identity so the sync-only rule can be disabled\n//? independently when a consumer uses api but not sync.\n\n//? Double-cast + any-spread disabled file-wide: bridges `Rule.Node` to specific\n//? AST shapes; the spread iterates over `Object.entries(node)` whose values are\n//? typed as `any` because eslint's AST shape varies by node kind.\n/* eslint-disable no-restricted-syntax, @typescript-eslint/no-unsafe-argument */\n\nimport type { EslintRule } from '../internal/ruleTypes.js';\n\nconst SUSPECT_PARAM_NAMES = new Set(['name', 'version', 'route', 'syncName']);\nconst SUSPECT_CALLEES = new Set(['syncRequest', 'upsertSyncEventCallback']);\n\n//? AST keys that point to parents or non-AST metadata. Skipped to avoid\n//? infinite loops when eslint adds `parent` pointers during traversal.\nconst SKIP_KEYS = new Set(['parent', 'loc', 'range', 'tokens', 'comments']);\n\nconst containsSuspectCall = (body: unknown): boolean => {\n if (!body || typeof body !== 'object') return false;\n const visited = new WeakSet<object>();\n const stack: unknown[] = [body];\n while (stack.length > 0) {\n const node = stack.pop() as Record<string, unknown> | null;\n if (!node || typeof node !== 'object') continue;\n if (visited.has(node)) continue;\n visited.add(node);\n const callee = (node as { type?: string; callee?: { type?: string; name?: string } }).callee;\n if (\n (node as { type?: string }).type === 'CallExpression' &&\n callee?.type === 'Identifier' &&\n SUSPECT_CALLEES.has(callee.name ?? '')\n ) {\n return true;\n }\n for (const [key, value] of Object.entries(node)) {\n if (SKIP_KEYS.has(key)) continue;\n if (Array.isArray(value)) stack.push(...value);\n else if (value && typeof value === 'object') stack.push(value);\n }\n }\n return false;\n};\n\nconst isSuspect = (node: {\n params?: { type?: string; name?: string; typeAnnotation?: { typeAnnotation?: { type?: string } } }[];\n body?: unknown;\n}): boolean => {\n if (!node.params || node.params.length === 0) return false;\n const hasSuspectStringParam = node.params.some((p) => {\n if (p.type !== 'Identifier' || !p.name) return false;\n if (!SUSPECT_PARAM_NAMES.has(p.name)) return false;\n return p.typeAnnotation?.typeAnnotation?.type === 'TSStringKeyword';\n });\n if (!hasSuspectStringParam) return false;\n return containsSuspectCall(node.body);\n};\n\nconst rule: EslintRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Disallow local wrappers around `syncRequest` / `upsertSyncEventCallback` whose `name`/`version` params are typed as plain `string`.',\n },\n messages: {\n unsafeSyncWrapper:\n 'This wrapper around `syncRequest` / `upsertSyncEventCallback` accepts a `string` route param and erases the generic event-payload inference. Call the framework helper directly with literal route names.',\n },\n schema: [],\n },\n create(context) {\n return {\n FunctionDeclaration(node) {\n if (isSuspect(node)) {\n context.report({ node, messageId: 'unsafeSyncWrapper' });\n }\n },\n ArrowFunctionExpression(node) {\n if (isSuspect(node)) {\n context.report({ node, messageId: 'unsafeSyncWrapper' });\n }\n },\n };\n },\n};\n\nexport default rule;\n","//? Rule: warn on `confirm(...)` / `window.confirm(...)` calls. The framework\n//? provides `menuHandler.confirm({ title, content, input? })` from\n//? `src/_functions/menuHandler.ts` which renders a styled, theme-aware,\n//? Promise-returning confirm dialog and respects keyboard / accessibility.\n\nimport type { EslintRule } from '../internal/ruleTypes.js';\n\nconst rule: EslintRule = {\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Prefer `menuHandler.confirm({ title, content })` over the native `confirm()` dialog.',\n },\n messages: {\n useMenuHandlerConfirm:\n 'Use `menuHandler.confirm({ title, content })` from `src/_functions/menuHandler.ts` instead of native `confirm()`. Returns a `Promise<boolean>` with proper theming.',\n },\n schema: [],\n },\n create(context) {\n return {\n CallExpression(node) {\n const callee = node.callee;\n if (callee.type === 'Identifier' && callee.name === 'confirm') {\n context.report({ node, messageId: 'useMenuHandlerConfirm' });\n return;\n }\n if (\n callee.type === 'MemberExpression' &&\n callee.object.type === 'Identifier' &&\n callee.object.name === 'window' &&\n callee.property.type === 'Identifier' &&\n callee.property.name === 'confirm'\n ) {\n context.report({ node, messageId: 'useMenuHandlerConfirm' });\n }\n },\n };\n },\n};\n\nexport default rule;\n","//? Rule: warn when a JSX `<select>` element is used. The framework ships\n//? `Dropdown` (src/_components/Dropdown.tsx) with search, keyboard nav,\n//? sizing variants, and consistent dark/light styling. Native `<select>`\n//? skips all of that.\n\nimport type { Rule } from 'eslint';\n\nimport type { EslintRule } from '../internal/ruleTypes.js';\n\n//? JSX AST nodes are extended at runtime by eslint-plugin-react, but\n//? eslint's stock `Rule.RuleListener` type does not know about them. We\n//? describe the shapes we read inline.\ninterface JsxOpeningElementShape {\n name: { type: string; name?: string };\n}\n\nconst rule: EslintRule = {\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Prefer the framework `Dropdown` component over native `<select>` elements.',\n },\n messages: {\n useDropdown:\n 'Use `Dropdown` from `src/_components/Dropdown.tsx` (or `MultiSelectDropdown` for multi-select) instead of native `<select>`. The framework component handles keyboard nav, search, sizing, and dark/light theme.',\n },\n schema: [],\n },\n create(context) {\n const listener: Rule.RuleListener = {\n JSXOpeningElement(rawNode: unknown): void {\n const node = rawNode as JsxOpeningElementShape & Rule.Node;\n if (node.name.type === 'JSXIdentifier' && node.name.name === 'select') {\n context.report({ node, messageId: 'useDropdown' });\n }\n },\n };\n return listener;\n },\n};\n\nexport default rule;\n","//? Rule: warn on `alert(...)` / `window.alert(...)`. The framework's\n//? `notify` helper (from @luckystack/core) renders themed toast\n//? notifications via the registered notifier (default sonner).\n\nimport type { EslintRule } from '../internal/ruleTypes.js';\n\nconst rule: EslintRule = {\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Prefer `notify()` over the native `alert()` dialog.',\n },\n messages: {\n useNotify:\n 'Use `notify({ type, message })` from `@luckystack/core` instead of native `alert()`. Renders a themed toast via the registered notifier.',\n },\n schema: [],\n },\n create(context) {\n return {\n CallExpression(node) {\n const callee = node.callee;\n if (callee.type === 'Identifier' && callee.name === 'alert') {\n context.report({ node, messageId: 'useNotify' });\n return;\n }\n if (\n callee.type === 'MemberExpression' &&\n callee.object.type === 'Identifier' &&\n callee.object.name === 'window' &&\n callee.property.type === 'Identifier' &&\n callee.property.name === 'alert'\n ) {\n context.report({ node, messageId: 'useNotify' });\n }\n },\n };\n },\n};\n\nexport default rule;\n","//? Public entry for `@luckystack/core/eslint`. Composes the LuckyStack\n//? flat-config tier that the scaffolded `eslint.luckystack.config.js`\n//? consumes. Rules that target an optional `@luckystack/*` peer package\n//? are gated via `hasPackage(...)` so absent peers cause silent rule\n//? skips (NOT noisy \"rule not found\" errors).\n//?\n//? Override surface: consumers can spread this default export and append\n//? their own config blocks, or replace individual rule severities by\n//? re-stating them with `'off' | 'warn' | 'error'`.\n\nimport type { Linter } from 'eslint';\n\nimport { hasPackage } from './internal/hasPackage.js';\nimport type { EslintRule } from './internal/ruleTypes.js';\n\nimport noArbitraryTailwindColor from './rules/no-arbitrary-tailwind-color.js';\nimport noDirectPrismaImportInComponents from './rules/no-direct-prisma-import-in-components.js';\nimport noRawFetchInSrc from './rules/no-raw-fetch-in-src.js';\nimport noRawTryCatch from './rules/no-raw-try-catch.js';\nimport noUnsafeApiWrappers from './rules/no-unsafe-api-wrappers.js';\nimport noUnsafeSyncWrappers from './rules/no-unsafe-sync-wrappers.js';\nimport preferLuckystackConfirm from './rules/prefer-luckystack-confirm.js';\nimport preferLuckystackDropdown from './rules/prefer-luckystack-dropdown.js';\nimport preferLuckystackNotify from './rules/prefer-luckystack-notify.js';\n\nconst allRules: Record<string, EslintRule> = {\n 'no-raw-try-catch': noRawTryCatch,\n 'no-raw-fetch-in-src': noRawFetchInSrc,\n 'no-unsafe-api-wrappers': noUnsafeApiWrappers,\n 'no-unsafe-sync-wrappers': noUnsafeSyncWrappers,\n 'prefer-luckystack-dropdown': preferLuckystackDropdown,\n 'prefer-luckystack-confirm': preferLuckystackConfirm,\n 'prefer-luckystack-notify': preferLuckystackNotify,\n 'no-direct-prisma-import-in-components': noDirectPrismaImportInComponents,\n 'no-arbitrary-tailwind-color': noArbitraryTailwindColor,\n};\n\nconst hasApi = hasPackage('@luckystack/api');\nconst hasCore = hasPackage('@luckystack/core');\nconst hasSync = hasPackage('@luckystack/sync');\n\nconst ruleSeverities: Record<string, Linter.RuleSeverity> = {\n // Always-on errors.\n 'luckystack/no-raw-try-catch': 'error',\n\n // Package-gated errors. Disabled when the relevant peer is absent so\n // a consumer using only @luckystack/core sees no \"use apiRequest\" noise.\n 'luckystack/no-raw-fetch-in-src': hasApi || hasCore ? 'error' : 'off',\n 'luckystack/no-unsafe-api-wrappers': hasApi ? 'error' : 'off',\n 'luckystack/no-unsafe-sync-wrappers': hasSync ? 'error' : 'off',\n\n // Warnings — enabled by default per framework owner's preference.\n 'luckystack/prefer-luckystack-dropdown': 'warn',\n 'luckystack/prefer-luckystack-confirm': 'warn',\n 'luckystack/prefer-luckystack-notify': 'warn',\n 'luckystack/no-direct-prisma-import-in-components': 'warn',\n 'luckystack/no-arbitrary-tailwind-color': 'warn',\n};\n\nconst luckystackConfig: Linter.Config[] = [\n {\n files: ['**/*.{ts,tsx,js,jsx,mjs,cjs}'],\n plugins: {\n luckystack: { rules: allRules },\n },\n rules: ruleSeverities,\n },\n];\n\nexport default luckystackConfig;\n\n//? Named exports for advanced consumers who want to assemble their own\n//? config or selectively re-enable individual rules.\nexport { allRules as rules };\n\n\nexport {hasPackage} from './internal/hasPackage.js';"],"mappings":"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","names":["rule","rule","rule","rule","SUSPECT_PARAM_NAMES","SKIP_KEYS","rule","rule","rule","rule"]}
|
|
1
|
+
{"version":3,"sources":["../../src/eslint/internal/hasPackage.ts","../../src/eslint/rules/no-arbitrary-tailwind-color.ts","../../src/eslint/rules/no-direct-prisma-import-in-components.ts","../../src/eslint/rules/no-local-socket-enumeration.ts","../../src/eslint/rules/no-raw-fetch-in-src.ts","../../src/eslint/rules/no-raw-try-catch.ts","../../src/eslint/rules/no-unsafe-api-wrappers.ts","../../src/eslint/rules/no-unsafe-sync-wrappers.ts","../../src/eslint/rules/prefer-luckystack-confirm.ts","../../src/eslint/rules/prefer-luckystack-dropdown.ts","../../src/eslint/rules/prefer-luckystack-notify.ts","../../src/eslint/index.ts"],"sourcesContent":["//? Peer-dep presence probe used to gate package-dependent eslint rules.\n//? Resolves against `process.cwd()` so the CONSUMER's node_modules drives\n//? activation, not this package's own resolution graph.\n//?\n//? Implementation: tries `require.resolve` first (works for CJS-compatible\n//? packages), falls back to an fs-based `node_modules/<name>/package.json`\n//? check for pure-ESM packages whose `exports` map omits the `require`\n//? condition (which is the case for the @luckystack/* packages themselves).\n\nimport { existsSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\n\nconst localRequire = createRequire(import.meta.url);\n\nconst checkOnDisk = (name: string): boolean => {\n const segments = name.startsWith('@')\n ? name.split('/').slice(0, 2)\n : [name.split('/')[0] ?? name];\n return existsSync(path.join(process.cwd(), 'node_modules', ...segments, 'package.json'));\n};\n\nexport const hasPackage = (name: string): boolean => {\n try {\n localRequire.resolve(name, { paths: [process.cwd()] });\n return true;\n } catch {\n return checkOnDisk(name);\n }\n};\n","//? Rule: warn on arbitrary Tailwind color values (`text-[#fff]`,\n//? `bg-[#abc123]`, `border-[#000]`) inside className strings. The framework\n//? mandates colors from `src/index.css` `@theme` block (rule 14). Arbitrary\n//? colors break dark-mode auto-switching.\n\nimport type { Rule } from 'eslint';\n\nimport type { EslintRule } from '../internal/ruleTypes.js';\n\nconst ARBITRARY_COLOR_PATTERN = /\\b(?:text|bg|border|ring|fill|stroke|from|to|via|outline|decoration|accent|caret|placeholder|divide)-\\[#[0-9a-fA-F]{3,8}\\]/;\n\n//? This rule reads JSX AST nodes (`JSXAttribute`, `JSXExpressionContainer`,\n//? `TemplateLiteral` quasis) that eslint-plugin-react injects into the visitor\n//? map at runtime but that eslint's stock `Rule.Node` type doesn't model. The\n//? objects ARE real eslint nodes at runtime — `context.report({ node })` only\n//? needs `{ type, loc, range, ... }`, all present. This guard narrows an\n//? `unknown` to `Rule.Node` in one place so the call sites stay cast-free and\n//? the previous file-wide `as unknown as Rule.Node` double-casts are gone.\nconst asRuleNode = (node: object): Rule.Node => node as Rule.Node;\n\n//? JSX AST nodes (JSXAttribute, JSXExpressionContainer, …) are not part of\n//? eslint's stock estree AST. eslint-plugin-react extends the visitor map at\n//? runtime, but the stock `Rule.RuleListener` type does not know about them,\n//? so we describe the shapes we read inline. The actual eslint runtime gives\n//? us the right object; this is purely a TS-side annotation.\ntype ClassNameValueShape =\n | { type: 'Literal'; value: unknown }\n | { type: 'JSXExpressionContainer'; expression: ExpressionShape }\n | { type: string };\n\ntype ExpressionShape =\n | { type: 'Literal'; value: unknown }\n | { type: 'TemplateLiteral'; quasis: readonly { value: { cooked?: string; raw: string } }[] }\n | { type: string };\n\ninterface JsxAttributeShape {\n name: { type: string; name?: string };\n value?: ClassNameValueShape | null;\n}\n\nconst reportIfArbitrary = (\n context: Rule.RuleContext,\n node: Rule.Node,\n value: string,\n): void => {\n if (ARBITRARY_COLOR_PATTERN.test(value)) {\n context.report({ node, messageId: 'useThemeToken' });\n }\n};\n\nconst rule: EslintRule = {\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Disallow arbitrary Tailwind color values (`text-[#…]`, `bg-[#…]`, …). Use tokens from `src/index.css` `@theme` block.',\n },\n messages: {\n useThemeToken:\n 'Do not use arbitrary Tailwind color values like `text-[#…]`. Use a token from `src/index.css` `@theme` block (e.g. `bg-primary`, `text-title`, `border-divider`). Arbitrary colors break dark-mode auto-switching.',\n },\n schema: [],\n },\n create(context) {\n const listener: Rule.RuleListener = {\n JSXAttribute(rawNode: unknown): void {\n const node = rawNode as JsxAttributeShape & Rule.Node;\n if (node.name.type !== 'JSXIdentifier' || node.name.name !== 'className') return;\n const value = node.value;\n if (!value) return;\n if (value.type === 'Literal' && typeof (value as { value?: unknown }).value === 'string') {\n reportIfArbitrary(context, asRuleNode(value), (value as { value: string }).value);\n return;\n }\n if (value.type === 'JSXExpressionContainer') {\n const expression = (value as { expression: ExpressionShape }).expression;\n if (expression.type === 'TemplateLiteral') {\n const quasis = (expression as { quasis: readonly { value: { cooked?: string; raw: string } }[] }).quasis;\n for (const quasi of quasis) {\n reportIfArbitrary(\n context,\n asRuleNode(quasi),\n quasi.value.cooked ?? quasi.value.raw,\n );\n }\n } else if (expression.type === 'Literal' && typeof (expression as { value?: unknown }).value === 'string') {\n reportIfArbitrary(\n context,\n asRuleNode(expression),\n (expression as { value: string }).value,\n );\n }\n }\n },\n };\n return listener;\n },\n};\n\nexport default rule;\n","//? Rule: warn on `import … from '@prisma/client'` outside of allowed\n//? boundaries. Convention: app code re-exports Prisma model types through\n//? `src/_types/<ModelName>.ts` so swapping providers / extending types\n//? stays centralised. Direct imports leak Prisma details into UI code.\n//?\n//? Allowed paths: `src/_types/**`, `server/**`, and any file ending in\n//? `Adapter.ts` (legitimate adapter implementations).\n\nimport type { EslintRule } from '../internal/ruleTypes.js';\n\nconst isAllowedPath = (filename: string): boolean => {\n const normalised = filename.replaceAll('\\\\', '/');\n if (normalised.includes('/src/_types/')) return true;\n if (normalised.includes('/server/')) return true;\n if (normalised.endsWith('Adapter.ts') || normalised.endsWith('Adapter.tsx')) return true;\n if (normalised.includes('/packages/')) return true;\n //? Project-root config files (the LuckyStack `config.ts` convention) and\n //? any `*.config.ts` are framework boundaries — Prisma types there are\n //? expected and not a \"leaking into component code\" concern.\n if (normalised.endsWith('/config.ts') || normalised.endsWith('.config.ts')) return true;\n if (normalised.includes('/prisma/')) return true;\n return false;\n};\n\nconst rule: EslintRule = {\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Re-export Prisma model types through `src/_types/<Model>.ts` rather than importing `@prisma/client` directly in components.',\n },\n messages: {\n reExportThroughTypes:\n 'Do not import from `@prisma/client` here. Re-export the type through `src/_types/<ModelName>.ts` and import that instead — keeps Prisma details out of component code.',\n },\n schema: [],\n },\n create(context) {\n if (isAllowedPath(context.filename)) return {};\n return {\n ImportDeclaration(node) {\n if (node.source.value === '@prisma/client') {\n context.report({ node, messageId: 'reExportThroughTypes' });\n }\n },\n };\n },\n};\n\nexport default rule;\n","//? Rule: forbid enumerating the Socket.io server's LOCAL socket maps.\n//? Three ways of reaching a room's sockets look identical in the editor:\n//? - `io.sockets.adapter.rooms.get(room)` / `io.sockets.adapter.sids`\n//? — only THIS instance.\n//? - enumerating `io.sockets.sockets` (`.values()`, `.keys()`,\n//? `.entries()`, `.forEach()`, `.size`, `for...of`, spread)\n//? — only THIS instance.\n//? - `await io.in(room).fetchSockets()` — ALL instances (Redis-adapter\n//? aware).\n//? The Redis adapter synchronises DELIVERY (`io.to(room).emit()`) but NOT\n//? those two local maps. A broadcast that enumerates a local map, or an\n//? emit gated on `adapter.rooms.has(room)`, works in dev (one instance)\n//? and silently goes dead on every other instance of a split deployment.\n//? A consumer project made this exact mistake three times independently.\n//?\n//? Good path: `getRoomSockets(room, { userId })` from `@luckystack/core`,\n//? or `io.in(room).fetchSockets()`. A deliberate per-instance use (e.g. a\n//? sweep cleaning up its own connections) opts out with\n//? `// eslint-disable-next-line luckystack/no-local-socket-enumeration -- <why>`\n//? and, at runtime, `getIoInstance({ raw: true })` — the dev-mode\n//? `getIoInstance()` proxy throws on these accesses.\n//?\n//? Deliberately NOT reported:\n//? - `io.sockets.sockets.get(id)` / `.has(id)` / `.delete(id)` — a\n//? keyed lookup from a local socket handler is per definition local\n//? and correct; only ENUMERATION is wrong.\n//? - `socket.rooms.has(room)` — the per-socket room set is fine.\n//? - Computed access (`io['sockets']['adapter']['rooms']`) — out of\n//? scope here; the runtime proxy catches it.\n//?\n//? Gated by hasPackage('@luckystack/core') OR hasPackage('@luckystack/sync')\n//? at the config-composition layer.\n\nimport type { Rule } from 'eslint';\nimport type * as ESTree from 'estree';\n\nimport type { EslintRule } from '../internal/ruleTypes.js';\n\nconst ADAPTER_LOCAL_MAPS = new Set(['rooms', 'sids']);\nconst ENUMERATING_MEMBERS = new Set(['values', 'keys', 'entries', 'forEach', 'size']);\n\n//? `<X>.<name>` — a non-computed member access whose property is `name`.\nconst isNamedMember = (\n node: ESTree.Node,\n name: string,\n): node is ESTree.MemberExpression & { property: ESTree.Identifier } =>\n node.type === 'MemberExpression' &&\n !node.computed &&\n node.property.type === 'Identifier' &&\n node.property.name === name;\n\n//? `<Y>.sockets.sockets` — the local socket map on a Socket.io server.\nconst isLocalSocketsMap = (node: ESTree.Node): boolean =>\n isNamedMember(node, 'sockets') && isNamedMember(node.object, 'sockets');\n\nconst rule: EslintRule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Disallow enumerating the Socket.io server\\'s local socket maps (`.adapter.rooms`, `.adapter.sids`, iterating `io.sockets.sockets`). They only see THIS instance; use `getRoomSockets(...)` or `io.in(room).fetchSockets()`.',\n },\n messages: {\n localAdapterMap:\n '`.adapter.{{name}}` only sees sockets connected to THIS instance — on a multi-instance deployment every other instance is invisible here. Use `getRoomSockets(room, { userId })` from `@luckystack/core` or `await io.in(room).fetchSockets()`. For deliberate per-instance work use `getIoInstance({ raw: true })` and add `// eslint-disable-next-line luckystack/no-local-socket-enumeration -- <why>`.',\n localSocketEnumeration:\n 'Enumerating `io.sockets.sockets` (`.{{name}}`) only sees sockets connected to THIS instance — on a multi-instance deployment every other instance is invisible here. Use `getRoomSockets(room, { userId })` from `@luckystack/core` or `await io.in(room).fetchSockets()`. For deliberate per-instance work use `getIoInstance({ raw: true })` and add `// eslint-disable-next-line luckystack/no-local-socket-enumeration -- <why>`.',\n },\n schema: [],\n },\n create(context: Rule.RuleContext) {\n const reportEnumeration = (node: ESTree.Node, name: string): void => {\n context.report({ node, messageId: 'localSocketEnumeration', data: { name } });\n };\n return {\n MemberExpression(node) {\n if (node.computed || node.property.type !== 'Identifier') return;\n const name = node.property.name;\n //? (a) `<X>.adapter.rooms` / `<X>.adapter.sids`\n if (ADAPTER_LOCAL_MAPS.has(name) && isNamedMember(node.object, 'adapter')) {\n context.report({ node, messageId: 'localAdapterMap', data: { name } });\n return;\n }\n //? (b) `<Y>.sockets.sockets.values()` / `.keys()` / `.entries()` /\n //? `.forEach()` / `.size` — `.get()` / `.has()` / `.delete()` pass.\n if (ENUMERATING_MEMBERS.has(name) && isLocalSocketsMap(node.object)) {\n reportEnumeration(node, name);\n }\n },\n //? (c) `for (const s of <Y>.sockets.sockets)`\n ForOfStatement(node) {\n if (isLocalSocketsMap(node.right)) reportEnumeration(node.right, 'for...of');\n },\n //? (d) `[...<Y>.sockets.sockets]`\n SpreadElement(node) {\n if (isLocalSocketsMap(node.argument)) reportEnumeration(node.argument, '...spread');\n },\n };\n },\n};\n\nexport default rule;\n","//? Rule: forbid direct `fetch(...)` calls to framework `/api/*` or\n//? `/sync/*` routes. The framework provides typed transport helpers\n//? `apiRequest` (for `_api/` routes) and `syncRequest` (for `_sync/`\n//? routes) which preserve route/version generics + auth/CSRF/validation.\n//?\n//? The rule is deliberately narrow:\n//? - Fires when the URL is a literal / template containing `/api/` or\n//? `/sync/` (framework route patterns).\n//? - Skips URLs whose PATH STARTS with `/auth/` — the framework's auth\n//? endpoints (`/auth/api/credentials`, OAuth callbacks) are HTTP-only\n//? and intentionally use raw fetch (cookies, headers, redirects). The\n//? start-of-path check (not `.includes`) means a consumer's typed\n//? route under an `auth/` page folder (URL `/api/auth/...`) still\n//? fires correctly.\n//? - Skips external URLs (starts with `http://`, `https://`, `//`) —\n//? consumers fetching third-party APIs are fine.\n//? - Skips dynamic URLs (variable, function call, etc.) — be permissive\n//? rather than flag-by-default; consumers can opt in via comments.\n//?\n//? Gated by hasPackage('@luckystack/api') OR hasPackage('@luckystack/core')\n//? at the config-composition layer.\n\nimport type { EslintRule } from '../internal/ruleTypes.js';\n\ninterface AstNodeShape {\n type?: string;\n value?: unknown;\n quasis?: { value?: { raw?: string; cooked?: string } }[];\n}\n\nconst extractStaticUrlText = (urlNode: unknown): string | null => {\n if (!urlNode || typeof urlNode !== 'object') return null;\n const n = urlNode as AstNodeShape;\n if (n.type === 'Literal' && typeof n.value === 'string') return n.value;\n if (n.type === 'TemplateLiteral' && Array.isArray(n.quasis)) {\n return n.quasis.map((q) => q.value?.cooked ?? q.value?.raw ?? '').join('|');\n }\n return null;\n};\n\nconst targetsFrameworkRoute = (urlNode: unknown): boolean => {\n const text = extractStaticUrlText(urlNode);\n if (text === null) return false;\n if (/^(https?:)?\\/\\//.test(text)) return false;\n //? Skip only when the path STARTS with `/auth/` (framework's special\n //? endpoints). The leading `|` accounts for `extractStaticUrlText`'s\n //? template-literal join when the first quasi is empty, e.g.\n //? `${backendUrl}/auth/api/credentials` joins to `|/auth/api/credentials`.\n if (/^(?:\\|)?\\/auth\\//.test(text)) return false;\n return /(?:^|[^A-Za-z])\\/(?:api|sync)\\//.test(text);\n};\n\nconst rule: EslintRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Disallow raw `fetch(...)` to framework `/api/*` and `/sync/*` routes. Use `apiRequest` / `syncRequest` instead.',\n },\n messages: {\n useApiRequest:\n 'Do not call `fetch` directly for framework `/api/*` routes. Use `apiRequest({ name, version, data })` for typed inference, or `httpFetch(...)` from `@luckystack/core` if you need a raw same-origin request.',\n useSyncRequest:\n 'Do not call `fetch` directly for framework `/sync/*` routes. Use `syncRequest({ name, version, data, receiver })` instead.',\n },\n schema: [],\n },\n create(context) {\n return {\n CallExpression(node) {\n const callee = node.callee;\n const isFetchCall =\n (callee.type === 'Identifier' && callee.name === 'fetch') ||\n (callee.type === 'MemberExpression' &&\n callee.object.type === 'Identifier' &&\n callee.object.name === 'window' &&\n callee.property.type === 'Identifier' &&\n callee.property.name === 'fetch');\n if (!isFetchCall) return;\n const url = node.arguments[0];\n if (!targetsFrameworkRoute(url)) return;\n const text = extractStaticUrlText(url) ?? '';\n const messageId = text.includes('/sync/') ? 'useSyncRequest' : 'useApiRequest';\n context.report({ node, messageId });\n },\n };\n },\n};\n\nexport default rule;\n","//? Rule: forbid raw `try { … } catch` inside src/server/shared code.\n//? The framework provides `tryCatch` from @luckystack/core (and the API/sync\n//? handler `functions.tryCatch` injection) that handles Sentry capture +\n//? normalised tuple returns. Raw try/catch bypasses that.\n\nimport type { EslintRule } from '../internal/ruleTypes.js';\n\nconst rule: EslintRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Disallow raw `try { … } catch` blocks — use the framework `tryCatch` helper instead.',\n },\n messages: {\n useTryCatch:\n 'Do not use raw try/catch. Import `tryCatch` from `@luckystack/core` (client) or `server/functions/tryCatch` (server), or use the injected `functions.tryCatch` in API / sync handlers.',\n },\n schema: [],\n },\n create(context) {\n return {\n TryStatement(node) {\n //? Only fire when an actual catch clause is present. `try { } finally { }`\n //? is a different construct (cleanup, not error handling) and the\n //? `tryCatch` helper is not a substitute.\n if (node.handler) {\n context.report({ node, messageId: 'useTryCatch' });\n }\n },\n };\n },\n};\n\nexport default rule;\n","//? Rule: forbid local wrappers around `apiRequest` whose parameter types\n//? erase the route/version generics. The framework's type system relies on\n//? literal `name`/`version` strings to infer `serverOutput` / `clientOutput`.\n//? A wrapper like:\n//?\n//? const unsafeApi = (name: string, version: string, data: unknown) =>\n//? apiRequest({ name: name as any, ... });\n//?\n//? destroys that inference. Always call `apiRequest` directly with route\n//? literals, or write a wrapper that preserves the generics.\n//?\n//? Gated by hasPackage('@luckystack/api') at the config-composition layer.\n\n//? Double-cast + any-spread disabled file-wide: bridges `Rule.Node` to specific\n//? AST shapes; the spread iterates over `Object.entries(node)` whose values are\n//? typed as `any` because eslint's AST shape varies by node kind.\n/* eslint-disable no-restricted-syntax, @typescript-eslint/no-unsafe-argument */\n\nimport type { EslintRule } from '../internal/ruleTypes.js';\n\nconst SUSPECT_PARAM_NAMES = new Set(['name', 'version', 'route', 'apiName']);\n\n//? AST keys that point to parents or non-AST metadata. Including them in\n//? a recursive descent causes infinite loops because eslint adds `parent`\n//? pointers during traversal.\nconst SKIP_KEYS = new Set(['parent', 'loc', 'range', 'tokens', 'comments']);\n\nconst containsApiRequestCall = (body: unknown): boolean => {\n if (!body || typeof body !== 'object') return false;\n const visited = new WeakSet<object>();\n const stack: unknown[] = [body];\n while (stack.length > 0) {\n const node = stack.pop() as Record<string, unknown> | null;\n if (!node || typeof node !== 'object') continue;\n if (visited.has(node)) continue;\n visited.add(node);\n const callee = (node as { type?: string; callee?: { type?: string; name?: string } }).callee;\n if (\n (node as { type?: string }).type === 'CallExpression' &&\n callee?.type === 'Identifier' &&\n (callee.name === 'apiRequest' || callee.name === 'syncRequest')\n ) {\n return true;\n }\n for (const [key, value] of Object.entries(node)) {\n if (SKIP_KEYS.has(key)) continue;\n if (Array.isArray(value)) stack.push(...value);\n else if (value && typeof value === 'object') stack.push(value);\n }\n }\n return false;\n};\n\nconst isSuspectFunction = (node: {\n params?: { type?: string; name?: string; typeAnnotation?: { typeAnnotation?: { type?: string } } }[];\n body?: unknown;\n}): boolean => {\n if (!node.params || node.params.length === 0) return false;\n const hasSuspectStringParam = node.params.some((p) => {\n if (p.type !== 'Identifier' || !p.name) return false;\n if (!SUSPECT_PARAM_NAMES.has(p.name)) return false;\n const annotation = p.typeAnnotation?.typeAnnotation?.type;\n return annotation === 'TSStringKeyword';\n });\n if (!hasSuspectStringParam) return false;\n return containsApiRequestCall(node.body);\n};\n\nconst rule: EslintRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Disallow local wrappers around `apiRequest` / `syncRequest` whose `name`/`version` params are typed as plain `string` — this erases the generic route/version inference.',\n },\n messages: {\n unsafeWrapper:\n 'This wrapper around `apiRequest` / `syncRequest` accepts `{{paramName}}: string` and erases the route/version generic inference. Call the framework helper directly with literal route names, or type the parameter as a literal union (e.g. `name: \\'examples/getUserData\\'`).',\n },\n schema: [],\n },\n create(context) {\n return {\n FunctionDeclaration(node) {\n if (isSuspectFunction(node)) {\n const suspect = (node.params as { name?: string }[]).find((p) => p.name && SUSPECT_PARAM_NAMES.has(p.name));\n context.report({ node, messageId: 'unsafeWrapper', data: { paramName: suspect?.name ?? 'name' } });\n }\n },\n ArrowFunctionExpression(node) {\n if (isSuspectFunction(node)) {\n const suspect = (node.params as { name?: string }[]).find((p) => p.name && SUSPECT_PARAM_NAMES.has(p.name));\n context.report({ node, messageId: 'unsafeWrapper', data: { paramName: suspect?.name ?? 'name' } });\n }\n },\n };\n },\n};\n\nexport default rule;\n","//? Rule: same shape as no-unsafe-api-wrappers, but targeting wrappers\n//? around `syncRequest` and `upsertSyncEventCallback`. Gated by\n//? hasPackage('@luckystack/sync') at the config-composition layer.\n//?\n//? Note: the api-wrappers rule already covers the `syncRequest` call site;\n//? this rule additionally catches `upsertSyncEventCallback` wrappers and\n//? exists as a distinct identity so the sync-only rule can be disabled\n//? independently when a consumer uses api but not sync.\n\n//? Double-cast + any-spread disabled file-wide: bridges `Rule.Node` to specific\n//? AST shapes; the spread iterates over `Object.entries(node)` whose values are\n//? typed as `any` because eslint's AST shape varies by node kind.\n/* eslint-disable no-restricted-syntax, @typescript-eslint/no-unsafe-argument */\n\nimport type { EslintRule } from '../internal/ruleTypes.js';\n\nconst SUSPECT_PARAM_NAMES = new Set(['name', 'version', 'route', 'syncName']);\nconst SUSPECT_CALLEES = new Set(['syncRequest', 'upsertSyncEventCallback']);\n\n//? AST keys that point to parents or non-AST metadata. Skipped to avoid\n//? infinite loops when eslint adds `parent` pointers during traversal.\nconst SKIP_KEYS = new Set(['parent', 'loc', 'range', 'tokens', 'comments']);\n\nconst containsSuspectCall = (body: unknown): boolean => {\n if (!body || typeof body !== 'object') return false;\n const visited = new WeakSet<object>();\n const stack: unknown[] = [body];\n while (stack.length > 0) {\n const node = stack.pop() as Record<string, unknown> | null;\n if (!node || typeof node !== 'object') continue;\n if (visited.has(node)) continue;\n visited.add(node);\n const callee = (node as { type?: string; callee?: { type?: string; name?: string } }).callee;\n if (\n (node as { type?: string }).type === 'CallExpression' &&\n callee?.type === 'Identifier' &&\n SUSPECT_CALLEES.has(callee.name ?? '')\n ) {\n return true;\n }\n for (const [key, value] of Object.entries(node)) {\n if (SKIP_KEYS.has(key)) continue;\n if (Array.isArray(value)) stack.push(...value);\n else if (value && typeof value === 'object') stack.push(value);\n }\n }\n return false;\n};\n\nconst isSuspect = (node: {\n params?: { type?: string; name?: string; typeAnnotation?: { typeAnnotation?: { type?: string } } }[];\n body?: unknown;\n}): boolean => {\n if (!node.params || node.params.length === 0) return false;\n const hasSuspectStringParam = node.params.some((p) => {\n if (p.type !== 'Identifier' || !p.name) return false;\n if (!SUSPECT_PARAM_NAMES.has(p.name)) return false;\n return p.typeAnnotation?.typeAnnotation?.type === 'TSStringKeyword';\n });\n if (!hasSuspectStringParam) return false;\n return containsSuspectCall(node.body);\n};\n\nconst rule: EslintRule = {\n meta: {\n type: 'problem',\n docs: {\n description: 'Disallow local wrappers around `syncRequest` / `upsertSyncEventCallback` whose `name`/`version` params are typed as plain `string`.',\n },\n messages: {\n unsafeSyncWrapper:\n 'This wrapper around `syncRequest` / `upsertSyncEventCallback` accepts a `string` route param and erases the generic event-payload inference. Call the framework helper directly with literal route names.',\n },\n schema: [],\n },\n create(context) {\n return {\n FunctionDeclaration(node) {\n if (isSuspect(node)) {\n context.report({ node, messageId: 'unsafeSyncWrapper' });\n }\n },\n ArrowFunctionExpression(node) {\n if (isSuspect(node)) {\n context.report({ node, messageId: 'unsafeSyncWrapper' });\n }\n },\n };\n },\n};\n\nexport default rule;\n","//? Rule: warn on `confirm(...)` / `window.confirm(...)` calls. The framework\n//? provides `menuHandler.confirm({ title, content, input? })` from\n//? `src/_functions/menuHandler.ts` which renders a styled, theme-aware,\n//? Promise-returning confirm dialog and respects keyboard / accessibility.\n\nimport type { EslintRule } from '../internal/ruleTypes.js';\n\nconst rule: EslintRule = {\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Prefer `menuHandler.confirm({ title, content })` over the native `confirm()` dialog.',\n },\n messages: {\n useMenuHandlerConfirm:\n 'Use `menuHandler.confirm({ title, content })` from `src/_functions/menuHandler.ts` instead of native `confirm()`. Returns a `Promise<boolean>` with proper theming.',\n },\n schema: [],\n },\n create(context) {\n return {\n CallExpression(node) {\n const callee = node.callee;\n if (callee.type === 'Identifier' && callee.name === 'confirm') {\n context.report({ node, messageId: 'useMenuHandlerConfirm' });\n return;\n }\n if (\n callee.type === 'MemberExpression' &&\n callee.object.type === 'Identifier' &&\n callee.object.name === 'window' &&\n callee.property.type === 'Identifier' &&\n callee.property.name === 'confirm'\n ) {\n context.report({ node, messageId: 'useMenuHandlerConfirm' });\n }\n },\n };\n },\n};\n\nexport default rule;\n","//? Rule: warn when a JSX `<select>` element is used. The framework ships\n//? `Dropdown` (src/_components/Dropdown.tsx) with search, keyboard nav,\n//? sizing variants, and consistent dark/light styling. Native `<select>`\n//? skips all of that.\n\nimport type { Rule } from 'eslint';\n\nimport type { EslintRule } from '../internal/ruleTypes.js';\n\n//? JSX AST nodes are extended at runtime by eslint-plugin-react, but\n//? eslint's stock `Rule.RuleListener` type does not know about them. We\n//? describe the shapes we read inline.\ninterface JsxOpeningElementShape {\n name: { type: string; name?: string };\n}\n\nconst rule: EslintRule = {\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Prefer the framework `Dropdown` component over native `<select>` elements.',\n },\n messages: {\n useDropdown:\n 'Use `Dropdown` from `src/_components/Dropdown.tsx` (or `MultiSelectDropdown` for multi-select) instead of native `<select>`. The framework component handles keyboard nav, search, sizing, and dark/light theme.',\n },\n schema: [],\n },\n create(context) {\n const listener: Rule.RuleListener = {\n JSXOpeningElement(rawNode: unknown): void {\n const node = rawNode as JsxOpeningElementShape & Rule.Node;\n if (node.name.type === 'JSXIdentifier' && node.name.name === 'select') {\n context.report({ node, messageId: 'useDropdown' });\n }\n },\n };\n return listener;\n },\n};\n\nexport default rule;\n","//? Rule: warn on `alert(...)` / `window.alert(...)`. The framework's\n//? `notify` helper (from @luckystack/core) renders themed toast\n//? notifications via the registered notifier (default sonner).\n\nimport type { EslintRule } from '../internal/ruleTypes.js';\n\nconst rule: EslintRule = {\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Prefer `notify()` over the native `alert()` dialog.',\n },\n messages: {\n useNotify:\n 'Use `notify({ type, message })` from `@luckystack/core` instead of native `alert()`. Renders a themed toast via the registered notifier.',\n },\n schema: [],\n },\n create(context) {\n return {\n CallExpression(node) {\n const callee = node.callee;\n if (callee.type === 'Identifier' && callee.name === 'alert') {\n context.report({ node, messageId: 'useNotify' });\n return;\n }\n if (\n callee.type === 'MemberExpression' &&\n callee.object.type === 'Identifier' &&\n callee.object.name === 'window' &&\n callee.property.type === 'Identifier' &&\n callee.property.name === 'alert'\n ) {\n context.report({ node, messageId: 'useNotify' });\n }\n },\n };\n },\n};\n\nexport default rule;\n","//? Public entry for `@luckystack/core/eslint`. Composes the LuckyStack\n//? flat-config tier that the scaffolded `eslint.luckystack.config.js`\n//? consumes. Rules that target an optional `@luckystack/*` peer package\n//? are gated via `hasPackage(...)` so absent peers cause silent rule\n//? skips (NOT noisy \"rule not found\" errors).\n//?\n//? Override surface: consumers can spread this default export and append\n//? their own config blocks, or replace individual rule severities by\n//? re-stating them with `'off' | 'warn' | 'error'`.\n\nimport type { Linter } from 'eslint';\n\nimport { hasPackage } from './internal/hasPackage.js';\nimport type { EslintRule } from './internal/ruleTypes.js';\n\nimport noArbitraryTailwindColor from './rules/no-arbitrary-tailwind-color.js';\nimport noDirectPrismaImportInComponents from './rules/no-direct-prisma-import-in-components.js';\nimport noLocalSocketEnumeration from './rules/no-local-socket-enumeration.js';\nimport noRawFetchInSrc from './rules/no-raw-fetch-in-src.js';\nimport noRawTryCatch from './rules/no-raw-try-catch.js';\nimport noUnsafeApiWrappers from './rules/no-unsafe-api-wrappers.js';\nimport noUnsafeSyncWrappers from './rules/no-unsafe-sync-wrappers.js';\nimport preferLuckystackConfirm from './rules/prefer-luckystack-confirm.js';\nimport preferLuckystackDropdown from './rules/prefer-luckystack-dropdown.js';\nimport preferLuckystackNotify from './rules/prefer-luckystack-notify.js';\n\nconst allRules: Record<string, EslintRule> = {\n 'no-raw-try-catch': noRawTryCatch,\n 'no-raw-fetch-in-src': noRawFetchInSrc,\n 'no-unsafe-api-wrappers': noUnsafeApiWrappers,\n 'no-unsafe-sync-wrappers': noUnsafeSyncWrappers,\n 'no-local-socket-enumeration': noLocalSocketEnumeration,\n 'prefer-luckystack-dropdown': preferLuckystackDropdown,\n 'prefer-luckystack-confirm': preferLuckystackConfirm,\n 'prefer-luckystack-notify': preferLuckystackNotify,\n 'no-direct-prisma-import-in-components': noDirectPrismaImportInComponents,\n 'no-arbitrary-tailwind-color': noArbitraryTailwindColor,\n};\n\nconst hasApi = hasPackage('@luckystack/api');\nconst hasCore = hasPackage('@luckystack/core');\nconst hasSync = hasPackage('@luckystack/sync');\n\nconst ruleSeverities: Record<string, Linter.RuleSeverity> = {\n // Always-on errors.\n 'luckystack/no-raw-try-catch': 'error',\n\n // Package-gated errors. Disabled when the relevant peer is absent so\n // a consumer using only @luckystack/core sees no \"use apiRequest\" noise.\n 'luckystack/no-raw-fetch-in-src': hasApi || hasCore ? 'error' : 'off',\n 'luckystack/no-unsafe-api-wrappers': hasApi ? 'error' : 'off',\n 'luckystack/no-unsafe-sync-wrappers': hasSync ? 'error' : 'off',\n // Local Socket.io maps (`.adapter.rooms`, iterating `io.sockets.sockets`) only see one instance.\n 'luckystack/no-local-socket-enumeration': hasCore || hasSync ? 'error' : 'off',\n\n // Warnings — enabled by default per framework owner's preference.\n 'luckystack/prefer-luckystack-dropdown': 'warn',\n 'luckystack/prefer-luckystack-confirm': 'warn',\n 'luckystack/prefer-luckystack-notify': 'warn',\n 'luckystack/no-direct-prisma-import-in-components': 'warn',\n 'luckystack/no-arbitrary-tailwind-color': 'warn',\n};\n\nconst luckystackConfig: Linter.Config[] = [\n {\n files: ['**/*.{ts,tsx,js,jsx,mjs,cjs}'],\n plugins: {\n luckystack: { rules: allRules },\n },\n rules: ruleSeverities,\n },\n];\n\nexport default luckystackConfig;\n\n//? Named exports for advanced consumers who want to assemble their own\n//? config or selectively re-enable individual rules.\nexport { allRules as rules };\n\n\nexport {hasPackage} from './internal/hasPackage.js';"],"mappings":"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","names":["rule","rule","rule","rule","rule","SUSPECT_PARAM_NAMES","SKIP_KEYS","rule","rule","rule","rule"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -113,7 +113,16 @@ interface RoomNameFormatterContext {
|
|
|
113
113
|
* seen, identically to `'broadcast'`.
|
|
114
114
|
*/
|
|
115
115
|
purpose: 'join' | 'leave' | 'broadcast' | 'presence';
|
|
116
|
-
/**
|
|
116
|
+
/**
|
|
117
|
+
* Session user id of whoever is performing THIS operation, when known, else
|
|
118
|
+
* null — CONTEXT ONLY. For a shared content room under `'broadcast'` the
|
|
119
|
+
* framework passes the joiner on join/rejoin, the SENDER on fan-out and the
|
|
120
|
+
* originator on `broadcastStream`, so the value differs per call for the SAME
|
|
121
|
+
* room. A formatter must therefore never fold `userId` into a content room's
|
|
122
|
+
* physical name: join and fan-out would resolve to different rooms and every
|
|
123
|
+
* broadcast would reach nobody, silently. Per-user isolation belongs in the
|
|
124
|
+
* raw room code the consumer chooses, or in the separate `'presence'` family.
|
|
125
|
+
*/
|
|
117
126
|
userId?: string | null;
|
|
118
127
|
}
|
|
119
128
|
type RoomNameFormatter = (rawRoomName: string, ctx: RoomNameFormatterContext) => string;
|
|
@@ -245,7 +254,53 @@ interface syncMessage {
|
|
|
245
254
|
ignoreSelf?: boolean;
|
|
246
255
|
}
|
|
247
256
|
declare const setIoInstance: (io: Server | null) => void;
|
|
248
|
-
|
|
257
|
+
interface GetIoInstanceOptions {
|
|
258
|
+
/**
|
|
259
|
+
* Return the raw Socket.io server even outside production. Reach for this
|
|
260
|
+
* ONLY when the per-instance view is what you want (backpressure sampling, a
|
|
261
|
+
* sweep cleaning up this process's own connections) — and say why in a
|
|
262
|
+
* comment, because the lint rule `luckystack/no-local-socket-enumeration`
|
|
263
|
+
* will still flag the access.
|
|
264
|
+
*/
|
|
265
|
+
raw?: boolean;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Read the running Socket.io server. Outside production the returned value is
|
|
269
|
+
* a guarded view that THROWS on the per-instance surfaces
|
|
270
|
+
* (`sockets.adapter.rooms`, `sockets.adapter.sids`, enumerating
|
|
271
|
+
* `sockets.sockets`) — see `localSocketEnumerationGuard.ts` for why. Pass
|
|
272
|
+
* `{ raw: true }` for the unwrapped server. In production the raw server is
|
|
273
|
+
* always returned; the guard costs nothing there.
|
|
274
|
+
*/
|
|
275
|
+
declare const getIoInstance: (options?: GetIoInstanceOptions) => Server | null;
|
|
276
|
+
|
|
277
|
+
/** Thrown (outside production) when consumer code touches a per-instance socket map. */
|
|
278
|
+
declare class LocalSocketEnumerationError extends Error {
|
|
279
|
+
constructor(surface: string);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** A recipient as returned by Socket.io's adapter-aware `fetchSockets()`. */
|
|
283
|
+
type RoomSocket = Awaited<ReturnType<Server['fetchSockets']>>[number];
|
|
284
|
+
interface GetRoomSocketsOptions {
|
|
285
|
+
/**
|
|
286
|
+
* User id forwarded to the room-name formatter as CONTEXT. Leave it out (or
|
|
287
|
+
* pass `null`) for a shared room — the formatter contract forbids folding it
|
|
288
|
+
* into a content room's physical name, so it only matters to a formatter that
|
|
289
|
+
* logs or branches on it.
|
|
290
|
+
*/
|
|
291
|
+
userId?: string | null;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Resolve every socket currently in `room`, on EVERY instance sharing the
|
|
295
|
+
* Redis adapter. `room` is the LOGICAL code (what the client joined); it is
|
|
296
|
+
* routed through the registered room-name formatter under the canonical
|
|
297
|
+
* `'broadcast'` purpose exactly like the sync fan-out. The `'all'` sentinel
|
|
298
|
+
* returns every connected socket everywhere.
|
|
299
|
+
*
|
|
300
|
+
* Throws when no Socket.io server is registered: a silent `[]` is precisely the
|
|
301
|
+
* failure class this helper exists to remove.
|
|
302
|
+
*/
|
|
303
|
+
declare const getRoomSockets: (room: string, options?: GetRoomSocketsOptions) => Promise<RoomSocket[]>;
|
|
249
304
|
|
|
250
305
|
type RuntimeMapRecord = Record<string, unknown>;
|
|
251
306
|
interface RuntimeApiMapsResult {
|
|
@@ -640,6 +695,7 @@ interface AttachSocketRedisAdapterOptions {
|
|
|
640
695
|
/**
|
|
641
696
|
* Options forwarded to `@socket.io/redis-adapter`'s `createAdapter`
|
|
642
697
|
* (e.g. `key` prefix, `requestsTimeout`, `publishOnSpecificResponseChannel`).
|
|
698
|
+
* `key` defaults to {@link resolveSocketAdapterKey} when omitted.
|
|
643
699
|
*/
|
|
644
700
|
adapterOptions?: Partial<RedisAdapterOptions>;
|
|
645
701
|
/**
|
|
@@ -651,6 +707,7 @@ interface AttachSocketRedisAdapterOptions {
|
|
|
651
707
|
/** Pre-built sub client (see `pubClient`). */
|
|
652
708
|
subClient?: Redis;
|
|
653
709
|
}
|
|
710
|
+
declare const resolveSocketAdapterKey: () => string;
|
|
654
711
|
declare const attachSocketRedisAdapter: (io: Server, options?: AttachSocketRedisAdapterOptions) => void;
|
|
655
712
|
|
|
656
713
|
declare const BOOT_KEY_PREFIX = "luckystack:boot:";
|
|
@@ -1734,9 +1791,14 @@ interface PreSyncRecipientPayload {
|
|
|
1734
1791
|
/** Socket id of THIS recipient. */
|
|
1735
1792
|
recipientSocketId: string;
|
|
1736
1793
|
/**
|
|
1737
|
-
*
|
|
1738
|
-
*
|
|
1739
|
-
*
|
|
1794
|
+
* Session token of THIS recipient, as read from its socket handshake — the
|
|
1795
|
+
* same value the fan-out uses for `ignoreSelf`. `null` for an anonymous
|
|
1796
|
+
* socket. Cheap: no session read is performed to produce it.
|
|
1797
|
+
*/
|
|
1798
|
+
recipientToken: string | null;
|
|
1799
|
+
/**
|
|
1800
|
+
* Always `null`. Kept on the payload for backward compatibility; resolve the
|
|
1801
|
+
* user from `recipientToken` when you need it.
|
|
1740
1802
|
*/
|
|
1741
1803
|
recipientUserId: string | null;
|
|
1742
1804
|
/** The server-validated output about to be sent. */
|
|
@@ -1821,4 +1883,4 @@ declare const clearAllHooks: () => void;
|
|
|
1821
1883
|
declare const registerSyncHook: <TName extends SyncHookName>(name: TName, handler: SyncHookHandler<SyncHookPayloads[TName]>) => (() => void);
|
|
1822
1884
|
declare const dispatchSyncHook: <TName extends SyncHookName>(name: TName, payload: SyncHookPayloads[TName]) => void;
|
|
1823
1885
|
|
|
1824
|
-
export { type ApiAuthRejectedPayload, type ApiErrorPayload, type ApiResponseEnvelope, AuthProps, type AvatarConfig, type AvatarConfigInput, BOOT_KEY_PREFIX, BaseSessionLayout, type BootUuidHeartbeat, type CheckRateLimitParams, type CookiePrefixConstraints, type CorsRejectedPayload, type CreateLocalizedNormalizerInput, type CsrfConfig, type CsrfCookieOptions, type CsrfMismatchPayload, DEFAULT_AVATAR_CONFIG, DEFAULT_CLIENT_KEY, DEFAULT_CSRF_CONFIG, DEFAULT_ENV_FILES, DEFAULT_REDACTED_LOG_KEYS, DEPTH_TRUNCATED_PLACEHOLDER, type DbHealthCheck, type DbHealthResult, DeepPartial, type DispatchResult, type EmailAttachment, type EmailDeliveryOutcome, type EmailMessage, type EmailResult, type EmailSendContext, type EmailSender, type EmailSenderRegistry, type ErrorFormatter, type ErrorFormatterContext, ErrorParam, ErrorResponseInput, type ErrorTracker, type ErrorTrackerContext, type ErrorTrackerEvent, type ErrorTrackerUser, type FormatterEnvelope, GENERATED_API_DOCS_PATH, GENERATED_API_SCHEMAS_PATH, GENERATED_SOCKET_TYPES_PATH, HealthHashConfig, type HealthHashDescriptor, type HookHandler, type HookName, type HookPayloads, type HookResult, type HookSessionShape, type HookStopSignal, type HttpMethod, type LanguageCode, type LocaleReloader, type LocalizedNormalizer, type NormalizeErrorResponseInput, NormalizedErrorResponse, type OnUploadCompletePayload, type OnUploadStartPayload, type OneTimeTokenHandle, PUBLIC_DIR, type PeerRequire, type PostApiExecutePayload, type PostApiRespondPayload, type PostApiValidatePayload, type PostAvatarServePayload, type PostErrorNormalizePayload, type PostHttpRequestPayload, type PostSessionRefreshPayload, type PostSyncAuthorizePayload, type PostSyncExecutePayload, type PostSyncFanoutPayload, type PostSyncValidatePayload, type PreApiExecutePayload, type PreApiRespondPayload, type PreApiValidatePayload, type PreAvatarServePayload, type PreCaptureFilter, type PreErrorNormalizePayload, type PreHttpRequestPayload, type PreServerStopPayload, type PreSessionRefreshPayload, type PreSocketMessagePayload, type PreSyncAuthorizePayload, type PreSyncExecutePayload, type PreSyncFanoutPayload, type PreSyncRecipientPayload, type PreSyncValidatePayload, type ProcessUploadInput, type ProcessUploadResult, REDACTED_PLACEHOLDER, ROOT_DIR, type RateLimitExceededPayload, type RateLimitResult, type RateLimitStrategy, type RedisConnectionOptions, type RedisKeyFormatter, type Registry, type RegistryOptions, type ResolveClientIpParams, type RoomNameFormatter, type RoomNameFormatterContext, type RuntimeApiMapsResult, type RuntimeEnv, type RuntimeMapsProvider, type RuntimeSyncMapsResult, SERVER_DIR, SERVER_FUNCTIONS_DIR, SHARED_DIR, SRC_DIR, type SessionCreatedPayload, type SessionLogoutInput, type SessionProvider, type SessionRevokedPayload, type SessionSaveResult, type SocketMiddleware, type SpanHandle, type SpanResult, type SyncErrorPayload, type SyncHookHandler, type SyncHookName, type SyncHookPayloads, TEST_DIRECTORY_NAMES, TEST_FILE_PATTERN, TSCONFIG_ALIAS_FILES, type TranslateFunction, type TranslateInput, UNKNOWN_CLIENT_IP, UPLOADS_DIR, type ValidationResult$1 as ValidationResult, abortAllForSocket, abortApiByResponseIndex, abortSyncByCb, acquireLease, allowedOrigin, type apiMessage, appendErrorTracker, applyCookiePrefixConstraints, applyErrorFormatter, applySocketMiddlewares, applyStrayKeyPrefix, attachSocketRedisAdapter, bootstrapEnv, captureException, captureExceptionAcrossTrackers, captureMessage, captureMessageAcrossTrackers, checkRateLimit, clearAllHooks, clearAllRateLimits, clearRateLimit, clearSocketMiddlewares, collectSynchronizedEnvKeys, computeSynchronizedEnvHashes, consumeOneTimeToken, consumeOneTimeTokenJson, createLocalizedNormalizer, createRegistry, defaultRateLimitStrategy, defaultRedisKeyFormatter, defaultRoomNameFormatter, deriveTokenBucketId, describeHealthHashConfig, dispatchHook, dispatchSyncHook, ensurePeerDepInstalled, env, escapeHtml, extractLanguageFromHeader, extractTokenFromRequest, extractTokenFromSocket, flushErrorTrackers, formatKey, formatRoomName, getActiveErrorTrackers, getAvatarConfig, getBindAddress, getCookieValue, getCsrfConfig, getCurrentErrorTrackerIdentity, getDbHealthCheck, getEffectiveHttpMethod, getEmailSender, getEmailSenderByName, getEnv, getEnvFiles, getErrorFormatter, getGeneratedApiDocsPath, getGeneratedApiSchemasPath, getGeneratedSocketTypesPath, getIoInstance, getLocaleReloader, getLocalizedNormalizer, getParams, getPrismaClient, getPrismaClientFor, getPrismaClientKeys, getPublicDir, getRateLimitStatus, getRateLimitStrategy, getRedactedLogKeys, getRedisClient, getRedisClientFor, getRedisClientKeys, getRedisConnectionOptions, getRedisKeyFormatter, getRegisteredSessionProvider, getRoomNameFormatter, getRuntimeApiMaps, getRuntimeSyncMaps, getServerDir, getServerFunctionDirs, getServerFunctionsDir, getSharedDir, getSocketMiddlewares, getSrcDir, getUploadsDir, hasCookie, hashSynchronizedValue, hashSynchronizedValueWith, inferHttpMethod, initConsolelog, initSharedSentry, isDbHealthCheckRegistered, isEmailSenderRegistered, isFalsy, isGetMethodName, isLocalizedNormalizerRegistered, isLoopbackIp, isMethodAllowed, isPrismaClientRegistered, isPrismaClientResolvable, isProduction, isProductionRuntime, isRedactedLogKey, isRedisClientRegistered, isRuntimeMapsProviderRegistered, isSessionProviderRegistered, isTestDirectory, isTestFile, isTestRuntime, issueOneTimeToken, listEmailSenderNames, loadEnvFiles, loadPeer, normalizeErrorResponse, normalizeOrigin, oneTimeTokenKey, performLogout, prisma, processUpload, readBootUuid, readSession, rebuildDefaultRedisClient, recordMetricAcrossTrackers, redis, refreshBootUuid, registerApiAbortController, registerAvatarConfig, registerBindAddress, registerBoundAddress, registerCsrfConfig, registerDbHealthCheck, registerEmailSender, registerEmailSenders, registerErrorFormatter, registerErrorTracker, registerErrorTrackers, registerHook, registerLocaleReloader, registerLocalizedNormalizer, registerPreCaptureFilter, registerPrismaClient, registerRateLimitStrategy, registerRedactedLogKeys, registerRedisClient, registerRedisKeyFormatter, registerRoomNameFormatter, registerRuntimeMapsProvider, registerSessionProvider, registerSocketMiddleware, registerStrayPrefixCommand, registerSyncAbortController, registerSyncHook, releaseLease, removeSession, renewLease, resetClientsForTests, resetCsrfConfigForTests, resetDbHealthCheckForTests, resetDefaultRedisClient, resetRedactedLogKeysForTests, resetRedisKeyFormatterForTests, resetSessionProviderForTests, resolveClientIp, resolveDevCallbackUrl, resolveEnvKey, resolveErrorMessage, resolveFromRoot, resolveHealthHashConfig, resolveHealthHashConfigFromDescriptor, resolveRuntimeMode, runWithErrorTrackerIdentity, runWithErrorTrackerIdentityScope, sanitizeErrorString, sanitizeErrorStrings, sanitizeForLog, serveAvatar, setCurrentErrorTrackerIdentity, setErrorTrackerUser, setIoInstance, setSentryUser, startBootUuidHeartbeat, startSpan, startSpanAcrossTrackers, startSpanHandle, type syncMessage, tryCatch, unregisterApiAbortController, unregisterSyncAbortController, validateInputByType, validateRequest, writeBootUuid, writeSession };
|
|
1886
|
+
export { type ApiAuthRejectedPayload, type ApiErrorPayload, type ApiResponseEnvelope, AuthProps, type AvatarConfig, type AvatarConfigInput, BOOT_KEY_PREFIX, BaseSessionLayout, type BootUuidHeartbeat, type CheckRateLimitParams, type CookiePrefixConstraints, type CorsRejectedPayload, type CreateLocalizedNormalizerInput, type CsrfConfig, type CsrfCookieOptions, type CsrfMismatchPayload, DEFAULT_AVATAR_CONFIG, DEFAULT_CLIENT_KEY, DEFAULT_CSRF_CONFIG, DEFAULT_ENV_FILES, DEFAULT_REDACTED_LOG_KEYS, DEPTH_TRUNCATED_PLACEHOLDER, type DbHealthCheck, type DbHealthResult, DeepPartial, type DispatchResult, type EmailAttachment, type EmailDeliveryOutcome, type EmailMessage, type EmailResult, type EmailSendContext, type EmailSender, type EmailSenderRegistry, type ErrorFormatter, type ErrorFormatterContext, ErrorParam, ErrorResponseInput, type ErrorTracker, type ErrorTrackerContext, type ErrorTrackerEvent, type ErrorTrackerUser, type FormatterEnvelope, GENERATED_API_DOCS_PATH, GENERATED_API_SCHEMAS_PATH, GENERATED_SOCKET_TYPES_PATH, type GetIoInstanceOptions, type GetRoomSocketsOptions, HealthHashConfig, type HealthHashDescriptor, type HookHandler, type HookName, type HookPayloads, type HookResult, type HookSessionShape, type HookStopSignal, type HttpMethod, type LanguageCode, LocalSocketEnumerationError, type LocaleReloader, type LocalizedNormalizer, type NormalizeErrorResponseInput, NormalizedErrorResponse, type OnUploadCompletePayload, type OnUploadStartPayload, type OneTimeTokenHandle, PUBLIC_DIR, type PeerRequire, type PostApiExecutePayload, type PostApiRespondPayload, type PostApiValidatePayload, type PostAvatarServePayload, type PostErrorNormalizePayload, type PostHttpRequestPayload, type PostSessionRefreshPayload, type PostSyncAuthorizePayload, type PostSyncExecutePayload, type PostSyncFanoutPayload, type PostSyncValidatePayload, type PreApiExecutePayload, type PreApiRespondPayload, type PreApiValidatePayload, type PreAvatarServePayload, type PreCaptureFilter, type PreErrorNormalizePayload, type PreHttpRequestPayload, type PreServerStopPayload, type PreSessionRefreshPayload, type PreSocketMessagePayload, type PreSyncAuthorizePayload, type PreSyncExecutePayload, type PreSyncFanoutPayload, type PreSyncRecipientPayload, type PreSyncValidatePayload, type ProcessUploadInput, type ProcessUploadResult, REDACTED_PLACEHOLDER, ROOT_DIR, type RateLimitExceededPayload, type RateLimitResult, type RateLimitStrategy, type RedisConnectionOptions, type RedisKeyFormatter, type Registry, type RegistryOptions, type ResolveClientIpParams, type RoomNameFormatter, type RoomNameFormatterContext, type RoomSocket, type RuntimeApiMapsResult, type RuntimeEnv, type RuntimeMapsProvider, type RuntimeSyncMapsResult, SERVER_DIR, SERVER_FUNCTIONS_DIR, SHARED_DIR, SRC_DIR, type SessionCreatedPayload, type SessionLogoutInput, type SessionProvider, type SessionRevokedPayload, type SessionSaveResult, type SocketMiddleware, type SpanHandle, type SpanResult, type SyncErrorPayload, type SyncHookHandler, type SyncHookName, type SyncHookPayloads, TEST_DIRECTORY_NAMES, TEST_FILE_PATTERN, TSCONFIG_ALIAS_FILES, type TranslateFunction, type TranslateInput, UNKNOWN_CLIENT_IP, UPLOADS_DIR, type ValidationResult$1 as ValidationResult, abortAllForSocket, abortApiByResponseIndex, abortSyncByCb, acquireLease, allowedOrigin, type apiMessage, appendErrorTracker, applyCookiePrefixConstraints, applyErrorFormatter, applySocketMiddlewares, applyStrayKeyPrefix, attachSocketRedisAdapter, bootstrapEnv, captureException, captureExceptionAcrossTrackers, captureMessage, captureMessageAcrossTrackers, checkRateLimit, clearAllHooks, clearAllRateLimits, clearRateLimit, clearSocketMiddlewares, collectSynchronizedEnvKeys, computeSynchronizedEnvHashes, consumeOneTimeToken, consumeOneTimeTokenJson, createLocalizedNormalizer, createRegistry, defaultRateLimitStrategy, defaultRedisKeyFormatter, defaultRoomNameFormatter, deriveTokenBucketId, describeHealthHashConfig, dispatchHook, dispatchSyncHook, ensurePeerDepInstalled, env, escapeHtml, extractLanguageFromHeader, extractTokenFromRequest, extractTokenFromSocket, flushErrorTrackers, formatKey, formatRoomName, getActiveErrorTrackers, getAvatarConfig, getBindAddress, getCookieValue, getCsrfConfig, getCurrentErrorTrackerIdentity, getDbHealthCheck, getEffectiveHttpMethod, getEmailSender, getEmailSenderByName, getEnv, getEnvFiles, getErrorFormatter, getGeneratedApiDocsPath, getGeneratedApiSchemasPath, getGeneratedSocketTypesPath, getIoInstance, getLocaleReloader, getLocalizedNormalizer, getParams, getPrismaClient, getPrismaClientFor, getPrismaClientKeys, getPublicDir, getRateLimitStatus, getRateLimitStrategy, getRedactedLogKeys, getRedisClient, getRedisClientFor, getRedisClientKeys, getRedisConnectionOptions, getRedisKeyFormatter, getRegisteredSessionProvider, getRoomNameFormatter, getRoomSockets, getRuntimeApiMaps, getRuntimeSyncMaps, getServerDir, getServerFunctionDirs, getServerFunctionsDir, getSharedDir, getSocketMiddlewares, getSrcDir, getUploadsDir, hasCookie, hashSynchronizedValue, hashSynchronizedValueWith, inferHttpMethod, initConsolelog, initSharedSentry, isDbHealthCheckRegistered, isEmailSenderRegistered, isFalsy, isGetMethodName, isLocalizedNormalizerRegistered, isLoopbackIp, isMethodAllowed, isPrismaClientRegistered, isPrismaClientResolvable, isProduction, isProductionRuntime, isRedactedLogKey, isRedisClientRegistered, isRuntimeMapsProviderRegistered, isSessionProviderRegistered, isTestDirectory, isTestFile, isTestRuntime, issueOneTimeToken, listEmailSenderNames, loadEnvFiles, loadPeer, normalizeErrorResponse, normalizeOrigin, oneTimeTokenKey, performLogout, prisma, processUpload, readBootUuid, readSession, rebuildDefaultRedisClient, recordMetricAcrossTrackers, redis, refreshBootUuid, registerApiAbortController, registerAvatarConfig, registerBindAddress, registerBoundAddress, registerCsrfConfig, registerDbHealthCheck, registerEmailSender, registerEmailSenders, registerErrorFormatter, registerErrorTracker, registerErrorTrackers, registerHook, registerLocaleReloader, registerLocalizedNormalizer, registerPreCaptureFilter, registerPrismaClient, registerRateLimitStrategy, registerRedactedLogKeys, registerRedisClient, registerRedisKeyFormatter, registerRoomNameFormatter, registerRuntimeMapsProvider, registerSessionProvider, registerSocketMiddleware, registerStrayPrefixCommand, registerSyncAbortController, registerSyncHook, releaseLease, removeSession, renewLease, resetClientsForTests, resetCsrfConfigForTests, resetDbHealthCheckForTests, resetDefaultRedisClient, resetRedactedLogKeysForTests, resetRedisKeyFormatterForTests, resetSessionProviderForTests, resolveClientIp, resolveDevCallbackUrl, resolveEnvKey, resolveErrorMessage, resolveFromRoot, resolveHealthHashConfig, resolveHealthHashConfigFromDescriptor, resolveRuntimeMode, resolveSocketAdapterKey, runWithErrorTrackerIdentity, runWithErrorTrackerIdentityScope, sanitizeErrorString, sanitizeErrorStrings, sanitizeForLog, serveAvatar, setCurrentErrorTrackerIdentity, setErrorTrackerUser, setIoInstance, setSentryUser, startBootUuidHeartbeat, startSpan, startSpanAcrossTrackers, startSpanHandle, type syncMessage, tryCatch, unregisterApiAbortController, unregisterSyncAbortController, validateInputByType, validateRequest, writeBootUuid, writeSession };
|