@meeovi/layer-lists 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/app/components/features/archived.vue +64 -0
  2. package/app/components/features/bookmarks.vue +64 -0
  3. package/app/components/features/lists.vue +61 -0
  4. package/app/components/features/starred.vue +64 -0
  5. package/app/components/lists/ListItemCard.vue +190 -0
  6. package/app/components/lists/add-bookmark.vue +52 -0
  7. package/app/components/lists/add-list-item.vue +88 -0
  8. package/app/components/lists/add-list.vue +57 -0
  9. package/app/components/lists/lists.vue +6 -0
  10. package/app/components/lists/listsettings.vue +145 -0
  11. package/app/components/lists/update-bookmark.vue +267 -0
  12. package/app/components/lists/update-list.vue +192 -0
  13. package/app/components/media/MediaPlayer.vue +302 -0
  14. package/app/components/partials/addtolist.vue +233 -0
  15. package/app/components/partials/createListBtn.vue +95 -0
  16. package/app/components/partials/listBtn.vue +35 -0
  17. package/app/components/related/list.vue +33 -0
  18. package/app/components/related/relatedlists.vue +43 -0
  19. package/app/components/tasks/TaskItem.vue +204 -0
  20. package/app/composables/bookmarks/createBookmark.js +30 -0
  21. package/app/composables/bookmarks/deleteBookmark.js +15 -0
  22. package/app/composables/bookmarks/updateBookmark.js +15 -0
  23. package/app/composables/config.ts +17 -0
  24. package/app/composables/content/uploadFiles.js +41 -0
  25. package/app/composables/globals/useDirectusForm.ts +1 -0
  26. package/app/composables/lists/createList.js +25 -0
  27. package/app/composables/lists/deleteList.js +14 -0
  28. package/app/composables/lists/updateList.js +20 -0
  29. package/app/composables/lists/useBookmarks.js +69 -0
  30. package/app/composables/lists/useLists.js +120 -0
  31. package/app/composables/lists/usePlaylist.js +64 -0
  32. package/app/composables/lists/useSaved.js +29 -0
  33. package/app/composables/lists/useTasks.js +86 -0
  34. package/app/composables/lists/useWishlist.js +51 -0
  35. package/app/composables/module.ts +75 -0
  36. package/app/composables/providers/atproto.ts +156 -0
  37. package/app/composables/providers/directus.ts +145 -0
  38. package/app/composables/providers/memory.ts +127 -0
  39. package/app/composables/registry.ts +18 -0
  40. package/app/composables/types.ts +44 -0
  41. package/app/composables/useLists.ts +20 -0
  42. package/app/composables/utils/health.ts +16 -0
  43. package/app/composables/utils/transforms.ts +42 -0
  44. package/app/composables/utils/validation.ts +21 -0
  45. package/app/pages/lists/bookmark/[id].vue +76 -0
  46. package/app/pages/lists/index.vue +152 -0
  47. package/app/pages/lists/list/[...slug].vue +233 -0
  48. package/global.d.ts +1 -0
  49. package/index.js +3 -0
  50. package/package.json +3 -11
  51. package/shims.d.ts +7 -0
  52. package/tsconfig.json +21 -0
@@ -0,0 +1,29 @@
1
+ export const useSaved = () => {
2
+ const { $directus } = useNuxtApp();
3
+ const user = useSupabaseUser();
4
+
5
+ const isSaved = async (productId) => {
6
+ if (!user.value) return false;
7
+
8
+ const lists = await $directus.items('lists').readItems({
9
+ filter: { user: { _eq: user.value.id } },
10
+ fields: ['id']
11
+ });
12
+
13
+ const listIds = lists.map((l) => l.id);
14
+
15
+ if (listIds.length === 0) return false;
16
+
17
+ const saved = await $directus.items('list_items').readItems({
18
+ filter: {
19
+ list: { _in: listIds },
20
+ product: { _eq: productId }
21
+ }
22
+ });
23
+
24
+ return saved.length > 0;
25
+ };
26
+
27
+ return { isSaved };
28
+ };
29
+
@@ -0,0 +1,86 @@
1
+ export const useTasks = () => {
2
+ const { createList, addToList, getUserLists, updateListItem } = useLists()
3
+
4
+ const createTaskList = async (name, description = '') => {
5
+ return await createList({
6
+ name,
7
+ description,
8
+ type: 'tasks',
9
+ visibility: 'private'
10
+ })
11
+ }
12
+
13
+ const addTask = async (listId, taskData) => {
14
+ const { title, description, priority, due_date, labels } = taskData
15
+
16
+ return await addToList(listId, {
17
+ type: 'task',
18
+ title,
19
+ description,
20
+ priority: priority || 'medium',
21
+ due_date,
22
+ labels: labels || [],
23
+ completed: false,
24
+ date_created: new Date().toISOString(),
25
+ subtasks: []
26
+ })
27
+ }
28
+
29
+ const completeTask = async (itemId) => {
30
+ return await updateListItem(itemId, {
31
+ 'content.completed': true,
32
+ 'content.date_completed': new Date().toISOString()
33
+ })
34
+ }
35
+
36
+ const uncompleteTask = async (itemId) => {
37
+ return await updateListItem(itemId, {
38
+ 'content.completed': false,
39
+ 'content.date_completed': null
40
+ })
41
+ }
42
+
43
+ const updateTaskPriority = async (itemId, priority) => {
44
+ return await updateListItem(itemId, {
45
+ 'content.priority': priority
46
+ })
47
+ }
48
+
49
+ const updateTaskDueDate = async (itemId, dueDate) => {
50
+ return await updateListItem(itemId, {
51
+ 'content.due_date': dueDate
52
+ })
53
+ }
54
+
55
+ const addSubtask = async (itemId, subtaskTitle) => {
56
+ const { getListById } = useLists()
57
+ const item = await getListById(itemId)
58
+ const subtasks = item.content.subtasks || []
59
+
60
+ subtasks.push({
61
+ id: Date.now(),
62
+ title: subtaskTitle,
63
+ completed: false,
64
+ date_created: new Date().toISOString()
65
+ })
66
+
67
+ return await updateListItem(itemId, {
68
+ 'content.subtasks': subtasks
69
+ })
70
+ }
71
+
72
+ const getUserTaskLists = async () => {
73
+ return await getUserLists('tasks')
74
+ }
75
+
76
+ return {
77
+ createTaskList,
78
+ addTask,
79
+ completeTask,
80
+ uncompleteTask,
81
+ updateTaskPriority,
82
+ updateTaskDueDate,
83
+ addSubtask,
84
+ getUserTaskLists
85
+ }
86
+ }
@@ -0,0 +1,51 @@
1
+ export const useWishlist = () => {
2
+ const { createList, addToList, getUserLists, removeFromList } = useLists()
3
+
4
+ const createWishlist = async (name = 'My Wishlist', description = '') => {
5
+ return await createList({
6
+ name,
7
+ description,
8
+ type: 'wishlist',
9
+ visibility: 'private'
10
+ })
11
+ }
12
+
13
+ const addToWishlist = async (wishlistId, itemData) => {
14
+ const { title, url, price, image, description, category } = itemData
15
+
16
+ return await addToList(wishlistId, {
17
+ type: 'product',
18
+ title,
19
+ url,
20
+ price,
21
+ image,
22
+ description,
23
+ category,
24
+ date_added: new Date().toISOString(),
25
+ priority: itemData.priority || 'medium'
26
+ })
27
+ }
28
+
29
+ const getUserWishlists = async () => {
30
+ return await getUserLists('wishlist')
31
+ }
32
+
33
+ const removeFromWishlist = async (itemId) => {
34
+ return await removeFromList(itemId)
35
+ }
36
+
37
+ const updateWishlistItemPriority = async (itemId, priority) => {
38
+ const { updateListItem } = useLists()
39
+ return await updateListItem(itemId, {
40
+ 'content.priority': priority
41
+ })
42
+ }
43
+
44
+ return {
45
+ createWishlist,
46
+ addToWishlist,
47
+ getUserWishlists,
48
+ removeFromWishlist,
49
+ updateWishlistItemPriority
50
+ }
51
+ }
@@ -0,0 +1,75 @@
1
+ import {
2
+ defineAlternateModule,
3
+ useAlternateEventBus,
4
+ useAlternateContext,
5
+ type AlternateContext,
6
+ type ListsAdapter
7
+ } from '@meeovi/core'
8
+
9
+ import { getListsConfig } from './config'
10
+ import { registerListsProviderRuntime } from './registry'
11
+
12
+ export default defineAlternateModule({
13
+ id: 'lists',
14
+ adapters: {},
15
+
16
+ async setup(ctx: AlternateContext) {
17
+ const bus = useAlternateEventBus()
18
+ const config = getListsConfig()
19
+
20
+ // If a core adapter was registered under the `lists` key, register it
21
+ // into the local provider registry so UI code can consume it via `useLists()`.
22
+ // If a core adapter was registered under the `lists` key, adapt it
23
+ // into the `ListsProvider` shape and register it into the local registry.
24
+ try {
25
+ const runtimeAdapter = ctx.getAdapter('lists' as any) as ListsAdapter | undefined
26
+ if (runtimeAdapter) {
27
+ const provider = {
28
+ getList: (id: string) => runtimeAdapter.getList(id),
29
+ listLists: (params?: Record<string, unknown>) => runtimeAdapter.listLists(params),
30
+ createList: (data: Partial<Record<string, unknown>>) => runtimeAdapter.createList(data),
31
+ updateList: (id: string, data: Partial<Record<string, unknown>>) => runtimeAdapter.updateList(id, data),
32
+ deleteList: (id: string) => runtimeAdapter.deleteList(id),
33
+
34
+ addItem: (listId: string, item: Partial<Record<string, unknown>>) => runtimeAdapter.addItem(listId, item),
35
+ updateItem: (listId: string, itemId: string, data: Partial<Record<string, unknown>>) => runtimeAdapter.updateItem(listId, itemId, data),
36
+ deleteItem: (listId: string, itemId: string) => runtimeAdapter.deleteItem(listId, itemId),
37
+
38
+ reorderItems: runtimeAdapter.reorderItems ? (listId: string, itemIds: string[]) => runtimeAdapter.reorderItems!(listId, itemIds) : undefined
39
+ }
40
+
41
+ registerListsProviderRuntime('core', provider)
42
+ }
43
+ } catch (e) {
44
+ // noop
45
+ }
46
+
47
+ // Listen for runtime adapter registrations from ModuleRegistry
48
+ bus.on('adapter:registered' as any, (payload: any) => {
49
+ try {
50
+ if (payload?.key === 'lists') {
51
+ const runtimeAdapter = ctx.getAdapter('lists' as any) as ListsAdapter | undefined
52
+ if (runtimeAdapter) {
53
+ const provider = {
54
+ getList: (id: string) => runtimeAdapter.getList(id),
55
+ listLists: (params?: Record<string, unknown>) => runtimeAdapter.listLists(params),
56
+ createList: (data: Partial<Record<string, unknown>>) => runtimeAdapter.createList(data),
57
+ updateList: (id: string, data: Partial<Record<string, unknown>>) => runtimeAdapter.updateList(id, data),
58
+ deleteList: (id: string) => runtimeAdapter.deleteList(id),
59
+
60
+ addItem: (listId: string, item: Partial<Record<string, unknown>>) => runtimeAdapter.addItem(listId, item),
61
+ updateItem: (listId: string, itemId: string, data: Partial<Record<string, unknown>>) => runtimeAdapter.updateItem(listId, itemId, data),
62
+ deleteItem: (listId: string, itemId: string) => runtimeAdapter.deleteItem(listId, itemId),
63
+
64
+ reorderItems: runtimeAdapter.reorderItems ? (listId: string, itemIds: string[]) => runtimeAdapter.reorderItems!(listId, itemIds) : undefined
65
+ }
66
+
67
+ registerListsProviderRuntime('core', provider)
68
+ }
69
+ }
70
+ } catch (e) {
71
+ /* noop */
72
+ }
73
+ })
74
+ }
75
+ })
@@ -0,0 +1,156 @@
1
+ import { registerListsProvider } from '../registry'
2
+ import type { ListsProvider, List, ListItem } from '../types'
3
+ import { wrapSocialRequest } from '@meeovi/social'
4
+ import { transformList, transformItem } from '../utils/transforms'
5
+ import { validateListInput, validateItemInput } from '../utils/validation'
6
+ import { getListsConfig } from '../config'
7
+
8
+ async function atprotoFetch(path: string, options: RequestInit = {}) {
9
+ const { baseUrl, apiKey } = getListsConfig()
10
+
11
+ const res = await fetch(`${baseUrl}${path}`, {
12
+ ...options,
13
+ headers: {
14
+ 'Content-Type': 'application/json',
15
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
16
+ ...(options.headers || {})
17
+ }
18
+ })
19
+
20
+ if (!res.ok) {
21
+ const error: any = new Error(`ATProto error: ${res.status}`)
22
+ error.status = res.status
23
+ error.response = res
24
+ throw error
25
+ }
26
+
27
+ return res.json()
28
+ }
29
+
30
+ const AtprotoListsProvider: ListsProvider = {
31
+ async getList(id) {
32
+ return wrapSocialRequest('atproto', async () => {
33
+ const data = await atprotoFetch(`/xrpc/app.bsky.graph.getList?list=${id}`)
34
+ return transformList(data.list)
35
+ }, {
36
+ cacheKey: `atproto:list:${id}`,
37
+ ttlMs: 1000 * 30,
38
+ retry: true,
39
+ swr: true
40
+ })
41
+ },
42
+
43
+ async listLists() {
44
+ return wrapSocialRequest('atproto', async () => {
45
+ const data = await atprotoFetch(`/xrpc/app.bsky.graph.getLists`)
46
+ return data.lists.map(transformList)
47
+ }, {
48
+ cacheKey: `atproto:lists`,
49
+ ttlMs: 1000 * 30,
50
+ retry: true,
51
+ swr: true
52
+ })
53
+ },
54
+
55
+ async createList(data) {
56
+ validateListInput(data)
57
+
58
+ return wrapSocialRequest('atproto', async () => {
59
+ const result = await atprotoFetch(`/xrpc/app.bsky.graph.createList`, {
60
+ method: 'POST',
61
+ body: JSON.stringify({
62
+ name: data.title,
63
+ purpose: data.type ?? 'list',
64
+ description: data.metadata?.description ?? ''
65
+ })
66
+ })
67
+
68
+ return transformList(result)
69
+ })
70
+ },
71
+
72
+ async updateList(id, data) {
73
+ validateListInput(data)
74
+
75
+ return wrapSocialRequest('atproto', async () => {
76
+ const result = await atprotoFetch(`/xrpc/app.bsky.graph.updateList`, {
77
+ method: 'POST',
78
+ body: JSON.stringify({
79
+ list: id,
80
+ name: data.title,
81
+ description: data.metadata?.description
82
+ })
83
+ })
84
+
85
+ return transformList(result)
86
+ })
87
+ },
88
+
89
+ async deleteList(id) {
90
+ return wrapSocialRequest('atproto', async () => {
91
+ await atprotoFetch(`/xrpc/app.bsky.graph.deleteList`, {
92
+ method: 'POST',
93
+ body: JSON.stringify({ list: id })
94
+ })
95
+ })
96
+ },
97
+
98
+ async addItem(listId, item) {
99
+ validateItemInput(item)
100
+
101
+ return wrapSocialRequest('atproto', async () => {
102
+ const result = await atprotoFetch(`/xrpc/app.bsky.graph.addListItem`, {
103
+ method: 'POST',
104
+ body: JSON.stringify({
105
+ list: listId,
106
+ subject: item.title // ATProto uses "subject" for list entries
107
+ })
108
+ })
109
+
110
+ return transformItem(result)
111
+ })
112
+ },
113
+
114
+ async updateItem(listId, itemId, data) {
115
+ validateItemInput(data)
116
+
117
+ return wrapSocialRequest('atproto', async () => {
118
+ const result = await atprotoFetch(`/xrpc/app.bsky.graph.updateListItem`, {
119
+ method: 'POST',
120
+ body: JSON.stringify({
121
+ list: listId,
122
+ item: itemId,
123
+ ...data
124
+ })
125
+ })
126
+
127
+ return transformItem(result)
128
+ })
129
+ },
130
+
131
+ async deleteItem(listId, itemId) {
132
+ return wrapSocialRequest('atproto', async () => {
133
+ await atprotoFetch(`/xrpc/app.bsky.graph.deleteListItem`, {
134
+ method: 'POST',
135
+ body: JSON.stringify({
136
+ list: listId,
137
+ item: itemId
138
+ })
139
+ })
140
+ })
141
+ },
142
+
143
+ async reorderItems(listId, itemIds) {
144
+ return wrapSocialRequest('atproto', async () => {
145
+ await atprotoFetch(`/xrpc/app.bsky.graph.reorderListItems`, {
146
+ method: 'POST',
147
+ body: JSON.stringify({
148
+ list: listId,
149
+ items: itemIds
150
+ })
151
+ })
152
+ })
153
+ }
154
+ }
155
+
156
+ registerListsProvider('atproto', AtprotoListsProvider)
@@ -0,0 +1,145 @@
1
+ import { registerListsProvider } from '../registry'
2
+ import type { ListsProvider } from '../types'
3
+ import { getListsConfig } from '../config'
4
+
5
+ async function directusFetch(path: string, options: RequestInit = {}) {
6
+ const cfg = getListsConfig()
7
+ const base = cfg.baseUrl?.replace(/\/$/, '') || ''
8
+
9
+ const res = await fetch(`${base}${path}`, {
10
+ ...options,
11
+ headers: {
12
+ 'Content-Type': 'application/json',
13
+ ...(cfg.apiKey ? { Authorization: `Bearer ${cfg.apiKey}` } : {}),
14
+ ...(options.headers || {})
15
+ }
16
+ })
17
+
18
+ if (!res.ok) {
19
+ const err: any = new Error(`Directus error: ${res.status}`)
20
+ err.status = res.status
21
+ err.response = res
22
+ throw err
23
+ }
24
+
25
+ return res.json()
26
+ }
27
+
28
+ const DirectusListsProvider: ListsProvider = {
29
+ async getList(id) {
30
+ const json = await directusFetch(`/lists/${id}`)
31
+ return json.list ?? json
32
+ },
33
+
34
+ async listLists() {
35
+ const json = await directusFetch(`/lists`)
36
+ return json.lists ?? json
37
+ },
38
+
39
+ async createList(data) {
40
+ const json = await directusFetch(`/lists`, { method: 'POST', body: JSON.stringify(data) })
41
+ return json.list ?? json
42
+ },
43
+
44
+ async updateList(id, data) {
45
+ const json = await directusFetch(`/lists/${id}`, { method: 'PUT', body: JSON.stringify(data) })
46
+ return json.list ?? json
47
+ },
48
+
49
+ async deleteList(id) {
50
+ await directusFetch(`/lists/${id}`, { method: 'DELETE' })
51
+ },
52
+
53
+ async addItem(listId, item) {
54
+ const json = await directusFetch(`/lists/${listId}/items`, { method: 'POST', body: JSON.stringify(item) })
55
+ return json.item ?? json
56
+ },
57
+
58
+ async updateItem(listId, itemId, data) {
59
+ const json = await directusFetch(`/lists/${listId}/items/${itemId}`, { method: 'PUT', body: JSON.stringify(data) })
60
+ return json.item ?? json
61
+ },
62
+
63
+ async deleteItem(listId, itemId) {
64
+ await directusFetch(`/lists/${listId}/items/${itemId}`, { method: 'DELETE' })
65
+ },
66
+
67
+ async reorderItems(listId, itemIds) {
68
+ await directusFetch(`/lists/${listId}/reorder`, { method: 'POST', body: JSON.stringify({ items: itemIds }) })
69
+ },
70
+
71
+ async toggleComplete(listId, itemId, completed = true) {
72
+ try {
73
+ const json = await directusFetch(`/lists/${listId}/items/${itemId}/toggle`, { method: 'POST', body: JSON.stringify({ completed }) })
74
+ return json.item ?? json
75
+ } catch (e) {
76
+ const json = await directusFetch(`/lists/${listId}/items/${itemId}`, { method: 'PUT', body: JSON.stringify({ completed }) })
77
+ return json.item ?? json
78
+ }
79
+ },
80
+
81
+ async setDueDate(listId, itemId, dueDate) {
82
+ try {
83
+ const json = await directusFetch(`/lists/${listId}/items/${itemId}/due`, { method: 'POST', body: JSON.stringify({ dueDate }) })
84
+ return json.item ?? json
85
+ } catch (e) {
86
+ const json = await directusFetch(`/lists/${listId}/items/${itemId}`, { method: 'PUT', body: JSON.stringify({ metadata: { dueDate } }) })
87
+ return json.item ?? json
88
+ }
89
+ },
90
+
91
+ async setReminder(listId, itemId, reminder) {
92
+ try {
93
+ const json = await directusFetch(`/lists/${listId}/items/${itemId}/reminder`, { method: 'POST', body: JSON.stringify({ reminder }) })
94
+ return json.item ?? json
95
+ } catch (e) {
96
+ const json = await directusFetch(`/lists/${listId}/items/${itemId}`, { method: 'PUT', body: JSON.stringify({ metadata: { reminder } }) })
97
+ return json.item ?? json
98
+ }
99
+ },
100
+
101
+ async setPriority(listId, itemId, priority) {
102
+ try {
103
+ const json = await directusFetch(`/lists/${listId}/items/${itemId}/priority`, { method: 'POST', body: JSON.stringify({ priority }) })
104
+ return json.item ?? json
105
+ } catch (e) {
106
+ const json = await directusFetch(`/lists/${listId}/items/${itemId}`, { method: 'PUT', body: JSON.stringify({ metadata: { priority } }) })
107
+ return json.item ?? json
108
+ }
109
+ },
110
+
111
+ async shareList(listId, userId, role = 'editor') {
112
+ try {
113
+ await directusFetch(`/lists/${listId}/share`, { method: 'POST', body: JSON.stringify({ userId, role }) })
114
+ } catch (e) {
115
+ const json = await directusFetch(`/lists/${listId}`)
116
+ const list = json.list ?? json
117
+ const collaborators = (list.metadata?.collaborators || []).concat({ userId, role })
118
+ await directusFetch(`/lists/${listId}`, { method: 'PUT', body: JSON.stringify({ metadata: { ...(list.metadata || {}), collaborators } }) })
119
+ }
120
+ },
121
+
122
+ async searchItems(listId, query) {
123
+ try {
124
+ const json = await directusFetch(`/lists/${listId}/search?q=${encodeURIComponent(String(query))}`)
125
+ return json.items ?? json
126
+ } catch (e) {
127
+ const json = await directusFetch(`/lists/${listId}`)
128
+ const list = json.list ?? json
129
+ const q = String(query).toLowerCase()
130
+ return list.items.filter((i: any) => (i.title || '').toLowerCase().includes(q) || (i.description || '').toLowerCase().includes(q))
131
+ }
132
+ },
133
+
134
+ async archiveList(listId) {
135
+ try {
136
+ await directusFetch(`/lists/${listId}/archive`, { method: 'POST' })
137
+ } catch (e) {
138
+ const json = await directusFetch(`/lists/${listId}`)
139
+ const list = json.list ?? json
140
+ await directusFetch(`/lists/${listId}`, { method: 'PUT', body: JSON.stringify({ metadata: { ...(list.metadata || {}), archived: true } }) })
141
+ }
142
+ }
143
+ }
144
+
145
+ registerListsProvider('directus', DirectusListsProvider)
@@ -0,0 +1,127 @@
1
+ import { registerListsProvider } from '../registry'
2
+ import type { List, ListItem, ListsProvider } from '../types'
3
+ import { nanoid } from 'nanoid'
4
+
5
+ const lists = new Map<string, List>()
6
+
7
+ const MemoryListsProvider: ListsProvider = {
8
+ async getList(id) {
9
+ const list = lists.get(id)
10
+ if (!list) throw new Error(`List ${id} not found`)
11
+ return list
12
+ },
13
+
14
+ async listLists() {
15
+ return Array.from(lists.values())
16
+ },
17
+
18
+ async createList(data) {
19
+ const id = nanoid()
20
+ const list: List = {
21
+ id,
22
+ title: data.title || 'Untitled List',
23
+ type: data.type || 'list',
24
+ items: [],
25
+ metadata: data.metadata || {},
26
+ createdAt: new Date().toISOString(),
27
+ updatedAt: new Date().toISOString()
28
+ }
29
+ lists.set(id, list)
30
+ return list
31
+ },
32
+
33
+ async updateList(id, data) {
34
+ const list = await this.getList(id)
35
+ const updated = {
36
+ ...list,
37
+ ...data,
38
+ updatedAt: new Date().toISOString()
39
+ }
40
+ lists.set(id, updated)
41
+ return updated
42
+ },
43
+
44
+ async deleteList(id) {
45
+ lists.delete(id)
46
+ },
47
+
48
+ async addItem(listId, item) {
49
+ const list = await this.getList(listId)
50
+ const newItem: ListItem = {
51
+ id: nanoid(),
52
+ title: item.title || '',
53
+ description: item.description,
54
+ completed: item.completed || false,
55
+ position: list.items.length,
56
+ parentId: item.parentId,
57
+ metadata: item.metadata || {},
58
+ createdAt: new Date().toISOString(),
59
+ updatedAt: new Date().toISOString()
60
+ }
61
+ list.items.push(newItem)
62
+ return newItem
63
+ },
64
+
65
+ async updateItem(listId, itemId, data) {
66
+ const list = await this.getList(listId)
67
+ const item = list.items.find(i => i.id === itemId)
68
+ if (!item) throw new Error(`Item ${itemId} not found`)
69
+ Object.assign(item, data, { updatedAt: new Date().toISOString() })
70
+ return item
71
+ },
72
+
73
+ async deleteItem(listId, itemId) {
74
+ const list = await this.getList(listId)
75
+ list.items = list.items.filter(i => i.id !== itemId)
76
+ },
77
+
78
+ async reorderItems(listId, itemIds) {
79
+ const list = await this.getList(listId)
80
+ const newOrder = itemIds.map(id => list.items.find(i => i.id === id)!)
81
+ list.items = newOrder.map((item, index) => ({
82
+ ...item,
83
+ position: index
84
+ }))
85
+ },
86
+
87
+ async toggleComplete(listId, itemId, completed = true) {
88
+ const item = await this.updateItem(listId, itemId, { completed })
89
+ return item
90
+ },
91
+
92
+ async setDueDate(listId, itemId, dueDate) {
93
+ const item = await this.updateItem(listId, itemId, { metadata: { ...( (await this.getList(listId)).items.find(i => i.id === itemId)?.metadata || {} ), dueDate } })
94
+ return item
95
+ },
96
+
97
+ async setReminder(listId, itemId, reminder) {
98
+ const item = await this.updateItem(listId, itemId, { metadata: { ...( (await this.getList(listId)).items.find(i => i.id === itemId)?.metadata || {} ), reminder } })
99
+ return item
100
+ },
101
+
102
+ async setPriority(listId, itemId, priority) {
103
+ const item = await this.updateItem(listId, itemId, { metadata: { ...( (await this.getList(listId)).items.find(i => i.id === itemId)?.metadata || {} ), priority } })
104
+ return item
105
+ },
106
+
107
+ async shareList(listId, userId, role = 'editor') {
108
+ // memory store: attach collaborators in list.metadata.collaborators
109
+ const list = await this.getList(listId)
110
+ const collaborators = (list.metadata?.collaborators as any[]) || []
111
+ collaborators.push({ userId, role })
112
+ list.metadata = { ...(list.metadata || {}), collaborators }
113
+ },
114
+
115
+ async searchItems(listId, query) {
116
+ const list = await this.getList(listId)
117
+ const q = String(query).toLowerCase()
118
+ return list.items.filter(i => (i.title || '').toLowerCase().includes(q) || (i.description || '').toLowerCase().includes(q))
119
+ },
120
+
121
+ async archiveList(listId) {
122
+ const list = await this.getList(listId)
123
+ list.metadata = { ...(list.metadata || {}), archived: true }
124
+ }
125
+ }
126
+
127
+ registerListsProvider('memory', MemoryListsProvider)
@@ -0,0 +1,18 @@
1
+ import type { ListsProvider } from './types'
2
+
3
+ const providers: Record<string, ListsProvider> = {}
4
+
5
+ export function registerListsProvider(name: string, provider: ListsProvider) {
6
+ providers[name] = provider
7
+ }
8
+
9
+ // Allow runtime registration from other modules (e.g. via core ModuleRegistry adapters)
10
+ export function registerListsProviderRuntime(name: string, provider: ListsProvider) {
11
+ registerListsProvider(name, provider)
12
+ }
13
+
14
+ export function getListsProvider(name: string): ListsProvider {
15
+ const provider = providers[name]
16
+ if (!provider) throw new Error(`Lists provider "${name}" not found`)
17
+ return provider
18
+ }