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.
- package/README.md +147 -12
- package/dist/__tmp_db_safety_probe.d.ts +2 -0
- package/dist/__tmp_db_safety_probe.d.ts.map +1 -0
- package/dist/__tmp_db_safety_probe.js +13 -0
- package/dist/__tmp_scoped_astrusted_probe.d.ts +2 -0
- package/dist/__tmp_scoped_astrusted_probe.d.ts.map +1 -0
- package/dist/__tmp_scoped_astrusted_probe.js +10 -0
- package/dist/__tmp_scoped_derived_probe.d.ts +2 -0
- package/dist/__tmp_scoped_derived_probe.d.ts.map +1 -0
- package/dist/__tmp_scoped_derived_probe.js +12 -0
- package/dist/__tmp_scoped_mutation_probe.d.ts +2 -0
- package/dist/__tmp_scoped_mutation_probe.d.ts.map +1 -0
- package/dist/__tmp_scoped_mutation_probe.js +15 -0
- package/dist/cache.d.ts +3 -1
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +14 -3
- package/dist/cli/bin.d.ts +8 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +150 -0
- package/dist/cli/db.d.ts.map +1 -1
- package/dist/cli/db.js +83 -18
- package/dist/cli/feature.d.ts +1 -0
- package/dist/cli/feature.d.ts.map +1 -1
- package/dist/cli/feature.js +76 -11
- package/dist/cli/generate.d.ts.map +1 -1
- package/dist/cli/generate.js +124 -22
- package/dist/cli/index.d.ts +6 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +16 -4
- package/dist/cli/inline-tests.d.ts.map +1 -1
- package/dist/cli/inline-tests.js +4 -0
- package/dist/cli/openapi.d.ts +2 -1
- package/dist/cli/openapi.d.ts.map +1 -1
- package/dist/cli/openapi.js +68 -3
- package/dist/effect/action.d.ts +92 -3
- package/dist/effect/action.d.ts.map +1 -1
- package/dist/effect/action.js +38 -32
- package/dist/effect/auth.d.ts +3 -1
- package/dist/effect/auth.d.ts.map +1 -1
- package/dist/effect/auth.js +17 -3
- package/dist/effect/bridge.d.ts +5 -0
- package/dist/effect/bridge.d.ts.map +1 -1
- package/dist/effect/bridge.js +5 -2
- package/dist/effect/error-catalog.d.ts +1 -0
- package/dist/effect/error-catalog.d.ts.map +1 -1
- package/dist/effect/error-catalog.js +17 -1
- package/dist/effect/index.d.ts +2 -2
- package/dist/effect/index.d.ts.map +1 -1
- package/dist/effect/index.js +2 -2
- package/dist/effect/routing.d.ts.map +1 -1
- package/dist/effect/routing.js +14 -14
- package/dist/effect/services.d.ts +3 -0
- package/dist/effect/services.d.ts.map +1 -1
- package/dist/effect/validation.d.ts +53 -1
- package/dist/effect/validation.d.ts.map +1 -1
- package/dist/effect/validation.js +161 -27
- package/dist/schema.d.ts +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +1 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +3 -2
- 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
|
|
121
|
-
|
|
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
|
|
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 {
|
|
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
|
-
|
|
2134
|
-
|
|
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
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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,
|
|
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
|
package/dist/cache.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
224
|
-
|
|
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 @@
|
|
|
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"}
|
package/dist/cli/bin.js
ADDED
|
@@ -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
|
+
}
|
package/dist/cli/db.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/cli/db.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ;;OAEG;IACH,OAAO,EAAE,OAAO,CAAA;IAChB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,MAAM,EAAE,YAAY,GAAG,SAAS,GAAG,OAAO,CAAA;IAC1C;;OAEG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;OAEG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;OAEG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;OAEG;IACH,UAAU,EAAE,eAAe,EAAE,CAAA;IAC7B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,OAAO,EAAE,OAAO,CAAA;IAChB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;OAEG;IACH,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,OAAO,EAAE,OAAO,CAAA;IAChB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACxB;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;OAEG;IACH,WAAW,EAAE,MAAM,CAAA;IACnB;;OAEG;IACH,EAAE,EAAE,MAAM,CAAA;IACV;;OAEG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACxC;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CAC7C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,mBAAmB,GAAG,mBAAmB,CAEpF;AAED;;;;;;;;GAQG;AACH,wBAAgB,GAAG,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAM/E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,QAAQ,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,cAAc,CAAC,CAkCtF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC,
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/cli/db.ts"],"names":[],"mappings":"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"}
|