@tangle-network/agent-app 0.30.0 → 0.31.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/dist/IntakeInterview-WHVKMNQW.js +8 -0
- package/dist/IntakeInterview-WHVKMNQW.js.map +1 -0
- package/dist/chunk-4K7BZYO7.js +124 -0
- package/dist/chunk-4K7BZYO7.js.map +1 -0
- package/dist/chunk-5I72FRZY.js +212 -0
- package/dist/chunk-5I72FRZY.js.map +1 -0
- package/dist/{chunk-WOVCWPFF.js → chunk-FJUOBNIY.js} +1 -1
- package/dist/chunk-ND3XEGBE.js +119 -0
- package/dist/chunk-ND3XEGBE.js.map +1 -0
- package/dist/chunk-USYXHDKE.js +30 -0
- package/dist/chunk-USYXHDKE.js.map +1 -0
- package/dist/design-canvas-react/index.js +1 -1
- package/dist/design-canvas-react/lazy.js +1 -1
- package/dist/intakes/api.d.ts +59 -0
- package/dist/intakes/api.js +63 -0
- package/dist/intakes/api.js.map +1 -0
- package/dist/intakes/drizzle.d.ts +385 -0
- package/dist/intakes/drizzle.js +61 -0
- package/dist/intakes/drizzle.js.map +1 -0
- package/dist/intakes/index.d.ts +42 -0
- package/dist/intakes/index.js +31 -0
- package/dist/intakes/index.js.map +1 -0
- package/dist/intakes-react/index.d.ts +48 -0
- package/dist/intakes-react/index.js +8 -0
- package/dist/intakes-react/index.js.map +1 -0
- package/dist/intakes-react/lazy.d.ts +8 -0
- package/dist/intakes-react/lazy.js +9 -0
- package/dist/intakes-react/lazy.js.map +1 -0
- package/dist/model-Cuj7d-xT.d.ts +116 -0
- package/package.json +26 -1
- /package/dist/{chunk-WOVCWPFF.js.map → chunk-FJUOBNIY.js.map} +0 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { I as IntakeAnswerValue, a as IntakeQuestion, b as IntakeGraph } from '../model-Cuj7d-xT.js';
|
|
2
|
+
import { IntakeStore } from './drizzle.js';
|
|
3
|
+
import 'drizzle-orm/sqlite-core';
|
|
4
|
+
import './index.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Framework-neutral intake API: the get-current / save-answer / complete logic,
|
|
8
|
+
* lifted out of any one app's route file. Each app mounts these in its own
|
|
9
|
+
* route with its own auth — the handlers take an already-resolved store (the
|
|
10
|
+
* caller built it from `createUserIntakeStore` / `createProjectIntakeStore`
|
|
11
|
+
* after running RBAC), the intake graph, and the parsed inputs. They return
|
|
12
|
+
* web-standard `Response`s (available in Workers, Node 18+, Deno, browsers), so
|
|
13
|
+
* "framework-neutral" is literal: no Remix/React-Router/Express import anywhere.
|
|
14
|
+
*
|
|
15
|
+
* The handlers own the graph→HTTP mapping the UI needs: `getCurrentIntake`
|
|
16
|
+
* returns the loaded state PLUS the next question to ask (the question-graph
|
|
17
|
+
* traversal from the `./intakes` leaf) and the progress counter, so the client
|
|
18
|
+
* renders one question at a time without re-deriving the graph. Store
|
|
19
|
+
* `IntakeError`s map to typed 4xx codes, never a generic 500 — an invalid
|
|
20
|
+
* answer is a 400, an incomplete-complete is a 409, never a silent success.
|
|
21
|
+
*
|
|
22
|
+
* Imports `drizzle-orm` transitively (through the store types), so this is a
|
|
23
|
+
* subpath, never re-exported from root.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** What the client renders: the saved state, the next prompt, and progress. */
|
|
27
|
+
interface CurrentIntakeView {
|
|
28
|
+
graphId: string;
|
|
29
|
+
title: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
answers: Record<string, IntakeAnswerValue>;
|
|
32
|
+
/** The next question to ask, or null when the interview is done. */
|
|
33
|
+
nextQuestion: IntakeQuestion | null;
|
|
34
|
+
completed: boolean;
|
|
35
|
+
completedAt: string | null;
|
|
36
|
+
progress: {
|
|
37
|
+
answered: number;
|
|
38
|
+
total: number;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
interface IntakeApiOptions {
|
|
42
|
+
store: IntakeStore;
|
|
43
|
+
graph: IntakeGraph;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Build the intake API bound to one store + graph. Returns three handlers; an
|
|
47
|
+
* app maps its route methods onto them (GET→getCurrentIntake, POST→saveAnswer,
|
|
48
|
+
* a complete action→completeIntake).
|
|
49
|
+
*/
|
|
50
|
+
declare function createIntakeApi(opts: IntakeApiOptions): {
|
|
51
|
+
getCurrentIntake: () => Promise<Response>;
|
|
52
|
+
saveAnswer: (input: {
|
|
53
|
+
questionId?: string;
|
|
54
|
+
value?: IntakeAnswerValue;
|
|
55
|
+
}) => Promise<Response>;
|
|
56
|
+
completeIntake: () => Promise<Response>;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export { type CurrentIntakeView, type IntakeApiOptions, createIntakeApi };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IntakeError
|
|
3
|
+
} from "../chunk-4K7BZYO7.js";
|
|
4
|
+
import "../chunk-USYXHDKE.js";
|
|
5
|
+
import {
|
|
6
|
+
intakeProgress,
|
|
7
|
+
nextQuestion
|
|
8
|
+
} from "../chunk-ND3XEGBE.js";
|
|
9
|
+
|
|
10
|
+
// src/intakes/api.ts
|
|
11
|
+
function createIntakeApi(opts) {
|
|
12
|
+
const { store, graph } = opts;
|
|
13
|
+
function view(state) {
|
|
14
|
+
return {
|
|
15
|
+
graphId: graph.id,
|
|
16
|
+
title: graph.title,
|
|
17
|
+
...graph.description ? { description: graph.description } : {},
|
|
18
|
+
answers: state.payload.answers,
|
|
19
|
+
nextQuestion: nextQuestion(graph, state.payload.answers),
|
|
20
|
+
completed: state.completed,
|
|
21
|
+
completedAt: state.completedAt ? state.completedAt.toISOString() : null,
|
|
22
|
+
progress: intakeProgress(graph, state.payload.answers)
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
async function getCurrentIntake() {
|
|
26
|
+
const state = await store.get();
|
|
27
|
+
return Response.json(view(state));
|
|
28
|
+
}
|
|
29
|
+
async function saveAnswer(input) {
|
|
30
|
+
if (!input.questionId) return Response.json({ error: "Missing questionId" }, { status: 400 });
|
|
31
|
+
try {
|
|
32
|
+
const state = await store.save(input.questionId, input.value ?? null);
|
|
33
|
+
return Response.json(view(state));
|
|
34
|
+
} catch (err) {
|
|
35
|
+
return intakeErrorResponse(err);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function completeIntake() {
|
|
39
|
+
try {
|
|
40
|
+
const state = await store.complete();
|
|
41
|
+
return Response.json(view(state));
|
|
42
|
+
} catch (err) {
|
|
43
|
+
return intakeErrorResponse(err);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { getCurrentIntake, saveAnswer, completeIntake };
|
|
47
|
+
}
|
|
48
|
+
var ERROR_STATUS = {
|
|
49
|
+
"invalid-answer": 400,
|
|
50
|
+
"unknown-question": 404,
|
|
51
|
+
incomplete: 409,
|
|
52
|
+
"stale-graph": 409
|
|
53
|
+
};
|
|
54
|
+
function intakeErrorResponse(err) {
|
|
55
|
+
if (err instanceof IntakeError) {
|
|
56
|
+
return Response.json({ error: err.message, code: err.code }, { status: ERROR_STATUS[err.code] ?? 400 });
|
|
57
|
+
}
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
export {
|
|
61
|
+
createIntakeApi
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/intakes/api.ts"],"sourcesContent":["/**\n * Framework-neutral intake API: the get-current / save-answer / complete logic,\n * lifted out of any one app's route file. Each app mounts these in its own\n * route with its own auth — the handlers take an already-resolved store (the\n * caller built it from `createUserIntakeStore` / `createProjectIntakeStore`\n * after running RBAC), the intake graph, and the parsed inputs. They return\n * web-standard `Response`s (available in Workers, Node 18+, Deno, browsers), so\n * \"framework-neutral\" is literal: no Remix/React-Router/Express import anywhere.\n *\n * The handlers own the graph→HTTP mapping the UI needs: `getCurrentIntake`\n * returns the loaded state PLUS the next question to ask (the question-graph\n * traversal from the `./intakes` leaf) and the progress counter, so the client\n * renders one question at a time without re-deriving the graph. Store\n * `IntakeError`s map to typed 4xx codes, never a generic 500 — an invalid\n * answer is a 400, an incomplete-complete is a 409, never a silent success.\n *\n * Imports `drizzle-orm` transitively (through the store types), so this is a\n * subpath, never re-exported from root.\n */\n\nimport {\n type IntakeAnswerValue,\n type IntakeGraph,\n type IntakeQuestion,\n intakeProgress,\n nextQuestion,\n} from './model'\nimport { type IntakeStore, IntakeError } from './drizzle/store'\n\n/** What the client renders: the saved state, the next prompt, and progress. */\nexport interface CurrentIntakeView {\n graphId: string\n title: string\n description?: string\n answers: Record<string, IntakeAnswerValue>\n /** The next question to ask, or null when the interview is done. */\n nextQuestion: IntakeQuestion | null\n completed: boolean\n completedAt: string | null\n progress: { answered: number; total: number }\n}\n\nexport interface IntakeApiOptions {\n store: IntakeStore\n graph: IntakeGraph\n}\n\n/**\n * Build the intake API bound to one store + graph. Returns three handlers; an\n * app maps its route methods onto them (GET→getCurrentIntake, POST→saveAnswer,\n * a complete action→completeIntake).\n */\nexport function createIntakeApi(opts: IntakeApiOptions) {\n const { store, graph } = opts\n\n function view(state: Awaited<ReturnType<IntakeStore['get']>>): CurrentIntakeView {\n return {\n graphId: graph.id,\n title: graph.title,\n ...(graph.description ? { description: graph.description } : {}),\n answers: state.payload.answers,\n nextQuestion: nextQuestion(graph, state.payload.answers),\n completed: state.completed,\n completedAt: state.completedAt ? state.completedAt.toISOString() : null,\n progress: intakeProgress(graph, state.payload.answers),\n }\n }\n\n async function getCurrentIntake(): Promise<Response> {\n const state = await store.get()\n return Response.json(view(state))\n }\n\n async function saveAnswer(input: { questionId?: string; value?: IntakeAnswerValue }): Promise<Response> {\n if (!input.questionId) return Response.json({ error: 'Missing questionId' }, { status: 400 })\n try {\n const state = await store.save(input.questionId, input.value ?? null)\n return Response.json(view(state))\n } catch (err) {\n return intakeErrorResponse(err)\n }\n }\n\n async function completeIntake(): Promise<Response> {\n try {\n const state = await store.complete()\n return Response.json(view(state))\n } catch (err) {\n return intakeErrorResponse(err)\n }\n }\n\n return { getCurrentIntake, saveAnswer, completeIntake }\n}\n\nconst ERROR_STATUS: Record<string, number> = {\n 'invalid-answer': 400,\n 'unknown-question': 404,\n incomplete: 409,\n 'stale-graph': 409,\n}\n\nfunction intakeErrorResponse(err: unknown): Response {\n if (err instanceof IntakeError) {\n return Response.json({ error: err.message, code: err.code }, { status: ERROR_STATUS[err.code] ?? 400 })\n }\n throw err\n}\n"],"mappings":";;;;;;;;;;AAoDO,SAAS,gBAAgB,MAAwB;AACtD,QAAM,EAAE,OAAO,MAAM,IAAI;AAEzB,WAAS,KAAK,OAAmE;AAC/E,WAAO;AAAA,MACL,SAAS,MAAM;AAAA,MACf,OAAO,MAAM;AAAA,MACb,GAAI,MAAM,cAAc,EAAE,aAAa,MAAM,YAAY,IAAI,CAAC;AAAA,MAC9D,SAAS,MAAM,QAAQ;AAAA,MACvB,cAAc,aAAa,OAAO,MAAM,QAAQ,OAAO;AAAA,MACvD,WAAW,MAAM;AAAA,MACjB,aAAa,MAAM,cAAc,MAAM,YAAY,YAAY,IAAI;AAAA,MACnE,UAAU,eAAe,OAAO,MAAM,QAAQ,OAAO;AAAA,IACvD;AAAA,EACF;AAEA,iBAAe,mBAAsC;AACnD,UAAM,QAAQ,MAAM,MAAM,IAAI;AAC9B,WAAO,SAAS,KAAK,KAAK,KAAK,CAAC;AAAA,EAClC;AAEA,iBAAe,WAAW,OAA8E;AACtG,QAAI,CAAC,MAAM,WAAY,QAAO,SAAS,KAAK,EAAE,OAAO,qBAAqB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAC5F,QAAI;AACF,YAAM,QAAQ,MAAM,MAAM,KAAK,MAAM,YAAY,MAAM,SAAS,IAAI;AACpE,aAAO,SAAS,KAAK,KAAK,KAAK,CAAC;AAAA,IAClC,SAAS,KAAK;AACZ,aAAO,oBAAoB,GAAG;AAAA,IAChC;AAAA,EACF;AAEA,iBAAe,iBAAoC;AACjD,QAAI;AACF,YAAM,QAAQ,MAAM,MAAM,SAAS;AACnC,aAAO,SAAS,KAAK,KAAK,KAAK,CAAC;AAAA,IAClC,SAAS,KAAK;AACZ,aAAO,oBAAoB,GAAG;AAAA,IAChC;AAAA,EACF;AAEA,SAAO,EAAE,kBAAkB,YAAY,eAAe;AACxD;AAEA,IAAM,eAAuC;AAAA,EAC3C,kBAAkB;AAAA,EAClB,oBAAoB;AAAA,EACpB,YAAY;AAAA,EACZ,eAAe;AACjB;AAEA,SAAS,oBAAoB,KAAwB;AACnD,MAAI,eAAe,aAAa;AAC9B,WAAO,SAAS,KAAK,EAAE,OAAO,IAAI,SAAS,MAAM,IAAI,KAAK,GAAG,EAAE,QAAQ,aAAa,IAAI,IAAI,KAAK,IAAI,CAAC;AAAA,EACxG;AACA,QAAM;AACR;","names":[]}
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import * as drizzle_orm_sqlite_core from 'drizzle-orm/sqlite-core';
|
|
2
|
+
import { AnySQLiteTable, AnySQLiteColumn, BaseSQLiteDatabase } from 'drizzle-orm/sqlite-core';
|
|
3
|
+
import { I as IntakeAnswerValue, b as IntakeGraph } from '../model-Cuj7d-xT.js';
|
|
4
|
+
import { IntakePayload } from './index.js';
|
|
5
|
+
|
|
6
|
+
/** A product table referenced by FK — only the `id` column is touched. */
|
|
7
|
+
type IntakeParentTable = AnySQLiteTable & {
|
|
8
|
+
id: AnySQLiteColumn;
|
|
9
|
+
};
|
|
10
|
+
interface CreateIntakeTablesOptions {
|
|
11
|
+
/** The product's user table — user-intake rows reference `userTable.id`. */
|
|
12
|
+
userTable: IntakeParentTable;
|
|
13
|
+
/**
|
|
14
|
+
* The product's workspace table — project-intake rows reference
|
|
15
|
+
* `workspaceTable.id`. OPTIONAL: omit it for a single-user / non-workspace
|
|
16
|
+
* app that wants per-user onboarding only. When omitted, no `projectIntake`
|
|
17
|
+
* table is created.
|
|
18
|
+
*/
|
|
19
|
+
workspaceTable?: IntakeParentTable;
|
|
20
|
+
}
|
|
21
|
+
declare function createUserIntakeTable(userTable: IntakeParentTable): drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
|
|
22
|
+
name: "user_intake";
|
|
23
|
+
schema: undefined;
|
|
24
|
+
columns: {
|
|
25
|
+
id: drizzle_orm_sqlite_core.SQLiteColumn<{
|
|
26
|
+
name: "id";
|
|
27
|
+
tableName: "user_intake";
|
|
28
|
+
dataType: "string";
|
|
29
|
+
columnType: "SQLiteText";
|
|
30
|
+
data: string;
|
|
31
|
+
driverParam: string;
|
|
32
|
+
notNull: true;
|
|
33
|
+
hasDefault: true;
|
|
34
|
+
isPrimaryKey: true;
|
|
35
|
+
isAutoincrement: false;
|
|
36
|
+
hasRuntimeDefault: false;
|
|
37
|
+
enumValues: [string, ...string[]];
|
|
38
|
+
baseColumn: never;
|
|
39
|
+
identity: undefined;
|
|
40
|
+
generated: undefined;
|
|
41
|
+
}, {}, {
|
|
42
|
+
length: number | undefined;
|
|
43
|
+
}>;
|
|
44
|
+
userId: drizzle_orm_sqlite_core.SQLiteColumn<{
|
|
45
|
+
name: "user_id";
|
|
46
|
+
tableName: "user_intake";
|
|
47
|
+
dataType: "string";
|
|
48
|
+
columnType: "SQLiteText";
|
|
49
|
+
data: string;
|
|
50
|
+
driverParam: string;
|
|
51
|
+
notNull: true;
|
|
52
|
+
hasDefault: false;
|
|
53
|
+
isPrimaryKey: false;
|
|
54
|
+
isAutoincrement: false;
|
|
55
|
+
hasRuntimeDefault: false;
|
|
56
|
+
enumValues: [string, ...string[]];
|
|
57
|
+
baseColumn: never;
|
|
58
|
+
identity: undefined;
|
|
59
|
+
generated: undefined;
|
|
60
|
+
}, {}, {
|
|
61
|
+
length: number | undefined;
|
|
62
|
+
}>;
|
|
63
|
+
graphId: drizzle_orm_sqlite_core.SQLiteColumn<{
|
|
64
|
+
name: "graph_id";
|
|
65
|
+
tableName: "user_intake";
|
|
66
|
+
dataType: "string";
|
|
67
|
+
columnType: "SQLiteText";
|
|
68
|
+
data: string;
|
|
69
|
+
driverParam: string;
|
|
70
|
+
notNull: true;
|
|
71
|
+
hasDefault: false;
|
|
72
|
+
isPrimaryKey: false;
|
|
73
|
+
isAutoincrement: false;
|
|
74
|
+
hasRuntimeDefault: false;
|
|
75
|
+
enumValues: [string, ...string[]];
|
|
76
|
+
baseColumn: never;
|
|
77
|
+
identity: undefined;
|
|
78
|
+
generated: undefined;
|
|
79
|
+
}, {}, {
|
|
80
|
+
length: number | undefined;
|
|
81
|
+
}>;
|
|
82
|
+
payload: drizzle_orm_sqlite_core.SQLiteColumn<{
|
|
83
|
+
name: "payload";
|
|
84
|
+
tableName: "user_intake";
|
|
85
|
+
dataType: "json";
|
|
86
|
+
columnType: "SQLiteTextJson";
|
|
87
|
+
data: unknown;
|
|
88
|
+
driverParam: string;
|
|
89
|
+
notNull: true;
|
|
90
|
+
hasDefault: false;
|
|
91
|
+
isPrimaryKey: false;
|
|
92
|
+
isAutoincrement: false;
|
|
93
|
+
hasRuntimeDefault: false;
|
|
94
|
+
enumValues: undefined;
|
|
95
|
+
baseColumn: never;
|
|
96
|
+
identity: undefined;
|
|
97
|
+
generated: undefined;
|
|
98
|
+
}, {}, {}>;
|
|
99
|
+
completedAt: drizzle_orm_sqlite_core.SQLiteColumn<{
|
|
100
|
+
name: "completed_at";
|
|
101
|
+
tableName: "user_intake";
|
|
102
|
+
dataType: "date";
|
|
103
|
+
columnType: "SQLiteTimestamp";
|
|
104
|
+
data: Date;
|
|
105
|
+
driverParam: number;
|
|
106
|
+
notNull: false;
|
|
107
|
+
hasDefault: false;
|
|
108
|
+
isPrimaryKey: false;
|
|
109
|
+
isAutoincrement: false;
|
|
110
|
+
hasRuntimeDefault: false;
|
|
111
|
+
enumValues: undefined;
|
|
112
|
+
baseColumn: never;
|
|
113
|
+
identity: undefined;
|
|
114
|
+
generated: undefined;
|
|
115
|
+
}, {}, {}>;
|
|
116
|
+
createdAt: drizzle_orm_sqlite_core.SQLiteColumn<{
|
|
117
|
+
name: "created_at";
|
|
118
|
+
tableName: "user_intake";
|
|
119
|
+
dataType: "date";
|
|
120
|
+
columnType: "SQLiteTimestamp";
|
|
121
|
+
data: Date;
|
|
122
|
+
driverParam: number;
|
|
123
|
+
notNull: true;
|
|
124
|
+
hasDefault: true;
|
|
125
|
+
isPrimaryKey: false;
|
|
126
|
+
isAutoincrement: false;
|
|
127
|
+
hasRuntimeDefault: false;
|
|
128
|
+
enumValues: undefined;
|
|
129
|
+
baseColumn: never;
|
|
130
|
+
identity: undefined;
|
|
131
|
+
generated: undefined;
|
|
132
|
+
}, {}, {}>;
|
|
133
|
+
updatedAt: drizzle_orm_sqlite_core.SQLiteColumn<{
|
|
134
|
+
name: "updated_at";
|
|
135
|
+
tableName: "user_intake";
|
|
136
|
+
dataType: "date";
|
|
137
|
+
columnType: "SQLiteTimestamp";
|
|
138
|
+
data: Date;
|
|
139
|
+
driverParam: number;
|
|
140
|
+
notNull: true;
|
|
141
|
+
hasDefault: true;
|
|
142
|
+
isPrimaryKey: false;
|
|
143
|
+
isAutoincrement: false;
|
|
144
|
+
hasRuntimeDefault: false;
|
|
145
|
+
enumValues: undefined;
|
|
146
|
+
baseColumn: never;
|
|
147
|
+
identity: undefined;
|
|
148
|
+
generated: undefined;
|
|
149
|
+
}, {}, {}>;
|
|
150
|
+
};
|
|
151
|
+
dialect: "sqlite";
|
|
152
|
+
}>;
|
|
153
|
+
declare function createProjectIntakeTable(workspaceTable: IntakeParentTable): drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
|
|
154
|
+
name: "project_intake";
|
|
155
|
+
schema: undefined;
|
|
156
|
+
columns: {
|
|
157
|
+
id: drizzle_orm_sqlite_core.SQLiteColumn<{
|
|
158
|
+
name: "id";
|
|
159
|
+
tableName: "project_intake";
|
|
160
|
+
dataType: "string";
|
|
161
|
+
columnType: "SQLiteText";
|
|
162
|
+
data: string;
|
|
163
|
+
driverParam: string;
|
|
164
|
+
notNull: true;
|
|
165
|
+
hasDefault: true;
|
|
166
|
+
isPrimaryKey: true;
|
|
167
|
+
isAutoincrement: false;
|
|
168
|
+
hasRuntimeDefault: false;
|
|
169
|
+
enumValues: [string, ...string[]];
|
|
170
|
+
baseColumn: never;
|
|
171
|
+
identity: undefined;
|
|
172
|
+
generated: undefined;
|
|
173
|
+
}, {}, {
|
|
174
|
+
length: number | undefined;
|
|
175
|
+
}>;
|
|
176
|
+
workspaceId: drizzle_orm_sqlite_core.SQLiteColumn<{
|
|
177
|
+
name: "workspace_id";
|
|
178
|
+
tableName: "project_intake";
|
|
179
|
+
dataType: "string";
|
|
180
|
+
columnType: "SQLiteText";
|
|
181
|
+
data: string;
|
|
182
|
+
driverParam: string;
|
|
183
|
+
notNull: true;
|
|
184
|
+
hasDefault: false;
|
|
185
|
+
isPrimaryKey: false;
|
|
186
|
+
isAutoincrement: false;
|
|
187
|
+
hasRuntimeDefault: false;
|
|
188
|
+
enumValues: [string, ...string[]];
|
|
189
|
+
baseColumn: never;
|
|
190
|
+
identity: undefined;
|
|
191
|
+
generated: undefined;
|
|
192
|
+
}, {}, {
|
|
193
|
+
length: number | undefined;
|
|
194
|
+
}>;
|
|
195
|
+
graphId: drizzle_orm_sqlite_core.SQLiteColumn<{
|
|
196
|
+
name: "graph_id";
|
|
197
|
+
tableName: "project_intake";
|
|
198
|
+
dataType: "string";
|
|
199
|
+
columnType: "SQLiteText";
|
|
200
|
+
data: string;
|
|
201
|
+
driverParam: string;
|
|
202
|
+
notNull: true;
|
|
203
|
+
hasDefault: false;
|
|
204
|
+
isPrimaryKey: false;
|
|
205
|
+
isAutoincrement: false;
|
|
206
|
+
hasRuntimeDefault: false;
|
|
207
|
+
enumValues: [string, ...string[]];
|
|
208
|
+
baseColumn: never;
|
|
209
|
+
identity: undefined;
|
|
210
|
+
generated: undefined;
|
|
211
|
+
}, {}, {
|
|
212
|
+
length: number | undefined;
|
|
213
|
+
}>;
|
|
214
|
+
payload: drizzle_orm_sqlite_core.SQLiteColumn<{
|
|
215
|
+
name: "payload";
|
|
216
|
+
tableName: "project_intake";
|
|
217
|
+
dataType: "json";
|
|
218
|
+
columnType: "SQLiteTextJson";
|
|
219
|
+
data: unknown;
|
|
220
|
+
driverParam: string;
|
|
221
|
+
notNull: true;
|
|
222
|
+
hasDefault: false;
|
|
223
|
+
isPrimaryKey: false;
|
|
224
|
+
isAutoincrement: false;
|
|
225
|
+
hasRuntimeDefault: false;
|
|
226
|
+
enumValues: undefined;
|
|
227
|
+
baseColumn: never;
|
|
228
|
+
identity: undefined;
|
|
229
|
+
generated: undefined;
|
|
230
|
+
}, {}, {}>;
|
|
231
|
+
completedAt: drizzle_orm_sqlite_core.SQLiteColumn<{
|
|
232
|
+
name: "completed_at";
|
|
233
|
+
tableName: "project_intake";
|
|
234
|
+
dataType: "date";
|
|
235
|
+
columnType: "SQLiteTimestamp";
|
|
236
|
+
data: Date;
|
|
237
|
+
driverParam: number;
|
|
238
|
+
notNull: false;
|
|
239
|
+
hasDefault: false;
|
|
240
|
+
isPrimaryKey: false;
|
|
241
|
+
isAutoincrement: false;
|
|
242
|
+
hasRuntimeDefault: false;
|
|
243
|
+
enumValues: undefined;
|
|
244
|
+
baseColumn: never;
|
|
245
|
+
identity: undefined;
|
|
246
|
+
generated: undefined;
|
|
247
|
+
}, {}, {}>;
|
|
248
|
+
createdAt: drizzle_orm_sqlite_core.SQLiteColumn<{
|
|
249
|
+
name: "created_at";
|
|
250
|
+
tableName: "project_intake";
|
|
251
|
+
dataType: "date";
|
|
252
|
+
columnType: "SQLiteTimestamp";
|
|
253
|
+
data: Date;
|
|
254
|
+
driverParam: number;
|
|
255
|
+
notNull: true;
|
|
256
|
+
hasDefault: true;
|
|
257
|
+
isPrimaryKey: false;
|
|
258
|
+
isAutoincrement: false;
|
|
259
|
+
hasRuntimeDefault: false;
|
|
260
|
+
enumValues: undefined;
|
|
261
|
+
baseColumn: never;
|
|
262
|
+
identity: undefined;
|
|
263
|
+
generated: undefined;
|
|
264
|
+
}, {}, {}>;
|
|
265
|
+
updatedAt: drizzle_orm_sqlite_core.SQLiteColumn<{
|
|
266
|
+
name: "updated_at";
|
|
267
|
+
tableName: "project_intake";
|
|
268
|
+
dataType: "date";
|
|
269
|
+
columnType: "SQLiteTimestamp";
|
|
270
|
+
data: Date;
|
|
271
|
+
driverParam: number;
|
|
272
|
+
notNull: true;
|
|
273
|
+
hasDefault: true;
|
|
274
|
+
isPrimaryKey: false;
|
|
275
|
+
isAutoincrement: false;
|
|
276
|
+
hasRuntimeDefault: false;
|
|
277
|
+
enumValues: undefined;
|
|
278
|
+
baseColumn: never;
|
|
279
|
+
identity: undefined;
|
|
280
|
+
generated: undefined;
|
|
281
|
+
}, {}, {}>;
|
|
282
|
+
};
|
|
283
|
+
dialect: "sqlite";
|
|
284
|
+
}>;
|
|
285
|
+
/**
|
|
286
|
+
* Build the intake tables wired to the product's tables. Always returns
|
|
287
|
+
* `userIntake`; returns `projectIntake` only when `workspaceTable` is passed.
|
|
288
|
+
* The return type carries `projectIntake?` so a consumer that passes no
|
|
289
|
+
* workspace table cannot reference a table that does not exist.
|
|
290
|
+
*/
|
|
291
|
+
declare function createIntakeTables<O extends CreateIntakeTablesOptions>(opts: O): IntakeTables<O>;
|
|
292
|
+
type UserIntakeTable = ReturnType<typeof createUserIntakeTable>;
|
|
293
|
+
type ProjectIntakeTable = ReturnType<typeof createProjectIntakeTable>;
|
|
294
|
+
/**
|
|
295
|
+
* The tables returned for a given options shape: `projectIntake` is present in
|
|
296
|
+
* the type exactly when `workspaceTable` was provided.
|
|
297
|
+
*/
|
|
298
|
+
type IntakeTables<O extends CreateIntakeTablesOptions> = O extends {
|
|
299
|
+
workspaceTable: IntakeParentTable;
|
|
300
|
+
} ? {
|
|
301
|
+
userIntake: UserIntakeTable;
|
|
302
|
+
projectIntake: ProjectIntakeTable;
|
|
303
|
+
} : {
|
|
304
|
+
userIntake: UserIntakeTable;
|
|
305
|
+
projectIntake?: ProjectIntakeTable;
|
|
306
|
+
};
|
|
307
|
+
/** The union table shape, for code that handles either scope generically. */
|
|
308
|
+
type AnyIntakeTables = {
|
|
309
|
+
userIntake: UserIntakeTable;
|
|
310
|
+
projectIntake?: ProjectIntakeTable;
|
|
311
|
+
};
|
|
312
|
+
type UserIntakeRow = UserIntakeTable['$inferSelect'];
|
|
313
|
+
type ProjectIntakeRow = ProjectIntakeTable['$inferSelect'];
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Drizzle-backed store over the tables from `createIntakeTables`. One store
|
|
317
|
+
* builder per scope: `createUserIntakeStore` (keyed on userId) and
|
|
318
|
+
* `createProjectIntakeStore` (keyed on workspaceId). Each returns the same
|
|
319
|
+
* three-method surface — `get` / `save` / `complete` — so the api handlers and
|
|
320
|
+
* the UI talk to one shape regardless of scope.
|
|
321
|
+
*
|
|
322
|
+
* Works against any SQLite drizzle driver (better-sqlite3, D1, libsql): the
|
|
323
|
+
* builders are awaited, never `.run()`/`.all()`, so sync and async drivers
|
|
324
|
+
* behave identically.
|
|
325
|
+
*
|
|
326
|
+
* Validation is fail-loud and pure (from the `./intakes` leaf). `save` runs
|
|
327
|
+
* `validateAnswer` before writing, so an invalid answer can never enter the
|
|
328
|
+
* payload; `complete` runs `payloadComplete` and refuses to stamp an
|
|
329
|
+
* incomplete (or stale-graph) intake — it throws a typed `IntakeError` rather
|
|
330
|
+
* than silently writing a half-finished onboarding state.
|
|
331
|
+
*
|
|
332
|
+
* `getProjectIntakeStore` pins `workspaceId` in every WHERE clause; RBAC runs
|
|
333
|
+
* in the route before the store is built, but the scope key is enforced here
|
|
334
|
+
* too, so a leaked id can never read or write across the boundary.
|
|
335
|
+
*
|
|
336
|
+
* Imports `drizzle-orm`, so this is a subpath, never re-exported from root.
|
|
337
|
+
*/
|
|
338
|
+
|
|
339
|
+
/** Any SQLite drizzle database — `any` erases driver-specific generics so
|
|
340
|
+
* better-sqlite3, D1, and libsql handles all fit. */
|
|
341
|
+
type IntakeDatabase = BaseSQLiteDatabase<'sync' | 'async', any, any>;
|
|
342
|
+
/** A loaded intake: the payload plus whether it is complete against the graph. */
|
|
343
|
+
interface IntakeState {
|
|
344
|
+
payload: IntakePayload;
|
|
345
|
+
completed: boolean;
|
|
346
|
+
completedAt: Date | null;
|
|
347
|
+
}
|
|
348
|
+
type IntakeErrorCode = 'invalid-answer' | 'unknown-question' | 'incomplete' | 'stale-graph';
|
|
349
|
+
/** Thrown by store mutations on a refused write — callers map it to a 4xx. */
|
|
350
|
+
declare class IntakeError extends Error {
|
|
351
|
+
readonly code: IntakeErrorCode;
|
|
352
|
+
constructor(code: IntakeErrorCode, message: string);
|
|
353
|
+
}
|
|
354
|
+
/** The three-method store surface, identical for both scopes. */
|
|
355
|
+
interface IntakeStore {
|
|
356
|
+
/** Load the current intake, or seed an empty payload when none exists yet. */
|
|
357
|
+
get(): Promise<IntakeState>;
|
|
358
|
+
/** Validate and persist one answer; returns the updated state. */
|
|
359
|
+
save(questionId: string, value: IntakeAnswerValue): Promise<IntakeState>;
|
|
360
|
+
/** Stamp the intake complete; throws IntakeError when not yet completable. */
|
|
361
|
+
complete(): Promise<IntakeState>;
|
|
362
|
+
}
|
|
363
|
+
interface CreateUserIntakeStoreOptions {
|
|
364
|
+
db: IntakeDatabase;
|
|
365
|
+
/** The user-intake table from createIntakeTables. */
|
|
366
|
+
table: UserIntakeTable;
|
|
367
|
+
/** The intake definition this store collects answers against. */
|
|
368
|
+
graph: IntakeGraph;
|
|
369
|
+
/** The user whose onboarding this is. */
|
|
370
|
+
userId: string;
|
|
371
|
+
}
|
|
372
|
+
/** Build the per-user onboarding store, scoped to one userId. */
|
|
373
|
+
declare function createUserIntakeStore(opts: CreateUserIntakeStoreOptions): IntakeStore;
|
|
374
|
+
interface CreateProjectIntakeStoreOptions {
|
|
375
|
+
db: IntakeDatabase;
|
|
376
|
+
/** The project-intake table from createIntakeTables (workspace scope). */
|
|
377
|
+
table: ProjectIntakeTable;
|
|
378
|
+
graph: IntakeGraph;
|
|
379
|
+
/** The workspace this intake is attached to. */
|
|
380
|
+
workspaceId: string;
|
|
381
|
+
}
|
|
382
|
+
/** Build the per-project store, scoped to one workspaceId. */
|
|
383
|
+
declare function createProjectIntakeStore(opts: CreateProjectIntakeStoreOptions): IntakeStore;
|
|
384
|
+
|
|
385
|
+
export { type AnyIntakeTables, type CreateIntakeTablesOptions, type CreateProjectIntakeStoreOptions, type CreateUserIntakeStoreOptions, type IntakeDatabase, IntakeError, type IntakeErrorCode, type IntakeParentTable, type IntakeState, type IntakeStore, type IntakeTables, type ProjectIntakeRow, type ProjectIntakeTable, type UserIntakeRow, type UserIntakeTable, createIntakeTables, createProjectIntakeStore, createUserIntakeStore };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IntakeError,
|
|
3
|
+
createProjectIntakeStore,
|
|
4
|
+
createUserIntakeStore
|
|
5
|
+
} from "../chunk-4K7BZYO7.js";
|
|
6
|
+
import "../chunk-USYXHDKE.js";
|
|
7
|
+
import "../chunk-ND3XEGBE.js";
|
|
8
|
+
|
|
9
|
+
// src/intakes/drizzle/schema.ts
|
|
10
|
+
import { sql } from "drizzle-orm";
|
|
11
|
+
import { index, integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
|
|
12
|
+
var hexId = () => text("id").primaryKey().default(sql`(lower(hex(randomblob(16))))`);
|
|
13
|
+
var createdAt = () => integer("created_at", { mode: "timestamp" }).notNull().default(sql`(unixepoch())`);
|
|
14
|
+
var updatedAt = () => integer("updated_at", { mode: "timestamp" }).notNull().default(sql`(unixepoch())`);
|
|
15
|
+
function createUserIntakeTable(userTable) {
|
|
16
|
+
return sqliteTable("user_intake", {
|
|
17
|
+
id: hexId(),
|
|
18
|
+
userId: text("user_id").notNull().references(() => userTable.id, { onDelete: "cascade" }),
|
|
19
|
+
/** The intake definition id the payload was collected against. */
|
|
20
|
+
graphId: text("graph_id").notNull(),
|
|
21
|
+
/** The IntakePayload JSON blob. */
|
|
22
|
+
payload: text("payload", { mode: "json" }).notNull(),
|
|
23
|
+
/** Set when the user finishes the onboarding interview; null while open. */
|
|
24
|
+
completedAt: integer("completed_at", { mode: "timestamp" }),
|
|
25
|
+
createdAt: createdAt(),
|
|
26
|
+
updatedAt: updatedAt()
|
|
27
|
+
}, (table) => [
|
|
28
|
+
uniqueIndex("uniq_user_intake_user").on(table.userId)
|
|
29
|
+
]);
|
|
30
|
+
}
|
|
31
|
+
function createProjectIntakeTable(workspaceTable) {
|
|
32
|
+
return sqliteTable("project_intake", {
|
|
33
|
+
id: hexId(),
|
|
34
|
+
workspaceId: text("workspace_id").notNull().references(() => workspaceTable.id, { onDelete: "cascade" }),
|
|
35
|
+
graphId: text("graph_id").notNull(),
|
|
36
|
+
payload: text("payload", { mode: "json" }).notNull(),
|
|
37
|
+
completedAt: integer("completed_at", { mode: "timestamp" }),
|
|
38
|
+
createdAt: createdAt(),
|
|
39
|
+
updatedAt: updatedAt()
|
|
40
|
+
}, (table) => [
|
|
41
|
+
uniqueIndex("uniq_project_intake_workspace").on(table.workspaceId),
|
|
42
|
+
index("idx_project_intake_graph").on(table.graphId)
|
|
43
|
+
]);
|
|
44
|
+
}
|
|
45
|
+
function createIntakeTables(opts) {
|
|
46
|
+
const userIntake = createUserIntakeTable(opts.userTable);
|
|
47
|
+
const result = {
|
|
48
|
+
userIntake
|
|
49
|
+
};
|
|
50
|
+
if (opts.workspaceTable) {
|
|
51
|
+
result.projectIntake = createProjectIntakeTable(opts.workspaceTable);
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
export {
|
|
56
|
+
IntakeError,
|
|
57
|
+
createIntakeTables,
|
|
58
|
+
createProjectIntakeStore,
|
|
59
|
+
createUserIntakeStore
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=drizzle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/intakes/drizzle/schema.ts"],"sourcesContent":["/**\n * Drizzle schema factory for the intake tables. The product owns the user (and\n * optionally workspace) tables; this factory creates the intake tables and\n * wires their foreign keys into the passed-in tables so the whole graph lives\n * in one drizzle schema with real cascade semantics.\n *\n * Two tables, two scopes:\n * - `user_intake` — the one-time onboarding interview, keyed on `user.id`\n * (UNIQUE per user: one onboarding payload per user). ALWAYS created.\n * - `project_intake` — the structured intake attached to a workspace, keyed\n * on `workspace.id` (UNIQUE per workspace). Created ONLY when a\n * `workspaceTable` is passed.\n *\n * `workspaceTable` is OPTIONAL by design: a non-workspace app (a single-user\n * tool, tax-without-workspaces) adopts per-user onboarding alone with zero\n * teams and zero workspace concept. When omitted, `createIntakeTables` returns\n * `{ userIntake }` and no `projectIntake` — the FK to a workspace table that\n * does not exist is never created.\n *\n * Imports `drizzle-orm` at module top — that is WHY this lives behind the\n * `/intakes/drizzle` sub-subpath. The pure `./intakes` leaf imports none of\n * this, so a consumer that never touches the DB never pulls the optional peer.\n */\n\nimport { sql } from 'drizzle-orm'\nimport { index, integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core'\nimport type { AnySQLiteColumn, AnySQLiteTable } from 'drizzle-orm/sqlite-core'\n\n/** A product table referenced by FK — only the `id` column is touched. */\nexport type IntakeParentTable = AnySQLiteTable & { id: AnySQLiteColumn }\n\nexport interface CreateIntakeTablesOptions {\n /** The product's user table — user-intake rows reference `userTable.id`. */\n userTable: IntakeParentTable\n /**\n * The product's workspace table — project-intake rows reference\n * `workspaceTable.id`. OPTIONAL: omit it for a single-user / non-workspace\n * app that wants per-user onboarding only. When omitted, no `projectIntake`\n * table is created.\n */\n workspaceTable?: IntakeParentTable\n}\n\nconst hexId = () => text('id').primaryKey().default(sql`(lower(hex(randomblob(16))))`)\n\nconst createdAt = () => integer('created_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`)\n\nconst updatedAt = () => integer('updated_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`)\n\nfunction createUserIntakeTable(userTable: IntakeParentTable) {\n return sqliteTable('user_intake', {\n id: hexId(),\n userId: text('user_id').notNull().references(() => userTable.id, { onDelete: 'cascade' }),\n /** The intake definition id the payload was collected against. */\n graphId: text('graph_id').notNull(),\n /** The IntakePayload JSON blob. */\n payload: text('payload', { mode: 'json' }).notNull(),\n /** Set when the user finishes the onboarding interview; null while open. */\n completedAt: integer('completed_at', { mode: 'timestamp' }),\n createdAt: createdAt(),\n updatedAt: updatedAt(),\n }, (table) => [\n uniqueIndex('uniq_user_intake_user').on(table.userId),\n ])\n}\n\nfunction createProjectIntakeTable(workspaceTable: IntakeParentTable) {\n return sqliteTable('project_intake', {\n id: hexId(),\n workspaceId: text('workspace_id').notNull().references(() => workspaceTable.id, { onDelete: 'cascade' }),\n graphId: text('graph_id').notNull(),\n payload: text('payload', { mode: 'json' }).notNull(),\n completedAt: integer('completed_at', { mode: 'timestamp' }),\n createdAt: createdAt(),\n updatedAt: updatedAt(),\n }, (table) => [\n uniqueIndex('uniq_project_intake_workspace').on(table.workspaceId),\n index('idx_project_intake_graph').on(table.graphId),\n ])\n}\n\n/**\n * Build the intake tables wired to the product's tables. Always returns\n * `userIntake`; returns `projectIntake` only when `workspaceTable` is passed.\n * The return type carries `projectIntake?` so a consumer that passes no\n * workspace table cannot reference a table that does not exist.\n */\nexport function createIntakeTables<O extends CreateIntakeTablesOptions>(\n opts: O,\n): IntakeTables<O> {\n const userIntake = createUserIntakeTable(opts.userTable)\n const result: { userIntake: ReturnType<typeof createUserIntakeTable>; projectIntake?: ReturnType<typeof createProjectIntakeTable> } = {\n userIntake,\n }\n if (opts.workspaceTable) {\n result.projectIntake = createProjectIntakeTable(opts.workspaceTable)\n }\n return result as IntakeTables<O>\n}\n\nexport type UserIntakeTable = ReturnType<typeof createUserIntakeTable>\nexport type ProjectIntakeTable = ReturnType<typeof createProjectIntakeTable>\n\n/**\n * The tables returned for a given options shape: `projectIntake` is present in\n * the type exactly when `workspaceTable` was provided.\n */\nexport type IntakeTables<O extends CreateIntakeTablesOptions> =\n O extends { workspaceTable: IntakeParentTable }\n ? { userIntake: UserIntakeTable; projectIntake: ProjectIntakeTable }\n : { userIntake: UserIntakeTable; projectIntake?: ProjectIntakeTable }\n\n/** The union table shape, for code that handles either scope generically. */\nexport type AnyIntakeTables = { userIntake: UserIntakeTable; projectIntake?: ProjectIntakeTable }\n\nexport type UserIntakeRow = UserIntakeTable['$inferSelect']\nexport type ProjectIntakeRow = ProjectIntakeTable['$inferSelect']\n"],"mappings":";;;;;;;;;AAwBA,SAAS,WAAW;AACpB,SAAS,OAAO,SAAS,aAAa,MAAM,mBAAmB;AAkB/D,IAAM,QAAQ,MAAM,KAAK,IAAI,EAAE,WAAW,EAAE,QAAQ,iCAAiC;AAErF,IAAM,YAAY,MAAM,QAAQ,cAAc,EAAE,MAAM,YAAY,CAAC,EAAE,QAAQ,EAAE,QAAQ,kBAAkB;AAEzG,IAAM,YAAY,MAAM,QAAQ,cAAc,EAAE,MAAM,YAAY,CAAC,EAAE,QAAQ,EAAE,QAAQ,kBAAkB;AAEzG,SAAS,sBAAsB,WAA8B;AAC3D,SAAO,YAAY,eAAe;AAAA,IAChC,IAAI,MAAM;AAAA,IACV,QAAQ,KAAK,SAAS,EAAE,QAAQ,EAAE,WAAW,MAAM,UAAU,IAAI,EAAE,UAAU,UAAU,CAAC;AAAA;AAAA,IAExF,SAAS,KAAK,UAAU,EAAE,QAAQ;AAAA;AAAA,IAElC,SAAS,KAAK,WAAW,EAAE,MAAM,OAAO,CAAC,EAAE,QAAQ;AAAA;AAAA,IAEnD,aAAa,QAAQ,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAAA,IAC1D,WAAW,UAAU;AAAA,IACrB,WAAW,UAAU;AAAA,EACvB,GAAG,CAAC,UAAU;AAAA,IACZ,YAAY,uBAAuB,EAAE,GAAG,MAAM,MAAM;AAAA,EACtD,CAAC;AACH;AAEA,SAAS,yBAAyB,gBAAmC;AACnE,SAAO,YAAY,kBAAkB;AAAA,IACnC,IAAI,MAAM;AAAA,IACV,aAAa,KAAK,cAAc,EAAE,QAAQ,EAAE,WAAW,MAAM,eAAe,IAAI,EAAE,UAAU,UAAU,CAAC;AAAA,IACvG,SAAS,KAAK,UAAU,EAAE,QAAQ;AAAA,IAClC,SAAS,KAAK,WAAW,EAAE,MAAM,OAAO,CAAC,EAAE,QAAQ;AAAA,IACnD,aAAa,QAAQ,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAAA,IAC1D,WAAW,UAAU;AAAA,IACrB,WAAW,UAAU;AAAA,EACvB,GAAG,CAAC,UAAU;AAAA,IACZ,YAAY,+BAA+B,EAAE,GAAG,MAAM,WAAW;AAAA,IACjE,MAAM,0BAA0B,EAAE,GAAG,MAAM,OAAO;AAAA,EACpD,CAAC;AACH;AAQO,SAAS,mBACd,MACiB;AACjB,QAAM,aAAa,sBAAsB,KAAK,SAAS;AACvD,QAAM,SAAgI;AAAA,IACpI;AAAA,EACF;AACA,MAAI,KAAK,gBAAgB;AACvB,WAAO,gBAAgB,yBAAyB,KAAK,cAAc;AAAA,EACrE;AACA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { c as IntakeAnswers, b as IntakeGraph, I as IntakeAnswerValue } from '../model-Cuj7d-xT.js';
|
|
2
|
+
export { A as AnswerRejectionReason, d as AnswerValidationResult, e as IntakeAnswerType, f as IntakeOption, a as IntakeQuestion, g as getQuestion, h as hasAnswer, i as intakeProgress, j as isComplete, n as nextQuestion, r as reachableQuestions, v as validateAnswer } from '../model-Cuj7d-xT.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Pure completion-state helpers for an intake payload — the small algebra over
|
|
6
|
+
* the JSON blob the store persists. No I/O: the store reads/writes the row;
|
|
7
|
+
* these functions only build and judge the payload shape, so the same logic
|
|
8
|
+
* runs in the UI, the handlers, and the DB layer without divergence.
|
|
9
|
+
*
|
|
10
|
+
* A payload is `{ graphId, answers, completedAt? }`. It carries the answers
|
|
11
|
+
* and the id of the graph they were collected against, so a later graph
|
|
12
|
+
* revision can detect a stale payload (`graphId` mismatch) rather than
|
|
13
|
+
* silently mixing answers from two question sets.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** The persisted JSON for one intake (the `payload` column). */
|
|
17
|
+
interface IntakePayload {
|
|
18
|
+
/** The graph id the answers were collected against — guards stale schemas. */
|
|
19
|
+
graphId: string;
|
|
20
|
+
answers: IntakeAnswers;
|
|
21
|
+
/** ISO-8601 instant the intake was completed, or undefined while in progress. */
|
|
22
|
+
completedAt?: string;
|
|
23
|
+
}
|
|
24
|
+
/** An empty payload for a fresh intake against `graph`. */
|
|
25
|
+
declare function emptyPayload(graph: IntakeGraph): IntakePayload;
|
|
26
|
+
/** A copy of `payload` with one answer set (does not mutate the input). */
|
|
27
|
+
declare function withAnswer(payload: IntakePayload, questionId: string, value: IntakeAnswerValue): IntakePayload;
|
|
28
|
+
/**
|
|
29
|
+
* True when the payload's answers complete the graph AND the payload was
|
|
30
|
+
* collected against THIS graph. A `graphId` mismatch is never "complete" —
|
|
31
|
+
* the answers belong to a different question set and must be re-collected.
|
|
32
|
+
*/
|
|
33
|
+
declare function payloadComplete(graph: IntakeGraph, payload: IntakePayload): boolean;
|
|
34
|
+
/** True when the payload was collected against a DIFFERENT graph revision. */
|
|
35
|
+
declare function payloadIsStale(graph: IntakeGraph, payload: IntakePayload): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Stamp the payload complete at `at` (default now). Returns a copy; pure. The
|
|
38
|
+
* caller has already checked `payloadComplete` — this only records the instant.
|
|
39
|
+
*/
|
|
40
|
+
declare function markComplete(payload: IntakePayload, at?: Date): IntakePayload;
|
|
41
|
+
|
|
42
|
+
export { IntakeAnswerValue, IntakeAnswers, IntakeGraph, type IntakePayload, emptyPayload, markComplete, payloadComplete, payloadIsStale, withAnswer };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
emptyPayload,
|
|
3
|
+
markComplete,
|
|
4
|
+
payloadComplete,
|
|
5
|
+
payloadIsStale,
|
|
6
|
+
withAnswer
|
|
7
|
+
} from "../chunk-USYXHDKE.js";
|
|
8
|
+
import {
|
|
9
|
+
getQuestion,
|
|
10
|
+
hasAnswer,
|
|
11
|
+
intakeProgress,
|
|
12
|
+
isComplete,
|
|
13
|
+
nextQuestion,
|
|
14
|
+
reachableQuestions,
|
|
15
|
+
validateAnswer
|
|
16
|
+
} from "../chunk-ND3XEGBE.js";
|
|
17
|
+
export {
|
|
18
|
+
emptyPayload,
|
|
19
|
+
getQuestion,
|
|
20
|
+
hasAnswer,
|
|
21
|
+
intakeProgress,
|
|
22
|
+
isComplete,
|
|
23
|
+
markComplete,
|
|
24
|
+
nextQuestion,
|
|
25
|
+
payloadComplete,
|
|
26
|
+
payloadIsStale,
|
|
27
|
+
reachableQuestions,
|
|
28
|
+
validateAnswer,
|
|
29
|
+
withAnswer
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|