@wovin/core 0.2.2 → 0.3.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.
Files changed (98) hide show
  1. package/dist/applog/datom-types.d.ts.map +1 -1
  2. package/dist/applog.js +1 -1
  3. package/dist/blockstore.js +2 -0
  4. package/dist/blockstore.js.map +1 -1
  5. package/dist/{chunk-SHUHRHOT.js → chunk-2OXLPZQI.js} +10 -3
  6. package/dist/chunk-2OXLPZQI.js.map +1 -0
  7. package/dist/{chunk-3SUFNJEZ.js → chunk-2PJFLZRC.js} +7 -2
  8. package/dist/{chunk-3SUFNJEZ.js.map → chunk-2PJFLZRC.js.map} +1 -1
  9. package/dist/chunk-64EJIJAJ.js +17 -0
  10. package/dist/chunk-64EJIJAJ.js.map +1 -0
  11. package/dist/chunk-7QEGHKR4.js +17 -0
  12. package/dist/chunk-7QEGHKR4.js.map +1 -0
  13. package/dist/{chunk-OC6Z6CQW.js → chunk-EHO2BFFY.js} +2 -2
  14. package/dist/chunk-ICBK7NC4.js +27 -0
  15. package/dist/chunk-ICBK7NC4.js.map +1 -0
  16. package/dist/{chunk-22WDFLXO.js → chunk-OKXRRWNS.js} +3 -3
  17. package/dist/{chunk-6ALNRM3J.js → chunk-Q4EMPWA3.js} +15 -8
  18. package/dist/chunk-Q4EMPWA3.js.map +1 -0
  19. package/dist/{chunk-HUIQ54TT.js → chunk-VGIACGWX.js} +3 -3
  20. package/dist/{chunk-BLF5MAWU.js → chunk-WVW4YXB5.js} +2 -2
  21. package/dist/chunk-XF4DWOAE.js +25 -0
  22. package/dist/chunk-XF4DWOAE.js.map +1 -0
  23. package/dist/index.js +7 -7
  24. package/dist/ipfs/car.d.ts.map +1 -1
  25. package/dist/ipfs.js +4 -4
  26. package/dist/ipns/gateway-resolver.d.ts +21 -0
  27. package/dist/ipns/gateway-resolver.d.ts.map +1 -0
  28. package/dist/ipns/ipns-record.d.ts +28 -7
  29. package/dist/ipns/ipns-record.d.ts.map +1 -1
  30. package/dist/ipns/ipns-w3name.d.ts +15 -0
  31. package/dist/ipns/ipns-w3name.d.ts.map +1 -0
  32. package/dist/ipns/ipns-watcher.d.ts +190 -0
  33. package/dist/ipns/ipns-watcher.d.ts.map +1 -0
  34. package/dist/ipns.d.ts +3 -0
  35. package/dist/ipns.d.ts.map +1 -1
  36. package/dist/ipns.js +488 -8
  37. package/dist/ipns.js.map +1 -1
  38. package/dist/pubsub/snap-push.d.ts +2 -2
  39. package/dist/pubsub/snap-push.d.ts.map +1 -1
  40. package/dist/pubsub.js +4 -4
  41. package/dist/query.js +3 -3
  42. package/dist/retrieve.js +4 -4
  43. package/dist/thread.js +1 -1
  44. package/dist/viewmodel/adapters/arktype.d.ts +33 -0
  45. package/dist/viewmodel/adapters/arktype.d.ts.map +1 -0
  46. package/dist/viewmodel/adapters/arktype.js +7 -0
  47. package/dist/viewmodel/adapters/arktype.js.map +1 -0
  48. package/dist/viewmodel/adapters/typebox.d.ts +35 -0
  49. package/dist/viewmodel/adapters/typebox.d.ts.map +1 -0
  50. package/dist/viewmodel/adapters/typebox.js +7 -0
  51. package/dist/viewmodel/adapters/typebox.js.map +1 -0
  52. package/dist/viewmodel/adapters/typia.d.ts +40 -0
  53. package/dist/viewmodel/adapters/typia.d.ts.map +1 -0
  54. package/dist/viewmodel/adapters/typia.js +7 -0
  55. package/dist/viewmodel/adapters/typia.js.map +1 -0
  56. package/dist/viewmodel/adapters/zod.d.ts +30 -0
  57. package/dist/viewmodel/adapters/zod.d.ts.map +1 -0
  58. package/dist/viewmodel/adapters/zod.js +7 -0
  59. package/dist/viewmodel/adapters/zod.js.map +1 -0
  60. package/dist/viewmodel/builder.d.ts +40 -0
  61. package/dist/viewmodel/builder.d.ts.map +1 -0
  62. package/dist/viewmodel/examples/all-adapters.d.ts +26 -0
  63. package/dist/viewmodel/examples/all-adapters.d.ts.map +1 -0
  64. package/dist/viewmodel/factory.d.ts +38 -0
  65. package/dist/viewmodel/factory.d.ts.map +1 -0
  66. package/dist/viewmodel/index.d.ts +10 -0
  67. package/dist/viewmodel/index.d.ts.map +1 -0
  68. package/dist/viewmodel/index.js +313 -0
  69. package/dist/viewmodel/index.js.map +1 -0
  70. package/dist/viewmodel/schema-adapter.d.ts +16 -0
  71. package/dist/viewmodel/schema-adapter.d.ts.map +1 -0
  72. package/dist/viewmodel/types.d.ts +97 -0
  73. package/dist/viewmodel/types.d.ts.map +1 -0
  74. package/package.json +29 -3
  75. package/src/applog/datom-types.ts +2 -2
  76. package/src/ipfs/car.ts +8 -2
  77. package/src/ipns/gateway-resolver.ts +63 -0
  78. package/src/ipns/ipns-record.ts +68 -17
  79. package/src/ipns/ipns-w3name.ts +103 -0
  80. package/src/ipns/ipns-watcher.ts +607 -0
  81. package/src/ipns.ts +3 -0
  82. package/src/pubsub/snap-push.ts +6 -5
  83. package/src/viewmodel/adapters/arktype.ts +44 -0
  84. package/src/viewmodel/adapters/typebox.ts +59 -0
  85. package/src/viewmodel/adapters/typia.ts +50 -0
  86. package/src/viewmodel/adapters/zod.ts +55 -0
  87. package/src/viewmodel/builder.ts +71 -0
  88. package/src/viewmodel/examples/all-adapters.ts +206 -0
  89. package/src/viewmodel/factory.ts +330 -0
  90. package/src/viewmodel/index.ts +22 -0
  91. package/src/viewmodel/schema-adapter.ts +27 -0
  92. package/src/viewmodel/types.ts +152 -0
  93. package/dist/chunk-6ALNRM3J.js.map +0 -1
  94. package/dist/chunk-SHUHRHOT.js.map +0 -1
  95. /package/dist/{chunk-OC6Z6CQW.js.map → chunk-EHO2BFFY.js.map} +0 -0
  96. /package/dist/{chunk-22WDFLXO.js.map → chunk-OKXRRWNS.js.map} +0 -0
  97. /package/dist/{chunk-HUIQ54TT.js.map → chunk-VGIACGWX.js.map} +0 -0
  98. /package/dist/{chunk-BLF5MAWU.js.map → chunk-WVW4YXB5.js.map} +0 -0
@@ -0,0 +1,59 @@
1
+ import type { ApplogValue } from '../../applog/datom-types.ts'
2
+ import type { ISchemaAdapter, VMAttributeDef } from '../types.ts'
3
+
4
+ /**
5
+ * Create a schema adapter from a TypeBox object schema.
6
+ *
7
+ * Usage:
8
+ * ```ts
9
+ * import { Type } from '@sinclair/typebox'
10
+ * import { createTypeBoxAdapter } from '@wovin/core/viewmodel/adapters/typebox'
11
+ *
12
+ * const MySchema = Type.Object({
13
+ * name: Type.String(),
14
+ * age: Type.Optional(Type.Number()),
15
+ * })
16
+ *
17
+ * const adapter = createTypeBoxAdapter(MySchema, 'myEntity')
18
+ * ```
19
+ *
20
+ * The type parameter `T` is the inferred static type of the schema.
21
+ * Can pass a compiled validator separately if runtime validation is needed.
22
+ */
23
+ export function createTypeBoxAdapter<T extends Record<string, ApplogValue>>(
24
+ typeBoxSchema: { properties: Record<string, any>; type?: string },
25
+ entityPrefix: string,
26
+ options?: {
27
+ /** Override at-paths for specific attributes */
28
+ atOverrides?: Record<string, string>
29
+ /** Default values */
30
+ defaults?: Partial<T>
31
+ /** Property name to at-path mapping function */
32
+ toAtPath?: (entityPrefix: string, attrName: string) => string
33
+ /** A pre-compiled validator function (e.g. from TypeCompiler.Compile) */
34
+ validator?: (value: unknown) => value is T
35
+ },
36
+ ): ISchemaAdapter<T> {
37
+ const properties = typeBoxSchema.properties as Record<string, any>
38
+ const toAtPath = options?.toAtPath ?? ((prefix, name) => `${prefix}/${name}`)
39
+
40
+ const attributeDefs: VMAttributeDef[] = Object.entries(properties).map(([name, propSchema]: [string, any]) => {
41
+ const isOptional = propSchema.optional ?? false
42
+ const wovinAtName = propSchema.wovinAtName
43
+ const atPath = options?.atOverrides?.[name] ?? wovinAtName ?? toAtPath(entityPrefix, name)
44
+
45
+ return {
46
+ name,
47
+ atPath,
48
+ optional: isOptional,
49
+ defaultValue: propSchema.default,
50
+ }
51
+ })
52
+
53
+ return {
54
+ getAttributeDefs: () => attributeDefs,
55
+ getDefaults: () => (options?.defaults ?? {}) as Partial<T>,
56
+ getEntityPrefix: () => entityPrefix,
57
+ createValidator: options?.validator ? () => options.validator! : undefined,
58
+ }
59
+ }
@@ -0,0 +1,50 @@
1
+ import type { ApplogValue } from '../../applog/datom-types.ts'
2
+ import type { ISchemaAdapter, VMAttributeDef } from '../types.ts'
3
+
4
+ /**
5
+ * Pattern for creating a schema adapter from a Typia-generated schema.
6
+ *
7
+ * Typia is NOT a runtime dependency of @wovin/core, so this adapter
8
+ * works by accepting the Typia type object directly along with the
9
+ * attribute definitions (which must be provided separately since
10
+ * Typia doesn't expose runtime property introspection by default).
11
+ *
12
+ * Usage:
13
+ * ```ts
14
+ * import typia from 'typia'
15
+ * import { createTypiaAdapter } from '@wovin/core/viewmodel/adapters/typia'
16
+ *
17
+ * interface MyEntity { name: string; age?: number }
18
+ *
19
+ * const adapter = createTypiaAdapter<MyEntity>(
20
+ * typia.createValidate<MyEntity>(),
21
+ * 'myEntity',
22
+ * {
23
+ * attributes: [
24
+ * { name: 'name', atPath: 'myEntity/name', optional: false },
25
+ * { name: 'age', atPath: 'myEntity/age', optional: true },
26
+ * ],
27
+ * },
28
+ * )
29
+ * ```
30
+ */
31
+ export function createTypiaAdapter<T extends Record<string, ApplogValue>>(
32
+ _typiaValidator: (input: unknown) => { success: boolean; data: T | null; errors?: any[] },
33
+ entityPrefix: string,
34
+ options: {
35
+ /** Attribute definitions (required for Typia since property introspection is compile-time) */
36
+ attributes: VMAttributeDef[]
37
+ /** Default values */
38
+ defaults?: Partial<T>
39
+ },
40
+ ): ISchemaAdapter<T> {
41
+ return {
42
+ getAttributeDefs: () => options.attributes,
43
+ getDefaults: () => (options?.defaults ?? {}) as Partial<T>,
44
+ getEntityPrefix: () => entityPrefix,
45
+ createValidator: () => (value: unknown): value is T => {
46
+ const result = _typiaValidator(value)
47
+ return result.success
48
+ },
49
+ }
50
+ }
@@ -0,0 +1,55 @@
1
+ import type { ApplogValue } from '../../applog/datom-types.ts'
2
+ import type { ISchemaAdapter, VMAttributeDef } from '../types.ts'
3
+ import type { ZodObject, ZodType, ZodTypeAny } from 'zod'
4
+
5
+ /**
6
+ * Create a schema adapter from a Zod object schema.
7
+ *
8
+ * Usage:
9
+ * ```ts
10
+ * import { z } from 'zod'
11
+ * import { createZodAdapter } from '@wovin/core/viewmodel/adapters/zod'
12
+ *
13
+ * const MySchema = z.object({
14
+ * name: z.string(),
15
+ * age: z.number().optional(),
16
+ * })
17
+ *
18
+ * const adapter = createZodAdapter(MySchema, 'myEntity')
19
+ * ```
20
+ *
21
+ * The type parameter `T` is the inferred type of the schema.
22
+ */
23
+ export function createZodAdapter<T extends Record<string, ApplogValue> = Record<string, ApplogValue>>(
24
+ zodSchema: ZodObject<Record<string, ZodTypeAny>>,
25
+ entityPrefix: string,
26
+ options?: {
27
+ /** Override at-paths for specific attributes */
28
+ atOverrides?: Record<string, string>
29
+ /** Default values */
30
+ defaults?: Partial<T>
31
+ /** Property name to at-path mapping function */
32
+ toAtPath?: (entityPrefix: string, attrName: string) => string
33
+ },
34
+ ): ISchemaAdapter<T> {
35
+ const shape = zodSchema.shape as Record<string, ZodTypeAny>
36
+ const toAtPath = options?.toAtPath ?? ((prefix, name) => `${prefix}/${name}`)
37
+
38
+ const attributeDefs: VMAttributeDef[] = Object.entries(shape).map(([name, fieldSchema]: [string, ZodTypeAny]) => {
39
+ const isOptional = fieldSchema.isOptional() || fieldSchema.isNullable()
40
+ const atPath = options?.atOverrides?.[name] ?? toAtPath(entityPrefix, name)
41
+
42
+ return {
43
+ name,
44
+ atPath,
45
+ optional: isOptional,
46
+ }
47
+ })
48
+
49
+ return {
50
+ getAttributeDefs: () => attributeDefs,
51
+ getDefaults: () => (options?.defaults ?? {}) as Partial<T>,
52
+ getEntityPrefix: () => entityPrefix,
53
+ createValidator: () => (value: unknown): value is T => zodSchema.safeParse(value).success,
54
+ }
55
+ }
@@ -0,0 +1,71 @@
1
+ import type { ApplogForInsertOptionalAgent, ApplogValue, EntityID } from '../applog/datom-types.ts'
2
+
3
+ /**
4
+ * Generic ObjectBuilder for type-safe entity creation and updates.
5
+ *
6
+ * Provides a fluent API to build a set of applogs for an entity.
7
+ * Modeled after the note3 builder pattern.
8
+ *
9
+ * Usage:
10
+ * ```ts
11
+ * ObjectBuilder.create({ name: 'foo' })
12
+ * .update({ type: 'bar' })
13
+ * .build(thread)
14
+ * ```
15
+ */
16
+ export class ObjectBuilder<
17
+ TARGET extends Record<string, ApplogValue> = Record<string, ApplogValue>,
18
+ > {
19
+ constructor(
20
+ private _data: Partial<TARGET>,
21
+ public en: EntityID | null = null,
22
+ private _atPrefix: string | null = null,
23
+ private _atOverrides: Partial<Record<keyof TARGET, string>> = {},
24
+ ) {}
25
+
26
+ /**
27
+ * Create a new builder with initial data.
28
+ */
29
+ static create<TARGET extends Record<string, ApplogValue>>(
30
+ init: Partial<TARGET> = {},
31
+ en?: EntityID,
32
+ atPrefix?: string,
33
+ atOverrides?: Partial<Record<keyof TARGET, string>>,
34
+ ): ObjectBuilder<TARGET> {
35
+ return new ObjectBuilder<TARGET>(init, en, atPrefix, atOverrides)
36
+ }
37
+
38
+ /**
39
+ * Update the data being built.
40
+ */
41
+ update(updateObj: Partial<TARGET>): this {
42
+ Object.assign(this._data, updateObj)
43
+ return this
44
+ }
45
+
46
+ /**
47
+ * Build the applogs for this entity, optionally resolving against a thread.
48
+ */
49
+ build(thread?: {
50
+ insert(applogs: ApplogForInsertOptionalAgent[]): ApplogForInsertOptionalAgent[]
51
+ }): ApplogForInsertOptionalAgent[] {
52
+ const atPrefix = this._atPrefix
53
+ const applogs: ApplogForInsertOptionalAgent[] = []
54
+
55
+ for (const [key, value] of Object.entries(this._data) as [keyof TARGET, ApplogValue][]) {
56
+ if (value === undefined) continue
57
+ const atOverride = this._atOverrides[key]
58
+ const at = atOverride ?? (atPrefix ? `${atPrefix}/${String(key)}` : String(key))
59
+ applogs.push({ en: this.en!, at, vl: value })
60
+ }
61
+
62
+ return applogs
63
+ }
64
+
65
+ /**
66
+ * Get the raw data being built.
67
+ */
68
+ get data(): Partial<TARGET> {
69
+ return { ...this._data }
70
+ }
71
+ }
@@ -0,0 +1,206 @@
1
+ /**
2
+ * ═══════════════════════════════════════════════════════════════
3
+ * Schema Adapter Examples
4
+ * ═══════════════════════════════════════════════════════════════
5
+ *
6
+ * This file demonstrates how to use each schema adapter with the
7
+ * generic ViewModel factory. Uncomment the adapter you want to use.
8
+ *
9
+ * Each section is self-contained and shows:
10
+ * 1. Schema definition
11
+ * 2. Adapter creation
12
+ * 3. ViewModel factory usage
13
+ * 4. Basic VM usage
14
+ *
15
+ * Note: Not all schema libraries are installed as dependencies.
16
+ * ✓ TypeBox — peer dependency of @wovin/core (available)
17
+ * ✓ Zod — dependency of @wovin/core (available)
18
+ * ✗ ArkType — NOT installed (example shown for pattern reference)
19
+ * ✗ Typia — NOT installed (example shown for pattern reference)
20
+ */
21
+
22
+ import { createViewModelFactory, ObjectBuilder, type ISchemaAdapter, type VMAttributeDef } from '../index.ts'
23
+ import type { Thread } from '../../thread/basic.ts'
24
+ import type { ApplogValue, EntityID } from '../../applog/datom-types.ts'
25
+
26
+ // ═══════════════════════════════════════════════════════════════
27
+ // Shared helpers for examples
28
+ // ═══════════════════════════════════════════════════════════════
29
+
30
+ /** Dummy thread for examples */
31
+ function createMockThread(): Thread {
32
+ return { name: 'mock', applogs: [], filters: [], subscribe: () => () => {} } as unknown as Thread
33
+ }
34
+
35
+ interface Note extends Record<string, ApplogValue> {
36
+ title: string
37
+ body: string
38
+ tags?: string
39
+ }
40
+
41
+ // ═══════════════════════════════════════════════════════════════
42
+ // 1. TypeBox Adapter Example
43
+ // ═══════════════════════════════════════════════════════════════
44
+
45
+ // import { Type } from '@sinclair/typebox'
46
+ // import { Static } from '@sinclair/typebox'
47
+ // import { createTypeBoxAdapter } from '../adapters/typebox.ts'
48
+
49
+ export function typeBoxExample() {
50
+ /*
51
+
52
+ const NoteSchema = Type.Object({
53
+ title: Type.String(),
54
+ body: Type.String(),
55
+ tags: Type.Optional(Type.String()),
56
+ })
57
+ type Note = Static<typeof NoteSchema>
58
+
59
+ const NoteVM = createViewModelFactory<Note>({
60
+ adapter: createTypeBoxAdapter(NoteSchema, 'note', {
61
+ atOverrides: {
62
+ title: 'note/title',
63
+ body: 'note/body',
64
+ tags: 'note/tags',
65
+ },
66
+ }),
67
+ vmName: 'Note',
68
+ })
69
+
70
+ const thread = createMockThread()
71
+ const note = NoteVM.get('abc123' as EntityID, thread)
72
+ console.log(note.title) // reactive read
73
+ note.title = 'Hello' // writes to thread via applog
74
+
75
+ */
76
+ }
77
+
78
+ // ═══════════════════════════════════════════════════════════════
79
+ // 2. Zod Adapter Example
80
+ // ═══════════════════════════════════════════════════════════════
81
+
82
+ // import { z } from 'zod'
83
+ // import { createZodAdapter } from '../adapters/zod.ts'
84
+
85
+ export function zodExample() {
86
+ /*
87
+
88
+ const NoteSchema = z.object({
89
+ title: z.string(),
90
+ body: z.string(),
91
+ tags: z.string().optional(),
92
+ })
93
+ type Note = z.infer<typeof NoteSchema>
94
+
95
+ const NoteVM = createViewModelFactory<Note>({
96
+ adapter: createZodAdapter(NoteSchema, 'note', {
97
+ atOverrides: {
98
+ title: 'note/title',
99
+ body: 'note/body',
100
+ tags: 'note/tags',
101
+ },
102
+ }),
103
+ vmName: 'Note',
104
+ })
105
+
106
+ const thread = createMockThread()
107
+ const note = NoteVM.get('abc123' as EntityID, thread)
108
+ console.log(note.title)
109
+ note.title = 'Hello'
110
+
111
+ */
112
+ }
113
+
114
+ // ═══════════════════════════════════════════════════════════════
115
+ // 3. ArkType Adapter Pattern
116
+ // ═══════════════════════════════════════════════════════════════
117
+ // ArkType is NOT a dependency. This shows the pattern if you install it.
118
+
119
+ // import { createArkTypeAdapter } from '../adapters/arktype.ts'
120
+
121
+ export function arkTypePattern() {
122
+ /*
123
+
124
+ // First, define attributes manually (ArkType doesn't expose runtime props)
125
+ const attrs: VMAttributeDef[] = [
126
+ { name: 'title', atPath: 'note/title', optional: false },
127
+ { name: 'body', atPath: 'note/body', optional: false },
128
+ { name: 'tags', atPath: 'note/tags', optional: true },
129
+ ]
130
+
131
+ // Then create the adapter with a validator
132
+ // import { type } from 'arktype'
133
+ // const NoteSchema = type({ title: 'string', body: 'string', 'tags?': 'string' })
134
+
135
+ const NoteVM = createViewModelFactory<Note>({
136
+ adapter: createArkTypeAdapter(
137
+ // NoteSchema, // your arktype validator
138
+ {} as any, // placeholder
139
+ 'note',
140
+ { attributes: attrs },
141
+ ),
142
+ vmName: 'Note',
143
+ })
144
+
145
+ */
146
+ }
147
+
148
+ // ═══════════════════════════════════════════════════════════════
149
+ // 4. Typia Adapter Pattern
150
+ // ═══════════════════════════════════════════════════════════════
151
+ // Typia is NOT a dependency. This shows the pattern if you install it.
152
+
153
+ // import { createTypiaAdapter } from '../adapters/typia.ts'
154
+
155
+ export function typiaPattern() {
156
+ /*
157
+
158
+ // Define attributes manually (Typia strips types at compile time)
159
+ const attrs: VMAttributeDef[] = [
160
+ { name: 'title', atPath: 'note/title', optional: false },
161
+ { name: 'body', atPath: 'note/body', optional: false },
162
+ { name: 'tags', atPath: 'note/tags', optional: true },
163
+ ]
164
+
165
+ // Then create the adapter with a typia validator
166
+ // import typia from 'typia'
167
+ // interface Note { title: string; body: string; tags?: string }
168
+ // const validate = typia.createValidate<Note>()
169
+
170
+ const NoteVM = createViewModelFactory<Note>({
171
+ adapter: createTypiaAdapter(
172
+ // validate, // your typia validator
173
+ {} as any, // placeholder
174
+ 'note',
175
+ { attributes: attrs },
176
+ ),
177
+ vmName: 'Note',
178
+ })
179
+
180
+ */
181
+ }
182
+
183
+ // ═══════════════════════════════════════════════════════════════
184
+ // 5. Minimal adapter (no schema library)
185
+ // ═══════════════════════════════════════════════════════════════
186
+
187
+ export function minimalNoSchemaExample() {
188
+ // You don't need a schema library at all — use createAdapterFromAttributes
189
+
190
+ const adapter: ISchemaAdapter<Note> = {
191
+ getAttributeDefs: (): VMAttributeDef[] => [
192
+ { name: 'title', atPath: 'note/title', optional: false },
193
+ { name: 'body', atPath: 'note/body', optional: false },
194
+ { name: 'tags', atPath: 'note/tags', optional: true },
195
+ ],
196
+ getDefaults: () => ({ title: '', body: '' }),
197
+ getEntityPrefix: () => 'note',
198
+ }
199
+
200
+ const NoteVM = createViewModelFactory<Note>({
201
+ adapter,
202
+ vmName: 'Note',
203
+ })
204
+
205
+ console.log('Minimal NoteVM created:', NoteVM.vmName)
206
+ }