@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.
Files changed (133) hide show
  1. package/package.json +40 -0
  2. package/src/actions/action.test.ts +108 -0
  3. package/src/actions/action.ts +60 -0
  4. package/src/actions/loader.ts +47 -0
  5. package/src/api/actions.ts +124 -0
  6. package/src/api/meta-mutations.ts +922 -0
  7. package/src/api/meta.ts +222 -0
  8. package/src/api/reports.ts +98 -0
  9. package/src/api/server.ts +24 -0
  10. package/src/api/tables.ts +108 -0
  11. package/src/api/views.ts +44 -0
  12. package/src/boot.ts +206 -0
  13. package/src/cli/ai-commands.ts +220 -0
  14. package/src/cli/check.ts +169 -0
  15. package/src/cli/cli-utils.test.ts +313 -0
  16. package/src/cli/describe.test.ts +151 -0
  17. package/src/cli/describe.ts +88 -0
  18. package/src/cli/emit-result.test.ts +160 -0
  19. package/src/cli/format.ts +150 -0
  20. package/src/cli/http-client.ts +55 -0
  21. package/src/cli/index.ts +162 -0
  22. package/src/cli/init.ts +35 -0
  23. package/src/cli/project-context.ts +38 -0
  24. package/src/cli/request.ts +146 -0
  25. package/src/cli/routes.ts +418 -0
  26. package/src/cli/rows-insert-master-detail.test.ts +124 -0
  27. package/src/cli/rows-insert-master-detail.ts +186 -0
  28. package/src/cli/rows-insert.test.ts +137 -0
  29. package/src/cli/rows-insert.ts +97 -0
  30. package/src/cli/serve-single.ts +49 -0
  31. package/src/create-project.ts +81 -0
  32. package/src/data/count.ts +62 -0
  33. package/src/data/crud.test.ts +188 -0
  34. package/src/data/crud.ts +242 -0
  35. package/src/data/lookup.test.ts +96 -0
  36. package/src/data/lookup.ts +104 -0
  37. package/src/data/query-parser.test.ts +67 -0
  38. package/src/data/query-parser.ts +106 -0
  39. package/src/data/sanitize.test.ts +57 -0
  40. package/src/data/sanitize.ts +25 -0
  41. package/src/data/save-pipeline.test.ts +115 -0
  42. package/src/data/save-pipeline.ts +93 -0
  43. package/src/data/validate.test.ts +110 -0
  44. package/src/data/validate.ts +98 -0
  45. package/src/db/errors.ts +20 -0
  46. package/src/db/logger.ts +63 -0
  47. package/src/db/sqlite-connection.test.ts +59 -0
  48. package/src/db/sqlite-connection.ts +79 -0
  49. package/src/index.ts +111 -0
  50. package/src/integration/api-actions.test.ts +60 -0
  51. package/src/integration/api-global.test.ts +21 -0
  52. package/src/integration/api-meta.test.ts +252 -0
  53. package/src/integration/api-reports.test.ts +77 -0
  54. package/src/integration/api-tables.test.ts +238 -0
  55. package/src/integration/api-views.test.ts +39 -0
  56. package/src/integration/cli-routes.test.ts +167 -0
  57. package/src/integration/fixtures/actions/create-account.ts +23 -0
  58. package/src/integration/fixtures/reports/account-list.ts +25 -0
  59. package/src/integration/fixtures/schema/accounts.ts +21 -0
  60. package/src/integration/fixtures/schema/audit-log.ts +19 -0
  61. package/src/integration/fixtures/schema/journal-entries.ts +20 -0
  62. package/src/integration/fixtures/views/dashboard.tsx +4 -0
  63. package/src/integration/fixtures/views/settings.tsx +3 -0
  64. package/src/integration/setup.ts +72 -0
  65. package/src/introspect/db-helpers.ts +109 -0
  66. package/src/introspect/describe-all.test.ts +73 -0
  67. package/src/introspect/describe-all.ts +80 -0
  68. package/src/introspect/describe.test.ts +65 -0
  69. package/src/introspect/describe.ts +184 -0
  70. package/src/introspect/exec.test.ts +103 -0
  71. package/src/introspect/exec.ts +57 -0
  72. package/src/introspect/indexes.test.ts +41 -0
  73. package/src/introspect/indexes.ts +95 -0
  74. package/src/introspect/inference.ts +98 -0
  75. package/src/introspect/list-tables.test.ts +40 -0
  76. package/src/introspect/list-tables.ts +62 -0
  77. package/src/introspect/query.test.ts +77 -0
  78. package/src/introspect/query.ts +47 -0
  79. package/src/introspect/sample.test.ts +67 -0
  80. package/src/introspect/sample.ts +50 -0
  81. package/src/introspect/sql-safety.ts +76 -0
  82. package/src/introspect/sqlite/db-helpers.test.ts +79 -0
  83. package/src/introspect/sqlite/db-helpers.ts +56 -0
  84. package/src/introspect/sqlite/describe-all.ts +21 -0
  85. package/src/introspect/sqlite/describe.test.ts +160 -0
  86. package/src/introspect/sqlite/describe.ts +185 -0
  87. package/src/introspect/sqlite/exec.ts +57 -0
  88. package/src/introspect/sqlite/indexes.test.ts +60 -0
  89. package/src/introspect/sqlite/indexes.ts +96 -0
  90. package/src/introspect/sqlite/list-tables.test.ts +100 -0
  91. package/src/introspect/sqlite/list-tables.ts +67 -0
  92. package/src/introspect/sqlite/query.ts +49 -0
  93. package/src/introspect/sqlite/sample.ts +50 -0
  94. package/src/introspect/table-rename.test.ts +235 -0
  95. package/src/introspect/table-rename.ts +115 -0
  96. package/src/introspect/types.ts +95 -0
  97. package/src/reports/check.test.ts +499 -0
  98. package/src/reports/check.ts +208 -0
  99. package/src/reports/engine.test.ts +1465 -0
  100. package/src/reports/engine.ts +678 -0
  101. package/src/reports/loader.ts +55 -0
  102. package/src/reports/report.ts +308 -0
  103. package/src/reports/sql-bind.ts +161 -0
  104. package/src/reports/sqlite-bind.test.ts +98 -0
  105. package/src/reports/sqlite-bind.ts +58 -0
  106. package/src/reports/sqlite-sql-client.ts +42 -0
  107. package/src/runtime.ts +3 -0
  108. package/src/schema/check.ts +90 -0
  109. package/src/schema/ddl.test.ts +210 -0
  110. package/src/schema/ddl.ts +180 -0
  111. package/src/schema/dynamic-builder.ts +297 -0
  112. package/src/schema/extract.test.ts +261 -0
  113. package/src/schema/extract.ts +285 -0
  114. package/src/schema/loader.test.ts +31 -0
  115. package/src/schema/loader.ts +60 -0
  116. package/src/schema/metadata-io.test.ts +261 -0
  117. package/src/schema/metadata-io.ts +161 -0
  118. package/src/schema/metadata-tables.test.ts +737 -0
  119. package/src/schema/metadata-tables.ts +341 -0
  120. package/src/schema/migrate.ts +195 -0
  121. package/src/schema/normalize-datatype.test.ts +58 -0
  122. package/src/schema/normalize-datatype.ts +99 -0
  123. package/src/schema/registry.test.ts +174 -0
  124. package/src/schema/registry.ts +139 -0
  125. package/src/schema/reserved.ts +227 -0
  126. package/src/schema/table.ts +135 -0
  127. package/src/test-fixtures/schema/accounts.ts +24 -0
  128. package/src/test-fixtures/schema/not-a-table.ts +6 -0
  129. package/src/testing/test-utils.ts +44 -0
  130. package/src/views/loader.test.ts +70 -0
  131. package/src/views/loader.ts +38 -0
  132. package/src/views/view.test.ts +121 -0
  133. 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
+ }