Repository navigation
Expand file tree
/
Copy pathclient_meta.go
More file actions
193 lines (170 loc) · 6.79 KB
/
Copy pathclient_meta.go
File metadata and controls
193 lines (170 loc) · 6.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
package forge
import (
"time"
"github.com/xraph/forge/internal/router"
)
// EntityDef declares how a type is identified in a client-side normalized cache.
//
// IDField is the JSON property name in the response body -- `id`,
// `order_number` -- not the Go field name.
type EntityDef = router.EntityDef
// ForgeEntity is implemented by types that override entity inference. The
// schema generator honours it, marking the declared id property so identity
// travels with the type rather than being repeated on every route.
//
// The marker beats the `id` name heuristic, so this is the way to say "the
// field named id is not what identifies this record". Declare identity once per
// type: this and the `forge:"id"` struct tag write the same marker, and using
// both on different fields resolves to no entity at all.
//
// Example:
//
// type Order struct {
// Number string `json:"order_number"`
// Total int `json:"total"`
// }
//
// func (Order) ForgeEntity() forge.EntityDef {
// return forge.EntityDef{Type: "Order", IDField: "order_number"}
// }
type ForgeEntity = router.ForgeEntity
// EnvelopeDef declares which JSON property of a wrapper type carries the
// entity. Leave ItemsField empty to have generation resolve the sole
// entity-typed property.
type EnvelopeDef = router.EnvelopeDef
// ForgeEnvelope is implemented by types that WRAP an entity rather than being
// one -- a paginated page, a `{data, meta}` result.
//
// An ordinary `PageOrder{Items []Order; Total int}` does not need it: on a
// read, the client generator resolves a wrapper whose properties include
// exactly one array of an entity. Declare this for the two it will not, a
// wrapper around a single record and a wrapper carrying two collections, and
// the operation gets exactly the contract returning `[]Order` would.
//
// The orders inside such a response are normalized either way -- the generated
// field map routes into them without any declaration. What this adds is the
// cache contract.
//
// Example:
//
// type PageOrder struct {
// Items []Order `json:"items"`
// Total int `json:"total"`
// }
//
// func (PageOrder) ForgeEnvelope() forge.EnvelopeDef { return forge.EnvelopeDef{} }
type ForgeEnvelope = router.ForgeEnvelope
// StreamIntent is what a stream message does to the cache.
type StreamIntent = router.StreamIntent
// StreamBinding binds one channel message to an entity type.
type StreamBinding = router.StreamBinding
// EmitsBuilder accumulates one stream binding.
type EmitsBuilder = router.EmitsBuilder
const (
StreamUpsert = router.StreamUpsert
StreamPatch = router.StreamPatch
StreamEvict = router.StreamEvict
)
// Emits declares that a channel emits `message` carrying entity T.
//
// Example:
//
// router.WebSocket("/ws/orders", handler,
// forge.WithStreamBinding(
// forge.Emits[Order]("order.created"),
// forge.Emits[Order]("order.updated"),
// forge.Emits[Order]("order.deleted"),
// ),
// )
func Emits[T any](message string) *EmitsBuilder { return router.Emits[T](message) }
// WithEntity overrides inferred identity for this endpoint's response.
//
// IDField is the JSON property name, so it must match the key that appears in
// the response body -- `id`, not the Go field name `ID`. Generation warns when
// the named property is absent from the response schema, because as declared it
// would produce a cache key that never matches a record.
//
// Prefer implementing ForgeEntity on the type: identity is intrinsic to a type,
// and declaring it per route repeats it on every endpoint returning an Order.
// This option exists for types you cannot add a method to, and for the one
// endpoint whose response is identified differently from the rest.
//
// Example:
//
// router.GET("/orders/{id}", getOrder,
// forge.WithEntity(forge.EntityDef{Type: "Order", IDField: "id"}),
// )
func WithEntity(def EntityDef) RouteOption { return router.WithEntity(def) }
// WithoutEntity keeps this endpoint's response out of the normalized store.
// Use it for projections and snapshots that must not merge with the canonical
// record.
//
// Example:
//
// router.GET("/orders/{id}/audit-snapshot", h, forge.WithoutEntity())
func WithoutEntity() RouteOption { return router.WithoutEntity() }
// WithInvalidates declares cross-entity invalidation effects.
// Same-entity invalidation is derived, so this is only for edges a reader
// would not predict.
//
// Example:
//
// router.POST("/orders", createOrder,
// forge.WithInvalidates("Inventory[]", "Customer:{req.customerId}"),
// )
func WithInvalidates(tags ...string) RouteOption { return router.WithInvalidates(tags...) }
// WithoutInvalidation suppresses a derived invalidation for endpoints that
// cannot change list membership.
func WithoutInvalidation(tags ...string) RouteOption {
return router.WithoutInvalidation(tags...)
}
// WithStreamBinding declares which entity updates a channel emits.
//
// Example:
//
// router.WebSocket("/ws/orders", handler,
// forge.WithStreamBinding(
// forge.Emits[Order]("order.created"),
// forge.Emits[Order]("order.updated"),
// forge.Emits[Order]("order.deleted"),
// ),
// )
func WithStreamBinding(builders ...*EmitsBuilder) RouteOption {
return router.WithStreamBinding(builders...)
}
// WithStaleTime declares how long this endpoint's result stays fresh on the
// client. Reads only; the generator drops it on a write.
//
// Example:
//
// router.GET("/orders", listOrders, forge.WithStaleTime(30*time.Second))
func WithStaleTime(d time.Duration) RouteOption { return router.WithStaleTime(d) }
// SyncDef is one x-forge-sync declaration.
type SyncDef = router.SyncDef
// SyncOption refines a sync declaration.
type SyncOption = router.SyncOption
// x-forge-sync protocol and role names.
const (
SyncProtocolGroveCRDT = router.SyncProtocolGroveCRDT
SyncRolePull = router.SyncRolePull
SyncRolePush = router.SyncRolePush
SyncRoleStream = router.SyncRoleStream
SyncRoleSocket = router.SyncRoleSocket
)
// WithSync declares that this route is a sync endpoint for entity, whose
// records live in table on the server. The generated Dart client lists it
// in its sync table so forge_client_grove can own the entity.
//
// Example:
//
// router.POST("/datasets/:id/sync/pull", pull,
// forge.WithSync(forge.SyncProtocolGroveCRDT, "DatasetRow", "",
// forge.SyncDataset("{id}"), forge.SyncRole(forge.SyncRolePull)),
// )
func WithSync(protocol, entity, table string, opts ...SyncOption) RouteOption {
return router.WithSync(protocol, entity, table, opts...)
}
// SyncDataset names the path parameter that selects the dataset, e.g. "{id}".
func SyncDataset(param string) SyncOption { return router.SyncDataset(param) }
// SyncRole sets the route's sync role: pull, push, stream or socket.
func SyncRole(role string) SyncOption { return router.SyncRole(role) }