@fuzdev/fuz_app 0.133.0 → 0.135.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 (112) hide show
  1. package/dist/actions/CLAUDE.md +91 -9
  2. package/dist/actions/register_action_ws.d.ts +23 -3
  3. package/dist/actions/register_action_ws.d.ts.map +1 -1
  4. package/dist/actions/register_action_ws.js +57 -37
  5. package/dist/actions/transports.d.ts +23 -0
  6. package/dist/actions/transports.d.ts.map +1 -1
  7. package/dist/actions/transports.js +23 -0
  8. package/dist/actions/transports_ws_backend.d.ts +132 -9
  9. package/dist/actions/transports_ws_backend.d.ts.map +1 -1
  10. package/dist/actions/transports_ws_backend.js +236 -33
  11. package/dist/actions/ws_endpoint_spec.d.ts +9 -0
  12. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
  13. package/dist/actions/ws_outbound_writer.d.ts +206 -0
  14. package/dist/actions/ws_outbound_writer.d.ts.map +1 -0
  15. package/dist/actions/ws_outbound_writer.js +431 -0
  16. package/dist/auth/CLAUDE.md +2 -2
  17. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  18. package/dist/auth/audit_log_routes.js +3 -1
  19. package/dist/auth/cell_actions.js +1 -1
  20. package/dist/auth/role_schema.d.ts +14 -3
  21. package/dist/auth/role_schema.d.ts.map +1 -1
  22. package/dist/auth/role_schema.js +14 -2
  23. package/dist/db/CLAUDE.md +10 -7
  24. package/dist/db/create_db.d.ts +8 -6
  25. package/dist/db/create_db.d.ts.map +1 -1
  26. package/dist/db/create_db.js +11 -10
  27. package/dist/db/db.d.ts +9 -7
  28. package/dist/db/db.d.ts.map +1 -1
  29. package/dist/db/db.js +5 -4
  30. package/dist/db/db_pglet.d.ts +43 -0
  31. package/dist/db/db_pglet.d.ts.map +1 -0
  32. package/dist/db/db_pglet.js +55 -0
  33. package/dist/db/migrate.d.ts +1 -1
  34. package/dist/db/migrate.js +2 -2
  35. package/dist/http/CLAUDE.md +2 -1
  36. package/dist/http/body_limit.d.ts +61 -0
  37. package/dist/http/body_limit.d.ts.map +1 -0
  38. package/dist/http/body_limit.js +87 -0
  39. package/dist/http/db_routes.js +2 -2
  40. package/dist/http/error_schemas.d.ts +2 -0
  41. package/dist/http/error_schemas.d.ts.map +1 -1
  42. package/dist/http/error_schemas.js +2 -0
  43. package/dist/realtime/outbound_budget.d.ts +91 -0
  44. package/dist/realtime/outbound_budget.d.ts.map +1 -0
  45. package/dist/realtime/outbound_budget.js +108 -0
  46. package/dist/realtime/sse.d.ts +36 -3
  47. package/dist/realtime/sse.d.ts.map +1 -1
  48. package/dist/realtime/sse.js +58 -6
  49. package/dist/realtime/sse_constants.d.ts +12 -0
  50. package/dist/realtime/sse_constants.d.ts.map +1 -1
  51. package/dist/realtime/sse_constants.js +12 -0
  52. package/dist/server/app_server.d.ts.map +1 -1
  53. package/dist/server/app_server.js +3 -6
  54. package/dist/testing/CLAUDE.md +87 -28
  55. package/dist/testing/admin_integration.js +1 -1
  56. package/dist/testing/app_server.d.ts +5 -5
  57. package/dist/testing/app_server.d.ts.map +1 -1
  58. package/dist/testing/app_server.js +21 -20
  59. package/dist/testing/audit_completeness.d.ts +1 -1
  60. package/dist/testing/audit_completeness.js +1 -1
  61. package/dist/testing/audit_drift_guard.d.ts +1 -1
  62. package/dist/testing/audit_drift_guard.js +1 -1
  63. package/dist/testing/cross_backend/body_size_smuggling.d.ts +9 -9
  64. package/dist/testing/cross_backend/body_size_smuggling.d.ts.map +1 -1
  65. package/dist/testing/cross_backend/body_size_smuggling.js +110 -60
  66. package/dist/testing/cross_backend/capabilities.d.ts +18 -15
  67. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  68. package/dist/testing/cross_backend/capabilities.js +1 -0
  69. package/dist/testing/cross_backend/default_backend_configs.d.ts +5 -5
  70. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  71. package/dist/testing/cross_backend/default_backend_configs.js +6 -6
  72. package/dist/testing/cross_backend/in_process_setup.d.ts +2 -2
  73. package/dist/testing/cross_backend/in_process_setup.js +3 -3
  74. package/dist/testing/cross_backend/role_grant_role_grammar.d.ts +6 -0
  75. package/dist/testing/cross_backend/role_grant_role_grammar.d.ts.map +1 -0
  76. package/dist/testing/cross_backend/role_grant_role_grammar.js +117 -0
  77. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts +2 -3
  78. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  79. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +2 -3
  80. package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +1 -1
  81. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  82. package/dist/testing/cross_backend/testing_backdoor.js +8 -4
  83. package/dist/testing/cross_backend/testing_reset_actions.d.ts +84 -4
  84. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  85. package/dist/testing/cross_backend/testing_reset_actions.js +102 -3
  86. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +2 -2
  87. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  88. package/dist/testing/cross_backend/ts_spine_backend_config.js +9 -7
  89. package/dist/testing/cross_backend/ws_outbound_overflow.d.ts +29 -0
  90. package/dist/testing/cross_backend/ws_outbound_overflow.d.ts.map +1 -0
  91. package/dist/testing/cross_backend/ws_outbound_overflow.js +211 -0
  92. package/dist/testing/db.d.ts +49 -60
  93. package/dist/testing/db.d.ts.map +1 -1
  94. package/dist/testing/db.js +104 -101
  95. package/dist/testing/integration.js +1 -1
  96. package/dist/testing/rate_limiting.d.ts +2 -1
  97. package/dist/testing/rate_limiting.d.ts.map +1 -1
  98. package/dist/testing/rate_limiting.js +2 -2
  99. package/dist/testing/schema_introspect.d.ts +10 -2
  100. package/dist/testing/schema_introspect.d.ts.map +1 -1
  101. package/dist/testing/schema_introspect.js +15 -7
  102. package/dist/testing/sse_round_trip.d.ts +1 -1
  103. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  104. package/dist/testing/sse_round_trip.js +2 -2
  105. package/dist/testing/stubs.js +1 -1
  106. package/dist/testing/transports/ws_transport.d.ts +4 -2
  107. package/dist/testing/transports/ws_transport.d.ts.map +1 -1
  108. package/dist/testing/transports/ws_transport.js +2 -1
  109. package/dist/testing/ws_round_trip.d.ts +32 -2
  110. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  111. package/dist/testing/ws_round_trip.js +26 -4
  112. package/package.json +6 -1
@@ -165,7 +165,7 @@ export const create_cell_actions = (deps) => {
165
165
  // **in-tx** (only when an authorizer is mounted), reusing the parent row
166
166
  // when the parent *is* the root, else one read by `root_id` — so the
167
167
  // authorizer stays pure + the read rides the handler's connection (no
168
- // separate-handle deadlock on the single-connection PGlite).
168
+ // separate-handle deadlock on the single-connection in-memory drivers).
169
169
  const parent_id = input.parent_id ?? null;
170
170
  let root_id = null;
171
171
  let root_data = null;
@@ -24,7 +24,17 @@ import { z } from 'zod';
24
24
  import { type CredentialTypeSchemaResult } from './credential_type_schema.js';
25
25
  import { type GrantPathSchemaResult } from './grant_path_schema.js';
26
26
  import type { ScopeKindSchemaResult } from './scope_kind_schema.js';
27
- /** Valid role name: lowercase letters and underscores, no leading/trailing underscore. */
27
+ /**
28
+ * The longest role name `RoleName` admits, in characters. A role string is
29
+ * caller-chosen text until it passes the grammar, and the grant verbs record
30
+ * the role they refuse in a failure audit row, so the grammar bounds its
31
+ * length too. Same value as the Rust spine's `ROLE_NAME_LENGTH_MAX`.
32
+ */
33
+ export declare const ROLE_NAME_LENGTH_MAX = 64;
34
+ /**
35
+ * Valid role name: lowercase letters and underscores, no leading/trailing
36
+ * underscore, at most `ROLE_NAME_LENGTH_MAX` characters.
37
+ */
28
38
  export declare const RoleName: z.ZodString;
29
39
  export type RoleName = z.infer<typeof RoleName>;
30
40
  /** System-level role. Requires daemon token (filesystem proof). Controls the keep. */
@@ -78,7 +88,7 @@ export declare const is_builtin_role: (name: string) => name is BuiltinRole;
78
88
  * `builtin_role_specs_by_name`.
79
89
  */
80
90
  export interface RoleSpec {
81
- /** Unique role name. Must match `RoleName` regex; collisions with builtins throw. */
91
+ /** Unique role name. Must match `RoleName` — its regex and length cap; collisions with builtins throw. */
82
92
  name: string;
83
93
  /** Admin-UI-facing copy describing the role's intent. */
84
94
  description?: string;
@@ -146,7 +156,8 @@ export interface RoleSchemaResult {
146
156
  *
147
157
  * Construction-time guards (all fire on misconfiguration):
148
158
  *
149
- * 1. Every `consumer_roles[i].name` matches `RoleName` regex.
159
+ * 1. Every `consumer_roles[i].name` matches `RoleName` — its regex and its
160
+ * `ROLE_NAME_LENGTH_MAX` length cap.
150
161
  * 2. No two consumer roles share a name.
151
162
  * 3. No consumer role collides with a builtin (`keeper` / `admin`).
152
163
  * 4. When `options.credential_types` is supplied, every entry in
@@ -1 +1 @@
1
- {"version":3,"file":"role_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAEN,KAAK,0BAA0B,EAC/B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAGN,KAAK,qBAAqB,EAC1B,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAEpE,0FAA0F;AAC1F,eAAO,MAAM,QAAQ,aAKnB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAIhD,sFAAsF;AACtF,eAAO,MAAM,WAAW,WAAW,CAAC;AAEpC,iEAAiE;AACjE,eAAO,MAAM,UAAU,UAAU,CAAC;AAElC,+CAA+C;AAC/C,eAAO,MAAM,aAAa,8BAAqC,CAAC;AAEhE,yCAAyC;AACzC,eAAO,MAAM,WAAW;;;EAAwB,CAAC;AACjD,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,KAAG,IAAI,IAAI,WACX,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,QAAQ;IACxB,qFAAqF;IACrF,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,yBAAyB,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAClD;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC/C;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACpC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAuBnE,CAAC;AAEH,iGAAiG;AACjG,MAAM,WAAW,uBAAuB;IACvC,uGAAuG;IACvG,gBAAgB,CAAC,EAAE,0BAA0B,CAAC;IAC9C,+FAA+F;IAC/F,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,oFAAoF;IACpF,WAAW,CAAC,EAAE,qBAAqB,CAAC;CACpC;AAED,oFAAoF;AACpF,MAAM,WAAW,gBAAgB;IAChC,0GAA0G;IAC1G,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACxB,yEAAyE;IACzE,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;CAC1C;AAqBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,eAAO,MAAM,kBAAkB,GAC9B,gBAAgB,aAAa,CAAC,QAAQ,CAAC,EACvC,UAAS,uBAA4B,KACnC,gBA2CF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAC/B,YAAY,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,EACzC,MAAM,MAAM,EACZ,YAAY,MAAM,KAChB,OAGF,CAAC;AAEF,kFAAkF;AAClF,eAAO,MAAM,0BAA0B,GACtC,YAAY,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,EACzC,YAAY,MAAM,KAChB,KAAK,CAAC,MAAM,CAMd,CAAC"}
1
+ {"version":3,"file":"role_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAEN,KAAK,0BAA0B,EAC/B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAGN,KAAK,qBAAqB,EAC1B,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAEpE;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC;;;GAGG;AACH,eAAO,MAAM,QAAQ,aAMnB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAIhD,sFAAsF;AACtF,eAAO,MAAM,WAAW,WAAW,CAAC;AAEpC,iEAAiE;AACjE,eAAO,MAAM,UAAU,UAAU,CAAC;AAElC,+CAA+C;AAC/C,eAAO,MAAM,aAAa,8BAAqC,CAAC;AAEhE,yCAAyC;AACzC,eAAO,MAAM,WAAW;;;EAAwB,CAAC;AACjD,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,KAAG,IAAI,IAAI,WACX,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,QAAQ;IACxB,0GAA0G;IAC1G,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,yBAAyB,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAClD;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC/C;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACpC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAuBnE,CAAC;AAEH,iGAAiG;AACjG,MAAM,WAAW,uBAAuB;IACvC,uGAAuG;IACvG,gBAAgB,CAAC,EAAE,0BAA0B,CAAC;IAC9C,+FAA+F;IAC/F,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,oFAAoF;IACpF,WAAW,CAAC,EAAE,qBAAqB,CAAC;CACpC;AAED,oFAAoF;AACpF,MAAM,WAAW,gBAAgB;IAChC,0GAA0G;IAC1G,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACxB,yEAAyE;IACzE,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;CAC1C;AAqBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,eAAO,MAAM,kBAAkB,GAC9B,gBAAgB,aAAa,CAAC,QAAQ,CAAC,EACvC,UAAS,uBAA4B,KACnC,gBA2CF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAC/B,YAAY,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,EACzC,MAAM,MAAM,EACZ,YAAY,MAAM,KAChB,OAGF,CAAC;AAEF,kFAAkF;AAClF,eAAO,MAAM,0BAA0B,GACtC,YAAY,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,EACzC,YAAY,MAAM,KAChB,KAAK,CAAC,MAAM,CAMd,CAAC"}
@@ -23,9 +23,20 @@
23
23
  import { z } from 'zod';
24
24
  import { CREDENTIAL_TYPE_DAEMON_TOKEN } from './credential_type_schema.js';
25
25
  import { GRANT_PATH_ADMIN, GRANT_PATH_BOOTSTRAP } from './grant_path_schema.js';
26
- /** Valid role name: lowercase letters and underscores, no leading/trailing underscore. */
26
+ /**
27
+ * The longest role name `RoleName` admits, in characters. A role string is
28
+ * caller-chosen text until it passes the grammar, and the grant verbs record
29
+ * the role they refuse in a failure audit row, so the grammar bounds its
30
+ * length too. Same value as the Rust spine's `ROLE_NAME_LENGTH_MAX`.
31
+ */
32
+ export const ROLE_NAME_LENGTH_MAX = 64;
33
+ /**
34
+ * Valid role name: lowercase letters and underscores, no leading/trailing
35
+ * underscore, at most `ROLE_NAME_LENGTH_MAX` characters.
36
+ */
27
37
  export const RoleName = z
28
38
  .string()
39
+ .max(ROLE_NAME_LENGTH_MAX, `Role names must be at most ${ROLE_NAME_LENGTH_MAX} characters`)
29
40
  .regex(/^[a-z][a-z_]*[a-z]$|^[a-z]$/, 'Role names must be lowercase letters and underscores (a-z_), no leading/trailing underscore');
30
41
  // Builtin roles — provided by fuz_app, always available.
31
42
  /** System-level role. Requires daemon token (filesystem proof). Controls the keep. */
@@ -99,7 +110,8 @@ const validate_registry_membership = (role_name, field, values, registry) => {
99
110
  *
100
111
  * Construction-time guards (all fire on misconfiguration):
101
112
  *
102
- * 1. Every `consumer_roles[i].name` matches `RoleName` regex.
113
+ * 1. Every `consumer_roles[i].name` matches `RoleName` — its regex and its
114
+ * `ROLE_NAME_LENGTH_MAX` length cap.
103
115
  * 2. No two consumer roles share a name.
104
116
  * 3. No consumer role collides with a builtin (`keeper` / `admin`).
105
117
  * 4. When `options.credential_types` is supplied, every entry in
package/dist/db/CLAUDE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # db/
2
2
 
3
- > Database layer: the `Db` abstraction over pg + PGlite, the migration
3
+ > Database layer: the `Db` abstraction over pg, PGlite and pglet, the migration
4
4
  > runner, and the cell content-primitive schema + queries.
5
5
 
6
6
  For server assembly, `QueryDeps`, and migration-runner contract see the
@@ -12,20 +12,23 @@ the code.
12
12
 
13
13
  ## Generic db infrastructure
14
14
 
15
- - `db.ts` — `Db` abstraction over pg + PGlite, `query` / `query_one` /
15
+ - `db.ts` — `Db` abstraction over pg, PGlite and pglet, `query` / `query_one` /
16
16
  `transaction`, `no_nested_transaction` guard.
17
17
  - `create_db.ts` — URL-based driver auto-detection (`postgres` /
18
- `pglite-file` / `pglite-memory`).
19
- - `db_pg.ts` / `db_pglite.ts` — driver adapters. `db_pg.ts` also exports
20
- `register_pg_type_parsers`, the shared int8→number coercion (so
18
+ `pglite-file` for `file://` / `pglet-memory` for `memory://`, over
19
+ `@fuzdev/pglet-wasm`). An in-memory PGlite is `create_pglite_db(new PGlite())`.
20
+ - `db_pg.ts` / `db_pglite.ts` / `db_pglet.ts` — driver adapters. `db_pg.ts` also
21
+ exports `register_pg_type_parsers`, the shared int8→number coercion (so
21
22
  `BIGSERIAL` columns read as JS numbers, matching PGlite) — called by both
22
23
  `create_db` and the test pg factory (`testing/db.ts`) so prod and tests
23
- read the same wire shape from one registration site.
24
+ read the same wire shape from one registration site. `db_pglet.ts`'s
25
+ `PGLET_COERCION` is pglet's half of the same convention, which every pglet
26
+ instance fuz_app creates is given.
24
27
  - `migrate.ts` — advisory-lock migration runner (`run_migrations`,
25
28
  `baseline`), `Migration` / `MigrationNamespace` / `MigrationError`.
26
29
  - `query_deps.ts` — `QueryDeps = {db}`, the first param to every `query_*`.
27
30
  - `assert_row.ts` — `assert_row(row, context)` for INSERT … RETURNING.
28
- - `pg_error.ts` — `pg_error_code` (SQLSTATE extractor for `pg`/PGlite errors), `is_pg_unique_violation` (Postgres `23505`).
31
+ - `pg_error.ts` — `pg_error_code` (SQLSTATE extractor for `pg`/PGlite/pglet errors), `is_pg_unique_violation` (Postgres `23505`).
29
32
  - `sql_identifier.ts` — `assert_valid_sql_identifier`.
30
33
  - `sql_columns.ts` — `columns_sql` / `qualify_columns` / `omit_columns`,
31
34
  the derivation helpers every `*_COLUMNS` projection const's variants
@@ -4,11 +4,12 @@
4
4
  * Selects the appropriate database driver based on `database_url`:
5
5
  * - `postgres://` or `postgresql://` — uses `pg` (PostgreSQL)
6
6
  * - `file://` — uses `@electric-sql/pglite` (file-based)
7
- * - `memory://` — uses `@electric-sql/pglite` (in-memory)
7
+ * - `memory://` — uses `@fuzdev/pglet-wasm` (in-memory)
8
8
  *
9
- * Both `pg` and `@electric-sql/pglite` are optional peer dependencies,
10
- * dynamically imported only when needed. For direct driver construction
11
- * without auto-detection, use `db/db_pg.ts` or `db/db_pglite.ts`.
9
+ * `pg`, `@electric-sql/pglite` and `@fuzdev/pglet-wasm` are optional peer
10
+ * dependencies, dynamically imported only when needed. For direct driver
11
+ * construction without auto-detection, use `db/db_pg.ts`, `db/db_pglite.ts`
12
+ * or `db/db_pglet.ts` — an in-memory PGlite is `create_pglite_db(new PGlite())`.
12
13
  *
13
14
  * @module
14
15
  */
@@ -28,12 +29,13 @@ export interface CreateDbResult {
28
29
  * know which driver is in use.
29
30
  *
30
31
  * For direct driver construction without URL routing, import
31
- * `create_pg_db` from `db/db_pg.ts` or `create_pglite_db` from `db/db_pglite.ts`.
32
+ * `create_pg_db` from `db/db_pg.ts`, `create_pglite_db` from `db/db_pglite.ts`
33
+ * or `create_pglet_db_driver` from `db/db_pglet.ts`.
32
34
  *
33
35
  * @param database_url - connection URL (`postgres://`, `postgresql://`, `file://`, or `memory://`)
34
36
  * @returns database instance, close callback, type, and display name
35
37
  * @throws Error if `database_url` uses an unsupported scheme. Driver
36
- * construction (`pg.Pool` or `PGlite`) may also throw on bad connection
38
+ * construction (`pg.Pool`, `PGlite` or pglet) may also throw on bad connection
37
39
  * parameters or missing peer-dependency packages.
38
40
  */
39
41
  export declare const create_db: (database_url: string) => Promise<CreateDbResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"create_db.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/create_db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAI1C,yCAAyC;AACzC,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,EAAE,CAAC;IACP,iFAAiF;IACjF,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS,GAAU,cAAc,MAAM,KAAG,OAAO,CAAC,cAAc,CAmC5E,CAAC"}
1
+ {"version":3,"file":"create_db.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/create_db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAK1C,yCAAyC;AACzC,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,EAAE,CAAC;IACP,iFAAiF;IACjF,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,SAAS,GAAU,cAAc,MAAM,KAAG,OAAO,CAAC,cAAc,CAiC5E,CAAC"}
@@ -4,16 +4,18 @@
4
4
  * Selects the appropriate database driver based on `database_url`:
5
5
  * - `postgres://` or `postgresql://` — uses `pg` (PostgreSQL)
6
6
  * - `file://` — uses `@electric-sql/pglite` (file-based)
7
- * - `memory://` — uses `@electric-sql/pglite` (in-memory)
7
+ * - `memory://` — uses `@fuzdev/pglet-wasm` (in-memory)
8
8
  *
9
- * Both `pg` and `@electric-sql/pglite` are optional peer dependencies,
10
- * dynamically imported only when needed. For direct driver construction
11
- * without auto-detection, use `db/db_pg.ts` or `db/db_pglite.ts`.
9
+ * `pg`, `@electric-sql/pglite` and `@fuzdev/pglet-wasm` are optional peer
10
+ * dependencies, dynamically imported only when needed. For direct driver
11
+ * construction without auto-detection, use `db/db_pg.ts`, `db/db_pglite.ts`
12
+ * or `db/db_pglet.ts` — an in-memory PGlite is `create_pglite_db(new PGlite())`.
12
13
  *
13
14
  * @module
14
15
  */
15
16
  import { create_pg_db, register_pg_type_parsers } from './db_pg.js';
16
17
  import { create_pglite_db } from './db_pglite.js';
18
+ import { create_pglet_db_driver, create_pglet_instance } from './db_pglet.js';
17
19
  /**
18
20
  * Create a database connection based on a URL.
19
21
  *
@@ -21,12 +23,13 @@ import { create_pglite_db } from './db_pglite.js';
21
23
  * know which driver is in use.
22
24
  *
23
25
  * For direct driver construction without URL routing, import
24
- * `create_pg_db` from `db/db_pg.ts` or `create_pglite_db` from `db/db_pglite.ts`.
26
+ * `create_pg_db` from `db/db_pg.ts`, `create_pglite_db` from `db/db_pglite.ts`
27
+ * or `create_pglet_db_driver` from `db/db_pglet.ts`.
25
28
  *
26
29
  * @param database_url - connection URL (`postgres://`, `postgresql://`, `file://`, or `memory://`)
27
30
  * @returns database instance, close callback, type, and display name
28
31
  * @throws Error if `database_url` uses an unsupported scheme. Driver
29
- * construction (`pg.Pool` or `PGlite`) may also throw on bad connection
32
+ * construction (`pg.Pool`, `PGlite` or pglet) may also throw on bad connection
30
33
  * parameters or missing peer-dependency packages.
31
34
  */
32
35
  export const create_db = async (database_url) => {
@@ -43,10 +46,8 @@ export const create_db = async (database_url) => {
43
46
  };
44
47
  }
45
48
  if (database_url.startsWith('memory://')) {
46
- const { PGlite } = await import('@electric-sql/pglite');
47
- const pglite = new PGlite(database_url);
48
- const { db, close } = create_pglite_db(pglite);
49
- return { db, close, db_type: 'pglite-memory', db_name: '(memory)' };
49
+ const { db, close } = create_pglet_db_driver(await create_pglet_instance());
50
+ return { db, close, db_type: 'pglet-memory', db_name: '(memory)' };
50
51
  }
51
52
  if (database_url.startsWith('file://')) {
52
53
  const path = new URL(database_url).pathname;
package/dist/db/db.d.ts CHANGED
@@ -2,17 +2,17 @@
2
2
  * Database wrapper with duck-typed interface.
3
3
  *
4
4
  * Accepts any client with a query(text, values) method.
5
- * Both `pg.Pool` and `@electric-sql/pglite` satisfy this interface.
5
+ * `pg.Pool`, `@electric-sql/pglite` and `@fuzdev/pglet-wasm` all satisfy this interface.
6
6
  *
7
7
  * Transaction safety is provided by an injected `transaction` callback —
8
- * the driver adapters (`db/db_pg.ts`, `db/db_pglite.ts`) supply the driver-appropriate
8
+ * the driver adapters (`db/db_pg.ts`, `db/db_pglite.ts`, `db/db_pglet.ts`) supply the driver-appropriate
9
9
  * implementation. Close is handled externally (returned alongside the `Db`
10
10
  * as `DbDriverResult`), not as a method on this class.
11
11
  *
12
12
  * @module
13
13
  */
14
14
  /**
15
- * Minimal interface that both pg and pglite satisfy.
15
+ * Minimal interface that pg, pglite and pglet satisfy.
16
16
  */
17
17
  export interface DbClient {
18
18
  query: <T = unknown>(text: string, values?: Array<unknown>) => Promise<{
@@ -25,6 +25,7 @@ export interface DbClient {
25
25
  * `transaction` is injected by `create_db` which knows the driver.
26
26
  * For `pg`: acquires a dedicated pool client per transaction.
27
27
  * For PGlite: delegates to `pglite.transaction()`.
28
+ * For pglet: delegates to the adapter's `transaction()`.
28
29
  */
29
30
  export interface DbDeps {
30
31
  client: DbClient;
@@ -33,7 +34,7 @@ export interface DbDeps {
33
34
  /**
34
35
  * Result of constructing a driver-specific `Db`.
35
36
  *
36
- * Returned by `create_pg_db()` and `create_pglite_db()`.
37
+ * Returned by `create_pg_db()`, `create_pglite_db()` and `create_pglet_db_driver()`.
37
38
  * The `close` callback is bound to the actual driver — callers
38
39
  * never need to know which driver is in use.
39
40
  */
@@ -43,7 +44,7 @@ export interface DbDriverResult {
43
44
  close: () => Promise<void>;
44
45
  }
45
46
  /** Database driver type. */
46
- export type DbType = 'postgres' | 'pglite-file' | 'pglite-memory';
47
+ export type DbType = 'postgres' | 'pglite-file' | 'pglet-memory';
47
48
  /**
48
49
  * Sentinel transaction function for transaction-scoped `Db` instances.
49
50
  *
@@ -56,8 +57,9 @@ export declare const no_nested_transaction: DbDeps['transaction'];
56
57
  /**
57
58
  * Database wrapper providing a consistent query and transaction interface.
58
59
  *
59
- * Construct via `create_pg_db()` from `db/db_pg.ts` or `create_pglite_db()` from
60
- * `db/db_pglite.ts` for proper transaction support, or via `create_db()` for
60
+ * Construct via `create_pg_db()` from `db/db_pg.ts`, `create_pglite_db()` from
61
+ * `db/db_pglite.ts` or `create_pglet_db_driver()` from `db/db_pglet.ts` for proper
62
+ * transaction support, or via `create_db()` for
61
63
  * URL-based auto-detection.
62
64
  *
63
65
  * @example
@@ -1 +1 @@
1
- {"version":3,"file":"db.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;GAEG;AACH,MAAM,WAAW,QAAQ;IACxB,KAAK,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;KAAE,CAAC,CAAC;CAC3F;AAED;;;;;;GAMG;AACH,MAAM,WAAW,MAAM;IACtB,MAAM,EAAE,QAAQ,CAAC;IACjB,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CAC9D;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,EAAE,CAAC;IACP,iFAAiF;IACjF,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED,4BAA4B;AAC5B,MAAM,MAAM,MAAM,GAAG,UAAU,GAAG,aAAa,GAAG,eAAe,CAAC;AAElE;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,aAAa,CAEvD,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,EAAE;;IACd,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;gBAId,OAAO,EAAE,MAAM;IAK3B;;;;;;OAMG;IACG,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAKxE;;;;;;OAMG;IACG,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAKjF;;;;;;;;;;;OAWG;IACG,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAG/D"}
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;GAEG;AACH,MAAM,WAAW,QAAQ;IACxB,KAAK,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;KAAE,CAAC,CAAC;CAC3F;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,MAAM;IACtB,MAAM,EAAE,QAAQ,CAAC;IACjB,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CAC9D;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,EAAE,CAAC;IACP,iFAAiF;IACjF,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED,4BAA4B;AAC5B,MAAM,MAAM,MAAM,GAAG,UAAU,GAAG,aAAa,GAAG,cAAc,CAAC;AAEjE;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,aAAa,CAEvD,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,EAAE;;IACd,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;gBAId,OAAO,EAAE,MAAM;IAK3B;;;;;;OAMG;IACG,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAKxE;;;;;;OAMG;IACG,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAKjF;;;;;;;;;;;OAWG;IACG,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAG/D"}
package/dist/db/db.js CHANGED
@@ -2,10 +2,10 @@
2
2
  * Database wrapper with duck-typed interface.
3
3
  *
4
4
  * Accepts any client with a query(text, values) method.
5
- * Both `pg.Pool` and `@electric-sql/pglite` satisfy this interface.
5
+ * `pg.Pool`, `@electric-sql/pglite` and `@fuzdev/pglet-wasm` all satisfy this interface.
6
6
  *
7
7
  * Transaction safety is provided by an injected `transaction` callback —
8
- * the driver adapters (`db/db_pg.ts`, `db/db_pglite.ts`) supply the driver-appropriate
8
+ * the driver adapters (`db/db_pg.ts`, `db/db_pglite.ts`, `db/db_pglet.ts`) supply the driver-appropriate
9
9
  * implementation. Close is handled externally (returned alongside the `Db`
10
10
  * as `DbDriverResult`), not as a method on this class.
11
11
  *
@@ -25,8 +25,9 @@ export const no_nested_transaction = () => {
25
25
  /**
26
26
  * Database wrapper providing a consistent query and transaction interface.
27
27
  *
28
- * Construct via `create_pg_db()` from `db/db_pg.ts` or `create_pglite_db()` from
29
- * `db/db_pglite.ts` for proper transaction support, or via `create_db()` for
28
+ * Construct via `create_pg_db()` from `db/db_pg.ts`, `create_pglite_db()` from
29
+ * `db/db_pglite.ts` or `create_pglet_db_driver()` from `db/db_pglet.ts` for proper
30
+ * transaction support, or via `create_db()` for
30
31
  * URL-based auto-detection.
31
32
  *
32
33
  * @example
@@ -0,0 +1,43 @@
1
+ /**
2
+ * pglet driver adapter for `Db`.
3
+ *
4
+ * Provides `create_pglet_instance()`, which loads `@fuzdev/pglet-wasm` on
5
+ * demand and creates an in-memory instance with `PGLET_COERCION`, the value
6
+ * coercion every fuz_app pglet instance is created with, and
7
+ * `create_pglet_db_driver()` to construct a `Db` backed by one. The package is
8
+ * imported dynamically, so it stays an optional peer.
9
+ *
10
+ * @module
11
+ */
12
+ import type { CoercionOptions, PgletDb } from '@fuzdev/pglet-wasm';
13
+ import { type DbDriverResult } from './db.js';
14
+ /**
15
+ * The value coercion fuz_app creates every pglet instance with.
16
+ *
17
+ * The adapter defaults int8 to `bigint`; fuz_app reads int8 as a JS number on
18
+ * every driver (`register_pg_type_parsers` for `pg`, PGlite's own default), so
19
+ * `AuditLogEvent.seq` and friends validate identically across drivers.
20
+ */
21
+ export declare const PGLET_COERCION: CoercionOptions;
22
+ /**
23
+ * Create an in-memory pglet instance with `PGLET_COERCION`.
24
+ *
25
+ * Loads `@fuzdev/pglet-wasm` on demand and awaits its `init()` — a no-op on the
26
+ * package's Node entry, where the wasm loads at import; the browser entry loads
27
+ * it here.
28
+ *
29
+ * @returns a fresh, empty instance; wrap it with `create_pglet_db_driver`
30
+ */
31
+ export declare const create_pglet_instance: () => Promise<PgletDb>;
32
+ /**
33
+ * Create a `Db` backed by a pglet instance.
34
+ *
35
+ Named `_driver` to stay clear of `@fuzdev/pglet-wasm`'s own `create_pglet_db`,
36
+ * which creates the instance this wraps. Create it with `create_pglet_instance`
37
+ * (or with `PGLET_COERCION`) so it reads values as the other drivers do.
38
+ *
39
+ * @param pglet - a `@fuzdev/pglet-wasm` instance
40
+ * @returns the `Db` instance and a `close` callback bound to `pglet.close()`
41
+ */
42
+ export declare const create_pglet_db_driver: (pglet: PgletDb) => DbDriverResult;
43
+ //# sourceMappingURL=db_pglet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db_pglet.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/db_pglet.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAEnE,OAAO,EAA6B,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAEzE;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,EAAE,eAAoC,CAAC;AAElE;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,QAAa,OAAO,CAAC,OAAO,CAI7D,CAAC;AAaF;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,GAAI,OAAO,OAAO,KAAG,cAGtD,CAAC"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * pglet driver adapter for `Db`.
3
+ *
4
+ * Provides `create_pglet_instance()`, which loads `@fuzdev/pglet-wasm` on
5
+ * demand and creates an in-memory instance with `PGLET_COERCION`, the value
6
+ * coercion every fuz_app pglet instance is created with, and
7
+ * `create_pglet_db_driver()` to construct a `Db` backed by one. The package is
8
+ * imported dynamically, so it stays an optional peer.
9
+ *
10
+ * @module
11
+ */
12
+ import { Db, no_nested_transaction } from './db.js';
13
+ /**
14
+ * The value coercion fuz_app creates every pglet instance with.
15
+ *
16
+ * The adapter defaults int8 to `bigint`; fuz_app reads int8 as a JS number on
17
+ * every driver (`register_pg_type_parsers` for `pg`, PGlite's own default), so
18
+ * `AuditLogEvent.seq` and friends validate identically across drivers.
19
+ */
20
+ export const PGLET_COERCION = { int8: 'number' };
21
+ /**
22
+ * Create an in-memory pglet instance with `PGLET_COERCION`.
23
+ *
24
+ * Loads `@fuzdev/pglet-wasm` on demand and awaits its `init()` — a no-op on the
25
+ * package's Node entry, where the wasm loads at import; the browser entry loads
26
+ * it here.
27
+ *
28
+ * @returns a fresh, empty instance; wrap it with `create_pglet_db_driver`
29
+ */
30
+ export const create_pglet_instance = async () => {
31
+ const pglet_wasm = await import('@fuzdev/pglet-wasm');
32
+ await pglet_wasm.init();
33
+ return pglet_wasm.create_pglet_db({ coercion: PGLET_COERCION });
34
+ };
35
+ /**
36
+ * Create a transaction implementation for pglet.
37
+ *
38
+ * Delegates to the adapter's single-flight `transaction()` (`BEGIN` / `COMMIT`,
39
+ * `ROLLBACK` on throw).
40
+ */
41
+ const create_pglet_transaction = (pglet) => async (fn) => pglet.transaction(async (tx) => fn(new Db({ client: tx, transaction: no_nested_transaction })));
42
+ /**
43
+ * Create a `Db` backed by a pglet instance.
44
+ *
45
+ Named `_driver` to stay clear of `@fuzdev/pglet-wasm`'s own `create_pglet_db`,
46
+ * which creates the instance this wraps. Create it with `create_pglet_instance`
47
+ * (or with `PGLET_COERCION`) so it reads values as the other drivers do.
48
+ *
49
+ * @param pglet - a `@fuzdev/pglet-wasm` instance
50
+ * @returns the `Db` instance and a `close` callback bound to `pglet.close()`
51
+ */
52
+ export const create_pglet_db_driver = (pglet) => ({
53
+ db: new Db({ client: pglet, transaction: create_pglet_transaction(pglet) }),
54
+ close: () => pglet.close()
55
+ });
@@ -50,7 +50,7 @@
50
50
  * tx atomicity + the `(namespace, name)` PK on `schema_version`: the
51
51
  * loser's INSERT hits a PK violation, the chain-tx rolls back, and the
52
52
  * next boot reads the committed state and proceeds cleanly. Environments
53
- * without `pg_advisory_lock` (some PGlite versions) silently fall through.
53
+ * without `pg_advisory_lock` (pglet) silently fall through.
54
54
  *
55
55
  * @module
56
56
  */
@@ -50,7 +50,7 @@
50
50
  * tx atomicity + the `(namespace, name)` PK on `schema_version`: the
51
51
  * loser's INSERT hits a PK violation, the chain-tx rolls back, and the
52
52
  * next boot reads the committed state and proceeds cleanly. Environments
53
- * without `pg_advisory_lock` (some PGlite versions) silently fall through.
53
+ * without `pg_advisory_lock` (pglet) silently fall through.
54
54
  *
55
55
  * @module
56
56
  */
@@ -99,7 +99,7 @@ const namespace_lock_key = (namespace) => {
99
99
  * Run `fn` with the namespace's advisory lock held.
100
100
  *
101
101
  * Acquire / release are best-effort and silently fall through in
102
- * environments without `pg_advisory_lock` (some PGlite versions). The
102
+ * environments without `pg_advisory_lock` (pglet). The
103
103
  * `finally` ensures release on any throw from `fn`.
104
104
  */
105
105
  const with_namespace_lock = async (db, namespace, fn) => {
@@ -19,6 +19,7 @@ effects, see ../../../docs/architecture.md.
19
19
  - `http/error_schemas.ts` — `ERROR_*` constants, standard error shapes, `derive_error_schemas`.
20
20
  - `http/schema_helpers.ts` — shared Zod introspection (null/void/strict/surface/merge/middleware-applies) + `to_void_params` / `to_input_params` / `safe_parse_dropping_unknown_keys`.
21
21
  - `http/middleware_spec.ts` — `MiddlewareSpec` interface.
22
+ - `http/body_limit.ts` — `create_body_limit_middleware(max_size)`: the `413` body cap `create_app_server` mounts. Refuses on `Content-Length` without reading `c.req.raw.body` and answers `Connection: close` — `413` past the cap, `400` `ERROR_INVALID_CONTENT_LENGTH` when the value is not a plain run of ASCII digits (a length the cap cannot read, which Hono's `bodyLimit` would pass uncounted) — else falls through to Hono's streaming `bodyLimit` (no `Content-Length`, or one beside `Transfer-Encoding`; that `413` carries no `Connection: close`). Mounted with `app.use`, not as a `MiddlewareSpec`, so neither status appears in a route's merged error schemas. After the header `413` Node closes at once (the header is what makes `@hono/node-server` do so), Deno closes once the declared body has arrived, and Bun keeps the connection and serves the next request (../../../docs/security.md §Body Size Limiting).
22
23
  - `http/surface.ts` — `AppSurface`, `AppSurfaceSpec`, `generate_app_surface`, diagnostics.
23
24
  - `http/surface_query.ts` — pure filters/groupings over `AppSurface`.
24
25
  - `http/proxy.ts` — trusted-proxy middleware, CIDR parsing, rightmost-first XFF resolution. Pulls `hono/utils/ipaddr` for CIDR matching.
@@ -207,7 +208,7 @@ confirmation.
207
208
  ### `ERROR_*` constants by category
208
209
 
209
210
  - **Validation**: `ERROR_INVALID_REQUEST_BODY`, `ERROR_INVALID_JSON_BODY`, `ERROR_INVALID_ROUTE_PARAMS`, `ERROR_INVALID_QUERY_PARAMS`
210
- - **Auth**: `ERROR_AUTHENTICATION_REQUIRED`, `ERROR_INSUFFICIENT_PERMISSIONS`, `ERROR_CREDENTIAL_TYPE_REQUIRED`, `ERROR_RATE_LIMIT_EXCEEDED`, `ERROR_INVALID_CREDENTIALS`, `ERROR_PAYLOAD_TOO_LARGE`
211
+ - **Auth**: `ERROR_AUTHENTICATION_REQUIRED`, `ERROR_INSUFFICIENT_PERMISSIONS`, `ERROR_CREDENTIAL_TYPE_REQUIRED`, `ERROR_RATE_LIMIT_EXCEEDED`, `ERROR_INVALID_CREDENTIALS`, `ERROR_PAYLOAD_TOO_LARGE`, `ERROR_INVALID_CONTENT_LENGTH`
211
212
  - **Origin + bearer**: `ERROR_FORBIDDEN_ORIGIN`, `ERROR_INVALID_TOKEN`, `ERROR_ACCOUNT_NOT_FOUND`
212
213
  - **Keeper/daemon**: `ERROR_KEEPER_ACCOUNT_NOT_FOUND`
213
214
  - **Bootstrap**: `ERROR_ALREADY_BOOTSTRAPPED`, `ERROR_TOKEN_FILE_MISSING`
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Request body size limit — rejects an oversized body with `413`, and a
3
+ * `Content-Length` it cannot read with `400`, before auth and validation.
4
+ *
5
+ * Wraps Hono's `bodyLimit` with a header-first check:
6
+ *
7
+ * - A `Content-Length` that is not a plain run of ASCII digits (`abc`, `-1`,
8
+ * `+5`, `1e9`, `0x1F4`, empty, or a combined `5, 500`) is refused with `400`
9
+ * `{error: 'invalid_content_length'}`. A cap cannot be applied to a length
10
+ * that does not parse, and `bodyLimit` passes such a request on uncounted.
11
+ * The runtimes' HTTP parsers answer their own bodiless `400` to most of
12
+ * these before the application sees them, so this branch is defense in
13
+ * depth where they do and the refusal where they do not: Node's refuses
14
+ * them all; Deno hands over a repeated equal header as one combined value
15
+ * (`50, 50`); Bun hands over an empty value and a repeated header, equal or
16
+ * differing (`5, 50`), framing the body on the first (observed on Node
17
+ * 24.14.1, Deno 2.8.3, Bun 1.3.11). The Rust spine's parser (hyper) refuses
18
+ * a malformed value and differing repeats, and accepts an equal repeat as
19
+ * the one value.
20
+ * - A request whose `Content-Length` exceeds the cap is refused with `413`
21
+ * `{error: 'payload_too_large'}`, whatever its method. A digit run of any
22
+ * length compares as a number, so one too long to represent exactly is over
23
+ * the cap, never unreadable.
24
+ * - Both refusals answer on the header alone — without touching
25
+ * `c.req.raw.body` — and carry `Connection: close`.
26
+ * - A request with a well-formed `Content-Length` beside `Transfer-Encoding`,
27
+ * or with no `Content-Length`, falls through to `bodyLimit`'s streaming
28
+ * count: with `Transfer-Encoding` the declared length does not frame the
29
+ * body, so the bytes that arrive are what is measured. That `413` is sent
30
+ * once more than the cap has been read and carries no `Connection: close`.
31
+ * The Node, Deno and Bun parsers refuse a request carrying both headers
32
+ * with their own `400`; hyper accepts one and frames its body as chunked.
33
+ * - A well-formed `Content-Length` within the cap is trusted to frame the
34
+ * body, as the runtime's HTTP parser enforces.
35
+ *
36
+ * What the connection does after the header `413` is the runtime's
37
+ * (`docs/security.md` §"Body Size Limiting"):
38
+ *
39
+ * - **Node** (`@hono/node-server`) closes at once, the body unread. The
40
+ * `Connection: close` header is what does it: without one the adapter
41
+ * holds the connection for a drain window and then closes or, the body
42
+ * untouched and arriving within the window, reads it off and serves the
43
+ * next request.
44
+ * - **Deno** closes once the whole declared body has arrived and holds the
45
+ * connection open while a client withholds it; a request pipelined behind
46
+ * the body is not served. Neither the header nor what the handler does with
47
+ * the body changes that (observed on Deno 2.8.3).
48
+ * - **Bun** keeps the connection, skips the declared body, and serves the
49
+ * next request on it (observed on Bun 1.3.11).
50
+ *
51
+ * @module
52
+ */
53
+ import type { MiddlewareHandler } from 'hono';
54
+ /**
55
+ * Create the body size limit middleware.
56
+ *
57
+ * @param max_size - the largest accepted body, in bytes
58
+ * @returns middleware answering `413` `{error: 'payload_too_large'}` past the cap and `400` `{error: 'invalid_content_length'}` to a `Content-Length` that is not a run of digits
59
+ */
60
+ export declare const create_body_limit_middleware: (max_size: number) => MiddlewareHandler;
61
+ //# sourceMappingURL=body_limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"body_limit.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/body_limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAQ9C;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,GAAI,UAAU,MAAM,KAAG,iBAwB/D,CAAC"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Request body size limit — rejects an oversized body with `413`, and a
3
+ * `Content-Length` it cannot read with `400`, before auth and validation.
4
+ *
5
+ * Wraps Hono's `bodyLimit` with a header-first check:
6
+ *
7
+ * - A `Content-Length` that is not a plain run of ASCII digits (`abc`, `-1`,
8
+ * `+5`, `1e9`, `0x1F4`, empty, or a combined `5, 500`) is refused with `400`
9
+ * `{error: 'invalid_content_length'}`. A cap cannot be applied to a length
10
+ * that does not parse, and `bodyLimit` passes such a request on uncounted.
11
+ * The runtimes' HTTP parsers answer their own bodiless `400` to most of
12
+ * these before the application sees them, so this branch is defense in
13
+ * depth where they do and the refusal where they do not: Node's refuses
14
+ * them all; Deno hands over a repeated equal header as one combined value
15
+ * (`50, 50`); Bun hands over an empty value and a repeated header, equal or
16
+ * differing (`5, 50`), framing the body on the first (observed on Node
17
+ * 24.14.1, Deno 2.8.3, Bun 1.3.11). The Rust spine's parser (hyper) refuses
18
+ * a malformed value and differing repeats, and accepts an equal repeat as
19
+ * the one value.
20
+ * - A request whose `Content-Length` exceeds the cap is refused with `413`
21
+ * `{error: 'payload_too_large'}`, whatever its method. A digit run of any
22
+ * length compares as a number, so one too long to represent exactly is over
23
+ * the cap, never unreadable.
24
+ * - Both refusals answer on the header alone — without touching
25
+ * `c.req.raw.body` — and carry `Connection: close`.
26
+ * - A request with a well-formed `Content-Length` beside `Transfer-Encoding`,
27
+ * or with no `Content-Length`, falls through to `bodyLimit`'s streaming
28
+ * count: with `Transfer-Encoding` the declared length does not frame the
29
+ * body, so the bytes that arrive are what is measured. That `413` is sent
30
+ * once more than the cap has been read and carries no `Connection: close`.
31
+ * The Node, Deno and Bun parsers refuse a request carrying both headers
32
+ * with their own `400`; hyper accepts one and frames its body as chunked.
33
+ * - A well-formed `Content-Length` within the cap is trusted to frame the
34
+ * body, as the runtime's HTTP parser enforces.
35
+ *
36
+ * What the connection does after the header `413` is the runtime's
37
+ * (`docs/security.md` §"Body Size Limiting"):
38
+ *
39
+ * - **Node** (`@hono/node-server`) closes at once, the body unread. The
40
+ * `Connection: close` header is what does it: without one the adapter
41
+ * holds the connection for a drain window and then closes or, the body
42
+ * untouched and arriving within the window, reads it off and serves the
43
+ * next request.
44
+ * - **Deno** closes once the whole declared body has arrived and holds the
45
+ * connection open while a client withholds it; a request pipelined behind
46
+ * the body is not served. Neither the header nor what the handler does with
47
+ * the body changes that (observed on Deno 2.8.3).
48
+ * - **Bun** keeps the connection, skips the declared body, and serves the
49
+ * next request on it (observed on Bun 1.3.11).
50
+ *
51
+ * @module
52
+ */
53
+ import { bodyLimit } from 'hono/body-limit';
54
+ import { ERROR_INVALID_CONTENT_LENGTH, ERROR_PAYLOAD_TOO_LARGE } from './error_schemas.js';
55
+ /** A well-formed `Content-Length` value: one or more ASCII digits and nothing else. */
56
+ const CONTENT_LENGTH_DIGITS = /^[0-9]+$/;
57
+ /**
58
+ * Create the body size limit middleware.
59
+ *
60
+ * @param max_size - the largest accepted body, in bytes
61
+ * @returns middleware answering `413` `{error: 'payload_too_large'}` past the cap and `400` `{error: 'invalid_content_length'}` to a `Content-Length` that is not a run of digits
62
+ */
63
+ export const create_body_limit_middleware = (max_size) => {
64
+ const streaming_limit = bodyLimit({
65
+ maxSize: max_size,
66
+ onError: (c) => c.json({ error: ERROR_PAYLOAD_TOO_LARGE }, 413)
67
+ });
68
+ return async (c, next) => {
69
+ const content_length = c.req.header('content-length');
70
+ if (content_length !== undefined) {
71
+ if (!CONTENT_LENGTH_DIGITS.test(content_length)) {
72
+ // a length that does not parse cannot be capped, and `bodyLimit`
73
+ // would pass the request on without counting it
74
+ return c.json({ error: ERROR_INVALID_CONTENT_LENGTH }, 400, { connection: 'close' });
75
+ }
76
+ // `Number` of a digit run is never `NaN`; past exact precision it is
77
+ // still a huge number (or `Infinity`), so it compares as over the cap
78
+ if (!c.req.header('transfer-encoding') && Number(content_length) > max_size) {
79
+ // refused on the header alone, the body untouched; `Connection: close`
80
+ // makes `@hono/node-server` close at once instead of holding the
81
+ // connection or serving the request behind the body
82
+ return c.json({ error: ERROR_PAYLOAD_TOO_LARGE }, 413, { connection: 'close' });
83
+ }
84
+ }
85
+ return streaming_limit(c, next);
86
+ };
87
+ };
@@ -425,8 +425,8 @@ export const create_db_route_specs = (deps, options) => {
425
425
  }
426
426
  // 23503 foreign_key_violation / 23001 restrict_violation — the
427
427
  // same referencing-row refusal: PG 18+ raises 23001 for `ON
428
- // DELETE RESTRICT` constraints where PG ≤17 (PGlite today)
429
- // raises 23503 for both.
428
+ // DELETE RESTRICT` constraints where PG ≤17 raises 23503 for
429
+ // both.
430
430
  if (code === '23503' || code === '23001') {
431
431
  const pg_err = err;
432
432
  log?.warn('Foreign key violation:', pg_err.detail, pg_err.constraint);