@gencow/core 0.1.27 → 0.1.29
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/auth-config.d.ts +92 -5
- package/dist/config.d.ts +107 -0
- package/dist/config.js +12 -0
- package/dist/context.d.ts +139 -0
- package/dist/context.js +3 -0
- package/dist/crud.d.ts +5 -5
- package/dist/crud.js +19 -35
- package/dist/document-types.d.ts +65 -0
- package/dist/document-types.js +15 -0
- package/dist/grounded-answer-types.d.ts +62 -0
- package/dist/grounded-answer-types.js +6 -0
- package/dist/http-action.d.ts +77 -0
- package/dist/http-action.js +41 -0
- package/dist/index.d.ts +30 -5
- package/dist/index.js +15 -2
- package/dist/platform-capacity-profile.d.ts +19 -0
- package/dist/platform-capacity-profile.js +94 -0
- package/dist/procedure.d.ts +58 -0
- package/dist/procedure.js +115 -0
- package/dist/rag-ingest-types.d.ts +39 -0
- package/dist/rag-ingest-types.js +1 -0
- package/dist/rag-operations-types.d.ts +81 -0
- package/dist/rag-operations-types.js +1 -0
- package/dist/rag-schema.d.ts +1466 -0
- package/dist/rag-schema.js +87 -0
- package/dist/reactive-mutation-types.d.ts +11 -0
- package/dist/reactive-mutation-types.js +1 -0
- package/dist/reactive-mutation.d.ts +51 -0
- package/dist/reactive-mutation.js +75 -0
- package/dist/reactive-query-types.d.ts +12 -0
- package/dist/reactive-query-types.js +1 -0
- package/dist/reactive-query.d.ts +14 -0
- package/dist/reactive-query.js +28 -0
- package/dist/reactive-realtime.d.ts +48 -0
- package/dist/reactive-realtime.js +236 -0
- package/dist/reactive.d.ts +29 -5
- package/dist/reactive.js +65 -0
- package/dist/rls-db.d.ts +9 -2
- package/dist/runtime-env-policy.d.ts +5 -0
- package/dist/runtime-env-policy.js +56 -0
- package/dist/search-types.d.ts +83 -0
- package/dist/search-types.js +1 -0
- package/dist/server.d.ts +1 -2
- package/dist/server.js +0 -1
- package/dist/storage-metering.d.ts +13 -0
- package/dist/storage-metering.js +18 -0
- package/dist/storage-shared.d.ts +36 -0
- package/dist/storage-shared.js +39 -0
- package/dist/storage.d.ts +5 -27
- package/dist/storage.js +30 -22
- package/dist/wake-app-result.d.ts +22 -0
- package/dist/wake-app-result.js +11 -0
- package/dist/workflow-types.d.ts +16 -2
- package/dist/workflow.d.ts +1 -1
- package/dist/workflow.js +136 -11
- package/dist/workflows-api.js +71 -3
- package/package.json +11 -7
- package/src/auth-config.ts +104 -3
- package/src/config.ts +119 -0
- package/src/context.ts +152 -0
- package/src/crud.ts +18 -35
- package/src/document-types.ts +102 -0
- package/src/grounded-answer-types.ts +78 -0
- package/src/http-action.ts +101 -0
- package/src/index.ts +142 -19
- package/src/platform-capacity-profile.ts +114 -0
- package/src/procedure.ts +283 -0
- package/src/rag-ingest-types.ts +52 -0
- package/src/rag-operations-types.ts +90 -0
- package/src/rag-schema.ts +94 -0
- package/src/reactive-mutation-types.ts +13 -0
- package/src/reactive-mutation.ts +115 -0
- package/src/reactive-query-types.ts +14 -0
- package/src/reactive-query.ts +48 -0
- package/src/reactive-realtime.ts +267 -0
- package/src/rls-db.ts +9 -4
- package/src/runtime-env-policy.ts +66 -0
- package/src/search-types.ts +91 -0
- package/src/server.ts +6 -2
- package/src/storage-metering.ts +35 -0
- package/src/storage-shared.ts +74 -0
- package/src/storage.ts +44 -53
- package/src/wake-app-result.ts +37 -0
- package/src/workflow-types.ts +16 -2
- package/src/workflow.ts +166 -12
- package/src/workflows-api.ts +82 -3
- package/src/__tests__/auth.test.ts +0 -118
- package/src/__tests__/crons.test.ts +0 -83
- package/src/__tests__/crud-codegen-integration.test.ts +0 -246
- package/src/__tests__/crud-owner-rls.test.ts +0 -387
- package/src/__tests__/crud.test.ts +0 -930
- package/src/__tests__/dist-exports.test.ts +0 -176
- package/src/__tests__/fixtures/basic/auth.ts +0 -32
- package/src/__tests__/fixtures/basic/drizzle.config.ts +0 -12
- package/src/__tests__/fixtures/basic/index.ts +0 -6
- package/src/__tests__/fixtures/basic/migrations/0000_last_warstar.sql +0 -75
- package/src/__tests__/fixtures/basic/migrations/meta/0000_snapshot.json +0 -497
- package/src/__tests__/fixtures/basic/migrations/meta/_journal.json +0 -13
- package/src/__tests__/fixtures/basic/schema.ts +0 -51
- package/src/__tests__/fixtures/basic/tasks.ts +0 -15
- package/src/__tests__/fixtures/common/auth-schema.ts +0 -67
- package/src/__tests__/helpers/basic-rls-fixture.ts +0 -135
- package/src/__tests__/helpers/pglite-migrations.ts +0 -32
- package/src/__tests__/helpers/pglite-rls-session.ts +0 -51
- package/src/__tests__/helpers/seed-like-fill.ts +0 -202
- package/src/__tests__/helpers/test-gencow-ctx-rls.ts +0 -50
- package/src/__tests__/httpaction.test.ts +0 -122
- package/src/__tests__/image-optimization.test.ts +0 -648
- package/src/__tests__/load.test.ts +0 -389
- package/src/__tests__/network-sim.test.ts +0 -319
- package/src/__tests__/reactive.test.ts +0 -479
- package/src/__tests__/retry.test.ts +0 -113
- package/src/__tests__/rls-crud-basic.test.ts +0 -317
- package/src/__tests__/rls-crud-no-owner-rls-pglite.test.ts +0 -117
- package/src/__tests__/rls-custom-mutation-handlers.test.ts +0 -142
- package/src/__tests__/rls-custom-query-handlers.test.ts +0 -128
- package/src/__tests__/rls-db-leased-connection.test.ts +0 -118
- package/src/__tests__/rls-session-and-policies.test.ts +0 -228
- package/src/__tests__/scheduler-durable-v2.test.ts +0 -288
- package/src/__tests__/scheduler-durable.test.ts +0 -173
- package/src/__tests__/scheduler-exec.test.ts +0 -328
- package/src/__tests__/scheduler.test.ts +0 -187
- package/src/__tests__/storage.test.ts +0 -334
- package/src/__tests__/tsconfig.json +0 -8
- package/src/__tests__/validator.test.ts +0 -323
- package/src/__tests__/workflow.test.ts +0 -606
- package/src/__tests__/ws-integration.test.ts +0 -309
- package/src/__tests__/ws-scale.test.ts +0 -241
- package/src/auth.ts +0 -155
- package/src/reactive.ts +0 -580
package/dist/workflow.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { sql } from "drizzle-orm";
|
|
2
|
-
import { mutation } from "./reactive.js";
|
|
2
|
+
import { mutation } from "./reactive-mutation.js";
|
|
3
3
|
import { registerWorkflowsApi } from "./workflows-api.js";
|
|
4
4
|
const workflowRegistry = (globalThis.__gencow_workflowRegistry ??= new Map());
|
|
5
5
|
export const DEFAULT_WORKFLOW_MAX_DURATION_MS = 30 * 60 * 1000;
|
|
@@ -57,10 +57,115 @@ export function parseWorkflowDurationMs(raw, label = "workflow duration") {
|
|
|
57
57
|
}
|
|
58
58
|
return parseDurationString(raw, label);
|
|
59
59
|
}
|
|
60
|
-
function normalizeMaxDurationMs(maxDuration) {
|
|
60
|
+
function normalizeMaxDurationMs(maxDuration, label = "workflow() maxDuration") {
|
|
61
61
|
if (maxDuration == null)
|
|
62
62
|
return DEFAULT_WORKFLOW_MAX_DURATION_MS;
|
|
63
|
-
return parseWorkflowDurationMs(maxDuration,
|
|
63
|
+
return parseWorkflowDurationMs(maxDuration, label);
|
|
64
|
+
}
|
|
65
|
+
function normalizeOptionalDurationMs(duration, label) {
|
|
66
|
+
return duration == null ? null : parseWorkflowDurationMs(duration, label);
|
|
67
|
+
}
|
|
68
|
+
function normalizeConcurrency(concurrency) {
|
|
69
|
+
if (concurrency == null)
|
|
70
|
+
return null;
|
|
71
|
+
if (!Number.isFinite(concurrency) || concurrency <= 0) {
|
|
72
|
+
throw new Error(`workflow() concurrency must be a positive finite number, got "${concurrency}"`);
|
|
73
|
+
}
|
|
74
|
+
return Math.floor(concurrency);
|
|
75
|
+
}
|
|
76
|
+
function isMissingWorkflowV2SchemaError(error) {
|
|
77
|
+
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
78
|
+
const cause = error && typeof error === "object" && "cause" in error ? error.cause : null;
|
|
79
|
+
const causeCode = cause && typeof cause === "object" && "code" in cause ? String(cause.code) : "";
|
|
80
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
81
|
+
return (code === "42P01" ||
|
|
82
|
+
code === "42703" ||
|
|
83
|
+
causeCode === "42P01" ||
|
|
84
|
+
causeCode === "42703" ||
|
|
85
|
+
message.includes('relation "_gencow_workflow_runs_v2" does not exist') ||
|
|
86
|
+
message.includes("relation _gencow_workflow_runs_v2 does not exist") ||
|
|
87
|
+
message.includes('relation "_gencow_workflow_outbox_v2" does not exist') ||
|
|
88
|
+
message.includes("relation _gencow_workflow_outbox_v2 does not exist") ||
|
|
89
|
+
message.includes('column "max_active_duration_ms"') ||
|
|
90
|
+
message.includes('column "retry_count"') ||
|
|
91
|
+
message.includes('column "user_id"'));
|
|
92
|
+
}
|
|
93
|
+
async function tryInsertWorkflowV2WakeOutbox(db, workflowId) {
|
|
94
|
+
try {
|
|
95
|
+
await db.execute(sql `
|
|
96
|
+
INSERT INTO _gencow_workflow_outbox_v2 (
|
|
97
|
+
id,
|
|
98
|
+
run_id,
|
|
99
|
+
kind,
|
|
100
|
+
available_at,
|
|
101
|
+
status
|
|
102
|
+
)
|
|
103
|
+
VALUES (
|
|
104
|
+
${`start:${workflowId}`},
|
|
105
|
+
${workflowId},
|
|
106
|
+
'wake_run',
|
|
107
|
+
NOW(),
|
|
108
|
+
'pending'
|
|
109
|
+
)
|
|
110
|
+
ON CONFLICT (id) DO NOTHING
|
|
111
|
+
`);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (!isMissingWorkflowV2SchemaError(error))
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function tryInsertWorkflowV2Run(options) {
|
|
119
|
+
try {
|
|
120
|
+
await options.db.execute(sql `
|
|
121
|
+
INSERT INTO _gencow_workflow_runs_v2 (
|
|
122
|
+
id,
|
|
123
|
+
workflow_name,
|
|
124
|
+
workflow_version,
|
|
125
|
+
args_json,
|
|
126
|
+
user_id,
|
|
127
|
+
max_active_duration_ms,
|
|
128
|
+
lifecycle_deadline_at,
|
|
129
|
+
retry_count,
|
|
130
|
+
max_retries,
|
|
131
|
+
max_attempts
|
|
132
|
+
)
|
|
133
|
+
VALUES (
|
|
134
|
+
${options.workflowId},
|
|
135
|
+
${options.workflowName},
|
|
136
|
+
${options.workflowVersion ?? null},
|
|
137
|
+
${JSON.stringify(options.args)}::jsonb,
|
|
138
|
+
${options.userId},
|
|
139
|
+
${options.maxActiveDurationMs},
|
|
140
|
+
CASE
|
|
141
|
+
WHEN ${options.lifecycleTimeoutMs}::bigint IS NULL THEN NULL
|
|
142
|
+
ELSE NOW() + (${options.lifecycleTimeoutMs}::bigint * INTERVAL '1 millisecond')
|
|
143
|
+
END,
|
|
144
|
+
0,
|
|
145
|
+
${options.maxRetries},
|
|
146
|
+
${options.maxRetries + 1}
|
|
147
|
+
)
|
|
148
|
+
`);
|
|
149
|
+
await tryInsertWorkflowV2WakeOutbox(options.db, options.workflowId);
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
if (isMissingWorkflowV2SchemaError(error))
|
|
154
|
+
return false;
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async function tryDeleteWorkflowV2Run(db, workflowId) {
|
|
159
|
+
try {
|
|
160
|
+
await db.execute(sql `
|
|
161
|
+
DELETE FROM _gencow_workflow_runs_v2
|
|
162
|
+
WHERE id = ${workflowId}
|
|
163
|
+
`);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
if (!isMissingWorkflowV2SchemaError(error))
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
64
169
|
}
|
|
65
170
|
export function getWorkflowResumeActionName(name) {
|
|
66
171
|
return `${WORKFLOW_RESUME_ACTION_PREFIX}.${name}`;
|
|
@@ -85,13 +190,18 @@ export function getRegisteredWorkflows() {
|
|
|
85
190
|
*/
|
|
86
191
|
export function workflow(name, options) {
|
|
87
192
|
registerWorkflowsApi();
|
|
88
|
-
const
|
|
193
|
+
const maxActiveDurationMs = normalizeMaxDurationMs(options.maxActiveDuration ?? options.maxDuration, options.maxActiveDuration == null ? "workflow() maxDuration" : "workflow() maxActiveDuration");
|
|
194
|
+
const lifecycleTimeoutMs = normalizeOptionalDurationMs(options.lifecycleTimeout, "workflow() lifecycleTimeout");
|
|
89
195
|
const maxRetries = clampRetries(options.retries);
|
|
90
196
|
const def = {
|
|
91
197
|
name,
|
|
92
198
|
argsSchema: options.args,
|
|
93
199
|
isPublic: options.public === true,
|
|
94
|
-
|
|
200
|
+
version: options.version,
|
|
201
|
+
maxDurationMs: maxActiveDurationMs,
|
|
202
|
+
maxActiveDurationMs,
|
|
203
|
+
lifecycleTimeoutMs,
|
|
204
|
+
concurrency: normalizeConcurrency(options.concurrency),
|
|
95
205
|
maxRetries,
|
|
96
206
|
handler: options.handler,
|
|
97
207
|
};
|
|
@@ -121,15 +231,27 @@ export function workflow(name, options) {
|
|
|
121
231
|
${workflowId},
|
|
122
232
|
${name},
|
|
123
233
|
${JSON.stringify(persistedArgs)}::jsonb,
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
234
|
+
${realtimeToken},
|
|
235
|
+
'pending',
|
|
236
|
+
0,
|
|
237
|
+
${maxRetries},
|
|
238
|
+
${maxActiveDurationMs},
|
|
239
|
+
${ownerId}
|
|
130
240
|
)
|
|
131
241
|
`);
|
|
242
|
+
let insertedWorkflowV2 = false;
|
|
132
243
|
try {
|
|
244
|
+
insertedWorkflowV2 = await tryInsertWorkflowV2Run({
|
|
245
|
+
db: ctx.unsafeDb,
|
|
246
|
+
workflowId,
|
|
247
|
+
workflowName: name,
|
|
248
|
+
workflowVersion: options.version ?? null,
|
|
249
|
+
args: persistedArgs,
|
|
250
|
+
userId: ownerId,
|
|
251
|
+
maxActiveDurationMs,
|
|
252
|
+
lifecycleTimeoutMs,
|
|
253
|
+
maxRetries,
|
|
254
|
+
});
|
|
133
255
|
const scheduledJobId = ctx.scheduler.runAfter(0, resumeAction, { workflowId });
|
|
134
256
|
return {
|
|
135
257
|
id: workflowId,
|
|
@@ -143,6 +265,9 @@ export function workflow(name, options) {
|
|
|
143
265
|
DELETE FROM _gencow_workflows
|
|
144
266
|
WHERE id = ${workflowId}
|
|
145
267
|
`);
|
|
268
|
+
if (insertedWorkflowV2) {
|
|
269
|
+
await tryDeleteWorkflowV2Run(ctx.unsafeDb, workflowId);
|
|
270
|
+
}
|
|
146
271
|
throw error;
|
|
147
272
|
}
|
|
148
273
|
},
|
package/dist/workflows-api.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { sql } from "drizzle-orm";
|
|
2
|
-
import {
|
|
2
|
+
import { query } from "./reactive-query.js";
|
|
3
|
+
import { mutation } from "./reactive-mutation.js";
|
|
3
4
|
import { createWorkflowRealtimeToken, deserializeWorkflowValue, getWorkflowResumeActionName, getWorkflowRealtimeKey, serializeWorkflowValue, } from "./workflow.js";
|
|
4
5
|
import { GencowValidationError, v } from "./v.js";
|
|
5
6
|
import { deriveWorkflowStatus } from "./workflow-types.js";
|
|
@@ -40,6 +41,7 @@ function mapWorkflowSummary(row) {
|
|
|
40
41
|
derivedStatus: deriveWorkflowStatus(row.status, row.current_step),
|
|
41
42
|
currentStep: row.current_step,
|
|
42
43
|
error: row.error,
|
|
44
|
+
errorCode: row.error_code,
|
|
43
45
|
retryCount: row.retry_count,
|
|
44
46
|
maxRetries: row.max_retries,
|
|
45
47
|
maxDurationMs: Number(row.max_duration_ms),
|
|
@@ -125,9 +127,66 @@ async function loadWorkflowSignalTarget(db, workflowId) {
|
|
|
125
127
|
FROM _gencow_workflows
|
|
126
128
|
WHERE id = ${workflowId}
|
|
127
129
|
LIMIT 1
|
|
128
|
-
|
|
130
|
+
`);
|
|
129
131
|
return rowsFromResult(result)[0] ?? null;
|
|
130
132
|
}
|
|
133
|
+
function isMissingWorkflowV2SignalSchemaError(error) {
|
|
134
|
+
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
135
|
+
const cause = error && typeof error === "object" && "cause" in error ? error.cause : null;
|
|
136
|
+
const causeCode = cause && typeof cause === "object" && "code" in cause ? String(cause.code) : "";
|
|
137
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
138
|
+
return (code === "42P01" ||
|
|
139
|
+
code === "42703" ||
|
|
140
|
+
causeCode === "42P01" ||
|
|
141
|
+
causeCode === "42703" ||
|
|
142
|
+
((code === "23503" || causeCode === "23503") && message.includes("_gencow_workflow_signals_v2")) ||
|
|
143
|
+
message.includes('relation "_gencow_workflow_signals_v2" does not exist') ||
|
|
144
|
+
message.includes("relation _gencow_workflow_signals_v2 does not exist") ||
|
|
145
|
+
message.includes('relation "_gencow_workflow_runs_v2" does not exist') ||
|
|
146
|
+
message.includes("relation _gencow_workflow_runs_v2 does not exist"));
|
|
147
|
+
}
|
|
148
|
+
async function tryRecordWorkflowV2Signal(options) {
|
|
149
|
+
try {
|
|
150
|
+
await options.db.execute(sql `
|
|
151
|
+
WITH inserted AS (
|
|
152
|
+
INSERT INTO _gencow_workflow_signals_v2 (
|
|
153
|
+
id,
|
|
154
|
+
run_id,
|
|
155
|
+
event_name,
|
|
156
|
+
payload_json,
|
|
157
|
+
idempotency_key
|
|
158
|
+
)
|
|
159
|
+
VALUES (
|
|
160
|
+
${crypto.randomUUID()},
|
|
161
|
+
${options.workflowId},
|
|
162
|
+
${options.event},
|
|
163
|
+
${JSON.stringify(options.payload)}::jsonb,
|
|
164
|
+
${crypto.randomUUID()}
|
|
165
|
+
)
|
|
166
|
+
RETURNING run_id
|
|
167
|
+
)
|
|
168
|
+
UPDATE _gencow_workflow_runs_v2 run
|
|
169
|
+
SET
|
|
170
|
+
status = 'queued',
|
|
171
|
+
runnable_at = NOW(),
|
|
172
|
+
lease_owner = NULL,
|
|
173
|
+
lease_expires_at = NULL,
|
|
174
|
+
heartbeat_at = NULL,
|
|
175
|
+
updated_at = NOW()
|
|
176
|
+
FROM inserted
|
|
177
|
+
WHERE run.id = inserted.run_id
|
|
178
|
+
AND run.status = 'waiting'
|
|
179
|
+
AND run.completed_at IS NULL
|
|
180
|
+
AND run.cancel_requested_at IS NULL
|
|
181
|
+
`);
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
if (isMissingWorkflowV2SignalSchemaError(error))
|
|
186
|
+
return false;
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
131
190
|
export async function loadWorkflowSnapshot(db, workflowId, options) {
|
|
132
191
|
const workflowResult = await db.execute(sql `
|
|
133
192
|
SELECT
|
|
@@ -138,6 +197,7 @@ export async function loadWorkflowSnapshot(db, workflowId, options) {
|
|
|
138
197
|
current_step,
|
|
139
198
|
result,
|
|
140
199
|
error,
|
|
200
|
+
error_code,
|
|
141
201
|
retry_count,
|
|
142
202
|
max_retries,
|
|
143
203
|
max_duration_ms,
|
|
@@ -237,9 +297,15 @@ export function registerWorkflowsApi() {
|
|
|
237
297
|
${crypto.randomUUID()},
|
|
238
298
|
${workflow.id},
|
|
239
299
|
${normalizedEvent},
|
|
240
|
-
|
|
300
|
+
${JSON.stringify(persistedPayload)}::jsonb
|
|
241
301
|
)
|
|
242
302
|
`);
|
|
303
|
+
await tryRecordWorkflowV2Signal({
|
|
304
|
+
db: ctx.unsafeDb,
|
|
305
|
+
workflowId: workflow.id,
|
|
306
|
+
event: normalizedEvent,
|
|
307
|
+
payload: persistedPayload,
|
|
308
|
+
});
|
|
243
309
|
let scheduledJobId = null;
|
|
244
310
|
if (workflow.status === "pending" && workflow.current_step?.startsWith("wait:")) {
|
|
245
311
|
try {
|
|
@@ -279,6 +345,7 @@ export function registerWorkflowsApi() {
|
|
|
279
345
|
current_step,
|
|
280
346
|
result,
|
|
281
347
|
error,
|
|
348
|
+
error_code,
|
|
282
349
|
retry_count,
|
|
283
350
|
max_retries,
|
|
284
351
|
max_duration_ms,
|
|
@@ -300,6 +367,7 @@ export function registerWorkflowsApi() {
|
|
|
300
367
|
current_step,
|
|
301
368
|
result,
|
|
302
369
|
error,
|
|
370
|
+
error_code,
|
|
303
371
|
retry_count,
|
|
304
372
|
max_retries,
|
|
305
373
|
max_duration_ms,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gencow/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.29",
|
|
4
4
|
"description": "Gencow core library — defineQuery, defineMutation, reactive subscriptions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -21,7 +21,16 @@
|
|
|
21
21
|
"dist/",
|
|
22
22
|
"src/"
|
|
23
23
|
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"db:generate:fixture-basic": "drizzle-kit generate --config ./src/__tests__/fixtures/basic/drizzle.config.ts",
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"test:coverage": "node ../../scripts/run-package-coverage.mjs",
|
|
28
|
+
"coverage:compare": "node ../../scripts/compare-package-coverage.mjs",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"prepublishOnly": "npm run build"
|
|
31
|
+
},
|
|
24
32
|
"dependencies": {
|
|
33
|
+
"@standard-schema/spec": "^1.1.0",
|
|
25
34
|
"node-cron": "^4.2.1"
|
|
26
35
|
},
|
|
27
36
|
"peerDependencies": {
|
|
@@ -39,10 +48,5 @@
|
|
|
39
48
|
"hono": "^4.12.0",
|
|
40
49
|
"typescript": "^5.9.3",
|
|
41
50
|
"uuid": "^13.0.0"
|
|
42
|
-
},
|
|
43
|
-
"scripts": {
|
|
44
|
-
"db:generate:fixture-basic": "drizzle-kit generate --config ./src/__tests__/fixtures/basic/drizzle.config.ts",
|
|
45
|
-
"build": "tsc",
|
|
46
|
-
"typecheck": "tsc --noEmit"
|
|
47
51
|
}
|
|
48
|
-
}
|
|
52
|
+
}
|
package/src/auth-config.ts
CHANGED
|
@@ -10,6 +10,20 @@
|
|
|
10
10
|
|
|
11
11
|
// ─── Email Verification ──────────────────────────────────
|
|
12
12
|
|
|
13
|
+
export interface AuthHookContext {
|
|
14
|
+
db?: unknown;
|
|
15
|
+
request?: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AuthUserLike {
|
|
19
|
+
id?: string;
|
|
20
|
+
email: string;
|
|
21
|
+
name?: string | null;
|
|
22
|
+
emailVerified?: boolean;
|
|
23
|
+
createdAt?: Date | string;
|
|
24
|
+
updatedAt?: Date | string;
|
|
25
|
+
}
|
|
26
|
+
|
|
13
27
|
export interface AuthEmailVerification {
|
|
14
28
|
/** 가입 시 인증 메일 자동 발송 (default: true) */
|
|
15
29
|
sendOnSignUp?: boolean;
|
|
@@ -19,18 +33,105 @@ export interface AuthEmailVerification {
|
|
|
19
33
|
autoSignInAfterVerification?: boolean;
|
|
20
34
|
/** 인증 메일 발송 함수 — 사용자가 직접 구현 */
|
|
21
35
|
sendVerificationEmail: (data: {
|
|
22
|
-
user:
|
|
36
|
+
user: AuthUserLike;
|
|
23
37
|
url: string;
|
|
24
38
|
token: string;
|
|
25
|
-
}) => Promise<void>;
|
|
39
|
+
}, context?: AuthHookContext) => Promise<void>;
|
|
40
|
+
/** 인증 직전 훅 — better-auth의 emailVerification.beforeEmailVerification으로 전달 */
|
|
41
|
+
beforeEmailVerification?: (user: AuthUserLike, context?: AuthHookContext) => Promise<void>;
|
|
42
|
+
/** 인증 완료 훅 — 환영 메일 등 idempotent 후처리에 사용 */
|
|
43
|
+
afterEmailVerification?: (user: AuthUserLike, context?: AuthHookContext) => Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface AuthPasswordReset {
|
|
47
|
+
/** 비밀번호 재설정 메일 발송 함수 — reset URL은 반드시 사용자에게 전달되어야 한다. */
|
|
48
|
+
sendResetPassword: (data: {
|
|
49
|
+
user: AuthUserLike;
|
|
50
|
+
url: string;
|
|
51
|
+
token: string;
|
|
52
|
+
}, context?: AuthHookContext) => Promise<void>;
|
|
53
|
+
/** 비밀번호 재설정 완료 후 훅 */
|
|
54
|
+
onPasswordReset?: (data: { user: AuthUserLike }, context?: AuthHookContext) => Promise<void>;
|
|
55
|
+
/** reset token 유효 시간(초). default: better-auth 기본값 3600초 */
|
|
56
|
+
resetPasswordTokenExpiresIn?: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface AuthEvents {
|
|
60
|
+
/** 회원가입 성공 후 실행. 운영 알림처럼 실패해도 가입을 막지 않아야 하는 작업에 사용. */
|
|
61
|
+
afterSignUp?: (data: { user: AuthUserLike; inviteCode?: string | null }, context?: AuthHookContext) => Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── Social Login / OAuth ───────────────────────────────
|
|
65
|
+
|
|
66
|
+
export interface SocialProviderConfig {
|
|
67
|
+
clientId: string;
|
|
68
|
+
clientSecret: string;
|
|
69
|
+
/** Provider-specific callback override. Most apps should leave this unset. */
|
|
70
|
+
redirectURI?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface OAuthUserInfo {
|
|
74
|
+
id: string;
|
|
75
|
+
email: string;
|
|
76
|
+
name?: string;
|
|
77
|
+
image?: string;
|
|
78
|
+
emailVerified?: boolean;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface CustomOAuthProvider {
|
|
82
|
+
clientId: string;
|
|
83
|
+
clientSecret?: string;
|
|
84
|
+
authorizationUrl: string;
|
|
85
|
+
tokenUrl: string;
|
|
86
|
+
userInfoUrl: string;
|
|
87
|
+
scopes?: string[];
|
|
88
|
+
pkce?: boolean;
|
|
89
|
+
/** Provider-specific callback override. Most apps should leave this unset. */
|
|
90
|
+
redirectURI?: string;
|
|
91
|
+
/** Map provider profile responses such as Naver/Kakao into better-auth user fields. */
|
|
92
|
+
mapProfileToUser?: (profile: Record<string, unknown>) => Partial<OAuthUserInfo> | Promise<Partial<OAuthUserInfo>>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type KakaoConfig = SocialProviderConfig & Partial<CustomOAuthProvider>;
|
|
96
|
+
export type NaverConfig = SocialProviderConfig & Partial<CustomOAuthProvider>;
|
|
97
|
+
|
|
98
|
+
export type SocialProvidersConfig = {
|
|
99
|
+
google?: SocialProviderConfig;
|
|
100
|
+
apple?: SocialProviderConfig;
|
|
101
|
+
/**
|
|
102
|
+
* Kakao/Naver use Gencow's built-in OAuth endpoint defaults.
|
|
103
|
+
* Override any CustomOAuthProvider field when a provider dashboard requires it.
|
|
104
|
+
*/
|
|
105
|
+
kakao?: KakaoConfig;
|
|
106
|
+
naver?: NaverConfig;
|
|
107
|
+
/** Additional OAuth providers can be configured with explicit OAuth endpoints. */
|
|
108
|
+
[providerId: string]:
|
|
109
|
+
| SocialProviderConfig
|
|
110
|
+
| KakaoConfig
|
|
111
|
+
| NaverConfig
|
|
112
|
+
| CustomOAuthProvider
|
|
113
|
+
| undefined;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export interface AuthOAuthConfig {
|
|
117
|
+
/**
|
|
118
|
+
* Frontend route that receives the short-lived OAuth handoff code.
|
|
119
|
+
* If omitted, Gencow uses APP_PUBLIC_DOMAIN + /auth/callback, then falls back to the auth server origin.
|
|
120
|
+
*/
|
|
121
|
+
callbackURL?: string;
|
|
122
|
+
/** Extra allowed frontend callback URLs for local development or multi-frontend apps. */
|
|
123
|
+
allowedCallbackURLs?: string[];
|
|
26
124
|
}
|
|
27
125
|
|
|
28
126
|
// ─── Auth Config ─────────────────────────────────────────
|
|
29
127
|
|
|
30
128
|
export interface GencowAuthConfig {
|
|
31
129
|
emailVerification?: AuthEmailVerification;
|
|
130
|
+
socialProviders?: SocialProvidersConfig;
|
|
131
|
+
oauth?: AuthOAuthConfig;
|
|
132
|
+
passwordReset?: AuthPasswordReset;
|
|
133
|
+
events?: AuthEvents;
|
|
32
134
|
// 확장 예정:
|
|
33
|
-
// socialProviders?: { ... }
|
|
34
135
|
// passwordPolicy?: { ... }
|
|
35
136
|
// sessionExpiry?: number
|
|
36
137
|
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-facing project configuration for `gencow.config.ts`.
|
|
3
|
+
*
|
|
4
|
+
* The config file is intentionally small today, but we expose a stable typed
|
|
5
|
+
* contract so editors can offer autocomplete and inline explanations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Cloud deployment settings written to `gencow.config.ts`.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* deploy: {
|
|
14
|
+
* app: "my-todo",
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export interface GencowDeployConfig {
|
|
19
|
+
/**
|
|
20
|
+
* Default cloud app name to target for CLI flows that attach project metadata.
|
|
21
|
+
*
|
|
22
|
+
* This is commonly added automatically after `gencow app create`.
|
|
23
|
+
*/
|
|
24
|
+
app?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Local database settings for the Gencow dev server.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* db: {
|
|
33
|
+
* url: "./.gencow/data",
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export interface GencowDbConfig {
|
|
38
|
+
/**
|
|
39
|
+
* Local database path used by the default PGlite setup.
|
|
40
|
+
*
|
|
41
|
+
* Default: `"./.gencow/data"`
|
|
42
|
+
*/
|
|
43
|
+
url?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Project configuration for `gencow.config.ts`.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* import { defineConfig } from "@gencow/core";
|
|
52
|
+
*
|
|
53
|
+
* export default defineConfig({
|
|
54
|
+
* functionsDir: "./gencow",
|
|
55
|
+
* schema: ["./gencow/schema.ts", "./gencow/auth-schema.ts"],
|
|
56
|
+
* codegenOutDir: "./src/gencow",
|
|
57
|
+
* storage: "./.gencow/uploads",
|
|
58
|
+
* db: { url: "./.gencow/data" },
|
|
59
|
+
* port: 5456,
|
|
60
|
+
* deploy: { app: "my-app" },
|
|
61
|
+
* });
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export interface GencowConfig {
|
|
65
|
+
/**
|
|
66
|
+
* Directory containing your backend modules such as `index.ts`, `schema.ts`,
|
|
67
|
+
* and optional files like `crons.ts` or `seed.ts`.
|
|
68
|
+
*
|
|
69
|
+
* Default: `"./gencow"`
|
|
70
|
+
*/
|
|
71
|
+
functionsDir?: string;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Path to the Drizzle schema file, or multiple schema files when your project
|
|
75
|
+
* splits auth and application tables.
|
|
76
|
+
*
|
|
77
|
+
* Default: `"./gencow/schema.ts"`
|
|
78
|
+
*/
|
|
79
|
+
schema?: string | string[];
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Output directory for generated frontend codegen artifacts such as `api.ts`
|
|
83
|
+
* and declaration files.
|
|
84
|
+
*
|
|
85
|
+
* Default: `"./src/gencow"`
|
|
86
|
+
*/
|
|
87
|
+
codegenOutDir?: string;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Directory used for uploaded file storage in local development.
|
|
91
|
+
*
|
|
92
|
+
* Default: `"./.gencow/uploads"`
|
|
93
|
+
*/
|
|
94
|
+
storage?: string;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Local database configuration.
|
|
98
|
+
*/
|
|
99
|
+
db?: GencowDbConfig;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Port used by the local dev server.
|
|
103
|
+
*
|
|
104
|
+
* Default: `5456`
|
|
105
|
+
*/
|
|
106
|
+
port?: number;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Optional cloud deployment metadata.
|
|
110
|
+
*/
|
|
111
|
+
deploy?: GencowDeployConfig;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Helper for authoring typed `gencow.config.ts` files with editor autocomplete.
|
|
116
|
+
*/
|
|
117
|
+
export function defineConfig(config: GencowConfig): GencowConfig {
|
|
118
|
+
return config;
|
|
119
|
+
}
|