target-supabase-sdk 0.2.10 → 0.2.11
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/dist/browser.d.ts +3 -4
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +3 -3
- package/dist/chunks/{enable-log-persist-BB-unD4p.js → enable-log-persist-Cu3SGxK7.js} +160 -30
- package/dist/chunks/enable-log-persist-Cu3SGxK7.js.map +1 -0
- package/dist/chunks/{list.api-6Q4_-7oN.js → list.api-DSTOHzOC.js} +48 -13
- package/dist/chunks/list.api-DSTOHzOC.js.map +1 -0
- package/dist/chunks/{task-queue-D5uWjqVN.js → task-queue-BHvNgb-X.js} +54 -3
- package/dist/chunks/task-queue-BHvNgb-X.js.map +1 -0
- package/dist/core.api.d.ts +6 -15
- package/dist/core.api.d.ts.map +1 -1
- package/dist/core.interface.d.ts +5 -0
- package/dist/core.interface.d.ts.map +1 -1
- package/dist/core.schema.d.ts +77 -0
- package/dist/core.schema.d.ts.map +1 -0
- package/dist/link/index.d.ts +10 -0
- package/dist/link/index.d.ts.map +1 -0
- package/dist/link/link.build.d.ts +45 -0
- package/dist/link/link.build.d.ts.map +1 -0
- package/dist/list/index.d.ts +10 -0
- package/dist/list/index.d.ts.map +1 -0
- package/dist/list/list.build.d.ts +21 -0
- package/dist/list/list.build.d.ts.map +1 -0
- package/dist/node.js +6 -6
- package/dist/parcel/parcel.api.d.ts +3 -3
- package/dist/parcel/parcel.api.d.ts.map +1 -1
- package/dist/preload.js +3 -2
- package/dist/preload.js.map +1 -1
- package/dist/service/config.api.d.ts.map +1 -1
- package/dist/service/service.api.d.ts +6 -6
- package/dist/service/service.api.d.ts.map +1 -1
- package/dist/shared/log/log-persist-errors.d.ts +4 -0
- package/dist/shared/log/log-persist-errors.d.ts.map +1 -0
- package/dist/shared/log/log-persist-logger.d.ts +1 -0
- package/dist/shared/log/log-persist-logger.d.ts.map +1 -1
- package/dist/shared/log/log-persist.config.d.ts.map +1 -1
- package/dist/shared/log/log-persist.d.ts +8 -1
- package/dist/shared/log/log-persist.d.ts.map +1 -1
- package/dist/shared/log/log-persist.interface.d.ts +8 -0
- package/dist/shared/log/log-persist.interface.d.ts.map +1 -1
- package/dist/task/task-post-validated.api.d.ts +2 -2
- package/dist/task/task-post.api.d.ts +6 -6
- package/dist/trigger/trigger.api.d.ts +6 -6
- package/package.json +1 -1
- package/src/browser.ts +3 -4
- package/src/core.api.ts +19 -28
- package/src/core.interface.ts +6 -0
- package/src/core.schema.ts +51 -0
- package/src/link/index.ts +10 -0
- package/src/link/link.build.ts +48 -0
- package/src/list/index.ts +13 -0
- package/src/list/list.build.ts +51 -0
- package/src/shared/log/log-persist-errors.ts +16 -0
- package/src/shared/log/log-persist-logger.ts +28 -0
- package/src/shared/log/log-persist.config.ts +20 -0
- package/src/shared/log/log-persist.interface.ts +8 -0
- package/src/shared/log/log-persist.ts +161 -43
- package/dist/chunks/enable-log-persist-BB-unD4p.js.map +0 -1
- package/dist/chunks/list.api-6Q4_-7oN.js.map +0 -1
- package/dist/chunks/task-queue-D5uWjqVN.js.map +0 -1
|
@@ -13,22 +13,22 @@ export declare const postTaskSchema: z.ZodObject<{
|
|
|
13
13
|
traceId: z.ZodOptional<z.ZodString>;
|
|
14
14
|
traceParentId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
15
15
|
}, "strip", z.ZodTypeAny, {
|
|
16
|
-
name: string;
|
|
17
16
|
value: string;
|
|
17
|
+
name: string;
|
|
18
18
|
tagList: string[];
|
|
19
19
|
taskStatus: TaskStatus.OPEN | TaskStatus.TODO;
|
|
20
|
+
params?: unknown;
|
|
20
21
|
extra?: string | undefined;
|
|
21
22
|
traceId?: string | undefined;
|
|
22
23
|
traceParentId?: string | null | undefined;
|
|
23
|
-
params?: unknown;
|
|
24
24
|
}, {
|
|
25
|
-
name: string;
|
|
26
25
|
value: string;
|
|
26
|
+
name: string;
|
|
27
|
+
params?: unknown;
|
|
27
28
|
tagList?: string[] | undefined;
|
|
28
29
|
extra?: string | undefined;
|
|
29
30
|
traceId?: string | undefined;
|
|
30
31
|
traceParentId?: string | null | undefined;
|
|
31
|
-
params?: unknown;
|
|
32
32
|
taskStatus?: TaskStatus.OPEN | TaskStatus.TODO | undefined;
|
|
33
33
|
}>;
|
|
34
34
|
export type PostTaskPayload = z.infer<typeof postTaskSchema>;
|
|
@@ -42,13 +42,13 @@ export declare function createTaskRow({ name, value, params, taskStatus, tagList
|
|
|
42
42
|
* Execution-time checks live in {@link TaskManager.prepareTask}.
|
|
43
43
|
*/
|
|
44
44
|
export declare const postTask: (payload: {
|
|
45
|
-
name: string;
|
|
46
45
|
value: string;
|
|
46
|
+
name: string;
|
|
47
47
|
tagList: string[];
|
|
48
48
|
taskStatus: TaskStatus.OPEN | TaskStatus.TODO;
|
|
49
|
+
params?: unknown;
|
|
49
50
|
extra?: string | undefined;
|
|
50
51
|
traceId?: string | undefined;
|
|
51
52
|
traceParentId?: string | null | undefined;
|
|
52
|
-
params?: unknown;
|
|
53
53
|
}) => SupabaseResponse<Task> | Promise<SupabaseResponse<Task>>;
|
|
54
54
|
//# sourceMappingURL=task-post.api.d.ts.map
|
|
@@ -41,10 +41,10 @@ export declare const postTriggerSchema: z.ZodObject<{
|
|
|
41
41
|
tagList: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
|
|
42
42
|
traceId: z.ZodOptional<z.ZodString>;
|
|
43
43
|
}, "strip", z.ZodTypeAny, {
|
|
44
|
-
|
|
44
|
+
status: TriggerStatus;
|
|
45
45
|
value: string;
|
|
46
|
+
name: string;
|
|
46
47
|
tagList: string[];
|
|
47
|
-
status: TriggerStatus;
|
|
48
48
|
action: {
|
|
49
49
|
taskTypeKey: string;
|
|
50
50
|
kind: "post_task";
|
|
@@ -59,8 +59,8 @@ export declare const postTriggerSchema: z.ZodObject<{
|
|
|
59
59
|
};
|
|
60
60
|
traceId?: string | undefined;
|
|
61
61
|
}, {
|
|
62
|
-
name: string;
|
|
63
62
|
value: string;
|
|
63
|
+
name: string;
|
|
64
64
|
action: {
|
|
65
65
|
taskTypeKey: string;
|
|
66
66
|
kind: "post_task";
|
|
@@ -73,9 +73,9 @@ export declare const postTriggerSchema: z.ZodObject<{
|
|
|
73
73
|
minute: number;
|
|
74
74
|
kind: "daily";
|
|
75
75
|
};
|
|
76
|
+
status?: TriggerStatus | undefined;
|
|
76
77
|
tagList?: string[] | undefined;
|
|
77
78
|
traceId?: string | undefined;
|
|
78
|
-
status?: TriggerStatus | undefined;
|
|
79
79
|
}>;
|
|
80
80
|
export type PostTriggerPayload = z.infer<typeof postTriggerSchema>;
|
|
81
81
|
export declare const scanEnabledTriggersSchema: z.ZodObject<{
|
|
@@ -109,10 +109,10 @@ export declare const scanEnabledTriggers: (payload: {
|
|
|
109
109
|
}) => SupabaseResponse<Trigger[]> | Promise<SupabaseResponse<Trigger[]>>;
|
|
110
110
|
/** Create a trigger row (scheduler / admin). */
|
|
111
111
|
export declare const postTrigger: (payload: {
|
|
112
|
-
|
|
112
|
+
status: TriggerStatus;
|
|
113
113
|
value: string;
|
|
114
|
+
name: string;
|
|
114
115
|
tagList: string[];
|
|
115
|
-
status: TriggerStatus;
|
|
116
116
|
action: {
|
|
117
117
|
taskTypeKey: string;
|
|
118
118
|
kind: "post_task";
|
package/package.json
CHANGED
package/src/browser.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
export type { SupabaseClient } from "@supabase/supabase-js";
|
|
9
9
|
export * from "./core.api";
|
|
10
10
|
export * from "./core.interface";
|
|
11
|
+
export * from "./core.schema";
|
|
11
12
|
export * from "./core.utils";
|
|
12
13
|
export type {
|
|
13
14
|
GetExtractionPayload,
|
|
@@ -28,10 +29,8 @@ export {
|
|
|
28
29
|
export type { Extraction, ExtractionDetails } from "./extraction/extraction.interface";
|
|
29
30
|
export { CategoryExtraction } from "./extraction/extraction.interface";
|
|
30
31
|
export * from "./idea/idea.interface";
|
|
31
|
-
export * from "./link/
|
|
32
|
-
export * from "./
|
|
33
|
-
export * from "./list/list.api";
|
|
34
|
-
export * from "./list/list.interface";
|
|
32
|
+
export * from "./link/index";
|
|
33
|
+
export * from "./list/index";
|
|
35
34
|
export * from "./node/node.api";
|
|
36
35
|
export * from "./node/node.interface";
|
|
37
36
|
export type {
|
package/src/core.api.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import type { PostgrestFilterBuilder } from "@supabase/postgrest-js";
|
|
2
2
|
import type { ZodError, ZodType } from "zod";
|
|
3
|
-
import { generateResponse, type Target, type TargetDraft } from "./core.interface";
|
|
4
|
-
import {
|
|
3
|
+
import { generateResponse, type QueryFilter, type Target, type TargetDraft } from "./core.interface";
|
|
4
|
+
import { type PostTargetPayload, postTargetPayloadSchema } from "./core.schema";
|
|
5
|
+
import { type BaseValidator, handleSupabaseError } from "./core.utils";
|
|
5
6
|
import { supabase } from "./supabase";
|
|
6
7
|
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
export type { QueryFilter } from "./core.interface";
|
|
9
|
+
export type { PostTargetPayload } from "./core.schema";
|
|
10
|
+
export {
|
|
11
|
+
formatZodError,
|
|
12
|
+
postTargetPayloadSchema,
|
|
13
|
+
queryFilterSchema,
|
|
14
|
+
safeParseWithSchema,
|
|
15
|
+
targetDraftSchema,
|
|
16
|
+
} from "./core.schema";
|
|
12
17
|
|
|
13
18
|
export const MAX_TARGET_LIST_PAGE_SIZE = 100;
|
|
14
19
|
|
|
@@ -379,30 +384,16 @@ export const deleteTarget = async ({ id, filterList }: { id: string; filterList?
|
|
|
379
384
|
return generateResponse.success();
|
|
380
385
|
};
|
|
381
386
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
tagList: Target["tagList"];
|
|
387
|
-
extra?: Target["extra"];
|
|
388
|
-
details?: Target["details"];
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
class PostTargetPayloadValidator extends BaseValidator<PostTargetPayload> {
|
|
392
|
-
protected requiredFields: (keyof PostTargetPayload)[] = ["category", "name", "value", "tagList"];
|
|
393
|
-
protected optionalFields: (keyof PostTargetPayload)[] = ["extra", "details"];
|
|
394
|
-
|
|
395
|
-
constructor() {
|
|
396
|
-
super();
|
|
397
|
-
// Add custom
|
|
398
|
-
this.addCustomValidator((_val) => {
|
|
399
|
-
return true;
|
|
400
|
-
});
|
|
387
|
+
function parsePostTargetPayload(payload: unknown): PostTargetPayload {
|
|
388
|
+
const result = postTargetPayloadSchema.safeParse(payload);
|
|
389
|
+
if (!result.success) {
|
|
390
|
+
throw formatZodValidationError("PostTargetPayload", result.error);
|
|
401
391
|
}
|
|
392
|
+
return result.data;
|
|
402
393
|
}
|
|
403
394
|
|
|
404
395
|
export const postTarget = async (payload: PostTargetPayload) => {
|
|
405
|
-
const validPayload =
|
|
396
|
+
const validPayload = parsePostTargetPayload(payload);
|
|
406
397
|
|
|
407
398
|
const { data, error } = await supabase.client
|
|
408
399
|
.from("target")
|
|
@@ -443,7 +434,7 @@ export const patchTarget = async ({ id, ...restPayload }: PatchTargetPayload) =>
|
|
|
443
434
|
}
|
|
444
435
|
if (fetchError) handleSupabaseError("patchTarget", fetchError, "Failed to fetch target.");
|
|
445
436
|
|
|
446
|
-
const updatedTarget =
|
|
437
|
+
const updatedTarget = parsePostTargetPayload(restPayload);
|
|
447
438
|
|
|
448
439
|
const { data, error } = await supabase.client
|
|
449
440
|
.from("target")
|
package/src/core.interface.ts
CHANGED
|
@@ -14,6 +14,12 @@ export interface Target {
|
|
|
14
14
|
/** Target row before persistence — no `id` or `created_at`. */
|
|
15
15
|
export type TargetDraft<T extends Target> = Omit<T, "id" | "created_at">;
|
|
16
16
|
|
|
17
|
+
export interface QueryFilter {
|
|
18
|
+
field: string;
|
|
19
|
+
operator: "eq" | "neq" | "in";
|
|
20
|
+
value: unknown;
|
|
21
|
+
}
|
|
22
|
+
|
|
17
23
|
export enum StatusCode {
|
|
18
24
|
SUCCESS = 200,
|
|
19
25
|
ERROR = 400,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type ZodError, type ZodType, z } from "zod";
|
|
2
|
+
import type { QueryFilter } from "./core.interface";
|
|
3
|
+
|
|
4
|
+
/** {@link QueryFilter} — shared by createTarget redundancy checks and message payloads. */
|
|
5
|
+
export const queryFilterSchema = z
|
|
6
|
+
.object({
|
|
7
|
+
field: z.string().trim().min(1),
|
|
8
|
+
operator: z.enum(["eq", "neq", "in"]),
|
|
9
|
+
value: z.any(),
|
|
10
|
+
})
|
|
11
|
+
.transform(
|
|
12
|
+
(row): QueryFilter => ({
|
|
13
|
+
field: row.field,
|
|
14
|
+
operator: row.operator,
|
|
15
|
+
value: row.value,
|
|
16
|
+
}),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
/** Persisted Target create/patch body (no `id` / `created_at`). */
|
|
20
|
+
export const postTargetPayloadSchema = z.object({
|
|
21
|
+
name: z.string(),
|
|
22
|
+
value: z.string(),
|
|
23
|
+
category: z.string(),
|
|
24
|
+
tagList: z.array(z.string()),
|
|
25
|
+
extra: z.string().optional(),
|
|
26
|
+
details: z.unknown().optional(),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export type PostTargetPayload = z.infer<typeof postTargetPayloadSchema>;
|
|
30
|
+
|
|
31
|
+
/** {@link TargetDraft} — stricter trim on identity fields for draft/message boundaries. */
|
|
32
|
+
export const targetDraftSchema = postTargetPayloadSchema.extend({
|
|
33
|
+
name: z.string().trim().min(1),
|
|
34
|
+
value: z.string().trim().min(1),
|
|
35
|
+
category: z.string().trim().min(1),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export function formatZodError(error: ZodError): string {
|
|
39
|
+
return error.issues.map((issue) => issue.message).join("; ");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function safeParseWithSchema<T extends ZodType>(
|
|
43
|
+
schema: T,
|
|
44
|
+
input: unknown,
|
|
45
|
+
): { ok: true; data: z.infer<T> } | { ok: false; error: string } {
|
|
46
|
+
const result = schema.safeParse(input);
|
|
47
|
+
if (!result.success) {
|
|
48
|
+
return { ok: false, error: formatZodError(result.error) };
|
|
49
|
+
}
|
|
50
|
+
return { ok: true, data: result.data };
|
|
51
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Link domain public API — curated re-exports only.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type { PostLinkCreatePayload } from "./link.api";
|
|
6
|
+
export { PostLinkValidator, postLinkCreate } from "./link.api";
|
|
7
|
+
export type { LinkTargetDraftBuildInput } from "./link.build";
|
|
8
|
+
export { buildLinkTargetDraft, LINK_MANIFEST_VERSION, linkTargetDraftBuildInputSchema } from "./link.build";
|
|
9
|
+
export type { Link, LinkDetails } from "./link.interface";
|
|
10
|
+
export { CategoryLink } from "./link.interface";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { TargetDraft } from "../core.interface";
|
|
3
|
+
import { CategoryLink, type Link } from "./link.interface";
|
|
4
|
+
|
|
5
|
+
export const LINK_MANIFEST_VERSION = 0;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Convenience input for {@link buildLinkTargetDraft} — must not include `category`.
|
|
9
|
+
* Also accepted in {@link buildListTargetDraft} `items` as syntactic sugar (see `list.build.ts`).
|
|
10
|
+
*/
|
|
11
|
+
export interface LinkTargetDraftBuildInput<O = unknown> {
|
|
12
|
+
name: string;
|
|
13
|
+
value: string;
|
|
14
|
+
description: string;
|
|
15
|
+
preview?: string;
|
|
16
|
+
loaderKey: string;
|
|
17
|
+
tagList?: string[];
|
|
18
|
+
original: O;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Runtime shape check for {@link LinkTargetDraftBuildInput} — `.strict()` rejects `category` and other extra keys. */
|
|
22
|
+
export const linkTargetDraftBuildInputSchema = z
|
|
23
|
+
.object({
|
|
24
|
+
name: z.string(),
|
|
25
|
+
value: z.string(),
|
|
26
|
+
description: z.string(),
|
|
27
|
+
preview: z.string().optional(),
|
|
28
|
+
loaderKey: z.string(),
|
|
29
|
+
tagList: z.array(z.string()).optional(),
|
|
30
|
+
original: z.unknown(),
|
|
31
|
+
})
|
|
32
|
+
.strict();
|
|
33
|
+
|
|
34
|
+
export function buildLinkTargetDraft<O = unknown>(input: LinkTargetDraftBuildInput<O>): TargetDraft<Link> {
|
|
35
|
+
return {
|
|
36
|
+
name: input.name,
|
|
37
|
+
value: input.value,
|
|
38
|
+
category: CategoryLink.LINK,
|
|
39
|
+
tagList: input.tagList ?? [],
|
|
40
|
+
details: {
|
|
41
|
+
manifestVersion: LINK_MANIFEST_VERSION,
|
|
42
|
+
description: input.description,
|
|
43
|
+
preview: input.preview ?? "",
|
|
44
|
+
loaderKey: input.loaderKey,
|
|
45
|
+
original: input.original,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List domain public API — curated re-exports only.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type { PostListCreatePayload } from "./list.api";
|
|
6
|
+
export { PostListValidator, postListCreate, postLogListCreate } from "./list.api";
|
|
7
|
+
export type { BuildListTargetDraftInput } from "./list.build";
|
|
8
|
+
export {
|
|
9
|
+
buildListTargetDraft,
|
|
10
|
+
LIST_MANIFEST_VERSION,
|
|
11
|
+
} from "./list.build";
|
|
12
|
+
export type { List, ListDetails } from "./list.interface";
|
|
13
|
+
export { CategoryList } from "./list.interface";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { TargetDraft } from "../core.interface";
|
|
2
|
+
import {
|
|
3
|
+
buildLinkTargetDraft,
|
|
4
|
+
type LinkTargetDraftBuildInput,
|
|
5
|
+
linkTargetDraftBuildInputSchema,
|
|
6
|
+
} from "../link/link.build";
|
|
7
|
+
import { CategoryList, type List, type ListDetails } from "./list.interface";
|
|
8
|
+
|
|
9
|
+
export const LIST_MANIFEST_VERSION = 0;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Input for {@link buildListTargetDraft}.
|
|
13
|
+
*
|
|
14
|
+
* `items`: most lists store Link rows. {@link LinkTargetDraftBuildInput} is syntactic
|
|
15
|
+
* sugar — expanded via `buildLinkTargetDraft`. Any other shape is stored as-is.
|
|
16
|
+
*/
|
|
17
|
+
export type BuildListTargetDraftInput<O = unknown, TCustom = unknown> = {
|
|
18
|
+
loaderKey: string;
|
|
19
|
+
name: string;
|
|
20
|
+
value: string;
|
|
21
|
+
meta?: unknown;
|
|
22
|
+
preview?: string;
|
|
23
|
+
tagList?: string[];
|
|
24
|
+
items: Array<LinkTargetDraftBuildInput<O> | TCustom>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function isLinkTargetDraftBuildInput(item: unknown): item is LinkTargetDraftBuildInput {
|
|
28
|
+
return linkTargetDraftBuildInputSchema.safeParse(item).success;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function buildListTargetDraft<O = unknown, TCustom = unknown>(
|
|
32
|
+
input: BuildListTargetDraftInput<O, TCustom>,
|
|
33
|
+
): TargetDraft<List> {
|
|
34
|
+
const items = input.items.map((item) => (isLinkTargetDraftBuildInput(item) ? buildLinkTargetDraft(item) : item));
|
|
35
|
+
|
|
36
|
+
const details: ListDetails = {
|
|
37
|
+
manifestVersion: LIST_MANIFEST_VERSION,
|
|
38
|
+
meta: input.meta ?? "",
|
|
39
|
+
preview: input.preview ?? "",
|
|
40
|
+
loaderKey: input.loaderKey,
|
|
41
|
+
items,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
name: input.name,
|
|
46
|
+
value: input.value,
|
|
47
|
+
category: CategoryList.LIST,
|
|
48
|
+
tagList: input.tagList ?? [],
|
|
49
|
+
details,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const PERMANENT_LOG_PERSIST_ERROR_PATTERNS = [
|
|
2
|
+
/not initialized/i,
|
|
3
|
+
/call initialize\(\) first/i,
|
|
4
|
+
/missing required supabase environment/i,
|
|
5
|
+
/preload env incomplete/i,
|
|
6
|
+
];
|
|
7
|
+
|
|
8
|
+
export function formatLogPersistError(error: unknown): string {
|
|
9
|
+
return error instanceof Error ? error.message : String(error);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Errors that will not succeed on retry without fixing process configuration. */
|
|
13
|
+
export function isPermanentLogPersistError(error: unknown): boolean {
|
|
14
|
+
const message = formatLogPersistError(error);
|
|
15
|
+
return PERMANENT_LOG_PERSIST_ERROR_PATTERNS.some((pattern) => pattern.test(message));
|
|
16
|
+
}
|
|
@@ -8,13 +8,41 @@ export const LOG_PERSIST_INTERNAL_TOPIC = "log-persist-internal";
|
|
|
8
8
|
|
|
9
9
|
const logger = createLogger({ module: "log-persist" });
|
|
10
10
|
|
|
11
|
+
let errorLogRateLimitMs = 5_000;
|
|
12
|
+
const lastLoggedAt = new Map<string, number>();
|
|
13
|
+
|
|
14
|
+
export function configurePersistLoggerRateLimit(ms: number): void {
|
|
15
|
+
errorLogRateLimitMs = Math.max(0, ms);
|
|
16
|
+
}
|
|
17
|
+
|
|
11
18
|
export function isLogPersistInternalTopic(topic: string | undefined): boolean {
|
|
12
19
|
return topic === LOG_PERSIST_INTERNAL_TOPIC;
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
type PersistLogData = Record<string, unknown>;
|
|
16
23
|
|
|
24
|
+
function shouldEmit(level: "debug" | "info" | "warn" | "error", message: string): boolean {
|
|
25
|
+
if (level !== "error" && level !== "warn") {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
if (errorLogRateLimitMs <= 0) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const key = `${level}:${message}`;
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
const last = lastLoggedAt.get(key);
|
|
35
|
+
if (last != null && now - last < errorLogRateLimitMs) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
lastLoggedAt.set(key, now);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
17
42
|
function write(level: "debug" | "info" | "warn" | "error", message: string, data?: PersistLogData): void {
|
|
43
|
+
if (!shouldEmit(level, message)) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
18
46
|
logger[level](message, {
|
|
19
47
|
topic: LOG_PERSIST_INTERNAL_TOPIC,
|
|
20
48
|
...(data != null ? { data } : {}),
|
|
@@ -19,6 +19,9 @@ export const DEFAULT_LOG_PERSIST_CONFIG: LogPersistConfig = {
|
|
|
19
19
|
},
|
|
20
20
|
postTimeoutMs: 30_000,
|
|
21
21
|
shutdownDrainTimeoutMs: 15_000,
|
|
22
|
+
laneRetryIntervalMs: 5_000,
|
|
23
|
+
errorLogRateLimitMs: 5_000,
|
|
24
|
+
circuitBreakerPermanentFailureThreshold: 3,
|
|
22
25
|
};
|
|
23
26
|
|
|
24
27
|
export function resolveLogPersistConfig(partial?: Partial<LogPersistConfig>): LogPersistConfig {
|
|
@@ -29,6 +32,10 @@ export function resolveLogPersistConfig(partial?: Partial<LogPersistConfig>): Lo
|
|
|
29
32
|
slow: { ...defaults.slow, ...partial?.slow },
|
|
30
33
|
postTimeoutMs: partial?.postTimeoutMs ?? defaults.postTimeoutMs,
|
|
31
34
|
shutdownDrainTimeoutMs: partial?.shutdownDrainTimeoutMs ?? defaults.shutdownDrainTimeoutMs,
|
|
35
|
+
laneRetryIntervalMs: partial?.laneRetryIntervalMs ?? defaults.laneRetryIntervalMs,
|
|
36
|
+
errorLogRateLimitMs: partial?.errorLogRateLimitMs ?? defaults.errorLogRateLimitMs,
|
|
37
|
+
circuitBreakerPermanentFailureThreshold:
|
|
38
|
+
partial?.circuitBreakerPermanentFailureThreshold ?? defaults.circuitBreakerPermanentFailureThreshold,
|
|
32
39
|
};
|
|
33
40
|
}
|
|
34
41
|
|
|
@@ -91,5 +98,18 @@ export function logPersistConfigFromEnv(env: NodeJS.ProcessEnv = process.env): L
|
|
|
91
98
|
DEFAULT_LOG_PERSIST_CONFIG.shutdownDrainTimeoutMs,
|
|
92
99
|
{ env },
|
|
93
100
|
),
|
|
101
|
+
laneRetryIntervalMs: envMs("LOG_PERSIST_LANE_RETRY_MS", DEFAULT_LOG_PERSIST_CONFIG.laneRetryIntervalMs, {
|
|
102
|
+
env,
|
|
103
|
+
}),
|
|
104
|
+
errorLogRateLimitMs: envMs(
|
|
105
|
+
"LOG_PERSIST_ERROR_LOG_RATE_LIMIT_MS",
|
|
106
|
+
DEFAULT_LOG_PERSIST_CONFIG.errorLogRateLimitMs,
|
|
107
|
+
{ env },
|
|
108
|
+
),
|
|
109
|
+
circuitBreakerPermanentFailureThreshold: envInt(
|
|
110
|
+
"LOG_PERSIST_CIRCUIT_BREAKER_THRESHOLD",
|
|
111
|
+
DEFAULT_LOG_PERSIST_CONFIG.circuitBreakerPermanentFailureThreshold,
|
|
112
|
+
{ env, min: 1 },
|
|
113
|
+
),
|
|
94
114
|
});
|
|
95
115
|
}
|
|
@@ -41,6 +41,12 @@ export interface LogPersistConfig {
|
|
|
41
41
|
postTimeoutMs: number;
|
|
42
42
|
/** Graceful shutdown drain ceiling */
|
|
43
43
|
shutdownDrainTimeoutMs: number;
|
|
44
|
+
/** Delay before medium/slow lane flush retries after failure (fast lane uses the same interval). */
|
|
45
|
+
laneRetryIntervalMs: number;
|
|
46
|
+
/** Suppress duplicate persistLogger warn/error lines with the same message within this window. */
|
|
47
|
+
errorLogRateLimitMs: number;
|
|
48
|
+
/** Disable log-persist after this many consecutive permanent flush errors. */
|
|
49
|
+
circuitBreakerPermanentFailureThreshold: number;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
export interface EnableLogPersistOptions {
|
|
@@ -89,6 +95,8 @@ export interface LogPersistQueueBytesStats {
|
|
|
89
95
|
export interface LogPersistStats {
|
|
90
96
|
enabled: boolean;
|
|
91
97
|
draining: boolean;
|
|
98
|
+
circuitBreakerTripped: boolean;
|
|
99
|
+
permanentFailureCount: number;
|
|
92
100
|
service: string | null;
|
|
93
101
|
process: string | null;
|
|
94
102
|
queues: LogPersistQueueStats;
|