@logixjs/domain 0.0.2 → 1.0.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.
package/dist/Crud.cjs CHANGED
@@ -85,7 +85,7 @@ var defineCrud = (id, spec, extend) => {
85
85
  const QuerySchema = spec.query ?? DefaultQueryInputSchema;
86
86
  const IdSchema = spec.id ?? import_effect.Schema.String;
87
87
  const idField = spec.idField ?? "id";
88
- class Api extends import_effect.Context.Tag(`${id}/crud/api`)() {
88
+ class Api extends import_effect.ServiceMap.Service()(`${id}/crud/api`) {
89
89
  }
90
90
  const services = { api: Api };
91
91
  const Actions = makeActions(spec.entity, QuerySchema, IdSchema);
@@ -211,47 +211,37 @@ var defineCrud = (id, spec, extend) => {
211
211
  ) : Logix.Module.make(id, def);
212
212
  const install = module2.logic(
213
213
  ($) => import_effect.Effect.gen(function* () {
214
+ const missingApiMessage = `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`;
215
+ const runWithApi = (dispatchFailed, run) => import_effect.Effect.gen(function* () {
216
+ const apiOpt = yield* import_effect.Effect.serviceOption(services.api);
217
+ if (import_effect.Option.isNone(apiOpt)) {
218
+ yield* dispatchFailed(missingApiMessage);
219
+ return;
220
+ }
221
+ yield* run(apiOpt.value);
222
+ }).pipe(
223
+ import_effect.Effect.catch((error) => dispatchFailed(toErrorMessage(error))),
224
+ import_effect.Effect.asVoid
225
+ );
214
226
  yield* $.onAction("query").runFork(
215
- (action) => import_effect.Effect.gen(function* () {
216
- const apiOpt = yield* import_effect.Effect.serviceOption(services.api);
217
- if (import_effect.Option.isNone(apiOpt)) {
218
- yield* $.dispatchers.queryFailed(
219
- `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`
220
- );
221
- return;
222
- }
223
- const api = apiOpt.value;
224
- const result = yield* api.list(action.payload);
225
- yield* $.dispatchers.querySucceeded(result);
226
- }).pipe(import_effect.Effect.catchAll((e) => $.dispatchers.queryFailed(toErrorMessage(e))))
227
+ (action) => runWithApi(
228
+ $.dispatchers.queryFailed,
229
+ (api) => api.list(action.payload).pipe(import_effect.Effect.flatMap((result) => $.dispatchers.querySucceeded(result)))
230
+ )
227
231
  );
228
232
  yield* $.onAction("save").runFork(
229
- (action) => import_effect.Effect.gen(function* () {
230
- const apiOpt = yield* import_effect.Effect.serviceOption(services.api);
231
- if (import_effect.Option.isNone(apiOpt)) {
232
- yield* $.dispatchers.saveFailed(
233
- `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`
234
- );
235
- return;
236
- }
237
- const api = apiOpt.value;
238
- const entity = yield* api.save(action.payload);
239
- yield* $.dispatchers.saveSucceeded(entity);
240
- }).pipe(import_effect.Effect.catchAll((e) => $.dispatchers.saveFailed(toErrorMessage(e))))
233
+ (action) => runWithApi(
234
+ $.dispatchers.saveFailed,
235
+ (api) => api.save(action.payload).pipe(import_effect.Effect.flatMap((entity) => $.dispatchers.saveSucceeded(entity)))
236
+ )
241
237
  );
242
238
  yield* $.onAction("remove").runFork(
243
- (action) => import_effect.Effect.gen(function* () {
244
- const apiOpt = yield* import_effect.Effect.serviceOption(services.api);
245
- if (import_effect.Option.isNone(apiOpt)) {
246
- yield* $.dispatchers.removeFailed(
247
- `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`
248
- );
249
- return;
250
- }
251
- const api = apiOpt.value;
252
- yield* api.remove(action.payload);
253
- yield* $.dispatchers.removeSucceeded(action.payload);
254
- }).pipe(import_effect.Effect.catchAll((e) => $.dispatchers.removeFailed(toErrorMessage(e))))
239
+ (action) => runWithApi(
240
+ $.dispatchers.removeFailed,
241
+ (api) => api.remove(action.payload).pipe(
242
+ import_effect.Effect.flatMap(() => $.dispatchers.removeSucceeded(action.payload))
243
+ )
244
+ )
255
245
  );
256
246
  }),
257
247
  { id: "install" }
package/dist/Crud.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/Crud.ts","../src/internal/crud/Crud.ts"],"sourcesContent":["export * from './internal/crud/Crud.js'\nexport type * from './internal/crud/Crud.js'\n","import * as Logix from '@logixjs/core'\nimport { Context, Effect, Option, Schema } from 'effect'\n\nconst DefaultQueryInputSchema = Schema.Struct({\n pageSize: Schema.Number,\n})\n\nexport type CrudDefaultQueryInput = Schema.Schema.Type<typeof DefaultQueryInputSchema>\n\nexport interface CrudQueryResult<Entity> {\n readonly items: ReadonlyArray<Entity>\n readonly total?: number\n}\n\nexport interface CrudApi<Entity extends object, QueryInput, Id> {\n readonly list: (input: QueryInput) => Effect.Effect<CrudQueryResult<Entity>, unknown, never>\n readonly save: (entity: Entity) => Effect.Effect<Entity, unknown, never>\n readonly remove: (id: Id) => Effect.Effect<void, unknown, never>\n}\n\nexport interface CrudSpec<Entity extends object, QueryInput = CrudDefaultQueryInput, Id = string> {\n readonly entity: Schema.Schema<Entity>\n readonly query?: Schema.Schema<QueryInput>\n readonly id?: Schema.Schema<Id>\n readonly initial?: ReadonlyArray<Entity>\n /**\n * idField:\n * - The default primary key field for upsert/remove in reducers (default: \"id\").\n * - For more complex primary-key strategies, prefer handling results in a custom upper-layer Logic and writing back to state.\n */\n readonly idField?: string\n}\n\nexport type CrudState<Entity, QueryInput> = {\n readonly items: ReadonlyArray<Entity>\n readonly loading: boolean\n readonly error: string | undefined\n readonly lastQuery: QueryInput | undefined\n readonly total: number | undefined\n}\n\nexport type CrudActionMap<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly query: Schema.Schema<QueryInput>\n readonly querySucceeded: Schema.Schema<CrudQueryResult<Entity>>\n readonly queryFailed: Schema.Schema<string>\n\n readonly save: Schema.Schema<Entity>\n readonly saveSucceeded: Schema.Schema<Entity>\n readonly saveFailed: Schema.Schema<string>\n\n readonly remove: Schema.Schema<Id>\n readonly removeSucceeded: Schema.Schema<Id>\n readonly removeFailed: Schema.Schema<string>\n\n readonly clearError: Schema.Schema<void>\n} & ExtraActions\n\nexport type CrudAction<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.ActionsFromMap<CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudShape<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Shape<Schema.Schema<CrudState<Entity, QueryInput>>, CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudServices<Entity extends object, QueryInput, Id> = {\n readonly api: Logix.State.Tag<CrudApi<Entity, QueryInput, Id>>\n}\n\nexport type CrudHandleExt<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly controller: CrudController<Entity, QueryInput, Id, ExtraActions>['controller']\n readonly services: CrudServices<Entity, QueryInput, Id>\n}\n\nexport interface CrudController<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> {\n readonly runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, Id, ExtraActions>>\n readonly getState: Effect.Effect<CrudState<Entity, QueryInput>>\n readonly dispatch: (action: CrudAction<Entity, QueryInput, Id, ExtraActions>) => Effect.Effect<void>\n readonly controller: {\n readonly list: (input: QueryInput) => Effect.Effect<void>\n readonly save: (entity: Entity) => Effect.Effect<void>\n readonly remove: (id: Id) => Effect.Effect<void>\n readonly clearError: () => Effect.Effect<void>\n readonly idField: string\n }\n}\n\nconst makeActions = <Entity extends object, QueryInput, Id>(\n entity: Schema.Schema<Entity>,\n query: Schema.Schema<QueryInput>,\n id: Schema.Schema<Id>,\n): CrudActionMap<Entity, QueryInput, Id> =>\n ({\n query,\n querySucceeded: Schema.Struct({\n items: Schema.Array(entity),\n total: Schema.optional(Schema.Number),\n }) as Schema.Schema<CrudQueryResult<Entity>>,\n queryFailed: Schema.String,\n\n save: entity,\n saveSucceeded: entity,\n saveFailed: Schema.String,\n\n remove: id,\n removeSucceeded: id,\n removeFailed: Schema.String,\n\n clearError: Schema.Void,\n }) satisfies CrudActionMap<Entity, QueryInput, Id>\n\nconst toErrorMessage = (error: unknown): string => {\n if (error === null || error === undefined) return 'unknown error'\n if (typeof error === 'string') return error\n if (error instanceof Error && typeof error.message === 'string' && error.message.length > 0) {\n return error.message\n }\n if (typeof error === 'object') {\n if ('message' in error) {\n const message = (error as { readonly message?: unknown }).message\n if (typeof message === 'string' && message.length > 0) return message\n }\n try {\n const json = JSON.stringify(error)\n if (typeof json === 'string' && json.length > 0) return json\n } catch {\n // ignore\n }\n }\n return String(error)\n}\n\nconst upsertByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n entity: Entity,\n idField: string,\n): ReadonlyArray<Entity> => {\n const id = (entity as Record<string, unknown>)[idField]\n const idx = items.findIndex((x) => (x as Record<string, unknown>)[idField] === id)\n if (idx < 0) return [...items, entity]\n return items.map((x, i) => (i === idx ? entity : x))\n}\n\nconst removeByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n id: unknown,\n idField: string,\n): ReadonlyArray<Entity> => items.filter((x) => (x as Record<string, unknown>)[idField] !== id)\n\nexport type CrudModule<\n Id extends string,\n Entity extends object,\n QueryInput,\n EntityId,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Module.Module<\n Id,\n CrudShape<Entity, QueryInput, EntityId, ExtraActions>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>,\n unknown\n> & {\n readonly _kind: 'Module'\n readonly services: CrudServices<Entity, QueryInput, EntityId>\n}\n\nconst defineCrud = <\n Id extends string,\n Entity extends object,\n QueryInput = CrudDefaultQueryInput,\n EntityId = string,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n>(\n id: Id,\n spec: CrudSpec<Entity, QueryInput, EntityId>,\n extend?: Logix.Module.MakeExtendDef<\n Schema.Schema<CrudState<Entity, QueryInput>>,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n): CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions> => {\n const QuerySchema = (spec.query ?? DefaultQueryInputSchema) as Schema.Schema<QueryInput>\n const IdSchema = (spec.id ?? Schema.String) as Schema.Schema<EntityId>\n\n const idField = spec.idField ?? 'id'\n\n class Api extends Context.Tag(`${id}/crud/api`)<Api, CrudApi<Entity, QueryInput, EntityId>>() {}\n\n const services = { api: Api } as const satisfies CrudServices<Entity, QueryInput, EntityId>\n\n const Actions = makeActions(spec.entity, QuerySchema, IdSchema)\n\n const StateSchema = Schema.Struct({\n items: Schema.Array(spec.entity),\n loading: Schema.Boolean,\n error: Schema.UndefinedOr(Schema.String),\n lastQuery: Schema.UndefinedOr(QuerySchema),\n total: Schema.UndefinedOr(Schema.Number),\n })\n\n type Reducers = Logix.ReducersFromMap<typeof StateSchema, CrudActionMap<Entity, QueryInput, EntityId>>\n\n const reducers: Reducers = {\n query: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('lastQuery')\n return {\n ...state,\n loading: true,\n error: undefined,\n lastQuery: action.payload,\n }\n },\n querySucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n sink?.('total')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: action.payload.items,\n total: action.payload.total,\n }\n },\n queryFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n save: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n saveSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: upsertByIdField(state.items, action.payload as Entity, idField),\n }\n },\n saveFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n remove: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n removeSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: removeByIdField(state.items, action.payload, idField),\n }\n },\n removeFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n clearError: (state, _action, sink) => {\n sink?.('error')\n return {\n ...state,\n error: undefined,\n }\n },\n }\n\n const def = {\n state: StateSchema,\n actions: Actions,\n reducers,\n schemas: { entity: spec.entity },\n meta: { kind: 'crud', idField },\n services,\n }\n\n const module = extend\n ? Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(\n id,\n def,\n extend as unknown as Logix.Module.MakeExtendDef<\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n )\n : Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(id, def)\n\n const install = module.logic(\n ($) =>\n Effect.gen(function* () {\n yield* $.onAction('query').runFork((action) =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api)\n if (Option.isNone(apiOpt)) {\n yield* $.dispatchers.queryFailed(\n `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`,\n )\n return\n }\n const api = apiOpt.value\n\n const result = yield* api.list(action.payload as QueryInput)\n yield* $.dispatchers.querySucceeded(result)\n }).pipe(Effect.catchAll((e) => $.dispatchers.queryFailed(toErrorMessage(e)))),\n )\n\n yield* $.onAction('save').runFork((action) =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api)\n if (Option.isNone(apiOpt)) {\n yield* $.dispatchers.saveFailed(\n `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`,\n )\n return\n }\n const api = apiOpt.value\n\n const entity = yield* api.save(action.payload as Entity)\n yield* $.dispatchers.saveSucceeded(entity)\n }).pipe(Effect.catchAll((e) => $.dispatchers.saveFailed(toErrorMessage(e)))),\n )\n\n yield* $.onAction('remove').runFork((action) =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api)\n if (Option.isNone(apiOpt)) {\n yield* $.dispatchers.removeFailed(\n `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`,\n )\n return\n }\n const api = apiOpt.value\n\n yield* api.remove(action.payload as EntityId)\n yield* $.dispatchers.removeSucceeded(action.payload as EntityId)\n }).pipe(Effect.catchAll((e) => $.dispatchers.removeFailed(toErrorMessage(e)))),\n )\n }),\n { id: 'install' },\n )\n\n const controller = {\n make: (\n runtime: Logix.ModuleRuntime<\n CrudState<Entity, QueryInput>,\n CrudAction<Entity, QueryInput, EntityId, ExtraActions>\n >,\n ): CrudController<Entity, QueryInput, EntityId, ExtraActions> => ({\n runtime,\n getState: runtime.getState,\n dispatch: runtime.dispatch,\n controller: {\n list: (input: QueryInput) =>\n runtime.dispatch({ _tag: 'query', payload: input } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n save: (entity: Entity) =>\n runtime.dispatch({ _tag: 'save', payload: entity } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n remove: (id: EntityId) =>\n runtime.dispatch({ _tag: 'remove', payload: id } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n clearError: () =>\n runtime.dispatch({ _tag: 'clearError' } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n idField,\n },\n }),\n }\n\n const EXTEND_HANDLE = Symbol.for('logix.module.handle.extend')\n ;(module.tag as unknown as Record<PropertyKey, unknown>)[EXTEND_HANDLE] = (\n runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, EntityId, ExtraActions>>,\n base: Logix.ModuleHandle<Logix.AnyModuleShape>,\n ) => {\n const crud = controller.make(runtime)\n return {\n ...base,\n controller: crud.controller,\n services,\n }\n }\n\n return module.implement({\n initial: {\n items: Array.from(spec.initial ?? []),\n loading: false,\n error: undefined,\n lastQuery: undefined,\n total: undefined,\n } as CrudState<Entity, QueryInput>,\n logics: [install],\n }) as unknown as CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions>\n}\n\nexport const CRUDModule = Logix.Module.Manage.make({\n kind: 'crud',\n define: defineCrud,\n})\n"],"mappings":"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","names":["module","id"]}
1
+ {"version":3,"sources":["../src/Crud.ts","../src/internal/crud/Crud.ts"],"sourcesContent":["export * from './internal/crud/Crud.js'\nexport type * from './internal/crud/Crud.js'\n","import * as Logix from '@logixjs/core'\nimport { Effect, Option, Schema, ServiceMap } from 'effect'\n\nconst DefaultQueryInputSchema = Schema.Struct({\n pageSize: Schema.Number,\n})\n\nexport type CrudDefaultQueryInput = Schema.Schema.Type<typeof DefaultQueryInputSchema>\n\nexport interface CrudQueryResult<Entity> {\n readonly items: ReadonlyArray<Entity>\n readonly total?: number\n}\n\nexport interface CrudApi<Entity extends object, QueryInput, Id> {\n readonly list: (input: QueryInput) => Effect.Effect<CrudQueryResult<Entity>, unknown, never>\n readonly save: (entity: Entity) => Effect.Effect<Entity, unknown, never>\n readonly remove: (id: Id) => Effect.Effect<void, unknown, never>\n}\n\nexport interface CrudSpec<Entity extends object, QueryInput = CrudDefaultQueryInput, Id = string> {\n readonly entity: Schema.Schema<Entity>\n readonly query?: Schema.Schema<QueryInput>\n readonly id?: Schema.Schema<Id>\n readonly initial?: ReadonlyArray<Entity>\n /**\n * idField:\n * - The default primary key field for upsert/remove in reducers (default: \"id\").\n * - For more complex primary-key strategies, prefer handling results in a custom upper-layer Logic and writing back to state.\n */\n readonly idField?: string\n}\n\nexport type CrudState<Entity, QueryInput> = {\n readonly items: ReadonlyArray<Entity>\n readonly loading: boolean\n readonly error: string | undefined\n readonly lastQuery: QueryInput | undefined\n readonly total: number | undefined\n}\n\nexport type CrudActionMap<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly query: Schema.Schema<QueryInput>\n readonly querySucceeded: Schema.Schema<CrudQueryResult<Entity>>\n readonly queryFailed: Schema.Schema<string>\n\n readonly save: Schema.Schema<Entity>\n readonly saveSucceeded: Schema.Schema<Entity>\n readonly saveFailed: Schema.Schema<string>\n\n readonly remove: Schema.Schema<Id>\n readonly removeSucceeded: Schema.Schema<Id>\n readonly removeFailed: Schema.Schema<string>\n\n readonly clearError: Schema.Schema<void>\n} & ExtraActions\n\nexport type CrudAction<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.ActionsFromMap<CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudShape<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Shape<Schema.Schema<CrudState<Entity, QueryInput>>, CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudServices<Entity extends object, QueryInput, Id> = {\n readonly api: ServiceMap.Key<any, CrudApi<Entity, QueryInput, Id>>\n}\n\nexport type CrudHandleExt<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly controller: CrudController<Entity, QueryInput, Id, ExtraActions>['controller']\n readonly services: CrudServices<Entity, QueryInput, Id>\n}\n\nexport interface CrudController<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> {\n readonly runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, Id, ExtraActions>>\n readonly getState: Effect.Effect<CrudState<Entity, QueryInput>>\n readonly dispatch: (action: CrudAction<Entity, QueryInput, Id, ExtraActions>) => Effect.Effect<void>\n readonly controller: {\n readonly list: (input: QueryInput) => Effect.Effect<void>\n readonly save: (entity: Entity) => Effect.Effect<void>\n readonly remove: (id: Id) => Effect.Effect<void>\n readonly clearError: () => Effect.Effect<void>\n readonly idField: string\n }\n}\n\nconst makeActions = <Entity extends object, QueryInput, Id>(\n entity: Schema.Schema<Entity>,\n query: Schema.Schema<QueryInput>,\n id: Schema.Schema<Id>,\n): CrudActionMap<Entity, QueryInput, Id> =>\n ({\n query,\n querySucceeded: Schema.Struct({\n items: Schema.Array(entity),\n total: Schema.optional(Schema.Number),\n }) as Schema.Schema<CrudQueryResult<Entity>>,\n queryFailed: Schema.String,\n\n save: entity,\n saveSucceeded: entity,\n saveFailed: Schema.String,\n\n remove: id,\n removeSucceeded: id,\n removeFailed: Schema.String,\n\n clearError: Schema.Void,\n }) satisfies CrudActionMap<Entity, QueryInput, Id>\n\nconst toErrorMessage = (error: unknown): string => {\n if (error === null || error === undefined) return 'unknown error'\n if (typeof error === 'string') return error\n if (error instanceof Error && typeof error.message === 'string' && error.message.length > 0) {\n return error.message\n }\n if (typeof error === 'object') {\n if ('message' in error) {\n const message = (error as { readonly message?: unknown }).message\n if (typeof message === 'string' && message.length > 0) return message\n }\n try {\n const json = JSON.stringify(error)\n if (typeof json === 'string' && json.length > 0) return json\n } catch {\n // ignore\n }\n }\n return String(error)\n}\n\nconst upsertByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n entity: Entity,\n idField: string,\n): ReadonlyArray<Entity> => {\n const id = (entity as Record<string, unknown>)[idField]\n const idx = items.findIndex((x) => (x as Record<string, unknown>)[idField] === id)\n if (idx < 0) return [...items, entity]\n return items.map((x, i) => (i === idx ? entity : x))\n}\n\nconst removeByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n id: unknown,\n idField: string,\n): ReadonlyArray<Entity> => items.filter((x) => (x as Record<string, unknown>)[idField] !== id)\n\nexport type CrudModule<\n Id extends string,\n Entity extends object,\n QueryInput,\n EntityId,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Module.Module<\n Id,\n CrudShape<Entity, QueryInput, EntityId, ExtraActions>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>,\n unknown\n> & {\n readonly _kind: 'Module'\n readonly services: CrudServices<Entity, QueryInput, EntityId>\n}\n\nconst defineCrud = <\n Id extends string,\n Entity extends object,\n QueryInput = CrudDefaultQueryInput,\n EntityId = string,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n>(\n id: Id,\n spec: CrudSpec<Entity, QueryInput, EntityId>,\n extend?: Logix.Module.MakeExtendDef<\n Schema.Schema<CrudState<Entity, QueryInput>>,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n): CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions> => {\n const QuerySchema = (spec.query ?? DefaultQueryInputSchema) as Schema.Schema<QueryInput>\n const IdSchema = (spec.id ?? Schema.String) as Schema.Schema<EntityId>\n\n const idField = spec.idField ?? 'id'\n\n class Api extends ServiceMap.Service<Api, CrudApi<Entity, QueryInput, EntityId>>()( `${id}/crud/api`) {}\n\n const services = { api: Api } as const satisfies CrudServices<Entity, QueryInput, EntityId>\n\n const Actions = makeActions(spec.entity, QuerySchema, IdSchema)\n\n const StateSchema = Schema.Struct({\n items: Schema.Array(spec.entity),\n loading: Schema.Boolean,\n error: Schema.UndefinedOr(Schema.String),\n lastQuery: Schema.UndefinedOr(QuerySchema),\n total: Schema.UndefinedOr(Schema.Number),\n })\n\n type Reducers = Logix.ReducersFromMap<typeof StateSchema, CrudActionMap<Entity, QueryInput, EntityId>>\n\n const reducers: Reducers = {\n query: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('lastQuery')\n return {\n ...state,\n loading: true,\n error: undefined,\n lastQuery: action.payload,\n }\n },\n querySucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n sink?.('total')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: action.payload.items,\n total: action.payload.total,\n }\n },\n queryFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n save: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n saveSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: upsertByIdField(state.items, action.payload as Entity, idField),\n }\n },\n saveFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n remove: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n removeSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: removeByIdField(state.items, action.payload, idField),\n }\n },\n removeFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n clearError: (state, _action, sink) => {\n sink?.('error')\n return {\n ...state,\n error: undefined,\n }\n },\n }\n\n const def = {\n state: StateSchema,\n actions: Actions,\n reducers,\n schemas: { entity: spec.entity },\n meta: { kind: 'crud', idField },\n services,\n }\n\n const module = extend\n ? Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(\n id,\n def,\n extend as unknown as Logix.Module.MakeExtendDef<\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n )\n : Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(id, def)\n\n const install = module.logic(\n ($) =>\n Effect.gen(function* () {\n const missingApiMessage = `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`\n\n const runWithApi = (\n dispatchFailed: (message: string) => Effect.Effect<void, never, any>,\n run: (api: CrudApi<Entity, QueryInput, EntityId>) => Effect.Effect<unknown, unknown, any>,\n ): Effect.Effect<void, never, any> =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api as ServiceMap.Key<any, CrudApi<Entity, QueryInput, EntityId>>)\n if (Option.isNone(apiOpt)) {\n yield* dispatchFailed(missingApiMessage)\n return\n }\n yield* run(apiOpt.value)\n }).pipe(\n Effect.catch((error) => dispatchFailed(toErrorMessage(error))),\n Effect.asVoid,\n )\n\n yield* $.onAction('query').runFork((action) =>\n runWithApi($.dispatchers.queryFailed, (api) =>\n api.list(action.payload as QueryInput).pipe(Effect.flatMap((result) => $.dispatchers.querySucceeded(result))),\n ),\n )\n\n yield* $.onAction('save').runFork((action) =>\n runWithApi($.dispatchers.saveFailed, (api) =>\n api.save(action.payload as Entity).pipe(Effect.flatMap((entity) => $.dispatchers.saveSucceeded(entity))),\n ),\n )\n\n yield* $.onAction('remove').runFork((action) =>\n runWithApi($.dispatchers.removeFailed, (api) =>\n api.remove(action.payload as EntityId).pipe(\n Effect.flatMap(() => $.dispatchers.removeSucceeded(action.payload as EntityId)),\n ),\n ),\n )\n }),\n { id: 'install' },\n )\n\n const controller = {\n make: (\n runtime: Logix.ModuleRuntime<\n CrudState<Entity, QueryInput>,\n CrudAction<Entity, QueryInput, EntityId, ExtraActions>\n >,\n ): CrudController<Entity, QueryInput, EntityId, ExtraActions> => ({\n runtime,\n getState: runtime.getState,\n dispatch: runtime.dispatch,\n controller: {\n list: (input: QueryInput) =>\n runtime.dispatch({ _tag: 'query', payload: input } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n save: (entity: Entity) =>\n runtime.dispatch({ _tag: 'save', payload: entity } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n remove: (id: EntityId) =>\n runtime.dispatch({ _tag: 'remove', payload: id } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n clearError: () =>\n runtime.dispatch({ _tag: 'clearError' } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n idField,\n },\n }),\n }\n\n const EXTEND_HANDLE = Symbol.for('logix.module.handle.extend')\n ;(module.tag as unknown as Record<PropertyKey, unknown>)[EXTEND_HANDLE] = (\n runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, EntityId, ExtraActions>>,\n base: Logix.ModuleHandle<Logix.AnyModuleShape>,\n ) => {\n const crud = controller.make(runtime)\n return {\n ...base,\n controller: crud.controller,\n services,\n }\n }\n\n return module.implement({\n initial: {\n items: Array.from(spec.initial ?? []),\n loading: false,\n error: undefined,\n lastQuery: undefined,\n total: undefined,\n } as CrudState<Entity, QueryInput>,\n logics: [install],\n }) as unknown as CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions>\n}\n\nexport const CRUDModule = Logix.Module.Manage.make({\n kind: 'crud',\n define: defineCrud,\n})\n"],"mappings":"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","names":["module","id"]}
package/dist/Crud.d.cts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as Logix from '@logixjs/core';
2
- import { Schema, Effect } from 'effect';
2
+ import { Schema, Effect, ServiceMap } from 'effect';
3
3
 
4
4
  declare const DefaultQueryInputSchema: Schema.Struct<{
5
- pageSize: typeof Schema.Number;
5
+ readonly pageSize: Schema.Number;
6
6
  }>;
7
7
  type CrudDefaultQueryInput = Schema.Schema.Type<typeof DefaultQueryInputSchema>;
8
8
  interface CrudQueryResult<Entity> {
@@ -48,7 +48,7 @@ type CrudActionMap<Entity extends object, QueryInput, Id, ExtraActions extends R
48
48
  type CrudAction<Entity extends object, QueryInput, Id, ExtraActions extends Record<string, Logix.AnySchema> = {}> = Logix.ActionsFromMap<CrudActionMap<Entity, QueryInput, Id, ExtraActions>>;
49
49
  type CrudShape<Entity extends object, QueryInput, Id, ExtraActions extends Record<string, Logix.AnySchema> = {}> = Logix.Shape<Schema.Schema<CrudState<Entity, QueryInput>>, CrudActionMap<Entity, QueryInput, Id, ExtraActions>>;
50
50
  type CrudServices<Entity extends object, QueryInput, Id> = {
51
- readonly api: Logix.State.Tag<CrudApi<Entity, QueryInput, Id>>;
51
+ readonly api: ServiceMap.Key<any, CrudApi<Entity, QueryInput, Id>>;
52
52
  };
53
53
  type CrudHandleExt<Entity extends object, QueryInput, Id, ExtraActions extends Record<string, Logix.AnySchema> = {}> = {
54
54
  readonly controller: CrudController<Entity, QueryInput, Id, ExtraActions>['controller'];
package/dist/Crud.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as Logix from '@logixjs/core';
2
- import { Schema, Effect } from 'effect';
2
+ import { Schema, Effect, ServiceMap } from 'effect';
3
3
 
4
4
  declare const DefaultQueryInputSchema: Schema.Struct<{
5
- pageSize: typeof Schema.Number;
5
+ readonly pageSize: Schema.Number;
6
6
  }>;
7
7
  type CrudDefaultQueryInput = Schema.Schema.Type<typeof DefaultQueryInputSchema>;
8
8
  interface CrudQueryResult<Entity> {
@@ -48,7 +48,7 @@ type CrudActionMap<Entity extends object, QueryInput, Id, ExtraActions extends R
48
48
  type CrudAction<Entity extends object, QueryInput, Id, ExtraActions extends Record<string, Logix.AnySchema> = {}> = Logix.ActionsFromMap<CrudActionMap<Entity, QueryInput, Id, ExtraActions>>;
49
49
  type CrudShape<Entity extends object, QueryInput, Id, ExtraActions extends Record<string, Logix.AnySchema> = {}> = Logix.Shape<Schema.Schema<CrudState<Entity, QueryInput>>, CrudActionMap<Entity, QueryInput, Id, ExtraActions>>;
50
50
  type CrudServices<Entity extends object, QueryInput, Id> = {
51
- readonly api: Logix.State.Tag<CrudApi<Entity, QueryInput, Id>>;
51
+ readonly api: ServiceMap.Key<any, CrudApi<Entity, QueryInput, Id>>;
52
52
  };
53
53
  type CrudHandleExt<Entity extends object, QueryInput, Id, ExtraActions extends Record<string, Logix.AnySchema> = {}> = {
54
54
  readonly controller: CrudController<Entity, QueryInput, Id, ExtraActions>['controller'];
package/dist/Crud.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  CRUDModule
3
- } from "./chunk-SCBUT2ZQ.js";
3
+ } from "./chunk-CAROO5QW.js";
4
4
  export {
5
5
  CRUDModule
6
6
  };
@@ -1,6 +1,6 @@
1
1
  // src/internal/crud/Crud.ts
2
2
  import * as Logix from "@logixjs/core";
3
- import { Context, Effect, Option, Schema } from "effect";
3
+ import { Effect, Option, Schema, ServiceMap } from "effect";
4
4
  var DefaultQueryInputSchema = Schema.Struct({
5
5
  pageSize: Schema.Number
6
6
  });
@@ -49,7 +49,7 @@ var defineCrud = (id, spec, extend) => {
49
49
  const QuerySchema = spec.query ?? DefaultQueryInputSchema;
50
50
  const IdSchema = spec.id ?? Schema.String;
51
51
  const idField = spec.idField ?? "id";
52
- class Api extends Context.Tag(`${id}/crud/api`)() {
52
+ class Api extends ServiceMap.Service()(`${id}/crud/api`) {
53
53
  }
54
54
  const services = { api: Api };
55
55
  const Actions = makeActions(spec.entity, QuerySchema, IdSchema);
@@ -175,47 +175,37 @@ var defineCrud = (id, spec, extend) => {
175
175
  ) : Logix.Module.make(id, def);
176
176
  const install = module.logic(
177
177
  ($) => Effect.gen(function* () {
178
+ const missingApiMessage = `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`;
179
+ const runWithApi = (dispatchFailed, run) => Effect.gen(function* () {
180
+ const apiOpt = yield* Effect.serviceOption(services.api);
181
+ if (Option.isNone(apiOpt)) {
182
+ yield* dispatchFailed(missingApiMessage);
183
+ return;
184
+ }
185
+ yield* run(apiOpt.value);
186
+ }).pipe(
187
+ Effect.catch((error) => dispatchFailed(toErrorMessage(error))),
188
+ Effect.asVoid
189
+ );
178
190
  yield* $.onAction("query").runFork(
179
- (action) => Effect.gen(function* () {
180
- const apiOpt = yield* Effect.serviceOption(services.api);
181
- if (Option.isNone(apiOpt)) {
182
- yield* $.dispatchers.queryFailed(
183
- `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`
184
- );
185
- return;
186
- }
187
- const api = apiOpt.value;
188
- const result = yield* api.list(action.payload);
189
- yield* $.dispatchers.querySucceeded(result);
190
- }).pipe(Effect.catchAll((e) => $.dispatchers.queryFailed(toErrorMessage(e))))
191
+ (action) => runWithApi(
192
+ $.dispatchers.queryFailed,
193
+ (api) => api.list(action.payload).pipe(Effect.flatMap((result) => $.dispatchers.querySucceeded(result)))
194
+ )
191
195
  );
192
196
  yield* $.onAction("save").runFork(
193
- (action) => Effect.gen(function* () {
194
- const apiOpt = yield* Effect.serviceOption(services.api);
195
- if (Option.isNone(apiOpt)) {
196
- yield* $.dispatchers.saveFailed(
197
- `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`
198
- );
199
- return;
200
- }
201
- const api = apiOpt.value;
202
- const entity = yield* api.save(action.payload);
203
- yield* $.dispatchers.saveSucceeded(entity);
204
- }).pipe(Effect.catchAll((e) => $.dispatchers.saveFailed(toErrorMessage(e))))
197
+ (action) => runWithApi(
198
+ $.dispatchers.saveFailed,
199
+ (api) => api.save(action.payload).pipe(Effect.flatMap((entity) => $.dispatchers.saveSucceeded(entity)))
200
+ )
205
201
  );
206
202
  yield* $.onAction("remove").runFork(
207
- (action) => Effect.gen(function* () {
208
- const apiOpt = yield* Effect.serviceOption(services.api);
209
- if (Option.isNone(apiOpt)) {
210
- yield* $.dispatchers.removeFailed(
211
- `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`
212
- );
213
- return;
214
- }
215
- const api = apiOpt.value;
216
- yield* api.remove(action.payload);
217
- yield* $.dispatchers.removeSucceeded(action.payload);
218
- }).pipe(Effect.catchAll((e) => $.dispatchers.removeFailed(toErrorMessage(e))))
203
+ (action) => runWithApi(
204
+ $.dispatchers.removeFailed,
205
+ (api) => api.remove(action.payload).pipe(
206
+ Effect.flatMap(() => $.dispatchers.removeSucceeded(action.payload))
207
+ )
208
+ )
219
209
  );
220
210
  }),
221
211
  { id: "install" }
@@ -262,4 +252,4 @@ var CRUDModule = Logix.Module.Manage.make({
262
252
  export {
263
253
  CRUDModule
264
254
  };
265
- //# sourceMappingURL=chunk-SCBUT2ZQ.js.map
255
+ //# sourceMappingURL=chunk-CAROO5QW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/crud/Crud.ts"],"sourcesContent":["import * as Logix from '@logixjs/core'\nimport { Effect, Option, Schema, ServiceMap } from 'effect'\n\nconst DefaultQueryInputSchema = Schema.Struct({\n pageSize: Schema.Number,\n})\n\nexport type CrudDefaultQueryInput = Schema.Schema.Type<typeof DefaultQueryInputSchema>\n\nexport interface CrudQueryResult<Entity> {\n readonly items: ReadonlyArray<Entity>\n readonly total?: number\n}\n\nexport interface CrudApi<Entity extends object, QueryInput, Id> {\n readonly list: (input: QueryInput) => Effect.Effect<CrudQueryResult<Entity>, unknown, never>\n readonly save: (entity: Entity) => Effect.Effect<Entity, unknown, never>\n readonly remove: (id: Id) => Effect.Effect<void, unknown, never>\n}\n\nexport interface CrudSpec<Entity extends object, QueryInput = CrudDefaultQueryInput, Id = string> {\n readonly entity: Schema.Schema<Entity>\n readonly query?: Schema.Schema<QueryInput>\n readonly id?: Schema.Schema<Id>\n readonly initial?: ReadonlyArray<Entity>\n /**\n * idField:\n * - The default primary key field for upsert/remove in reducers (default: \"id\").\n * - For more complex primary-key strategies, prefer handling results in a custom upper-layer Logic and writing back to state.\n */\n readonly idField?: string\n}\n\nexport type CrudState<Entity, QueryInput> = {\n readonly items: ReadonlyArray<Entity>\n readonly loading: boolean\n readonly error: string | undefined\n readonly lastQuery: QueryInput | undefined\n readonly total: number | undefined\n}\n\nexport type CrudActionMap<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly query: Schema.Schema<QueryInput>\n readonly querySucceeded: Schema.Schema<CrudQueryResult<Entity>>\n readonly queryFailed: Schema.Schema<string>\n\n readonly save: Schema.Schema<Entity>\n readonly saveSucceeded: Schema.Schema<Entity>\n readonly saveFailed: Schema.Schema<string>\n\n readonly remove: Schema.Schema<Id>\n readonly removeSucceeded: Schema.Schema<Id>\n readonly removeFailed: Schema.Schema<string>\n\n readonly clearError: Schema.Schema<void>\n} & ExtraActions\n\nexport type CrudAction<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.ActionsFromMap<CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudShape<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Shape<Schema.Schema<CrudState<Entity, QueryInput>>, CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudServices<Entity extends object, QueryInput, Id> = {\n readonly api: ServiceMap.Key<any, CrudApi<Entity, QueryInput, Id>>\n}\n\nexport type CrudHandleExt<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly controller: CrudController<Entity, QueryInput, Id, ExtraActions>['controller']\n readonly services: CrudServices<Entity, QueryInput, Id>\n}\n\nexport interface CrudController<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> {\n readonly runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, Id, ExtraActions>>\n readonly getState: Effect.Effect<CrudState<Entity, QueryInput>>\n readonly dispatch: (action: CrudAction<Entity, QueryInput, Id, ExtraActions>) => Effect.Effect<void>\n readonly controller: {\n readonly list: (input: QueryInput) => Effect.Effect<void>\n readonly save: (entity: Entity) => Effect.Effect<void>\n readonly remove: (id: Id) => Effect.Effect<void>\n readonly clearError: () => Effect.Effect<void>\n readonly idField: string\n }\n}\n\nconst makeActions = <Entity extends object, QueryInput, Id>(\n entity: Schema.Schema<Entity>,\n query: Schema.Schema<QueryInput>,\n id: Schema.Schema<Id>,\n): CrudActionMap<Entity, QueryInput, Id> =>\n ({\n query,\n querySucceeded: Schema.Struct({\n items: Schema.Array(entity),\n total: Schema.optional(Schema.Number),\n }) as Schema.Schema<CrudQueryResult<Entity>>,\n queryFailed: Schema.String,\n\n save: entity,\n saveSucceeded: entity,\n saveFailed: Schema.String,\n\n remove: id,\n removeSucceeded: id,\n removeFailed: Schema.String,\n\n clearError: Schema.Void,\n }) satisfies CrudActionMap<Entity, QueryInput, Id>\n\nconst toErrorMessage = (error: unknown): string => {\n if (error === null || error === undefined) return 'unknown error'\n if (typeof error === 'string') return error\n if (error instanceof Error && typeof error.message === 'string' && error.message.length > 0) {\n return error.message\n }\n if (typeof error === 'object') {\n if ('message' in error) {\n const message = (error as { readonly message?: unknown }).message\n if (typeof message === 'string' && message.length > 0) return message\n }\n try {\n const json = JSON.stringify(error)\n if (typeof json === 'string' && json.length > 0) return json\n } catch {\n // ignore\n }\n }\n return String(error)\n}\n\nconst upsertByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n entity: Entity,\n idField: string,\n): ReadonlyArray<Entity> => {\n const id = (entity as Record<string, unknown>)[idField]\n const idx = items.findIndex((x) => (x as Record<string, unknown>)[idField] === id)\n if (idx < 0) return [...items, entity]\n return items.map((x, i) => (i === idx ? entity : x))\n}\n\nconst removeByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n id: unknown,\n idField: string,\n): ReadonlyArray<Entity> => items.filter((x) => (x as Record<string, unknown>)[idField] !== id)\n\nexport type CrudModule<\n Id extends string,\n Entity extends object,\n QueryInput,\n EntityId,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Module.Module<\n Id,\n CrudShape<Entity, QueryInput, EntityId, ExtraActions>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>,\n unknown\n> & {\n readonly _kind: 'Module'\n readonly services: CrudServices<Entity, QueryInput, EntityId>\n}\n\nconst defineCrud = <\n Id extends string,\n Entity extends object,\n QueryInput = CrudDefaultQueryInput,\n EntityId = string,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n>(\n id: Id,\n spec: CrudSpec<Entity, QueryInput, EntityId>,\n extend?: Logix.Module.MakeExtendDef<\n Schema.Schema<CrudState<Entity, QueryInput>>,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n): CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions> => {\n const QuerySchema = (spec.query ?? DefaultQueryInputSchema) as Schema.Schema<QueryInput>\n const IdSchema = (spec.id ?? Schema.String) as Schema.Schema<EntityId>\n\n const idField = spec.idField ?? 'id'\n\n class Api extends ServiceMap.Service<Api, CrudApi<Entity, QueryInput, EntityId>>()( `${id}/crud/api`) {}\n\n const services = { api: Api } as const satisfies CrudServices<Entity, QueryInput, EntityId>\n\n const Actions = makeActions(spec.entity, QuerySchema, IdSchema)\n\n const StateSchema = Schema.Struct({\n items: Schema.Array(spec.entity),\n loading: Schema.Boolean,\n error: Schema.UndefinedOr(Schema.String),\n lastQuery: Schema.UndefinedOr(QuerySchema),\n total: Schema.UndefinedOr(Schema.Number),\n })\n\n type Reducers = Logix.ReducersFromMap<typeof StateSchema, CrudActionMap<Entity, QueryInput, EntityId>>\n\n const reducers: Reducers = {\n query: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('lastQuery')\n return {\n ...state,\n loading: true,\n error: undefined,\n lastQuery: action.payload,\n }\n },\n querySucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n sink?.('total')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: action.payload.items,\n total: action.payload.total,\n }\n },\n queryFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n save: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n saveSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: upsertByIdField(state.items, action.payload as Entity, idField),\n }\n },\n saveFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n remove: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n removeSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: removeByIdField(state.items, action.payload, idField),\n }\n },\n removeFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n clearError: (state, _action, sink) => {\n sink?.('error')\n return {\n ...state,\n error: undefined,\n }\n },\n }\n\n const def = {\n state: StateSchema,\n actions: Actions,\n reducers,\n schemas: { entity: spec.entity },\n meta: { kind: 'crud', idField },\n services,\n }\n\n const module = extend\n ? Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(\n id,\n def,\n extend as unknown as Logix.Module.MakeExtendDef<\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n )\n : Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(id, def)\n\n const install = module.logic(\n ($) =>\n Effect.gen(function* () {\n const missingApiMessage = `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`\n\n const runWithApi = (\n dispatchFailed: (message: string) => Effect.Effect<void, never, any>,\n run: (api: CrudApi<Entity, QueryInput, EntityId>) => Effect.Effect<unknown, unknown, any>,\n ): Effect.Effect<void, never, any> =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api as ServiceMap.Key<any, CrudApi<Entity, QueryInput, EntityId>>)\n if (Option.isNone(apiOpt)) {\n yield* dispatchFailed(missingApiMessage)\n return\n }\n yield* run(apiOpt.value)\n }).pipe(\n Effect.catch((error) => dispatchFailed(toErrorMessage(error))),\n Effect.asVoid,\n )\n\n yield* $.onAction('query').runFork((action) =>\n runWithApi($.dispatchers.queryFailed, (api) =>\n api.list(action.payload as QueryInput).pipe(Effect.flatMap((result) => $.dispatchers.querySucceeded(result))),\n ),\n )\n\n yield* $.onAction('save').runFork((action) =>\n runWithApi($.dispatchers.saveFailed, (api) =>\n api.save(action.payload as Entity).pipe(Effect.flatMap((entity) => $.dispatchers.saveSucceeded(entity))),\n ),\n )\n\n yield* $.onAction('remove').runFork((action) =>\n runWithApi($.dispatchers.removeFailed, (api) =>\n api.remove(action.payload as EntityId).pipe(\n Effect.flatMap(() => $.dispatchers.removeSucceeded(action.payload as EntityId)),\n ),\n ),\n )\n }),\n { id: 'install' },\n )\n\n const controller = {\n make: (\n runtime: Logix.ModuleRuntime<\n CrudState<Entity, QueryInput>,\n CrudAction<Entity, QueryInput, EntityId, ExtraActions>\n >,\n ): CrudController<Entity, QueryInput, EntityId, ExtraActions> => ({\n runtime,\n getState: runtime.getState,\n dispatch: runtime.dispatch,\n controller: {\n list: (input: QueryInput) =>\n runtime.dispatch({ _tag: 'query', payload: input } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n save: (entity: Entity) =>\n runtime.dispatch({ _tag: 'save', payload: entity } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n remove: (id: EntityId) =>\n runtime.dispatch({ _tag: 'remove', payload: id } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n clearError: () =>\n runtime.dispatch({ _tag: 'clearError' } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n idField,\n },\n }),\n }\n\n const EXTEND_HANDLE = Symbol.for('logix.module.handle.extend')\n ;(module.tag as unknown as Record<PropertyKey, unknown>)[EXTEND_HANDLE] = (\n runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, EntityId, ExtraActions>>,\n base: Logix.ModuleHandle<Logix.AnyModuleShape>,\n ) => {\n const crud = controller.make(runtime)\n return {\n ...base,\n controller: crud.controller,\n services,\n }\n }\n\n return module.implement({\n initial: {\n items: Array.from(spec.initial ?? []),\n loading: false,\n error: undefined,\n lastQuery: undefined,\n total: undefined,\n } as CrudState<Entity, QueryInput>,\n logics: [install],\n }) as unknown as CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions>\n}\n\nexport const CRUDModule = Logix.Module.Manage.make({\n kind: 'crud',\n define: defineCrud,\n})\n"],"mappings":"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","names":["id"]}
package/dist/index.cjs CHANGED
@@ -85,7 +85,7 @@ var defineCrud = (id, spec, extend) => {
85
85
  const QuerySchema = spec.query ?? DefaultQueryInputSchema;
86
86
  const IdSchema = spec.id ?? import_effect.Schema.String;
87
87
  const idField = spec.idField ?? "id";
88
- class Api extends import_effect.Context.Tag(`${id}/crud/api`)() {
88
+ class Api extends import_effect.ServiceMap.Service()(`${id}/crud/api`) {
89
89
  }
90
90
  const services = { api: Api };
91
91
  const Actions = makeActions(spec.entity, QuerySchema, IdSchema);
@@ -211,47 +211,37 @@ var defineCrud = (id, spec, extend) => {
211
211
  ) : Logix.Module.make(id, def);
212
212
  const install = module2.logic(
213
213
  ($) => import_effect.Effect.gen(function* () {
214
+ const missingApiMessage = `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`;
215
+ const runWithApi = (dispatchFailed, run) => import_effect.Effect.gen(function* () {
216
+ const apiOpt = yield* import_effect.Effect.serviceOption(services.api);
217
+ if (import_effect.Option.isNone(apiOpt)) {
218
+ yield* dispatchFailed(missingApiMessage);
219
+ return;
220
+ }
221
+ yield* run(apiOpt.value);
222
+ }).pipe(
223
+ import_effect.Effect.catch((error) => dispatchFailed(toErrorMessage(error))),
224
+ import_effect.Effect.asVoid
225
+ );
214
226
  yield* $.onAction("query").runFork(
215
- (action) => import_effect.Effect.gen(function* () {
216
- const apiOpt = yield* import_effect.Effect.serviceOption(services.api);
217
- if (import_effect.Option.isNone(apiOpt)) {
218
- yield* $.dispatchers.queryFailed(
219
- `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`
220
- );
221
- return;
222
- }
223
- const api = apiOpt.value;
224
- const result = yield* api.list(action.payload);
225
- yield* $.dispatchers.querySucceeded(result);
226
- }).pipe(import_effect.Effect.catchAll((e) => $.dispatchers.queryFailed(toErrorMessage(e))))
227
+ (action) => runWithApi(
228
+ $.dispatchers.queryFailed,
229
+ (api) => api.list(action.payload).pipe(import_effect.Effect.flatMap((result) => $.dispatchers.querySucceeded(result)))
230
+ )
227
231
  );
228
232
  yield* $.onAction("save").runFork(
229
- (action) => import_effect.Effect.gen(function* () {
230
- const apiOpt = yield* import_effect.Effect.serviceOption(services.api);
231
- if (import_effect.Option.isNone(apiOpt)) {
232
- yield* $.dispatchers.saveFailed(
233
- `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`
234
- );
235
- return;
236
- }
237
- const api = apiOpt.value;
238
- const entity = yield* api.save(action.payload);
239
- yield* $.dispatchers.saveSucceeded(entity);
240
- }).pipe(import_effect.Effect.catchAll((e) => $.dispatchers.saveFailed(toErrorMessage(e))))
233
+ (action) => runWithApi(
234
+ $.dispatchers.saveFailed,
235
+ (api) => api.save(action.payload).pipe(import_effect.Effect.flatMap((entity) => $.dispatchers.saveSucceeded(entity)))
236
+ )
241
237
  );
242
238
  yield* $.onAction("remove").runFork(
243
- (action) => import_effect.Effect.gen(function* () {
244
- const apiOpt = yield* import_effect.Effect.serviceOption(services.api);
245
- if (import_effect.Option.isNone(apiOpt)) {
246
- yield* $.dispatchers.removeFailed(
247
- `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`
248
- );
249
- return;
250
- }
251
- const api = apiOpt.value;
252
- yield* api.remove(action.payload);
253
- yield* $.dispatchers.removeSucceeded(action.payload);
254
- }).pipe(import_effect.Effect.catchAll((e) => $.dispatchers.removeFailed(toErrorMessage(e))))
239
+ (action) => runWithApi(
240
+ $.dispatchers.removeFailed,
241
+ (api) => api.remove(action.payload).pipe(
242
+ import_effect.Effect.flatMap(() => $.dispatchers.removeSucceeded(action.payload))
243
+ )
244
+ )
255
245
  );
256
246
  }),
257
247
  { id: "install" }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/internal/crud/Crud.ts"],"sourcesContent":["export * from './Crud.js'\nexport type * from './Crud.js'\n","import * as Logix from '@logixjs/core'\nimport { Context, Effect, Option, Schema } from 'effect'\n\nconst DefaultQueryInputSchema = Schema.Struct({\n pageSize: Schema.Number,\n})\n\nexport type CrudDefaultQueryInput = Schema.Schema.Type<typeof DefaultQueryInputSchema>\n\nexport interface CrudQueryResult<Entity> {\n readonly items: ReadonlyArray<Entity>\n readonly total?: number\n}\n\nexport interface CrudApi<Entity extends object, QueryInput, Id> {\n readonly list: (input: QueryInput) => Effect.Effect<CrudQueryResult<Entity>, unknown, never>\n readonly save: (entity: Entity) => Effect.Effect<Entity, unknown, never>\n readonly remove: (id: Id) => Effect.Effect<void, unknown, never>\n}\n\nexport interface CrudSpec<Entity extends object, QueryInput = CrudDefaultQueryInput, Id = string> {\n readonly entity: Schema.Schema<Entity>\n readonly query?: Schema.Schema<QueryInput>\n readonly id?: Schema.Schema<Id>\n readonly initial?: ReadonlyArray<Entity>\n /**\n * idField:\n * - The default primary key field for upsert/remove in reducers (default: \"id\").\n * - For more complex primary-key strategies, prefer handling results in a custom upper-layer Logic and writing back to state.\n */\n readonly idField?: string\n}\n\nexport type CrudState<Entity, QueryInput> = {\n readonly items: ReadonlyArray<Entity>\n readonly loading: boolean\n readonly error: string | undefined\n readonly lastQuery: QueryInput | undefined\n readonly total: number | undefined\n}\n\nexport type CrudActionMap<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly query: Schema.Schema<QueryInput>\n readonly querySucceeded: Schema.Schema<CrudQueryResult<Entity>>\n readonly queryFailed: Schema.Schema<string>\n\n readonly save: Schema.Schema<Entity>\n readonly saveSucceeded: Schema.Schema<Entity>\n readonly saveFailed: Schema.Schema<string>\n\n readonly remove: Schema.Schema<Id>\n readonly removeSucceeded: Schema.Schema<Id>\n readonly removeFailed: Schema.Schema<string>\n\n readonly clearError: Schema.Schema<void>\n} & ExtraActions\n\nexport type CrudAction<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.ActionsFromMap<CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudShape<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Shape<Schema.Schema<CrudState<Entity, QueryInput>>, CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudServices<Entity extends object, QueryInput, Id> = {\n readonly api: Logix.State.Tag<CrudApi<Entity, QueryInput, Id>>\n}\n\nexport type CrudHandleExt<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly controller: CrudController<Entity, QueryInput, Id, ExtraActions>['controller']\n readonly services: CrudServices<Entity, QueryInput, Id>\n}\n\nexport interface CrudController<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> {\n readonly runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, Id, ExtraActions>>\n readonly getState: Effect.Effect<CrudState<Entity, QueryInput>>\n readonly dispatch: (action: CrudAction<Entity, QueryInput, Id, ExtraActions>) => Effect.Effect<void>\n readonly controller: {\n readonly list: (input: QueryInput) => Effect.Effect<void>\n readonly save: (entity: Entity) => Effect.Effect<void>\n readonly remove: (id: Id) => Effect.Effect<void>\n readonly clearError: () => Effect.Effect<void>\n readonly idField: string\n }\n}\n\nconst makeActions = <Entity extends object, QueryInput, Id>(\n entity: Schema.Schema<Entity>,\n query: Schema.Schema<QueryInput>,\n id: Schema.Schema<Id>,\n): CrudActionMap<Entity, QueryInput, Id> =>\n ({\n query,\n querySucceeded: Schema.Struct({\n items: Schema.Array(entity),\n total: Schema.optional(Schema.Number),\n }) as Schema.Schema<CrudQueryResult<Entity>>,\n queryFailed: Schema.String,\n\n save: entity,\n saveSucceeded: entity,\n saveFailed: Schema.String,\n\n remove: id,\n removeSucceeded: id,\n removeFailed: Schema.String,\n\n clearError: Schema.Void,\n }) satisfies CrudActionMap<Entity, QueryInput, Id>\n\nconst toErrorMessage = (error: unknown): string => {\n if (error === null || error === undefined) return 'unknown error'\n if (typeof error === 'string') return error\n if (error instanceof Error && typeof error.message === 'string' && error.message.length > 0) {\n return error.message\n }\n if (typeof error === 'object') {\n if ('message' in error) {\n const message = (error as { readonly message?: unknown }).message\n if (typeof message === 'string' && message.length > 0) return message\n }\n try {\n const json = JSON.stringify(error)\n if (typeof json === 'string' && json.length > 0) return json\n } catch {\n // ignore\n }\n }\n return String(error)\n}\n\nconst upsertByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n entity: Entity,\n idField: string,\n): ReadonlyArray<Entity> => {\n const id = (entity as Record<string, unknown>)[idField]\n const idx = items.findIndex((x) => (x as Record<string, unknown>)[idField] === id)\n if (idx < 0) return [...items, entity]\n return items.map((x, i) => (i === idx ? entity : x))\n}\n\nconst removeByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n id: unknown,\n idField: string,\n): ReadonlyArray<Entity> => items.filter((x) => (x as Record<string, unknown>)[idField] !== id)\n\nexport type CrudModule<\n Id extends string,\n Entity extends object,\n QueryInput,\n EntityId,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Module.Module<\n Id,\n CrudShape<Entity, QueryInput, EntityId, ExtraActions>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>,\n unknown\n> & {\n readonly _kind: 'Module'\n readonly services: CrudServices<Entity, QueryInput, EntityId>\n}\n\nconst defineCrud = <\n Id extends string,\n Entity extends object,\n QueryInput = CrudDefaultQueryInput,\n EntityId = string,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n>(\n id: Id,\n spec: CrudSpec<Entity, QueryInput, EntityId>,\n extend?: Logix.Module.MakeExtendDef<\n Schema.Schema<CrudState<Entity, QueryInput>>,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n): CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions> => {\n const QuerySchema = (spec.query ?? DefaultQueryInputSchema) as Schema.Schema<QueryInput>\n const IdSchema = (spec.id ?? Schema.String) as Schema.Schema<EntityId>\n\n const idField = spec.idField ?? 'id'\n\n class Api extends Context.Tag(`${id}/crud/api`)<Api, CrudApi<Entity, QueryInput, EntityId>>() {}\n\n const services = { api: Api } as const satisfies CrudServices<Entity, QueryInput, EntityId>\n\n const Actions = makeActions(spec.entity, QuerySchema, IdSchema)\n\n const StateSchema = Schema.Struct({\n items: Schema.Array(spec.entity),\n loading: Schema.Boolean,\n error: Schema.UndefinedOr(Schema.String),\n lastQuery: Schema.UndefinedOr(QuerySchema),\n total: Schema.UndefinedOr(Schema.Number),\n })\n\n type Reducers = Logix.ReducersFromMap<typeof StateSchema, CrudActionMap<Entity, QueryInput, EntityId>>\n\n const reducers: Reducers = {\n query: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('lastQuery')\n return {\n ...state,\n loading: true,\n error: undefined,\n lastQuery: action.payload,\n }\n },\n querySucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n sink?.('total')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: action.payload.items,\n total: action.payload.total,\n }\n },\n queryFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n save: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n saveSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: upsertByIdField(state.items, action.payload as Entity, idField),\n }\n },\n saveFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n remove: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n removeSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: removeByIdField(state.items, action.payload, idField),\n }\n },\n removeFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n clearError: (state, _action, sink) => {\n sink?.('error')\n return {\n ...state,\n error: undefined,\n }\n },\n }\n\n const def = {\n state: StateSchema,\n actions: Actions,\n reducers,\n schemas: { entity: spec.entity },\n meta: { kind: 'crud', idField },\n services,\n }\n\n const module = extend\n ? Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(\n id,\n def,\n extend as unknown as Logix.Module.MakeExtendDef<\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n )\n : Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(id, def)\n\n const install = module.logic(\n ($) =>\n Effect.gen(function* () {\n yield* $.onAction('query').runFork((action) =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api)\n if (Option.isNone(apiOpt)) {\n yield* $.dispatchers.queryFailed(\n `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`,\n )\n return\n }\n const api = apiOpt.value\n\n const result = yield* api.list(action.payload as QueryInput)\n yield* $.dispatchers.querySucceeded(result)\n }).pipe(Effect.catchAll((e) => $.dispatchers.queryFailed(toErrorMessage(e)))),\n )\n\n yield* $.onAction('save').runFork((action) =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api)\n if (Option.isNone(apiOpt)) {\n yield* $.dispatchers.saveFailed(\n `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`,\n )\n return\n }\n const api = apiOpt.value\n\n const entity = yield* api.save(action.payload as Entity)\n yield* $.dispatchers.saveSucceeded(entity)\n }).pipe(Effect.catchAll((e) => $.dispatchers.saveFailed(toErrorMessage(e)))),\n )\n\n yield* $.onAction('remove').runFork((action) =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api)\n if (Option.isNone(apiOpt)) {\n yield* $.dispatchers.removeFailed(\n `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`,\n )\n return\n }\n const api = apiOpt.value\n\n yield* api.remove(action.payload as EntityId)\n yield* $.dispatchers.removeSucceeded(action.payload as EntityId)\n }).pipe(Effect.catchAll((e) => $.dispatchers.removeFailed(toErrorMessage(e)))),\n )\n }),\n { id: 'install' },\n )\n\n const controller = {\n make: (\n runtime: Logix.ModuleRuntime<\n CrudState<Entity, QueryInput>,\n CrudAction<Entity, QueryInput, EntityId, ExtraActions>\n >,\n ): CrudController<Entity, QueryInput, EntityId, ExtraActions> => ({\n runtime,\n getState: runtime.getState,\n dispatch: runtime.dispatch,\n controller: {\n list: (input: QueryInput) =>\n runtime.dispatch({ _tag: 'query', payload: input } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n save: (entity: Entity) =>\n runtime.dispatch({ _tag: 'save', payload: entity } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n remove: (id: EntityId) =>\n runtime.dispatch({ _tag: 'remove', payload: id } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n clearError: () =>\n runtime.dispatch({ _tag: 'clearError' } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n idField,\n },\n }),\n }\n\n const EXTEND_HANDLE = Symbol.for('logix.module.handle.extend')\n ;(module.tag as unknown as Record<PropertyKey, unknown>)[EXTEND_HANDLE] = (\n runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, EntityId, ExtraActions>>,\n base: Logix.ModuleHandle<Logix.AnyModuleShape>,\n ) => {\n const crud = controller.make(runtime)\n return {\n ...base,\n controller: crud.controller,\n services,\n }\n }\n\n return module.implement({\n initial: {\n items: Array.from(spec.initial ?? []),\n loading: false,\n error: undefined,\n lastQuery: undefined,\n total: undefined,\n } as CrudState<Entity, QueryInput>,\n logics: [install],\n }) as unknown as CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions>\n}\n\nexport const CRUDModule = Logix.Module.Manage.make({\n kind: 'crud',\n define: defineCrud,\n})\n"],"mappings":"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","names":["module","id"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/internal/crud/Crud.ts"],"sourcesContent":["export * from './Crud.js'\nexport type * from './Crud.js'\n","import * as Logix from '@logixjs/core'\nimport { Effect, Option, Schema, ServiceMap } from 'effect'\n\nconst DefaultQueryInputSchema = Schema.Struct({\n pageSize: Schema.Number,\n})\n\nexport type CrudDefaultQueryInput = Schema.Schema.Type<typeof DefaultQueryInputSchema>\n\nexport interface CrudQueryResult<Entity> {\n readonly items: ReadonlyArray<Entity>\n readonly total?: number\n}\n\nexport interface CrudApi<Entity extends object, QueryInput, Id> {\n readonly list: (input: QueryInput) => Effect.Effect<CrudQueryResult<Entity>, unknown, never>\n readonly save: (entity: Entity) => Effect.Effect<Entity, unknown, never>\n readonly remove: (id: Id) => Effect.Effect<void, unknown, never>\n}\n\nexport interface CrudSpec<Entity extends object, QueryInput = CrudDefaultQueryInput, Id = string> {\n readonly entity: Schema.Schema<Entity>\n readonly query?: Schema.Schema<QueryInput>\n readonly id?: Schema.Schema<Id>\n readonly initial?: ReadonlyArray<Entity>\n /**\n * idField:\n * - The default primary key field for upsert/remove in reducers (default: \"id\").\n * - For more complex primary-key strategies, prefer handling results in a custom upper-layer Logic and writing back to state.\n */\n readonly idField?: string\n}\n\nexport type CrudState<Entity, QueryInput> = {\n readonly items: ReadonlyArray<Entity>\n readonly loading: boolean\n readonly error: string | undefined\n readonly lastQuery: QueryInput | undefined\n readonly total: number | undefined\n}\n\nexport type CrudActionMap<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly query: Schema.Schema<QueryInput>\n readonly querySucceeded: Schema.Schema<CrudQueryResult<Entity>>\n readonly queryFailed: Schema.Schema<string>\n\n readonly save: Schema.Schema<Entity>\n readonly saveSucceeded: Schema.Schema<Entity>\n readonly saveFailed: Schema.Schema<string>\n\n readonly remove: Schema.Schema<Id>\n readonly removeSucceeded: Schema.Schema<Id>\n readonly removeFailed: Schema.Schema<string>\n\n readonly clearError: Schema.Schema<void>\n} & ExtraActions\n\nexport type CrudAction<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.ActionsFromMap<CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudShape<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Shape<Schema.Schema<CrudState<Entity, QueryInput>>, CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudServices<Entity extends object, QueryInput, Id> = {\n readonly api: ServiceMap.Key<any, CrudApi<Entity, QueryInput, Id>>\n}\n\nexport type CrudHandleExt<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly controller: CrudController<Entity, QueryInput, Id, ExtraActions>['controller']\n readonly services: CrudServices<Entity, QueryInput, Id>\n}\n\nexport interface CrudController<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> {\n readonly runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, Id, ExtraActions>>\n readonly getState: Effect.Effect<CrudState<Entity, QueryInput>>\n readonly dispatch: (action: CrudAction<Entity, QueryInput, Id, ExtraActions>) => Effect.Effect<void>\n readonly controller: {\n readonly list: (input: QueryInput) => Effect.Effect<void>\n readonly save: (entity: Entity) => Effect.Effect<void>\n readonly remove: (id: Id) => Effect.Effect<void>\n readonly clearError: () => Effect.Effect<void>\n readonly idField: string\n }\n}\n\nconst makeActions = <Entity extends object, QueryInput, Id>(\n entity: Schema.Schema<Entity>,\n query: Schema.Schema<QueryInput>,\n id: Schema.Schema<Id>,\n): CrudActionMap<Entity, QueryInput, Id> =>\n ({\n query,\n querySucceeded: Schema.Struct({\n items: Schema.Array(entity),\n total: Schema.optional(Schema.Number),\n }) as Schema.Schema<CrudQueryResult<Entity>>,\n queryFailed: Schema.String,\n\n save: entity,\n saveSucceeded: entity,\n saveFailed: Schema.String,\n\n remove: id,\n removeSucceeded: id,\n removeFailed: Schema.String,\n\n clearError: Schema.Void,\n }) satisfies CrudActionMap<Entity, QueryInput, Id>\n\nconst toErrorMessage = (error: unknown): string => {\n if (error === null || error === undefined) return 'unknown error'\n if (typeof error === 'string') return error\n if (error instanceof Error && typeof error.message === 'string' && error.message.length > 0) {\n return error.message\n }\n if (typeof error === 'object') {\n if ('message' in error) {\n const message = (error as { readonly message?: unknown }).message\n if (typeof message === 'string' && message.length > 0) return message\n }\n try {\n const json = JSON.stringify(error)\n if (typeof json === 'string' && json.length > 0) return json\n } catch {\n // ignore\n }\n }\n return String(error)\n}\n\nconst upsertByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n entity: Entity,\n idField: string,\n): ReadonlyArray<Entity> => {\n const id = (entity as Record<string, unknown>)[idField]\n const idx = items.findIndex((x) => (x as Record<string, unknown>)[idField] === id)\n if (idx < 0) return [...items, entity]\n return items.map((x, i) => (i === idx ? entity : x))\n}\n\nconst removeByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n id: unknown,\n idField: string,\n): ReadonlyArray<Entity> => items.filter((x) => (x as Record<string, unknown>)[idField] !== id)\n\nexport type CrudModule<\n Id extends string,\n Entity extends object,\n QueryInput,\n EntityId,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Module.Module<\n Id,\n CrudShape<Entity, QueryInput, EntityId, ExtraActions>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>,\n unknown\n> & {\n readonly _kind: 'Module'\n readonly services: CrudServices<Entity, QueryInput, EntityId>\n}\n\nconst defineCrud = <\n Id extends string,\n Entity extends object,\n QueryInput = CrudDefaultQueryInput,\n EntityId = string,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n>(\n id: Id,\n spec: CrudSpec<Entity, QueryInput, EntityId>,\n extend?: Logix.Module.MakeExtendDef<\n Schema.Schema<CrudState<Entity, QueryInput>>,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n): CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions> => {\n const QuerySchema = (spec.query ?? DefaultQueryInputSchema) as Schema.Schema<QueryInput>\n const IdSchema = (spec.id ?? Schema.String) as Schema.Schema<EntityId>\n\n const idField = spec.idField ?? 'id'\n\n class Api extends ServiceMap.Service<Api, CrudApi<Entity, QueryInput, EntityId>>()( `${id}/crud/api`) {}\n\n const services = { api: Api } as const satisfies CrudServices<Entity, QueryInput, EntityId>\n\n const Actions = makeActions(spec.entity, QuerySchema, IdSchema)\n\n const StateSchema = Schema.Struct({\n items: Schema.Array(spec.entity),\n loading: Schema.Boolean,\n error: Schema.UndefinedOr(Schema.String),\n lastQuery: Schema.UndefinedOr(QuerySchema),\n total: Schema.UndefinedOr(Schema.Number),\n })\n\n type Reducers = Logix.ReducersFromMap<typeof StateSchema, CrudActionMap<Entity, QueryInput, EntityId>>\n\n const reducers: Reducers = {\n query: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('lastQuery')\n return {\n ...state,\n loading: true,\n error: undefined,\n lastQuery: action.payload,\n }\n },\n querySucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n sink?.('total')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: action.payload.items,\n total: action.payload.total,\n }\n },\n queryFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n save: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n saveSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: upsertByIdField(state.items, action.payload as Entity, idField),\n }\n },\n saveFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n remove: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n removeSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: removeByIdField(state.items, action.payload, idField),\n }\n },\n removeFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n clearError: (state, _action, sink) => {\n sink?.('error')\n return {\n ...state,\n error: undefined,\n }\n },\n }\n\n const def = {\n state: StateSchema,\n actions: Actions,\n reducers,\n schemas: { entity: spec.entity },\n meta: { kind: 'crud', idField },\n services,\n }\n\n const module = extend\n ? Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(\n id,\n def,\n extend as unknown as Logix.Module.MakeExtendDef<\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n )\n : Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(id, def)\n\n const install = module.logic(\n ($) =>\n Effect.gen(function* () {\n const missingApiMessage = `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`\n\n const runWithApi = (\n dispatchFailed: (message: string) => Effect.Effect<void, never, any>,\n run: (api: CrudApi<Entity, QueryInput, EntityId>) => Effect.Effect<unknown, unknown, any>,\n ): Effect.Effect<void, never, any> =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api as ServiceMap.Key<any, CrudApi<Entity, QueryInput, EntityId>>)\n if (Option.isNone(apiOpt)) {\n yield* dispatchFailed(missingApiMessage)\n return\n }\n yield* run(apiOpt.value)\n }).pipe(\n Effect.catch((error) => dispatchFailed(toErrorMessage(error))),\n Effect.asVoid,\n )\n\n yield* $.onAction('query').runFork((action) =>\n runWithApi($.dispatchers.queryFailed, (api) =>\n api.list(action.payload as QueryInput).pipe(Effect.flatMap((result) => $.dispatchers.querySucceeded(result))),\n ),\n )\n\n yield* $.onAction('save').runFork((action) =>\n runWithApi($.dispatchers.saveFailed, (api) =>\n api.save(action.payload as Entity).pipe(Effect.flatMap((entity) => $.dispatchers.saveSucceeded(entity))),\n ),\n )\n\n yield* $.onAction('remove').runFork((action) =>\n runWithApi($.dispatchers.removeFailed, (api) =>\n api.remove(action.payload as EntityId).pipe(\n Effect.flatMap(() => $.dispatchers.removeSucceeded(action.payload as EntityId)),\n ),\n ),\n )\n }),\n { id: 'install' },\n )\n\n const controller = {\n make: (\n runtime: Logix.ModuleRuntime<\n CrudState<Entity, QueryInput>,\n CrudAction<Entity, QueryInput, EntityId, ExtraActions>\n >,\n ): CrudController<Entity, QueryInput, EntityId, ExtraActions> => ({\n runtime,\n getState: runtime.getState,\n dispatch: runtime.dispatch,\n controller: {\n list: (input: QueryInput) =>\n runtime.dispatch({ _tag: 'query', payload: input } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n save: (entity: Entity) =>\n runtime.dispatch({ _tag: 'save', payload: entity } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n remove: (id: EntityId) =>\n runtime.dispatch({ _tag: 'remove', payload: id } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n clearError: () =>\n runtime.dispatch({ _tag: 'clearError' } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n idField,\n },\n }),\n }\n\n const EXTEND_HANDLE = Symbol.for('logix.module.handle.extend')\n ;(module.tag as unknown as Record<PropertyKey, unknown>)[EXTEND_HANDLE] = (\n runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, EntityId, ExtraActions>>,\n base: Logix.ModuleHandle<Logix.AnyModuleShape>,\n ) => {\n const crud = controller.make(runtime)\n return {\n ...base,\n controller: crud.controller,\n services,\n }\n }\n\n return module.implement({\n initial: {\n items: Array.from(spec.initial ?? []),\n loading: false,\n error: undefined,\n lastQuery: undefined,\n total: undefined,\n } as CrudState<Entity, QueryInput>,\n logics: [install],\n }) as unknown as CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions>\n}\n\nexport const CRUDModule = Logix.Module.Manage.make({\n kind: 'crud',\n define: defineCrud,\n})\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,YAAuB;AACvB,oBAAmD;AAEnD,IAAM,0BAA0B,qBAAO,OAAO;AAAA,EAC5C,UAAU,qBAAO;AACnB,CAAC;AAuGD,IAAM,cAAc,CAClB,QACA,OACA,QAEC;AAAA,EACC;AAAA,EACA,gBAAgB,qBAAO,OAAO;AAAA,IAC5B,OAAO,qBAAO,MAAM,MAAM;AAAA,IAC1B,OAAO,qBAAO,SAAS,qBAAO,MAAM;AAAA,EACtC,CAAC;AAAA,EACD,aAAa,qBAAO;AAAA,EAEpB,MAAM;AAAA,EACN,eAAe;AAAA,EACf,YAAY,qBAAO;AAAA,EAEnB,QAAQ;AAAA,EACR,iBAAiB;AAAA,EACjB,cAAc,qBAAO;AAAA,EAErB,YAAY,qBAAO;AACrB;AAEF,IAAM,iBAAiB,CAAC,UAA2B;AACjD,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,iBAAiB,SAAS,OAAO,MAAM,YAAY,YAAY,MAAM,QAAQ,SAAS,GAAG;AAC3F,WAAO,MAAM;AAAA,EACf;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,QAAI,aAAa,OAAO;AACtB,YAAM,UAAW,MAAyC;AAC1D,UAAI,OAAO,YAAY,YAAY,QAAQ,SAAS,EAAG,QAAO;AAAA,IAChE;AACA,QAAI;AACF,YAAM,OAAO,KAAK,UAAU,KAAK;AACjC,UAAI,OAAO,SAAS,YAAY,KAAK,SAAS,EAAG,QAAO;AAAA,IAC1D,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO,OAAO,KAAK;AACrB;AAEA,IAAM,kBAAkB,CACtB,OACA,QACA,YAC0B;AAC1B,QAAM,KAAM,OAAmC,OAAO;AACtD,QAAM,MAAM,MAAM,UAAU,CAAC,MAAO,EAA8B,OAAO,MAAM,EAAE;AACjF,MAAI,MAAM,EAAG,QAAO,CAAC,GAAG,OAAO,MAAM;AACrC,SAAO,MAAM,IAAI,CAAC,GAAG,MAAO,MAAM,MAAM,SAAS,CAAE;AACrD;AAEA,IAAM,kBAAkB,CACtB,OACA,IACA,YAC0B,MAAM,OAAO,CAAC,MAAO,EAA8B,OAAO,MAAM,EAAE;AAkB9F,IAAM,aAAa,CAOjB,IACA,MACA,WAK+D;AAC/D,QAAM,cAAe,KAAK,SAAS;AACnC,QAAM,WAAY,KAAK,MAAM,qBAAO;AAEpC,QAAM,UAAU,KAAK,WAAW;AAAA,EAEhC,MAAM,YAAY,yBAAW,QAAoD,EAAG,GAAG,EAAE,WAAW,EAAE;AAAA,EAAC;AAEvG,QAAM,WAAW,EAAE,KAAK,IAAI;AAE5B,QAAM,UAAU,YAAY,KAAK,QAAQ,aAAa,QAAQ;AAE9D,QAAM,cAAc,qBAAO,OAAO;AAAA,IAChC,OAAO,qBAAO,MAAM,KAAK,MAAM;AAAA,IAC/B,SAAS,qBAAO;AAAA,IAChB,OAAO,qBAAO,YAAY,qBAAO,MAAM;AAAA,IACvC,WAAW,qBAAO,YAAY,WAAW;AAAA,IACzC,OAAO,qBAAO,YAAY,qBAAO,MAAM;AAAA,EACzC,CAAC;AAID,QAAM,WAAqB;AAAA,IACzB,OAAO,CAAC,OAAO,QAAQ,SAAS;AAC9B,aAAO,SAAS;AAChB,aAAO,OAAO;AACd,aAAO,WAAW;AAClB,aAAO;AAAA,QACL,GAAG;AAAA,QACH,SAAS;AAAA,QACT,OAAO;AAAA,QACP,WAAW,OAAO;AAAA,MACpB;AAAA,IACF;AAAA,IACA,gBAAgB,CAAC,OAAO,QAAQ,SAAS;AACvC,aAAO,SAAS;AAChB,aAAO,OAAO;AACd,aAAO,OAAO;AACd,aAAO,OAAO;AACd,aAAO;AAAA,QACL,GAAG;AAAA,QACH,SAAS;AAAA,QACT,OAAO;AAAA,QACP,OAAO,OAAO,QAAQ;AAAA,QACtB,OAAO,OAAO,QAAQ;AAAA,MACxB;AAAA,IACF;AAAA,IACA,aAAa,CAAC,OAAO,QAAQ,SAAS;AACpC,aAAO,SAAS;AAChB,aAAO,OAAO;AACd,aAAO;AAAA,QACL,GAAG;AAAA,QACH,SAAS;AAAA,QACT,OAAO,OAAO;AAAA,MAChB;AAAA,IACF;AAAA,IAEA,MAAM,CAAC,OAAO,SAAS,SAAS;AAC9B,aAAO,SAAS;AAChB,aAAO,OAAO;AACd,aAAO;AAAA,QACL,GAAG;AAAA,QACH,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,eAAe,CAAC,OAAO,QAAQ,SAAS;AACtC,aAAO,SAAS;AAChB,aAAO,OAAO;AACd,aAAO,OAAO;AACd,aAAO;AAAA,QACL,GAAG;AAAA,QACH,SAAS;AAAA,QACT,OAAO;AAAA,QACP,OAAO,gBAAgB,MAAM,OAAO,OAAO,SAAmB,OAAO;AAAA,MACvE;AAAA,IACF;AAAA,IACA,YAAY,CAAC,OAAO,QAAQ,SAAS;AACnC,aAAO,SAAS;AAChB,aAAO,OAAO;AACd,aAAO;AAAA,QACL,GAAG;AAAA,QACH,SAAS;AAAA,QACT,OAAO,OAAO;AAAA,MAChB;AAAA,IACF;AAAA,IAEA,QAAQ,CAAC,OAAO,SAAS,SAAS;AAChC,aAAO,SAAS;AAChB,aAAO,OAAO;AACd,aAAO;AAAA,QACL,GAAG;AAAA,QACH,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,iBAAiB,CAAC,OAAO,QAAQ,SAAS;AACxC,aAAO,SAAS;AAChB,aAAO,OAAO;AACd,aAAO,OAAO;AACd,aAAO;AAAA,QACL,GAAG;AAAA,QACH,SAAS;AAAA,QACT,OAAO;AAAA,QACP,OAAO,gBAAgB,MAAM,OAAO,OAAO,SAAS,OAAO;AAAA,MAC7D;AAAA,IACF;AAAA,IACA,cAAc,CAAC,OAAO,QAAQ,SAAS;AACrC,aAAO,SAAS;AAChB,aAAO,OAAO;AACd,aAAO;AAAA,QACL,GAAG;AAAA,QACH,SAAS;AAAA,QACT,OAAO,OAAO;AAAA,MAChB;AAAA,IACF;AAAA,IAEA,YAAY,CAAC,OAAO,SAAS,SAAS;AACpC,aAAO,OAAO;AACd,aAAO;AAAA,QACL,GAAG;AAAA,QACH,OAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAEA,QAAM,MAAM;AAAA,IACV,OAAO;AAAA,IACP,SAAS;AAAA,IACT;AAAA,IACA,SAAS,EAAE,QAAQ,KAAK,OAAO;AAAA,IAC/B,MAAM,EAAE,MAAM,QAAQ,QAAQ;AAAA,IAC9B;AAAA,EACF;AAEA,QAAMA,UAAS,SACL,aAAO;AAAA,IAMX;AAAA,IACA;AAAA,IACA;AAAA,EAKF,IACM,aAAO,KAKX,IAAI,GAAG;AAEb,QAAM,UAAUA,QAAO;AAAA,IACrB,CAAC,MACC,qBAAO,IAAI,aAAa;AACtB,YAAM,oBAAoB,4DAA4D,EAAE;AAExF,YAAM,aAAa,CACjB,gBACA,QAEA,qBAAO,IAAI,aAAa;AACtB,cAAM,SAAS,OAAO,qBAAO,cAAc,SAAS,GAAiE;AACrH,YAAI,qBAAO,OAAO,MAAM,GAAG;AACzB,iBAAO,eAAe,iBAAiB;AACvC;AAAA,QACF;AACA,eAAO,IAAI,OAAO,KAAK;AAAA,MACzB,CAAC,EAAE;AAAA,QACD,qBAAO,MAAM,CAAC,UAAU,eAAe,eAAe,KAAK,CAAC,CAAC;AAAA,QAC7D,qBAAO;AAAA,MACT;AAEF,aAAO,EAAE,SAAS,OAAO,EAAE;AAAA,QAAQ,CAAC,WAClC;AAAA,UAAW,EAAE,YAAY;AAAA,UAAa,CAAC,QACrC,IAAI,KAAK,OAAO,OAAqB,EAAE,KAAK,qBAAO,QAAQ,CAAC,WAAW,EAAE,YAAY,eAAe,MAAM,CAAC,CAAC;AAAA,QAC9G;AAAA,MACF;AAEA,aAAO,EAAE,SAAS,MAAM,EAAE;AAAA,QAAQ,CAAC,WACjC;AAAA,UAAW,EAAE,YAAY;AAAA,UAAY,CAAC,QACpC,IAAI,KAAK,OAAO,OAAiB,EAAE,KAAK,qBAAO,QAAQ,CAAC,WAAW,EAAE,YAAY,cAAc,MAAM,CAAC,CAAC;AAAA,QACzG;AAAA,MACF;AAEA,aAAO,EAAE,SAAS,QAAQ,EAAE;AAAA,QAAQ,CAAC,WACnC;AAAA,UAAW,EAAE,YAAY;AAAA,UAAc,CAAC,QACtC,IAAI,OAAO,OAAO,OAAmB,EAAE;AAAA,YACrC,qBAAO,QAAQ,MAAM,EAAE,YAAY,gBAAgB,OAAO,OAAmB,CAAC;AAAA,UAChF;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAAA,IACH,EAAE,IAAI,UAAU;AAAA,EAClB;AAEA,QAAM,aAAa;AAAA,IACjB,MAAM,CACJ,aAIgE;AAAA,MAChE;AAAA,MACA,UAAU,QAAQ;AAAA,MAClB,UAAU,QAAQ;AAAA,MAClB,YAAY;AAAA,QACV,MAAM,CAAC,UACL,QAAQ,SAAS,EAAE,MAAM,SAAS,SAAS,MAAM,CAA2D;AAAA,QAC9G,MAAM,CAAC,WACL,QAAQ,SAAS,EAAE,MAAM,QAAQ,SAAS,OAAO,CAA2D;AAAA,QAC9G,QAAQ,CAACC,QACP,QAAQ,SAAS,EAAE,MAAM,UAAU,SAASA,IAAG,CAA2D;AAAA,QAC5G,YAAY,MACV,QAAQ,SAAS,EAAE,MAAM,aAAa,CAA2D;AAAA,QACnG;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,gBAAgB,uBAAO,IAAI,4BAA4B;AAC5D,EAACD,QAAO,IAAgD,aAAa,IAAI,CACxE,SACA,SACG;AACH,UAAM,OAAO,WAAW,KAAK,OAAO;AACpC,WAAO;AAAA,MACL,GAAG;AAAA,MACH,YAAY,KAAK;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAEA,SAAOA,QAAO,UAAU;AAAA,IACtB,SAAS;AAAA,MACP,OAAO,MAAM,KAAK,KAAK,WAAW,CAAC,CAAC;AAAA,MACpC,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,MACX,OAAO;AAAA,IACT;AAAA,IACA,QAAQ,CAAC,OAAO;AAAA,EAClB,CAAC;AACH;AAEO,IAAM,aAAmB,aAAO,OAAO,KAAK;AAAA,EACjD,MAAM;AAAA,EACN,QAAQ;AACV,CAAC;","names":["module","id"]}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  CRUDModule
3
- } from "./chunk-SCBUT2ZQ.js";
3
+ } from "./chunk-CAROO5QW.js";
4
4
  export {
5
5
  CRUDModule
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,11 @@
1
1
  {
2
2
  "name": "@logixjs/domain",
3
- "version": "0.0.2",
3
+ "version": "1.0.0",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "https://github.com/yoyooyooo/logix",
7
+ "directory": "packages/domain"
8
+ },
4
9
  "license": "Apache-2.0",
5
10
  "description": "Domain modules (based on Logix.Module.Manage) for Logix v3",
6
11
  "files": [
@@ -28,18 +33,18 @@
28
33
  "access": "public"
29
34
  },
30
35
  "dependencies": {
31
- "effect": "^3.19.8",
32
- "@logixjs/core": "0.0.2"
36
+ "effect": "4.0.0-beta.28",
37
+ "@logixjs/core": "1.0.0"
33
38
  },
34
39
  "devDependencies": {
35
- "@effect/vitest": "^0.27.0",
40
+ "@effect/vitest": "4.0.0-beta.28",
36
41
  "tsup": "^8.0.0",
37
42
  "typescript": "^5.8.2",
38
43
  "vitest": "^4.0.15"
39
44
  },
40
45
  "peerDependencies": {
41
- "effect": "^3.19.8",
42
- "@logixjs/core": "0.0.2"
46
+ "effect": "4.0.0-beta.28",
47
+ "@logixjs/core": "1.0.0"
43
48
  },
44
49
  "scripts": {
45
50
  "build": "tsup --format cjs,esm --dts",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/internal/crud/Crud.ts"],"sourcesContent":["import * as Logix from '@logixjs/core'\nimport { Context, Effect, Option, Schema } from 'effect'\n\nconst DefaultQueryInputSchema = Schema.Struct({\n pageSize: Schema.Number,\n})\n\nexport type CrudDefaultQueryInput = Schema.Schema.Type<typeof DefaultQueryInputSchema>\n\nexport interface CrudQueryResult<Entity> {\n readonly items: ReadonlyArray<Entity>\n readonly total?: number\n}\n\nexport interface CrudApi<Entity extends object, QueryInput, Id> {\n readonly list: (input: QueryInput) => Effect.Effect<CrudQueryResult<Entity>, unknown, never>\n readonly save: (entity: Entity) => Effect.Effect<Entity, unknown, never>\n readonly remove: (id: Id) => Effect.Effect<void, unknown, never>\n}\n\nexport interface CrudSpec<Entity extends object, QueryInput = CrudDefaultQueryInput, Id = string> {\n readonly entity: Schema.Schema<Entity>\n readonly query?: Schema.Schema<QueryInput>\n readonly id?: Schema.Schema<Id>\n readonly initial?: ReadonlyArray<Entity>\n /**\n * idField:\n * - The default primary key field for upsert/remove in reducers (default: \"id\").\n * - For more complex primary-key strategies, prefer handling results in a custom upper-layer Logic and writing back to state.\n */\n readonly idField?: string\n}\n\nexport type CrudState<Entity, QueryInput> = {\n readonly items: ReadonlyArray<Entity>\n readonly loading: boolean\n readonly error: string | undefined\n readonly lastQuery: QueryInput | undefined\n readonly total: number | undefined\n}\n\nexport type CrudActionMap<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly query: Schema.Schema<QueryInput>\n readonly querySucceeded: Schema.Schema<CrudQueryResult<Entity>>\n readonly queryFailed: Schema.Schema<string>\n\n readonly save: Schema.Schema<Entity>\n readonly saveSucceeded: Schema.Schema<Entity>\n readonly saveFailed: Schema.Schema<string>\n\n readonly remove: Schema.Schema<Id>\n readonly removeSucceeded: Schema.Schema<Id>\n readonly removeFailed: Schema.Schema<string>\n\n readonly clearError: Schema.Schema<void>\n} & ExtraActions\n\nexport type CrudAction<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.ActionsFromMap<CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudShape<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Shape<Schema.Schema<CrudState<Entity, QueryInput>>, CrudActionMap<Entity, QueryInput, Id, ExtraActions>>\n\nexport type CrudServices<Entity extends object, QueryInput, Id> = {\n readonly api: Logix.State.Tag<CrudApi<Entity, QueryInput, Id>>\n}\n\nexport type CrudHandleExt<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = {\n readonly controller: CrudController<Entity, QueryInput, Id, ExtraActions>['controller']\n readonly services: CrudServices<Entity, QueryInput, Id>\n}\n\nexport interface CrudController<\n Entity extends object,\n QueryInput,\n Id,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> {\n readonly runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, Id, ExtraActions>>\n readonly getState: Effect.Effect<CrudState<Entity, QueryInput>>\n readonly dispatch: (action: CrudAction<Entity, QueryInput, Id, ExtraActions>) => Effect.Effect<void>\n readonly controller: {\n readonly list: (input: QueryInput) => Effect.Effect<void>\n readonly save: (entity: Entity) => Effect.Effect<void>\n readonly remove: (id: Id) => Effect.Effect<void>\n readonly clearError: () => Effect.Effect<void>\n readonly idField: string\n }\n}\n\nconst makeActions = <Entity extends object, QueryInput, Id>(\n entity: Schema.Schema<Entity>,\n query: Schema.Schema<QueryInput>,\n id: Schema.Schema<Id>,\n): CrudActionMap<Entity, QueryInput, Id> =>\n ({\n query,\n querySucceeded: Schema.Struct({\n items: Schema.Array(entity),\n total: Schema.optional(Schema.Number),\n }) as Schema.Schema<CrudQueryResult<Entity>>,\n queryFailed: Schema.String,\n\n save: entity,\n saveSucceeded: entity,\n saveFailed: Schema.String,\n\n remove: id,\n removeSucceeded: id,\n removeFailed: Schema.String,\n\n clearError: Schema.Void,\n }) satisfies CrudActionMap<Entity, QueryInput, Id>\n\nconst toErrorMessage = (error: unknown): string => {\n if (error === null || error === undefined) return 'unknown error'\n if (typeof error === 'string') return error\n if (error instanceof Error && typeof error.message === 'string' && error.message.length > 0) {\n return error.message\n }\n if (typeof error === 'object') {\n if ('message' in error) {\n const message = (error as { readonly message?: unknown }).message\n if (typeof message === 'string' && message.length > 0) return message\n }\n try {\n const json = JSON.stringify(error)\n if (typeof json === 'string' && json.length > 0) return json\n } catch {\n // ignore\n }\n }\n return String(error)\n}\n\nconst upsertByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n entity: Entity,\n idField: string,\n): ReadonlyArray<Entity> => {\n const id = (entity as Record<string, unknown>)[idField]\n const idx = items.findIndex((x) => (x as Record<string, unknown>)[idField] === id)\n if (idx < 0) return [...items, entity]\n return items.map((x, i) => (i === idx ? entity : x))\n}\n\nconst removeByIdField = <Entity extends object>(\n items: ReadonlyArray<Entity>,\n id: unknown,\n idField: string,\n): ReadonlyArray<Entity> => items.filter((x) => (x as Record<string, unknown>)[idField] !== id)\n\nexport type CrudModule<\n Id extends string,\n Entity extends object,\n QueryInput,\n EntityId,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n> = Logix.Module.Module<\n Id,\n CrudShape<Entity, QueryInput, EntityId, ExtraActions>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>,\n unknown\n> & {\n readonly _kind: 'Module'\n readonly services: CrudServices<Entity, QueryInput, EntityId>\n}\n\nconst defineCrud = <\n Id extends string,\n Entity extends object,\n QueryInput = CrudDefaultQueryInput,\n EntityId = string,\n ExtraActions extends Record<string, Logix.AnySchema> = {},\n>(\n id: Id,\n spec: CrudSpec<Entity, QueryInput, EntityId>,\n extend?: Logix.Module.MakeExtendDef<\n Schema.Schema<CrudState<Entity, QueryInput>>,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n): CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions> => {\n const QuerySchema = (spec.query ?? DefaultQueryInputSchema) as Schema.Schema<QueryInput>\n const IdSchema = (spec.id ?? Schema.String) as Schema.Schema<EntityId>\n\n const idField = spec.idField ?? 'id'\n\n class Api extends Context.Tag(`${id}/crud/api`)<Api, CrudApi<Entity, QueryInput, EntityId>>() {}\n\n const services = { api: Api } as const satisfies CrudServices<Entity, QueryInput, EntityId>\n\n const Actions = makeActions(spec.entity, QuerySchema, IdSchema)\n\n const StateSchema = Schema.Struct({\n items: Schema.Array(spec.entity),\n loading: Schema.Boolean,\n error: Schema.UndefinedOr(Schema.String),\n lastQuery: Schema.UndefinedOr(QuerySchema),\n total: Schema.UndefinedOr(Schema.Number),\n })\n\n type Reducers = Logix.ReducersFromMap<typeof StateSchema, CrudActionMap<Entity, QueryInput, EntityId>>\n\n const reducers: Reducers = {\n query: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('lastQuery')\n return {\n ...state,\n loading: true,\n error: undefined,\n lastQuery: action.payload,\n }\n },\n querySucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n sink?.('total')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: action.payload.items,\n total: action.payload.total,\n }\n },\n queryFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n save: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n saveSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: upsertByIdField(state.items, action.payload as Entity, idField),\n }\n },\n saveFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n remove: (state, _action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: true,\n error: undefined,\n }\n },\n removeSucceeded: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n sink?.('items')\n return {\n ...state,\n loading: false,\n error: undefined,\n items: removeByIdField(state.items, action.payload, idField),\n }\n },\n removeFailed: (state, action, sink) => {\n sink?.('loading')\n sink?.('error')\n return {\n ...state,\n loading: false,\n error: action.payload,\n }\n },\n\n clearError: (state, _action, sink) => {\n sink?.('error')\n return {\n ...state,\n error: undefined,\n }\n },\n }\n\n const def = {\n state: StateSchema,\n actions: Actions,\n reducers,\n schemas: { entity: spec.entity },\n meta: { kind: 'crud', idField },\n services,\n }\n\n const module = extend\n ? Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(\n id,\n def,\n extend as unknown as Logix.Module.MakeExtendDef<\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n ExtraActions\n >,\n )\n : Logix.Module.make<\n Id,\n typeof StateSchema,\n CrudActionMap<Entity, QueryInput, EntityId>,\n CrudHandleExt<Entity, QueryInput, EntityId, ExtraActions>\n >(id, def)\n\n const install = module.logic(\n ($) =>\n Effect.gen(function* () {\n yield* $.onAction('query').runFork((action) =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api)\n if (Option.isNone(apiOpt)) {\n yield* $.dispatchers.queryFailed(\n `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`,\n )\n return\n }\n const api = apiOpt.value\n\n const result = yield* api.list(action.payload as QueryInput)\n yield* $.dispatchers.querySucceeded(result)\n }).pipe(Effect.catchAll((e) => $.dispatchers.queryFailed(toErrorMessage(e)))),\n )\n\n yield* $.onAction('save').runFork((action) =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api)\n if (Option.isNone(apiOpt)) {\n yield* $.dispatchers.saveFailed(\n `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`,\n )\n return\n }\n const api = apiOpt.value\n\n const entity = yield* api.save(action.payload as Entity)\n yield* $.dispatchers.saveSucceeded(entity)\n }).pipe(Effect.catchAll((e) => $.dispatchers.saveFailed(toErrorMessage(e)))),\n )\n\n yield* $.onAction('remove').runFork((action) =>\n Effect.gen(function* () {\n const apiOpt = yield* Effect.serviceOption(services.api)\n if (Option.isNone(apiOpt)) {\n yield* $.dispatchers.removeFailed(\n `[CRUDModule] Missing services.api; provide Layer.succeed(${id}.services.api, impl) via withLayer/withLayers/Runtime layer.`,\n )\n return\n }\n const api = apiOpt.value\n\n yield* api.remove(action.payload as EntityId)\n yield* $.dispatchers.removeSucceeded(action.payload as EntityId)\n }).pipe(Effect.catchAll((e) => $.dispatchers.removeFailed(toErrorMessage(e)))),\n )\n }),\n { id: 'install' },\n )\n\n const controller = {\n make: (\n runtime: Logix.ModuleRuntime<\n CrudState<Entity, QueryInput>,\n CrudAction<Entity, QueryInput, EntityId, ExtraActions>\n >,\n ): CrudController<Entity, QueryInput, EntityId, ExtraActions> => ({\n runtime,\n getState: runtime.getState,\n dispatch: runtime.dispatch,\n controller: {\n list: (input: QueryInput) =>\n runtime.dispatch({ _tag: 'query', payload: input } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n save: (entity: Entity) =>\n runtime.dispatch({ _tag: 'save', payload: entity } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n remove: (id: EntityId) =>\n runtime.dispatch({ _tag: 'remove', payload: id } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n clearError: () =>\n runtime.dispatch({ _tag: 'clearError' } as CrudAction<Entity, QueryInput, EntityId, ExtraActions>),\n idField,\n },\n }),\n }\n\n const EXTEND_HANDLE = Symbol.for('logix.module.handle.extend')\n ;(module.tag as unknown as Record<PropertyKey, unknown>)[EXTEND_HANDLE] = (\n runtime: Logix.ModuleRuntime<CrudState<Entity, QueryInput>, CrudAction<Entity, QueryInput, EntityId, ExtraActions>>,\n base: Logix.ModuleHandle<Logix.AnyModuleShape>,\n ) => {\n const crud = controller.make(runtime)\n return {\n ...base,\n controller: crud.controller,\n services,\n }\n }\n\n return module.implement({\n initial: {\n items: Array.from(spec.initial ?? []),\n loading: false,\n error: undefined,\n lastQuery: undefined,\n total: undefined,\n } as CrudState<Entity, QueryInput>,\n logics: [install],\n }) as unknown as CrudModule<Id, Entity, QueryInput, EntityId, ExtraActions>\n}\n\nexport const CRUDModule = Logix.Module.Manage.make({\n kind: 'crud',\n define: defineCrud,\n})\n"],"mappings":"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","names":["id"]}