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,23 @@
1
+ /** @typedef {"permissive" | "required"} LocalAuthenticationMode */
2
+ /**
3
+ * @param {unknown} value
4
+ * @param {number} [status]
5
+ * @returns {Response}
6
+ */
7
+ export function jsonResponse(value, status = 200) {
8
+ const body = JSON.stringify(value);
9
+ if (new TextEncoder().encode(body).byteLength > 4 * 1024 * 1024)
10
+ return problemResponse(503, "capacity");
11
+ return new Response(body, {
12
+ status,
13
+ headers: { "content-type": "application/json" },
14
+ });
15
+ }
16
+ /**
17
+ * @param {number} status
18
+ * @param {string} code
19
+ * @returns {Response}
20
+ */
21
+ export function problemResponse(status, code) {
22
+ return jsonResponse({ error: { code } }, status);
23
+ }
@@ -0,0 +1,109 @@
1
+ export const localSqlitePresenceIndicators = /** @type {const} */ ([
2
+ "CI",
3
+ "CONTINUOUS_INTEGRATION",
4
+ "GITHUB_ACTIONS",
5
+ "GITLAB_CI",
6
+ "FORGEJO_ACTIONS",
7
+ "GITEA_ACTIONS",
8
+ "TF_BUILD",
9
+ "BUILDKITE",
10
+ "CIRCLECI",
11
+ "JENKINS_URL",
12
+ "TEAMCITY_VERSION",
13
+ "VERCEL",
14
+ "NETLIFY",
15
+ "CF_PAGES",
16
+ "RENDER",
17
+ "FLY_APP_NAME",
18
+ "K_SERVICE",
19
+ "CLOUD_RUN_JOB",
20
+ "KUBERNETES_SERVICE_HOST",
21
+ "AWS_LAMBDA_FUNCTION_NAME",
22
+ "AWS_EXECUTION_ENV",
23
+ "ECS_CONTAINER_METADATA_URI",
24
+ "ECS_CONTAINER_METADATA_URI_V4",
25
+ "WEBSITE_INSTANCE_ID",
26
+ "WEBSITE_SITE_NAME",
27
+ "FUNCTIONS_WORKER_RUNTIME",
28
+ "DYNO",
29
+ "HEROKU_APP_NAME",
30
+ "RAILWAY_ENVIRONMENT",
31
+ "RAILWAY_SERVICE_ID",
32
+ "NOMAD_ALLOC_ID",
33
+ "TERRASCALE_DEPLOYMENT",
34
+ "TERRASCALE_QUALIFICATION",
35
+ ]);
36
+
37
+ /**
38
+ * Explicit opt-in for a hosted development environment.
39
+ *
40
+ * Hosting platforms set a presence indicator on every workload, including
41
+ * development environments, so the SDK cannot tell a hosted dev environment
42
+ * from production by presence alone. Setting this variable to a true value
43
+ * relaxes *platform* indicators only: CI, qualification, and every stage
44
+ * indicator (`NODE_ENV=production`, `STAGE=staging`, …) still refuse.
45
+ */
46
+ export const localSqliteHostedOptIn = "TERRASCALE_LOCAL_SQLITE_ALLOW_HOSTED";
47
+
48
+ /**
49
+ * Presence indicators the opt-in never relaxes.
50
+ *
51
+ * @type {ReadonlySet<string>}
52
+ */
53
+ export const localSqliteUnrelaxableIndicators = new Set([
54
+ "CI",
55
+ "CONTINUOUS_INTEGRATION",
56
+ "GITHUB_ACTIONS",
57
+ "GITLAB_CI",
58
+ "FORGEJO_ACTIONS",
59
+ "GITEA_ACTIONS",
60
+ "TF_BUILD",
61
+ "BUILDKITE",
62
+ "CIRCLECI",
63
+ "JENKINS_URL",
64
+ "TEAMCITY_VERSION",
65
+ "TERRASCALE_DEPLOYMENT",
66
+ "TERRASCALE_QUALIFICATION",
67
+ ]);
68
+
69
+ export const localSqliteStageIndicators = /** @type {const} */ ([
70
+ "NODE_ENV",
71
+ "APP_ENV",
72
+ "ENVIRONMENT",
73
+ "STAGE",
74
+ "DEPLOY_ENV",
75
+ "DEPLOYMENT_ENVIRONMENT",
76
+ "TERRASCALE_ENV",
77
+ "TERRASCALE_ENVIRONMENT",
78
+ "VERCEL_ENV",
79
+ ]);
80
+
81
+ const FORBIDDEN_STAGE_PATTERN =
82
+ /(?:^|[-_.])(?:prod(?:uction)?|stag(?:e|ing)|preview|deploy(?:ed|ment)?|qualif(?:ication|ying|y)|ci)(?:$|[-_.])/iu;
83
+ const FALSE_INDICATOR_VALUES = new Set(["0", "false", "no", "off"]);
84
+ const OPT_IN_VALUES = new Set(["1", "true", "yes", "on", "enabled"]);
85
+
86
+ /**
87
+ * @param {Readonly<Record<string, string | undefined>>} [environment]
88
+ * @returns {void}
89
+ */
90
+ export function assertLocalSqliteEnvironment(environment = process.env) {
91
+ const hostedOptIn = OPT_IN_VALUES.has(
92
+ (environment[localSqliteHostedOptIn] ?? "").trim().toLowerCase(),
93
+ );
94
+
95
+ for (const name of localSqlitePresenceIndicators) {
96
+ const value = environment[name]?.trim();
97
+ if (value === undefined || value.length === 0) continue;
98
+ if (FALSE_INDICATOR_VALUES.has(value.toLowerCase())) continue;
99
+ if (hostedOptIn && !localSqliteUnrelaxableIndicators.has(name)) continue;
100
+ throw new TypeError(`SQLite local development mode is disabled when ${name} is set.`);
101
+ }
102
+
103
+ for (const name of localSqliteStageIndicators) {
104
+ const value = environment[name]?.trim();
105
+ if (value !== undefined && FORBIDDEN_STAGE_PATTERN.test(value)) {
106
+ throw new TypeError(`SQLite local development mode is disabled for ${name}=${value}.`);
107
+ }
108
+ }
109
+ }
@@ -0,0 +1,204 @@
1
+ import { isAbsolute } from "node:path";
2
+ import { randomUUID } from "node:crypto";
3
+ import { createTerraBaseClient } from "../database.js";
4
+ import { normalizeLocalBearerToken } from "./authentication.js";
5
+ import { assertLocalSqliteEnvironment } from "./environment.js";
6
+ import { TerraScaleLocalRouter } from "./router.js";
7
+ import { TerraScaleLocalHttpServer } from "./server.js";
8
+ import { TerraScaleSqliteStore } from "./store.js";
9
+ /** @import { TerraBaseClient, TerraBaseClientConfig } from "../database.js" */
10
+ /** @import { CollectionBinding, DatabaseScope } from "../database-types.js" */
11
+
12
+ /** @typedef {import("./context.js").LocalAuthenticationMode} LocalAuthenticationMode */
13
+ /** @typedef {import("./server.js").LocalServerAddress} LocalServerAddress */
14
+ /** @typedef {import("./better-auth.js").LocalBetterAuthAdapterFactory} LocalBetterAuthAdapterFactory */
15
+
16
+ /**
17
+ * @typedef {{
18
+ * readonly sqlite: { readonly enabled: true; readonly databasePath: string };
19
+ * readonly scope?: DatabaseScope;
20
+ * readonly authentication?: LocalAuthenticationMode;
21
+ * readonly bearerToken?: string;
22
+ * readonly allowRemote?: boolean;
23
+ * }} TerraScaleSqliteRuntimeOptions
24
+ */
25
+ /**
26
+ * @typedef {{
27
+ * readonly host?: string;
28
+ * readonly port?: number;
29
+ * }} TerraScaleLocalListenOptions
30
+ */
31
+
32
+ /** Explicit Node development backend. Never load this entrypoint in deployment or qualification. */
33
+ export class TerraScaleSqliteRuntime {
34
+ /** @type {TerraScaleSqliteStore} */
35
+ #store;
36
+ /** @type {TerraScaleLocalRouter} */
37
+ #router;
38
+ /** @type {TerraScaleLocalHttpServer} */
39
+ #server;
40
+ /** @type {string} */
41
+ #token;
42
+ /**
43
+ * @readonly
44
+ * @type {DatabaseScope}
45
+ */
46
+ scope;
47
+ #closed = false;
48
+ /** @type {Promise<void> | undefined} */
49
+ #closePromise;
50
+ /**
51
+ * @param {TerraScaleSqliteRuntimeOptions} options
52
+ */
53
+ constructor(options) {
54
+ assertLocalSqliteEnvironment();
55
+ if (options?.sqlite?.enabled !== true)
56
+ throw new TypeError("SQLite runtime requires sqlite: { enabled: true }.");
57
+ if (!isAbsolute(options.sqlite.databasePath))
58
+ throw new TypeError(
59
+ "SQLite runtime databasePath must be an absolute path.",
60
+ );
61
+ const authentication = options.authentication ?? "permissive";
62
+ if (authentication !== "permissive" && authentication !== "required")
63
+ throw new TypeError("Invalid local authentication mode.");
64
+ if (
65
+ options.allowRemote !== undefined &&
66
+ typeof options.allowRemote !== "boolean"
67
+ )
68
+ throw new TypeError("allowRemote must be boolean.");
69
+ if (options.allowRemote === true && authentication !== "required")
70
+ throw new TypeError(
71
+ "Remote local listeners require explicit authentication.",
72
+ );
73
+ if (authentication === "permissive" && options.bearerToken !== undefined)
74
+ throw new TypeError("bearerToken requires authentication: required.");
75
+ this.#token =
76
+ authentication === "required"
77
+ ? normalizeLocalBearerToken(options.bearerToken)
78
+ : "local-development-only";
79
+ this.#store = new TerraScaleSqliteStore(options.sqlite.databasePath);
80
+ try {
81
+ const stored =
82
+ /** @type {{ tenant: string; database: string; shard: string } | undefined} */ (
83
+ this.#store.getMeta("local-native-scope")
84
+ );
85
+ const scope =
86
+ options.scope ??
87
+ (stored === undefined
88
+ ? { tenant: randomUUID(), database: randomUUID(), shard: 0n }
89
+ : { ...stored, shard: BigInt(stored.shard) });
90
+ this.#router = new TerraScaleLocalRouter(this.#store, scope, {
91
+ authentication,
92
+ ...(authentication === "required" ? { bearerToken: this.#token } : {}),
93
+ });
94
+ this.scope = Object.freeze({ ...scope });
95
+ this.#store.setMeta("local-native-scope", {
96
+ ...scope,
97
+ shard: scope.shard.toString(),
98
+ });
99
+ this.#server = new TerraScaleLocalHttpServer(
100
+ (request) => this.#router.handle(request),
101
+ { allowRemote: options.allowRemote ?? false },
102
+ );
103
+ } catch (error) {
104
+ this.#store.close();
105
+ throw error;
106
+ }
107
+ }
108
+ /** @returns {string} */
109
+ get databasePath() {
110
+ return this.#store.databasePath;
111
+ }
112
+ /** @returns {LocalServerAddress | undefined} */
113
+ get address() {
114
+ return this.#server.address;
115
+ }
116
+ get authority() {
117
+ this.#assertOpen();
118
+ return this.#router.authority;
119
+ }
120
+ /**
121
+ * @param {CollectionBinding} binding
122
+ * @returns {Promise<void>}
123
+ */
124
+ async bindCollection(binding) {
125
+ this.#assertOpen();
126
+ await this.#store.exclusive(() => {
127
+ this.#assertOpen();
128
+ this.#store.transaction(() => this.#router.bindCollection(binding));
129
+ });
130
+ }
131
+ /**
132
+ * @param {Pick<TerraBaseClientConfig, "requestTimeoutMs">} [options]
133
+ * @returns {TerraBaseClient}
134
+ */
135
+ createClient(options = {}) {
136
+ this.#assertOpen();
137
+ return createTerraBaseClient({
138
+ ...this.scope,
139
+ ...options,
140
+ endpoint: "http://127.0.0.1",
141
+ apiKey: this.#token,
142
+ fetch: async (input, init) => {
143
+ this.#assertOpen();
144
+ return this.#router.fetch(input, init);
145
+ },
146
+ });
147
+ }
148
+ /** @returns {Promise<LocalBetterAuthAdapterFactory>} */
149
+ async createBetterAuthAdapter() {
150
+ this.#assertOpen();
151
+ const { createLocalBetterAuthAdapter } = await import("./better-auth.js");
152
+ this.#assertOpen();
153
+ return createLocalBetterAuthAdapter(this.#store);
154
+ }
155
+ /**
156
+ * @param {TerraScaleLocalListenOptions} [options]
157
+ * @returns {Promise<LocalServerAddress>}
158
+ */
159
+ start(options = {}) {
160
+ this.#assertOpen();
161
+ return this.#server.start(options);
162
+ }
163
+ /** @returns {Promise<void>} */
164
+ stop() {
165
+ return this.#server.close();
166
+ }
167
+ /** @returns {Promise<void>} */
168
+ async reset() {
169
+ this.#assertOpen();
170
+ await this.#store.exclusive(() => {
171
+ this.#assertOpen();
172
+ this.#store.reset();
173
+ this.#router = new TerraScaleLocalRouter(this.#store, this.scope, {
174
+ authentication: "required",
175
+ bearerToken: this.#token,
176
+ });
177
+ this.#store.setMeta("local-native-scope", {
178
+ ...this.scope,
179
+ shard: this.scope.shard.toString(),
180
+ });
181
+ });
182
+ }
183
+ /** @returns {Promise<void>} */
184
+ close() {
185
+ if (this.#closePromise !== undefined) return this.#closePromise;
186
+ this.#closed = true;
187
+ this.#closePromise = (async () => {
188
+ await this.#server.close();
189
+ await this.#store.exclusive(() => this.#store.close());
190
+ })();
191
+ return this.#closePromise;
192
+ }
193
+ /** @returns {void} */
194
+ #assertOpen() {
195
+ if (this.#closed) throw new Error("The SQLite runtime is closed.");
196
+ }
197
+ }
198
+ /**
199
+ * @param {TerraScaleSqliteRuntimeOptions} options
200
+ * @returns {TerraScaleSqliteRuntime}
201
+ */
202
+ export function createTerraScaleSqliteRuntime(options) {
203
+ return new TerraScaleSqliteRuntime(options);
204
+ }