@persistmemory/sdk 0.2.0 → 0.4.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.
@@ -1,6 +1,6 @@
1
1
  import type { HttpClient, RequestOptions } from "../http.js";
2
2
  import { Paginated } from "../pagination.js";
3
- import type { CreateSpaceParams, ListSpacesParams, Memory, Space, UpdateSpaceParams } from "../types.js";
3
+ import type { CreateSpaceParams, ListSpacesParams, Memory, ShareSpaceParams, Space, SpaceCollaborator, GrantableSpaceRole, UpdateSpaceParams, WorkingSpace } from "../types.js";
4
4
  /**
5
5
  * Spaces: a boundary around a set of memories.
6
6
  *
@@ -36,12 +36,22 @@ export declare class Spaces {
36
36
  * `delete` never destroys a memory that is filed in another Space as well —
37
37
  * that one is detached and left alone. `deleted` and `kept` come back so you
38
38
  * can say what actually happened.
39
+ *
40
+ * `moveTo` NAMES WHERE THE STRANDED ONES GO, and is refused-into rather than
41
+ * required: most deletions strand nothing, because everything in the Space
42
+ * is also filed elsewhere, and demanding a destination for those would be a
43
+ * question about nothing. When something WOULD be left in no Space at all,
44
+ * the server answers 400 naming this field. It used to file them into the
45
+ * account's default, which no longer exists — nothing picks a Space on
46
+ * anybody's behalf, so "keep these" has no answer unless you say where.
39
47
  */
40
48
  delete(id: string, params: {
41
49
  readonly memories: "keep" | "delete";
50
+ readonly moveTo?: string;
42
51
  }, options?: RequestOptions): Promise<{
43
52
  deleted: number;
44
53
  kept: number;
54
+ rehomed?: number;
45
55
  }>;
46
56
  /**
47
57
  * Merges Spaces into a NEW one, leaving every source exactly as it was.
@@ -60,29 +70,46 @@ export declare class Spaces {
60
70
  added: number;
61
71
  }>;
62
72
  /**
63
- * The Space this account files into when a capture names none.
73
+ * Which Space a context is working in.
74
+ *
75
+ * `chosen` absent means NOTHING sent from that context is kept — there is no
76
+ * default behind it and nothing picks one. The reply used to carry a
77
+ * `fallback` for that case and no longer can.
64
78
  *
65
- * `{}` an object with no `space` means there is no default, which is the
66
- * normal state rather than a gap. It is also what comes back after the Space
67
- * somebody chose has been deleted.
79
+ * `profile` is the command line's own profile name, and `surface` says which
80
+ * context is being asked about: `cli` for this profile, `email` for the
81
+ * account's ingest address. Between them they are the whole of what a caller
82
+ * may say about where it is working — the server builds the scope key
83
+ * itself, so this can never read or move where a chat is filing.
68
84
  */
69
- getDefault(options?: RequestOptions): Promise<{
70
- space?: Space;
71
- }>;
72
- /** `null` clears it. Not the same as omitting it, which is why the type says so. */
73
- setDefault(spaceId: string | null, options?: RequestOptions): Promise<{
74
- space?: Space;
75
- }>;
85
+ working(params?: {
86
+ readonly profile?: string;
87
+ readonly surface?: "cli" | "email";
88
+ }, options?: RequestOptions): Promise<WorkingSpace>;
89
+ /**
90
+ * Works in one from here on. `null` stops working in any.
91
+ *
92
+ * Takes an id or a NAME, because that is how a person says it. `null` rather
93
+ * than an omitted field: "clear this" and "I did not mention it" are
94
+ * different instructions, and clearing means this context keeps nothing
95
+ * until a Space is chosen again.
96
+ */
97
+ chooseWorking(space: string | null, params?: {
98
+ readonly profile?: string;
99
+ readonly surface?: "cli" | "email";
100
+ }, options?: RequestOptions): Promise<WorkingSpace>;
76
101
  /** Renaming, retention, and archiving - `archived` is a field, not a verb. */
77
102
  update(id: string, params: UpdateSpaceParams, options?: RequestOptions): Promise<Space>;
78
103
  /**
79
104
  * The memories filed in a Space.
80
105
  *
81
- * This endpoint answers `{ data, pagination: { limit } }` with no cursor: it
82
- * returns the first `limit` members and stops. Wrapped in a `Paginated`
83
- * anyway so it reads like every other list, and it simply yields one page -
84
- * a caller who needs more should filter `memories.list` by `spaceIds`, which
85
- * is the endpoint that actually pages.
106
+ * The cursor is PASSED. This fetch used to ignore the paginator's cursor
107
+ * on the stale belief that the endpoint had none the server has minted
108
+ * `pagination.nextCursor` since it started paging, and its own comment
109
+ * says "both SDKs iterate by reading pagination". Ignoring it meant every
110
+ * page request was identical: the loop guard saw a non-advancing fetch and
111
+ * stopped silently, so `all()` returned the first page twice and dropped
112
+ * everything after it — duplicated AND truncated data, with no error.
86
113
  */
87
114
  memories(id: string, params?: {
88
115
  readonly limit?: number;
@@ -100,4 +127,69 @@ export declare class Spaces {
100
127
  removeMemories(id: string, memoryIds: readonly string[], options?: RequestOptions): Promise<{
101
128
  removed: number;
102
129
  }>;
130
+ /**
131
+ * Who can see this Space, including invitations nobody has accepted.
132
+ *
133
+ * A DIFFERENT EDGE from `memories()` next door, and the difference is worth
134
+ * holding on to: that one maps a MEMORY to a Space, this one maps a PERSON
135
+ * to a Space. The server keeps them in two tables with two names for exactly
136
+ * that reason.
137
+ *
138
+ * Read `acceptedAt` before you render a row. An invitation grants nothing
139
+ * until it is accepted, so a list that draws invited and accepted people the
140
+ * same way tells its user somebody is reading their memories when nobody is.
141
+ *
142
+ * Paginated like every other list here. A Space has a handful of
143
+ * collaborators rather than thousands, so this will usually be one page -
144
+ * which costs a caller nothing and means the shape does not change if a
145
+ * Space ever has an organisation on it.
146
+ */
147
+ collaborators(id: string, params?: {
148
+ readonly limit?: number;
149
+ }, options?: RequestOptions): Paginated<SpaceCollaborator>;
150
+ /**
151
+ * Offers somebody sight of a Space. Answers with the invitation.
152
+ *
153
+ * AN OFFER, NOT A GRANT, and the returned `acceptedAt` will be absent to
154
+ * prove it. The recipient has to accept before they can see anything, which
155
+ * is the property that keeps "nothing enters your memory without you" true
156
+ * even when somebody else starts the sharing. Do not tell your user their
157
+ * Space "has been shared" on the strength of a 2xx here.
158
+ *
159
+ * WHAT THEY GET IS THE WHOLE SPACE: every memory already filed in it and
160
+ * every memory that lands in it afterwards. There is no narrower grant, and
161
+ * `role` does not make one - it decides what they may do BESIDES read.
162
+ *
163
+ * Worth an idempotency key when a person is behind it. A double-clicked
164
+ * "share" is two invitations to the same address, and the second one is a
165
+ * second email arriving at somebody who has already been asked.
166
+ */
167
+ share(id: string, params: ShareSpaceParams, options?: RequestOptions): Promise<SpaceCollaborator>;
168
+ /**
169
+ * Ends somebody's access, or withdraws an invitation they never accepted.
170
+ *
171
+ * Nothing was ever copied into their account - a collaborator SEES the
172
+ * owner's memories rather than holding a duplicate - so this is one write
173
+ * and not a cascade, and there is no orphaned copy left behind.
174
+ *
175
+ * A body on a DELETE, matching `removeMemories` above. The alternative is an
176
+ * address in a path segment, where every `.`, `+` and `@` is a chance for a
177
+ * proxy or a router to normalise somebody else's email into the one that
178
+ * gets revoked.
179
+ */
180
+ unshare(id: string, email: string, options?: RequestOptions): Promise<{
181
+ email: string;
182
+ }>;
183
+ /**
184
+ * Changes what an existing collaborator may do. Never invites anybody.
185
+ *
186
+ * The quiet one. Moving somebody from `viewer` to `owner` sends no
187
+ * invitation and needs no acceptance, and afterwards they can share the
188
+ * Space onward and revoke the person who promoted them. Show your user what
189
+ * `owner` means before you send this, not after.
190
+ */
191
+ setRole(id: string, params: {
192
+ readonly email: string;
193
+ readonly role: GrantableSpaceRole;
194
+ }, options?: RequestOptions): Promise<SpaceCollaborator>;
103
195
  }
package/dist/types.d.ts CHANGED
@@ -70,10 +70,35 @@ export interface Temporal {
70
70
  /** True while the claim is believed to hold. */
71
71
  readonly current: boolean;
72
72
  }
73
+ /**
74
+ * Whoever owns a memory that is not yours.
75
+ *
76
+ * A name, never a user id: the API does not send one, and nothing here takes
77
+ * one. `name` is absent when the account set neither a display name nor a
78
+ * username, which is why `mine` below and not this is what says the memory
79
+ * belongs to somebody else.
80
+ */
81
+ export interface SharedBy {
82
+ readonly name: string;
83
+ }
73
84
  export interface Memory {
74
85
  readonly id: string;
75
86
  readonly type: MemoryType;
76
87
  readonly state: MemoryState;
88
+ /**
89
+ * Whether YOU own this memory.
90
+ *
91
+ * Not always true. A listing returns everything your key may see, and that
92
+ * includes memories owned by people who shared a Space with you — filed in
93
+ * the same Spaces you file into, so `spaceIds` will not tell them apart.
94
+ * Anything that reads a memory back to a person, or puts one in a model's
95
+ * context, has to carry this: an unmarked claim from a colleague is
96
+ * indistinguishable from one of your own, and a model will state it in your
97
+ * voice.
98
+ */
99
+ readonly mine: boolean;
100
+ /** Who it belongs to, when `mine` is false and the API can name them. */
101
+ readonly sharedBy?: SharedBy;
77
102
  readonly title: string;
78
103
  readonly content: string;
79
104
  readonly value?: Readonly<Record<string, unknown>>;
@@ -116,6 +141,21 @@ export interface ListMemoriesParams {
116
141
  export interface RememberParams {
117
142
  readonly text: string;
118
143
  readonly title?: string;
144
+ /**
145
+ * The Spaces this is filed into. REQUIRED BY THE SERVER, though optional
146
+ * here.
147
+ *
148
+ * A capture with no Space is refused with a 400 that names this account's
149
+ * Spaces. It used to be filed into the account's default, its oldest Space,
150
+ * or a new one made for it — three destinations nobody chose — and none of
151
+ * those exists now: a Space is chosen or nothing is kept.
152
+ *
153
+ * It stays optional in the TYPE so that a caller upgrading this package gets
154
+ * the explanation from the server, in a sentence listing the Spaces they
155
+ * actually have, rather than a compile error that says "required" and leaves
156
+ * them looking for a list. Every call that omits it now fails at runtime;
157
+ * that break is intended and is the whole change.
158
+ */
119
159
  readonly spaceIds?: readonly string[];
120
160
  }
121
161
  /**
@@ -222,6 +262,35 @@ export interface Space {
222
262
  /** Set when archived. An archived Space is hidden, not deleted. */
223
263
  readonly archivedAt?: string;
224
264
  }
265
+ /**
266
+ * Where one context is filing, and whether anybody decided it.
267
+ *
268
+ * TWO FIELDS BECAUSE THEY ARE TWO FACTS, and merging them is the mistake this
269
+ * shape exists to make impossible. `chosen` is a Space somebody picked here: it
270
+ * narrows what a question is answered from as well as where a note lands.
271
+ * `fallback` is the account's own default, adopted automatically for captures
272
+ * that name none, and it narrows NOTHING — reading through it would silently
273
+ * scope every question the person ever asked to a Space they never picked.
274
+ *
275
+ * Neither present means no Space can be named at all, which is a real state
276
+ * rather than a gap: an account with no Spaces yet.
277
+ */
278
+ export interface WorkingSpace {
279
+ /** The context this answers about — `cli:default`, `email`. The server owns it. */
280
+ readonly scope: string;
281
+ /**
282
+ * The Space chosen for this context.
283
+ *
284
+ * ABSENT MEANS NOTHING IS KEPT from it, not "kept somewhere else". There
285
+ * used to be a `fallback` beside this — the account-wide Space a capture
286
+ * fell into when nobody chose — and it is gone with the setting itself: a
287
+ * Space is chosen or the capture is refused.
288
+ */
289
+ readonly chosen?: {
290
+ readonly id: string;
291
+ readonly name: string;
292
+ };
293
+ }
225
294
  export interface CreateSpaceParams {
226
295
  readonly name: string;
227
296
  readonly description?: string;
@@ -241,6 +310,56 @@ export interface ListSpacesParams {
241
310
  readonly cursor?: string;
242
311
  readonly includeArchived?: boolean;
243
312
  }
313
+ /**
314
+ * What somebody you shared a Space with may do with it.
315
+ *
316
+ * A WIDENING SCALE: every role sees everything filed in the Space, and the
317
+ * role decides only what they may do BESIDES read. `viewer` reads it,
318
+ * `editor` also files new memories into it, `owner` can additionally share it
319
+ * onward and revoke people - including you.
320
+ */
321
+ export type SpaceRole = "viewer" | "editor" | "owner";
322
+ /**
323
+ * A role that may actually be GRANTED, which is not every role.
324
+ *
325
+ * `owner` is real — the Space's owner holds it and a collaborator listing
326
+ * shows it — but no request can hand it out. `POST /collaborators` and
327
+ * `PATCH /collaborators/:id` both take `["viewer", "editor"]`, and the store
328
+ * throws `CannotGrantOwnership` behind them, so a call carrying `owner` is
329
+ * refused with a 400 every time.
330
+ *
331
+ * Separate from `SpaceRole` rather than narrowing it, because the two are
332
+ * genuinely different questions: what a collaborator MAY BE is three values,
333
+ * what you may SET them to is two. Typing the parameters as `SpaceRole` made
334
+ * the compiler accept a call the server always rejects — a typed client whose
335
+ * types are wrong about the server is worse than an untyped one, because the
336
+ * error arrives at runtime after the type system said it was fine.
337
+ */
338
+ export type GrantableSpaceRole = Exclude<SpaceRole, "owner">;
339
+ /**
340
+ * One person who has been given, or merely offered, sight of a Space.
341
+ *
342
+ * `acceptedAt` is the field to read before you render anything. An invitation
343
+ * grants NOTHING until the recipient accepts, so a UI that shows an invited
344
+ * person the same way it shows an accepted one tells its user that somebody is
345
+ * reading their memories when nobody is - and read the other way, hides that
346
+ * an invitation never landed.
347
+ */
348
+ export interface SpaceCollaborator {
349
+ readonly email: string;
350
+ readonly name?: string;
351
+ readonly role: SpaceRole;
352
+ readonly invitedBy?: string;
353
+ readonly invitedAt?: string;
354
+ /** Absent means they have accepted nothing and can see nothing. */
355
+ readonly acceptedAt?: string;
356
+ }
357
+ export interface ShareSpaceParams {
358
+ /** The person to offer the Space to, by address. */
359
+ readonly email: string;
360
+ /** See `GrantableSpaceRole` — `owner` cannot be given away. */
361
+ readonly role: GrantableSpaceRole;
362
+ }
244
363
  export interface Source {
245
364
  readonly id: string;
246
365
  readonly provider: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@persistmemory/sdk",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "The official TypeScript client for the PersistMemory API",
5
5
  "license": "MIT",
6
6
  "author": "PersistMemory",