@c9up/ream 0.2.0 → 0.2.2

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 (62) hide show
  1. package/dist/Ignitor.d.ts +0 -8
  2. package/dist/Ignitor.d.ts.map +1 -1
  3. package/dist/Ignitor.js +15 -1
  4. package/dist/Ignitor.js.map +1 -1
  5. package/dist/bodyparser/MultipartFile.d.ts +22 -12
  6. package/dist/bodyparser/MultipartFile.d.ts.map +1 -1
  7. package/dist/bodyparser/MultipartFile.js +42 -15
  8. package/dist/bodyparser/MultipartFile.js.map +1 -1
  9. package/dist/errors/aggregate.d.ts +24 -0
  10. package/dist/errors/aggregate.d.ts.map +1 -0
  11. package/dist/errors/aggregate.js +24 -0
  12. package/dist/errors/aggregate.js.map +1 -0
  13. package/dist/events/services/main.d.ts.map +1 -1
  14. package/dist/events/services/main.js +3 -10
  15. package/dist/events/services/main.js.map +1 -1
  16. package/dist/http/Exception.d.ts +76 -6
  17. package/dist/http/Exception.d.ts.map +1 -1
  18. package/dist/http/Exception.js +178 -85
  19. package/dist/http/Exception.js.map +1 -1
  20. package/dist/index.d.ts +10 -4
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +8 -4
  23. package/dist/index.js.map +1 -1
  24. package/dist/router/Router.js +1 -1
  25. package/dist/router/Router.js.map +1 -1
  26. package/dist/services/app.d.ts.map +1 -1
  27. package/dist/services/app.js +3 -21
  28. package/dist/services/app.js.map +1 -1
  29. package/dist/services/console.d.ts.map +1 -1
  30. package/dist/services/console.js +3 -19
  31. package/dist/services/console.js.map +1 -1
  32. package/dist/services/createServiceProxy.d.ts +19 -0
  33. package/dist/services/createServiceProxy.d.ts.map +1 -0
  34. package/dist/services/createServiceProxy.js +44 -0
  35. package/dist/services/createServiceProxy.js.map +1 -0
  36. package/dist/services/router.d.ts.map +1 -1
  37. package/dist/services/router.js +3 -12
  38. package/dist/services/router.js.map +1 -1
  39. package/dist/services/server.d.ts.map +1 -1
  40. package/dist/services/server.js +3 -22
  41. package/dist/services/server.js.map +1 -1
  42. package/dist/types/container.d.ts +64 -0
  43. package/dist/types/container.d.ts.map +1 -0
  44. package/dist/types/container.js +23 -0
  45. package/dist/types/container.js.map +1 -0
  46. package/events.win32-x64-msvc.node +0 -0
  47. package/index.win32-x64-msvc.node +0 -0
  48. package/package.json +52 -30
  49. package/scheduler.win32-x64-msvc.node +0 -0
  50. package/src/Ignitor.ts +15 -9
  51. package/src/bodyparser/MultipartFile.ts +43 -15
  52. package/src/errors/aggregate.ts +33 -0
  53. package/src/events/services/main.ts +6 -12
  54. package/src/http/Exception.ts +225 -99
  55. package/src/index.ts +15 -2
  56. package/src/router/Router.ts +1 -1
  57. package/src/services/app.ts +6 -23
  58. package/src/services/console.ts +6 -21
  59. package/src/services/createServiceProxy.ts +47 -0
  60. package/src/services/router.ts +6 -14
  61. package/src/services/server.ts +6 -24
  62. package/src/types/container.ts +66 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c9up/ream",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Ream — Rust-powered Node.js application framework",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -11,6 +11,14 @@
11
11
  "types": "./dist/index.d.ts",
12
12
  "import": "./dist/index.js"
13
13
  },
14
+ "./types": {
15
+ "types": "./dist/types/container.d.ts",
16
+ "import": "./dist/types/container.js"
17
+ },
18
+ "./exceptions": {
19
+ "types": "./dist/http/Exception.d.ts",
20
+ "import": "./dist/http/Exception.js"
21
+ },
14
22
  "./console": {
15
23
  "types": "./dist/console/index.d.ts",
16
24
  "import": "./dist/console/index.js"
@@ -43,10 +51,42 @@
43
51
  "types": "./dist/session/SessionMiddleware.d.ts",
44
52
  "import": "./dist/session/SessionMiddleware.js"
45
53
  },
54
+ "./helpers": {
55
+ "types": "./dist/helpers/index.d.ts",
56
+ "import": "./dist/helpers/index.js"
57
+ },
58
+ "./helpers/string": {
59
+ "types": "./dist/helpers/string.d.ts",
60
+ "import": "./dist/helpers/string.js"
61
+ },
62
+ "./helpers/is": {
63
+ "types": "./dist/helpers/is.d.ts",
64
+ "import": "./dist/helpers/is.js"
65
+ },
66
+ "./helpers/base64": {
67
+ "types": "./dist/helpers/base64.d.ts",
68
+ "import": "./dist/helpers/base64.js"
69
+ },
70
+ "./helpers/types": {
71
+ "types": "./dist/helpers/types.d.ts",
72
+ "import": "./dist/helpers/types.js"
73
+ },
74
+ "./testing": {
75
+ "types": "./dist/testing/TestClient.d.ts",
76
+ "import": "./dist/testing/TestClient.js"
77
+ },
78
+ "./bootstrap": {
79
+ "types": "./dist/bootstrap/hyper-server.d.ts",
80
+ "import": "./dist/bootstrap/hyper-server.js"
81
+ },
46
82
  "./events": {
47
83
  "types": "./dist/events/index.d.ts",
48
84
  "import": "./dist/events/index.js"
49
85
  },
86
+ "./health": {
87
+ "types": "./dist/health/index.d.ts",
88
+ "import": "./dist/health/index.js"
89
+ },
50
90
  "./events/provider": {
51
91
  "types": "./dist/events/EventsProvider.d.ts",
52
92
  "import": "./dist/events/EventsProvider.js"
@@ -95,36 +135,18 @@
95
135
  "types": "./dist/events/configure.d.ts",
96
136
  "import": "./dist/events/configure.js"
97
137
  },
98
- "./helpers": {
99
- "types": "./dist/helpers/index.d.ts",
100
- "import": "./dist/helpers/index.js"
101
- },
102
- "./helpers/string": {
103
- "types": "./dist/helpers/string.d.ts",
104
- "import": "./dist/helpers/string.js"
105
- },
106
- "./helpers/is": {
107
- "types": "./dist/helpers/is.d.ts",
108
- "import": "./dist/helpers/is.js"
109
- },
110
- "./helpers/base64": {
111
- "types": "./dist/helpers/base64.d.ts",
112
- "import": "./dist/helpers/base64.js"
113
- },
114
- "./helpers/types": {
115
- "types": "./dist/helpers/types.d.ts",
116
- "import": "./dist/helpers/types.js"
117
- },
118
- "./testing": {
119
- "types": "./dist/testing/TestClient.d.ts",
120
- "import": "./dist/testing/TestClient.js"
121
- },
122
- "./bootstrap": {
123
- "types": "./dist/bootstrap/hyper-server.d.ts",
124
- "import": "./dist/bootstrap/hyper-server.js"
125
- },
126
138
  "./tsconfig.app.json": "./tsconfig.app.json",
127
- "./swcrc.app.json": "./swcrc.app.json"
139
+ "./swcrc.app.json": "./swcrc.app.json",
140
+ "./tsconfig.package.json": "./tsconfig.package.json",
141
+ "./tsconfig.client.json": "./tsconfig.client.json",
142
+ "./session_middleware": {
143
+ "types": "./dist/session/SessionMiddleware.d.ts",
144
+ "import": "./dist/session/SessionMiddleware.js"
145
+ },
146
+ "./bodyparser_middleware": {
147
+ "types": "./dist/bodyparser/BodyParserMiddleware.d.ts",
148
+ "import": "./dist/bodyparser/BodyParserMiddleware.js"
149
+ }
128
150
  },
129
151
  "files": [
130
152
  "LICENSE",
Binary file
package/src/Ignitor.ts CHANGED
@@ -142,14 +142,6 @@ export interface TestsConfig {
142
142
  * `tests/bootstrap.ts`; that stays the default here, and this overrides it.
143
143
  */
144
144
  bootstrap?: string
145
- /**
146
- * Point `@japa/runner/core` at helix's shim in every worker, so official Japa
147
- * plugins (`@japa/assert`, …) instrument the runner that is actually running.
148
- *
149
- * Off by default: redirecting a package specifier is not something to do
150
- * behind a user's back, and a project with no Japa plugin gains nothing.
151
- */
152
- japaPlugins?: boolean
153
145
  }
154
146
 
155
147
  /** defineConfig helper — like AdonisJS defineConfig(). */
@@ -269,6 +261,9 @@ export class Ignitor {
269
261
  // registry" and say something useful in each case.
270
262
  this.app.container.singleton('migrations', () => this.migrations)
271
263
  this.app.container.singleton('app', () => this.app)
264
+ // AdonisJS binds the config store too, so code that only needs settings can
265
+ // resolve `config` instead of taking the whole application.
266
+ this.app.container.singleton('config', () => this.app.config)
272
267
  // The HttpContext CLASS, so an integration provider can extend it the way
273
268
  // AdonisJS does — `HttpContext.getter('view', …)` is how the view layer
274
269
  // attaches `ctx.view`. Exported from the barrel too, but a provider that
@@ -733,7 +728,18 @@ export class Ignitor {
733
728
  for (const file of files) {
734
729
  const key = basename(file).replace(/\.(ts|js)$/, '')
735
730
  const mod = await import(pathToFileURL(join(configDir, file)).href)
736
- this.app.config.set(key, mod.default ?? mod)
731
+ // A config file that declares `export default` owns the value it exports,
732
+ // even when that value is `undefined` — that is how a module says "I am
733
+ // not configured", and a provider's `if (!config) return` guard has to be
734
+ // able to see it. Testing the binding rather than the value is what keeps
735
+ // that readable; `mod.default ?? mod` turns the absent value back into the
736
+ // truthy namespace and makes the guard unreachable.
737
+ //
738
+ // Without a `default` binding, the named exports become the config, which
739
+ // is what AdonisJS does (`fsImportAll`). Spread rather than stored as-is:
740
+ // an ESM namespace is sealed and null-prototype, so a later merge into
741
+ // this entry would fail on it.
742
+ this.app.config.set(key, 'default' in mod ? mod.default : { ...mod })
737
743
  }
738
744
  }
739
745
 
@@ -19,22 +19,49 @@ export class MultipartFile {
19
19
  readonly clientName: string
20
20
 
21
21
  /**
22
- * The PRIMARY mime type from the Content-Type header — `image` for
23
- * `image/png`. Pair it with {@link subtype}, as AdonisJS does.
22
+ * The PRIMARY mime type — `image` for `image/png`. Pair it with
23
+ * {@link subtype}, as AdonisJS does.
24
24
  *
25
- * BREAKING as of this version: it used to hold the whole `image/png`. The
26
- * change aligns the field with upstream, and TypeScript cannot catch a
27
- * comparison against a full mime string — `file.type === 'image/png'` now
28
- * simply never matches. Use `file.subtype` or {@link detectedType}.
25
+ * DERIVED FROM THE MAGIC BYTES when they can be read, and only from the
26
+ * Content-Type header when they cannot — upstream's precedence, and the one
27
+ * that matters: the header is written by the client, so a `.exe` announced as
28
+ * `image/png` would otherwise sail through a mime allowlist. A renamed file
29
+ * reports what it actually is.
29
30
  *
30
- * ATTACKER-CONTROLLED — never trust it for security decisions or as a
31
- * response Content-Type. Prefer {@link detectedType}, which is fingerprinted
32
- * from the file's magic bytes.
31
+ * BREAKING as of 0.2.0: it used to hold the whole `image/png`. TypeScript
32
+ * cannot catch a comparison against a full mime string — `file.type ===
33
+ * 'image/png'` still compiles and simply never matches. Use {@link mime}.
33
34
  */
34
- readonly type?: string
35
+ get type(): string | undefined {
36
+ return this.#mimeParts()[0]
37
+ }
38
+
39
+ /** The mime SUBTYPE — `png` for `image/png`. Same source as {@link type}. */
40
+ get subtype(): string | undefined {
41
+ return this.#mimeParts()[1]
42
+ }
43
+
44
+ /**
45
+ * The full mime to validate against — `image/png`.
46
+ *
47
+ * What an allowlist wants, and the reason this exists: splitting the mime for
48
+ * upstream parity left every caller re-joining it by hand, and a caller that
49
+ * forgot got a filter that silently matched nothing. Same trustworthy source
50
+ * as {@link type}.
51
+ */
52
+ get mime(): string | undefined {
53
+ const [type, subtype] = this.#mimeParts()
54
+ if (!type) return undefined
55
+ return subtype ? `${type}/${subtype}` : type
56
+ }
35
57
 
36
- /** The mime SUBTYPE — `png` for `image/png`. Attacker-controlled, like {@link type}. */
37
- readonly subtype?: string
58
+ /** Split the trustworthy mime once, detected bytes first, header second. */
59
+ #mimeParts(): [string | undefined, string | undefined] {
60
+ const source = this.#detected?.mime ?? this.#headerMime
61
+ if (!source) return [undefined, undefined]
62
+ const [type, subtype] = source.split(';')[0].trim().split('/')
63
+ return [type || undefined, subtype || undefined]
64
+ }
38
65
 
39
66
  /**
40
67
  * The part's headers.
@@ -78,6 +105,9 @@ export class MultipartFile {
78
105
  /** Rules set through `sizeLimit` / `allowedExtensions`, used by a bare `validate()`. */
79
106
  #validationOptions: FileValidationOptions = {}
80
107
 
108
+ /** The Content-Type the client sent — attacker-controlled, the fallback only. */
109
+ readonly #headerMime: string
110
+
81
111
  /** Magic-byte fingerprint — set by {@link detectType}; absent for content `file-type` can't detect (text: txt/csv/svg/json). */
82
112
  #detected?: { ext: string; mime: string }
83
113
 
@@ -90,9 +120,7 @@ export class MultipartFile {
90
120
  this.fieldName = options.fieldName
91
121
  this.clientName = options.clientName
92
122
  this.headers = { 'content-type': options.type }
93
- const [type, subtype] = options.type.split(';')[0].trim().split('/')
94
- this.type = type || undefined
95
- this.subtype = subtype || undefined
123
+ this.#headerMime = options.type
96
124
  this.content = options.content
97
125
  this.size = options.content.length
98
126
  const segments = options.clientName.split('.')
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The framework's exceptions, in one bag.
3
+ *
4
+ * AdonisJS exposes `import { errors } from '@adonisjs/core'` (and one bag per
5
+ * module) so an app can branch on `error instanceof errors.E_ROUTE_NOT_FOUND`
6
+ * without importing each class by name. The flat named exports stay — this is
7
+ * the shape a migrating app already writes.
8
+ */
9
+
10
+ import { EnvValidationException } from '../env/Env.js'
11
+ import {
12
+ E_FORBIDDEN,
13
+ E_HTTP_EXCEPTION,
14
+ E_HTTP_REQUEST_ABORTED,
15
+ E_ROUTE_NOT_FOUND,
16
+ E_ROW_NOT_FOUND,
17
+ E_UNAUTHORIZED,
18
+ E_VALIDATION_ERROR,
19
+ } from '../http/Exception.js'
20
+ import { E_INSECURE_APP_KEY, E_MISSING_APP_KEY } from '../security/CookieSigner.js'
21
+
22
+ export const errors = {
23
+ E_FORBIDDEN,
24
+ E_HTTP_EXCEPTION,
25
+ E_HTTP_REQUEST_ABORTED,
26
+ E_INSECURE_APP_KEY,
27
+ E_INVALID_ENV_VARIABLES: EnvValidationException,
28
+ E_MISSING_APP_KEY,
29
+ E_ROUTE_NOT_FOUND,
30
+ E_ROW_NOT_FOUND,
31
+ E_UNAUTHORIZED,
32
+ E_VALIDATION_ERROR,
33
+ } as const
@@ -9,6 +9,7 @@
9
9
  * Populated by `EventsProvider.boot()`.
10
10
  */
11
11
 
12
+ import { createServiceProxy } from '../../services/createServiceProxy.js'
12
13
  import type { Emitter } from '../Emitter.js'
13
14
 
14
15
  let instance: Emitter | undefined
@@ -23,17 +24,10 @@ export function getEmitter(): Emitter | undefined {
23
24
  return instance
24
25
  }
25
26
 
26
- const emitter: Emitter = new Proxy({} as Emitter, {
27
- get(_target, prop) {
28
- if (!instance) {
29
- throw new Error(
30
- '[ream:events] Emitter singleton accessed before EventsProvider.boot() ran. ' +
31
- 'Check that `@c9up/ream/events/provider` is listed in your reamrc.ts providers.',
32
- )
33
- }
34
- const value = Reflect.get(instance, prop, instance)
35
- return typeof value === 'function' ? value.bind(instance) : value
36
- },
37
- })
27
+ const emitter: Emitter = createServiceProxy<Emitter>(
28
+ () => instance,
29
+ '[ream:events] Emitter singleton accessed before EventsProvider.boot() ran. ' +
30
+ 'Check that `@c9up/ream/events/provider` is listed in your reamrc.ts providers.',
31
+ )
38
32
 
39
33
  export default emitter