@sudobility/sudojo_lib 0.0.101 → 0.0.103
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -0
- package/dist/hooks/useGamePlay.d.ts +3 -2
- package/dist/hooks/useGamePlay.d.ts.map +1 -1
- package/dist/hooks/useGamePlay.js +3 -3
- package/dist/hooks/useGamePlay.js.map +1 -1
- package/dist/stores/gamePlayStore.d.ts +2 -1
- package/dist/stores/gamePlayStore.d.ts.map +1 -1
- package/dist/stores/gamePlayStore.js +4 -4
- package/dist/stores/gamePlayStore.js.map +1 -1
- package/package.json +9 -9
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# @sudobility/sudojo_lib
|
|
2
|
+
|
|
3
|
+
Business logic library for Sudojo with React hooks for game state, hints, and Sudoku operations.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @sudobility/sudojo_lib
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { useBoard, useHint, useGameSession } from '@sudobility/sudojo_lib';
|
|
15
|
+
|
|
16
|
+
function GameComponent() {
|
|
17
|
+
const { board, setCell, clearCell } = useBoard(initialPuzzle);
|
|
18
|
+
const { getHint, isLoading } = useHint();
|
|
19
|
+
const { startGame, endGame, timer } = useGameSession();
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { validateBoard, isBoardComplete, getCandidates } from '@sudobility/sudojo_lib';
|
|
25
|
+
|
|
26
|
+
const isValid = validateBoard(board);
|
|
27
|
+
const candidates = getCandidates(board, row, col);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## API
|
|
31
|
+
|
|
32
|
+
- **Hooks**: `useBoard`, `useHint`, `useGameSession`, `useSudoku` (replaces deprecated `useGame`)
|
|
33
|
+
- **Utilities**: `validateBoard`, `isBoardComplete`, `getCandidates`, and more
|
|
34
|
+
- Shared between web (`sudojo_app`) and mobile (`sudojo_app_rn`) apps
|
|
35
|
+
|
|
36
|
+
## Development
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
bun run build # Build to dist/
|
|
40
|
+
bun run test # Run Vitest
|
|
41
|
+
bun run test:run # Run tests once
|
|
42
|
+
bun run typecheck # TypeScript check
|
|
43
|
+
bun run lint # ESLint
|
|
44
|
+
bun run check-all # Lint + typecheck + tests
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Related Packages
|
|
48
|
+
|
|
49
|
+
- `@sudobility/sudojo_client` -- API client hooks (peer dependency)
|
|
50
|
+
- `@sudobility/sudojo_types` -- Type definitions
|
|
51
|
+
- `sudojo_app` / `sudojo_app_rn` -- Consumer apps
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
BUSL-1.1
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* const { currentGame, startGame, updateProgress, clearGame } = useGamePlay({ slot: 'daily' });
|
|
11
11
|
*
|
|
12
12
|
* // Start a new game
|
|
13
|
-
* startGame('daily',
|
|
13
|
+
* startGame('daily', board, solution, { dailyDate: '2024-01-15' });
|
|
14
14
|
*
|
|
15
15
|
* // Update progress (debounced)
|
|
16
16
|
* updateProgress(inputString, pencilmarksString, isPencilMode, autoPencilmarks, elapsedTime);
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* clearGame();
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
|
+
import type { SolverBoard } from '@sudobility/sudojo_types';
|
|
22
23
|
import type { CurrentGame, CurrentGameMeta, GameSlot, GameSource } from '../types/currentGame';
|
|
23
24
|
export interface UseGamePlayOptions {
|
|
24
25
|
/** Which game slot to use (default: 'play') */
|
|
@@ -32,7 +33,7 @@ export interface UseGamePlayResult {
|
|
|
32
33
|
/** Whether there's an active game */
|
|
33
34
|
hasCurrentGame: boolean;
|
|
34
35
|
/** Start a new game */
|
|
35
|
-
startGame: (source: GameSource,
|
|
36
|
+
startGame: (source: GameSource, board: SolverBoard, solution: string, meta?: CurrentGameMeta) => void;
|
|
36
37
|
/** Update progress (debounced internally) */
|
|
37
38
|
updateProgress: (inputString: string, pencilmarksString: string, isPencilMode: boolean, autoPencilmarks: boolean, elapsedTime: number) => void;
|
|
38
39
|
/** Clear current game (call on completion) */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useGamePlay.d.ts","sourceRoot":"","sources":["../../src/hooks/useGamePlay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;
|
|
1
|
+
{"version":3,"file":"useGamePlay.d.ts","sourceRoot":"","sources":["../../src/hooks/useGamePlay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAE5D,OAAO,KAAK,EACV,WAAW,EACX,eAAe,EACf,QAAQ,EACR,UAAU,EACX,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,kBAAkB;IACjC,+CAA+C;IAC/C,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,yDAAyD;IACzD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,wCAAwC;IACxC,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,qCAAqC;IACrC,cAAc,EAAE,OAAO,CAAC;IACxB,uBAAuB;IACvB,SAAS,EAAE,CACT,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,eAAe,KACnB,IAAI,CAAC;IACV,6CAA6C;IAC7C,cAAc,EAAE,CACd,WAAW,EAAE,MAAM,EACnB,iBAAiB,EAAE,MAAM,EACzB,YAAY,EAAE,OAAO,EACrB,eAAe,EAAE,OAAO,EACxB,WAAW,EAAE,MAAM,KAChB,IAAI,CAAC;IACV,8CAA8C;IAC9C,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,wBAAgB,WAAW,CACzB,OAAO,GAAE,kBAAuB,GAC/B,iBAAiB,CA2GnB"}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* const { currentGame, startGame, updateProgress, clearGame } = useGamePlay({ slot: 'daily' });
|
|
11
11
|
*
|
|
12
12
|
* // Start a new game
|
|
13
|
-
* startGame('daily',
|
|
13
|
+
* startGame('daily', board, solution, { dailyDate: '2024-01-15' });
|
|
14
14
|
*
|
|
15
15
|
* // Update progress (debounced)
|
|
16
16
|
* updateProgress(inputString, pencilmarksString, isPencilMode, autoPencilmarks, elapsedTime);
|
|
@@ -28,10 +28,10 @@ export function useGamePlay(options = {}) {
|
|
|
28
28
|
const timeoutRef = useRef(null);
|
|
29
29
|
const pendingUpdate = useRef(null);
|
|
30
30
|
// Curry slot into startGame so callers keep the same signature
|
|
31
|
-
const startGame = useCallback((source,
|
|
31
|
+
const startGame = useCallback((source, board, solution, meta) => {
|
|
32
32
|
useGamePlayStore
|
|
33
33
|
.getState()
|
|
34
|
-
.startGame(slot, source,
|
|
34
|
+
.startGame(slot, source, board, solution, meta);
|
|
35
35
|
}, [slot]);
|
|
36
36
|
// Curry slot into clearGame
|
|
37
37
|
const clearGame = useCallback(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useGamePlay.js","sourceRoot":"","sources":["../../src/hooks/useGamePlay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"useGamePlay.js","sourceRoot":"","sources":["../../src/hooks/useGamePlay.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook for managing the current game\n *\n * Provides a clean API for starting, updating, and clearing the current game.\n * The `slot` option selects which independent store slot to use ('daily' or 'play'),\n * keeping the returned function signatures identical to before.\n *\n * @example\n * ```tsx\n * const { currentGame, startGame, updateProgress, clearGame } = useGamePlay({ slot: 'daily' });\n *\n * // Start a new game\n * startGame('daily', board, solution, { dailyDate: '2024-01-15' });\n *\n * // Update progress (debounced)\n * updateProgress(inputString, pencilmarksString, isPencilMode, autoPencilmarks, elapsedTime);\n *\n * // Clear on completion\n * clearGame();\n * ```\n */\n\nimport { useCallback, useEffect, useRef } from 'react';\nimport type { SolverBoard } from '@sudobility/sudojo_types';\nimport { useGamePlayStore } from '../stores/gamePlayStore';\nimport type {\n CurrentGame,\n CurrentGameMeta,\n GameSlot,\n GameSource,\n} from '../types/currentGame';\n\nexport interface UseGamePlayOptions {\n /** Which game slot to use (default: 'play') */\n slot?: GameSlot;\n /** Debounce delay for auto-save in ms (default: 2000) */\n autoSaveDelay?: number;\n}\n\nexport interface UseGamePlayResult {\n /** Current game state (null if none) */\n currentGame: CurrentGame | null;\n /** Whether there's an active game */\n hasCurrentGame: boolean;\n /** Start a new game */\n startGame: (\n source: GameSource,\n board: SolverBoard,\n solution: string,\n meta?: CurrentGameMeta\n ) => void;\n /** Update progress (debounced internally) */\n updateProgress: (\n inputString: string,\n pencilmarksString: string,\n isPencilMode: boolean,\n autoPencilmarks: boolean,\n elapsedTime: number\n ) => void;\n /** Clear current game (call on completion) */\n clearGame: () => void;\n}\n\nexport function useGamePlay(\n options: UseGamePlayOptions = {}\n): UseGamePlayResult {\n const { slot = 'play', autoSaveDelay = 2000 } = options;\n\n // Select the correct slot from the store\n const currentGame = useGamePlayStore(s =>\n slot === 'daily' ? s.dailyGame : s.playGame\n );\n\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const pendingUpdate = useRef<\n [string, string, boolean, boolean, number] | null\n >(null);\n\n // Curry slot into startGame so callers keep the same signature\n const startGame = useCallback(\n (\n source: GameSource,\n board: SolverBoard,\n solution: string,\n meta?: CurrentGameMeta\n ) => {\n useGamePlayStore\n .getState()\n .startGame(slot, source, board, solution, meta);\n },\n [slot]\n );\n\n // Curry slot into clearGame\n const clearGame = useCallback(() => {\n useGamePlayStore.getState().clearGame(slot);\n }, [slot]);\n\n // Throttled update — saves at most every autoSaveDelay ms.\n // Unlike debounce, throttle ensures periodic saves even when called\n // continuously (e.g. every second from the game timer).\n const updateProgress = useCallback(\n (\n inputString: string,\n pencilmarksString: string,\n isPencilMode: boolean,\n autoPencilmarks: boolean,\n elapsedTime: number\n ) => {\n pendingUpdate.current = [\n inputString,\n pencilmarksString,\n isPencilMode,\n autoPencilmarks,\n elapsedTime,\n ];\n\n // Only start a new timer if one isn't already running (throttle)\n if (!timeoutRef.current) {\n timeoutRef.current = setTimeout(() => {\n if (pendingUpdate.current) {\n useGamePlayStore\n .getState()\n .updateProgress(slot, ...pendingUpdate.current);\n pendingUpdate.current = null;\n }\n timeoutRef.current = null;\n }, autoSaveDelay);\n }\n },\n [slot, autoSaveDelay]\n );\n\n // Flush pending updates on unmount and beforeunload\n useEffect(() => {\n const flush = () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n if (pendingUpdate.current) {\n useGamePlayStore\n .getState()\n .updateProgress(slot, ...pendingUpdate.current);\n pendingUpdate.current = null;\n }\n };\n\n if (\n typeof window !== 'undefined' &&\n typeof window.addEventListener === 'function'\n ) {\n window.addEventListener('beforeunload', flush);\n }\n return () => {\n if (\n typeof window !== 'undefined' &&\n typeof window.removeEventListener === 'function'\n ) {\n window.removeEventListener('beforeunload', flush);\n }\n flush();\n };\n }, [slot]);\n\n return {\n currentGame,\n hasCurrentGame: currentGame !== null,\n startGame,\n updateProgress,\n clearGame,\n };\n}\n"]}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Persists two independent game slots (daily / play) to localStorage
|
|
5
5
|
* so users can resume where they left off without one overwriting the other.
|
|
6
6
|
*/
|
|
7
|
+
import type { SolverBoard } from '@sudobility/sudojo_types';
|
|
7
8
|
import type { CurrentGame, CurrentGameMeta, GameSlot, GameSource } from '../types/currentGame';
|
|
8
9
|
export interface GamePlayState {
|
|
9
10
|
/** Daily game slot (null if none) */
|
|
@@ -11,7 +12,7 @@ export interface GamePlayState {
|
|
|
11
12
|
/** Play game slot — levels & entered puzzles (null if none) */
|
|
12
13
|
playGame: CurrentGame | null;
|
|
13
14
|
/** Start a new game in the given slot */
|
|
14
|
-
startGame: (slot: GameSlot, source: GameSource,
|
|
15
|
+
startGame: (slot: GameSlot, source: GameSource, board: SolverBoard, solution: string, meta?: CurrentGameMeta) => void;
|
|
15
16
|
/** Update game progress for the given slot */
|
|
16
17
|
updateProgress: (slot: GameSlot, inputString: string, pencilmarksString: string, isPencilMode: boolean, autoPencilmarks: boolean, elapsedTime: number) => void;
|
|
17
18
|
/** Clear the game in the given slot */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gamePlayStore.d.ts","sourceRoot":"","sources":["../../src/stores/gamePlayStore.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EACV,WAAW,EACX,eAAe,EACf,QAAQ,EACR,UAAU,EACX,MAAM,sBAAsB,CAAC;AAO9B,MAAM,WAAW,aAAa;IAC5B,qCAAqC;IACrC,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9B,+DAA+D;IAC/D,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAC;IAE7B,yCAAyC;IACzC,SAAS,EAAE,CACT,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,UAAU,EAClB,
|
|
1
|
+
{"version":3,"file":"gamePlayStore.d.ts","sourceRoot":"","sources":["../../src/stores/gamePlayStore.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EACV,WAAW,EACX,eAAe,EACf,QAAQ,EACR,UAAU,EACX,MAAM,sBAAsB,CAAC;AAO9B,MAAM,WAAW,aAAa;IAC5B,qCAAqC;IACrC,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9B,+DAA+D;IAC/D,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAC;IAE7B,yCAAyC;IACzC,SAAS,EAAE,CACT,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,eAAe,KACnB,IAAI,CAAC;IAEV,8CAA8C;IAC9C,cAAc,EAAE,CACd,IAAI,EAAE,QAAQ,EACd,WAAW,EAAE,MAAM,EACnB,iBAAiB,EAAE,MAAM,EACzB,YAAY,EAAE,OAAO,EACrB,eAAe,EAAE,OAAO,EACxB,WAAW,EAAE,MAAM,KAChB,IAAI,CAAC;IAEV,uCAAuC;IACvC,SAAS,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;CACrC;AAED,eAAO,MAAM,gBAAgB;;;;;;;;;;;;EA+F5B,CAAC"}
|
|
@@ -10,18 +10,18 @@ const slotField = (slot) => slot === 'daily' ? 'dailyGame' : 'playGame';
|
|
|
10
10
|
export const useGamePlayStore = create()(persist((set, get) => ({
|
|
11
11
|
dailyGame: null,
|
|
12
12
|
playGame: null,
|
|
13
|
-
startGame: (slot, source,
|
|
13
|
+
startGame: (slot, source, board, solution, meta = {}) => {
|
|
14
14
|
const now = new Date().toISOString();
|
|
15
15
|
set({
|
|
16
16
|
[slotField(slot)]: {
|
|
17
17
|
source,
|
|
18
|
-
puzzle,
|
|
18
|
+
puzzle: board.original,
|
|
19
19
|
solution,
|
|
20
20
|
meta,
|
|
21
21
|
inputString: '0'.repeat(81),
|
|
22
|
-
pencilmarksString:
|
|
22
|
+
pencilmarksString: board.pencilmark.numbers,
|
|
23
23
|
isPencilMode: false,
|
|
24
|
-
autoPencilmarks:
|
|
24
|
+
autoPencilmarks: board.pencilmark.autopencil,
|
|
25
25
|
elapsedTime: 0,
|
|
26
26
|
startedAt: now,
|
|
27
27
|
updatedAt: now,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gamePlayStore.js","sourceRoot":"","sources":["../../src/stores/gamePlayStore.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"gamePlayStore.js","sourceRoot":"","sources":["../../src/stores/gamePlayStore.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Zustand store for current game state\n *\n * Persists two independent game slots (daily / play) to localStorage\n * so users can resume where they left off without one overwriting the other.\n */\n\nimport { create } from 'zustand';\nimport { persist } from 'zustand/middleware';\nimport type { SolverBoard } from '@sudobility/sudojo_types';\nimport type {\n CurrentGame,\n CurrentGameMeta,\n GameSlot,\n GameSource,\n} from '../types/currentGame';\n\ntype SlotField = 'dailyGame' | 'playGame';\n\nconst slotField = (slot: GameSlot): SlotField =>\n slot === 'daily' ? 'dailyGame' : 'playGame';\n\nexport interface GamePlayState {\n /** Daily game slot (null if none) */\n dailyGame: CurrentGame | null;\n /** Play game slot — levels & entered puzzles (null if none) */\n playGame: CurrentGame | null;\n\n /** Start a new game in the given slot */\n startGame: (\n slot: GameSlot,\n source: GameSource,\n board: SolverBoard,\n solution: string,\n meta?: CurrentGameMeta\n ) => void;\n\n /** Update game progress for the given slot */\n updateProgress: (\n slot: GameSlot,\n inputString: string,\n pencilmarksString: string,\n isPencilMode: boolean,\n autoPencilmarks: boolean,\n elapsedTime: number\n ) => void;\n\n /** Clear the game in the given slot */\n clearGame: (slot: GameSlot) => void;\n}\n\nexport const useGamePlayStore = create<GamePlayState>()(\n persist(\n (set, get) => ({\n dailyGame: null,\n playGame: null,\n\n startGame: (slot, source, board, solution, meta = {}) => {\n const now = new Date().toISOString();\n set({\n [slotField(slot)]: {\n source,\n puzzle: board.original,\n solution,\n meta,\n inputString: '0'.repeat(81),\n pencilmarksString: board.pencilmark.numbers,\n isPencilMode: false,\n autoPencilmarks: board.pencilmark.autopencil,\n elapsedTime: 0,\n startedAt: now,\n updatedAt: now,\n },\n });\n },\n\n updateProgress: (\n slot,\n inputString,\n pencilmarksString,\n isPencilMode,\n autoPencilmarks,\n elapsedTime\n ) => {\n const field = slotField(slot);\n const current = get()[field];\n if (!current) return;\n\n set({\n [field]: {\n ...current,\n inputString,\n pencilmarksString,\n isPencilMode,\n autoPencilmarks,\n elapsedTime,\n updatedAt: new Date().toISOString(),\n },\n });\n },\n\n clearGame: slot => set({ [slotField(slot)]: null }),\n }),\n {\n name: 'sudojo-current-game',\n version: 2,\n migrate: (persistedState: unknown, version: number) => {\n if (version === 0) {\n // v0 had a single `currentGame` field — move it to the right slot\n const old = persistedState as {\n currentGame?: CurrentGame | null;\n };\n const game = old.currentGame ?? null;\n const isDailySource = game?.source === 'daily';\n\n // Remove the old field and populate the new slots\n const { currentGame: _, ...rest } = old as Record<string, unknown>;\n return {\n ...rest,\n dailyGame: isDailySource ? game : null,\n playGame: isDailySource ? null : game,\n };\n }\n if (version === 1) {\n const state = persistedState as {\n dailyGame?: CurrentGame | null;\n playGame?: CurrentGame | null;\n };\n const addAuto = (game: CurrentGame | null | undefined) =>\n game\n ? {\n ...game,\n autoPencilmarks:\n 'autoPencilmarks' in game ? game.autoPencilmarks : false,\n }\n : (game ?? null);\n return {\n ...state,\n dailyGame: addAuto(state.dailyGame),\n playGame: addAuto(state.playGame),\n };\n }\n return persistedState;\n },\n }\n )\n);\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sudobility/sudojo_lib",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.103",
|
|
4
4
|
"description": "Business logic library for Sudojo app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -46,10 +46,10 @@
|
|
|
46
46
|
"author": "Sudobility team",
|
|
47
47
|
"license": "BUSL-1.1",
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@sudobility/di": "^1.5.
|
|
50
|
-
"@sudobility/sudojo_client": "^0.0.
|
|
51
|
-
"@sudobility/sudojo_types": "^1.2.
|
|
52
|
-
"@sudobility/types": "^1.9.
|
|
49
|
+
"@sudobility/di": "^1.5.45",
|
|
50
|
+
"@sudobility/sudojo_client": "^0.0.86",
|
|
51
|
+
"@sudobility/sudojo_types": "^1.2.37",
|
|
52
|
+
"@sudobility/types": "^1.9.57",
|
|
53
53
|
"@tanstack/react-query": "^5.90.11",
|
|
54
54
|
"@testing-library/dom": "^10.4.1",
|
|
55
55
|
"@testing-library/react": "^16.3.0",
|
|
@@ -72,10 +72,10 @@
|
|
|
72
72
|
"zustand": "^5.0.9"
|
|
73
73
|
},
|
|
74
74
|
"peerDependencies": {
|
|
75
|
-
"@sudobility/di": "^1.5.
|
|
76
|
-
"@sudobility/sudojo_client": "^0.0.
|
|
77
|
-
"@sudobility/sudojo_types": "^1.2.
|
|
78
|
-
"@sudobility/types": "^1.9.
|
|
75
|
+
"@sudobility/di": "^1.5.45",
|
|
76
|
+
"@sudobility/sudojo_client": "^0.0.86",
|
|
77
|
+
"@sudobility/sudojo_types": "^1.2.37",
|
|
78
|
+
"@sudobility/types": "^1.9.57",
|
|
79
79
|
"@tanstack/react-query": ">=5.0.0",
|
|
80
80
|
"react": ">=18.0.0",
|
|
81
81
|
"zustand": ">=5.0.0"
|