@impetik/xeer-mcp 0.2.17 → 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/vendor/spec/actions.d.ts +5 -5
- package/vendor/spec/actions.js +11 -6
- package/vendor/spec/admin.d.ts +7 -5
- package/vendor/spec/admin.js +7 -21
- package/vendor/spec/diagnostics.js +77 -26
- package/vendor/spec/import-policy.d.ts +38 -11
- package/vendor/spec/import-policy.js +30 -8
- package/vendor/spec/index.d.ts +1 -0
- package/vendor/spec/index.js +1 -0
- package/vendor/spec/local-identity.d.ts +83 -8
- package/vendor/spec/local-identity.js +135 -11
- package/vendor/spec/page-cursor.d.ts +26 -0
- package/vendor/spec/page-cursor.js +45 -0
- package/vendor/spec/review.js +2 -0
- package/vendor/spec/route.d.ts +13 -0
- package/vendor/spec/route.js +33 -0
- package/vendor/spec/schema-lifecycle.d.ts +10 -4
- package/vendor/spec/schema-lifecycle.js +32 -4
- package/vendor/spec/schema-plan.d.ts +10 -1
- package/vendor/spec/schema-plan.js +22 -3
- package/vendor/spec/schema.d.ts +9 -0
- package/vendor/spec/schema.js +20 -0
- package/vendor/spec/state-export.d.ts +22 -0
- package/vendor/spec/state-export.js +42 -2
- package/vendor/spec/type-check-profile.d.ts +67 -0
- package/vendor/spec/type-check-profile.js +78 -0
- package/vendor/spec/types.d.ts +75 -5
- package/vendor/spec/types.js +10 -0
|
@@ -8,6 +8,10 @@ export const SCHEMA_PROTOCOL = 'xeer.schema.v0';
|
|
|
8
8
|
export const FIELD_ATTRIBUTES = Object.freeze([
|
|
9
9
|
'collate', 'default', 'enum', 'generated', 'onDelete', 'onUpdate', 'table', 'unique',
|
|
10
10
|
]);
|
|
11
|
+
/** The default a table that says nothing about its ids gets, in the one place the plan reads it. */
|
|
12
|
+
export function tableIdSource(table) {
|
|
13
|
+
return table.idSource ?? 'runtime';
|
|
14
|
+
}
|
|
11
15
|
export class SchemaPlanningError extends Error {
|
|
12
16
|
code;
|
|
13
17
|
constructor(code, message) {
|
|
@@ -61,6 +65,13 @@ function tableSteps(tableName, from, to) {
|
|
|
61
65
|
steps.push({ kind: 'table.constraint', table: tableName, constraint,
|
|
62
66
|
from: from[constraint] ?? null, to: to[constraint] ?? null });
|
|
63
67
|
}
|
|
68
|
+
// Read through the default, so declaring `"runtime"` on a table that had said nothing is the same
|
|
69
|
+
// table rather than a change — the same reading the hash takes when it omits the defaulted value.
|
|
70
|
+
const beforeIdSource = tableIdSource(from);
|
|
71
|
+
const afterIdSource = tableIdSource(to);
|
|
72
|
+
if (beforeIdSource !== afterIdSource) {
|
|
73
|
+
steps.push({ kind: 'table.idSource', table: tableName, from: beforeIdSource, to: afterIdSource });
|
|
74
|
+
}
|
|
64
75
|
// Indexes are compared through their normalized form, so `["slug"]` and `{ columns: ["slug"] }`
|
|
65
76
|
// are one index written two ways rather than a change from one to the other.
|
|
66
77
|
const terms = (index) => normalizedIndex(index).columns.map(indexTermLabel);
|
|
@@ -92,12 +103,20 @@ function tableSteps(tableName, from, to) {
|
|
|
92
103
|
}
|
|
93
104
|
/**
|
|
94
105
|
* Whether a step leaves the rows already stored valid. Nothing in the extended vocabulary does,
|
|
95
|
-
* with
|
|
96
|
-
*
|
|
106
|
+
* with two exceptions, and both are exceptions for the same reason: they govern what a *later* write
|
|
107
|
+
* is allowed to say and make no claim about what is already stored.
|
|
108
|
+
*
|
|
109
|
+
* A DEFAULT decides what an insert that omits the column receives, and says nothing about the rows
|
|
110
|
+
* already there. `idSource` decides who supplies the next row's id: turning it on leaves every stored
|
|
111
|
+
* uuid a valid primary key, and turning it off leaves every stored application id one too. Neither
|
|
112
|
+
* direction touches a column, an index, or a byte on disk — `id` is `TEXT PRIMARY KEY` under both —
|
|
113
|
+
* so a reset here would rewrite a table to produce the table it started with.
|
|
97
114
|
*/
|
|
98
115
|
function isCompatible(step) {
|
|
99
116
|
if (step.kind === 'table.add' || step.kind === 'index.add')
|
|
100
117
|
return true;
|
|
118
|
+
if (step.kind === 'table.idSource')
|
|
119
|
+
return true;
|
|
101
120
|
if (step.kind === 'field.add')
|
|
102
121
|
return step.optional;
|
|
103
122
|
if (step.kind === 'field.optional')
|
|
@@ -149,7 +168,7 @@ function unknownSchemaSteps(fromSchema, toSchema) {
|
|
|
149
168
|
for (const tableName of tableNames) {
|
|
150
169
|
const beforeTable = fromSchema.tables[tableName];
|
|
151
170
|
const afterTable = toSchema.tables[tableName];
|
|
152
|
-
steps.push(...unknownKeySteps(`database.tables.${tableName}`, beforeTable, afterTable, ['fields', 'indexes', 'unique', 'checks']));
|
|
171
|
+
steps.push(...unknownKeySteps(`database.tables.${tableName}`, beforeTable, afterTable, ['fields', 'indexes', 'unique', 'checks', 'idSource']));
|
|
153
172
|
const beforeFields = beforeTable?.fields ?? {};
|
|
154
173
|
const afterFields = afterTable?.fields ?? {};
|
|
155
174
|
const fieldNames = [...new Set([...Object.keys(beforeFields), ...Object.keys(afterFields)])].sort();
|
package/vendor/spec/schema.d.ts
CHANGED
|
@@ -62,6 +62,10 @@ export declare const normalizedDatabaseSchema: z.ZodObject<{
|
|
|
62
62
|
}, z.core.$strict>]>>>;
|
|
63
63
|
unique: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodString>>>;
|
|
64
64
|
checks: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
65
|
+
idSource: z.ZodOptional<z.ZodEnum<{
|
|
66
|
+
runtime: "runtime";
|
|
67
|
+
application: "application";
|
|
68
|
+
}>>;
|
|
65
69
|
}, z.core.$strict>>;
|
|
66
70
|
}, z.core.$strict>;
|
|
67
71
|
export declare const applicationManifestSchema: z.ZodObject<{
|
|
@@ -140,6 +144,10 @@ export declare const applicationManifestSchema: z.ZodObject<{
|
|
|
140
144
|
}, z.core.$strict>]>>>;
|
|
141
145
|
unique: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodString>>>;
|
|
142
146
|
checks: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
147
|
+
idSource: z.ZodOptional<z.ZodEnum<{
|
|
148
|
+
runtime: "runtime";
|
|
149
|
+
application: "application";
|
|
150
|
+
}>>;
|
|
143
151
|
}, z.core.$strict>>;
|
|
144
152
|
}, z.core.$strict>>;
|
|
145
153
|
storage: z.ZodOptional<z.ZodObject<{
|
|
@@ -164,6 +172,7 @@ export declare const applicationManifestSchema: z.ZodObject<{
|
|
|
164
172
|
responseBytes: z.ZodOptional<z.ZodNumber>;
|
|
165
173
|
clientBundleBytes: z.ZodOptional<z.ZodNumber>;
|
|
166
174
|
liveConnections: z.ZodOptional<z.ZodNumber>;
|
|
175
|
+
streamedBytes: z.ZodOptional<z.ZodNumber>;
|
|
167
176
|
}, z.core.$strict>>;
|
|
168
177
|
}, z.core.$strict>;
|
|
169
178
|
/** Stable editor-facing schema location emitted into every scaffolded `xeer.app.json`. */
|
package/vendor/spec/schema.js
CHANGED
|
@@ -138,6 +138,9 @@ const tableSchema = z.strictObject({
|
|
|
138
138
|
indexes: z.record(identifier, indexSchema).optional(),
|
|
139
139
|
unique: z.array(z.array(identifier).min(1)).optional(),
|
|
140
140
|
checks: z.record(identifier, expression).optional(),
|
|
141
|
+
// Who supplies a row's id. Absent means `"runtime"`, and absent is how it stays written: the
|
|
142
|
+
// normalized schema omits the default, so declaring it explicitly is accepted but changes nothing.
|
|
143
|
+
idSource: z.enum(['runtime', 'application']).optional(),
|
|
141
144
|
}).superRefine((table, ctx) => {
|
|
142
145
|
for (const [indexName, definition] of Object.entries(table.indexes ?? {})) {
|
|
143
146
|
const seen = new Set();
|
|
@@ -295,6 +298,10 @@ export const applicationManifestSchema = z.strictObject({
|
|
|
295
298
|
// and the runtime answers `live_disabled` for it rather than a retryable budget refusal. The
|
|
296
299
|
// bound is `min(0)` and not `positive()` so the editor schema publishes `minimum: 0`.
|
|
297
300
|
liveConnections: z.number().int().min(0).max(1_000).optional(),
|
|
301
|
+
// Deliberately above `responseBytes`' 4 MiB ceiling: a streamed body that had to fit inside the
|
|
302
|
+
// buffered one would give an author no reason to stream. It is still declared and still bounded —
|
|
303
|
+
// an unbudgeted server→client channel is not something this platform ships (decision 0007).
|
|
304
|
+
streamedBytes: z.number().int().positive().max(64 * 1024 * 1024).optional(),
|
|
298
305
|
}).optional(),
|
|
299
306
|
}).superRefine((manifest, ctx) => {
|
|
300
307
|
// Caught here, at `xeer check` time, rather than as a failure when the schema is applied.
|
|
@@ -423,6 +430,19 @@ export function normalizeApplicationManifest(manifest) {
|
|
|
423
430
|
// application's Durable Object continuously, so a positive default charged every application
|
|
424
431
|
// for a feature most never used. Declare a positive value to turn it on.
|
|
425
432
|
liveConnections: manifest.budgets?.liveConnections ?? 0,
|
|
433
|
+
// Present only when declared, mirroring `clientBundleBytes` below. The normalized budgets go
|
|
434
|
+
// into the artifact payload verbatim, so materializing the platform default here would move
|
|
435
|
+
// the `artifactId` of every application that never asked for a streamed response — the exact
|
|
436
|
+
// identity invariant `storage` and `clientBundleBytes` exist to hold. The runtime applies
|
|
437
|
+
// {@link STREAMED_BYTES_DEFAULT} where it reads the budget instead.
|
|
438
|
+
//
|
|
439
|
+
// A positive default is safe here in a way `liveConnections` is not: this ceiling only ever
|
|
440
|
+
// applies to a stream `liveConnections` already admitted, so the default cannot open a channel
|
|
441
|
+
// on its own. It matches `responseBytes` so that turning an endpoint from buffered to streamed
|
|
442
|
+
// does not silently change how many bytes it may send.
|
|
443
|
+
...(manifest.budgets?.streamedBytes !== undefined
|
|
444
|
+
? { streamedBytes: manifest.budgets.streamedBytes }
|
|
445
|
+
: {}),
|
|
426
446
|
// Present only when declared, mirroring `storage`: the platform default is applied at build
|
|
427
447
|
// time, so an application that never declares the budget hashes exactly as before (#212).
|
|
428
448
|
...(manifest.budgets?.clientBundleBytes !== undefined
|
|
@@ -100,6 +100,28 @@ export declare class StateTransferError extends Error {
|
|
|
100
100
|
/** JSON shape shared by the runtime response, the control-plane envelope, and CLI diagnostics. */
|
|
101
101
|
toDiagnostic(): StateTransferDiagnostic;
|
|
102
102
|
}
|
|
103
|
+
/** The longest a row id may be, in UTF-16 code units. */
|
|
104
|
+
export declare const RECORD_ID_MAX_LENGTH = 256;
|
|
105
|
+
export type RecordIdRefusal = 'record_id_empty' | 'record_id_too_long' | 'record_id_invalid';
|
|
106
|
+
/**
|
|
107
|
+
* Why a row id is refused, or `null` when it is acceptable.
|
|
108
|
+
*
|
|
109
|
+
* Until `idSource: "application"` this rule had one caller and lived inside it, because the only ids
|
|
110
|
+
* an application could produce came from `crypto.randomUUID()` and the only ones a human could were
|
|
111
|
+
* the admin editor's. Both of those are now joined by ids an application chooses on `insert`, so the
|
|
112
|
+
* rule is stated once here and read by all three — the alternative being three spellings of "a row
|
|
113
|
+
* id" that agree until one of them is edited.
|
|
114
|
+
*
|
|
115
|
+
* Bounded and quotable, and no more. The bound is what stops an id becoming an unbounded key in
|
|
116
|
+
* every index and error message that carries one; control characters are refused because an id is
|
|
117
|
+
* quoted back in diagnostics, admin listings and URLs, and a newline or a NUL inside one turns a
|
|
118
|
+
* message into two. Nothing narrower would be honest: these ids come from identity providers and
|
|
119
|
+
* other outside systems, and refusing a charset that a provider legitimately issues would make the
|
|
120
|
+
* capability unusable for exactly the case it exists for.
|
|
121
|
+
*/
|
|
122
|
+
export declare function recordIdRefusal(id: unknown): RecordIdRefusal | null;
|
|
123
|
+
/** The human wording for each refusal, shared so the runtime and the editor say the same thing. */
|
|
124
|
+
export declare function recordIdMessage(refusal: RecordIdRefusal, id: unknown): string;
|
|
103
125
|
/**
|
|
104
126
|
* Whether a decoded value satisfies a declared field. Shared with the runtime so an imported record
|
|
105
127
|
* is held to exactly the same rule a handler-written record is.
|
|
@@ -110,6 +110,42 @@ function validEncodedValue(value, path) {
|
|
|
110
110
|
}
|
|
111
111
|
throw malformed(`Unknown encoded value tag at ${path}.`, { tag: typeof tag === 'string' ? tag : null });
|
|
112
112
|
}
|
|
113
|
+
/** The longest a row id may be, in UTF-16 code units. */
|
|
114
|
+
export const RECORD_ID_MAX_LENGTH = 256;
|
|
115
|
+
/**
|
|
116
|
+
* Why a row id is refused, or `null` when it is acceptable.
|
|
117
|
+
*
|
|
118
|
+
* Until `idSource: "application"` this rule had one caller and lived inside it, because the only ids
|
|
119
|
+
* an application could produce came from `crypto.randomUUID()` and the only ones a human could were
|
|
120
|
+
* the admin editor's. Both of those are now joined by ids an application chooses on `insert`, so the
|
|
121
|
+
* rule is stated once here and read by all three — the alternative being three spellings of "a row
|
|
122
|
+
* id" that agree until one of them is edited.
|
|
123
|
+
*
|
|
124
|
+
* Bounded and quotable, and no more. The bound is what stops an id becoming an unbounded key in
|
|
125
|
+
* every index and error message that carries one; control characters are refused because an id is
|
|
126
|
+
* quoted back in diagnostics, admin listings and URLs, and a newline or a NUL inside one turns a
|
|
127
|
+
* message into two. Nothing narrower would be honest: these ids come from identity providers and
|
|
128
|
+
* other outside systems, and refusing a charset that a provider legitimately issues would make the
|
|
129
|
+
* capability unusable for exactly the case it exists for.
|
|
130
|
+
*/
|
|
131
|
+
export function recordIdRefusal(id) {
|
|
132
|
+
if (typeof id !== 'string' || id.length === 0)
|
|
133
|
+
return 'record_id_empty';
|
|
134
|
+
if (id.length > RECORD_ID_MAX_LENGTH)
|
|
135
|
+
return 'record_id_too_long';
|
|
136
|
+
return /[\u0000-\u001f\u007f-\u009f]/u.test(id) ? 'record_id_invalid' : null;
|
|
137
|
+
}
|
|
138
|
+
/** The human wording for each refusal, shared so the runtime and the editor say the same thing. */
|
|
139
|
+
export function recordIdMessage(refusal, id) {
|
|
140
|
+
switch (refusal) {
|
|
141
|
+
case 'record_id_empty':
|
|
142
|
+
return 'A row id must be a non-empty string.';
|
|
143
|
+
case 'record_id_too_long':
|
|
144
|
+
return `A row id may not exceed ${RECORD_ID_MAX_LENGTH} characters; received ${id.length}.`;
|
|
145
|
+
default:
|
|
146
|
+
return `A row id may not contain control characters: ${JSON.stringify(id.slice(0, 80))}`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
113
149
|
/**
|
|
114
150
|
* Whether a decoded value satisfies a declared field. Shared with the runtime so an imported record
|
|
115
151
|
* is held to exactly the same rule a handler-written record is.
|
|
@@ -156,8 +192,12 @@ function parseRecord(table, index, value) {
|
|
|
156
192
|
const row = plainObject(value);
|
|
157
193
|
if (!row)
|
|
158
194
|
throw malformed(`Row ${index} of table ${table} is not an object.`);
|
|
159
|
-
|
|
160
|
-
|
|
195
|
+
// The same rule an application `insert` and the admin editor are held to. An imported row is a row
|
|
196
|
+
// like any other, and a document carrying an id no live write could have produced would restore an
|
|
197
|
+
// application into a state it had no way to reach on its own.
|
|
198
|
+
const idRefusal = recordIdRefusal(row.id);
|
|
199
|
+
if (idRefusal) {
|
|
200
|
+
throw malformed(`Row ${index} of table ${table} has no usable id. ${recordIdMessage(idRefusal, row.id)}`);
|
|
161
201
|
}
|
|
162
202
|
if (!isoTimestamp(row.createdAt) || !isoTimestamp(row.updatedAt)) {
|
|
163
203
|
throw malformed(`Row ${row.id} of table ${table} has non-ISO timestamps.`);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The platform TypeScript profile, in the spelling a `tsconfig.json` uses.
|
|
3
|
+
*
|
|
4
|
+
* `xeer check` type-checks an application with its own compiler options rather than the project's
|
|
5
|
+
* `tsconfig.json`, because the answer must not depend on a file the author can edit. The editor,
|
|
6
|
+
* however, reads only the `tsconfig.json` — so anything the platform enforces and the scaffold omits
|
|
7
|
+
* shows up as a green editor and a red CLI, which is what `exactOptionalPropertyTypes` did (#248).
|
|
8
|
+
*
|
|
9
|
+
* The repair is not to copy the flags into the scaffold: a copy is a thing that drifts. This module
|
|
10
|
+
* is the one declaration. The compiler converts it into `ts.CompilerOptions` for the check, and the
|
|
11
|
+
* scaffold writes it into `tsconfig.json` verbatim, so the two cannot disagree without this file
|
|
12
|
+
* changing. It lives in the spec package rather than the compiler so that writing a scaffold does
|
|
13
|
+
* not require loading TypeScript.
|
|
14
|
+
*
|
|
15
|
+
* Values are the JSON spellings TypeScript accepts in a `tsconfig.json` (`"esnext"`, `"bundler"`,
|
|
16
|
+
* `"react-jsx"`, `lib` without the `lib.`/`.d.ts` affixes), which is what
|
|
17
|
+
* `ts.convertCompilerOptionsFromJson` reads.
|
|
18
|
+
*/
|
|
19
|
+
export declare const PLATFORM_TYPE_CHECK_COMPILER_OPTIONS: Readonly<{
|
|
20
|
+
readonly allowJs: true;
|
|
21
|
+
readonly allowArbitraryExtensions: true;
|
|
22
|
+
readonly checkJs: true;
|
|
23
|
+
readonly exactOptionalPropertyTypes: true;
|
|
24
|
+
readonly jsx: "react-jsx";
|
|
25
|
+
readonly jsxImportSource: "@impetik/xeer";
|
|
26
|
+
readonly lib: readonly string[];
|
|
27
|
+
readonly module: "esnext";
|
|
28
|
+
readonly moduleResolution: "bundler";
|
|
29
|
+
readonly noEmit: true;
|
|
30
|
+
readonly noUncheckedIndexedAccess: true;
|
|
31
|
+
readonly skipLibCheck: true;
|
|
32
|
+
readonly strict: true;
|
|
33
|
+
readonly target: "es2022";
|
|
34
|
+
readonly types: readonly never[];
|
|
35
|
+
readonly verbatimModuleSyntax: true;
|
|
36
|
+
}>;
|
|
37
|
+
/**
|
|
38
|
+
* The React-family editor mappings every scaffolded and template `tsconfig.json` carries. Each
|
|
39
|
+
* target is a shim inside the installed platform package that re-exports the platform's
|
|
40
|
+
* `preact/compat` surface, so the mapping resolves under npm hoisting and pnpm strictness alike.
|
|
41
|
+
*
|
|
42
|
+
* Not part of {@link PLATFORM_TYPE_CHECK_COMPILER_OPTIONS}: the platform check answers the same
|
|
43
|
+
* question through its module-resolution host instead, so this is the editor's half of one rule
|
|
44
|
+
* rather than a second rule.
|
|
45
|
+
*/
|
|
46
|
+
export declare const REACT_COMPAT_TSCONFIG_PATHS: Readonly<Record<string, readonly string[]>>;
|
|
47
|
+
/**
|
|
48
|
+
* The files an application's `tsconfig.json` type-checks: sources, TypeScript tests, and the
|
|
49
|
+
* generated contract.
|
|
50
|
+
*
|
|
51
|
+
* `tests/**\/*.ts` rather than `tests`, because the profile enables `checkJs` and the two sides build
|
|
52
|
+
* their file sets differently. The platform checks the entrypoint graph plus `tests/**\/*.test.ts`,
|
|
53
|
+
* so a `.js` file it reaches is a file it judges; a tsconfig's set is a directory glob, and `tests`
|
|
54
|
+
* would additionally drag in a `.mjs` harness written for Node — a file neither side is judging the
|
|
55
|
+
* same way. Restricting the glob to TypeScript keeps the two sets comparable without weakening any
|
|
56
|
+
* rule that decides how a checked file is judged.
|
|
57
|
+
*/
|
|
58
|
+
export declare const APPLICATION_TSCONFIG_INCLUDE: readonly string[];
|
|
59
|
+
/**
|
|
60
|
+
* The `tsconfig.json` document an application carries, as a serialized file body.
|
|
61
|
+
*
|
|
62
|
+
* One function so the scaffold, the checked-in templates, and any conformance test all name the same
|
|
63
|
+
* bytes. Serialized here rather than returned as an object because "the file the editor reads" is
|
|
64
|
+
* what has to match, and two callers stringifying the same object with different options would
|
|
65
|
+
* produce two different files.
|
|
66
|
+
*/
|
|
67
|
+
export declare function applicationTsconfigDocument(): string;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The platform TypeScript profile, in the spelling a `tsconfig.json` uses.
|
|
3
|
+
*
|
|
4
|
+
* `xeer check` type-checks an application with its own compiler options rather than the project's
|
|
5
|
+
* `tsconfig.json`, because the answer must not depend on a file the author can edit. The editor,
|
|
6
|
+
* however, reads only the `tsconfig.json` — so anything the platform enforces and the scaffold omits
|
|
7
|
+
* shows up as a green editor and a red CLI, which is what `exactOptionalPropertyTypes` did (#248).
|
|
8
|
+
*
|
|
9
|
+
* The repair is not to copy the flags into the scaffold: a copy is a thing that drifts. This module
|
|
10
|
+
* is the one declaration. The compiler converts it into `ts.CompilerOptions` for the check, and the
|
|
11
|
+
* scaffold writes it into `tsconfig.json` verbatim, so the two cannot disagree without this file
|
|
12
|
+
* changing. It lives in the spec package rather than the compiler so that writing a scaffold does
|
|
13
|
+
* not require loading TypeScript.
|
|
14
|
+
*
|
|
15
|
+
* Values are the JSON spellings TypeScript accepts in a `tsconfig.json` (`"esnext"`, `"bundler"`,
|
|
16
|
+
* `"react-jsx"`, `lib` without the `lib.`/`.d.ts` affixes), which is what
|
|
17
|
+
* `ts.convertCompilerOptionsFromJson` reads.
|
|
18
|
+
*/
|
|
19
|
+
export const PLATFORM_TYPE_CHECK_COMPILER_OPTIONS = Object.freeze({
|
|
20
|
+
allowJs: true,
|
|
21
|
+
allowArbitraryExtensions: true,
|
|
22
|
+
checkJs: true,
|
|
23
|
+
exactOptionalPropertyTypes: true,
|
|
24
|
+
jsx: 'react-jsx',
|
|
25
|
+
jsxImportSource: '@impetik/xeer',
|
|
26
|
+
lib: Object.freeze(['es2022', 'dom', 'dom.iterable']),
|
|
27
|
+
module: 'esnext',
|
|
28
|
+
moduleResolution: 'bundler',
|
|
29
|
+
noEmit: true,
|
|
30
|
+
noUncheckedIndexedAccess: true,
|
|
31
|
+
skipLibCheck: true,
|
|
32
|
+
strict: true,
|
|
33
|
+
target: 'es2022',
|
|
34
|
+
types: Object.freeze([]),
|
|
35
|
+
verbatimModuleSyntax: true,
|
|
36
|
+
});
|
|
37
|
+
/**
|
|
38
|
+
* The React-family editor mappings every scaffolded and template `tsconfig.json` carries. Each
|
|
39
|
+
* target is a shim inside the installed platform package that re-exports the platform's
|
|
40
|
+
* `preact/compat` surface, so the mapping resolves under npm hoisting and pnpm strictness alike.
|
|
41
|
+
*
|
|
42
|
+
* Not part of {@link PLATFORM_TYPE_CHECK_COMPILER_OPTIONS}: the platform check answers the same
|
|
43
|
+
* question through its module-resolution host instead, so this is the editor's half of one rule
|
|
44
|
+
* rather than a second rule.
|
|
45
|
+
*/
|
|
46
|
+
export const REACT_COMPAT_TSCONFIG_PATHS = Object.freeze({
|
|
47
|
+
'react': ['./node_modules/@impetik/xeer/dist/compat/react'],
|
|
48
|
+
'react-dom': ['./node_modules/@impetik/xeer/dist/compat/react-dom'],
|
|
49
|
+
'react-dom/client': ['./node_modules/@impetik/xeer/dist/compat/react-dom-client'],
|
|
50
|
+
'react/jsx-runtime': ['./node_modules/@impetik/xeer/dist/compat/react-jsx-runtime'],
|
|
51
|
+
'react/jsx-dev-runtime': ['./node_modules/@impetik/xeer/dist/compat/react-jsx-dev-runtime'],
|
|
52
|
+
});
|
|
53
|
+
/**
|
|
54
|
+
* The files an application's `tsconfig.json` type-checks: sources, TypeScript tests, and the
|
|
55
|
+
* generated contract.
|
|
56
|
+
*
|
|
57
|
+
* `tests/**\/*.ts` rather than `tests`, because the profile enables `checkJs` and the two sides build
|
|
58
|
+
* their file sets differently. The platform checks the entrypoint graph plus `tests/**\/*.test.ts`,
|
|
59
|
+
* so a `.js` file it reaches is a file it judges; a tsconfig's set is a directory glob, and `tests`
|
|
60
|
+
* would additionally drag in a `.mjs` harness written for Node — a file neither side is judging the
|
|
61
|
+
* same way. Restricting the glob to TypeScript keeps the two sets comparable without weakening any
|
|
62
|
+
* rule that decides how a checked file is judged.
|
|
63
|
+
*/
|
|
64
|
+
export const APPLICATION_TSCONFIG_INCLUDE = Object.freeze(['src', 'tests/**/*.ts', '.xeer/generated/**/*.d.ts']);
|
|
65
|
+
/**
|
|
66
|
+
* The `tsconfig.json` document an application carries, as a serialized file body.
|
|
67
|
+
*
|
|
68
|
+
* One function so the scaffold, the checked-in templates, and any conformance test all name the same
|
|
69
|
+
* bytes. Serialized here rather than returned as an object because "the file the editor reads" is
|
|
70
|
+
* what has to match, and two callers stringifying the same object with different options would
|
|
71
|
+
* produce two different files.
|
|
72
|
+
*/
|
|
73
|
+
export function applicationTsconfigDocument() {
|
|
74
|
+
return `${JSON.stringify({
|
|
75
|
+
compilerOptions: { ...PLATFORM_TYPE_CHECK_COMPILER_OPTIONS, paths: REACT_COMPAT_TSCONFIG_PATHS },
|
|
76
|
+
include: APPLICATION_TSCONFIG_INCLUDE,
|
|
77
|
+
}, null, 2)}\n`;
|
|
78
|
+
}
|
package/vendor/spec/types.d.ts
CHANGED
|
@@ -16,6 +16,20 @@ export interface Diagnostic {
|
|
|
16
16
|
file?: string;
|
|
17
17
|
span?: SourceSpan;
|
|
18
18
|
hint?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Set on a diagnostic that invalidated the generated contract: the compiler could not describe the
|
|
21
|
+
* application's operations, so every type derived from them is wrong downstream. Absent means
|
|
22
|
+
* "nothing is known", not "not primary" — a consumer that ignores the field reads the envelope
|
|
23
|
+
* exactly as it did before the field existed.
|
|
24
|
+
*/
|
|
25
|
+
primary?: true;
|
|
26
|
+
/**
|
|
27
|
+
* The `code` of the contract-invalidating diagnostic this one is a consequence of. Present only
|
|
28
|
+
* when the causal link is structural — the file consumes the contract the primary diagnostic
|
|
29
|
+
* broke — and never on a diagnostic in a file that carries a primary. Repair the primaries and
|
|
30
|
+
* re-run: a `causedBy` diagnostic that survives is a real error carrying its own location.
|
|
31
|
+
*/
|
|
32
|
+
causedBy?: string;
|
|
19
33
|
}
|
|
20
34
|
/**
|
|
21
35
|
* `ref` is a scalar in the only sense that matters here: it is one TEXT column holding one row id.
|
|
@@ -92,6 +106,22 @@ export interface TableDefinition {
|
|
|
92
106
|
unique?: string[][];
|
|
93
107
|
/** Table-level CHECK expressions by name. The name is what SQLite reports when one fails. */
|
|
94
108
|
checks?: Record<string, string>;
|
|
109
|
+
/**
|
|
110
|
+
* Who chooses a row's `id`. Defaults to `"runtime"`, which mints an unguessable uuid and refuses
|
|
111
|
+
* an `id` on insert.
|
|
112
|
+
*
|
|
113
|
+
* `"application"` makes `insert({ id, … })` required for this table and this table only, so a row
|
|
114
|
+
* can *be* the thing an outside system already names — the case this exists for is a `workspaces`
|
|
115
|
+
* row whose id is the identity provider's workspace id, which makes `ref` and
|
|
116
|
+
* `workspaceTable({ workspaceField: 'id' })` work against it without a second column to join
|
|
117
|
+
* through (decision 0006).
|
|
118
|
+
*
|
|
119
|
+
* Opt-in per table so the default stays the safe one: an application that does not ask for this
|
|
120
|
+
* cannot accidentally accept a client-chosen id. Nothing about the column changes — `id` is
|
|
121
|
+
* `TEXT PRIMARY KEY` either way — so a collision is the primary key's own constraint rather than
|
|
122
|
+
* new machinery, and turning this on or off is physically compatible with the rows already stored.
|
|
123
|
+
*/
|
|
124
|
+
idSource?: 'runtime' | 'application';
|
|
95
125
|
}
|
|
96
126
|
/**
|
|
97
127
|
* The capabilities an application may declare. A capability name in `capabilities[]` and its config
|
|
@@ -101,6 +131,16 @@ export interface TableDefinition {
|
|
|
101
131
|
*/
|
|
102
132
|
export declare const CAPABILITIES: readonly ["database", "storage"];
|
|
103
133
|
export type Capability = (typeof CAPABILITIES)[number];
|
|
134
|
+
/**
|
|
135
|
+
* Bytes one streamed endpoint response may pump when `budgets.streamedBytes` is not declared.
|
|
136
|
+
*
|
|
137
|
+
* Applied where the budget is *read* rather than where the manifest is normalized, because
|
|
138
|
+
* normalized budgets go into the artifact payload verbatim: materializing this default would move
|
|
139
|
+
* the `artifactId` of every application that never asked for a streamed response. It matches the
|
|
140
|
+
* `responseBytes` default so that turning an endpoint from buffered to streamed does not silently
|
|
141
|
+
* change how many bytes it may send.
|
|
142
|
+
*/
|
|
143
|
+
export declare const STREAMED_BYTES_DEFAULT = 1048576;
|
|
104
144
|
/**
|
|
105
145
|
* The `storage` capability's declared limits. Every field is optional and normalized to a default,
|
|
106
146
|
* and every one is a *logical* limit — a byte count the runtime enforces at call time. Nothing here
|
|
@@ -164,6 +204,15 @@ export interface ApplicationManifestV0 {
|
|
|
164
204
|
* runtime answers a terminal `live_disabled` without waking the state object.
|
|
165
205
|
*/
|
|
166
206
|
liveConnections?: number;
|
|
207
|
+
/**
|
|
208
|
+
* Bytes one streamed endpoint response may pump through the platform.
|
|
209
|
+
*
|
|
210
|
+
* A streamed body is bounded, not exempt: `responseBytes` bounds a body the platform buffered and
|
|
211
|
+
* can still refuse as a whole, and this bounds one the platform has already begun sending and can
|
|
212
|
+
* only terminate. They are different failures, so they are different ceilings. Exceeding this one
|
|
213
|
+
* aborts the stream mid-body, which the client observes as a failed read rather than a refusal.
|
|
214
|
+
*/
|
|
215
|
+
streamedBytes?: number;
|
|
167
216
|
/**
|
|
168
217
|
* Error-tier byte budget for the minified pre-gzip client JavaScript bundle. Defaults to the
|
|
169
218
|
* platform's 1 MiB when omitted; bounded above by the 10 MiB module ceiling. The advisory tier
|
|
@@ -206,8 +255,23 @@ export interface NormalizedApplicationManifestV0 {
|
|
|
206
255
|
mutationWrites: number;
|
|
207
256
|
requestBytes: number;
|
|
208
257
|
responseBytes: number;
|
|
209
|
-
/**
|
|
258
|
+
/**
|
|
259
|
+
* `0` means server push is off for this application; see {@link ApplicationManifestV0}.
|
|
260
|
+
*
|
|
261
|
+
* It is the ceiling on **every** server→client byte channel the state object holds open, not on
|
|
262
|
+
* live invalidation alone: an SSE subscription and a streamed endpoint response occupy the same
|
|
263
|
+
* pool. Two separately-bounded pools would add up to an unbounded total, which is the one answer
|
|
264
|
+
* decision 0007 refused.
|
|
265
|
+
*/
|
|
210
266
|
liveConnections: number;
|
|
267
|
+
/**
|
|
268
|
+
* Bytes one streamed endpoint response may pump; see {@link ApplicationManifestV0}.
|
|
269
|
+
*
|
|
270
|
+
* Present only when the manifest declares it, mirroring `clientBundleBytes` below: an
|
|
271
|
+
* application on the platform default hashes exactly as it did before the budget existed.
|
|
272
|
+
* Read it as `streamedBytes ?? STREAMED_BYTES_DEFAULT`.
|
|
273
|
+
*/
|
|
274
|
+
streamedBytes?: number;
|
|
211
275
|
/**
|
|
212
276
|
* Present only when the manifest declares it, mirroring `storage`: an application on the
|
|
213
277
|
* platform default hashes exactly as it did before the budget was declarable.
|
|
@@ -221,9 +285,14 @@ export interface SourceReceipt {
|
|
|
221
285
|
hash: `sha256:${string}`;
|
|
222
286
|
}
|
|
223
287
|
/**
|
|
224
|
-
* Provenance for one npm package
|
|
288
|
+
* Provenance for one npm package a zone bundle consumed (#212, #244). `files` receipts the exact
|
|
225
289
|
* bytes the bundler read — strictly stronger than lockfile integrity, which pins tarballs rather
|
|
226
290
|
* than the post-install state a patch or postinstall script left on disk.
|
|
291
|
+
*
|
|
292
|
+
* Deliberately not keyed by zone. A package both bundles consume is one dependency of one
|
|
293
|
+
* application at one version, and receipting it twice would say the artifact used two. The receipt
|
|
294
|
+
* is the union of the files either bundle read, so the same package contributing different modules
|
|
295
|
+
* to each zone is still one entry covering every byte that entered the artifact.
|
|
227
296
|
*/
|
|
228
297
|
export interface BundledDependencyReceiptV0 {
|
|
229
298
|
name: string;
|
|
@@ -278,9 +347,10 @@ export interface ApplicationArtifactV0 {
|
|
|
278
347
|
*/
|
|
279
348
|
storage?: NormalizedStorageConfigV0;
|
|
280
349
|
/**
|
|
281
|
-
* The normalized client runtime, recorded when the application declares `client` or the
|
|
282
|
-
* consumed open dependencies. Present-only-then for the same reason as `storage`: an
|
|
283
|
-
* application keeps its `artifactId` (#212).
|
|
350
|
+
* The normalized client runtime, recorded when the application declares `client` or the *client*
|
|
351
|
+
* bundle consumed open dependencies. Present-only-then for the same reason as `storage`: an
|
|
352
|
+
* untouched application keeps its `artifactId` (#212). A server-only dependency does not record
|
|
353
|
+
* it — nothing about the renderer was chosen by importing one (#244).
|
|
284
354
|
*/
|
|
285
355
|
clientRuntime?: NormalizedApplicationManifestV0['client']['runtime'];
|
|
286
356
|
budgets: NormalizedApplicationManifestV0['budgets'];
|
package/vendor/spec/types.js
CHANGED
|
@@ -9,3 +9,13 @@ export const INSPECT_PROTOCOL = 'xeer.inspect.v0';
|
|
|
9
9
|
* Cloudflare-primitive-backed capability to follow it (#29, design in #51).
|
|
10
10
|
*/
|
|
11
11
|
export const CAPABILITIES = Object.freeze(['database', 'storage']);
|
|
12
|
+
/**
|
|
13
|
+
* Bytes one streamed endpoint response may pump when `budgets.streamedBytes` is not declared.
|
|
14
|
+
*
|
|
15
|
+
* Applied where the budget is *read* rather than where the manifest is normalized, because
|
|
16
|
+
* normalized budgets go into the artifact payload verbatim: materializing this default would move
|
|
17
|
+
* the `artifactId` of every application that never asked for a streamed response. It matches the
|
|
18
|
+
* `responseBytes` default so that turning an endpoint from buffered to streamed does not silently
|
|
19
|
+
* change how many bytes it may send.
|
|
20
|
+
*/
|
|
21
|
+
export const STREAMED_BYTES_DEFAULT = 1_048_576;
|