turbine-orm 0.49.0 → 0.50.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 +122 -39
- package/dist/cjs/adapters/cockroachdb.d.ts +39 -0
- package/dist/cjs/adapters/index.d.ts +110 -0
- package/dist/cjs/adapters/yugabytedb.d.ts +51 -0
- package/dist/cjs/cli/config.d.ts +181 -0
- package/dist/cjs/cli/config.js +32 -6
- package/dist/cjs/cli/destructive.d.ts +38 -0
- package/dist/cjs/cli/index.d.ts +359 -0
- package/dist/cjs/cli/index.js +228 -56
- package/dist/cjs/cli/loader.d.ts +61 -0
- package/dist/cjs/cli/mcp.d.ts +42 -0
- package/dist/cjs/cli/migrate.d.ts +356 -0
- package/dist/cjs/cli/migrate.js +131 -40
- package/dist/cjs/cli/observe-ui.d.ts +1 -0
- package/dist/cjs/cli/observe-ui.js +14 -5
- package/dist/cjs/cli/observe.d.ts +25 -0
- package/dist/cjs/cli/observe.js +49 -12
- package/dist/cjs/cli/pii-tags.d.ts +53 -0
- package/dist/cjs/cli/prisma-report.d.ts +33 -0
- package/dist/cjs/cli/prisma-report.js +73 -0
- package/dist/cjs/cli/prisma-resolve.d.ts +106 -0
- package/dist/cjs/cli/prisma-resolve.js +1 -0
- package/dist/cjs/cli/prisma-schema.d.ts +176 -0
- package/dist/cjs/cli/prisma-schema.js +82 -4
- package/dist/cjs/cli/rate-limit.d.ts +32 -0
- package/dist/cjs/cli/rate-limit.js +45 -0
- package/dist/cjs/cli/studio-demo.d.ts +43 -0
- package/dist/cjs/cli/studio-ui.generated.d.ts +1 -0
- package/dist/cjs/cli/studio.d.ts +207 -0
- package/dist/cjs/cli/studio.js +136 -71
- package/dist/cjs/cli/ui.d.ts +73 -0
- package/dist/cjs/cli/ui.js +51 -9
- package/dist/cjs/client.d.ts +837 -0
- package/dist/cjs/client.js +3 -0
- package/dist/cjs/dialect.d.ts +516 -0
- package/dist/cjs/dialect.js +37 -12
- package/dist/cjs/errors.d.ts +370 -0
- package/dist/cjs/generate.d.ts +137 -0
- package/dist/cjs/generate.js +39 -6
- package/dist/cjs/index-advisor.d.ts +153 -0
- package/dist/cjs/index-stats.d.ts +384 -0
- package/dist/cjs/index.d.ts +55 -0
- package/dist/cjs/index.js +7 -2
- package/dist/cjs/introspect.d.ts +269 -0
- package/dist/cjs/mssql.d.ts +232 -0
- package/dist/cjs/mssql.js +6 -0
- package/dist/cjs/mysql.d.ts +173 -0
- package/dist/cjs/mysql.js +16 -0
- package/dist/cjs/nested-write.d.ts +96 -0
- package/dist/cjs/nested-write.js +414 -24
- package/dist/cjs/observe.d.ts +115 -0
- package/dist/cjs/optional-peer-import.d.cts +72 -0
- package/dist/cjs/pipeline-submittable.d.ts +93 -0
- package/dist/cjs/pipeline.d.ts +71 -0
- package/dist/cjs/powdb-introspect.d.ts +84 -0
- package/dist/cjs/powdb.d.ts +931 -0
- package/dist/cjs/powdb.js +106 -21
- package/dist/cjs/powql.d.ts +592 -0
- package/dist/cjs/powql.js +42 -6
- package/dist/cjs/prisma-compat.d.ts +283 -0
- package/dist/cjs/prisma-compat.js +167 -9
- package/dist/cjs/query/aggregates.d.ts +92 -0
- package/dist/cjs/query/aggregates.js +7 -3
- package/dist/cjs/query/batched-loader.d.ts +193 -0
- package/dist/cjs/query/builder.d.ts +849 -0
- package/dist/cjs/query/builder.js +571 -65
- package/dist/cjs/query/compound-unique.d.ts +51 -0
- package/dist/cjs/query/deferred.d.ts +223 -0
- package/dist/cjs/query/filters.d.ts +201 -0
- package/dist/cjs/query/index.d.ts +14 -0
- package/dist/cjs/query/index.js +6 -1
- package/dist/cjs/query/relations.d.ts +609 -0
- package/dist/cjs/query/relations.js +693 -46
- package/dist/cjs/query/types.d.ts +1300 -0
- package/dist/cjs/query/utils.d.ts +209 -0
- package/dist/cjs/query/utils.js +208 -1
- package/dist/cjs/query/warn-registry.d.ts +68 -0
- package/dist/cjs/query/warn-registry.js +9 -0
- package/dist/cjs/query/where-compile.d.ts +139 -0
- package/dist/cjs/query/where.d.ts +548 -0
- package/dist/cjs/query/where.js +58 -22
- package/dist/cjs/query/writes.d.ts +172 -0
- package/dist/cjs/query/writes.js +105 -12
- package/dist/cjs/realtime.d.ts +70 -0
- package/dist/cjs/schema-builder.d.ts +354 -0
- package/dist/cjs/schema-metadata.d.ts +83 -0
- package/dist/cjs/schema-sql.d.ts +217 -0
- package/dist/cjs/schema-sql.js +23 -5
- package/dist/cjs/schema.d.ts +356 -0
- package/dist/cjs/schema.js +125 -0
- package/dist/cjs/seed.d.ts +15 -0
- package/dist/cjs/serverless.d.ts +142 -0
- package/dist/cjs/sqlite.d.ts +143 -0
- package/dist/cjs/sqlite.js +4 -0
- package/dist/cjs/typed-sql.d.ts +102 -0
- package/dist/cli/config.d.ts +18 -4
- package/dist/cli/config.js +31 -6
- package/dist/cli/index.d.ts +123 -0
- package/dist/cli/index.js +223 -58
- package/dist/cli/migrate.d.ts +59 -10
- package/dist/cli/migrate.js +128 -41
- package/dist/cli/observe-ui.d.ts +1 -1
- package/dist/cli/observe-ui.js +14 -5
- package/dist/cli/observe.d.ts +7 -1
- package/dist/cli/observe.js +48 -12
- package/dist/cli/prisma-report.d.ts +14 -0
- package/dist/cli/prisma-report.js +72 -0
- package/dist/cli/prisma-resolve.d.ts +6 -0
- package/dist/cli/prisma-resolve.js +1 -0
- package/dist/cli/prisma-schema.d.ts +62 -2
- package/dist/cli/prisma-schema.js +81 -4
- package/dist/cli/rate-limit.d.ts +32 -0
- package/dist/cli/rate-limit.js +40 -0
- package/dist/cli/studio.d.ts +5 -5
- package/dist/cli/studio.js +135 -70
- package/dist/cli/ui.d.ts +1 -1
- package/dist/cli/ui.js +51 -9
- package/dist/client.d.ts +40 -0
- package/dist/client.js +3 -0
- package/dist/dialect.d.ts +17 -1
- package/dist/dialect.js +37 -12
- package/dist/generate.js +40 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mssql.js +6 -0
- package/dist/mysql.js +16 -0
- package/dist/nested-write.d.ts +2 -0
- package/dist/nested-write.js +415 -25
- package/dist/powdb.d.ts +4 -2
- package/dist/powdb.js +106 -21
- package/dist/powql.d.ts +5 -0
- package/dist/powql.js +42 -6
- package/dist/prisma-compat.d.ts +2 -0
- package/dist/prisma-compat.js +166 -8
- package/dist/query/aggregates.js +7 -3
- package/dist/query/builder.d.ts +292 -21
- package/dist/query/builder.js +570 -64
- package/dist/query/deferred.d.ts +39 -0
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +1 -1
- package/dist/query/relations.d.ts +173 -5
- package/dist/query/relations.js +688 -47
- package/dist/query/types.d.ts +123 -39
- package/dist/query/utils.d.ts +116 -0
- package/dist/query/utils.js +198 -0
- package/dist/query/warn-registry.d.ts +9 -0
- package/dist/query/warn-registry.js +9 -0
- package/dist/query/where.d.ts +38 -1
- package/dist/query/where.js +58 -23
- package/dist/query/writes.d.ts +42 -1
- package/dist/query/writes.js +104 -13
- package/dist/schema-sql.d.ts +14 -0
- package/dist/schema-sql.js +23 -5
- package/dist/schema.d.ts +38 -0
- package/dist/schema.js +123 -0
- package/dist/sqlite.js +4 -0
- package/package.json +77 -28
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-rolled `schema.prisma` subset parser (zero dependencies).
|
|
3
|
+
*
|
|
4
|
+
* Powers `turbine migrate-from-prisma`. It parses ONLY the constructs the
|
|
5
|
+
* name-mapper needs (models, enums, views, fields, `@map`/`@@map`, relations
|
|
6
|
+
* including implicit m2m junctions, `@@unique` including named selectors, and
|
|
7
|
+
* `@@id`) and is deliberately LENIENT everywhere else: any attribute, block, or
|
|
8
|
+
* token it does not recognize is skipped and recorded as a warning, never a
|
|
9
|
+
* fatal error. The live DATABASE is the authority for resolution, so a partial
|
|
10
|
+
* parse is still useful.
|
|
11
|
+
*
|
|
12
|
+
* It is a pure leaf like `cli/destructive.ts` - it reads a string and returns
|
|
13
|
+
* data, touches no filesystem, database, or process state, and imports nothing
|
|
14
|
+
* from the rest of the package.
|
|
15
|
+
*
|
|
16
|
+
* Where it MUST understand a construct (an unterminated block/string, a broken
|
|
17
|
+
* `@@id`/`@@unique`/`@@map`/`@relation`) it throws {@link PrismaParseError} with
|
|
18
|
+
* a 1-based line number.
|
|
19
|
+
*/
|
|
20
|
+
/** A parsed attribute argument: positional or `key: value`, string/array/raw. */
|
|
21
|
+
export interface PrismaAttrArg {
|
|
22
|
+
/** Named-argument key (e.g. `fields`, `references`, `name`, `map`). Absent for positional args. */
|
|
23
|
+
key?: string;
|
|
24
|
+
/** `'string'` (unquoted literal), `'array'` (list of idents/strings), or `'raw'` (bare token). */
|
|
25
|
+
kind: 'string' | 'array' | 'raw';
|
|
26
|
+
/** Scalar value for string/raw kinds. */
|
|
27
|
+
value?: string;
|
|
28
|
+
/** Element list for the array kind (strings unquoted). */
|
|
29
|
+
items?: string[];
|
|
30
|
+
}
|
|
31
|
+
/** A field- or block-level attribute (`@map(...)`, `@@unique(...)`, ...). */
|
|
32
|
+
export interface PrismaAttr {
|
|
33
|
+
/** Attribute name without the leading `@`/`@@` (e.g. `map`, `relation`, `id`, `unique`). */
|
|
34
|
+
name: string;
|
|
35
|
+
/** Parsed argument list (empty when the attribute took no parens). */
|
|
36
|
+
args: PrismaAttrArg[];
|
|
37
|
+
/** True for a block attribute (`@@name`), false for a field attribute (`@name`). */
|
|
38
|
+
block: boolean;
|
|
39
|
+
/** 1-based source line the attribute was found on. */
|
|
40
|
+
line: number;
|
|
41
|
+
}
|
|
42
|
+
/** A parsed field line inside a model/view/type block. */
|
|
43
|
+
export interface PrismaField {
|
|
44
|
+
/** Field name as declared (the Prisma API name). */
|
|
45
|
+
name: string;
|
|
46
|
+
/** Base type with `[]` / `?` stripped (a scalar, enum, or model name). */
|
|
47
|
+
type: string;
|
|
48
|
+
/** Trailing `?` - the field is optional/nullable. */
|
|
49
|
+
optional: boolean;
|
|
50
|
+
/** Trailing `[]` - the field is a list. */
|
|
51
|
+
isList: boolean;
|
|
52
|
+
/** Field attributes in source order. */
|
|
53
|
+
attrs: PrismaAttr[];
|
|
54
|
+
/** 1-based source line. */
|
|
55
|
+
line: number;
|
|
56
|
+
}
|
|
57
|
+
/** A compound key derived from a `@@id` / `@@unique` block attribute. */
|
|
58
|
+
export interface PrismaCompoundKey {
|
|
59
|
+
/** Prisma field names participating, in declared order. */
|
|
60
|
+
fields: string[];
|
|
61
|
+
/** Explicit `name:` selector, else undefined (caller derives the underscore-join). */
|
|
62
|
+
name?: string;
|
|
63
|
+
/** Explicit `map:` constraint name, if any. */
|
|
64
|
+
map?: string;
|
|
65
|
+
/** `'id'` (from `@@id`) or `'unique'` (from `@@unique`). */
|
|
66
|
+
kind: 'id' | 'unique';
|
|
67
|
+
/** 1-based source line of the block attribute. */
|
|
68
|
+
line: number;
|
|
69
|
+
}
|
|
70
|
+
/** A parsed `model` / `view` / `type` block. */
|
|
71
|
+
export interface PrismaModel {
|
|
72
|
+
name: string;
|
|
73
|
+
kind: 'model' | 'view' | 'type';
|
|
74
|
+
/** `@@map("...")` target table name, if present. */
|
|
75
|
+
map?: string;
|
|
76
|
+
fields: PrismaField[];
|
|
77
|
+
/** Compound keys from `@@id` and `@@unique`. */
|
|
78
|
+
compoundKeys: PrismaCompoundKey[];
|
|
79
|
+
/** Every block attribute (`@@index`, `@@schema`, ...), recorded verbatim. */
|
|
80
|
+
blockAttrs: PrismaAttr[];
|
|
81
|
+
/** 1-based source line of the block header. */
|
|
82
|
+
line: number;
|
|
83
|
+
}
|
|
84
|
+
/** A parsed `enum` block. */
|
|
85
|
+
export interface PrismaEnum {
|
|
86
|
+
name: string;
|
|
87
|
+
/** Value names in declared order. */
|
|
88
|
+
values: string[];
|
|
89
|
+
/** `@@map("...")` target enum-type name, if present. */
|
|
90
|
+
map?: string;
|
|
91
|
+
line: number;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* A connection-string value inside a `datasource` block: either a literal
|
|
95
|
+
* string (`url = "postgres://..."`) or an `env("NAME")` indirection.
|
|
96
|
+
*/
|
|
97
|
+
export type PrismaConnectionValue = {
|
|
98
|
+
kind: 'literal';
|
|
99
|
+
value: string;
|
|
100
|
+
} | {
|
|
101
|
+
kind: 'env';
|
|
102
|
+
variable: string;
|
|
103
|
+
};
|
|
104
|
+
/** A parsed `datasource` block. Only the connection keys are retained. */
|
|
105
|
+
export interface PrismaDatasource {
|
|
106
|
+
/** Block name (`datasource db { ... }` -> `db`). */
|
|
107
|
+
name: string;
|
|
108
|
+
/** `provider = "postgresql"`, when declared as a literal. */
|
|
109
|
+
provider?: string;
|
|
110
|
+
/** `url = ...`, when declared as a literal or an `env(...)` call. */
|
|
111
|
+
url?: PrismaConnectionValue;
|
|
112
|
+
/** `directUrl = ...` (Prisma's non-pooled connection), same forms as `url`. */
|
|
113
|
+
directUrl?: PrismaConnectionValue;
|
|
114
|
+
/** 1-based source line of the block header. */
|
|
115
|
+
line: number;
|
|
116
|
+
}
|
|
117
|
+
/** The full parse result. */
|
|
118
|
+
export interface PrismaSchemaAst {
|
|
119
|
+
models: PrismaModel[];
|
|
120
|
+
enums: PrismaEnum[];
|
|
121
|
+
/** Parsed `datasource` blocks, in source order. */
|
|
122
|
+
datasources: PrismaDatasource[];
|
|
123
|
+
/** Non-fatal notes: skipped/unknown blocks and attributes. */
|
|
124
|
+
warnings: string[];
|
|
125
|
+
}
|
|
126
|
+
/** Thrown for a malformed construct the parser must understand. Carries a line number. */
|
|
127
|
+
export declare class PrismaParseError extends Error {
|
|
128
|
+
readonly line: number;
|
|
129
|
+
constructor(message: string, line: number);
|
|
130
|
+
}
|
|
131
|
+
/** Where a datasource connection string was resolved from. */
|
|
132
|
+
export interface ResolvedPrismaDatasourceUrl {
|
|
133
|
+
/** The connection string itself. */
|
|
134
|
+
url: string;
|
|
135
|
+
/** Datasource block name it came from. */
|
|
136
|
+
datasource: string;
|
|
137
|
+
/** Which key supplied it. */
|
|
138
|
+
key: 'url' | 'directUrl';
|
|
139
|
+
/** The environment variable read, when the value was an `env(...)` call. */
|
|
140
|
+
variable?: string;
|
|
141
|
+
}
|
|
142
|
+
/** Result of {@link resolvePrismaDatasourceUrl}. */
|
|
143
|
+
export interface PrismaDatasourceUrlLookup {
|
|
144
|
+
/** The first usable connection string found, if any. */
|
|
145
|
+
resolved?: ResolvedPrismaDatasourceUrl;
|
|
146
|
+
/**
|
|
147
|
+
* Environment variable names the schema declares via `env(...)` that were
|
|
148
|
+
* unset or empty, in lookup order. Surfaced in the no-URL error so the user
|
|
149
|
+
* sees exactly which variable the schema asked for.
|
|
150
|
+
*/
|
|
151
|
+
missingVariables: string[];
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Resolve the connection string a `schema.prisma` declares, reading `env(...)`
|
|
155
|
+
* indirections out of the supplied environment (pure: the environment is an
|
|
156
|
+
* argument, never `process.env` directly).
|
|
157
|
+
*
|
|
158
|
+
* Per datasource block, `url` is preferred and `directUrl` is the fallback:
|
|
159
|
+
* `url` is what Prisma itself uses for everything but migrations, and a pooled
|
|
160
|
+
* `url` introspects the same catalog as its direct twin.
|
|
161
|
+
*/
|
|
162
|
+
export declare function resolvePrismaDatasourceUrl(ast: Pick<PrismaSchemaAst, 'datasources'>, env: Record<string, string | undefined>): PrismaDatasourceUrlLookup;
|
|
163
|
+
/**
|
|
164
|
+
* Parse a `schema.prisma` source string into a {@link PrismaSchemaAst}.
|
|
165
|
+
*
|
|
166
|
+
* Understands: model / view / type / enum blocks; field lines with `@map`,
|
|
167
|
+
* `@id`, `@unique`, `@default`, `@updatedAt`, `@ignore`, `@relation`; and block
|
|
168
|
+
* attributes `@@map`, `@@id`, `@@unique`, `@@index`, `@@schema`; plus the
|
|
169
|
+
* `provider` / `url` / `directUrl` keys of each `datasource` block (see
|
|
170
|
+
* {@link resolvePrismaDatasourceUrl}). Unknown attributes and blocks are
|
|
171
|
+
* skipped into {@link PrismaSchemaAst.warnings}.
|
|
172
|
+
*
|
|
173
|
+
* @throws {@link PrismaParseError} on an unterminated block/paren/string or a
|
|
174
|
+
* structurally broken `@@id` / `@@unique` / `@@map`.
|
|
175
|
+
*/
|
|
176
|
+
export declare function parsePrismaSchema(source: string): PrismaSchemaAst;
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
22
|
exports.PrismaParseError = void 0;
|
|
23
|
+
exports.resolvePrismaDatasourceUrl = resolvePrismaDatasourceUrl;
|
|
23
24
|
exports.parsePrismaSchema = parsePrismaSchema;
|
|
24
25
|
// ---------------------------------------------------------------------------
|
|
25
26
|
// Error
|
|
@@ -437,6 +438,78 @@ function parseEnumBody(block, src) {
|
|
|
437
438
|
}
|
|
438
439
|
return en;
|
|
439
440
|
}
|
|
441
|
+
/**
|
|
442
|
+
* Parse a `datasource` block value: a quoted literal, or an `env("NAME")` call.
|
|
443
|
+
* Returns null for any other form (e.g. an interpolated expression), which the
|
|
444
|
+
* caller records as "declared but not usable" rather than guessing.
|
|
445
|
+
*/
|
|
446
|
+
function parseConnectionValue(raw) {
|
|
447
|
+
if (/^"(?:[^"\\]|\\.)*"$/.test(raw))
|
|
448
|
+
return { kind: 'literal', value: unquote(raw) };
|
|
449
|
+
const envCall = raw.match(/^env\(\s*("(?:[^"\\]|\\.)*")\s*\)$/);
|
|
450
|
+
if (envCall) {
|
|
451
|
+
const variable = unquote(envCall[1]);
|
|
452
|
+
return variable ? { kind: 'env', variable } : null;
|
|
453
|
+
}
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Parse a `datasource` block. Only `provider`, `url`, and `directUrl` are kept;
|
|
458
|
+
* every other key (`shadowDatabaseUrl`, `relationMode`, `extensions`, ...) is
|
|
459
|
+
* irrelevant to name mapping and skipped silently, as before.
|
|
460
|
+
*/
|
|
461
|
+
function parseDatasourceBody(block, src) {
|
|
462
|
+
const ds = { name: block.name, line: block.headerLine };
|
|
463
|
+
for (const { text } of bodyLines(block.body, block.bodyOffset, src)) {
|
|
464
|
+
const m = text.match(/^([A-Za-z_]\w*)\s*=\s*(.+)$/);
|
|
465
|
+
if (!m)
|
|
466
|
+
continue;
|
|
467
|
+
const key = m[1];
|
|
468
|
+
const raw = m[2].trim();
|
|
469
|
+
if (key === 'provider') {
|
|
470
|
+
const value = parseConnectionValue(raw);
|
|
471
|
+
if (value?.kind === 'literal')
|
|
472
|
+
ds.provider = value.value;
|
|
473
|
+
}
|
|
474
|
+
else if (key === 'url' || key === 'directUrl') {
|
|
475
|
+
const value = parseConnectionValue(raw);
|
|
476
|
+
if (value)
|
|
477
|
+
ds[key] = value;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return ds;
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Resolve the connection string a `schema.prisma` declares, reading `env(...)`
|
|
484
|
+
* indirections out of the supplied environment (pure: the environment is an
|
|
485
|
+
* argument, never `process.env` directly).
|
|
486
|
+
*
|
|
487
|
+
* Per datasource block, `url` is preferred and `directUrl` is the fallback:
|
|
488
|
+
* `url` is what Prisma itself uses for everything but migrations, and a pooled
|
|
489
|
+
* `url` introspects the same catalog as its direct twin.
|
|
490
|
+
*/
|
|
491
|
+
function resolvePrismaDatasourceUrl(ast, env) {
|
|
492
|
+
const missingVariables = [];
|
|
493
|
+
for (const ds of ast.datasources) {
|
|
494
|
+
for (const key of ['url', 'directUrl']) {
|
|
495
|
+
const value = ds[key];
|
|
496
|
+
if (!value)
|
|
497
|
+
continue;
|
|
498
|
+
if (value.kind === 'literal') {
|
|
499
|
+
if (value.value)
|
|
500
|
+
return { resolved: { url: value.value, datasource: ds.name, key }, missingVariables };
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
const fromEnv = env[value.variable];
|
|
504
|
+
if (fromEnv) {
|
|
505
|
+
return { resolved: { url: fromEnv, datasource: ds.name, key, variable: value.variable }, missingVariables };
|
|
506
|
+
}
|
|
507
|
+
if (!missingVariables.includes(value.variable))
|
|
508
|
+
missingVariables.push(value.variable);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return { missingVariables };
|
|
512
|
+
}
|
|
440
513
|
// ---------------------------------------------------------------------------
|
|
441
514
|
// Entry point
|
|
442
515
|
// ---------------------------------------------------------------------------
|
|
@@ -445,15 +518,17 @@ function parseEnumBody(block, src) {
|
|
|
445
518
|
*
|
|
446
519
|
* Understands: model / view / type / enum blocks; field lines with `@map`,
|
|
447
520
|
* `@id`, `@unique`, `@default`, `@updatedAt`, `@ignore`, `@relation`; and block
|
|
448
|
-
* attributes `@@map`, `@@id`, `@@unique`, `@@index`, `@@schema
|
|
449
|
-
*
|
|
521
|
+
* attributes `@@map`, `@@id`, `@@unique`, `@@index`, `@@schema`; plus the
|
|
522
|
+
* `provider` / `url` / `directUrl` keys of each `datasource` block (see
|
|
523
|
+
* {@link resolvePrismaDatasourceUrl}). Unknown attributes and blocks are
|
|
524
|
+
* skipped into {@link PrismaSchemaAst.warnings}.
|
|
450
525
|
*
|
|
451
526
|
* @throws {@link PrismaParseError} on an unterminated block/paren/string or a
|
|
452
527
|
* structurally broken `@@id` / `@@unique` / `@@map`.
|
|
453
528
|
*/
|
|
454
529
|
function parsePrismaSchema(source) {
|
|
455
530
|
const src = stripComments(source);
|
|
456
|
-
const ast = { models: [], enums: [], warnings: [] };
|
|
531
|
+
const ast = { models: [], enums: [], datasources: [], warnings: [] };
|
|
457
532
|
for (const block of scanBlocks(src)) {
|
|
458
533
|
switch (block.keyword) {
|
|
459
534
|
case 'model':
|
|
@@ -472,8 +547,11 @@ function parsePrismaSchema(source) {
|
|
|
472
547
|
ast.enums.push(parseEnumBody(block, src));
|
|
473
548
|
break;
|
|
474
549
|
case 'datasource':
|
|
550
|
+
// Not a table, but it declares the connection string the CLI can reuse.
|
|
551
|
+
ast.datasources.push(parseDatasourceBody(block, src));
|
|
552
|
+
break;
|
|
475
553
|
case 'generator':
|
|
476
|
-
// Configuration
|
|
554
|
+
// Configuration block - irrelevant to name mapping.
|
|
477
555
|
break;
|
|
478
556
|
default:
|
|
479
557
|
ast.warnings.push(`Skipped unsupported block "${block.keyword} ${block.name}".`);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* turbine-orm CLI: fixed-window rate limiter
|
|
3
|
+
*
|
|
4
|
+
* A pure leaf module (same role as `cli/destructive.ts`): no imports beyond a
|
|
5
|
+
* Node type, no I/O, no state of its own. Both local servers, Studio and
|
|
6
|
+
* Observe, throttle through it so they behave identically without either one
|
|
7
|
+
* importing the other. Studio in particular statically pulls in the embedded
|
|
8
|
+
* UI, the demo store, PII redaction and QueryInterface, none of which Observe
|
|
9
|
+
* has any use for.
|
|
10
|
+
*/
|
|
11
|
+
import type { IncomingMessage } from 'node:http';
|
|
12
|
+
export declare const RATE_LIMIT_WINDOW_MS = 60000;
|
|
13
|
+
export declare const RATE_LIMIT_MAX_REQUESTS = 100;
|
|
14
|
+
/**
|
|
15
|
+
* Bucket identity for one caller. These are loopback tools with a single shared
|
|
16
|
+
* token, so the remote address is the only thing that distinguishes one client
|
|
17
|
+
* from another; requests with no socket (in-process dispatch) share one key.
|
|
18
|
+
*/
|
|
19
|
+
export declare function callerKey(req: IncomingMessage): string;
|
|
20
|
+
export interface RateLimitResult {
|
|
21
|
+
allowed: boolean;
|
|
22
|
+
resetAt: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Fixed-window counter. `limiter` is the caller's own state map (each server
|
|
26
|
+
* owns one, so exhausting one server's budget never touches the other's) and
|
|
27
|
+
* `token` is the bucket key, not a credential.
|
|
28
|
+
*/
|
|
29
|
+
export declare function checkRateLimit(limiter: Map<string, {
|
|
30
|
+
count: number;
|
|
31
|
+
resetAt: number;
|
|
32
|
+
}>, token: string): RateLimitResult;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* turbine-orm CLI: fixed-window rate limiter
|
|
4
|
+
*
|
|
5
|
+
* A pure leaf module (same role as `cli/destructive.ts`): no imports beyond a
|
|
6
|
+
* Node type, no I/O, no state of its own. Both local servers, Studio and
|
|
7
|
+
* Observe, throttle through it so they behave identically without either one
|
|
8
|
+
* importing the other. Studio in particular statically pulls in the embedded
|
|
9
|
+
* UI, the demo store, PII redaction and QueryInterface, none of which Observe
|
|
10
|
+
* has any use for.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.RATE_LIMIT_MAX_REQUESTS = exports.RATE_LIMIT_WINDOW_MS = void 0;
|
|
14
|
+
exports.callerKey = callerKey;
|
|
15
|
+
exports.checkRateLimit = checkRateLimit;
|
|
16
|
+
exports.RATE_LIMIT_WINDOW_MS = 60_000; // 60 seconds
|
|
17
|
+
exports.RATE_LIMIT_MAX_REQUESTS = 100;
|
|
18
|
+
/**
|
|
19
|
+
* Bucket identity for one caller. These are loopback tools with a single shared
|
|
20
|
+
* token, so the remote address is the only thing that distinguishes one client
|
|
21
|
+
* from another; requests with no socket (in-process dispatch) share one key.
|
|
22
|
+
*/
|
|
23
|
+
function callerKey(req) {
|
|
24
|
+
return req.socket?.remoteAddress ?? 'unknown';
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Fixed-window counter. `limiter` is the caller's own state map (each server
|
|
28
|
+
* owns one, so exhausting one server's budget never touches the other's) and
|
|
29
|
+
* `token` is the bucket key, not a credential.
|
|
30
|
+
*/
|
|
31
|
+
function checkRateLimit(limiter, token) {
|
|
32
|
+
const now = Date.now();
|
|
33
|
+
const entry = limiter.get(token);
|
|
34
|
+
if (!entry || now >= entry.resetAt) {
|
|
35
|
+
// Start a new window
|
|
36
|
+
const resetAt = now + exports.RATE_LIMIT_WINDOW_MS;
|
|
37
|
+
limiter.set(token, { count: 1, resetAt });
|
|
38
|
+
return { allowed: true, resetAt };
|
|
39
|
+
}
|
|
40
|
+
entry.count++;
|
|
41
|
+
if (entry.count > exports.RATE_LIMIT_MAX_REQUESTS) {
|
|
42
|
+
return { allowed: false, resetAt: entry.resetAt };
|
|
43
|
+
}
|
|
44
|
+
return { allowed: true, resetAt: entry.resetAt };
|
|
45
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* turbine-orm CLI: Studio demo mode (`turbine studio --demo`)
|
|
3
|
+
*
|
|
4
|
+
* Boots Studio with NO database and NO DATABASE_URL: a baked-in, seeded sample
|
|
5
|
+
* dataset served from an in-memory engine. It is the "feel the product in 10
|
|
6
|
+
* seconds" experience: read mode, PII redaction, and the single-row write flow,
|
|
7
|
+
* all safely fake.
|
|
8
|
+
*
|
|
9
|
+
* The store is backed by Turbine's OWN SQLite engine over `node:sqlite`'s
|
|
10
|
+
* `:memory:` database (a built-in on Node >= 22.5, zero new dependency). Because
|
|
11
|
+
* `:memory:` is per-handle, the store dies with the process and every launch
|
|
12
|
+
* starts pristine: writes genuinely apply (edits stick, a refresh shows them)
|
|
13
|
+
* but nothing is ever persisted anywhere.
|
|
14
|
+
*
|
|
15
|
+
* This module lives under `src/cli/` (coverage-excluded, never imported by
|
|
16
|
+
* library code) and reuses `SqlitePool` + `sqliteDialect` from `../sqlite.js`;
|
|
17
|
+
* it never writes its own SQL evaluator.
|
|
18
|
+
*/
|
|
19
|
+
import type { PgCompatPool } from '../client.js';
|
|
20
|
+
import type { Dialect } from '../dialect.js';
|
|
21
|
+
import type { SchemaMetadata } from '../schema.js';
|
|
22
|
+
/**
|
|
23
|
+
* The seeded sample schema. Four tables with realistic relations; `email` and
|
|
24
|
+
* `phone` are tagged `pii` so Studio's redaction path is exercised out of the
|
|
25
|
+
* box.
|
|
26
|
+
*/
|
|
27
|
+
export declare const DEMO_SCHEMA: SchemaMetadata;
|
|
28
|
+
export interface DemoContext {
|
|
29
|
+
/** In-memory SQLite pool (pg-compatible) backing the demo store. */
|
|
30
|
+
pool: PgCompatPool;
|
|
31
|
+
/** The seeded sample schema metadata. */
|
|
32
|
+
metadata: SchemaMetadata;
|
|
33
|
+
/** The SQLite dialect the Studio handlers compile against in demo mode. */
|
|
34
|
+
dialect: Dialect;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Open a fresh, seeded in-memory demo store and return the pool + metadata +
|
|
38
|
+
* dialect Studio needs. Each call yields an independent, pristine database
|
|
39
|
+
* (`:memory:` is per-handle), so demo launches never share state.
|
|
40
|
+
*
|
|
41
|
+
* @throws Error on Node < 22.5 (no built-in `node:sqlite`).
|
|
42
|
+
*/
|
|
43
|
+
export declare function createDemoContext(): DemoContext;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const STUDIO_HTML: string;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* turbine-orm CLI: Studio
|
|
3
|
+
*
|
|
4
|
+
* A local web UI for browsing databases, exploring relations, and composing
|
|
5
|
+
* queries visually. ORM-native since v0.19: there is no raw-SQL input surface.
|
|
6
|
+
* The Query tab builds `findMany` args that are validated against introspected
|
|
7
|
+
* metadata and compiled by QueryInterface (`/api/builder`). Pure Node (built-in
|
|
8
|
+
* `http` module), no runtime dependencies beyond `pg`. CLI defaults to 127.0.0.1
|
|
9
|
+
* and refuses non-loopback hosts unless `npx turbine studio --allow-remote`.
|
|
10
|
+
*
|
|
11
|
+
* Read-only by default. `turbine studio --write` opts in to single-row writes
|
|
12
|
+
* (see the write model below); without the flag the write API routes do not
|
|
13
|
+
* exist (they 404) and the UI renders no write affordances.
|
|
14
|
+
*
|
|
15
|
+
* Security model:
|
|
16
|
+
* • Loopback by default; CLI refuses non-loopback without --allow-remote
|
|
17
|
+
* • Random auth token generated per process, required in Cookie header
|
|
18
|
+
* • No SQL input surface at all: every identifier in a builder or write
|
|
19
|
+
* request is validated against the introspected schema; all values are
|
|
20
|
+
* $N params compiled through the query builders
|
|
21
|
+
* • Read routes run in a READ ONLY transaction (belt-and-suspenders)
|
|
22
|
+
* • Write routes (only when `--write` is set) run in a plain BEGIN/COMMIT
|
|
23
|
+
* transaction, require a matching Origin header (CSRF), and address every
|
|
24
|
+
* row by its full primary key (single row, or a capped `rows` array of
|
|
25
|
+
* PK-addressed statements run atomically)
|
|
26
|
+
* • 30s statement timeout via parameterized set_config()
|
|
27
|
+
* • Per-session rate limiting, cross-origin refusal, security headers, and a
|
|
28
|
+
* per-request CSP nonce for the inline script (no `unsafe-inline`)
|
|
29
|
+
*
|
|
30
|
+
* PII: columns tagged `pii` in code-first metadata are redacted server-side in
|
|
31
|
+
* every row-bearing response (the literal `•• redacted ••`) unless the server
|
|
32
|
+
* was started with `--show-pii`.
|
|
33
|
+
*
|
|
34
|
+
* Write model (opt-in): update a single row; insert/delete one row or a capped
|
|
35
|
+
* list of PK-addressed rows in one all-or-nothing transaction. DDL and
|
|
36
|
+
* predicate-based (unconditional) writes are deliberately unsupported. Use the
|
|
37
|
+
* CLI or migrate for schema changes and true bulk operations.
|
|
38
|
+
*/
|
|
39
|
+
import { type IncomingMessage, type ServerResponse } from 'node:http';
|
|
40
|
+
import type { PgCompatPool } from '../client.js';
|
|
41
|
+
import type { Dialect } from '../dialect.js';
|
|
42
|
+
import type { SchemaMetadata, TableMetadata } from '../schema.js';
|
|
43
|
+
export interface StudioOptions {
|
|
44
|
+
url: string;
|
|
45
|
+
schema: string;
|
|
46
|
+
port: number;
|
|
47
|
+
host: string;
|
|
48
|
+
openBrowser: boolean;
|
|
49
|
+
include?: string[];
|
|
50
|
+
exclude?: string[];
|
|
51
|
+
/** Directory where studio-queries.json is persisted. Defaults to `.turbine/` in cwd. */
|
|
52
|
+
stateDir?: string;
|
|
53
|
+
/** Database adapter for dialect-specific behavior (e.g. statement timeout syntax). */
|
|
54
|
+
adapter?: import('../adapters/index.js').DatabaseAdapter;
|
|
55
|
+
/**
|
|
56
|
+
* Opt in to single-row write routes (`/api/row/update|insert|delete`) and the
|
|
57
|
+
* write UI. Default `false`: read-only, with the write routes absent (404).
|
|
58
|
+
*/
|
|
59
|
+
write?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Reveal PII-tagged column values instead of redacting them. Default `false`.
|
|
62
|
+
*/
|
|
63
|
+
showPii?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Boot with a seeded, in-memory sample database instead of connecting to a
|
|
66
|
+
* real one (no DATABASE_URL required). Backed by Turbine's own SQLite engine
|
|
67
|
+
* over `node:sqlite` `:memory:`; nothing is ever persisted. Enables the live
|
|
68
|
+
* three-mode switcher (`/api/demo/mode`). Default `false`.
|
|
69
|
+
*/
|
|
70
|
+
demo?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Directory holding generated Turbine metadata (`turbine generate`'s `out`).
|
|
73
|
+
* PII tags are code-first declarations that introspection never sets, so
|
|
74
|
+
* without this Studio's redaction has nothing to redact against a real
|
|
75
|
+
* database. Read as text; nothing from it is executed. See `pii-tags.ts`.
|
|
76
|
+
*/
|
|
77
|
+
metadataDir?: string;
|
|
78
|
+
}
|
|
79
|
+
export interface StudioHandle {
|
|
80
|
+
/** Shut down the server + pool cleanly. */
|
|
81
|
+
dispose: () => Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Where PII tags came from, so the CLI can say so at startup. `null` when no
|
|
84
|
+
* generated metadata was found: redaction is then inert and the user needs to
|
|
85
|
+
* know that rather than assume protection.
|
|
86
|
+
*/
|
|
87
|
+
piiTags: {
|
|
88
|
+
path: string;
|
|
89
|
+
applied: number;
|
|
90
|
+
} | null;
|
|
91
|
+
/** Random per-process session token the UI sends via cookie. */
|
|
92
|
+
authToken: string;
|
|
93
|
+
/** Full URL including `?token=...`, safe to print for the user. */
|
|
94
|
+
url: string;
|
|
95
|
+
}
|
|
96
|
+
export interface StudioContext {
|
|
97
|
+
/**
|
|
98
|
+
* The pg-compatible pool. A real `pg.Pool` in normal mode, a `SqlitePool`
|
|
99
|
+
* over an in-memory database in demo mode. Typed as the minimal
|
|
100
|
+
* `PgCompatPool` contract so both shapes work through one code path (pg.Pool
|
|
101
|
+
* satisfies it; the query builders take `pg.Pool` via a cast, matching the
|
|
102
|
+
* external-pool seam in client.ts).
|
|
103
|
+
*/
|
|
104
|
+
pool: PgCompatPool;
|
|
105
|
+
metadata: SchemaMetadata;
|
|
106
|
+
options: StudioOptions;
|
|
107
|
+
authToken: string;
|
|
108
|
+
stateDir: string;
|
|
109
|
+
/** Resolved statement timeout (adapter-aware): parameterized SQL + values. */
|
|
110
|
+
statementTimeout: {
|
|
111
|
+
sql: string;
|
|
112
|
+
params: unknown[];
|
|
113
|
+
};
|
|
114
|
+
/** Rate limiter state: tracks requests per authenticated session. */
|
|
115
|
+
rateLimiter: Map<string, {
|
|
116
|
+
count: number;
|
|
117
|
+
resetAt: number;
|
|
118
|
+
}>;
|
|
119
|
+
/**
|
|
120
|
+
* True when write mode is enabled (`--write`): the `/api/row/*` routes exist
|
|
121
|
+
* and the UI renders write affordances. Absent/false → read-only. In demo
|
|
122
|
+
* mode this is toggled live by the `/api/demo/mode` switcher.
|
|
123
|
+
*/
|
|
124
|
+
writable?: boolean;
|
|
125
|
+
/** True when PII redaction is disabled (`--show-pii`). Absent/false → redact. */
|
|
126
|
+
showPii?: boolean;
|
|
127
|
+
/**
|
|
128
|
+
* True when Studio is running against the seeded in-memory demo store
|
|
129
|
+
* (`--demo`). Branches the handful of Postgres-specific statements onto their
|
|
130
|
+
* SQLite equivalents and enables the `/api/demo/mode` live switcher.
|
|
131
|
+
*/
|
|
132
|
+
demo?: boolean;
|
|
133
|
+
/**
|
|
134
|
+
* SQL dialect the builder/write handlers compile against. Absent → Postgres
|
|
135
|
+
* (the default). Set to the SQLite dialect in demo mode.
|
|
136
|
+
*/
|
|
137
|
+
dialect?: Dialect;
|
|
138
|
+
/**
|
|
139
|
+
* Demo mode only: saved queries live here instead of on disk, honoring the
|
|
140
|
+
* "nothing you do here is saved anywhere" promise. Absent in normal mode
|
|
141
|
+
* (saved queries persist to `<stateDir>/studio-queries.json`).
|
|
142
|
+
*/
|
|
143
|
+
memorySavedQueries?: SavedQueriesFile;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Start the Studio server. Returns a handle with the session token, a pre-built
|
|
147
|
+
* URL (including the token) that the CLI can print, and a disposer.
|
|
148
|
+
*
|
|
149
|
+
* Typical usage from the CLI:
|
|
150
|
+
* const studio = await startStudio(options);
|
|
151
|
+
* console.log(studio.url);
|
|
152
|
+
* process.on('SIGINT', () => studio.dispose().then(() => process.exit(0)));
|
|
153
|
+
*/
|
|
154
|
+
export declare function startStudio(options: StudioOptions): Promise<StudioHandle>;
|
|
155
|
+
export declare function handleRequest(req: IncomingMessage, res: ServerResponse, ctx: StudioContext): Promise<void>;
|
|
156
|
+
export declare function apiDemoMode(req: IncomingMessage, res: ServerResponse, ctx: StudioContext): Promise<void>;
|
|
157
|
+
/** One clickable reference from a column on this table to a row in another. */
|
|
158
|
+
export interface ForeignKeyLink {
|
|
159
|
+
/** Column on THIS table whose cell becomes the link. */
|
|
160
|
+
column: string;
|
|
161
|
+
/** Relation the link was derived from. */
|
|
162
|
+
relation: string;
|
|
163
|
+
/** Table the reference points at. */
|
|
164
|
+
targetTable: string;
|
|
165
|
+
/** Column on the target table the value is matched against. */
|
|
166
|
+
targetColumn: string;
|
|
167
|
+
}
|
|
168
|
+
/** One reverse jump: rows in a child table that reference this table. */
|
|
169
|
+
export interface ReferencedByLink {
|
|
170
|
+
/** Column on THIS table whose value the child column matches (usually the PK). */
|
|
171
|
+
column: string;
|
|
172
|
+
/** Relation the link was derived from. */
|
|
173
|
+
relation: string;
|
|
174
|
+
/** Child table holding the referencing rows. */
|
|
175
|
+
targetTable: string;
|
|
176
|
+
/** Referencing column on the child table. */
|
|
177
|
+
targetColumn: string;
|
|
178
|
+
}
|
|
179
|
+
export declare function relationLinksForTable(table: TableMetadata, metadata: SchemaMetadata, showPii: boolean): {
|
|
180
|
+
foreignKeys: ForeignKeyLink[];
|
|
181
|
+
referencedBy: ReferencedByLink[];
|
|
182
|
+
};
|
|
183
|
+
export declare function apiTableRows(res: ServerResponse, ctx: StudioContext, rawTableName: string, params: URLSearchParams): Promise<void>;
|
|
184
|
+
export declare function resolveColumnName(table: TableMetadata, nameOrField: string): string | null;
|
|
185
|
+
export declare function isTextishType(pgType: string): boolean;
|
|
186
|
+
export declare function escapeLikePattern(s: string): string;
|
|
187
|
+
export declare function apiBuilder(req: IncomingMessage, res: ServerResponse, ctx: StudioContext): Promise<void>;
|
|
188
|
+
export declare function apiRowWrite(req: IncomingMessage, res: ServerResponse, ctx: StudioContext, op: 'update' | 'insert' | 'delete'): Promise<void>;
|
|
189
|
+
interface SavedQuery {
|
|
190
|
+
id: string;
|
|
191
|
+
table: string;
|
|
192
|
+
name: string;
|
|
193
|
+
/** Studio only saves visual-builder queries: there is no raw-SQL surface. */
|
|
194
|
+
kind: 'builder';
|
|
195
|
+
args?: unknown;
|
|
196
|
+
createdAt: string;
|
|
197
|
+
}
|
|
198
|
+
interface SavedQueriesFile {
|
|
199
|
+
version: 1;
|
|
200
|
+
queries: SavedQuery[];
|
|
201
|
+
}
|
|
202
|
+
export declare function apiListSavedQueries(res: ServerResponse, ctx: StudioContext, params: URLSearchParams): void;
|
|
203
|
+
export declare function apiCreateSavedQuery(req: IncomingMessage, res: ServerResponse, ctx: StudioContext): Promise<void>;
|
|
204
|
+
export declare function apiDeleteSavedQuery(res: ServerResponse, ctx: StudioContext, id: string): void;
|
|
205
|
+
/** The literal replacement value for a redacted PII cell. */
|
|
206
|
+
export declare const PII_REDACTED = "\u2022\u2022 redacted \u2022\u2022";
|
|
207
|
+
export {};
|