@seedprotocol/sdk 0.1.46 → 0.1.48

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 (147) hide show
  1. package/dist/bin.js.map +1 -1
  2. package/dist/constants-BLctWkrn.js.map +1 -1
  3. package/dist/{index-BEzB8REh.js → index-DMIKRod-.js} +3684 -3660
  4. package/dist/index-DMIKRod-.js.map +1 -0
  5. package/dist/{index-ChGsdGPJ.js → index-wKss7188.js} +10 -10
  6. package/dist/index-wKss7188.js.map +1 -0
  7. package/dist/main.js +7 -7
  8. package/dist/{seed.schema.config-jKpK-lR6.js → seed.schema.config-C0M8Rcti.js} +7 -7
  9. package/dist/seed.schema.config-C0M8Rcti.js.map +1 -0
  10. package/dist/src/AppStateSchema.ts +10 -0
  11. package/dist/src/Attestation.ts +21 -0
  12. package/dist/src/ConfigSchema.ts +15 -0
  13. package/dist/src/ItemProperty.ts +383 -0
  14. package/dist/src/MetadataSchema.ts +28 -0
  15. package/dist/src/ModelSchema.ts +46 -0
  16. package/dist/src/ModelUidSchema.ts +16 -0
  17. package/dist/src/PropertyUidSchema.ts +16 -0
  18. package/dist/src/Schema.ts +17 -0
  19. package/dist/src/SeedSchema.ts +29 -0
  20. package/dist/src/VersionSchema.ts +16 -0
  21. package/dist/src/actors.ts +10 -0
  22. package/dist/src/addModelsToDb.ts +150 -0
  23. package/dist/src/allItems.ts +23 -0
  24. package/dist/src/arweave.ts +37 -0
  25. package/dist/src/browser.app.db.config.ts +27 -0
  26. package/dist/src/browser.seed.db.config.ts +33 -0
  27. package/dist/src/browser.ts +30 -0
  28. package/dist/src/checkStatus.ts +50 -0
  29. package/dist/src/client.ts +85 -0
  30. package/dist/src/configureFs.ts +81 -0
  31. package/dist/src/connectToDb.ts +74 -0
  32. package/dist/src/connectionManager.ts +67 -0
  33. package/dist/src/constants.ts +118 -0
  34. package/dist/src/create.ts +39 -0
  35. package/dist/src/createItem.ts +15 -0
  36. package/dist/src/createItemMachine.ts +37 -0
  37. package/dist/src/createPublishAttempt.ts +16 -0
  38. package/dist/src/createSeeds.ts +24 -0
  39. package/dist/src/createVersion.ts +33 -0
  40. package/dist/src/db.ts +247 -0
  41. package/dist/src/dbMachine.ts +181 -0
  42. package/dist/src/deleteItem.ts +19 -0
  43. package/dist/src/download.ts +289 -0
  44. package/dist/src/drizzle.ts +82 -0
  45. package/dist/src/environment.ts +15 -0
  46. package/dist/src/eventBus.ts +5 -0
  47. package/dist/src/events.ts +14 -0
  48. package/dist/src/fetchDataFromEas.ts +90 -0
  49. package/dist/src/fetchDbData.ts +16 -0
  50. package/dist/src/fetchRelatedItems.ts +179 -0
  51. package/dist/src/fetchSeeds.ts +44 -0
  52. package/dist/src/fetchVersions.ts +41 -0
  53. package/dist/src/files.ts +16 -0
  54. package/dist/src/fragment-masking.ts +87 -0
  55. package/dist/src/fsProxy.ts +36 -0
  56. package/dist/src/getItem.ts +189 -0
  57. package/dist/src/getItemProperties.ts +162 -0
  58. package/dist/src/getItems.ts +75 -0
  59. package/dist/src/getMetadata.ts +40 -0
  60. package/dist/src/getModelSchemas.ts +88 -0
  61. package/dist/src/getSchemaForModel.ts +42 -0
  62. package/dist/src/getSeedData.ts +34 -0
  63. package/dist/src/getVersionData.ts +58 -0
  64. package/dist/src/getVersionsForVersionUids.ts +39 -0
  65. package/dist/src/globalMachine.ts +259 -0
  66. package/dist/src/gql.ts +113 -0
  67. package/dist/src/graphql.ts +3201 -0
  68. package/dist/src/helpers.ts +150 -0
  69. package/dist/src/hydrateExistingItem.ts +137 -0
  70. package/dist/src/hydrateFromDb.ts +254 -0
  71. package/dist/src/hydrateNewItem.ts +34 -0
  72. package/dist/src/index.d.ts +5 -0
  73. package/dist/src/index.ts +21 -0
  74. package/dist/src/init.ts +61 -0
  75. package/dist/src/initialize.ts +127 -0
  76. package/dist/src/internalMachine.ts +220 -0
  77. package/dist/src/item.ts +324 -0
  78. package/dist/src/itemMachineAll.ts +158 -0
  79. package/dist/src/itemMachineSingle.ts +175 -0
  80. package/dist/src/loadAppDb.ts +47 -0
  81. package/dist/src/logger.ts +33 -0
  82. package/dist/src/machine.ts +55 -0
  83. package/dist/src/machines.ts +58 -0
  84. package/dist/src/migrate.ts +288 -0
  85. package/dist/src/model.ts +71 -0
  86. package/dist/src/modelClass.ts +19 -0
  87. package/dist/src/node.app.db.config.ts +40 -0
  88. package/dist/src/prepareDb.ts +34 -0
  89. package/dist/src/preparePublishRequestData.ts +81 -0
  90. package/dist/src/processItems.ts +71 -0
  91. package/dist/src/property.ts +161 -0
  92. package/dist/src/propertyMachine.ts +154 -0
  93. package/dist/src/publish.ts +31 -0
  94. package/dist/src/publishMachine.ts +77 -0
  95. package/dist/src/queries.ts +13 -0
  96. package/dist/src/read.ts +174 -0
  97. package/dist/src/recoverDeletedItem.ts +14 -0
  98. package/dist/src/request.ts +54 -0
  99. package/dist/src/requestAll.ts +157 -0
  100. package/dist/src/resolveRelatedValue.ts +348 -0
  101. package/dist/src/resolveRemoteStorage.ts +87 -0
  102. package/dist/src/save.ts +183 -0
  103. package/dist/src/saveConfig.ts +79 -0
  104. package/dist/src/saveDataToDb.ts +145 -0
  105. package/dist/src/saveMetadata.ts +18 -0
  106. package/dist/src/saveValueToDb.ts +94 -0
  107. package/dist/src/seed.schema.config.ts +25 -0
  108. package/dist/src/seed.ts +37 -0
  109. package/dist/src/seedData.ts +0 -0
  110. package/dist/src/seedProtocol.ts +17 -0
  111. package/dist/src/services.ts +359 -0
  112. package/dist/src/sqlWasmClient.ts +88 -0
  113. package/dist/src/syncDbWithEas.ts +686 -0
  114. package/dist/src/trash.ts +29 -0
  115. package/dist/src/ts-to-proto.ts +101 -0
  116. package/dist/src/types.ts +12 -0
  117. package/dist/src/upload.ts +86 -0
  118. package/dist/src/validate.ts +42 -0
  119. package/dist/src/validateInput.ts +33 -0
  120. package/dist/src/validateItemData.ts +20 -0
  121. package/dist/src/waitForDb.ts +23 -0
  122. package/dist/src/wasm.d.ts +8300 -0
  123. package/dist/src/write.ts +366 -0
  124. package/dist/types/src/browser/db/read/getModelSchemas.d.ts.map +1 -1
  125. package/dist/types/src/browser/events/item/create.d.ts.map +1 -1
  126. package/dist/types/src/browser/events/item/publish.d.ts.map +1 -1
  127. package/dist/types/src/browser/events/item/requestAll.d.ts.map +1 -1
  128. package/dist/types/src/browser/events/item/syncDbWithEas.d.ts.map +1 -1
  129. package/dist/types/src/browser/item/single/actors/hydrateExistingItem.d.ts +3 -1
  130. package/dist/types/src/browser/item/single/actors/hydrateExistingItem.d.ts.map +1 -1
  131. package/dist/types/src/browser/item/single/actors/saveDataToDb.d.ts +3 -3
  132. package/dist/types/src/browser/item/single/itemMachineSingle.d.ts +3 -3
  133. package/dist/types/src/browser/property/ItemProperty.d.ts +7 -7
  134. package/dist/types/src/browser/property/ItemProperty.d.ts.map +1 -1
  135. package/dist/types/src/browser/property/actors/hydrateFromDb.d.ts.map +1 -1
  136. package/dist/types/src/browser/property/actors/initialize.d.ts.map +1 -1
  137. package/dist/types/src/browser/property/actors/resolveRemoteStorage.d.ts +3 -3
  138. package/dist/types/src/browser/react/trash.d.ts +1 -1
  139. package/dist/types/src/browser/react/trash.d.ts.map +1 -1
  140. package/dist/types/src/browser/services/db/dbMachine.d.ts +6 -6
  141. package/dist/types/src/browser/services/publish/publishMachine.d.ts +17 -17
  142. package/dist/types/src/types/machines.d.ts +4 -0
  143. package/dist/types/src/types/machines.d.ts.map +1 -1
  144. package/package.json +1 -1
  145. package/dist/index-BEzB8REh.js.map +0 -1
  146. package/dist/index-ChGsdGPJ.js.map +0 -1
  147. package/dist/seed.schema.config-jKpK-lR6.js.map +0 -1
@@ -0,0 +1,127 @@
1
+ import { EventObject, fromCallback, Subscription } from 'xstate'
2
+ import { isNode, isReactNative } from '@/shared'
3
+ import {
4
+ GLOBAL_INITIALIZING_CREATE_ALL_ITEMS_SERVICES,
5
+ GLOBAL_INITIALIZING_INTERNAL_SERVICE_READY,
6
+ GLOBAL_INITIALIZING_SEND_CONFIG,
7
+ } from '@/browser/services/internal/constants'
8
+ import debug from 'debug'
9
+ import { FromCallbackInput, GlobalMachineContext } from '@/types'
10
+ import { getAppDb } from '@/browser/db/sqlWasmClient'
11
+ import { appState } from '@/shared/seedSchema'
12
+ import { like } from 'drizzle-orm'
13
+
14
+ const logger = debug('app:services:global:actors:initialize')
15
+
16
+ export const initialize = fromCallback<
17
+ EventObject,
18
+ FromCallbackInput<GlobalMachineContext>
19
+ >(({ sendBack, input: { event, context } }) => {
20
+ const { internalService, models, endpoints } = context
21
+
22
+ const { addresses } = event
23
+
24
+ let environment = 'browser'
25
+
26
+ if (isNode()) {
27
+ environment = 'node'
28
+ }
29
+
30
+ if (isReactNative()) {
31
+ environment = 'react-native'
32
+ }
33
+
34
+ let internalSubscription: Subscription | undefined
35
+ let easSubscription: Subscription | undefined
36
+
37
+ if (environment === 'browser' && models) {
38
+ const _initFileSystem = async (): Promise<void> => {
39
+ return
40
+ // return new Promise((resolve) => {
41
+ // })
42
+ }
43
+
44
+ const _initInternal = async (): Promise<void> => {
45
+ return new Promise((resolve) => {
46
+ internalSubscription = internalService.subscribe((snapshot) => {
47
+ logger('[sdk] [internal] snapshot', snapshot)
48
+ if (snapshot.value === 'ready') {
49
+ resolve()
50
+ }
51
+ })
52
+ internalService.send({ type: 'init', endpoints, addresses })
53
+ })
54
+ }
55
+
56
+ const _initAllItemsServices = async (): Promise<void> => {
57
+ const appDb = getAppDb()
58
+
59
+ const rows = await appDb
60
+ .select()
61
+ .from(appState)
62
+ .where(like(appState.key, 'snapshot__%'))
63
+
64
+ const payloadObj = {
65
+ create: {},
66
+ restore: {},
67
+ }
68
+
69
+ const modelNamesRestored: string[] = []
70
+
71
+ if (rows && rows.length > 0) {
72
+ for (const row of rows) {
73
+ const modelName = row.key.replace('snapshot__', '')
74
+ payloadObj.restore[modelName] = JSON.parse(row.value)
75
+ modelNamesRestored.push(modelName)
76
+ }
77
+ }
78
+ for (const [modelName, ModelClass] of Object.entries(models)) {
79
+ if (!modelNamesRestored.includes(modelName)) {
80
+ payloadObj.create[modelName] = ModelClass
81
+ }
82
+ }
83
+ sendBack({
84
+ type: GLOBAL_INITIALIZING_CREATE_ALL_ITEMS_SERVICES,
85
+ ...payloadObj,
86
+ })
87
+ }
88
+
89
+ const _initEas = async (): Promise<void> => {
90
+ // const { easService } = await import('@/browser/eas')
91
+ // easService.send({ type: 'init', endpoints, models })
92
+ // return new Promise((resolve) => {
93
+ // easSubscription = easService.subscribe((snapshot) => {
94
+ // if (snapshot.value === 'ready') {
95
+ // resolve()
96
+ // }
97
+ // })
98
+ // easService.send({ type: 'init', endpoints, models })
99
+ // })
100
+ }
101
+
102
+ _initFileSystem().then(() => {
103
+ logger('[global/actors] File system initialized')
104
+ })
105
+
106
+ _initInternal()
107
+ .then(() => {
108
+ return _initAllItemsServices()
109
+ })
110
+ .then(() => {
111
+ logger('[global/actors] Internal initialized')
112
+ sendBack({ type: GLOBAL_INITIALIZING_INTERNAL_SERVICE_READY })
113
+ internalSubscription?.unsubscribe()
114
+ })
115
+
116
+ // _initEas().then(() => {
117
+ // logger('EAS initialized')
118
+ // })
119
+ }
120
+
121
+ sendBack({ type: GLOBAL_INITIALIZING_SEND_CONFIG, environment })
122
+
123
+ return () => {
124
+ internalSubscription?.unsubscribe()
125
+ easSubscription?.unsubscribe()
126
+ }
127
+ })
@@ -0,0 +1,220 @@
1
+ import { assign, setup } from 'xstate'
2
+ import { createBrowserInspector } from '@statelyai/inspect'
3
+ import {
4
+ DB_NAME_APP,
5
+ INTERNAL_CONFIGURING_FS_SUCCESS,
6
+ INTERNAL_LOADING_APP_DB_SUCCESS,
7
+ INTERNAL_SAVING_CONFIG_SUCCESS,
8
+ INTERNAL_VALIDATING_INPUT_SUCCESS,
9
+ InternalState,
10
+ MachineIds,
11
+ } from './constants'
12
+ import { dbMachine } from '@/browser/services/db/dbMachine'
13
+ import debug from 'debug'
14
+ import { validateInput } from '@/browser/services/internal/actors/validateInput'
15
+ import { prepareDb } from '@/browser/services/internal/actors/prepareDb'
16
+ import { configureFs } from '@/browser/services/internal/actors/configureFs'
17
+ import { saveConfig } from '@/browser/services/internal/actors/saveConfig'
18
+ import { loadAppDb } from '@/browser/services/internal/actors/loadAppDb'
19
+ import { InternalMachineContext } from '@/types'
20
+
21
+ const logger = debug('app:services:internal:machine')
22
+
23
+ const { inspect } = createBrowserInspector({
24
+ autoStart: false,
25
+ })
26
+
27
+ const {
28
+ IDLE,
29
+ VALIDATING_INPUT,
30
+ SAVING_CONFIG,
31
+ CONFIGURING_FS,
32
+ LOADING_APP_DB,
33
+ } = InternalState
34
+
35
+ // Create the state machine
36
+ export const internalMachine = setup({
37
+ types: {
38
+ context: {} as Partial<InternalMachineContext>,
39
+ input: {} as Partial<InternalMachineContext> | undefined,
40
+ },
41
+ actors: {
42
+ prepareDb,
43
+ validateInput,
44
+ configureFs,
45
+ loadAppDb,
46
+ saveConfig,
47
+ },
48
+ }).createMachine({
49
+ id: MachineIds.INTERNAL,
50
+ initial: IDLE,
51
+ context: ({ input }) => {
52
+ return {
53
+ ...input,
54
+ error: undefined,
55
+ hasFiles: false,
56
+ }
57
+ },
58
+ states: {
59
+ [IDLE]: {
60
+ on: {
61
+ reValidate: VALIDATING_INPUT,
62
+ init: {
63
+ target: VALIDATING_INPUT,
64
+ actions: [
65
+ assign({
66
+ appDbService: ({ spawn }) =>
67
+ spawn(dbMachine, {
68
+ input: {
69
+ dbName: DB_NAME_APP,
70
+ },
71
+ }),
72
+ }),
73
+ ],
74
+ },
75
+ },
76
+ meta: {
77
+ displayText: 'Waiting for something to happen ...',
78
+ percentComplete: 0,
79
+ },
80
+ },
81
+ [VALIDATING_INPUT]: {
82
+ on: {
83
+ [INTERNAL_VALIDATING_INPUT_SUCCESS]: {
84
+ target: 'preparingDb',
85
+ actions: assign({
86
+ endpoints: ({ event }) => event.endpoints,
87
+ addresses: ({ event }) => event.addresses,
88
+ }),
89
+ },
90
+ },
91
+ invoke: {
92
+ src: 'validateInput',
93
+ input: ({ context, event }) => ({ context, event }),
94
+ },
95
+ meta: {
96
+ displayText: 'Validating input',
97
+ percentComplete: 20,
98
+ },
99
+ tags: ['loading'],
100
+ },
101
+ preparingDb: {
102
+ on: {
103
+ prepareDbSuccess: {
104
+ target: CONFIGURING_FS,
105
+ },
106
+ },
107
+ invoke: {
108
+ src: 'prepareDb',
109
+ input: ({ context, event }) => ({ context, event }),
110
+ },
111
+ },
112
+ [CONFIGURING_FS]: {
113
+ on: {
114
+ [INTERNAL_CONFIGURING_FS_SUCCESS]: {
115
+ target: LOADING_APP_DB,
116
+ actions: assign({ hasFiles: true }),
117
+ },
118
+ },
119
+ invoke: {
120
+ src: 'configureFs',
121
+ input: ({ context, event }) => ({ context, event }),
122
+ },
123
+ meta: {
124
+ displayText: 'Downloading app files',
125
+ percentComplete: 30,
126
+ },
127
+ tags: ['loading'],
128
+ },
129
+ [LOADING_APP_DB]: {
130
+ on: {
131
+ [INTERNAL_LOADING_APP_DB_SUCCESS]: {
132
+ target: SAVING_CONFIG,
133
+ actions: () => {
134
+ logger('[sdk] [internal/index] App DB loaded!')
135
+ },
136
+ },
137
+ },
138
+ invoke: {
139
+ src: 'loadAppDb',
140
+ input: ({ context, event }) => ({ context, event }),
141
+ },
142
+ },
143
+ // Save developer's config to DB
144
+ [SAVING_CONFIG]: {
145
+ on: {
146
+ [INTERNAL_SAVING_CONFIG_SUCCESS]: 'ready',
147
+ },
148
+ invoke: {
149
+ src: 'saveConfig',
150
+ input: ({ context, event }) => ({ context, event }),
151
+ },
152
+ meta: {
153
+ displayText: 'Saving configuration',
154
+ percentComplete: 80,
155
+ },
156
+ tags: ['loading'],
157
+ },
158
+ ready: {
159
+ entry: () => {
160
+ logger('[sdk] [internal/index] Ready!')
161
+ },
162
+ meta: {
163
+ displayText: "Crossing the t's ...",
164
+ percentComplete: 90,
165
+ },
166
+ },
167
+ error: {
168
+ on: {
169
+ retry: {
170
+ target: CONFIGURING_FS,
171
+ actions: assign({ error: undefined }),
172
+ },
173
+ },
174
+ entry: () => {
175
+ logger('[sdk] [internal/index] Error!')
176
+ },
177
+ meta: {
178
+ displayText: 'Whoops! Something went wrong.',
179
+ percentComplete: null,
180
+ },
181
+ tags: ['error'],
182
+ },
183
+ },
184
+ })
185
+
186
+ // const internalService = createActor(internalMachine, {
187
+ // input: {},
188
+ // inspect: (inspEvent) => {
189
+ // if (inspEvent.type === '@xstate.snapshot') {
190
+ // if (
191
+ // inspEvent.event &&
192
+ // inspEvent.event.snapshot &&
193
+ // inspEvent.event.snapshot.value
194
+ // ) {
195
+ // logger(
196
+ // `[internalService] ${inspEvent.event.snapshot.value}`,
197
+ // inspEvent,
198
+ // )
199
+ // return
200
+ // }
201
+ //
202
+ // if (inspEvent.snapshot && inspEvent.snapshot.value) {
203
+ // logger(`[internalService] ${inspEvent.snapshot.value}`, inspEvent)
204
+ // return
205
+ // }
206
+ //
207
+ // // logger(`[internalService] Uncaught event`, inspEvent)
208
+ // }
209
+ // },
210
+ // })
211
+
212
+ // internalService.subscribe((snapshot) => {
213
+ // globalService.send({ type: INTERNAL_SERVICE_SNAPSHOT, snapshot })
214
+ // })
215
+ //
216
+ // internalService.on(CHILD_SNAPSHOT, (emitted) => {
217
+ // globalService.send({ ...emitted })
218
+ // })
219
+
220
+ // internalService.start()
@@ -0,0 +1,324 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react'
2
+ import { createNewItem } from '@/browser/db/write'
3
+ import { Item } from '@/browser/item'
4
+ import { eventEmitter } from '@/eventBus'
5
+ import { useImmer } from 'use-immer'
6
+ import { orderBy } from 'lodash-es'
7
+ import { getAreItemEventHandlersReady } from '@/browser/events'
8
+ import { Subscription } from 'xstate'
9
+ import { useSelector } from '@xstate/react'
10
+ import debug from 'debug'
11
+ import { useGlobalServiceStatus, useIsDbReady } from '@/browser/react/services'
12
+ import { ModelValues } from '@/types'
13
+
14
+ const logger = debug('app:react:item')
15
+
16
+ type UseItemProps = {
17
+ modelName: string
18
+ seedLocalId?: string
19
+ seedUid?: string
20
+ }
21
+
22
+ type UseItemReturn<T extends ModelValues<T>> = {
23
+ item: Item<T> | undefined
24
+ itemData: ItemData<T>
25
+ itemStatus: string | Record<string, unknown> | undefined
26
+ }
27
+
28
+ type UseItem<T> = (props: UseItemProps) => UseItemReturn<T>
29
+
30
+ type ItemData<T> = Record<string, Partial<T>>
31
+
32
+ export const useItem: UseItem<any> = ({ modelName, seedLocalId, seedUid }) => {
33
+ const [itemData, setItemData] = useImmer<ItemData<any>>({})
34
+ const [item, setItem] = useState<Item<any> | undefined>()
35
+ const [itemSubscription, setItemSubscription] = useState<
36
+ Subscription | undefined
37
+ >()
38
+
39
+ const { status, internalStatus } = useGlobalServiceStatus()
40
+
41
+ const isReadingDb = useRef(false)
42
+
43
+ const itemStatus = useSelector(
44
+ item?.getService(),
45
+ (snapshot) => snapshot?.value,
46
+ )
47
+
48
+ const updateItem = useCallback((newItem: Item<any>) => {
49
+ setItemData((draft) => {
50
+ Object.keys(newItem.properties).forEach((propertyName) => {
51
+ const value = newItem[propertyName]
52
+ draft[propertyName] = value
53
+ })
54
+ })
55
+ }, [])
56
+
57
+ const readFromDb = useCallback(async () => {
58
+ if (
59
+ isReadingDb.current ||
60
+ internalStatus !== 'ready' ||
61
+ (!seedUid && !seedLocalId)
62
+ ) {
63
+ return
64
+ }
65
+ isReadingDb.current = true
66
+ const foundItem = await Item.find({
67
+ modelName,
68
+ seedLocalId,
69
+ seedUid,
70
+ })
71
+ if (!foundItem) {
72
+ logger('[useItem] [getItemFromDb] no item found', modelName, seedLocalId)
73
+ return
74
+ }
75
+ setItem(foundItem)
76
+ updateItem(foundItem)
77
+ isReadingDb.current = false
78
+ }, [internalStatus])
79
+
80
+ const listenerRef = useRef(readFromDb)
81
+
82
+ useEffect(() => {
83
+ listenerRef.current = readFromDb
84
+ }, [readFromDb])
85
+
86
+ useEffect(() => {
87
+ if (internalStatus === 'ready') {
88
+ listenerRef.current()
89
+ }
90
+ }, [internalStatus, status])
91
+
92
+ useEffect(() => {
93
+ if (item && !itemSubscription) {
94
+ const subscription = item.subscribe(async (_) => {
95
+ const newItem = await Item.find({ modelName, seedLocalId, seedUid })
96
+ if (!newItem) {
97
+ logger(
98
+ '[useItem] [itemSubscription] no item found',
99
+ modelName,
100
+ seedLocalId,
101
+ )
102
+ return
103
+ }
104
+ updateItem(newItem)
105
+ setItem(newItem)
106
+ })
107
+ setItemSubscription(subscription)
108
+ }
109
+
110
+ return () => {
111
+ itemSubscription?.unsubscribe()
112
+ }
113
+ }, [item, itemSubscription])
114
+
115
+ useEffect(() => {
116
+ const seedId = seedUid || seedLocalId
117
+
118
+ eventEmitter.addListener(`item.${modelName}.${seedId}.update`, () => {
119
+ listenerRef.current()
120
+ })
121
+
122
+ return () => {
123
+ eventEmitter.removeListener(
124
+ `item.${modelName}.${seedId}.update`,
125
+ readFromDb,
126
+ )
127
+ }
128
+ }, [])
129
+
130
+ return {
131
+ item,
132
+ itemData,
133
+ itemStatus,
134
+ }
135
+ }
136
+ type UseItemsReturn = {
137
+ items: Item<any>[]
138
+ isReadingDb: boolean
139
+ isInitialized: boolean
140
+ }
141
+ type UseItemsProps = {
142
+ modelName?: string
143
+ deleted?: boolean
144
+ }
145
+ type UseItems = (props: UseItemsProps) => UseItemsReturn
146
+ export const useItems: UseItems = ({ modelName, deleted }) => {
147
+ const [items, setItems] = useImmer<Item<any>[]>([])
148
+ const [isReadingDb, setIsReadingDb] = useState(false)
149
+ const [isInitialized, setIsInitialized] = useState(false)
150
+
151
+ const isDbReady = useIsDbReady()
152
+
153
+ const modelNameRef = useRef<string | undefined>(modelName)
154
+
155
+ const readFromDb = useCallback(
156
+ async (event) => {
157
+ if (
158
+ !event ||
159
+ !event.modelName ||
160
+ event.modelName !== modelNameRef.current ||
161
+ isReadingDb
162
+ ) {
163
+ return
164
+ }
165
+ setIsReadingDb(true)
166
+ const allItems = await Item.all(modelNameRef.current, deleted)
167
+ setItems(() => allItems)
168
+ setIsReadingDb(false)
169
+ },
170
+ [modelName, isReadingDb],
171
+ )
172
+
173
+ useEffect(() => {
174
+ if (isDbReady && !isInitialized) {
175
+ const _fetchItems = async (): Promise<void> => {
176
+ await readFromDb({ modelName })
177
+ setIsInitialized(true)
178
+ }
179
+
180
+ _fetchItems()
181
+ }
182
+ }, [isInitialized, isDbReady])
183
+
184
+ useEffect(() => {
185
+ eventEmitter.addListener('item.requestAll', readFromDb)
186
+
187
+ return () => {
188
+ eventEmitter.removeListener('item.requestAll')
189
+ }
190
+ }, [])
191
+
192
+ return {
193
+ items: orderBy(
194
+ items,
195
+ [
196
+ (item) =>
197
+ item.lastVersionPublishedAt ||
198
+ item.attestationCreatedAt ||
199
+ item.createdAt,
200
+ ],
201
+ ['desc'],
202
+ ).slice(0, 10),
203
+ isReadingDb,
204
+ isInitialized,
205
+ }
206
+ }
207
+ export const useItemIsReady = () => {
208
+ const [itemListenersReady, setItemListenersReady] = useState(false)
209
+
210
+ const itemEventListenersHandler = useCallback((_) => {
211
+ setItemListenersReady(true)
212
+ }, [])
213
+
214
+ useEffect(() => {
215
+ const areReady = getAreItemEventHandlersReady()
216
+
217
+ if (areReady) {
218
+ itemEventListenersHandler(true)
219
+ }
220
+
221
+ eventEmitter.addListener(
222
+ 'item.events.setupAllItemsEventHandlers',
223
+ itemEventListenersHandler,
224
+ )
225
+
226
+ return () => {
227
+ eventEmitter.removeListener('item.events.setupAllItemsEventHandlers')
228
+ }
229
+ }, [])
230
+
231
+ return {
232
+ isReady: itemListenersReady,
233
+ }
234
+ }
235
+ export const useCreateItem = <T>(modelName: string) => {
236
+ const [isCreatingItem, setIsCreatingItem] = useState(false)
237
+
238
+ const { isReady } = useItemIsReady()
239
+
240
+ const createItem = useCallback(
241
+ async (itemData) => {
242
+ if (!isReady) {
243
+ console.error(
244
+ `[useCreateItem] [createItem] called before listeners are ready`,
245
+ itemData,
246
+ )
247
+ return
248
+ }
249
+ if (isCreatingItem) {
250
+ // TODO: should we setup a queue for this?
251
+ console.error(
252
+ `[useCreateItem] [createItem] already creating item`,
253
+ itemData,
254
+ )
255
+ return
256
+ }
257
+
258
+ setIsCreatingItem(true)
259
+
260
+ const { seedLocalId } = await createNewItem({ modelName, ...itemData })
261
+
262
+ const newItem = await Item.find({ modelName, seedLocalId })
263
+
264
+ eventEmitter.emit('item.requestAll', { modelName })
265
+
266
+ setIsCreatingItem(false)
267
+ },
268
+ [isCreatingItem, isReady],
269
+ )
270
+
271
+ return {
272
+ createItem,
273
+ isCreatingItem,
274
+ }
275
+ }
276
+
277
+ type PublishItemResult = Error | undefined | void
278
+
279
+ type UsePublishItemReturn = {
280
+ publishItem: (
281
+ item: Item<any> | undefined,
282
+ callback: (result: PublishItemResult) => any,
283
+ ) => void
284
+ isPublishing: boolean
285
+ }
286
+
287
+ type PublishItemProps = [
288
+ item: Item<any> | undefined,
289
+ callback?: (result: PublishItemResult) => any | undefined,
290
+ ]
291
+
292
+ type PublishItem = (...props: PublishItemProps) => Promise<any>
293
+
294
+ export const usePublishItem = (): UsePublishItemReturn => {
295
+ const [isPublishing, setIsPublishing] = useState(false)
296
+
297
+ const isLocked = useRef(false)
298
+
299
+ const publishItem: PublishItem = useCallback(async (item, callback?) => {
300
+ if (!item || isLocked.current) {
301
+ return
302
+ }
303
+ isLocked.current = true
304
+ console.log('Publishing item', item)
305
+ setIsPublishing(true)
306
+ try {
307
+ await item.publish()
308
+ if (callback) {
309
+ callback()
310
+ }
311
+ } catch (e) {
312
+ if (callback) {
313
+ callback(e as Error)
314
+ }
315
+ }
316
+ setIsPublishing(false)
317
+ isLocked.current = false
318
+ }, [])
319
+
320
+ return {
321
+ publishItem,
322
+ isPublishing,
323
+ }
324
+ }