@starci/hfs 5.0.8 → 5.1.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 +10 -0
- package/cache/import-graph.mjs +6 -61
- package/cache/lint-family.d.mts +10 -1
- package/cache/lint-family.mjs +12 -5
- package/codemod/directive-first.d.mts +48 -0
- package/codemod/directive-first.mjs +136 -0
- package/codemod/explicit-surface-edit.d.mts +14 -0
- package/codemod/explicit-surface-edit.mjs +57 -0
- package/codemod/explicit-surface-names.d.mts +30 -0
- package/codemod/explicit-surface-names.mjs +135 -0
- package/codemod/explicit-surface-render.d.mts +29 -0
- package/codemod/explicit-surface-render.mjs +197 -0
- package/codemod/explicit-surface.d.mts +50 -0
- package/codemod/explicit-surface.mjs +374 -0
- package/codemod/main.d.mts +7 -5
- package/codemod/main.mjs +37 -16
- package/codemod/source-tree.d.mts +8 -0
- package/codemod/source-tree.mjs +23 -0
- package/lint/eslint-list.cjs +29 -0
- package/lint/run.d.mts +19 -5
- package/lint/run.mjs +47 -14
- package/package.json +1 -1
- package/runtime/knowledge/hfs/canon-pins.yaml +7 -7
- package/runtime/knowledge/hfs/rules.yaml +52 -1
- package/runtime/scripts/hfs/architecture/test-world-files.d.mts +4 -3
- package/runtime/scripts/hfs/architecture/test-world-files.mjs +26 -7
- package/runtime/scripts/hfs/explicit-surface-rule.d.mts +58 -0
- package/runtime/scripts/hfs/explicit-surface-rule.mjs +59 -0
- package/runtime/scripts/lib/declaration-surface.d.mts +35 -0
- package/runtime/scripts/lib/declaration-surface.mjs +112 -0
- package/templates/be/inbox/delivery-recorded.event.ts.tpl +2 -2
- package/templates/be/inbox/entities.index.ts.tpl +1 -1
- package/templates/be/inbox/module-definition.ts.tpl +8 -2
- package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +10 -2
- package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +8 -2
- package/templates/be/patterns/api/feature.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/api/graphql.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/cli/group.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/event-bus/platform/persistence/connection.ts.tpl +3 -2
- package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +4 -3
- package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/idempotency/platform/inbox/persistence/connection.ts.tpl +3 -2
- package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.sql.ts.tpl +7 -6
- package/templates/be/patterns/jobs/job.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/jobs/job.processor.ts.tpl +1 -1
- package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/jobs/platform/persistence.connection.ts.tpl +3 -2
- package/templates/be/patterns/jobs/platform/persistence.jobs.sql.ts.tpl +5 -4
- package/templates/be/patterns/projections/persistence.connection.ts.tpl +3 -2
- package/templates/be/patterns/projections/projection.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/queues/platform/persistence.connection.ts.tpl +3 -2
- package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +4 -3
- package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/queues/queue.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/reactors/consumer.ts.tpl +1 -1
- package/templates/be/patterns/reactors/message.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/realtime/channel.sse.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/saga/saga.done.consumer.ts.tpl +1 -1
- package/templates/be/patterns/saga/saga.failed.consumer.ts.tpl +1 -1
- package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/saga/saga.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +9 -3
- package/templates/be/platform/graphql/graphql.module-definition.ts +8 -3
- package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +9 -3
- package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +9 -3
- package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +9 -3
- package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +9 -3
- package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +9 -3
- package/templates/be/skeleton/src/modules/domain/identity/identity.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/domain/liveness/liveness.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/domain/note/persistence/connection.ts +3 -2
- package/templates/be/skeleton/src/modules/domain/note/persistence/note.sql.ts +2 -1
- package/templates/be/skeleton/src/modules/platform/clock/clock.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/database/index.ts +1 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.module-definition.ts +10 -4
- package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +8 -4
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.module-definition.ts +10 -4
- package/templates/be/skeleton/src/modules/platform/http-security/persistence/connection.ts +3 -2
- package/templates/be/skeleton/src/modules/platform/http-security/persistence/rate-limit.sql.ts +3 -2
- package/templates/be/skeleton/src/modules/platform/i18n/i18n.module-definition.ts +10 -4
- package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/logging/logging.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +8 -3
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts +5 -1
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts +5 -1
- package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/http-security/persistence/entities/index.ts +1 -1
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +7 -1
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +7 -1
- package/templates/be/table/api/feature.module-definition.ts.tpl +9 -3
- package/templates/be/table/api/graphql.module-definition.ts.tpl +9 -3
- package/templates/be/table/entities.index.ts.tpl +1 -1
- package/templates/be/table/module-definition.ts.tpl +8 -3
- package/templates/be/test-world/fixtures/migrations.sql.ts.tpl +1 -0
- package/templates/be/test-world/world/test-world.config.ts.tpl +8 -1
- package/templates/fe/common/app-layout.tsx.partial +1 -1
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +2 -1
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignIn.ts +11 -3
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +8 -2
- package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +2 -1
- package/templates/fe/skeleton/apps/__app__/src/modules/toast/hooks.ts +1 -1
- package/templates/fe/skeleton/apps/__app__/src/modules/toast/toast.tsx +4 -1
- package/templates/fe/skeleton/apps/__app__/src/redux/hooks.ts +3 -2
- package/templates/fe/skeleton/apps/__app__/src/redux/index.ts +1 -0
- package/templates/fe/skeleton/apps/__app__/src/redux/slices/session.ts +12 -4
- package/templates/fe/skeleton/apps/__app__/src/redux/store.ts +8 -3
- package/templates/fe/skeleton/apps/app/src/app/[locale]/layout.tsx +4 -3
- package/templates/fe/skeleton/apps/app/src/features/layouts/AppLayout/index.tsx +1 -1
- package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/app/src/features/pages/AppLoadingPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/app/src/features/pages/AppNotFoundPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/app/src/modules/i18n/index.ts +2 -1
- package/templates/fe/skeleton/apps/app/src/modules/i18n/request.ts +4 -1
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/layout.tsx +4 -3
- package/templates/fe/skeleton/apps/landing/src/features/layouts/LandingLayout/index.tsx +2 -2
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingLoadingPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingNotFoundPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/index.ts +2 -1
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/request.ts +4 -1
- package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +19 -1
- package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +1 -1
- package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +5 -1
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +4 -3
- package/templates/fe/skeleton-lite/apps/web/src/features/layouts/AppLayout/index.tsx +2 -2
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +2 -1
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppLoadingPage/index.tsx +2 -1
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppNotFoundPage/index.tsx +2 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +3 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +5 -1
- package/templates/fe/table/graphql/useMutateSwr.ts.tpl +2 -1
- package/templates/fe/table/graphql/useQuerySwr.ts.tpl +2 -1
- package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
- package/templates/fe/transport/graphql/clients/clients/no-auth.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/attach-access-token.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/error.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/http.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/locale.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/refresh-token.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/retry.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/timeout.ts +1 -1
- package/templates/fe/units/page/index.tsx.tpl +3 -2
- package/templates/fe/units/slot/SlotView.tsx.tpl +1 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { TypeScript } from '../lib/ts-ast.mjs';
|
|
2
|
+
/** The part of an ESLint rule context the rule reads (typescript-eslint's context satisfies it). */
|
|
3
|
+
export interface SurfaceContext {
|
|
4
|
+
readonly filename: string;
|
|
5
|
+
readonly sourceCode: {
|
|
6
|
+
readonly text: string;
|
|
7
|
+
getLocFromIndex(index: number): {
|
|
8
|
+
line: number;
|
|
9
|
+
column: number;
|
|
10
|
+
};
|
|
11
|
+
readonly parserServices?: {
|
|
12
|
+
readonly program?: {
|
|
13
|
+
getCompilerOptions(): {
|
|
14
|
+
module?: number;
|
|
15
|
+
};
|
|
16
|
+
} | null;
|
|
17
|
+
} | undefined;
|
|
18
|
+
};
|
|
19
|
+
report(descriptor: {
|
|
20
|
+
loc: {
|
|
21
|
+
start: {
|
|
22
|
+
line: number;
|
|
23
|
+
column: number;
|
|
24
|
+
};
|
|
25
|
+
end: {
|
|
26
|
+
line: number;
|
|
27
|
+
column: number;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
messageId: string;
|
|
31
|
+
data: Record<string, string>;
|
|
32
|
+
}): void;
|
|
33
|
+
}
|
|
34
|
+
/** The rule module the factory returns: a plain ESLint rule with one message. */
|
|
35
|
+
export interface SurfaceRule {
|
|
36
|
+
meta: {
|
|
37
|
+
type: 'problem';
|
|
38
|
+
docs: {
|
|
39
|
+
description: string;
|
|
40
|
+
};
|
|
41
|
+
schema: [];
|
|
42
|
+
messages: {
|
|
43
|
+
inferred: string;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
create(context: SurfaceContext): {
|
|
47
|
+
Program(): void;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The rule. `ts` is the canon's own TypeScript (its peer dependency); the side's `module` option comes from the typed program when the file is linted with one.
|
|
52
|
+
*
|
|
53
|
+
* @param input - `ts` is the compiler.
|
|
54
|
+
* @returns An ESLint rule reporting each export of a TypeScript source whose type is inferred.
|
|
55
|
+
*/
|
|
56
|
+
export declare const explicitSurfaceRule: ({ ts }: {
|
|
57
|
+
ts: TypeScript;
|
|
58
|
+
}) => SurfaceRule;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { declarationSurface, isEmittableSource } from '../lib/declaration-surface.mjs';
|
|
2
|
+
/** What to write, by what the classifier says to annotate. */
|
|
3
|
+
const HINTS = {
|
|
4
|
+
'return type': 'Write the return type after the parameter list: `(): Result =>` or `function name(): Result`.',
|
|
5
|
+
'variable type': 'Give the variable a type: `export const name: Result = ...`.',
|
|
6
|
+
'parameter type': 'Give the parameter a type: `(value: Input)`.',
|
|
7
|
+
'property type': 'Give the property a type: `name: Result = ...`.',
|
|
8
|
+
'accessor type': 'Give the accessor a type: `get name(): Result` (a setter takes it from its parameter).',
|
|
9
|
+
'destructured export': 'A destructuring pattern cannot carry a type per name: export each name as its own variable with a type, `export const name: Type = source.name`.',
|
|
10
|
+
'exported value': 'Declare the type (an interface or a type alias) and annotate the export with it: `export const name: Shape = ...`.',
|
|
11
|
+
};
|
|
12
|
+
/** The `--isolatedDeclarations` flag name TypeScript ends its reasons with says nothing to the reader. */
|
|
13
|
+
const plain = (reason) => reason.replace(/ with --isolatedDeclarations\.?/, '').replace(/\.$/, '');
|
|
14
|
+
/**
|
|
15
|
+
* The rule. `ts` is the canon's own TypeScript (its peer dependency); the side's `module` option comes from the typed program when the file is linted with one.
|
|
16
|
+
*
|
|
17
|
+
* @param input - `ts` is the compiler.
|
|
18
|
+
* @returns An ESLint rule reporting each export of a TypeScript source whose type is inferred.
|
|
19
|
+
*/
|
|
20
|
+
export const explicitSurfaceRule = ({ ts }) => ({
|
|
21
|
+
meta: {
|
|
22
|
+
type: 'problem',
|
|
23
|
+
docs: { description: 'An exported declaration states its type: the return type, variable type or parameter type is written, not inferred from a body or from other files.' },
|
|
24
|
+
schema: [],
|
|
25
|
+
messages: {
|
|
26
|
+
inferred: 'HFS_EXPLICIT_EXPORT_SURFACE: `{{name}}` is exported with an inferred type ({{reason}}), so importers cannot read it from this file and every one of them is linted again whenever the file changes. Annotate the {{annotate}}. {{hint}}',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
create(context) {
|
|
30
|
+
return {
|
|
31
|
+
Program() {
|
|
32
|
+
const file = context.filename;
|
|
33
|
+
if (!isEmittableSource(file))
|
|
34
|
+
return;
|
|
35
|
+
const { sourceCode } = context;
|
|
36
|
+
const module = sourceCode.parserServices?.program?.getCompilerOptions().module;
|
|
37
|
+
const whole = { start: { line: 1, column: 0 }, end: { line: 1, column: 0 } };
|
|
38
|
+
let surface;
|
|
39
|
+
try {
|
|
40
|
+
surface = declarationSurface(ts, file, sourceCode.text, module);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
surface = null;
|
|
44
|
+
}
|
|
45
|
+
if (surface === null) {
|
|
46
|
+
context.report({ loc: whole, messageId: 'inferred', data: { name: 'this file', reason: 'the declaration emit could not read it', annotate: 'exported value', hint: HINTS['exported value'] } });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
for (const problem of surface.problems) {
|
|
50
|
+
context.report({
|
|
51
|
+
loc: { start: sourceCode.getLocFromIndex(problem.start), end: sourceCode.getLocFromIndex(problem.start + problem.length) },
|
|
52
|
+
messageId: 'inferred',
|
|
53
|
+
data: { name: problem.name, reason: plain(problem.reason), annotate: problem.annotate, hint: HINTS[problem.annotate] },
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { TypeScript } from './ts-ast.mjs';
|
|
2
|
+
/** What the declaration emit cannot write without reading other files, and where: one export (or one part of it) the author must annotate. */
|
|
3
|
+
export interface SurfaceProblem {
|
|
4
|
+
/** The TypeScript diagnostic code (9007: function without a return type, 9010: variable without a type, ...). */
|
|
5
|
+
code: number;
|
|
6
|
+
/** The offset and length of the offending span in the file's text. */
|
|
7
|
+
start: number;
|
|
8
|
+
length: number;
|
|
9
|
+
/** The name of the export (or member) the span sits in, or `default export`. */
|
|
10
|
+
name: string;
|
|
11
|
+
/** What to annotate: `return type`, `variable type`, `parameter type`, `property type`, `accessor type`, `destructured export` or `exported value`. */
|
|
12
|
+
annotate: string;
|
|
13
|
+
/** TypeScript's own reason, one sentence. */
|
|
14
|
+
reason: string;
|
|
15
|
+
}
|
|
16
|
+
/** The declaration emit of one file: its text (the digest of the type surface) and the exports it could not write explicitly. */
|
|
17
|
+
export interface DeclarationSurface {
|
|
18
|
+
outputText: string;
|
|
19
|
+
problems: SurfaceProblem[];
|
|
20
|
+
}
|
|
21
|
+
/** A script file (`.js`, `.mjs`, `.cjs`, `.jsx`): it has no declaration emit and stands for its content. */
|
|
22
|
+
export declare const SCRIPT: RegExp;
|
|
23
|
+
/** A declaration file: it is its own surface. */
|
|
24
|
+
export declare const DECLARATION: RegExp;
|
|
25
|
+
/** A TypeScript source file whose surface the declaration emit writes. */
|
|
26
|
+
export declare const TYPESCRIPT: RegExp;
|
|
27
|
+
/** Whether `file` is a TypeScript source (not a declaration file) the declaration emit reads. */
|
|
28
|
+
export declare const isEmittableSource: (file: string) => boolean;
|
|
29
|
+
/**
|
|
30
|
+
* The declaration emit of one TypeScript source and the exports it could not write explicitly (a type inferred from bodies and other files), or null
|
|
31
|
+
* when the compiler has no `transpileDeclaration` (before TypeScript 5.5). A declaration whose only diagnostics are components returning JSX has the
|
|
32
|
+
* surface its emit shows. `module` is the side's `module` compiler option, which the emit reads. A throw from the compiler propagates: the caller
|
|
33
|
+
* decides what an unreadable file means.
|
|
34
|
+
*/
|
|
35
|
+
export declare function declarationSurface(ts: TypeScript, file: string, text: string, module?: number): DeclarationSurface | null;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/** A script file (`.js`, `.mjs`, `.cjs`, `.jsx`): it has no declaration emit and stands for its content. */
|
|
2
|
+
export const SCRIPT = /\.(?:[cm]?jsx?)$/;
|
|
3
|
+
/** A declaration file: it is its own surface. */
|
|
4
|
+
export const DECLARATION = /\.d\.[cm]?ts$/;
|
|
5
|
+
/** A TypeScript source file whose surface the declaration emit writes. */
|
|
6
|
+
export const TYPESCRIPT = /\.(?:[cm]?tsx?)$/;
|
|
7
|
+
/** Whether `file` is a TypeScript source (not a declaration file) the declaration emit reads. */
|
|
8
|
+
export const isEmittableSource = (file) => TYPESCRIPT.test(file) && !DECLARATION.test(file);
|
|
9
|
+
/** Whether `expression` is a JSX element, fragment, `null` or `undefined`, or a conditional of those: its type does not depend on any other file. */
|
|
10
|
+
function isJsxValue(ts, expression) {
|
|
11
|
+
if (expression === undefined)
|
|
12
|
+
return false;
|
|
13
|
+
let node = expression;
|
|
14
|
+
while (ts.isParenthesizedExpression(node))
|
|
15
|
+
node = node.expression;
|
|
16
|
+
if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node) || ts.isJsxFragment(node) || node.kind === ts.SyntaxKind.NullKeyword)
|
|
17
|
+
return true;
|
|
18
|
+
if (ts.isIdentifier(node))
|
|
19
|
+
return node.text === 'undefined';
|
|
20
|
+
return ts.isConditionalExpression(node) && isJsxValue(ts, node.whenTrue) && isJsxValue(ts, node.whenFalse);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Whether the function (or method) the declaration diagnostic at `start` points at has the type `JSX.Element | null | undefined` whatever its body
|
|
24
|
+
* reads: not async, not a generator, and every `return` of its own (and its concise body) a JSX value. A React component written without a return
|
|
25
|
+
* type is the common case; its inferred type is one the JSX namespace of the installed packages gives, which a lockfile change dirties as a whole.
|
|
26
|
+
*/
|
|
27
|
+
function returnsOnlyJsx(ts, source, start) {
|
|
28
|
+
let found;
|
|
29
|
+
const find = (node) => {
|
|
30
|
+
if (found !== undefined || start < node.getFullStart() || start >= node.end)
|
|
31
|
+
return;
|
|
32
|
+
const named = (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) && node.name !== undefined && node.name.getStart(source) === start;
|
|
33
|
+
if (named || ((ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && node.getStart(source) === start)) {
|
|
34
|
+
found = node;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
ts.forEachChild(node, find);
|
|
38
|
+
};
|
|
39
|
+
find(source);
|
|
40
|
+
const fn = found;
|
|
41
|
+
if (fn === undefined || fn.type !== undefined || fn.body === undefined)
|
|
42
|
+
return false;
|
|
43
|
+
if (fn.asteriskToken !== undefined || (ts.getCombinedModifierFlags(fn) & ts.ModifierFlags.Async) !== 0)
|
|
44
|
+
return false;
|
|
45
|
+
if (!ts.isBlock(fn.body))
|
|
46
|
+
return isJsxValue(ts, fn.body);
|
|
47
|
+
let only = true;
|
|
48
|
+
const visit = (node) => {
|
|
49
|
+
if (!only || ts.isFunctionLike(node) || ts.isClassLike(node))
|
|
50
|
+
return;
|
|
51
|
+
if (ts.isReturnStatement(node) && node.expression !== undefined && !isJsxValue(ts, node.expression))
|
|
52
|
+
only = false;
|
|
53
|
+
ts.forEachChild(node, visit);
|
|
54
|
+
};
|
|
55
|
+
ts.forEachChild(fn.body, visit);
|
|
56
|
+
return only;
|
|
57
|
+
}
|
|
58
|
+
/** What to annotate for each isolated-declaration diagnostic code; any other code asks for the exported value's type. */
|
|
59
|
+
const ANNOTATE = new Map([
|
|
60
|
+
[9007, 'return type'], [9008, 'return type'], [9009, 'accessor type'], [9010, 'variable type'], [9011, 'parameter type'], [9012, 'property type'],
|
|
61
|
+
[9019, 'destructured export'], [9025, 'parameter type'],
|
|
62
|
+
]);
|
|
63
|
+
/** The declaration kinds whose name labels the export a diagnostic sits in, innermost first. */
|
|
64
|
+
function nameOf(ts, source, start) {
|
|
65
|
+
let innermost = source;
|
|
66
|
+
const descend = (node) => {
|
|
67
|
+
if (start < node.getFullStart() || start >= node.end)
|
|
68
|
+
return;
|
|
69
|
+
innermost = node;
|
|
70
|
+
ts.forEachChild(node, descend);
|
|
71
|
+
};
|
|
72
|
+
ts.forEachChild(source, descend);
|
|
73
|
+
for (let node = innermost; node !== undefined && node !== source; node = node.parent) {
|
|
74
|
+
if (ts.isExportAssignment(node))
|
|
75
|
+
return 'default export';
|
|
76
|
+
if (ts.isBindingElement(node) && ts.isIdentifier(node.name))
|
|
77
|
+
return node.name.text;
|
|
78
|
+
if ((ts.isVariableDeclaration(node) || ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isMethodDeclaration(node) || ts.isPropertyDeclaration(node) || ts.isPropertyAssignment(node) || ts.isGetAccessorDeclaration(node)) && node.name !== undefined && ts.isIdentifier(node.name))
|
|
79
|
+
return node.name.text;
|
|
80
|
+
if (ts.isFunctionDeclaration(node) && (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Default) !== 0)
|
|
81
|
+
return 'default export';
|
|
82
|
+
}
|
|
83
|
+
return 'default export';
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The declaration emit of one TypeScript source and the exports it could not write explicitly (a type inferred from bodies and other files), or null
|
|
87
|
+
* when the compiler has no `transpileDeclaration` (before TypeScript 5.5). A declaration whose only diagnostics are components returning JSX has the
|
|
88
|
+
* surface its emit shows. `module` is the side's `module` compiler option, which the emit reads. A throw from the compiler propagates: the caller
|
|
89
|
+
* decides what an unreadable file means.
|
|
90
|
+
*/
|
|
91
|
+
export function declarationSurface(ts, file, text, module) {
|
|
92
|
+
const emit = ts.transpileDeclaration;
|
|
93
|
+
if (emit === undefined)
|
|
94
|
+
return null;
|
|
95
|
+
const declared = emit.call(ts, text, { compilerOptions: { jsx: ts.JsxEmit.Preserve, target: ts.ScriptTarget.Latest, ...(module === undefined ? {} : { module }) }, fileName: file, reportDiagnostics: true });
|
|
96
|
+
const diagnostics = (declared.diagnostics ?? []);
|
|
97
|
+
if (diagnostics.length === 0)
|
|
98
|
+
return { outputText: declared.outputText, problems: [] };
|
|
99
|
+
const source = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, file.endsWith('x') ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
100
|
+
const problems = [];
|
|
101
|
+
for (const diagnostic of diagnostics) {
|
|
102
|
+
const exempt = (diagnostic.code === 9007 || diagnostic.code === 9008) && diagnostic.start !== undefined && returnsOnlyJsx(ts, source, diagnostic.start);
|
|
103
|
+
if (exempt)
|
|
104
|
+
continue;
|
|
105
|
+
const start = diagnostic.start ?? 0;
|
|
106
|
+
problems.push({
|
|
107
|
+
code: diagnostic.code, start, length: diagnostic.length ?? 0, name: nameOf(ts, source, start), annotate: ANNOTATE.get(diagnostic.code) ?? 'exported value',
|
|
108
|
+
reason: ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n').split('\n')[0] ?? '',
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return { outputText: declared.outputText, problems };
|
|
112
|
+
}
|
|
@@ -10,10 +10,10 @@ export interface @@Provider@@DeliveryRecordedPayload {
|
|
|
10
10
|
|
|
11
11
|
/** Version 1 of `@@provider@@.delivery-recorded`, published once by the provider inbox intake. */
|
|
12
12
|
export class @@Provider@@DeliveryRecordedEvent extends BaseEvent {
|
|
13
|
-
static readonly eventName = "@@provider@@.delivery-recorded"
|
|
13
|
+
static readonly eventName: string = "@@provider@@.delivery-recorded"
|
|
14
14
|
static readonly version = 1
|
|
15
15
|
|
|
16
|
-
readonly eventName = @@Provider@@DeliveryRecordedEvent.eventName
|
|
16
|
+
readonly eventName: string = @@Provider@@DeliveryRecordedEvent.eventName
|
|
17
17
|
|
|
18
18
|
private constructor(
|
|
19
19
|
readonly eventId: string,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { @@Provider@@InboxEntity } from "./@@provider@@-inbox.entity"
|
|
2
2
|
|
|
3
3
|
/** The entities of the @@provider@@-inbox capability, named in PRIMARY_ENTITIES of platform/database/entities.ts. */
|
|
4
|
-
export const @@providerCamel@@InboxEntities = [@@Provider@@InboxEntity]
|
|
4
|
+
export const @@providerCamel@@InboxEntities: ReadonlyArray<typeof @@Provider@@InboxEntity> = [@@Provider@@InboxEntity]
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { ConfigurableModuleBuilder } from "@nestjs/common"
|
|
1
|
+
import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
|
|
2
2
|
import type { @@Provider@@InboxOptions } from "./@@provider@@-inbox.options"
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const built = new ConfigurableModuleBuilder<@@Provider@@InboxOptions>()
|
|
5
5
|
.setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
|
|
6
6
|
.build()
|
|
7
|
+
type Host = ConfigurableModuleHost<@@Provider@@InboxOptions, "register", "create", { isGlobal: boolean }>
|
|
8
|
+
|
|
9
|
+
/** The configurable-module class the module extends. */
|
|
10
|
+
export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
|
|
11
|
+
/** The options of a synchronous registration, for the register signature. */
|
|
12
|
+
export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import { ConfigurableModuleBuilder } from "@nestjs/common"
|
|
1
|
+
import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
|
|
2
2
|
import type { EventBusOptions } from "./event-bus.options"
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const built = new ConfigurableModuleBuilder<EventBusOptions>()
|
|
5
5
|
.setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
|
|
6
6
|
.build()
|
|
7
|
+
type Host = ConfigurableModuleHost<EventBusOptions, "register", "create", { isGlobal: boolean }>
|
|
8
|
+
|
|
9
|
+
/** The configurable-module class the module extends. */
|
|
10
|
+
export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
|
|
11
|
+
/** The injection token of the registered options. */
|
|
12
|
+
export const MODULE_OPTIONS_TOKEN: Host["MODULE_OPTIONS_TOKEN"] = built.MODULE_OPTIONS_TOKEN
|
|
13
|
+
/** The options of a synchronous registration, for the register signature. */
|
|
14
|
+
export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { ConfigurableModuleBuilder } from "@nestjs/common"
|
|
1
|
+
import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
|
|
2
2
|
import type { InboxOptions } from "./inbox.options"
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const built = new ConfigurableModuleBuilder<InboxOptions>()
|
|
5
5
|
.setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
|
|
6
6
|
.build()
|
|
7
|
+
type Host = ConfigurableModuleHost<InboxOptions, "register", "create", { isGlobal: boolean }>
|
|
8
|
+
|
|
9
|
+
/** The configurable-module class the module extends. */
|
|
10
|
+
export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
|
|
11
|
+
/** The options of a synchronous registration, for the register signature. */
|
|
12
|
+
export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { ConfigurableModuleBuilder } from "@nestjs/common"
|
|
1
|
+
import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
|
|
2
2
|
import type { @@Feature@@ApiOptions } from "./@@feature@@.options"
|
|
3
3
|
|
|
4
|
+
const built = new ConfigurableModuleBuilder<@@Feature@@ApiOptions>().setClassMethodName("register").build()
|
|
5
|
+
type Host = ConfigurableModuleHost<@@Feature@@ApiOptions, "register", "create", {}>
|
|
6
|
+
|
|
4
7
|
/** The configurable-module base of the @@feature@@ module. */
|
|
5
|
-
export const
|
|
6
|
-
|
|
8
|
+
export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
|
|
9
|
+
/** The injection token of the registered options. */
|
|
10
|
+
export const MODULE_OPTIONS_TOKEN: Host["MODULE_OPTIONS_TOKEN"] = built.MODULE_OPTIONS_TOKEN
|
|
11
|
+
/** The options of a synchronous registration, for the register signature. */
|
|
12
|
+
export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { ConfigurableModuleBuilder } from "@nestjs/common"
|
|
1
|
+
import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
|
|
2
2
|
import type { @@Feature@@GraphqlOptions } from "./@@feature@@-graphql.options"
|
|
3
3
|
|
|
4
|
+
const built = new ConfigurableModuleBuilder<@@Feature@@GraphqlOptions>().setClassMethodName("register").build()
|
|
5
|
+
type Host = ConfigurableModuleHost<@@Feature@@GraphqlOptions, "register", "create", {}>
|
|
6
|
+
|
|
4
7
|
/** The configurable-module base of the @@feature@@-graphql module. */
|
|
5
|
-
export const
|
|
6
|
-
|
|
8
|
+
export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
|
|
9
|
+
/** The injection token of the registered options. */
|
|
10
|
+
export const MODULE_OPTIONS_TOKEN: Host["MODULE_OPTIONS_TOKEN"] = built.MODULE_OPTIONS_TOKEN
|
|
11
|
+
/** The options of a synchronous registration, for the register signature. */
|
|
12
|
+
export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { ConfigurableModuleBuilder } from "@nestjs/common"
|
|
1
|
+
import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
|
|
2
2
|
import type { @@Group@@Options } from "./@@group@@.options"
|
|
3
3
|
|
|
4
|
+
const built = new ConfigurableModuleBuilder<@@Group@@Options>().setClassMethodName("register").build()
|
|
5
|
+
type Host = ConfigurableModuleHost<@@Group@@Options, "register", "create", {}>
|
|
6
|
+
|
|
4
7
|
/** The configurable-module base of the @@group@@ module. */
|
|
5
|
-
export const
|
|
6
|
-
|
|
8
|
+
export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
|
|
9
|
+
/** The injection token of the registered options. */
|
|
10
|
+
export const MODULE_OPTIONS_TOKEN: Host["MODULE_OPTIONS_TOKEN"] = built.MODULE_OPTIONS_TOKEN
|
|
11
|
+
/** The options of a synchronous registration, for the register signature. */
|
|
12
|
+
export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { ConfigurableModuleBuilder } from "@nestjs/common"
|
|
1
|
+
import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
|
|
2
2
|
import { EVENT_BUS_OPTIONS } from "./event-bus.decorators"
|
|
3
3
|
import type { EventBusOptions } from "./event-bus.options"
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
export const { ConfigurableModuleClass, OPTIONS_TYPE } = new ConfigurableModuleBuilder<EventBusOptions>({
|
|
5
|
+
const built = new ConfigurableModuleBuilder<EventBusOptions>({
|
|
7
6
|
optionsInjectionToken: EVENT_BUS_OPTIONS,
|
|
8
7
|
})
|
|
9
8
|
.setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
|
|
10
9
|
.build()
|
|
10
|
+
type Host = ConfigurableModuleHost<EventBusOptions, "register", "create", { isGlobal: boolean }>
|
|
11
|
+
|
|
12
|
+
/** The configurable-module base of the event bus; `isGlobal` is decided by the app root. */
|
|
13
|
+
export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
|
|
14
|
+
/** The options of a synchronous registration, for the register signature. */
|
|
15
|
+
export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import type { DatabaseConnectionOptions } from "@modules/platform/database"
|
|
1
2
|
import { EventOutboxEntity } from "./entities/event-outbox.entity"
|
|
2
3
|
import { CreateEventOutbox@@epochMs13@@ } from "./migrations/@@epochMs13@@-create-event-outbox"
|
|
3
4
|
|
|
4
5
|
/** The entities of the event bus, for every connection that publishes events. */
|
|
5
|
-
export const eventBusEntities = [EventOutboxEntity]
|
|
6
|
+
export const eventBusEntities: DatabaseConnectionOptions["entities"] = [EventOutboxEntity]
|
|
6
7
|
|
|
7
8
|
/** The migrations of the event bus, in the order they run. */
|
|
8
|
-
export const eventBusMigrations = [CreateEventOutbox@@epochMs13@@]
|
|
9
|
+
export const eventBusMigrations: DatabaseConnectionOptions["migrations"] = [CreateEventOutbox@@epochMs13@@]
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { sql } from "@modules/platform/database"
|
|
2
|
+
import type { SqlText } from "@modules/platform/database"
|
|
2
3
|
|
|
3
4
|
/** Writes one event in the outbox ($1 event id, $2 event name, $3 topic, $4 key, $5 envelope, $6 instant, $7 correlation id). */
|
|
4
|
-
export const INSERT_OUTBOX_ROW = sql`INSERT INTO event_outbox (event_id, event_name, topic, message_key, envelope, created_at, correlation_id)
|
|
5
|
+
export const INSERT_OUTBOX_ROW: SqlText = sql`INSERT INTO event_outbox (event_id, event_name, topic, message_key, envelope, created_at, correlation_id)
|
|
5
6
|
VALUES ($1, $2, $3, $4, $5, $6, $7)`
|
|
6
7
|
|
|
7
8
|
/** Locks and reads the oldest rows that wait ($1 batch size); a second relay skips the rows this one holds. */
|
|
8
|
-
export const SELECT_WAITING_ROWS = sql`SELECT id, topic, message_key, envelope, correlation_id FROM event_outbox
|
|
9
|
+
export const SELECT_WAITING_ROWS: SqlText = sql`SELECT id, topic, message_key, envelope, correlation_id FROM event_outbox
|
|
9
10
|
WHERE sent_at IS NULL ORDER BY id LIMIT $1 FOR UPDATE SKIP LOCKED`
|
|
10
11
|
|
|
11
12
|
/** Marks the rows as handed to the broker ($1 ids, $2 instant). */
|
|
12
|
-
export const MARK_ROWS_SENT = sql`UPDATE event_outbox SET sent_at = $2 WHERE id = ANY($1::bigint[])`
|
|
13
|
+
export const MARK_ROWS_SENT: SqlText = sql`UPDATE event_outbox SET sent_at = $2 WHERE id = ANY($1::bigint[])`
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import { ConfigurableModuleBuilder } from "@nestjs/common"
|
|
1
|
+
import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
|
|
2
2
|
import type { InboxOptions } from "./inbox.options"
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
export const { ConfigurableModuleClass, OPTIONS_TYPE } = new ConfigurableModuleBuilder<InboxOptions>()
|
|
4
|
+
const built = new ConfigurableModuleBuilder<InboxOptions>()
|
|
6
5
|
.setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
|
|
7
6
|
.build()
|
|
7
|
+
type Host = ConfigurableModuleHost<InboxOptions, "register", "create", { isGlobal: boolean }>
|
|
8
|
+
|
|
9
|
+
/** The configurable-module base of the inbox capability; `isGlobal` is decided by the app root. */
|
|
10
|
+
export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
|
|
11
|
+
/** The options of a synchronous registration, for the register signature. */
|
|
12
|
+
export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import type { DatabaseConnectionOptions } from "@modules/platform/database"
|
|
1
2
|
import { InboxClaimEntity } from "./entities/inbox-claim.entity"
|
|
2
3
|
import { CreateInboxClaims1789800005000 } from "./migrations/1789800005000-create-inbox-claims"
|
|
3
4
|
import { ClientOperationOutcomes1792800000000 } from "./migrations/1792800000000-client-operation-outcomes"
|
|
4
5
|
|
|
5
6
|
/** The entities of the inbox capability, for the connection that holds them. */
|
|
6
|
-
export const inboxEntities = [InboxClaimEntity]
|
|
7
|
+
export const inboxEntities: DatabaseConnectionOptions["entities"] = [InboxClaimEntity]
|
|
7
8
|
|
|
8
9
|
/** The migrations of the inbox capability, in the order they run. */
|
|
9
|
-
export const inboxMigrations = [CreateInboxClaims1789800005000, ClientOperationOutcomes1792800000000]
|
|
10
|
+
export const inboxMigrations: DatabaseConnectionOptions["migrations"] = [CreateInboxClaims1789800005000, ClientOperationOutcomes1792800000000]
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { sql } from "@modules/platform/database"
|
|
2
|
+
import type { SqlText } from "@modules/platform/database"
|
|
2
3
|
|
|
3
4
|
/** Claims the event ($1 source, $2 event id, $3 instant); answers one row when this call won and none when it was claimed before. */
|
|
4
|
-
export const CLAIM_EVENT = sql`INSERT INTO inbox_claims (source, event_id, claimed_at) VALUES ($1, $2, $3)
|
|
5
|
+
export const CLAIM_EVENT: SqlText = sql`INSERT INTO inbox_claims (source, event_id, claimed_at) VALUES ($1, $2, $3)
|
|
5
6
|
ON CONFLICT (source, event_id) DO NOTHING RETURNING event_id`
|
|
6
7
|
|
|
7
8
|
/** Gives a delivery claim back; client outcomes can expire only through the retention API. */
|
|
8
|
-
export const RELEASE_EVENT = sql`DELETE FROM inbox_claims WHERE source = $1 AND event_id = $2 AND request_fingerprint IS NULL`
|
|
9
|
+
export const RELEASE_EVENT: SqlText = sql`DELETE FROM inbox_claims WHERE source = $1 AND event_id = $2 AND request_fingerprint IS NULL`
|
|
9
10
|
|
|
10
11
|
/** A conflicting insert locks the existing row and returns its committed fingerprint and outcome after the winner commits. */
|
|
11
|
-
export const CLAIM_CLIENT = sql`INSERT INTO inbox_claims (source, event_id, request_fingerprint, claimed_at)
|
|
12
|
+
export const CLAIM_CLIENT: SqlText = sql`INSERT INTO inbox_claims (source, event_id, request_fingerprint, claimed_at)
|
|
12
13
|
VALUES ($1, $2, $3, $4)
|
|
13
14
|
ON CONFLICT (source, event_id) DO UPDATE SET outcome = COALESCE(inbox_claims.outcome, '{}'::jsonb)
|
|
14
15
|
RETURNING request_fingerprint, outcome, (outcome IS NOT NULL AND (jsonb_typeof(outcome) <> 'object' OR NOT (outcome ? 'value'))) AS incomplete`
|
|
15
16
|
|
|
16
17
|
/** The JSON envelope distinguishes a completed null result from an uncompleted claim in this transaction. */
|
|
17
|
-
export const SAVE_CLIENT_OUTCOME = sql`WITH saved AS (
|
|
18
|
+
export const SAVE_CLIENT_OUTCOME: SqlText = sql`WITH saved AS (
|
|
18
19
|
UPDATE inbox_claims SET outcome = $3::jsonb
|
|
19
20
|
WHERE source = $1 AND event_id = $2
|
|
20
21
|
RETURNING outcome
|
|
@@ -22,7 +23,7 @@ export const SAVE_CLIENT_OUTCOME = sql`WITH saved AS (
|
|
|
22
23
|
SELECT outcome FROM saved LIMIT 1`
|
|
23
24
|
|
|
24
25
|
/** Retention never deletes delivery rows or an incomplete claim; its bounded batch skips rows held by active retries. */
|
|
25
|
-
export const SWEEP_CLIENTS = sql`WITH expired AS (
|
|
26
|
+
export const SWEEP_CLIENTS: SqlText = sql`WITH expired AS (
|
|
26
27
|
SELECT source, event_id FROM inbox_claims
|
|
27
28
|
WHERE source LIKE 'client:%' AND request_fingerprint IS NOT NULL AND outcome IS NOT NULL AND jsonb_typeof(outcome) = 'object' AND outcome ? 'value' AND claimed_at < $1
|
|
28
29
|
ORDER BY claimed_at, source, event_id LIMIT $2 FOR UPDATE SKIP LOCKED
|
|
@@ -34,6 +35,6 @@ RETURNING claims.event_id
|
|
|
34
35
|
SELECT count(*)::int AS deleted_count FROM deleted`
|
|
35
36
|
|
|
36
37
|
/** Imports an already completed legacy operation without replacing a key another migration has already transferred. */
|
|
37
|
-
export const BACKFILL_CLIENT_OUTCOME = sql`INSERT INTO inbox_claims (source, event_id, request_fingerprint, claimed_at, outcome)
|
|
38
|
+
export const BACKFILL_CLIENT_OUTCOME: SqlText = sql`INSERT INTO inbox_claims (source, event_id, request_fingerprint, claimed_at, outcome)
|
|
38
39
|
VALUES ($1, $2, $3, $4, $5::jsonb)
|
|
39
40
|
ON CONFLICT (source, event_id) DO NOTHING`
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { ConfigurableModuleBuilder } from "@nestjs/common"
|
|
1
|
+
import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
|
|
2
2
|
import type { @@Job@@Options } from "./@@job@@.options"
|
|
3
3
|
|
|
4
|
+
const built = new ConfigurableModuleBuilder<@@Job@@Options>().setClassMethodName("register").build()
|
|
5
|
+
type Host = ConfigurableModuleHost<@@Job@@Options, "register", "create", {}>
|
|
6
|
+
|
|
4
7
|
/** The configurable-module base of the @@job@@ module. */
|
|
5
|
-
export const
|
|
6
|
-
|
|
8
|
+
export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
|
|
9
|
+
/** The injection token of the registered options. */
|
|
10
|
+
export const MODULE_OPTIONS_TOKEN: Host["MODULE_OPTIONS_TOKEN"] = built.MODULE_OPTIONS_TOKEN
|
|
11
|
+
/** The options of a synchronous registration, for the register signature. */
|
|
12
|
+
export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
|
|
@@ -7,7 +7,7 @@ import { @@Step@@Step } from "../../steps/@@step@@.step"
|
|
|
7
7
|
@Injectable()
|
|
8
8
|
/** The processor of the @@job@@ job: the runner claims and settles, `process` runs the steps in order. */
|
|
9
9
|
export class @@Job@@Processor extends FencedProcessor {
|
|
10
|
-
readonly queue = @@jobUpper@@_QUEUE
|
|
10
|
+
readonly queue: string = @@jobUpper@@_QUEUE
|
|
11
11
|
|
|
12
12
|
constructor(private readonly @@stepCamel@@Step: @@Step@@Step) {
|
|
13
13
|
super()
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { ConfigurableModuleBuilder } from "@nestjs/common"
|
|
1
|
+
import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
|
|
2
2
|
import type { @@Job@@QueueOptions } from "./@@job@@-queue.options"
|
|
3
3
|
|
|
4
|
+
const built = new ConfigurableModuleBuilder<@@Job@@QueueOptions>().setClassMethodName("register").build()
|
|
5
|
+
type Host = ConfigurableModuleHost<@@Job@@QueueOptions, "register", "create", {}>
|
|
6
|
+
|
|
4
7
|
/** The configurable-module base of the @@job@@-queue module. */
|
|
5
|
-
export const
|
|
6
|
-
|
|
8
|
+
export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
|
|
9
|
+
/** The injection token of the registered options. */
|
|
10
|
+
export const MODULE_OPTIONS_TOKEN: Host["MODULE_OPTIONS_TOKEN"] = built.MODULE_OPTIONS_TOKEN
|
|
11
|
+
/** The options of a synchronous registration, for the register signature. */
|
|
12
|
+
export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { ConfigurableModuleBuilder } from "@nestjs/common"
|
|
1
|
+
import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
|
|
2
2
|
import { JOBS_OPTIONS } from "./jobs.decorators"
|
|
3
3
|
import type { JobsOptions } from "./jobs.options"
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
export const { ConfigurableModuleClass, OPTIONS_TYPE } = new ConfigurableModuleBuilder<JobsOptions>({
|
|
5
|
+
const built = new ConfigurableModuleBuilder<JobsOptions>({
|
|
7
6
|
optionsInjectionToken: JOBS_OPTIONS,
|
|
8
7
|
})
|
|
9
8
|
.setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
|
|
10
9
|
.build()
|
|
10
|
+
type Host = ConfigurableModuleHost<JobsOptions, "register", "create", { isGlobal: boolean }>
|
|
11
|
+
|
|
12
|
+
/** The configurable-module base of the jobs; `isGlobal` is decided by the app root. */
|
|
13
|
+
export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
|
|
14
|
+
/** The options of a synchronous registration, for the register signature. */
|
|
15
|
+
export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import type { DatabaseConnectionOptions } from "@modules/platform/database"
|
|
1
2
|
import { JobEntity } from "./entities/job.entity"
|
|
2
3
|
import { CreateJobs@@epochMs13@@ } from "./migrations/@@epochMs13@@-create-jobs"
|
|
3
4
|
|
|
4
5
|
/** The entities of the jobs, for every connection that runs jobs. */
|
|
5
|
-
export const jobsEntities = [JobEntity]
|
|
6
|
+
export const jobsEntities: DatabaseConnectionOptions["entities"] = [JobEntity]
|
|
6
7
|
|
|
7
8
|
/** The migrations of the jobs, in the order they run. */
|
|
8
|
-
export const jobsMigrations = [CreateJobs@@epochMs13@@]
|
|
9
|
+
export const jobsMigrations: DatabaseConnectionOptions["migrations"] = [CreateJobs@@epochMs13@@]
|