@@ -7,22 +7,26 @@ A type-safe, generic wrapper for Google Cloud Datastore in Go. Provides a fluent
77- ** Type-safe generics** - Compile-time type checking for all operations
88- ** Fluent API** - Chainable methods for building queries
99- ** Pagination support** - Both offset and cursor-based pagination
10- - ** Batch operations** - Efficient multi-entity upsert and delete
10+ - ** Batch operations** - Efficient multi-entity get, upsert, and delete
1111- ** Filter operators** - Type-safe enum for query operators
1212- ** Aggregation queries** - Efficient count operations without loading entities
13+ - ** Auto-generated IDs** - Insert entities with Datastore-assigned IDs
1314
1415## Installation
16+
1517``` bash
1618go get github.com/yourusername/dsx
1719```
1820
1921## Quick Start
22+
2023``` go
2124package main
2225
2326import (
2427 " context"
2528 " log"
29+ " time"
2630
2731 " github.com/yourusername/dsx"
2832)
@@ -62,6 +66,7 @@ func main() {
6266## API Reference
6367
6468### Connecting
69+
6570``` go
6671// Using default credentials (GOOGLE_APPLICATION_CREDENTIALS)
6772db , err := dsx.Connect (ctx, " project-id" , " " , " " )
@@ -76,11 +81,13 @@ db, err := dsx.Connect(ctx, "project-id", "", credentialsJSON)
7681### Querying
7782
7883#### Basic Select
84+
7985``` go
8086users , err := dsx.Query [User](db, ctx, " User" ).Select ()
8187```
8288
8389#### With Filters
90+
8491``` go
8592// Single filter
8693users , err := dsx.Query [User](db, ctx, " User" ).
@@ -117,6 +124,7 @@ users, err := dsx.Query[User](db, ctx, "User").
117124| ` dsx.OpNotIn ` | Not in list |
118125
119126#### Ordering
127+
120128``` go
121129// Ascending
122130users , err := dsx.Query [User](db, ctx, " User" ).
@@ -136,6 +144,7 @@ users, err := dsx.Query[User](db, ctx, "User").
136144```
137145
138146#### Get Single Entity
147+
139148``` go
140149user , err := dsx.Query [User](db, ctx, " User" ).
141150 WithFilter (" Email" , dsx.OpEqual , " john@example.com" ).
@@ -147,6 +156,14 @@ if user == nil {
147156}
148157```
149158
159+ #### Get Multiple Entities by ID
160+
161+ ``` go
162+ users , err := dsx.GetMulti [User](db, ctx, " User" , []string {" user-1" , " user-2" , " user-3" })
163+ ```
164+
165+ Entities that don't exist will be zero-valued in the result slice. The result slice maintains the same order as the input keys.
166+
150167### Counting Entities
151168
152169Use ` Count() ` to efficiently count entities matching a query without loading them into memory.
@@ -166,6 +183,7 @@ activeCount, err := dsx.Query[User](db, ctx, "User").
166183### Pagination
167184
168185#### Offset-based (Simple)
186+
169187``` go
170188// Page 1
171189users , err := dsx.Query [User](db, ctx, " User" ).
@@ -182,6 +200,7 @@ users, err := dsx.Query[User](db, ctx, "User").
182200> ** Note:** Datastore has a maximum offset of 1000. For deeper pagination, use cursors.
183201
184202#### Cursor-based (Efficient)
203+
185204``` go
186205// First page
187206users , cursor , err := dsx.Query [User](db, ctx, " User" ).
@@ -218,6 +237,7 @@ for {
218237### Upserting
219238
220239#### Single Entity
240+
221241``` go
222242user := User {
223243 Name : " John Doe" ,
@@ -230,6 +250,7 @@ err := dsx.Query[User](db, ctx, "User").Upsert("user-123", &user)
230250```
231251
232252#### Multiple Entities
253+
233254``` go
234255users := map [string ]*User{
235256 " user-1" : {Name: " Alice" , Email: " alice@example.com" , Status: " active" },
@@ -242,7 +263,26 @@ err := dsx.Query[User](db, ctx, "User").UpsertMulti(users)
242263
243264> ** Note:** Datastore limits batch operations to 500 entities.
244265
266+ #### Insert with Auto-generated ID
267+
268+ Use ` InsertWithAutoID ` when you want Datastore to generate a unique numeric ID and need to know the ID after insertion.
269+
270+ ``` go
271+ order := Order {
272+ CustomerID : " cust-123" ,
273+ Total : 99.99 ,
274+ CreatedAt : time.Now (),
275+ }
276+
277+ key , err := dsx.Query [Order](db, ctx, " Order" ).InsertWithAutoID (&order)
278+ if err != nil {
279+ return err
280+ }
281+ fmt.Printf (" Created order with ID: %d \n " , key.ID )
282+ ```
283+
245284### Deleting
285+
246286``` go
247287// Delete by filter
248288err := dsx.Query [User](db, ctx, " User" ).
@@ -260,6 +300,7 @@ err := dsx.Query[User](db, ctx, "User").
260300### Advanced Features
261301
262302#### Ancestor Queries
303+
263304``` go
264305companyKey := datastore.NameKey (" Company" , " acme" , nil )
265306
@@ -269,21 +310,23 @@ employees, err := dsx.Query[Employee](db, ctx, "Employee").
269310```
270311
271312#### Distinct Results
313+
272314``` go
273315users , err := dsx.Query [User](db, ctx, " User" ).
274316 WithDistinct ().
275317 Select ()
276318```
277319
278320#### Keys Only
321+
279322``` go
280- // Get keys only (more efficient for counting)
281323qb := dsx.Query [User](db, ctx, " User" ).
282324 WithFilter (" Status" , dsx.OpEqual , " active" ).
283325 KeysOnly ()
284326```
285327
286328#### Access Underlying Client
329+
287330``` go
288331// For operations not covered by dsx
289332client := db.Client ()
@@ -292,6 +335,7 @@ client := db.Client()
292335## Indexing
293336
294337Datastore requires indexes for queries. Simple single-property filters use built-in indexes, but composite queries need explicit indexes in ` index.yaml ` :
338+
295339``` yaml
296340indexes :
297341- kind : User
@@ -302,6 +346,7 @@ indexes:
302346` ` `
303347
304348This index supports:
349+
305350` ` ` go
306351dsx.Query[User](db, ctx, "User").
307352 WithFilter("Status", dsx.OpEqual, "active").
@@ -312,6 +357,7 @@ dsx.Query[User](db, ctx, "User").
312357## Best Practices
313358
314359### Use Limit with Get()
360+
315361``` go
316362// Good - efficient
317363user , err := dsx.Query [User](db, ctx, " User" ).
@@ -326,6 +372,7 @@ user, err := dsx.Query[User](db, ctx, "User").
326372```
327373
328374### Use Count() Instead of Loading Entities
375+
329376``` go
330377// Good - uses aggregation query, no data loaded
331378count , err := dsx.Query [User](db, ctx, " User" ).
@@ -339,7 +386,22 @@ users, err := dsx.Query[User](db, ctx, "User").
339386count := len (users)
340387```
341388
389+ ### Use GetMulti for Multiple Known IDs
390+
391+ ``` go
392+ // Good - single API call
393+ users , err := dsx.GetMulti [User](db, ctx, " User" , []string {" user-1" , " user-2" , " user-3" })
394+
395+ // Bad - multiple API calls
396+ for _ , id := range ids {
397+ user , err := dsx.Query [User](db, ctx, " User" ).
398+ WithFilter (dsx.FieldKey , dsx.OpEqual , id).
399+ Get ()
400+ }
401+ ```
402+
342403### Use Cursors for Deep Pagination
404+
343405``` go
344406// Good - efficient at any depth
345407users , cursor , err := dsx.Query [User](db, ctx, " User" ).
@@ -355,6 +417,7 @@ users, err := dsx.Query[User](db, ctx, "User").
355417```
356418
357419### Batch Operations for Multiple Entities
420+
358421``` go
359422// Good - single API call
360423err := dsx.Query [User](db, ctx, " User" ).UpsertMulti (usersMap)
@@ -368,6 +431,7 @@ for id, user := range usersMap {
368431### Use noindex for Non-Queryable Fields
369432
370433In your struct, mark fields you don't query to save on index writes:
434+
371435``` go
372436type User struct {
373437 Name string
@@ -380,13 +444,17 @@ type User struct {
380444## Error Handling
381445
382446The package logs errors with context before returning them:
447+
383448```
384449datastore User select-error <error details>
385450datastore User upsert-error <error details>
386451datastore User delete get-all error <error details>
452+ datastore get-multi User error <error details>
453+ datastore Order insert-with-auto-id-error <error details>
387454```
388455
389456Common errors:
457+
390458- ** "query defined to use offset instead of cursor"** - Can't use ` SelectWithCursor() ` after ` WithOffset() `
391459- ** "query defined to use cursor"** - Can't use ` Select() ` after ` WithCursor() `
392460
0 commit comments