Skip to content

Commit ab7816c

Browse files
authored
feat: MVP 1. (#7)
* feat: MVP 1. * code review fixes * code review fixes pt.2 * regenerated schemas * code reviews pt. 2. * repo add fix * CORS and gitignore fix * code review fixes pt.1. * user settings refactor * cr fixes * token list fix * page layout optimalizations * progress * progress * progress * repository layer fixes * progress * code review fixes * code review fixes * added createElevatedContext * FS updates, system identity context * fs update * cr fixes * visual hierarchy update * persist log entries, bulk removes * progress on entity refactors * service details to data grid * review fixes, bulk remove fixes * navigation fixes * allow to clear logs * code review fixes * code review fixes * service creation and GH pull improvements * Form refactors * missing sync fixes * install fix * Service status history updates * log fix * process updates * detached process kill * export button * form handling improvements * updates * selection fix * service details page refactor * services refactor * prerequisites refactor * progress on prerequisites, cr fixes * component consolidation and cleanup * service form fixes * prerequisites and build fixes * progress on env.variables * prerequisite check in-memory * MCP server improvements * required files setup * github repo in memory search fix * fs update and fixes * theme support * furystack upgrade * eslint integration * prettier fix * test fix * gitignore fix * filled changelogs * removed e2e tests from main build pipeline * progress on fs upgrades * routes refactor * added stackCraftNavigate * type safe routing * config test fix * creation and smoke fix * shades upgrades, dog fooding progress * fixed dog fooding test
1 parent 482ba5c commit ab7816c

201 files changed

Lines changed: 28023 additions & 2525 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.cursor/rules/CODE_STYLE.mdc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ function getData() {}
180180
```typescript
181181
// ✅ Good - Shades component with event handlers
182182
const MyComponent = Shade({
183-
shadowDomName: 'my-component',
183+
customElementName: 'my-component',
184184
render: ({ props, injector }) => {
185185
const handleButtonClick = () => {
186186
// Internal logic
@@ -267,7 +267,7 @@ type UserProfileProps = {
267267
};
268268

269269
export const UserProfile = Shade<UserProfileProps>({
270-
shadowDomName: 'user-profile',
270+
customElementName: 'user-profile',
271271
render: ({ props }) => {
272272
// Component implementation
273273
},
@@ -501,7 +501,7 @@ const formatDate = (date: Date): string => {
501501

502502
// 5. Main component
503503
export const UserProfile = Shade<UserProfileProps>({
504-
shadowDomName: 'user-profile',
504+
customElementName: 'user-profile',
505505
render: ({ props, injector, useObservable, useDisposable }) => {
506506
// Services
507507
const userService = injector.getInstance(UserService);
@@ -646,7 +646,7 @@ export const formatCurrency = (value: number, currency: string): string => {
646646
* UserProfile component displays user information and allows editing
647647
*/
648648
export const UserProfile = Shade<UserProfileProps>({
649-
shadowDomName: 'user-profile',
649+
customElementName: 'user-profile',
650650
render: ({ props }) => {
651651
// Component implementation
652652
},

.cursor/rules/REST_SERVICE.mdc

Lines changed: 95 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,8 @@ import StackCraftApiSchemas from 'common/schemas/stack-craft-api.json' with { ty
2121
import { useHttpAuthentication, useRestService, useStaticFiles } from '@furystack/rest-service'
2222
import { injector } from './config.js'
2323

24-
// Set up authentication
25-
useHttpAuthentication(injector, {
26-
getUserStore: (sm) => sm.getStoreFor(User, 'username'),
27-
getSessionStore: (sm) => sm.getStoreFor(DefaultSession, 'sessionId'),
28-
})
24+
// Set up authentication (requires DataSets for User and DefaultSession to be pre-registered)
25+
useHttpAuthentication(injector)
2926

3027
// Set up REST API
3128
useRestService<StackCraftApi>({
@@ -185,8 +182,8 @@ import { FileSystemStore } from '@furystack/filesystem-store'
185182
import { Injector } from '@furystack/inject'
186183
import { useLogging, VerboseConsoleLogger } from '@furystack/logging'
187184
import { getRepository } from '@furystack/repository'
188-
import { usePasswordPolicy } from '@furystack/security'
189185
import { DefaultSession } from '@furystack/rest-service'
186+
import { PasswordResetToken, usePasswordPolicy } from '@furystack/security'
190187
import { User } from 'common'
191188

192189
export const injector = new Injector()
@@ -204,13 +201,12 @@ addStore(
204201
}),
205202
)
206203

207-
addStore(
208-
injector,
209-
new InMemoryStore({
210-
model: DefaultSession,
211-
primaryKey: 'sessionId',
212-
}),
213-
)
204+
addStore(injector, new InMemoryStore({ model: DefaultSession, primaryKey: 'sessionId' }))
205+
.addStore(new InMemoryStore({ model: PasswordResetToken, primaryKey: 'token' }))
206+
207+
// Create DataSets (required before useHttpAuthentication and usePasswordPolicy)
208+
getRepository(injector).createDataSet(DefaultSession, 'sessionId')
209+
getRepository(injector).createDataSet(PasswordResetToken, 'token')
214210

215211
// Set up password policy
216212
usePasswordPolicy(injector)
@@ -237,6 +233,74 @@ export const authorizedDataSet: Partial<DataSetSettings<unknown, unknown>> = {
237233
}
238234
```
239235

236+
## Data Access
237+
238+
### NEVER Use `getStoreManager()` or `StoreManager` Directly
239+
240+
All data access **must** go through the Repository layer (`getRepository()`) using DataSets. Direct `StoreManager` / `getStoreManager()` usage bypasses authorization and is **forbidden**.
241+
242+
```typescript
243+
// ❌ FORBIDDEN - bypasses authorization
244+
import { getStoreManager } from '@furystack/core'
245+
const sm = getStoreManager(injector)
246+
const users = await sm.getStoreFor(User, 'username').find({})
247+
248+
// ❌ FORBIDDEN - direct StoreManager injection
249+
@Injected(StoreManager)
250+
declare private storeManager: StoreManager
251+
252+
// ✅ Good - use Repository DataSets
253+
import { getRepository } from '@furystack/repository'
254+
const repository = getRepository(injector)
255+
const users = await repository.getDataSetFor(User, 'username').find(injector, {})
256+
```
257+
258+
### REST Action Handlers
259+
260+
REST action handlers receive a scoped `injector` with an `IdentityContext` already set up per-request. Pass it directly to DataSet methods:
261+
262+
```typescript
263+
const MyAction: RequestAction<MyEndpoint> = async ({ injector }) => {
264+
const repository = getRepository(injector)
265+
const items = await repository.getDataSetFor(MyModel, 'id').find(injector, {})
266+
return JsonResult({ items })
267+
}
268+
```
269+
270+
### Elevated Context for Background Operations
271+
272+
Background services, middleware, and startup code have no HTTP request context. Use `useSystemIdentityContext()` from `@furystack/core` to create a child injector with system-level privileges:
273+
274+
```typescript
275+
import { useSystemIdentityContext } from '@furystack/core'
276+
import { getRepository } from '@furystack/repository'
277+
import { usingAsync } from '@furystack/utils'
278+
279+
// One-off operation (automatic cleanup with usingAsync)
280+
await usingAsync(useSystemIdentityContext({ injector }), async (elevated) => {
281+
const repository = getRepository(elevated)
282+
const items = await repository.getDataSetFor(MyModel, 'id').find(elevated, {})
283+
await repository.getDataSetFor(MyModel, 'id').update(elevated, id, changes)
284+
})
285+
286+
// Singleton services (cache and dispose with service)
287+
@Injectable({ lifetime: 'singleton' })
288+
export class MyService {
289+
private elevatedInjector?: Injector
290+
291+
private getElevatedInjector(): Injector {
292+
if (!this.elevatedInjector) {
293+
this.elevatedInjector = useSystemIdentityContext({ injector: getInjectorReference(this) })
294+
}
295+
return this.elevatedInjector
296+
}
297+
298+
public async [Symbol.asyncDispose]() {
299+
await this.elevatedInjector?.[Symbol.asyncDispose]()
300+
}
301+
}
302+
```
303+
240304
## Store Types
241305

242306
### FileSystemStore
@@ -316,26 +380,25 @@ void attachShutdownHandler(injector)
316380

317381
### Seed Script
318382

319-
Create a seed script for initial data:
383+
Create a seed script for initial data using elevated context:
320384

321385
```typescript
322386
// service/src/seed.ts
323-
import { StoreManager } from '@furystack/core'
387+
import { useSystemIdentityContext } from '@furystack/core'
388+
import { getRepository } from '@furystack/repository'
389+
import { usingAsync } from '@furystack/utils'
324390
import { PasswordAuthenticator, PasswordCredential } from '@furystack/security'
325391
import { User } from 'common'
326392
import { injector } from './config.js'
327393

328394
export const seed = async (i: Injector): Promise<void> => {
329-
const sm = i.getInstance(StoreManager)
330-
const userStore = sm.getStoreFor(User, 'username')
331-
const pwcStore = sm.getStoreFor(PasswordCredential, 'userName')
332-
333-
// Create default user credentials
334-
const cred = await i.getInstance(PasswordAuthenticator).hasher.createCredential('testuser', 'password')
395+
await usingAsync(useSystemIdentityContext({ injector: i }), async (elevated) => {
396+
const repository = getRepository(elevated)
397+
const cred = await i.getInstance(PasswordAuthenticator).hasher.createCredential('testuser', 'password')
335398

336-
// Save to stores
337-
await pwcStore.add(cred)
338-
await userStore.add({ username: 'testuser', roles: [] })
399+
await repository.getDataSetFor(PasswordCredential, 'userName').add(elevated, cred)
400+
await repository.getDataSetFor(User, 'username').add(elevated, { username: 'testuser', roles: [] })
401+
})
339402
}
340403

341404
await seed(injector)
@@ -375,16 +438,21 @@ useRestService<StackCraftApi>({
375438
3. **Validate requests** - Use `Validate` wrapper with JSON schemas
376439
4. **Configure stores** - `FileSystemStore` for persistence, `InMemoryStore` for sessions
377440
5. **Handle authorization** - Define authorization functions for data sets
378-
6. **Graceful shutdown** - Implement proper cleanup with `Symbol.asyncDispose`
379-
7. **CORS setup** - Configure for frontend origins
441+
6. **NEVER use `getStoreManager()` or `StoreManager` directly** - Always use `getRepository().getDataSetFor()` for data access
442+
7. **Use `useSystemIdentityContext()` from `@furystack/core`** for background services, middleware, and startup operations that lack an HTTP request context
443+
8. **Graceful shutdown** - Implement proper cleanup with `Symbol.asyncDispose`
444+
9. **CORS setup** - Configure for frontend origins
380445

381446
**Service Checklist:**
382447

383448
- [ ] API types defined in `common` package
384449
- [ ] JSON schemas generated for validation
385450
- [ ] Stores configured for all models
386-
- [ ] Authentication set up with `useHttpAuthentication`
451+
- [ ] DataSets created for all models via `getRepository().createDataSet()` (including `DefaultSession` and `PasswordResetToken`)
452+
- [ ] Authentication set up with `useHttpAuthentication` (no options needed — uses DataSets by default)
387453
- [ ] Authorization functions defined
454+
- [ ] No `getStoreManager()` or `StoreManager` usage — all data access via Repository
455+
- [ ] Background services use `useSystemIdentityContext()` for data access
388456
- [ ] CORS configured for frontend
389457
- [ ] Graceful shutdown handler attached
390458
- [ ] Error handling for startup failures

0 commit comments

Comments
 (0)