@sapporta/server 0.0.1
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/package.json +40 -0
- package/src/actions/action.test.ts +108 -0
- package/src/actions/action.ts +60 -0
- package/src/actions/loader.ts +47 -0
- package/src/api/actions.ts +124 -0
- package/src/api/meta-mutations.ts +922 -0
- package/src/api/meta.ts +222 -0
- package/src/api/reports.ts +98 -0
- package/src/api/server.ts +24 -0
- package/src/api/tables.ts +108 -0
- package/src/api/views.ts +44 -0
- package/src/boot.ts +206 -0
- package/src/cli/ai-commands.ts +220 -0
- package/src/cli/check.ts +169 -0
- package/src/cli/cli-utils.test.ts +313 -0
- package/src/cli/describe.test.ts +151 -0
- package/src/cli/describe.ts +88 -0
- package/src/cli/emit-result.test.ts +160 -0
- package/src/cli/format.ts +150 -0
- package/src/cli/http-client.ts +55 -0
- package/src/cli/index.ts +162 -0
- package/src/cli/init.ts +35 -0
- package/src/cli/project-context.ts +38 -0
- package/src/cli/request.ts +146 -0
- package/src/cli/routes.ts +418 -0
- package/src/cli/rows-insert-master-detail.test.ts +124 -0
- package/src/cli/rows-insert-master-detail.ts +186 -0
- package/src/cli/rows-insert.test.ts +137 -0
- package/src/cli/rows-insert.ts +97 -0
- package/src/cli/serve-single.ts +49 -0
- package/src/create-project.ts +81 -0
- package/src/data/count.ts +62 -0
- package/src/data/crud.test.ts +188 -0
- package/src/data/crud.ts +242 -0
- package/src/data/lookup.test.ts +96 -0
- package/src/data/lookup.ts +104 -0
- package/src/data/query-parser.test.ts +67 -0
- package/src/data/query-parser.ts +106 -0
- package/src/data/sanitize.test.ts +57 -0
- package/src/data/sanitize.ts +25 -0
- package/src/data/save-pipeline.test.ts +115 -0
- package/src/data/save-pipeline.ts +93 -0
- package/src/data/validate.test.ts +110 -0
- package/src/data/validate.ts +98 -0
- package/src/db/errors.ts +20 -0
- package/src/db/logger.ts +63 -0
- package/src/db/sqlite-connection.test.ts +59 -0
- package/src/db/sqlite-connection.ts +79 -0
- package/src/index.ts +111 -0
- package/src/integration/api-actions.test.ts +60 -0
- package/src/integration/api-global.test.ts +21 -0
- package/src/integration/api-meta.test.ts +252 -0
- package/src/integration/api-reports.test.ts +77 -0
- package/src/integration/api-tables.test.ts +238 -0
- package/src/integration/api-views.test.ts +39 -0
- package/src/integration/cli-routes.test.ts +167 -0
- package/src/integration/fixtures/actions/create-account.ts +23 -0
- package/src/integration/fixtures/reports/account-list.ts +25 -0
- package/src/integration/fixtures/schema/accounts.ts +21 -0
- package/src/integration/fixtures/schema/audit-log.ts +19 -0
- package/src/integration/fixtures/schema/journal-entries.ts +20 -0
- package/src/integration/fixtures/views/dashboard.tsx +4 -0
- package/src/integration/fixtures/views/settings.tsx +3 -0
- package/src/integration/setup.ts +72 -0
- package/src/introspect/db-helpers.ts +109 -0
- package/src/introspect/describe-all.test.ts +73 -0
- package/src/introspect/describe-all.ts +80 -0
- package/src/introspect/describe.test.ts +65 -0
- package/src/introspect/describe.ts +184 -0
- package/src/introspect/exec.test.ts +103 -0
- package/src/introspect/exec.ts +57 -0
- package/src/introspect/indexes.test.ts +41 -0
- package/src/introspect/indexes.ts +95 -0
- package/src/introspect/inference.ts +98 -0
- package/src/introspect/list-tables.test.ts +40 -0
- package/src/introspect/list-tables.ts +62 -0
- package/src/introspect/query.test.ts +77 -0
- package/src/introspect/query.ts +47 -0
- package/src/introspect/sample.test.ts +67 -0
- package/src/introspect/sample.ts +50 -0
- package/src/introspect/sql-safety.ts +76 -0
- package/src/introspect/sqlite/db-helpers.test.ts +79 -0
- package/src/introspect/sqlite/db-helpers.ts +56 -0
- package/src/introspect/sqlite/describe-all.ts +21 -0
- package/src/introspect/sqlite/describe.test.ts +160 -0
- package/src/introspect/sqlite/describe.ts +185 -0
- package/src/introspect/sqlite/exec.ts +57 -0
- package/src/introspect/sqlite/indexes.test.ts +60 -0
- package/src/introspect/sqlite/indexes.ts +96 -0
- package/src/introspect/sqlite/list-tables.test.ts +100 -0
- package/src/introspect/sqlite/list-tables.ts +67 -0
- package/src/introspect/sqlite/query.ts +49 -0
- package/src/introspect/sqlite/sample.ts +50 -0
- package/src/introspect/table-rename.test.ts +235 -0
- package/src/introspect/table-rename.ts +115 -0
- package/src/introspect/types.ts +95 -0
- package/src/reports/check.test.ts +499 -0
- package/src/reports/check.ts +208 -0
- package/src/reports/engine.test.ts +1465 -0
- package/src/reports/engine.ts +678 -0
- package/src/reports/loader.ts +55 -0
- package/src/reports/report.ts +308 -0
- package/src/reports/sql-bind.ts +161 -0
- package/src/reports/sqlite-bind.test.ts +98 -0
- package/src/reports/sqlite-bind.ts +58 -0
- package/src/reports/sqlite-sql-client.ts +42 -0
- package/src/runtime.ts +3 -0
- package/src/schema/check.ts +90 -0
- package/src/schema/ddl.test.ts +210 -0
- package/src/schema/ddl.ts +180 -0
- package/src/schema/dynamic-builder.ts +297 -0
- package/src/schema/extract.test.ts +261 -0
- package/src/schema/extract.ts +285 -0
- package/src/schema/loader.test.ts +31 -0
- package/src/schema/loader.ts +60 -0
- package/src/schema/metadata-io.test.ts +261 -0
- package/src/schema/metadata-io.ts +161 -0
- package/src/schema/metadata-tables.test.ts +737 -0
- package/src/schema/metadata-tables.ts +341 -0
- package/src/schema/migrate.ts +195 -0
- package/src/schema/normalize-datatype.test.ts +58 -0
- package/src/schema/normalize-datatype.ts +99 -0
- package/src/schema/registry.test.ts +174 -0
- package/src/schema/registry.ts +139 -0
- package/src/schema/reserved.ts +227 -0
- package/src/schema/table.ts +135 -0
- package/src/test-fixtures/schema/accounts.ts +24 -0
- package/src/test-fixtures/schema/not-a-table.ts +6 -0
- package/src/testing/test-utils.ts +44 -0
- package/src/views/loader.test.ts +70 -0
- package/src/views/loader.ts +38 -0
- package/src/views/view.test.ts +121 -0
- package/src/views/view.ts +16 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// SQLite Metadata I/O — JSON serialization and query helpers
|
|
3
|
+
// ============================================================================
|
|
4
|
+
//
|
|
5
|
+
// Centralized JSON serialization for metadata table reads/writes.
|
|
6
|
+
// Prevents scattered JSON.parse()/JSON.stringify() calls across the codebase.
|
|
7
|
+
//
|
|
8
|
+
// SQLite stores arrays and objects as TEXT (JSON strings), unlike Postgres
|
|
9
|
+
// which has native JSONB and TEXT[] types. This module is the single point
|
|
10
|
+
// where that encoding boundary is crossed:
|
|
11
|
+
//
|
|
12
|
+
// Read path: deserializeColumnRow() — JSON.parse select_options/ui_hints
|
|
13
|
+
// Write path: serializeColumnValues() — JSON.stringify select_options/ui_hints
|
|
14
|
+
// Merge path: mergeUiHints() — read-modify-write for partial updates
|
|
15
|
+
// Query path: buildSetClause() — construct parameterized UPDATE SET clauses
|
|
16
|
+
//
|
|
17
|
+
// The Postgres codebase doesn't need these because:
|
|
18
|
+
// - TEXT[] ↔ native array (postgres.js handles encoding)
|
|
19
|
+
// - JSONB ↔ native object (postgres.js handles encoding)
|
|
20
|
+
// - tx(obj) builds SET clauses dynamically (postgres.js feature)
|
|
21
|
+
|
|
22
|
+
import type Database from "better-sqlite3";
|
|
23
|
+
import type { ColumnMetaRow } from "./dynamic-builder.js";
|
|
24
|
+
|
|
25
|
+
// ─── Deserialization (read path) ────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Row shape from _sapporta_columns before deserialization.
|
|
29
|
+
* All fields are SQLite native types (TEXT for JSON, INTEGER for booleans).
|
|
30
|
+
*/
|
|
31
|
+
export interface RawMetadataColumnRow {
|
|
32
|
+
table_name: string;
|
|
33
|
+
column_name: string;
|
|
34
|
+
column_type: string;
|
|
35
|
+
position: number;
|
|
36
|
+
not_null: number; // SQLite INTEGER (0/1)
|
|
37
|
+
is_unique: number; // SQLite INTEGER (0/1)
|
|
38
|
+
default_value: string | null;
|
|
39
|
+
references_table: string | null;
|
|
40
|
+
select_options: string | null; // JSON-encoded string[]
|
|
41
|
+
ui_hints: string; // JSON-encoded Record<string, unknown>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Deserialize a raw metadata column row from SQLite into the application type.
|
|
46
|
+
*
|
|
47
|
+
* Converts:
|
|
48
|
+
* - select_options: JSON string → string[] | undefined
|
|
49
|
+
* - ui_hints: JSON string → Record<string, unknown>
|
|
50
|
+
* - not_null/is_unique: INTEGER (0/1) → boolean
|
|
51
|
+
*/
|
|
52
|
+
export function deserializeColumnRow(
|
|
53
|
+
raw: RawMetadataColumnRow,
|
|
54
|
+
): ColumnMetaRow {
|
|
55
|
+
return {
|
|
56
|
+
column_name: raw.column_name,
|
|
57
|
+
column_type: raw.column_type,
|
|
58
|
+
position: raw.position,
|
|
59
|
+
not_null: raw.not_null === 1,
|
|
60
|
+
is_unique: raw.is_unique === 1,
|
|
61
|
+
default_value: raw.default_value ?? undefined,
|
|
62
|
+
references_table: raw.references_table ?? undefined,
|
|
63
|
+
select_options: raw.select_options
|
|
64
|
+
? JSON.parse(raw.select_options)
|
|
65
|
+
: undefined,
|
|
66
|
+
ui_hints: raw.ui_hints ? JSON.parse(raw.ui_hints) : {},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ─── Serialization (write path) ─────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Serialize column metadata values for INSERT/UPDATE into _sapporta_columns.
|
|
74
|
+
*
|
|
75
|
+
* Converts:
|
|
76
|
+
* - select_options: string[] → JSON string
|
|
77
|
+
* - ui_hints: Record<string, unknown> → JSON string
|
|
78
|
+
*
|
|
79
|
+
* Only serializes fields that are present in the input. Callers can pass
|
|
80
|
+
* partial objects for UPDATE operations.
|
|
81
|
+
*/
|
|
82
|
+
export function serializeColumnValues(
|
|
83
|
+
col: Partial<ColumnMetaRow>,
|
|
84
|
+
): Record<string, unknown> {
|
|
85
|
+
const out: Record<string, unknown> = { ...col };
|
|
86
|
+
|
|
87
|
+
if (col.select_options !== undefined) {
|
|
88
|
+
out.select_options = col.select_options
|
|
89
|
+
? JSON.stringify(col.select_options)
|
|
90
|
+
: null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (col.ui_hints !== undefined) {
|
|
94
|
+
out.ui_hints = JSON.stringify(col.ui_hints ?? {});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ─── JSONB merge (application-level) ────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Application-level JSONB merge for ui_hints.
|
|
104
|
+
*
|
|
105
|
+
* Replaces the Postgres pattern:
|
|
106
|
+
* UPDATE _sapporta_columns SET ui_hints = COALESCE(ui_hints, '{}'::jsonb) || $val::jsonb
|
|
107
|
+
*
|
|
108
|
+
* SQLite has no native JSONB merge operator, so we read-modify-write.
|
|
109
|
+
* This must be called within a transaction to avoid lost updates from
|
|
110
|
+
* concurrent requests. In practice, better-sqlite3's synchronous nature
|
|
111
|
+
* means only one JS operation runs at a time, but using a transaction
|
|
112
|
+
* is still correct for atomicity if the caller batches multiple writes.
|
|
113
|
+
*/
|
|
114
|
+
export function mergeUiHints(
|
|
115
|
+
sqlite: Database.Database,
|
|
116
|
+
tableName: string,
|
|
117
|
+
columnName: string,
|
|
118
|
+
merge: Record<string, unknown>,
|
|
119
|
+
): void {
|
|
120
|
+
const row = sqlite
|
|
121
|
+
.prepare(
|
|
122
|
+
`SELECT ui_hints FROM _sapporta_columns
|
|
123
|
+
WHERE table_name = ? AND column_name = ?`,
|
|
124
|
+
)
|
|
125
|
+
.get(tableName, columnName) as { ui_hints: string } | undefined;
|
|
126
|
+
|
|
127
|
+
const current = row?.ui_hints ? JSON.parse(row.ui_hints) : {};
|
|
128
|
+
const merged = { ...current, ...merge };
|
|
129
|
+
|
|
130
|
+
sqlite
|
|
131
|
+
.prepare(
|
|
132
|
+
`UPDATE _sapporta_columns SET ui_hints = ?
|
|
133
|
+
WHERE table_name = ? AND column_name = ?`,
|
|
134
|
+
)
|
|
135
|
+
.run(JSON.stringify(merged), tableName, columnName);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ─── Query helpers ──────────────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Build a parameterized SET clause from an object.
|
|
142
|
+
*
|
|
143
|
+
* Replaces the postgres.js tx(obj) dynamic SET pattern which constructs
|
|
144
|
+
* SET clauses from objects automatically. SQLite's better-sqlite3 has
|
|
145
|
+
* no equivalent, so we build it manually.
|
|
146
|
+
*
|
|
147
|
+
* Example:
|
|
148
|
+
* buildSetClause({ label: "Foo", position: 3 })
|
|
149
|
+
* → { clause: '"label" = ?, "position" = ?', values: ["Foo", 3] }
|
|
150
|
+
*
|
|
151
|
+
* Column names are double-quoted to handle reserved words safely.
|
|
152
|
+
* Values are parameterized (never interpolated) to prevent injection.
|
|
153
|
+
*/
|
|
154
|
+
export function buildSetClause(
|
|
155
|
+
obj: Record<string, unknown>,
|
|
156
|
+
): { clause: string; values: unknown[] } {
|
|
157
|
+
const keys = Object.keys(obj);
|
|
158
|
+
const clause = keys.map((k) => `"${k}" = ?`).join(", ");
|
|
159
|
+
const values = keys.map((k) => obj[k]);
|
|
160
|
+
return { clause, values };
|
|
161
|
+
}
|