turbine-orm 0.81.0 → 0.90.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/README.md +26 -25
- package/dist/checkout.d.ts +27 -2
- package/dist/checkout.js +75 -4
- package/dist/cjs/checkout.d.ts +27 -2
- package/dist/cjs/checkout.js +76 -3
- package/dist/cjs/cli/code-first-tags.d.ts +62 -0
- package/dist/cjs/cli/code-first-tags.js +124 -0
- package/dist/cjs/cli/compile-query.js +9 -0
- package/dist/cjs/cli/config.d.ts +35 -6
- package/dist/cjs/cli/config.js +17 -1
- package/dist/cjs/cli/destructive.d.ts +7 -4
- package/dist/cjs/cli/destructive.js +224 -20
- package/dist/cjs/cli/error-catalog.js +36 -0
- package/dist/cjs/cli/error-text.d.ts +15 -0
- package/dist/cjs/cli/error-text.js +40 -0
- package/dist/cjs/cli/index.d.ts +62 -4
- package/dist/cjs/cli/index.js +568 -108
- package/dist/cjs/cli/local-server-guard.d.ts +53 -0
- package/dist/cjs/cli/local-server-guard.js +119 -0
- package/dist/cjs/cli/local-server-output.d.ts +41 -0
- package/dist/cjs/cli/local-server-output.js +103 -0
- package/dist/cjs/cli/mcp.d.ts +8 -0
- package/dist/cjs/cli/mcp.js +121 -32
- package/dist/cjs/cli/migrate.d.ts +113 -5
- package/dist/cjs/cli/migrate.js +270 -21
- package/dist/cjs/cli/observe.d.ts +6 -1
- package/dist/cjs/cli/observe.js +85 -55
- package/dist/cjs/cli/pii-predicate-guard.d.ts +43 -23
- package/dist/cjs/cli/pii-predicate-guard.js +98 -37
- package/dist/cjs/cli/prisma-resolve.js +17 -13
- package/dist/cjs/cli/prisma-schema.js +12 -4
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/cli/studio.d.ts +14 -2
- package/dist/cjs/cli/studio.js +244 -69
- package/dist/cjs/cli/ui.d.ts +1 -1
- package/dist/cjs/client.d.ts +558 -85
- package/dist/cjs/client.js +1270 -395
- package/dist/cjs/connection-guard.d.ts +13 -0
- package/dist/cjs/connection-guard.js +28 -0
- package/dist/cjs/connection-url.d.ts +51 -16
- package/dist/cjs/connection-url.js +67 -34
- package/dist/cjs/diagnostics.d.ts +81 -0
- package/dist/cjs/diagnostics.js +147 -0
- package/dist/cjs/dialect.d.ts +449 -20
- package/dist/cjs/dialect.js +117 -76
- package/dist/cjs/engine-config.d.ts +7 -6
- package/dist/cjs/errors.d.ts +126 -6
- package/dist/cjs/errors.js +256 -13
- package/dist/cjs/generate.d.ts +57 -7
- package/dist/cjs/generate.js +635 -279
- package/dist/cjs/global-filters-config.d.ts +59 -0
- package/dist/cjs/global-filters-config.js +367 -0
- package/dist/cjs/index.d.ts +9 -4
- package/dist/cjs/index.js +10 -2
- package/dist/cjs/introspect.d.ts +31 -6
- package/dist/cjs/introspect.js +108 -50
- package/dist/cjs/log-codes.d.ts +12 -0
- package/dist/cjs/log-codes.js +77 -0
- package/dist/cjs/logger.d.ts +87 -0
- package/dist/cjs/logger.js +98 -0
- package/dist/cjs/mssql.d.ts +8 -5
- package/dist/cjs/mssql.js +352 -65
- package/dist/cjs/mysql.d.ts +3 -5
- package/dist/cjs/mysql.js +420 -70
- package/dist/cjs/nested-write.d.ts +3 -0
- package/dist/cjs/nested-write.js +179 -114
- package/dist/cjs/pipeline-submittable.d.ts +12 -0
- package/dist/cjs/pipeline-submittable.js +118 -44
- package/dist/cjs/pipeline.js +21 -26
- package/dist/cjs/powdb-introspect.d.ts +5 -2
- package/dist/cjs/powdb-introspect.js +20 -15
- package/dist/cjs/powdb-shared.d.ts +36 -5
- package/dist/cjs/powdb-shared.js +67 -8
- package/dist/cjs/powdb.d.ts +4 -0
- package/dist/cjs/powdb.js +183 -51
- package/dist/cjs/powql.d.ts +394 -51
- package/dist/cjs/powql.js +1829 -284
- package/dist/cjs/prisma-compat.d.ts +23 -15
- package/dist/cjs/prisma-compat.js +507 -372
- package/dist/cjs/query/aggregates.d.ts +17 -1
- package/dist/cjs/query/aggregates.js +261 -29
- package/dist/cjs/query/batched-loader.d.ts +25 -6
- package/dist/cjs/query/batched-loader.js +103 -20
- package/dist/cjs/query/builder.d.ts +325 -184
- package/dist/cjs/query/builder.js +1452 -490
- package/dist/cjs/query/column-ref.d.ts +61 -0
- package/dist/cjs/query/column-ref.js +73 -0
- package/dist/cjs/query/compound-unique.d.ts +21 -0
- package/dist/cjs/query/compound-unique.js +98 -18
- package/dist/cjs/query/deferred.d.ts +53 -16
- package/dist/cjs/query/expr.d.ts +217 -0
- package/dist/cjs/query/expr.js +859 -0
- package/dist/cjs/query/filters.d.ts +155 -53
- package/dist/cjs/query/filters.js +613 -76
- package/dist/cjs/query/index.d.ts +2 -0
- package/dist/cjs/query/index.js +7 -1
- package/dist/cjs/query/json-value.d.ts +43 -0
- package/dist/cjs/query/json-value.js +59 -0
- package/dist/cjs/query/normalize-args.d.ts +87 -0
- package/dist/cjs/query/normalize-args.js +527 -0
- package/dist/cjs/query/option-surface.d.ts +16 -4
- package/dist/cjs/query/option-surface.js +57 -14
- package/dist/cjs/query/relation-names.d.ts +14 -0
- package/dist/cjs/query/relation-names.js +77 -18
- package/dist/cjs/query/relations.d.ts +30 -3
- package/dist/cjs/query/relations.js +219 -63
- package/dist/cjs/query/row-lock.d.ts +49 -0
- package/dist/cjs/query/row-lock.js +138 -0
- package/dist/cjs/query/sql-comment.d.ts +161 -0
- package/dist/cjs/query/sql-comment.js +381 -0
- package/dist/cjs/query/stream-idle.d.ts +132 -0
- package/dist/cjs/query/stream-idle.js +212 -0
- package/dist/cjs/query/table-client.d.ts +101 -0
- package/dist/cjs/query/table-client.js +74 -0
- package/dist/cjs/query/types.d.ts +677 -298
- package/dist/cjs/query/utils.d.ts +105 -6
- package/dist/cjs/query/utils.js +426 -19
- package/dist/cjs/query/warn-registry.d.ts +18 -0
- package/dist/cjs/query/warn-registry.js +18 -0
- package/dist/cjs/query/where-compile.d.ts +138 -14
- package/dist/cjs/query/where-compile.js +513 -29
- package/dist/cjs/query/where.d.ts +159 -59
- package/dist/cjs/query/where.js +716 -246
- package/dist/cjs/query/writes.d.ts +112 -50
- package/dist/cjs/query/writes.js +1498 -200
- package/dist/cjs/query-events.d.ts +4 -2
- package/dist/cjs/query-events.js +3 -1
- package/dist/cjs/realtime.d.ts +7 -3
- package/dist/cjs/realtime.js +13 -2
- package/dist/cjs/schema-builder.d.ts +55 -4
- package/dist/cjs/schema-builder.js +382 -18
- package/dist/cjs/schema-metadata.d.ts +21 -1
- package/dist/cjs/schema-metadata.js +29 -7
- package/dist/cjs/schema-sql.d.ts +75 -5
- package/dist/cjs/schema-sql.js +497 -45
- package/dist/cjs/schema.d.ts +26 -5
- package/dist/cjs/schema.js +110 -13
- package/dist/cjs/sql-placeholders.d.ts +56 -0
- package/dist/cjs/sql-placeholders.js +204 -0
- package/dist/cjs/sqlite.d.ts +75 -20
- package/dist/cjs/sqlite.js +593 -95
- package/dist/cjs/supplied-pool-types.d.ts +48 -0
- package/dist/cjs/supplied-pool-types.js +256 -0
- package/dist/cjs/transaction-control.d.ts +51 -0
- package/dist/cjs/transaction-control.js +316 -0
- package/dist/cjs/transaction-state.d.ts +342 -0
- package/dist/cjs/transaction-state.js +707 -0
- package/dist/cjs/typed-sql.d.ts +37 -3
- package/dist/cjs/typed-sql.js +102 -8
- package/dist/cli/code-first-tags.d.ts +62 -0
- package/dist/cli/code-first-tags.js +120 -0
- package/dist/cli/compile-query.js +9 -0
- package/dist/cli/config.d.ts +35 -6
- package/dist/cli/config.js +16 -1
- package/dist/cli/destructive.d.ts +7 -4
- package/dist/cli/destructive.js +224 -20
- package/dist/cli/error-catalog.js +36 -0
- package/dist/cli/error-text.d.ts +15 -0
- package/dist/cli/error-text.js +37 -0
- package/dist/cli/index.d.ts +62 -4
- package/dist/cli/index.js +569 -112
- package/dist/cli/local-server-guard.d.ts +53 -0
- package/dist/cli/local-server-guard.js +113 -0
- package/dist/cli/local-server-output.d.ts +41 -0
- package/dist/cli/local-server-output.js +98 -0
- package/dist/cli/mcp.d.ts +8 -0
- package/dist/cli/mcp.js +121 -34
- package/dist/cli/migrate.d.ts +113 -5
- package/dist/cli/migrate.js +261 -21
- package/dist/cli/observe.d.ts +6 -1
- package/dist/cli/observe.js +84 -54
- package/dist/cli/pii-predicate-guard.d.ts +43 -23
- package/dist/cli/pii-predicate-guard.js +100 -35
- package/dist/cli/prisma-resolve.js +15 -11
- package/dist/cli/prisma-schema.js +12 -4
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/cli/studio.d.ts +14 -2
- package/dist/cli/studio.js +246 -71
- package/dist/cli/ui.d.ts +1 -1
- package/dist/client.d.ts +558 -85
- package/dist/client.js +1275 -400
- package/dist/connection-guard.d.ts +13 -0
- package/dist/connection-guard.js +27 -0
- package/dist/connection-url.d.ts +51 -16
- package/dist/connection-url.js +63 -34
- package/dist/diagnostics.d.ts +81 -0
- package/dist/diagnostics.js +144 -0
- package/dist/dialect.d.ts +449 -20
- package/dist/dialect.js +115 -43
- package/dist/engine-config.d.ts +7 -6
- package/dist/errors.d.ts +126 -6
- package/dist/errors.js +252 -12
- package/dist/generate.d.ts +57 -7
- package/dist/generate.js +635 -281
- package/dist/global-filters-config.d.ts +59 -0
- package/dist/global-filters-config.js +361 -0
- package/dist/index.d.ts +9 -4
- package/dist/index.js +4 -2
- package/dist/introspect.d.ts +31 -6
- package/dist/introspect.js +106 -50
- package/dist/log-codes.d.ts +12 -0
- package/dist/log-codes.js +74 -0
- package/dist/logger.d.ts +87 -0
- package/dist/logger.js +93 -0
- package/dist/mssql.d.ts +8 -5
- package/dist/mssql.js +354 -67
- package/dist/mysql.d.ts +3 -5
- package/dist/mysql.js +423 -73
- package/dist/nested-write.d.ts +3 -0
- package/dist/nested-write.js +177 -112
- package/dist/pipeline-submittable.d.ts +12 -0
- package/dist/pipeline-submittable.js +117 -44
- package/dist/pipeline.js +21 -26
- package/dist/powdb-introspect.d.ts +5 -2
- package/dist/powdb-introspect.js +21 -16
- package/dist/powdb-shared.d.ts +36 -5
- package/dist/powdb-shared.js +65 -8
- package/dist/powdb.d.ts +4 -0
- package/dist/powdb.js +183 -51
- package/dist/powql.d.ts +394 -51
- package/dist/powql.js +1836 -291
- package/dist/prisma-compat.d.ts +23 -15
- package/dist/prisma-compat.js +508 -373
- package/dist/query/aggregates.d.ts +17 -1
- package/dist/query/aggregates.js +263 -32
- package/dist/query/batched-loader.d.ts +25 -6
- package/dist/query/batched-loader.js +101 -21
- package/dist/query/builder.d.ts +325 -184
- package/dist/query/builder.js +1458 -495
- package/dist/query/column-ref.d.ts +61 -0
- package/dist/query/column-ref.js +66 -0
- package/dist/query/compound-unique.d.ts +21 -0
- package/dist/query/compound-unique.js +97 -19
- package/dist/query/deferred.d.ts +53 -16
- package/dist/query/expr.d.ts +217 -0
- package/dist/query/expr.js +848 -0
- package/dist/query/filters.d.ts +155 -53
- package/dist/query/filters.js +597 -72
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.js +4 -0
- package/dist/query/json-value.d.ts +43 -0
- package/dist/query/json-value.js +54 -0
- package/dist/query/normalize-args.d.ts +87 -0
- package/dist/query/normalize-args.js +517 -0
- package/dist/query/option-surface.d.ts +16 -4
- package/dist/query/option-surface.js +56 -13
- package/dist/query/relation-names.d.ts +14 -0
- package/dist/query/relation-names.js +77 -19
- package/dist/query/relations.d.ts +30 -3
- package/dist/query/relations.js +220 -67
- package/dist/query/row-lock.d.ts +49 -0
- package/dist/query/row-lock.js +131 -0
- package/dist/query/sql-comment.d.ts +161 -0
- package/dist/query/sql-comment.js +374 -0
- package/dist/query/stream-idle.d.ts +132 -0
- package/dist/query/stream-idle.js +203 -0
- package/dist/query/table-client.d.ts +101 -0
- package/dist/query/table-client.js +70 -0
- package/dist/query/types.d.ts +677 -298
- package/dist/query/utils.d.ts +105 -6
- package/dist/query/utils.js +414 -18
- package/dist/query/warn-registry.d.ts +18 -0
- package/dist/query/warn-registry.js +18 -0
- package/dist/query/where-compile.d.ts +138 -14
- package/dist/query/where-compile.js +508 -31
- package/dist/query/where.d.ts +159 -59
- package/dist/query/where.js +711 -248
- package/dist/query/writes.d.ts +112 -50
- package/dist/query/writes.js +1496 -202
- package/dist/query-events.d.ts +4 -2
- package/dist/query-events.js +3 -1
- package/dist/realtime.d.ts +7 -3
- package/dist/realtime.js +13 -2
- package/dist/schema-builder.d.ts +55 -4
- package/dist/schema-builder.js +381 -18
- package/dist/schema-metadata.d.ts +21 -1
- package/dist/schema-metadata.js +28 -7
- package/dist/schema-sql.d.ts +75 -5
- package/dist/schema-sql.js +494 -44
- package/dist/schema.d.ts +26 -5
- package/dist/schema.js +109 -13
- package/dist/sql-placeholders.d.ts +56 -0
- package/dist/sql-placeholders.js +200 -0
- package/dist/sqlite.d.ts +75 -20
- package/dist/sqlite.js +597 -99
- package/dist/supplied-pool-types.d.ts +48 -0
- package/dist/supplied-pool-types.js +247 -0
- package/dist/transaction-control.d.ts +51 -0
- package/dist/transaction-control.js +311 -0
- package/dist/transaction-state.d.ts +342 -0
- package/dist/transaction-state.js +696 -0
- package/dist/typed-sql.d.ts +37 -3
- package/dist/typed-sql.js +100 -8
- package/package.json +7 -4
- package/skills/turbine-orm/SKILL.md +56 -6
package/dist/cjs/cli/observe.js
CHANGED
|
@@ -16,6 +16,8 @@ const node_crypto_1 = require("node:crypto");
|
|
|
16
16
|
const node_http_1 = require("node:http");
|
|
17
17
|
const pg_1 = __importDefault(require("pg"));
|
|
18
18
|
const connection_guard_js_1 = require("../connection-guard.js");
|
|
19
|
+
const local_server_guard_js_1 = require("./local-server-guard.js");
|
|
20
|
+
const local_server_output_js_1 = require("./local-server-output.js");
|
|
19
21
|
const observe_ui_js_1 = require("./observe-ui.js");
|
|
20
22
|
const rate_limit_js_1 = require("./rate-limit.js");
|
|
21
23
|
const ui_js_1 = require("./ui.js");
|
|
@@ -34,31 +36,41 @@ async function startObserve(options) {
|
|
|
34
36
|
console.error(`[turbine] observe pool error: ${(0, ui_js_1.redactUrl)(err.message)}`);
|
|
35
37
|
});
|
|
36
38
|
(0, connection_guard_js_1.absorbCheckedOutErrors)(pool);
|
|
37
|
-
const probe = await pool.connect();
|
|
38
|
-
try {
|
|
39
|
-
await probe.query('SELECT 1');
|
|
40
|
-
}
|
|
41
|
-
finally {
|
|
42
|
-
probe.release();
|
|
43
|
-
}
|
|
44
39
|
const authToken = (0, node_crypto_1.randomBytes)(24).toString('hex');
|
|
40
|
+
const ticketGate = (0, local_server_guard_js_1.createTicketGate)();
|
|
45
41
|
const rateLimiter = new Map();
|
|
46
42
|
const server = (0, node_http_1.createServer)((req, res) => {
|
|
47
|
-
handleRequest(req, res, pool, options, authToken, rateLimiter).catch((err) => {
|
|
48
|
-
sendJson(res, 500, { error:
|
|
43
|
+
handleRequest(req, res, pool, options, authToken, rateLimiter, ticketGate).catch((err) => {
|
|
44
|
+
sendJson(res, 500, { error: (0, local_server_output_js_1.responseErrorText)(err) });
|
|
49
45
|
});
|
|
50
46
|
});
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
47
|
+
try {
|
|
48
|
+
const probe = await pool.connect();
|
|
49
|
+
try {
|
|
50
|
+
await probe.query('SELECT 1');
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
probe.release();
|
|
54
|
+
}
|
|
55
|
+
await new Promise((resolve, reject) => {
|
|
56
|
+
server.once('error', reject);
|
|
57
|
+
server.listen(options.port, options.host, () => {
|
|
58
|
+
server.off('error', reject);
|
|
59
|
+
resolve();
|
|
60
|
+
});
|
|
56
61
|
});
|
|
57
|
-
}
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
// A start that fails (probe refused, port taken) must not leave the probe
|
|
65
|
+
// connection idling in a pool nobody holds: it would keep a database session
|
|
66
|
+
// open and the process alive until the idle timeout.
|
|
67
|
+
await pool.end().catch(() => { });
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
58
70
|
const hostPart = options.host.includes(':') && !options.host.startsWith('[') ? `[${options.host}]` : options.host;
|
|
59
|
-
const url = `http://${hostPart}:${options.port}/?token=${
|
|
71
|
+
const url = `http://${hostPart}:${options.port}/?token=${ticketGate.token}`;
|
|
60
72
|
if (options.openBrowser) {
|
|
61
|
-
|
|
73
|
+
(0, local_server_output_js_1.openInBrowser)(url);
|
|
62
74
|
}
|
|
63
75
|
return {
|
|
64
76
|
authToken,
|
|
@@ -72,15 +84,31 @@ async function startObserve(options) {
|
|
|
72
84
|
// ---------------------------------------------------------------------------
|
|
73
85
|
// Request routing
|
|
74
86
|
// ---------------------------------------------------------------------------
|
|
75
|
-
async function handleRequest(req, res, pool, options, authToken, rateLimiter
|
|
87
|
+
async function handleRequest(req, res, pool, options, authToken, rateLimiter,
|
|
88
|
+
/** The one-time login ticket. Absent on a hand-built call: the ticket is then `authToken` itself. */
|
|
89
|
+
ticketGate = gateFor(authToken)) {
|
|
76
90
|
const hostPart = options.host.includes(':') && !options.host.startsWith('[') ? `[${options.host}]` : options.host;
|
|
77
91
|
const expectedOrigin = `http://${hostPart}:${options.port}`;
|
|
92
|
+
// DNS rebinding: a page on another domain that re-resolves to this port still
|
|
93
|
+
// sends its own hostname in `Host`. Checked before any route or token logic.
|
|
94
|
+
if (!(0, local_server_guard_js_1.isAllowedHostHeader)(req.headers.host, options.host, options.port)) {
|
|
95
|
+
sendJson(res, 403, { error: 'host not allowed: open Observe at the address printed in the terminal' });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
78
98
|
const origin = req.headers.origin;
|
|
79
99
|
if (origin && origin !== expectedOrigin) {
|
|
80
100
|
sendJson(res, 403, { error: 'cross-origin requests not allowed' });
|
|
81
101
|
return;
|
|
82
102
|
}
|
|
83
|
-
|
|
103
|
+
// A request target that is not a URL (`GET //`) is a client error, not a 500.
|
|
104
|
+
let url;
|
|
105
|
+
try {
|
|
106
|
+
url = new URL(req.url ?? '/', expectedOrigin);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
sendJson(res, 400, { error: 'malformed request target' });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
84
112
|
const pathname = url.pathname;
|
|
85
113
|
if (pathname === '/' || pathname === '/index.html') {
|
|
86
114
|
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
@@ -88,13 +116,28 @@ async function handleRequest(req, res, pool, options, authToken, rateLimiter) {
|
|
|
88
116
|
return;
|
|
89
117
|
}
|
|
90
118
|
const queryToken = url.searchParams.get('token');
|
|
91
|
-
if (queryToken
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
119
|
+
if (queryToken) {
|
|
120
|
+
// The printed URL's token is a ONE-TIME ticket, exchanged here for the
|
|
121
|
+
// session cookie by the first GET that carries it. HEAD never spends it.
|
|
122
|
+
const outcome = req.method === 'GET' ? (0, local_server_guard_js_1.redeemTicket)(ticketGate, queryToken) : 'invalid';
|
|
123
|
+
if (outcome === 'granted') {
|
|
124
|
+
res.writeHead(302, {
|
|
125
|
+
Location: '/',
|
|
126
|
+
'Set-Cookie': `turbine_observe_token=${authToken}; Path=/; HttpOnly; SameSite=Strict`,
|
|
127
|
+
});
|
|
128
|
+
res.end();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (outcome === 'used') {
|
|
132
|
+
if (isAuthorized(req, authToken)) {
|
|
133
|
+
res.writeHead(302, { Location: '/' });
|
|
134
|
+
res.end();
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
sendText(res, 403, 'This login link was already used. Restart Observe to get a new one.');
|
|
138
|
+
}
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
98
141
|
}
|
|
99
142
|
sendHtml(res, 200, observe_ui_js_1.OBSERVE_HTML, cspNonce());
|
|
100
143
|
return;
|
|
@@ -127,7 +170,7 @@ async function handleRequest(req, res, pool, options, authToken, rateLimiter) {
|
|
|
127
170
|
// ---------------------------------------------------------------------------
|
|
128
171
|
function isAuthorized(req, expectedToken) {
|
|
129
172
|
const headerToken = req.headers['x-turbine-token'];
|
|
130
|
-
if (typeof headerToken === 'string' && constantTimeEqual(headerToken, expectedToken)) {
|
|
173
|
+
if (typeof headerToken === 'string' && (0, local_server_guard_js_1.constantTimeEqual)(headerToken, expectedToken)) {
|
|
131
174
|
return true;
|
|
132
175
|
}
|
|
133
176
|
const cookieHeader = req.headers.cookie ?? '';
|
|
@@ -135,18 +178,23 @@ function isAuthorized(req, expectedToken) {
|
|
|
135
178
|
// ENDS with this one (`x_turbine_observe_token=...`) matched first and the real
|
|
136
179
|
// token was never compared, denying the legitimate session.
|
|
137
180
|
const match = /(?:^|;\s*)turbine_observe_token=([a-f0-9]+)/.exec(cookieHeader);
|
|
138
|
-
if (match?.[1] && constantTimeEqual(match[1], expectedToken)) {
|
|
181
|
+
if (match?.[1] && (0, local_server_guard_js_1.constantTimeEqual)(match[1], expectedToken)) {
|
|
139
182
|
return true;
|
|
140
183
|
}
|
|
141
184
|
return false;
|
|
142
185
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
186
|
+
/**
|
|
187
|
+
* The ticket for a caller that passed none (a hand-built dispatch): `authToken`
|
|
188
|
+
* itself, remembered per token so it is still spent exactly once.
|
|
189
|
+
*/
|
|
190
|
+
const FALLBACK_GATES = new Map();
|
|
191
|
+
function gateFor(authToken) {
|
|
192
|
+
let gate = FALLBACK_GATES.get(authToken);
|
|
193
|
+
if (!gate) {
|
|
194
|
+
gate = (0, local_server_guard_js_1.createTicketGate)(authToken);
|
|
195
|
+
FALLBACK_GATES.set(authToken, gate);
|
|
196
|
+
}
|
|
197
|
+
return gate;
|
|
150
198
|
}
|
|
151
199
|
// ---------------------------------------------------------------------------
|
|
152
200
|
// API handlers
|
|
@@ -195,7 +243,7 @@ async function apiLatency(res, pool, params) {
|
|
|
195
243
|
// this server writes no log of its own, so the HTTP response is the only
|
|
196
244
|
// channel either error could reach. Same shape as cli/studio.ts.
|
|
197
245
|
}
|
|
198
|
-
sendJson(res, 500, { error:
|
|
246
|
+
sendJson(res, 500, { error: (0, local_server_output_js_1.responseErrorText)(err) });
|
|
199
247
|
}
|
|
200
248
|
finally {
|
|
201
249
|
client.release();
|
|
@@ -236,7 +284,7 @@ async function apiModels(res, pool, params) {
|
|
|
236
284
|
// this server writes no log of its own, so the HTTP response is the only
|
|
237
285
|
// channel either error could reach. Same shape as cli/studio.ts.
|
|
238
286
|
}
|
|
239
|
-
sendJson(res, 500, { error:
|
|
287
|
+
sendJson(res, 500, { error: (0, local_server_output_js_1.responseErrorText)(err) });
|
|
240
288
|
}
|
|
241
289
|
finally {
|
|
242
290
|
client.release();
|
|
@@ -284,21 +332,3 @@ function sendText(res, status, text) {
|
|
|
284
332
|
res.writeHead(status, { ...SECURITY_HEADERS, 'Content-Security-Policy': JSON_CSP, 'Content-Type': 'text/plain' });
|
|
285
333
|
res.end(text);
|
|
286
334
|
}
|
|
287
|
-
// ---------------------------------------------------------------------------
|
|
288
|
-
// Browser open
|
|
289
|
-
// ---------------------------------------------------------------------------
|
|
290
|
-
function openUrl(url) {
|
|
291
|
-
const { platform: os } = process;
|
|
292
|
-
const { spawn } = require('node:child_process');
|
|
293
|
-
try {
|
|
294
|
-
if (os === 'darwin')
|
|
295
|
-
spawn('open', [url], { stdio: 'ignore', detached: true }).unref();
|
|
296
|
-
else if (os === 'win32')
|
|
297
|
-
spawn('cmd', ['/c', 'start', '', url], { stdio: 'ignore', detached: true }).unref();
|
|
298
|
-
else
|
|
299
|
-
spawn('xdg-open', [url], { stdio: 'ignore', detached: true }).unref();
|
|
300
|
-
}
|
|
301
|
-
catch {
|
|
302
|
-
// Best effort
|
|
303
|
-
}
|
|
304
|
-
}
|
|
@@ -62,32 +62,15 @@
|
|
|
62
62
|
* name-carrying position that is not a string is refused as a SHAPE here,
|
|
63
63
|
* never skipped: skipping is the branch that leaks.
|
|
64
64
|
*/
|
|
65
|
+
import { RELATION_FILTER_WRAPPERS } from '../query/filters.js';
|
|
65
66
|
import type { SchemaMetadata, TableMetadata } from '../schema.js';
|
|
66
67
|
/**
|
|
67
|
-
* Relation-filter wrappers whose body is a clause against the relation's target
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* builder unguarded.
|
|
72
|
-
*
|
|
73
|
-
* 1. this one, the only NAMED definition;
|
|
74
|
-
* 2. `query/where-compile.ts`, inlined as `'some' in x || 'every' in x || …`;
|
|
75
|
-
* 3. `prisma-compat.ts`, SPLIT across `RELATION_QUANTIFIERS`
|
|
76
|
-
* (`some`/`every`/`none`) and two inline `k === 'is' || k === 'isNot'`
|
|
77
|
-
* tests, which is the copy most likely to drift because half of it does
|
|
78
|
-
* not look like a list at all.
|
|
79
|
-
*
|
|
80
|
-
* Copy 2 belongs to the SQL compiler and cannot import this one (a query-path
|
|
81
|
-
* module must not depend on `cli/`), so the deduplication has to go the other
|
|
82
|
-
* way: the list wants to live in `query/filters.ts` and be imported here, the
|
|
83
|
-
* way {@link COLUMN_REF_OPERATORS} above now is.
|
|
84
|
-
*
|
|
85
|
-
* That destination now EXISTS (`RELATION_FILTER_WRAPPERS` in `query/filters.ts`)
|
|
86
|
-
* and nothing imports it, including this file, so it is a fourth copy rather
|
|
87
|
-
* than a deduplication and this comment is still the only thing holding them in
|
|
88
|
-
* step. Moving each walk onto it is a change per walk, each with its own tests.
|
|
68
|
+
* Relation-filter wrappers whose body is a clause against the relation's target:
|
|
69
|
+
* the SQL compiler's own list (`query/filters.ts`), imported so a wrapper the
|
|
70
|
+
* compiler understands can never reach the builder unguarded. Re-exported for
|
|
71
|
+
* callers that still import it from here.
|
|
89
72
|
*/
|
|
90
|
-
export
|
|
73
|
+
export { RELATION_FILTER_WRAPPERS };
|
|
91
74
|
/**
|
|
92
75
|
* Recursion bound for the guard walk.
|
|
93
76
|
*
|
|
@@ -101,6 +84,43 @@ export declare const RELATION_FILTER_WRAPPERS: readonly ["some", "none", "every"
|
|
|
101
84
|
* nothing the builder would accept is refused here for depth alone.
|
|
102
85
|
*/
|
|
103
86
|
export declare const PII_GUARD_MAX_DEPTH = 32;
|
|
87
|
+
/**
|
|
88
|
+
* Find a `lock` option anywhere a query level can carry one: the args
|
|
89
|
+
* themselves, or the options of any `with` entry at any depth. Returns the
|
|
90
|
+
* dotted path to the shallowest hit (`lock`, `with.posts.lock`), or `null`.
|
|
91
|
+
*
|
|
92
|
+
* WHY THIS IS NOT PART OF {@link assertNoPiiPredicates}. A locking read is not
|
|
93
|
+
* a hidden-column question, and the PII walk is conditional by design: Studio
|
|
94
|
+
* skips it under `--show-pii`, `explain_query` hands it an allowlisted subset
|
|
95
|
+
* of its arguments, and `compile_query` hands it only `where` for an aggregate.
|
|
96
|
+
* A read-only refusal has to hold in every one of those, so each surface calls
|
|
97
|
+
* this directly and unconditionally. It lives in this module because both
|
|
98
|
+
* surfaces already import it, which is what keeps the rule and its wording in
|
|
99
|
+
* one place.
|
|
100
|
+
*
|
|
101
|
+
* ANY VALUE other than `undefined` counts, `null` and `false` included. The
|
|
102
|
+
* builder treats exactly `undefined` as "no lock" and refuses every other
|
|
103
|
+
* non-lock value itself (E003), so answering "present" for all of them means
|
|
104
|
+
* the verdict here never depends on what the builder would have made of it.
|
|
105
|
+
*
|
|
106
|
+
* A `with` MAP is keyed by relation names, so a relation that happens to be
|
|
107
|
+
* called `lock` is a level to descend into and never a hit; only the option
|
|
108
|
+
* position on a level is read. A table column called `lock` is likewise safe:
|
|
109
|
+
* columns live under `where` / `orderBy` / `select`, which are not searched.
|
|
110
|
+
*
|
|
111
|
+
* Iterative and breadth-first, so a deeply nested payload costs time and no
|
|
112
|
+
* stack, and the shallowest hit is the one reported. The visited set makes it
|
|
113
|
+
* terminate on a cyclic object, which parsed JSON cannot produce but a caller
|
|
114
|
+
* of a pure helper can.
|
|
115
|
+
*/
|
|
116
|
+
export declare function findRowLockOption(args: Record<string, unknown>): string | null;
|
|
117
|
+
/**
|
|
118
|
+
* The refusal text for a `lock` found by {@link findRowLockOption}, shared so
|
|
119
|
+
* the two surfaces cannot word the same rule two ways. `path` is interpolated
|
|
120
|
+
* as given: it can carry a caller-chosen relation name, so each surface passes
|
|
121
|
+
* it through its own printable-name escaping first.
|
|
122
|
+
*/
|
|
123
|
+
export declare function rowLockRefusalMessage(path: string, tool: string): string;
|
|
104
124
|
/**
|
|
105
125
|
* The two things Studio and the MCP server genuinely do differently, plus the
|
|
106
126
|
* schema the walk resolves relation targets against.
|
|
@@ -65,35 +65,13 @@
|
|
|
65
65
|
*/
|
|
66
66
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
67
67
|
exports.PII_GUARD_MAX_DEPTH = exports.RELATION_FILTER_WRAPPERS = void 0;
|
|
68
|
+
exports.findRowLockOption = findRowLockOption;
|
|
69
|
+
exports.rowLockRefusalMessage = rowLockRefusalMessage;
|
|
68
70
|
exports.assertNoPiiPredicates = assertNoPiiPredicates;
|
|
71
|
+
const column_ref_js_1 = require("../query/column-ref.js");
|
|
69
72
|
const filters_js_1 = require("../query/filters.js");
|
|
73
|
+
Object.defineProperty(exports, "RELATION_FILTER_WRAPPERS", { enumerable: true, get: function () { return filters_js_1.RELATION_FILTER_WRAPPERS; } });
|
|
70
74
|
const utils_js_1 = require("../query/utils.js");
|
|
71
|
-
/**
|
|
72
|
-
* Relation-filter wrappers whose body is a clause against the relation's target.
|
|
73
|
-
*
|
|
74
|
-
* THREE copies of this list exist in the tree, and they have to agree: a wrapper
|
|
75
|
-
* the compiler understands and this guard does not is a wrapper that reaches the
|
|
76
|
-
* builder unguarded.
|
|
77
|
-
*
|
|
78
|
-
* 1. this one, the only NAMED definition;
|
|
79
|
-
* 2. `query/where-compile.ts`, inlined as `'some' in x || 'every' in x || …`;
|
|
80
|
-
* 3. `prisma-compat.ts`, SPLIT across `RELATION_QUANTIFIERS`
|
|
81
|
-
* (`some`/`every`/`none`) and two inline `k === 'is' || k === 'isNot'`
|
|
82
|
-
* tests, which is the copy most likely to drift because half of it does
|
|
83
|
-
* not look like a list at all.
|
|
84
|
-
*
|
|
85
|
-
* Copy 2 belongs to the SQL compiler and cannot import this one (a query-path
|
|
86
|
-
* module must not depend on `cli/`), so the deduplication has to go the other
|
|
87
|
-
* way: the list wants to live in `query/filters.ts` and be imported here, the
|
|
88
|
-
* way {@link COLUMN_REF_OPERATORS} above now is.
|
|
89
|
-
*
|
|
90
|
-
* That destination now EXISTS (`RELATION_FILTER_WRAPPERS` in `query/filters.ts`)
|
|
91
|
-
* and nothing imports it, including this file, so it is a fourth copy rather
|
|
92
|
-
* than a deduplication and this comment is still the only thing holding them in
|
|
93
|
-
* step. Moving each walk onto it is a change per walk, each with its own tests.
|
|
94
|
-
*/
|
|
95
|
-
exports.RELATION_FILTER_WRAPPERS = ['some', 'none', 'every', 'is', 'isNot'];
|
|
96
|
-
const RELATION_FILTER_WRAPPER_SET = new Set(exports.RELATION_FILTER_WRAPPERS);
|
|
97
75
|
/**
|
|
98
76
|
* Recursion bound for the guard walk.
|
|
99
77
|
*
|
|
@@ -133,6 +111,78 @@ const LEVEL_OBJECT_KEYS = new Set([
|
|
|
133
111
|
* which is what the builder itself branches on.
|
|
134
112
|
*/
|
|
135
113
|
const RELATION_PICK_KEYS = new Set(['pick', 'by', 'direction', 'nulls', 'plan']);
|
|
114
|
+
/**
|
|
115
|
+
* Find a `lock` option anywhere a query level can carry one: the args
|
|
116
|
+
* themselves, or the options of any `with` entry at any depth. Returns the
|
|
117
|
+
* dotted path to the shallowest hit (`lock`, `with.posts.lock`), or `null`.
|
|
118
|
+
*
|
|
119
|
+
* WHY THIS IS NOT PART OF {@link assertNoPiiPredicates}. A locking read is not
|
|
120
|
+
* a hidden-column question, and the PII walk is conditional by design: Studio
|
|
121
|
+
* skips it under `--show-pii`, `explain_query` hands it an allowlisted subset
|
|
122
|
+
* of its arguments, and `compile_query` hands it only `where` for an aggregate.
|
|
123
|
+
* A read-only refusal has to hold in every one of those, so each surface calls
|
|
124
|
+
* this directly and unconditionally. It lives in this module because both
|
|
125
|
+
* surfaces already import it, which is what keeps the rule and its wording in
|
|
126
|
+
* one place.
|
|
127
|
+
*
|
|
128
|
+
* ANY VALUE other than `undefined` counts, `null` and `false` included. The
|
|
129
|
+
* builder treats exactly `undefined` as "no lock" and refuses every other
|
|
130
|
+
* non-lock value itself (E003), so answering "present" for all of them means
|
|
131
|
+
* the verdict here never depends on what the builder would have made of it.
|
|
132
|
+
*
|
|
133
|
+
* A `with` MAP is keyed by relation names, so a relation that happens to be
|
|
134
|
+
* called `lock` is a level to descend into and never a hit; only the option
|
|
135
|
+
* position on a level is read. A table column called `lock` is likewise safe:
|
|
136
|
+
* columns live under `where` / `orderBy` / `select`, which are not searched.
|
|
137
|
+
*
|
|
138
|
+
* Iterative and breadth-first, so a deeply nested payload costs time and no
|
|
139
|
+
* stack, and the shallowest hit is the one reported. The visited set makes it
|
|
140
|
+
* terminate on a cyclic object, which parsed JSON cannot produce but a caller
|
|
141
|
+
* of a pure helper can.
|
|
142
|
+
*/
|
|
143
|
+
function findRowLockOption(args) {
|
|
144
|
+
const queue = [{ level: args, parent: null, relation: '' }];
|
|
145
|
+
const seen = new Set();
|
|
146
|
+
for (let next = 0; next < queue.length; next++) {
|
|
147
|
+
const frame = queue[next];
|
|
148
|
+
if (seen.has(frame.level))
|
|
149
|
+
continue;
|
|
150
|
+
seen.add(frame.level);
|
|
151
|
+
if (frame.level.lock !== undefined)
|
|
152
|
+
return lockPath(frame);
|
|
153
|
+
const withClause = frame.level.with;
|
|
154
|
+
if (!isPlainObject(withClause))
|
|
155
|
+
continue;
|
|
156
|
+
for (const [relation, spec] of Object.entries(withClause)) {
|
|
157
|
+
if (isPlainObject(spec))
|
|
158
|
+
queue.push({ level: spec, parent: frame, relation });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
/** `lock`, `with.posts.lock`, `with.posts.with.author.lock`: the path from the root to a hit. */
|
|
164
|
+
function lockPath(frame) {
|
|
165
|
+
const relations = [];
|
|
166
|
+
for (let at = frame; at !== null && at.parent !== null; at = at.parent) {
|
|
167
|
+
relations.push(at.relation);
|
|
168
|
+
}
|
|
169
|
+
const parts = [];
|
|
170
|
+
for (let i = relations.length - 1; i >= 0; i--)
|
|
171
|
+
parts.push('with', relations[i]);
|
|
172
|
+
parts.push('lock');
|
|
173
|
+
return parts.join('.');
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* The refusal text for a `lock` found by {@link findRowLockOption}, shared so
|
|
177
|
+
* the two surfaces cannot word the same rule two ways. `path` is interpolated
|
|
178
|
+
* as given: it can carry a caller-chosen relation name, so each surface passes
|
|
179
|
+
* it through its own printable-name escaping first.
|
|
180
|
+
*/
|
|
181
|
+
function rowLockRefusalMessage(path, tool) {
|
|
182
|
+
return (`${tool} does not accept \`${path}\`: it is read-only, and a locking read takes row locks, which needs a ` +
|
|
183
|
+
'write-capable transaction in your application. Remove it from the query here (nothing else about the query ' +
|
|
184
|
+
'changes), and run the locking read inside db.$transaction(async (tx) => ...) in your code.');
|
|
185
|
+
}
|
|
136
186
|
/** An object or array value, i.e. something that can carry a nested name. */
|
|
137
187
|
function isObjectLike(value) {
|
|
138
188
|
return value !== null && typeof value === 'object';
|
|
@@ -228,23 +278,34 @@ function assertNoPiiPredicates(args, rootTable, host) {
|
|
|
228
278
|
/**
|
|
229
279
|
* A column-keyed operator object (`{ gt: 5 }`, `{ sort, nulls }`, a JSON path,
|
|
230
280
|
* a vector distance) holds values, not names, with ONE exception: the
|
|
231
|
-
* comparison operators also accept `
|
|
232
|
-
* a column-to-column comparison in the same table. Scan for
|
|
281
|
+
* comparison operators also accept a column reference (`col('otherField')`),
|
|
282
|
+
* which compiles to a column-to-column comparison in the same table. Scan for
|
|
283
|
+
* that, including inside a `not` operator object, which compiles to
|
|
284
|
+
* `NOT (<inner>)` with the same operand positions.
|
|
285
|
+
*
|
|
286
|
+
* A reference carries a symbol brand that parsed JSON cannot produce, so the
|
|
287
|
+
* request data these tools compile never holds one, and the builder refuses
|
|
288
|
+
* the plain `{ col }` spelling. That spelling is refused HERE as well, as a
|
|
289
|
+
* shape: the guard must not depend on the builder to close a channel it is
|
|
290
|
+
* responsible for.
|
|
233
291
|
*
|
|
234
292
|
* The operator list is IMPORTED from `query/filters.ts` rather than copied:
|
|
235
293
|
* that is the set the SQL compiler itself branches on, and a guard that
|
|
236
294
|
* disagreed with it would be guarding a different query than the one that
|
|
237
|
-
* runs.
|
|
238
|
-
* channel this scan exists to close, silently.
|
|
295
|
+
* runs.
|
|
239
296
|
*/
|
|
240
|
-
const checkColumnRefs = (table, operatorNode) => {
|
|
297
|
+
const checkColumnRefs = (table, operatorNode, depth) => {
|
|
298
|
+
assertWithinDepth(depth);
|
|
241
299
|
for (const opKey of filters_js_1.COLUMN_REF_OPERATORS) {
|
|
242
300
|
const operand = (0, utils_js_1.ownLookup)(operatorNode, opKey);
|
|
243
|
-
if (
|
|
301
|
+
if ((0, filters_js_1.isColumnRef)(operand)) {
|
|
302
|
+
checkColumnName(table, operand.col);
|
|
244
303
|
continue;
|
|
245
|
-
|
|
246
|
-
if (
|
|
247
|
-
|
|
304
|
+
}
|
|
305
|
+
if ((0, column_ref_js_1.isPlainColumnRefShape)(operand))
|
|
306
|
+
host.refuseShape(table, `${opKey}: { col }`);
|
|
307
|
+
if (opKey === 'not' && isPlainObject(operand))
|
|
308
|
+
checkColumnRefs(table, operand, depth + 1);
|
|
248
309
|
}
|
|
249
310
|
};
|
|
250
311
|
/**
|
|
@@ -271,7 +332,7 @@ function assertNoPiiPredicates(args, rootTable, host) {
|
|
|
271
332
|
// body is a clause against the SAME target. Handing the wrapper to the
|
|
272
333
|
// column walker instead walked `is` as if it were a column of the target,
|
|
273
334
|
// so the inner clause was never visited at all.
|
|
274
|
-
if (scope === 'relation' && RELATION_FILTER_WRAPPER_SET.has(key)) {
|
|
335
|
+
if (scope === 'relation' && filters_js_1.RELATION_FILTER_WRAPPER_SET.has(key)) {
|
|
275
336
|
visitClause(value, table, depth + 1);
|
|
276
337
|
return;
|
|
277
338
|
}
|
|
@@ -299,7 +360,7 @@ function assertNoPiiPredicates(args, rootTable, host) {
|
|
|
299
360
|
if (scope === 'relation' && !table.allColumns.includes(column))
|
|
300
361
|
host.refuseShape(table, key);
|
|
301
362
|
if (isPlainObject(value))
|
|
302
|
-
checkColumnRefs(table, value);
|
|
363
|
+
checkColumnRefs(table, value, depth + 1);
|
|
303
364
|
};
|
|
304
365
|
/** A where / orderBy / cursor object (or a Prisma-style array of them). */
|
|
305
366
|
const visitClause = (node, table, depth) => {
|
|
@@ -16,9 +16,11 @@
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.DEFAULT_EXCLUDED_TABLES = void 0;
|
|
18
18
|
exports.resolvePrismaSchema = resolvePrismaSchema;
|
|
19
|
+
const introspect_js_1 = require("../introspect.js");
|
|
20
|
+
const utils_js_1 = require("../query/utils.js");
|
|
19
21
|
const schema_js_1 = require("../schema.js");
|
|
20
|
-
var
|
|
21
|
-
Object.defineProperty(exports, "DEFAULT_EXCLUDED_TABLES", { enumerable: true, get: function () { return
|
|
22
|
+
var introspect_js_2 = require("../introspect.js");
|
|
23
|
+
Object.defineProperty(exports, "DEFAULT_EXCLUDED_TABLES", { enumerable: true, get: function () { return introspect_js_2.DEFAULT_EXCLUDED_TABLES; } });
|
|
22
24
|
// ---------------------------------------------------------------------------
|
|
23
25
|
// Name-candidate helpers
|
|
24
26
|
// ---------------------------------------------------------------------------
|
|
@@ -106,7 +108,9 @@ function resolvePrismaSchema(ast, schema, options = {}) {
|
|
|
106
108
|
const result = {
|
|
107
109
|
models: [],
|
|
108
110
|
enums: [],
|
|
109
|
-
|
|
111
|
+
// Prototype-less: models, fields, relations and enums are keyed by names the
|
|
112
|
+
// Prisma schema chose, and `__proto__` / `constructor` are legal names there.
|
|
113
|
+
map: { models: (0, introspect_js_1.nullRecord)(), enums: (0, introspect_js_1.nullRecord)() },
|
|
110
114
|
hasUnresolved: false,
|
|
111
115
|
parseWarnings: ast.warnings,
|
|
112
116
|
noDb,
|
|
@@ -115,7 +119,7 @@ function resolvePrismaSchema(ast, schema, options = {}) {
|
|
|
115
119
|
for (const model of ast.models) {
|
|
116
120
|
const rt = modelTable.get(model.name);
|
|
117
121
|
const table = rt.table;
|
|
118
|
-
const tableMeta = table && resolvedSchema ? resolvedSchema.tables
|
|
122
|
+
const tableMeta = table && resolvedSchema ? (0, utils_js_1.ownLookup)(resolvedSchema.tables, table) : undefined;
|
|
119
123
|
const accessor = table ? (0, schema_js_1.snakeToCamel)(table) : null;
|
|
120
124
|
const status = noDb ? 'parsed' : table ? 'resolved' : 'unresolved';
|
|
121
125
|
const resolved = {
|
|
@@ -148,23 +152,23 @@ function resolvePrismaSchema(ast, schema, options = {}) {
|
|
|
148
152
|
result.models.push(resolved);
|
|
149
153
|
// Build the verified map entry (skip in --no-db and for unresolved tables).
|
|
150
154
|
if (!noDb && table && accessor) {
|
|
151
|
-
const fields =
|
|
155
|
+
const fields = (0, introspect_js_1.nullRecord)();
|
|
152
156
|
for (const f of resolved.fields) {
|
|
153
157
|
if (f.status === 'resolved' && f.turbineField)
|
|
154
158
|
fields[f.prismaName] = f.turbineField;
|
|
155
159
|
}
|
|
156
|
-
const relations =
|
|
160
|
+
const relations = (0, introspect_js_1.nullRecord)();
|
|
157
161
|
for (const r of resolved.relations) {
|
|
158
162
|
if (r.status === 'resolved' && r.turbineName && r.cardinality) {
|
|
159
163
|
relations[r.prismaName] = { name: r.turbineName, cardinality: r.cardinality };
|
|
160
164
|
}
|
|
161
165
|
}
|
|
162
|
-
const compoundUniques =
|
|
166
|
+
const compoundUniques = (0, introspect_js_1.nullRecord)();
|
|
163
167
|
for (const c of resolved.compoundUniques) {
|
|
164
168
|
if (c.status === 'resolved' && c.turbineFields)
|
|
165
169
|
compoundUniques[c.selector] = c.turbineFields;
|
|
166
170
|
}
|
|
167
|
-
const clientDefaults = collectClientDefaults(model, fields, resolvedSchema?.tables
|
|
171
|
+
const clientDefaults = collectClientDefaults(model, fields, (0, utils_js_1.ownLookup)(resolvedSchema?.tables ?? {}, table));
|
|
168
172
|
result.map.models[model.name] = {
|
|
169
173
|
table,
|
|
170
174
|
accessor,
|
|
@@ -264,7 +268,7 @@ function resolveScalarField(model, fieldName, tableMeta, noDb) {
|
|
|
264
268
|
if (noDb || !tableMeta) {
|
|
265
269
|
return { prismaName: fieldName, turbineField: null, column: null, status: noDb ? 'parsed' : 'unresolved' };
|
|
266
270
|
}
|
|
267
|
-
const turbineField = tableMeta.reverseColumnMap
|
|
271
|
+
const turbineField = (0, utils_js_1.ownLookup)(tableMeta.reverseColumnMap, column);
|
|
268
272
|
if (turbineField) {
|
|
269
273
|
return { prismaName: fieldName, turbineField, column, status: 'resolved' };
|
|
270
274
|
}
|
|
@@ -310,9 +314,9 @@ function relationFkColumns(model, field) {
|
|
|
310
314
|
function collectClientDefaults(model, resolvedFields, tableMeta) {
|
|
311
315
|
if (!tableMeta)
|
|
312
316
|
return undefined;
|
|
313
|
-
const out =
|
|
317
|
+
const out = (0, introspect_js_1.nullRecord)();
|
|
314
318
|
for (const field of model.fields) {
|
|
315
|
-
const turbineField = resolvedFields
|
|
319
|
+
const turbineField = (0, utils_js_1.ownLookup)(resolvedFields, field.name);
|
|
316
320
|
if (!turbineField)
|
|
317
321
|
continue;
|
|
318
322
|
const col = tableMeta.columns.find((c) => c.field === turbineField || c.name === turbineField);
|
|
@@ -472,11 +476,11 @@ function resolveCompoundUnique(model, key, tableMeta, noDb) {
|
|
|
472
476
|
if (noDb || !tableMeta)
|
|
473
477
|
return base;
|
|
474
478
|
const columns = key.fields.map((f) => fieldColumn(model, f));
|
|
475
|
-
const missing = columns.filter((c) => !tableMeta.reverseColumnMap
|
|
479
|
+
const missing = columns.filter((c) => !(0, utils_js_1.ownLookup)(tableMeta.reverseColumnMap, c));
|
|
476
480
|
if (missing.length > 0) {
|
|
477
481
|
return { ...base, reason: `column(s) not found: ${missing.join(', ')}` };
|
|
478
482
|
}
|
|
479
|
-
const turbineFields = columns.map((c) => tableMeta.reverseColumnMap
|
|
483
|
+
const turbineFields = columns.map((c) => (0, utils_js_1.ownLookup)(tableMeta.reverseColumnMap, c));
|
|
480
484
|
const want = [...columns].sort().join(',');
|
|
481
485
|
const matches = (setList) => setList.some((s) => [...s].sort().join(',') === want);
|
|
482
486
|
if (key.kind === 'id') {
|
|
@@ -93,11 +93,19 @@ function lineAt(src, offset) {
|
|
|
93
93
|
// ---------------------------------------------------------------------------
|
|
94
94
|
// Attribute-argument tokenizer
|
|
95
95
|
// ---------------------------------------------------------------------------
|
|
96
|
-
|
|
96
|
+
const QUOTED_ESCAPES = { '"': '"', '\\': '\\', n: '\n', t: '\t' };
|
|
97
|
+
/**
|
|
98
|
+
* Unquote a `"..."` literal, resolving the escapes Prisma supports.
|
|
99
|
+
*
|
|
100
|
+
* ONE left-to-right pass, because the escapes are not independent: after `\\`
|
|
101
|
+
* has become a backslash, that backslash must not be read again as the start of
|
|
102
|
+
* `\n` or `\t`. A chain of `.replace` calls does exactly that, so the literal
|
|
103
|
+
* `"C:\\new"` came out as `C:` + newline + `ew`.
|
|
104
|
+
*/
|
|
97
105
|
function unquote(raw) {
|
|
98
106
|
const s = raw.trim();
|
|
99
107
|
if (s.length >= 2 && s[0] === '"' && s[s.length - 1] === '"') {
|
|
100
|
-
return s.slice(1, -1).replace(/\\"/g,
|
|
108
|
+
return s.slice(1, -1).replace(/\\(["\\nt])/g, (_, escaped) => QUOTED_ESCAPES[escaped]);
|
|
101
109
|
}
|
|
102
110
|
return s;
|
|
103
111
|
}
|
|
@@ -429,7 +437,7 @@ function parseFieldLine(text, line, warnings) {
|
|
|
429
437
|
`Turbine generates the column from the database, so it IS present on the generated client.`);
|
|
430
438
|
}
|
|
431
439
|
for (const attr of attrs) {
|
|
432
|
-
const note = NOTED_FIELD_ATTRS[attr.name];
|
|
440
|
+
const note = Object.hasOwn(NOTED_FIELD_ATTRS, attr.name) ? NOTED_FIELD_ATTRS[attr.name] : undefined;
|
|
433
441
|
if (note)
|
|
434
442
|
warnings.push(`Line ${line}: field "${name}": ${note}`);
|
|
435
443
|
}
|
|
@@ -463,7 +471,7 @@ function parseModelBody(block, kind, src, warnings) {
|
|
|
463
471
|
// @@index and anything else: recorded in blockAttrs, unused. The
|
|
464
472
|
// few that change what the model MEANS get a warning rather than
|
|
465
473
|
// silence (see NOTED_BLOCK_ATTRS).
|
|
466
|
-
const note = NOTED_BLOCK_ATTRS[attr.name];
|
|
474
|
+
const note = Object.hasOwn(NOTED_BLOCK_ATTRS, attr.name) ? NOTED_BLOCK_ATTRS[attr.name] : undefined;
|
|
467
475
|
if (note)
|
|
468
476
|
warnings.push(`Line ${line}: model "${block.name}": ${note}`);
|
|
469
477
|
}
|