honertia 0.1.40 → 0.1.42

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/README.md +147 -12
  2. package/dist/__tmp_db_safety_probe.d.ts +2 -0
  3. package/dist/__tmp_db_safety_probe.d.ts.map +1 -0
  4. package/dist/__tmp_db_safety_probe.js +13 -0
  5. package/dist/__tmp_scoped_astrusted_probe.d.ts +2 -0
  6. package/dist/__tmp_scoped_astrusted_probe.d.ts.map +1 -0
  7. package/dist/__tmp_scoped_astrusted_probe.js +10 -0
  8. package/dist/__tmp_scoped_derived_probe.d.ts +2 -0
  9. package/dist/__tmp_scoped_derived_probe.d.ts.map +1 -0
  10. package/dist/__tmp_scoped_derived_probe.js +12 -0
  11. package/dist/__tmp_scoped_mutation_probe.d.ts +2 -0
  12. package/dist/__tmp_scoped_mutation_probe.d.ts.map +1 -0
  13. package/dist/__tmp_scoped_mutation_probe.js +15 -0
  14. package/dist/cache.d.ts +3 -1
  15. package/dist/cache.d.ts.map +1 -1
  16. package/dist/cache.js +14 -3
  17. package/dist/cli/bin.d.ts +8 -0
  18. package/dist/cli/bin.d.ts.map +1 -0
  19. package/dist/cli/bin.js +150 -0
  20. package/dist/cli/db.d.ts.map +1 -1
  21. package/dist/cli/db.js +83 -18
  22. package/dist/cli/feature.d.ts +1 -0
  23. package/dist/cli/feature.d.ts.map +1 -1
  24. package/dist/cli/feature.js +76 -11
  25. package/dist/cli/generate.d.ts.map +1 -1
  26. package/dist/cli/generate.js +124 -22
  27. package/dist/cli/index.d.ts +6 -2
  28. package/dist/cli/index.d.ts.map +1 -1
  29. package/dist/cli/index.js +16 -4
  30. package/dist/cli/inline-tests.d.ts.map +1 -1
  31. package/dist/cli/inline-tests.js +4 -0
  32. package/dist/cli/openapi.d.ts +2 -1
  33. package/dist/cli/openapi.d.ts.map +1 -1
  34. package/dist/cli/openapi.js +68 -3
  35. package/dist/effect/action.d.ts +92 -3
  36. package/dist/effect/action.d.ts.map +1 -1
  37. package/dist/effect/action.js +38 -32
  38. package/dist/effect/auth.d.ts +3 -1
  39. package/dist/effect/auth.d.ts.map +1 -1
  40. package/dist/effect/auth.js +17 -3
  41. package/dist/effect/bridge.d.ts +5 -0
  42. package/dist/effect/bridge.d.ts.map +1 -1
  43. package/dist/effect/bridge.js +5 -2
  44. package/dist/effect/error-catalog.d.ts +1 -0
  45. package/dist/effect/error-catalog.d.ts.map +1 -1
  46. package/dist/effect/error-catalog.js +17 -1
  47. package/dist/effect/index.d.ts +2 -2
  48. package/dist/effect/index.d.ts.map +1 -1
  49. package/dist/effect/index.js +2 -2
  50. package/dist/effect/routing.d.ts.map +1 -1
  51. package/dist/effect/routing.js +14 -14
  52. package/dist/effect/services.d.ts +3 -0
  53. package/dist/effect/services.d.ts.map +1 -1
  54. package/dist/effect/validation.d.ts +53 -1
  55. package/dist/effect/validation.d.ts.map +1 -1
  56. package/dist/effect/validation.js +161 -27
  57. package/dist/schema.d.ts +1 -1
  58. package/dist/schema.d.ts.map +1 -1
  59. package/dist/schema.js +1 -1
  60. package/dist/setup.d.ts.map +1 -1
  61. package/dist/setup.js +3 -2
  62. package/package.json +4 -1
package/README.md CHANGED
@@ -4,6 +4,14 @@ Inertia.js adapter for Hono with Effect.ts. Server-driven app with SPA behavior.
4
4
 
5
5
  ## CLI Commands
6
6
 
7
+ `honertia` is shipped as a package binary. You can run commands with:
8
+
9
+ ```bash
10
+ bunx honertia <command>
11
+ # or
12
+ npx honertia <command>
13
+ ```
14
+
7
15
  ### Generate Action
8
16
 
9
17
  ```bash
@@ -117,8 +125,8 @@ honertia db status # Show migration status
117
125
  honertia db status --json # JSON output
118
126
  honertia db migrate # Run pending migrations
119
127
  honertia db migrate --preview # Preview SQL without executing
120
- honertia db rollback # Rollback last migration
121
- honertia db rollback --preview # Preview rollback SQL
128
+ honertia db rollback --preview # Preview rollback SQL for latest applied migration
129
+ # Non-preview rollback execution is manual (run preview SQL yourself)
122
130
  honertia db generate add_email # Generate new migration
123
131
  ```
124
132
 
@@ -979,6 +987,93 @@ export const destroyProject = action(
979
987
  )
980
988
  ```
981
989
 
990
+ ### Real-World Transaction (Order Checkout)
991
+
992
+ Pass a validated/trusted transaction object as the second argument to
993
+ `dbMutation`/`dbTransaction` when you want strict write scoping.
994
+ Inside that callback, write methods only accept values that come from
995
+ that scoped object.
996
+
997
+ ```typescript
998
+ import { Effect, Schema as S } from 'effect'
999
+ import {
1000
+ action,
1001
+ authorize,
1002
+ validate,
1003
+ validateRequest,
1004
+ DatabaseService,
1005
+ dbTransaction,
1006
+ mergeMutationInput,
1007
+ redirect,
1008
+ requiredString,
1009
+ } from 'honertia/effect'
1010
+ import { eq } from 'drizzle-orm'
1011
+ import { orders, orderItems, inventory } from '~/db/schema'
1012
+
1013
+ const CheckoutSchema = S.Struct({
1014
+ productId: requiredString,
1015
+ quantity: S.NumberFromString,
1016
+ })
1017
+
1018
+ const CheckoutTransactionSchema = S.Struct({
1019
+ createOrder: S.Struct({
1020
+ userId: S.String,
1021
+ status: S.Literal('pending'),
1022
+ }),
1023
+ createItem: S.Struct({
1024
+ productId: S.String,
1025
+ quantity: S.Number,
1026
+ // Reserve transaction-derived fields you plan to fill later.
1027
+ orderId: S.optional(S.String),
1028
+ }),
1029
+ updateInventory: S.Struct({
1030
+ reserved: S.Number,
1031
+ }),
1032
+ })
1033
+
1034
+ export const checkout = action(
1035
+ Effect.gen(function* () {
1036
+ const auth = yield* authorize()
1037
+ const input = yield* validateRequest(CheckoutSchema, {
1038
+ errorComponent: 'Checkout/Show',
1039
+ })
1040
+ const db = yield* DatabaseService
1041
+
1042
+ const txInput = yield* validate(CheckoutTransactionSchema, {
1043
+ createOrder: {
1044
+ userId: auth.user.id,
1045
+ status: 'pending',
1046
+ },
1047
+ createItem: {
1048
+ productId: input.productId,
1049
+ quantity: input.quantity,
1050
+ },
1051
+ updateInventory: {
1052
+ reserved: input.quantity,
1053
+ },
1054
+ })
1055
+ // For untyped/unknown payloads (e.g. external JSON), use validateUnknown(schema, raw)
1056
+
1057
+ const order = yield* dbTransaction(db, txInput, async (tx, scoped) => {
1058
+ const [created] = await tx.insert(orders).values(scoped.createOrder).returning()
1059
+
1060
+ const itemInsert = mergeMutationInput(scoped.createItem, {
1061
+ orderId: created.id,
1062
+ })
1063
+ await tx.insert(orderItems).values(itemInsert)
1064
+
1065
+ await tx.update(inventory)
1066
+ .set(scoped.updateInventory)
1067
+ .where(eq(inventory.productId, scoped.createItem.productId))
1068
+
1069
+ return created
1070
+ })
1071
+
1072
+ return yield* redirect(`/orders/${order.id}`)
1073
+ })
1074
+ )
1075
+ ```
1076
+
982
1077
  ### API Endpoint (JSON Response)
983
1078
 
984
1079
  ```typescript
@@ -1200,6 +1295,30 @@ const input = yield* validateRequest(CreateEventSchema, {
1200
1295
  })
1201
1296
  ```
1202
1297
 
1298
+ ### Request Input Sources
1299
+
1300
+ `validateRequest(schema)` uses the legacy merge order by default:
1301
+ `params -> query -> body` (later sources win).
1302
+
1303
+ Use `options.request` to switch behavior:
1304
+
1305
+ ```typescript
1306
+ // Laravel-style input (query + body; route params excluded)
1307
+ const input = yield* validateRequest(Schema, {
1308
+ request: 'laravel',
1309
+ })
1310
+
1311
+ // Custom merge order + conflict policy
1312
+ const input = yield* validateRequest(Schema, {
1313
+ request: {
1314
+ order: ['params', 'query', 'body'],
1315
+ onConflict: 'error', // 'last-wins' | 'first-wins' | 'error'
1316
+ },
1317
+ })
1318
+ ```
1319
+
1320
+ If both `profile` and `order` are provided, `order` takes precedence.
1321
+
1203
1322
  ---
1204
1323
 
1205
1324
  ## Route Model Binding Examples
@@ -1819,7 +1938,8 @@ const handler = action(
1819
1938
  yield* cache.delete('my-key')
1820
1939
 
1821
1940
  // List keys by prefix
1822
- const keys = yield* cache.list({ prefix: 'user:' })
1941
+ const page = yield* cache.list({ prefix: 'user:' })
1942
+ const keys = page.keys
1823
1943
  })
1824
1944
  )
1825
1945
  ```
@@ -1857,7 +1977,10 @@ const createRedisCacheClient = (redisUrl: string): CacheClient => {
1857
1977
  Effect.tryPromise({
1858
1978
  try: async () => {
1859
1979
  const keys = await client.keys(options?.prefix ? `${options.prefix}*` : '*')
1860
- return { keys: keys.map((name) => ({ name })) }
1980
+ return {
1981
+ keys: keys.map((name) => ({ name })),
1982
+ list_complete: true,
1983
+ }
1861
1984
  },
1862
1985
  catch: (e) => new CacheClientError('Redis keys failed', e),
1863
1986
  }),
@@ -1907,6 +2030,7 @@ const makeTestCache = (): Layer.Layer<CacheService> => {
1907
2030
  keys: [...store.keys()]
1908
2031
  .filter((k) => !options?.prefix || k.startsWith(options.prefix))
1909
2032
  .map((name) => ({ name })),
2033
+ list_complete: true,
1910
2034
  })),
1911
2035
  }
1912
2036
 
@@ -1988,7 +2112,7 @@ Recommended cache key patterns:
1988
2112
 
1989
2113
  ### Stale-While-Revalidate (SWR)
1990
2114
 
1991
- The cache supports the stale-while-revalidate pattern for improved latency and resilience. When enabled, stale values are returned immediately while a background refresh is triggered.
2115
+ The cache supports the stale-while-revalidate pattern for improved latency and resilience. When enabled, stale values are returned immediately while a background refresh is triggered. In environments without `ExecutionContext`, stale entries are recomputed synchronously instead.
1992
2116
 
1993
2117
  ```typescript
1994
2118
  import { Effect, Duration } from 'effect'
@@ -2122,6 +2246,16 @@ export const dashboard = action(
2122
2246
  **Common use cases:**
2123
2247
 
2124
2248
  ```typescript
2249
+ import { Effect } from 'effect'
2250
+ import {
2251
+ ExecutionContextService,
2252
+ authorize,
2253
+ DatabaseService,
2254
+ dbMutation,
2255
+ asTrusted,
2256
+ BindingsService,
2257
+ } from 'honertia/effect'
2258
+
2125
2259
  // Audit logging
2126
2260
  const auditLog = (action: string, details: Record<string, unknown>) =>
2127
2261
  Effect.gen(function* () {
@@ -2130,14 +2264,14 @@ const auditLog = (action: string, details: Record<string, unknown>) =>
2130
2264
  const db = yield* DatabaseService
2131
2265
 
2132
2266
  yield* ctx.runInBackground(
2133
- Effect.tryPromise(() =>
2134
- db.insert(auditLogs).values({
2135
- userId: user.id,
2267
+ dbMutation(db, async (tx) => {
2268
+ await tx.insert(auditLogs).values(asTrusted({
2269
+ userId: user.user.id,
2136
2270
  action,
2137
2271
  details,
2138
2272
  timestamp: new Date(),
2139
- })
2140
- )
2273
+ }))
2274
+ })
2141
2275
  )
2142
2276
  })
2143
2277
 
@@ -2185,7 +2319,7 @@ const maybeNotifySlack = (message: string) =>
2185
2319
  **Important notes:**
2186
2320
  - Background tasks run after the response is sent to the user
2187
2321
  - Errors in background tasks are logged but don't crash the worker
2188
- - In non-Worker environments (tests, local dev), `isAvailable` is `false` and tasks are skipped
2322
+ - In non-Worker environments (tests, local dev), `isAvailable` is `false` and stale entries are recomputed synchronously
2189
2323
  - Use `catchAll` to handle errors gracefully in background tasks
2190
2324
 
2191
2325
  ### Cache Key Versioning
@@ -2276,6 +2410,7 @@ import {
2276
2410
  authorize,
2277
2411
  cache,
2278
2412
  cacheInvalidate,
2413
+ asTrusted,
2279
2414
  render,
2280
2415
  redirect,
2281
2416
  DatabaseService,
@@ -2331,7 +2466,7 @@ export const updateProfile = action(
2331
2466
 
2332
2467
  yield* dbMutation(db, async (db) => {
2333
2468
  await db.update(users)
2334
- .set({ name: input.name, bio: input.bio })
2469
+ .set(asTrusted({ name: input.name, bio: input.bio }))
2335
2470
  .where(eq(users.id, auth.user.id))
2336
2471
  })
2337
2472
 
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=__tmp_db_safety_probe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"__tmp_db_safety_probe.d.ts","sourceRoot":"","sources":["../src/__tmp_db_safety_probe.ts"],"names":[],"mappings":""}
@@ -0,0 +1,13 @@
1
+ import { asValidated } from './effect/validation.js';
2
+ async function probe() {
3
+ const raw = { name: 'Pat', email: 'pat@example.com' };
4
+ // @ts-expect-error raw input should be rejected by SafeTx
5
+ await db.insert('users').values(raw);
6
+ const validated = asValidated(raw);
7
+ await db.insert('users').values(validated);
8
+ const merged = { ...validated, email: validated.email.toLowerCase() };
9
+ await db.insert('users').values(merged);
10
+ const role = (Math.random() > 0.5 ? 'admin' : 'member');
11
+ const mergedWithUnvalidated = { ...validated, role };
12
+ await dbWithRole.insert('users').values(mergedWithUnvalidated);
13
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=__tmp_scoped_astrusted_probe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"__tmp_scoped_astrusted_probe.d.ts","sourceRoot":"","sources":["../src/__tmp_scoped_astrusted_probe.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ import { Effect } from 'effect';
2
+ import { dbMutation } from './effect/action.js';
3
+ import { asTrusted } from './effect/validation.js';
4
+ const txInput = asTrusted({ createUser: { name: 'pat' } });
5
+ const program = dbMutation(db, txInput, async (tx, scoped) => {
6
+ await tx.insert('users').values(scoped.createUser);
7
+ // @ts-expect-error scoped mode rejects ad-hoc asTrusted payloads
8
+ await tx.insert('users').values(asTrusted({ name: 'other' }));
9
+ });
10
+ void Effect.runSync(program);
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=__tmp_scoped_derived_probe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"__tmp_scoped_derived_probe.d.ts","sourceRoot":"","sources":["../src/__tmp_scoped_derived_probe.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ import { Effect } from 'effect';
2
+ import { dbTransaction } from './effect/action.js';
3
+ import { asTrusted } from './effect/validation.js';
4
+ const program = dbTransaction(db, txInput, async (tx, scoped) => {
5
+ const [created] = await tx.insert('orders').values(scoped.createOrder).returning();
6
+ // @ts-expect-error scoped mode should reject plain/asTrusted derived values
7
+ await tx.insert('orderItems').values(asTrusted({
8
+ ...scoped.createItem,
9
+ orderId: created.id,
10
+ }));
11
+ });
12
+ void Effect.runSync(program);
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=__tmp_scoped_mutation_probe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"__tmp_scoped_mutation_probe.d.ts","sourceRoot":"","sources":["../src/__tmp_scoped_mutation_probe.ts"],"names":[],"mappings":""}
@@ -0,0 +1,15 @@
1
+ import { Effect } from 'effect';
2
+ import { dbMutation } from './effect/action.js';
3
+ import { asTrusted } from './effect/validation.js';
4
+ const input = asTrusted({
5
+ createUser: {
6
+ name: 'Pat',
7
+ userId: 'u1',
8
+ },
9
+ });
10
+ const program = dbMutation(db, input, async (tx, scoped) => {
11
+ await tx.insert('users').values(scoped.createUser);
12
+ // @ts-expect-error scoped mode should reject ad-hoc trusted values
13
+ await tx.insert('users').values(asTrusted({ name: 'Other', userId: 'u2' }));
14
+ });
15
+ void Effect.runSync(program);
package/dist/cache.d.ts CHANGED
@@ -15,7 +15,7 @@ export type CacheOptions = {
15
15
  /**
16
16
  * Stale-while-revalidate window. When set, stale values within this window
17
17
  * are returned immediately while a background refresh is triggered.
18
- * Without ExecutionContext, the refresh happens on the next request.
18
+ * Without ExecutionContext, stale entries are recomputed synchronously.
19
19
  */
20
20
  swr?: Duration.DurationInput;
21
21
  /**
@@ -84,6 +84,8 @@ export declare class CacheError extends CacheError_base {
84
84
  export declare const cache: <V, E, R>(key: string, compute: Effect.Effect<V, E, R>, schema: Schema.Schema<V>, options: CacheOptions) => Effect.Effect<V, E | CacheError | CacheClientError | ParseResult.ParseError, R | CacheService | ExecutionContextService>;
85
85
  /**
86
86
  * Get a value from cache without computing.
87
+ * Returns any entry still present in the backing cache store.
88
+ * This does not apply `ttl`/`swr` freshness checks used by `cache()`.
87
89
  *
88
90
  * @example
89
91
  * ```typescript
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AACtE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAA;AAM9F,MAAM,MAAM,YAAY,GAAG;IACzB,qCAAqC;IACrC,GAAG,EAAE,QAAQ,CAAC,aAAa,CAAA;IAC3B;;;;OAIG;IACH,GAAG,CAAC,EAAE,QAAQ,CAAC,aAAa,CAAA;IAC5B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CAC3B,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CAC3B,CAAA;;;;;;;AAMD,qBAAa,UAAW,SAAQ,eAG9B;CAAG;AA2DL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,KAAK,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAC3B,KAAK,MAAM,EACX,SAAS,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAC/B,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EACxB,SAAS,YAAY,KACpB,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,UAAU,GAAG,gBAAgB,GAAG,WAAW,CAAC,UAAU,EAAE,CAAC,GAAG,YAAY,GAAG,uBAAuB,CAuDtH,CAAA;AAEJ;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,EACxB,KAAK,MAAM,EACX,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EACxB,UAAU,eAAe,KACxB,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,UAAU,GAAG,gBAAgB,GAAG,WAAW,CAAC,UAAU,EAAE,YAAY,CAenG,CAAA;AAEJ;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,EACxB,KAAK,MAAM,EACX,OAAO,CAAC,EACR,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EACxB,SAAS,YAAY,KACpB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,gBAAgB,GAAG,WAAW,CAAC,UAAU,EAAE,YAAY,CAevF,CAAA;AAEJ,MAAM,MAAM,sBAAsB,CAAC,CAAC,IAAI;IACtC,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;IACxB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,GAAG,OAAO,CAAA;CAC1B,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,GAAG,OAAO,EACzC,KAAK,MAAM,EACX,UAAU,sBAAsB,CAAC,CAAC,CAAC,KAClC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,gBAAgB,EAAE,YAAY,CAOjD,CAAA;AAEJ;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,GAChC,QAAQ,MAAM,KACb,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,gBAAgB,EAAE,YAAY,CAWjD,CAAA"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AACtE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAA;AAM9F,MAAM,MAAM,YAAY,GAAG;IACzB,qCAAqC;IACrC,GAAG,EAAE,QAAQ,CAAC,aAAa,CAAA;IAC3B;;;;OAIG;IACH,GAAG,CAAC,EAAE,QAAQ,CAAC,aAAa,CAAA;IAC5B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CAC3B,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CAC3B,CAAA;;;;;;;AAMD,qBAAa,UAAW,SAAQ,eAG9B;CAAG;AA2DL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,KAAK,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAC3B,KAAK,MAAM,EACX,SAAS,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAC/B,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EACxB,SAAS,YAAY,KACpB,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,UAAU,GAAG,gBAAgB,GAAG,WAAW,CAAC,UAAU,EAAE,CAAC,GAAG,YAAY,GAAG,uBAAuB,CA0DtH,CAAA;AAEJ;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,EACxB,KAAK,MAAM,EACX,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EACxB,UAAU,eAAe,KACxB,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,UAAU,GAAG,gBAAgB,GAAG,WAAW,CAAC,UAAU,EAAE,YAAY,CAenG,CAAA;AAEJ;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,EACxB,KAAK,MAAM,EACX,OAAO,CAAC,EACR,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EACxB,SAAS,YAAY,KACpB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,gBAAgB,GAAG,WAAW,CAAC,UAAU,EAAE,YAAY,CAevF,CAAA;AAEJ,MAAM,MAAM,sBAAsB,CAAC,CAAC,IAAI;IACtC,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;IACxB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,GAAG,OAAO,CAAA;CAC1B,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,GAAG,OAAO,EACzC,KAAK,MAAM,EACX,UAAU,sBAAsB,CAAC,CAAC,CAAC,KAClC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,gBAAgB,EAAE,YAAY,CAOjD,CAAA;AAEJ;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,GAChC,QAAQ,MAAM,KACb,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,gBAAgB,EAAE,YAAY,CAwBjD,CAAA"}
package/dist/cache.js CHANGED
@@ -131,8 +131,10 @@ export const cache = (key, compute, schema, options) => Effect.gen(function* ()
131
131
  const freshValue = yield* compute;
132
132
  yield* storeInCache(freshValue);
133
133
  }));
134
+ return entry.v;
134
135
  }
135
- return entry.v;
136
+ // No background execution available (tests/local dev): refresh inline.
137
+ // Fall through to synchronous recompute below.
136
138
  }
137
139
  // Beyond SWR window - fall through to recompute
138
140
  }
@@ -144,6 +146,8 @@ export const cache = (key, compute, schema, options) => Effect.gen(function* ()
144
146
  });
145
147
  /**
146
148
  * Get a value from cache without computing.
149
+ * Returns any entry still present in the backing cache store.
150
+ * This does not apply `ttl`/`swr` freshness checks used by `cache()`.
147
151
  *
148
152
  * @example
149
153
  * ```typescript
@@ -220,6 +224,13 @@ export const cacheInvalidate = (key, options) => Effect.gen(function* () {
220
224
  */
221
225
  export const cacheInvalidatePrefix = (prefix) => Effect.gen(function* () {
222
226
  const cacheService = yield* CacheService;
223
- const list = yield* cacheService.list({ prefix });
224
- yield* Effect.forEach(list.keys, (key) => cacheService.delete(key.name), { concurrency: 10 });
227
+ let cursor = undefined;
228
+ while (true) {
229
+ const page = yield* cacheService.list({ prefix, cursor });
230
+ yield* Effect.forEach(page.keys, (key) => cacheService.delete(key.name), { concurrency: 10 });
231
+ if (page.list_complete || page.cursor === undefined) {
232
+ break;
233
+ }
234
+ cursor = page.cursor;
235
+ }
225
236
  });
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Honertia CLI entrypoint.
4
+ *
5
+ * This powers the `honertia` executable distributed with the package.
6
+ */
7
+ export declare function runCli(args?: string[]): Promise<void>;
8
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":";AACA;;;;GAIG;AAwFH,wBAAsB,MAAM,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CA0DlF"}
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Honertia CLI entrypoint.
4
+ *
5
+ * This powers the `honertia` executable distributed with the package.
6
+ */
7
+ import { resolve } from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { runRoutes, routesHelp } from './index.js';
10
+ import { runCheck, checkHelp } from './check.js';
11
+ import { runDb, dbHelp } from './db.js';
12
+ import { runGenerateAction, generateActionHelp, runGenerateCrud, generateCrudHelp } from './generate.js';
13
+ import { runGenerateFeature, generateFeatureHelp } from './feature.js';
14
+ import { runGenerateOpenApi, generateOpenApiHelp } from './openapi.js';
15
+ import { runGenerateInlineTestsRunner, generateInlineTestsRunnerHelp } from './inline-tests.js';
16
+ function mainHelp() {
17
+ return `
18
+ honertia - Agent-first CLI for Honertia
19
+
20
+ USAGE:
21
+ honertia <command> [OPTIONS]
22
+
23
+ COMMANDS:
24
+ routes List registered routes
25
+ check Validate project routes/configuration
26
+ db <subcommand> Database migration commands
27
+ db:status Alias for "db status"
28
+ db:migrate Alias for "db migrate"
29
+ db:rollback Alias for "db rollback"
30
+ db:generate <name> Alias for "db generate <name>"
31
+ generate:action <name> Generate a colocated action file
32
+ generate:crud <resource> Generate CRUD action files
33
+ generate:feature <name> Generate a colocated feature file
34
+ generate:openapi Generate OpenAPI spec
35
+ generate:tests-runner Generate inline tests runner
36
+
37
+ EXAMPLES:
38
+ honertia routes --json
39
+ honertia check --verbose
40
+ honertia db status
41
+ honertia db:migrate --preview
42
+ honertia generate:action projects/create --method POST --path /projects
43
+ honertia generate:openapi --output openapi.json --format json
44
+
45
+ Run "honertia <command> --help" for command-specific options.
46
+ `.trim();
47
+ }
48
+ function commandHelp(command) {
49
+ switch (command) {
50
+ case 'routes':
51
+ return routesHelp();
52
+ case 'check':
53
+ return checkHelp();
54
+ case 'db':
55
+ case 'db:status':
56
+ case 'db:migrate':
57
+ case 'db:rollback':
58
+ case 'db:generate':
59
+ return dbHelp();
60
+ case 'generate:action':
61
+ return generateActionHelp();
62
+ case 'generate:crud':
63
+ return generateCrudHelp();
64
+ case 'generate:feature':
65
+ return generateFeatureHelp();
66
+ case 'generate:openapi':
67
+ return generateOpenApiHelp();
68
+ case 'generate:tests-runner':
69
+ return generateInlineTestsRunnerHelp();
70
+ default:
71
+ return null;
72
+ }
73
+ }
74
+ function normalizeCommand(args) {
75
+ if (args.length === 0) {
76
+ return { command: null, rest: [] };
77
+ }
78
+ const [first, ...rest] = args;
79
+ // Support grouped form: "generate action ..."
80
+ if (first === 'generate' && rest.length > 0) {
81
+ const [subcommand, ...remaining] = rest;
82
+ return { command: `generate:${subcommand}`, rest: remaining };
83
+ }
84
+ return { command: first, rest };
85
+ }
86
+ export async function runCli(args = process.argv.slice(2)) {
87
+ const { command, rest } = normalizeCommand(args);
88
+ if (!command || command === '--help' || command === '-h' || command === 'help') {
89
+ if (rest.length > 0) {
90
+ const help = commandHelp(rest[0]);
91
+ if (help) {
92
+ console.log(help);
93
+ return;
94
+ }
95
+ }
96
+ console.log(mainHelp());
97
+ return;
98
+ }
99
+ switch (command) {
100
+ case 'routes':
101
+ runRoutes(rest);
102
+ return;
103
+ case 'check':
104
+ runCheck(rest);
105
+ return;
106
+ case 'db':
107
+ await runDb(rest);
108
+ return;
109
+ case 'db:status':
110
+ await runDb(['status', ...rest]);
111
+ return;
112
+ case 'db:migrate':
113
+ await runDb(['migrate', ...rest]);
114
+ return;
115
+ case 'db:rollback':
116
+ await runDb(['rollback', ...rest]);
117
+ return;
118
+ case 'db:generate':
119
+ await runDb(['generate', ...rest]);
120
+ return;
121
+ case 'generate:action':
122
+ runGenerateAction(rest);
123
+ return;
124
+ case 'generate:crud':
125
+ runGenerateCrud(rest);
126
+ return;
127
+ case 'generate:feature':
128
+ runGenerateFeature(rest);
129
+ return;
130
+ case 'generate:openapi':
131
+ await runGenerateOpenApi(rest);
132
+ return;
133
+ case 'generate:tests-runner':
134
+ runGenerateInlineTestsRunner(rest);
135
+ return;
136
+ default:
137
+ console.error(`Unknown command: ${command}`);
138
+ console.error('Run "honertia --help" for usage');
139
+ process.exit(1);
140
+ }
141
+ }
142
+ const isMain = process.argv[1]
143
+ ? resolve(process.argv[1]) === fileURLToPath(import.meta.url)
144
+ : false;
145
+ if (isMain) {
146
+ runCli().catch((error) => {
147
+ console.error(error instanceof Error ? error.message : String(error));
148
+ process.exit(1);
149
+ });
150
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/cli/db.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/cli/db.ts"],"names":[],"mappings":"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"}