terrascale 0.3.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.
Files changed (71) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +143 -0
  3. package/package.json +159 -0
  4. package/sdk-current-contract.json +27 -0
  5. package/sdk-route-manifest.json +67 -0
  6. package/src/admin.js +9 -0
  7. package/src/better-auth.js +14 -0
  8. package/src/config.js +121 -0
  9. package/src/database-codec.js +845 -0
  10. package/src/database-types.js +237 -0
  11. package/src/database-view.js +422 -0
  12. package/src/database.js +420 -0
  13. package/src/discovery.js +374 -0
  14. package/src/http.js +887 -0
  15. package/src/index.js +79 -0
  16. package/src/local/authentication.js +47 -0
  17. package/src/local/better-auth.js +517 -0
  18. package/src/local/cli.js +51 -0
  19. package/src/local/context.js +23 -0
  20. package/src/local/environment.js +109 -0
  21. package/src/local/index.js +204 -0
  22. package/src/local/router.js +999 -0
  23. package/src/local/server.js +664 -0
  24. package/src/local/store.js +530 -0
  25. package/src/local/test-environment.js +74 -0
  26. package/src/management-contracts.js +72 -0
  27. package/src/management.js +12 -0
  28. package/src/native-origin.js +75 -0
  29. package/src/postgres.js +494 -0
  30. package/src/react/core.js +743 -0
  31. package/src/react/index.js +99 -0
  32. package/src/result.js +251 -0
  33. package/src/schema.js +366 -0
  34. package/src/sql.js +996 -0
  35. package/src/svelte/index.js +129 -0
  36. package/src/tanstack/index.js +511 -0
  37. package/src/ts-auth-discovery.js +190 -0
  38. package/src/ts-auth.js +3497 -0
  39. package/types/admin.d.ts +6 -0
  40. package/types/better-auth.d.ts +8 -0
  41. package/types/config.d.ts +58 -0
  42. package/types/database-codec.d.ts +111 -0
  43. package/types/database-types.d.ts +213 -0
  44. package/types/database-view.d.ts +183 -0
  45. package/types/database.d.ts +98 -0
  46. package/types/discovery.d.ts +114 -0
  47. package/types/http.d.ts +46 -0
  48. package/types/index.d.ts +52 -0
  49. package/types/local/authentication.d.ts +11 -0
  50. package/types/local/better-auth.d.ts +33 -0
  51. package/types/local/cli.d.ts +2 -0
  52. package/types/local/context.d.ts +14 -0
  53. package/types/local/environment.d.ts +23 -0
  54. package/types/local/index.d.ts +94 -0
  55. package/types/local/router.d.ts +66 -0
  56. package/types/local/server.d.ts +54 -0
  57. package/types/local/store.d.ts +106 -0
  58. package/types/local/test-environment.d.ts +25 -0
  59. package/types/management-contracts.d.ts +44 -0
  60. package/types/management.d.ts +6 -0
  61. package/types/native-origin.d.ts +23 -0
  62. package/types/postgres.d.ts +123 -0
  63. package/types/react/core.d.ts +366 -0
  64. package/types/react/index.d.ts +54 -0
  65. package/types/result.d.ts +161 -0
  66. package/types/schema.d.ts +145 -0
  67. package/types/sql.d.ts +288 -0
  68. package/types/svelte/index.d.ts +81 -0
  69. package/types/tanstack/index.d.ts +165 -0
  70. package/types/ts-auth-discovery.d.ts +11 -0
  71. package/types/ts-auth.d.ts +1826 -0
@@ -0,0 +1,530 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { DatabaseSync } from "node:sqlite";
3
+ import { dirname, isAbsolute } from "node:path";
4
+
5
+ const SCHEMA_VERSION = 1;
6
+
7
+ /**
8
+ * A small synchronous JSON record store for Node local adapters.
9
+ *
10
+ * This module intentionally imports node:sqlite and must not be used from a
11
+ * browser bundle. The database handle is kept private so consumers cannot
12
+ * bypass the store's serialization and transaction invariants.
13
+ */
14
+ export class TerraScaleSqliteStore {
15
+ /**
16
+ * @readonly
17
+ * @type {string}
18
+ */
19
+ databasePath;
20
+
21
+ /** @type {DatabaseSync} */
22
+ #database;
23
+ #closed = false;
24
+ /** @type {Promise<void>} */
25
+ #queue = Promise.resolve();
26
+ #savepointSequence = 0;
27
+ #transactionDepth = 0;
28
+
29
+ /**
30
+ * @param {string} databasePath
31
+ */
32
+ constructor(databasePath) {
33
+ if (typeof databasePath !== "string") {
34
+ throw new TypeError("databasePath must be a string.");
35
+ }
36
+ if (databasePath !== ":memory:" && !isAbsolute(databasePath)) {
37
+ throw new TypeError(
38
+ "databasePath must be ':memory:' or an absolute path.",
39
+ );
40
+ }
41
+
42
+ if (databasePath !== ":memory:") {
43
+ mkdirSync(dirname(databasePath), { recursive: true });
44
+ }
45
+
46
+ this.databasePath = databasePath;
47
+ this.#database = new DatabaseSync(databasePath);
48
+
49
+ try {
50
+ // These are deliberately explicit PRAGMAs rather than relying only on
51
+ // constructor options, which vary between supported Node 22 releases.
52
+ this.#database.exec(
53
+ "PRAGMA foreign_keys = ON; PRAGMA busy_timeout = 5000;",
54
+ );
55
+ if (databasePath !== ":memory:") {
56
+ this.#database.exec("PRAGMA journal_mode = WAL;");
57
+ }
58
+ this.#migrate();
59
+ } catch (error) {
60
+ this.#database.close();
61
+ this.#closed = true;
62
+ throw error;
63
+ }
64
+ }
65
+
66
+ /** @returns {void} */
67
+ close() {
68
+ if (this.#closed) {
69
+ return;
70
+ }
71
+ if (this.#transactionDepth !== 0) {
72
+ throw new Error(
73
+ "Cannot close a SQLite store while a transaction is active.",
74
+ );
75
+ }
76
+ this.#database.close();
77
+ this.#closed = true;
78
+ }
79
+
80
+ /** @returns {void} */
81
+ reset() {
82
+ this.#assertOpen();
83
+ this.transaction(() => {
84
+ this.#database.exec("DELETE FROM records; DELETE FROM metadata;");
85
+ });
86
+ }
87
+
88
+ /**
89
+ * @template T
90
+ * @param {string} namespace
91
+ * @param {string} scope
92
+ * @param {string} id
93
+ * @returns {T | undefined}
94
+ */
95
+ get(namespace, scope, id) {
96
+ this.#assertOpen();
97
+ assertKey(namespace, "namespace");
98
+ assertKey(scope, "scope");
99
+ assertKey(id, "id");
100
+
101
+ const row = this.#database
102
+ .prepare(
103
+ "SELECT value FROM records WHERE namespace = ? AND scope = ? AND id = ?",
104
+ )
105
+ .get(namespace, scope, id);
106
+ if (row === undefined) {
107
+ return undefined;
108
+ }
109
+ return /** @type {T} */ (parseJson(row.value, "record"));
110
+ }
111
+
112
+ /**
113
+ * @template T
114
+ * @param {string} namespace
115
+ * @param {string} scope
116
+ * @returns {T[]}
117
+ */
118
+ list(namespace, scope) {
119
+ this.#assertOpen();
120
+ assertKey(namespace, "namespace");
121
+ assertKey(scope, "scope");
122
+
123
+ const rows = this.#database
124
+ .prepare(
125
+ "SELECT value FROM records WHERE namespace = ? AND scope = ? ORDER BY id ASC",
126
+ )
127
+ .all(namespace, scope);
128
+ return rows.map((row) => /** @type {T} */ (parseJson(row.value, "record")));
129
+ }
130
+
131
+ /**
132
+ * @template T
133
+ * @param {string} namespace
134
+ * @returns {readonly {
135
+ * readonly scope: string;
136
+ * readonly id: string;
137
+ * readonly value: T;
138
+ * }[]}
139
+ */
140
+ listNamespace(namespace) {
141
+ this.#assertOpen();
142
+ assertKey(namespace, "namespace");
143
+
144
+ const rows = this.#database
145
+ .prepare(
146
+ "SELECT scope, id, value FROM records WHERE namespace = ? ORDER BY scope ASC, id ASC",
147
+ )
148
+ .all(namespace);
149
+ return rows.map((row) => ({
150
+ scope: String(row.scope),
151
+ id: String(row.id),
152
+ value: /** @type {T} */ (parseJson(row.value, "record")),
153
+ }));
154
+ }
155
+
156
+ /**
157
+ * @template T
158
+ * @param {string} namespace
159
+ * @param {string} scope
160
+ * @param {string} id
161
+ * @param {T} value
162
+ * @returns {void}
163
+ */
164
+ put(namespace, scope, id, value) {
165
+ this.#assertOpen();
166
+ assertKey(namespace, "namespace");
167
+ assertKey(scope, "scope");
168
+ assertKey(id, "id");
169
+ const encoded = stringifyJson(value);
170
+
171
+ this.#database
172
+ .prepare(
173
+ `INSERT INTO records (namespace, scope, id, value)
174
+ VALUES (?, ?, ?, ?)
175
+ ON CONFLICT(namespace, scope, id) DO UPDATE SET value = excluded.value`,
176
+ )
177
+ .run(namespace, scope, id, encoded);
178
+ }
179
+
180
+ /**
181
+ * @param {string} namespace
182
+ * @param {string} scope
183
+ * @param {string} id
184
+ * @returns {boolean}
185
+ */
186
+ delete(namespace, scope, id) {
187
+ this.#assertOpen();
188
+ assertKey(namespace, "namespace");
189
+ assertKey(scope, "scope");
190
+ assertKey(id, "id");
191
+
192
+ const result = this.#database
193
+ .prepare(
194
+ "DELETE FROM records WHERE namespace = ? AND scope = ? AND id = ?",
195
+ )
196
+ .run(namespace, scope, id);
197
+ return Number(result.changes) !== 0;
198
+ }
199
+
200
+ /**
201
+ * @param {string} namespace
202
+ * @returns {void}
203
+ */
204
+ clearNamespace(namespace) {
205
+ this.#assertOpen();
206
+ assertKey(namespace, "namespace");
207
+ this.#database
208
+ .prepare("DELETE FROM records WHERE namespace = ?")
209
+ .run(namespace);
210
+ }
211
+
212
+ /**
213
+ * @template T
214
+ * @param {string} key
215
+ * @returns {T | undefined}
216
+ */
217
+ getMeta(key) {
218
+ this.#assertOpen();
219
+ assertKey(key, "key");
220
+
221
+ const row = this.#database
222
+ .prepare("SELECT value FROM metadata WHERE key = ?")
223
+ .get(key);
224
+ if (row === undefined) {
225
+ return undefined;
226
+ }
227
+ return /** @type {T} */ (parseJson(row.value, "metadata value"));
228
+ }
229
+
230
+ /**
231
+ * @template T
232
+ * @param {string} key
233
+ * @param {T} value
234
+ * @returns {void}
235
+ */
236
+ setMeta(key, value) {
237
+ this.#assertOpen();
238
+ assertKey(key, "key");
239
+ const encoded = stringifyJson(value);
240
+
241
+ this.#database
242
+ .prepare(
243
+ `INSERT INTO metadata (key, value)
244
+ VALUES (?, ?)
245
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`,
246
+ )
247
+ .run(key, encoded);
248
+ }
249
+
250
+ /**
251
+ * Serialize local native requests and async auth callbacks on this connection.
252
+ *
253
+ * @template T
254
+ * @param {() => Promise<T> | T} work
255
+ * @returns {Promise<T>}
256
+ */
257
+ async exclusive(work) {
258
+ const prior = this.#queue;
259
+ /** @type {() => void} */
260
+ let release = () => {};
261
+ this.#queue = /** @type {Promise<void>} */ (
262
+ new Promise((resolve) => {
263
+ release = resolve;
264
+ })
265
+ );
266
+ await prior;
267
+ try {
268
+ this.#assertOpen();
269
+ return await work();
270
+ } finally {
271
+ release();
272
+ }
273
+ }
274
+
275
+ /**
276
+ * @template T
277
+ * @param {() => Promise<T>} work
278
+ * @returns {Promise<T>}
279
+ */
280
+ async transactionAsync(work) {
281
+ return this.exclusive(async () => {
282
+ if (this.#transactionDepth !== 0)
283
+ throw new Error(
284
+ "Nested asynchronous SQLite transaction is unavailable.",
285
+ );
286
+ this.#database.exec("BEGIN IMMEDIATE;");
287
+ this.#transactionDepth += 1;
288
+ try {
289
+ const result = await work();
290
+ this.#database.exec("COMMIT;");
291
+ return result;
292
+ } catch (error) {
293
+ this.#rollback();
294
+ throw error;
295
+ } finally {
296
+ this.#transactionDepth -= 1;
297
+ }
298
+ });
299
+ }
300
+
301
+ /**
302
+ * @template T
303
+ * @param {() => T} work
304
+ * @returns {T}
305
+ */
306
+ transaction(work) {
307
+ this.#assertOpen();
308
+ if (typeof work !== "function") {
309
+ throw new TypeError("transaction work must be a function.");
310
+ }
311
+
312
+ const nested = this.#transactionDepth !== 0;
313
+ const savepoint = nested
314
+ ? `terrascale_tx_${++this.#savepointSequence}`
315
+ : undefined;
316
+ if (savepoint === undefined) {
317
+ this.#database.exec("BEGIN IMMEDIATE;");
318
+ } else {
319
+ this.#database.exec(`SAVEPOINT ${savepoint};`);
320
+ }
321
+ this.#transactionDepth += 1;
322
+
323
+ try {
324
+ const result = work();
325
+ if (result instanceof Promise)
326
+ throw new TypeError("Use transactionAsync for asynchronous callbacks.");
327
+ if (savepoint === undefined) {
328
+ this.#database.exec("COMMIT;");
329
+ } else {
330
+ this.#database.exec(`RELEASE SAVEPOINT ${savepoint};`);
331
+ }
332
+ this.#transactionDepth -= 1;
333
+ return result;
334
+ } catch (error) {
335
+ this.#rollback(savepoint);
336
+ this.#transactionDepth -= 1;
337
+ throw error;
338
+ }
339
+ }
340
+
341
+ /** @returns {void} */
342
+ #migrate() {
343
+ this.#database.exec("BEGIN IMMEDIATE;");
344
+ try {
345
+ this.#database.exec(`
346
+ CREATE TABLE IF NOT EXISTS schema_migrations (
347
+ version INTEGER NOT NULL PRIMARY KEY,
348
+ applied_at TEXT NOT NULL
349
+ ) WITHOUT ROWID;
350
+ `);
351
+
352
+ const row = this.#database
353
+ .prepare(
354
+ "SELECT version FROM schema_migrations ORDER BY version DESC LIMIT 1",
355
+ )
356
+ .get();
357
+ const version = row?.version;
358
+ if (version !== undefined && typeof version !== "number") {
359
+ throw new Error("Invalid schema migration version.");
360
+ }
361
+ if (version !== undefined && version > SCHEMA_VERSION) {
362
+ throw new Error(
363
+ `Database schema version ${version} is newer than supported version ${SCHEMA_VERSION}.`,
364
+ );
365
+ }
366
+
367
+ if (version === undefined) {
368
+ this.#database.exec(`
369
+ CREATE TABLE records (
370
+ namespace TEXT NOT NULL,
371
+ scope TEXT NOT NULL,
372
+ id TEXT NOT NULL,
373
+ value TEXT NOT NULL CHECK (json_valid(value) = 1),
374
+ PRIMARY KEY (namespace, scope, id)
375
+ ) WITHOUT ROWID;
376
+
377
+ CREATE TABLE metadata (
378
+ key TEXT NOT NULL PRIMARY KEY,
379
+ value TEXT NOT NULL CHECK (json_valid(value) = 1)
380
+ ) WITHOUT ROWID;
381
+ `);
382
+ this.#database
383
+ .prepare(
384
+ "INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)",
385
+ )
386
+ .run(SCHEMA_VERSION, new Date().toISOString());
387
+ }
388
+ this.#database.exec("COMMIT;");
389
+ } catch (error) {
390
+ this.#rollback();
391
+ throw error;
392
+ }
393
+ }
394
+
395
+ /**
396
+ * @param {string | undefined} [savepoint]
397
+ * @returns {void}
398
+ */
399
+ #rollback(savepoint = undefined) {
400
+ try {
401
+ if (savepoint === undefined) {
402
+ this.#database.exec("ROLLBACK;");
403
+ } else {
404
+ this.#database.exec(
405
+ `ROLLBACK TO SAVEPOINT ${savepoint}; RELEASE SAVEPOINT ${savepoint};`,
406
+ );
407
+ }
408
+ } catch {
409
+ // Preserve the original work/migration exception. SQLite will normally
410
+ // have already rolled back a failed transaction automatically.
411
+ }
412
+ }
413
+
414
+ /** @returns {void} */
415
+ #assertOpen() {
416
+ if (this.#closed) {
417
+ throw new Error("The SQLite store is closed.");
418
+ }
419
+ }
420
+ }
421
+
422
+ /**
423
+ * @param {string} value
424
+ * @param {string} name
425
+ * @returns {void}
426
+ */
427
+ function assertKey(value, name) {
428
+ if (typeof value !== "string") {
429
+ throw new TypeError(`${name} must be a string.`);
430
+ }
431
+ if (value.includes("\u0000")) {
432
+ throw new TypeError(`${name} must not contain a NUL character.`);
433
+ }
434
+ }
435
+
436
+ /**
437
+ * @param {unknown} value
438
+ * @returns {string}
439
+ */
440
+ function stringifyJson(value) {
441
+ try {
442
+ assertJsonValue(value, new WeakSet());
443
+ const encoded = JSON.stringify(value);
444
+ if (encoded === undefined) {
445
+ throw new TypeError("Value must be JSON serializable.");
446
+ }
447
+ // JSON.stringify is expected to produce valid JSON, but parsing here
448
+ // keeps the invariant explicit before handing data to SQLite.
449
+ JSON.parse(encoded);
450
+ return encoded;
451
+ } catch (error) {
452
+ if (
453
+ error instanceof TypeError &&
454
+ error.message === "Value must be JSON serializable."
455
+ ) {
456
+ throw error;
457
+ }
458
+ throw new TypeError("Value must be JSON serializable.", { cause: error });
459
+ }
460
+ }
461
+
462
+ /**
463
+ * @param {unknown} value
464
+ * @param {WeakSet<object>} ancestors
465
+ * @returns {void}
466
+ */
467
+ function assertJsonValue(value, ancestors) {
468
+ if (
469
+ value === null ||
470
+ typeof value === "string" ||
471
+ typeof value === "boolean"
472
+ ) {
473
+ return;
474
+ }
475
+ if (typeof value === "number") {
476
+ if (!Number.isFinite(value)) {
477
+ throw new TypeError("JSON numbers must be finite.");
478
+ }
479
+ return;
480
+ }
481
+ if (
482
+ typeof value === "undefined" ||
483
+ typeof value === "bigint" ||
484
+ typeof value === "function" ||
485
+ typeof value === "symbol"
486
+ ) {
487
+ throw new TypeError("Value contains a non-JSON value.");
488
+ }
489
+ if (typeof value !== "object") {
490
+ throw new TypeError("Value contains a non-JSON value.");
491
+ }
492
+ if (ancestors.has(value)) {
493
+ throw new TypeError("Value contains a circular reference.");
494
+ }
495
+ ancestors.add(value);
496
+ if (Array.isArray(value)) {
497
+ for (const item of value) {
498
+ assertJsonValue(item, ancestors);
499
+ }
500
+ } else {
501
+ const objectValue = /** @type {Record<string, unknown>} */ (value);
502
+ for (const key of Object.keys(value)) {
503
+ assertJsonValue(objectValue[key], ancestors);
504
+ }
505
+ for (const symbol of Object.getOwnPropertySymbols(value)) {
506
+ if (Object.prototype.propertyIsEnumerable.call(value, symbol)) {
507
+ throw new TypeError("Value contains a symbol property.");
508
+ }
509
+ }
510
+ }
511
+ ancestors.delete(value);
512
+ }
513
+
514
+ /**
515
+ * @param {unknown} encoded
516
+ * @param {string} label
517
+ * @returns {unknown}
518
+ */
519
+ function parseJson(encoded, label) {
520
+ if (typeof encoded !== "string") {
521
+ throw new Error(`Invalid ${label}: SQLite value is not text.`);
522
+ }
523
+ try {
524
+ return JSON.parse(encoded);
525
+ } catch (error) {
526
+ throw new Error(`Invalid ${label}: stored JSON is malformed.`, {
527
+ cause: error,
528
+ });
529
+ }
530
+ }
@@ -0,0 +1,74 @@
1
+ import { assertLocalSqliteEnvironment } from "./environment.js";
2
+ /** Local development tests never relax CI/deployment/qualification guards. */
3
+ export const localSqliteUnitTestsAllowed = (() => {
4
+ try {
5
+ assertLocalSqliteEnvironment();
6
+ return true;
7
+ } catch {
8
+ return false;
9
+ }
10
+ })();
11
+ import { mkdtemp, rm } from "node:fs/promises";
12
+ import { join } from "node:path";
13
+ import { tmpdir } from "node:os";
14
+ import { randomBytes, randomUUID } from "node:crypto";
15
+ import { createTerraScaleSqliteRuntime } from "./index.js";
16
+ /** @import { TerraScaleSqliteRuntimeOptions } from "./index.js" */
17
+ /** @import { DatabaseCommit, DatabaseField, DatabasePoint } from "../database-types.js" */
18
+ /**
19
+ * @param {Omit<TerraScaleSqliteRuntimeOptions, "sqlite">} [options]
20
+ */
21
+ export async function localFixture(options = {}) {
22
+ const directory = await mkdtemp(join(tmpdir(), "terrascale-local-native-"));
23
+ const databasePath = join(directory, "development.sqlite");
24
+ const runtime = createTerraScaleSqliteRuntime({
25
+ ...options,
26
+ sqlite: { enabled: true, databasePath },
27
+ });
28
+ const collection = randomUUID();
29
+ const binding = {
30
+ collection,
31
+ schema_generation: 1n,
32
+ schema_digest: "a".repeat(64),
33
+ };
34
+ await runtime.bindCollection(binding);
35
+ const client = runtime.createClient();
36
+ /**
37
+ * @param {string} document
38
+ * @param {readonly DatabaseField[]} fields
39
+ * @param {DatabasePoint} [point]
40
+ * @returns {DatabaseCommit}
41
+ */
42
+ const commit = (document, fields, point) => ({
43
+ transaction_id: {
44
+ expires_at_unix_ms: BigInt(Date.now() + 60_000),
45
+ nonce: randomBytes(16).toString("hex"),
46
+ },
47
+ authority: runtime.authority,
48
+ writes: [
49
+ {
50
+ ...binding,
51
+ document,
52
+ condition:
53
+ point === undefined
54
+ ? { kind: "absent" }
55
+ : { kind: "version", version: /** @type {string} */ (point.state_version) },
56
+ observed: point?.context ?? [],
57
+ value: { kind: "live", fields },
58
+ },
59
+ ],
60
+ });
61
+ return {
62
+ runtime,
63
+ client,
64
+ collection,
65
+ binding,
66
+ directory,
67
+ databasePath,
68
+ commit,
69
+ close: async () => {
70
+ await runtime.close();
71
+ await rm(directory, { recursive: true, force: true });
72
+ },
73
+ };
74
+ }
@@ -0,0 +1,72 @@
1
+ /** Required capabilities whose producer/runtime admission is still open. */
2
+ export const terraScaleReleaseBlockers = Object.freeze({
3
+ management: Object.freeze({
4
+ code: "management_contract_unavailable",
5
+ owner: "C06/C07 regional management service",
6
+ required: Object.freeze([
7
+ "resource provisioning",
8
+ "schema/catalog plan/apply/status",
9
+ "credential lifecycle",
10
+ "backups/restore",
11
+ "imports/exports",
12
+ "usage",
13
+ "administration",
14
+ ]),
15
+ }),
16
+ betterAuth: Object.freeze({
17
+ code: "security_authority_unavailable",
18
+ owner: "native single-writer security admission and C06/C07",
19
+ required: Object.freeze([
20
+ "unique insert",
21
+ "compare-and-swap",
22
+ "one-time consume",
23
+ "bounded transactions",
24
+ "pagination/joins",
25
+ "indexes/migrations",
26
+ "revocation/denial",
27
+ "region concurrency",
28
+ ]),
29
+ }),
30
+ qualification: Object.freeze({
31
+ code: "runtime_qualification_unavailable",
32
+ owner: "real producer allocation and deployed customer application",
33
+ required: Object.freeze([
34
+ "installed package against real producer",
35
+ "deployed authenticated account/browser journey",
36
+ ]),
37
+ }),
38
+ });
39
+ /** @typedef {keyof typeof terraScaleReleaseBlockers} TerraScaleBlockedCapability */
40
+ export class TerraScaleCapabilityUnavailableError extends Error {
41
+ /**
42
+ * @readonly
43
+ * @type {string}
44
+ */
45
+ code;
46
+ /**
47
+ * @readonly
48
+ * @type {TerraScaleBlockedCapability}
49
+ */
50
+ capability;
51
+ /**
52
+ * @readonly
53
+ * @type {readonly string[]}
54
+ */
55
+ required;
56
+ /** @param {TerraScaleBlockedCapability} capability */
57
+ constructor(capability) {
58
+ const status = terraScaleReleaseBlockers[capability];
59
+ super(`${capability} requires admitted ${status.owner}.`);
60
+ this.name = "TerraScaleCapabilityUnavailableError";
61
+ this.code = status.code;
62
+ this.capability = capability;
63
+ this.required = status.required;
64
+ }
65
+ }
66
+ /**
67
+ * @param {TerraScaleBlockedCapability} capability
68
+ * @returns {never}
69
+ */
70
+ export function requireTerraScaleCapability(capability) {
71
+ throw new TerraScaleCapabilityUnavailableError(capability);
72
+ }
@@ -0,0 +1,12 @@
1
+ export {
2
+ terraScaleReleaseBlockers,
3
+ TerraScaleCapabilityUnavailableError,
4
+ } from "./management-contracts.js";
5
+ import { requireTerraScaleCapability } from "./management-contracts.js";
6
+ /**
7
+ * Required public management support remains closed until C06/C07 route admission.
8
+ * @returns {never}
9
+ */
10
+ export function requireTerraScaleManagement() {
11
+ return requireTerraScaleCapability("management");
12
+ }