toiljs 0.0.85 → 0.0.87

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 (132) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +2 -2
  3. package/build/cli/.tsbuildinfo +1 -1
  4. package/build/cli/index.js +303 -293
  5. package/build/compiler/.tsbuildinfo +1 -1
  6. package/build/compiler/config.d.ts +2 -0
  7. package/build/compiler/config.js +1 -0
  8. package/build/compiler/docs.js +8 -24
  9. package/build/compiler/generate.js +4 -2
  10. package/build/compiler/index.d.ts +1 -1
  11. package/build/compiler/index.js +69 -6
  12. package/build/compiler/toil-docs.generated.js +64 -22
  13. package/build/devserver/.tsbuildinfo +1 -1
  14. package/build/devserver/analytics/index.js +7 -3
  15. package/build/devserver/db/database.d.ts +4 -0
  16. package/build/devserver/db/database.js +43 -1
  17. package/build/devserver/db/index.d.ts +1 -1
  18. package/build/devserver/db/index.js +1 -1
  19. package/build/devserver/db/types.d.ts +3 -0
  20. package/build/devserver/db/types.js +2 -0
  21. package/build/devserver/runtime/module.js +4 -1
  22. package/docs/README.md +104 -65
  23. package/docs/auth/README.md +102 -0
  24. package/docs/auth/configuration.md +94 -0
  25. package/docs/auth/extending.md +202 -0
  26. package/docs/auth/how-it-works.md +138 -0
  27. package/docs/auth/usage.md +188 -0
  28. package/docs/backend/README.md +143 -0
  29. package/docs/backend/data.md +351 -0
  30. package/docs/backend/rest.md +402 -0
  31. package/docs/backend/rpc.md +226 -0
  32. package/docs/background/README.md +114 -0
  33. package/docs/background/daemons.md +230 -0
  34. package/docs/background/derive.md +179 -0
  35. package/docs/cli/README.md +377 -0
  36. package/docs/concepts/config.md +416 -0
  37. package/docs/concepts/decorators.md +127 -0
  38. package/docs/concepts/security.md +108 -0
  39. package/docs/concepts/tiers.md +166 -0
  40. package/docs/concepts/types.md +216 -0
  41. package/docs/database/README.md +143 -0
  42. package/docs/database/capacity.md +350 -0
  43. package/docs/database/counters.md +174 -0
  44. package/docs/database/documents.md +255 -0
  45. package/docs/database/events.md +307 -0
  46. package/docs/database/membership.md +246 -0
  47. package/docs/database/setup.md +155 -0
  48. package/docs/database/unique.md +216 -0
  49. package/docs/database/views.md +246 -0
  50. package/docs/frontend/README.md +101 -0
  51. package/docs/frontend/data-fetching.md +243 -0
  52. package/docs/frontend/images.md +148 -0
  53. package/docs/frontend/metadata.md +344 -0
  54. package/docs/frontend/rendering.md +236 -0
  55. package/docs/frontend/routing.md +344 -0
  56. package/docs/frontend/scripts.md +118 -0
  57. package/docs/frontend/search.md +191 -0
  58. package/docs/frontend/styling.md +147 -0
  59. package/docs/getting-started/README.md +81 -0
  60. package/docs/getting-started/create-project.md +131 -0
  61. package/docs/getting-started/deploy.md +101 -0
  62. package/docs/getting-started/first-app.md +215 -0
  63. package/docs/getting-started/installation.md +106 -0
  64. package/docs/getting-started/migrating.md +125 -0
  65. package/docs/getting-started/project-structure.md +163 -0
  66. package/docs/introduction/README.md +41 -0
  67. package/docs/introduction/design-principles.md +55 -0
  68. package/docs/introduction/distributed.md +74 -0
  69. package/docs/introduction/how-it-works.md +74 -0
  70. package/docs/introduction/hyperscale.md +51 -0
  71. package/docs/introduction/modern-stack.md +36 -0
  72. package/docs/introduction/vs-other-frameworks.md +48 -0
  73. package/docs/introduction/why-toil.md +93 -0
  74. package/docs/llms.txt +90 -0
  75. package/docs/realtime/README.md +102 -0
  76. package/docs/realtime/channels.md +211 -0
  77. package/docs/realtime/streams.md +369 -0
  78. package/docs/services/README.md +60 -0
  79. package/docs/services/analytics.md +268 -0
  80. package/docs/services/caching.md +175 -0
  81. package/docs/services/cookies.md +235 -0
  82. package/docs/services/crypto.md +209 -0
  83. package/docs/services/email.md +289 -0
  84. package/docs/services/environment.md +141 -0
  85. package/docs/services/ratelimit.md +174 -0
  86. package/docs/services/time.md +85 -0
  87. package/examples/basic/client/routes/analytics.tsx +13 -12
  88. package/examples/basic/server/models/SiteAnalytics.ts +29 -17
  89. package/examples/basic/server/routes/Analytics.ts +15 -17
  90. package/examples/basic/server/routes/UserId.ts +22 -0
  91. package/package.json +15 -11
  92. package/scripts/gen-toil-docs.mjs +24 -35
  93. package/server/auth/AuthController.ts +336 -0
  94. package/server/auth/AuthUser.ts +23 -0
  95. package/server/auth/index.ts +16 -0
  96. package/server/globals/auth.ts +31 -0
  97. package/server/globals/userid.ts +107 -0
  98. package/src/compiler/config.ts +13 -0
  99. package/src/compiler/docs.ts +16 -33
  100. package/src/compiler/generate.ts +6 -2
  101. package/src/compiler/index.ts +114 -6
  102. package/src/compiler/toil-docs.generated.ts +64 -22
  103. package/src/devserver/analytics/index.ts +10 -4
  104. package/src/devserver/db/database.ts +67 -1
  105. package/src/devserver/db/index.ts +1 -0
  106. package/src/devserver/db/types.ts +13 -0
  107. package/src/devserver/runtime/module.ts +7 -0
  108. package/test/analytics-dev.test.ts +2 -1
  109. package/test/devserver-database.test.ts +113 -0
  110. package/docs/auth-todo.md +0 -149
  111. package/docs/auth.md +0 -322
  112. package/docs/caching.md +0 -115
  113. package/docs/cli.md +0 -17
  114. package/docs/client.md +0 -39
  115. package/docs/cookies.md +0 -457
  116. package/docs/crypto.md +0 -130
  117. package/docs/daemon.md +0 -123
  118. package/docs/data.md +0 -131
  119. package/docs/derive.md +0 -159
  120. package/docs/email.md +0 -326
  121. package/docs/environment.md +0 -97
  122. package/docs/getting-started.md +0 -128
  123. package/docs/index.md +0 -30
  124. package/docs/ratelimit.md +0 -95
  125. package/docs/routing.md +0 -259
  126. package/docs/rpc.md +0 -149
  127. package/docs/server.md +0 -61
  128. package/docs/ssr.md +0 -632
  129. package/docs/streams.md +0 -178
  130. package/docs/styling.md +0 -22
  131. package/docs/tiers.md +0 -133
  132. package/docs/time.md +0 -43
@@ -3,6 +3,7 @@ import fs from 'node:fs';
3
3
  import { createRequire } from 'node:module';
4
4
  import net from 'node:net';
5
5
  import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
6
7
 
7
8
  import pc from 'picocolors';
8
9
  import { build as viteBuild, createServer, mergeConfig, type ViteDevServer } from 'vite';
@@ -106,6 +107,78 @@ function serverEntryFiles(root: string): string[] {
106
107
  return [...result].sort();
107
108
  }
108
109
 
110
+ /**
111
+ * The framework-shipped built-in-auth entry files (root-relative), appended to the toilscript entry
112
+ * set when `server.auth` is on. `AuthUser.ts` (the `@user` shape) is FIRST because `AuthController.ts`
113
+ * (the `@rest('auth')` controller) imports it. Primary lookup is the conventional install location
114
+ * under the app's `node_modules/toiljs` (which transparently follows a symlinked/linked toiljs); the
115
+ * fallback resolves the running toiljs package (`…/build/compiler/index.js` -> package root) for a
116
+ * hoisted install. Throws a clear error if the shipped files are missing.
117
+ */
118
+ function authEntryFiles(root: string, appHasUser: boolean): string[] {
119
+ // EXTEND mode: the app declares its own `@user`, so the build injects the reserved `toilUserId` +
120
+ // `username` identity fields into it (via `--authUser`) and does NOT append the shipped empty `@user`,
121
+ // only the controller. BUILTIN mode (no app `@user`): append both (the shipped empty `SessionUser`
122
+ // gets the same injected identity fields).
123
+ const names = appHasUser ? ['AuthController.ts'] : ['AuthUser.ts', 'AuthController.ts'];
124
+ const primary = names.map((n) => path.posix.join('node_modules/toiljs/server/auth', n));
125
+ if (primary.every((rel) => fs.existsSync(path.join(root, rel)))) return primary;
126
+ // Fallback: locate this running toiljs package dir and make the paths root-relative.
127
+ try {
128
+ const pkgDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
129
+ const abs = names.map((n) => path.join(pkgDir, 'server', 'auth', n));
130
+ if (abs.every((p) => fs.existsSync(p)))
131
+ return abs.map((p) => path.relative(root, p).replace(/\\/g, '/'));
132
+ } catch {
133
+ // fall through to the error below
134
+ }
135
+ throw new Error(
136
+ 'toiljs: server.auth is enabled but the built-in auth controller ' +
137
+ '(server/auth/AuthController.ts) was not found under node_modules/toiljs. ' +
138
+ 'Reinstall toiljs (npm i toiljs), or remove server.auth from toil.config.ts.',
139
+ );
140
+ }
141
+
142
+ /**
143
+ * Whether any server entry declares the escape-hatch `import 'toiljs/server/auth'` (a bare side-effect
144
+ * import) — the lighter opt-in that turns on the built-in auth surface without the `server.auth` config
145
+ * flag. Framework-shipped decorator sources under node_modules only WEAVE as explicit toilscript entries
146
+ * (a transitive import lands them under the `~lib/` LIBRARY prefix, where `@data`/`@rest`/`@user` do not
147
+ * weave), so the bare import is only a SIGNAL: it triggers the same entry injection as the flag. `import
148
+ * type` is skipped (erased). The marker module `server/auth/index.ts` is intentionally empty so it never
149
+ * pulls a conflicting `~lib/` copy of the controller into the compile.
150
+ */
151
+ /**
152
+ * Whether any server entry declares its own `@user` (EXTEND mode). When built-in auth is on and the app
153
+ * declares a `@user`, the build extends THAT class: toilscript injects the reserved `toilUserId` +
154
+ * `username` fields into it (via `--authUser`, erroring on a name collision) instead of appending the
155
+ * shipped empty built-in `@user`, so the app carries the identity plus its own fields with no boilerplate.
156
+ */
157
+ function serverDeclaresUser(root: string, files: string[]): boolean {
158
+ const re = /(^|[^.\w])@user\b/m;
159
+ for (const rel of files) {
160
+ try {
161
+ if (re.test(fs.readFileSync(path.join(root, rel), 'utf8'))) return true;
162
+ } catch {
163
+ // unreadable: skip
164
+ }
165
+ }
166
+ return false;
167
+ }
168
+
169
+ function serverImportsAuth(root: string, files: string[]): boolean {
170
+ // Bare side-effect import only (`import 'toiljs/server/auth'`); not `import type`, not a subpath.
171
+ const re = /(^|[^.\w])import\s+['"]toiljs\/server\/auth['"]/m;
172
+ for (const rel of files) {
173
+ try {
174
+ if (re.test(fs.readFileSync(path.join(root, rel), 'utf8'))) return true;
175
+ } catch {
176
+ // unreadable: skip
177
+ }
178
+ }
179
+ return false;
180
+ }
181
+
109
182
  /**
110
183
  * Builds the toilscript server target (which also regenerates `shared/server.ts` via
111
184
  * `--rpcModule`) when the project has one, signalled by a `toilconfig.json` at the root. This
@@ -115,7 +188,7 @@ function serverEntryFiles(root: string): string[] {
115
188
  * toilconfig entries) so dropped-in `@data`/`@rest` files are picked up. Runs the locally
116
189
  * installed `toilscript`, resolved + invoked via Node (no `.bin` shim / PATH assumptions).
117
190
  */
118
- export async function buildServer(root: string): Promise<void> {
191
+ export async function buildServer(root: string, auth: boolean = false): Promise<void> {
119
192
  if (!fs.existsSync(path.join(root, 'toilconfig.json'))) return;
120
193
 
121
194
  // Regenerate the editor-only server-globals d.ts each build (the same way
@@ -137,6 +210,29 @@ export async function buildServer(root: string): Promise<void> {
137
210
  // (optimization, features, runtime) still come from the toilconfig's `release` target.
138
211
  const files = serverEntryFiles(root);
139
212
 
213
+ // Built-in auth opt-in: APPEND the framework-shipped auth surface to the entry set BEFORE the tier
214
+ // split, so its `@user`/`@data`/`@database`/`@rest('auth')` decorators weave (a source under the
215
+ // `server/globals` LIB set — or transitively imported from node_modules under the `~lib/` prefix —
216
+ // would NOT weave) and the controller self-mounts at `/auth/*` with no hand-written boilerplate.
217
+ // AuthUser is ordered first (the controller imports it); AuthController lands in the REQUEST tier (it
218
+ // declares `@rest`) and AuthUser is shared into every pass (only `@user`/`@data`). They live under
219
+ // node_modules, so serverEntryFiles never scanned them. Opt-in is either the `server.auth` config flag
220
+ // OR the escape-hatch `import 'toiljs/server/auth'` in a server entry (which only MARKS the intent —
221
+ // it cannot weave on its own, being a `~lib/` import — so the build turns it into the same injection).
222
+ const authOn = auth || serverImportsAuth(root, files);
223
+ if (authOn) {
224
+ // EXTEND vs BUILTIN: if the app declares its own `@user`, extend that class (do not append the
225
+ // shipped `@user`); otherwise append the built-in empty one. Either way toilscript injects the
226
+ // reserved identity fields (`--authUser`, threaded into every pass below).
227
+ const appHasUser = serverDeclaresUser(root, files);
228
+ const authFiles = authEntryFiles(root, appHasUser);
229
+ const authSet = new Set(authFiles);
230
+ files.unshift(...authFiles);
231
+ for (let i = files.length - 1; i >= authFiles.length; i--) {
232
+ if (authSet.has(files[i])) files.splice(i, 1); // defensive dedup (normally none)
233
+ }
234
+ }
235
+
140
236
  // A project that declares a `@daemon` (L4 cold surface) and/or a `@stream` (L2/L3 stream
141
237
  // surface) compiles the ONE source tree into SEPARATE artifacts, one per deployment tier, via
142
238
  // one toilscript pass each; a project with only the request surface keeps the default
@@ -158,6 +254,7 @@ export async function buildServer(root: string): Promise<void> {
158
254
  mode: 'cold',
159
255
  outFile: artifacts.cold,
160
256
  withRpc: false,
257
+ authUser: authOn,
161
258
  });
162
259
  // STREAM pass: --targetMode hot into its OWN `release-stream.wasm`, no client RPC surface
163
260
  // (a resident stream box exposes `stream_dispatch`, not the request client surface). Driven
@@ -167,6 +264,7 @@ export async function buildServer(root: string): Promise<void> {
167
264
  mode: 'hot',
168
265
  outFile: artifacts.stream,
169
266
  withRpc: false,
267
+ authUser: authOn,
170
268
  });
171
269
  // REQUEST pass: the L1 artifact (= `outFile`), WITH the client RPC surface.
172
270
  // A pure daemon/stream project (no request files) skips it so toilscript is not handed an
@@ -181,6 +279,7 @@ export async function buildServer(root: string): Promise<void> {
181
279
  // the typed `Server.Stream` (class names, message-type encoders, merged @rest type)
182
280
  // WITHOUT compiling stream code into release.wasm.
183
281
  rpcSurfaceFiles: split.hasStream ? split.stream : undefined,
282
+ authUser: authOn,
184
283
  });
185
284
  // The stream pass carries no client RPC surface (withRpc:false), so toilscript never emits the
186
285
  // `Server.Stream` client into shared/server.ts. Append it here from the compiled stream
@@ -191,7 +290,12 @@ export async function buildServer(root: string): Promise<void> {
191
290
  }
192
291
 
193
292
  // Default request-only single-artifact path (no daemon/stream surface).
194
- await runToilscriptPass(root, binJs, files, { mode: null, outFile: null, withRpc: true });
293
+ await runToilscriptPass(root, binJs, files, {
294
+ mode: null,
295
+ outFile: null,
296
+ withRpc: true,
297
+ authUser: authOn,
298
+ });
195
299
  }
196
300
 
197
301
  /** Resolve the locally installed `toilscript` bin via Node (no `.bin` shim / PATH assumptions). */
@@ -405,6 +509,9 @@ interface PassOptions {
405
509
  * into this artifact. Lets the request pass emit `Server.Stream` without pulling stream code into
406
510
  * release.wasm. */
407
511
  readonly rpcSurfaceFiles?: readonly string[];
512
+ /** Built-in auth: pass `--authUser` so toilscript injects the reserved `toilUserId` + `username` fields
513
+ * into the single `@user`. Threaded into every pass so the `@user` codec is identical across tiers. */
514
+ readonly authUser?: boolean;
408
515
  }
409
516
 
410
517
  /** Run one toilscript pass. The toilscript CLI flag is `--targetMode` (camelCase). */
@@ -423,6 +530,7 @@ function runToilscriptPass(
423
530
  if (opts.withRpc) args.push('--rpcModule', 'shared/server.ts');
424
531
  if (opts.rpcSurfaceFiles)
425
532
  for (const surfaceFile of opts.rpcSurfaceFiles) args.push('--rpcSurfaceFiles', surfaceFile);
533
+ if (opts.authUser) args.push('--authUser');
426
534
  // Each pass is handed its OWN entry subset (the per-tier `files`); suppress the toilconfig
427
535
  // `entries` so toilscript does not ALSO append every project entry to every pass (which would
428
536
  // pull, e.g., a `@stream` class into the cold daemon pass). serverEntryFiles already folds
@@ -473,7 +581,7 @@ function watchServer(cfg: ResolvedToilConfig, watcher: ViteDevServer['watcher'])
473
581
  // Recompile emails/*.tsx -> the generated module before the server build,
474
582
  // so editing an email template hot-reloads like any other server change.
475
583
  renderEmails(cfg)
476
- .then(() => buildServer(root))
584
+ .then(() => buildServer(root, cfg.auth))
477
585
  .then(() => process.stdout.write(pc.green(' ✓ ') + pc.dim('server rebuilt') + '\n'))
478
586
  .catch((e: unknown) =>
479
587
  process.stdout.write(pc.red(` ✗ server rebuild failed: ${String(e)}`) + '\n'),
@@ -751,7 +859,7 @@ export async function dev(opts: ToilCommandOptions = {}): Promise<ViteDevServer>
751
859
  // documented fail-safe 500 until the next full `build`. A no-op without an `ssr = true` route.
752
860
  generate(cfg);
753
861
  if (hasServer) await extractServerSlots(cfg);
754
- await buildServer(cfg.root);
862
+ await buildServer(cfg.root, cfg.auth);
755
863
  if (hasServer) process.stdout.write(pc.green(' ✓ ') + pc.dim('server built') + '\n');
756
864
 
757
865
  if (!hasServer) {
@@ -874,7 +982,7 @@ export async function build(opts: ToilCommandOptions = {}): Promise<void> {
874
982
  // server compiles. (The `HASH` is finalized by the post-Vite `extractTemplates` below, which
875
983
  // recompiles the server only if it rotated.) A no-op for a project with no `ssr = true` route.
876
984
  const priorServerSlots = hasServer ? await extractServerSlots(cfg) : new Map<string, string>();
877
- await buildServer(cfg.root);
985
+ await buildServer(cfg.root, cfg.auth);
878
986
  if (opts.serverOnly) return;
879
987
  if (hasServer)
880
988
  process.stdout.write(
@@ -909,7 +1017,7 @@ export async function build(opts: ToilCommandOptions = {}): Promise<void> {
909
1017
  process.stdout.write(
910
1018
  pc.dim(' SSR template changed; recompiling the server with the new hash…') + '\n',
911
1019
  );
912
- await buildServer(cfg.root);
1020
+ await buildServer(cfg.root, cfg.auth);
913
1021
  }
914
1022
  }
915
1023