@mymehq/sdk 5.1.0 → 5.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.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { MergeStrategy, ConflictSnapshot, MergePolicy, ItemState, Tier, Item, CreateItemInput, PaginatedResult, ItemWithMetadata, Version, Edge, Metadata, SearchResult, CreateEdgeInput, EdgeTypeSchema, TypeSchema, CreateKeyInput, ApiKey, UpdateKeyInput, CreateWebhookInput, Webhook, UpdateWebhookInput, WebhookDelivery, ConnectionInstallResult, ConnectionUninstallResult, PreviewEventRequest, PreviewEventResult, TenantConfig, TenantQuota, Profile, UpdateProfileInput } from '@mymehq/shared';
2
- export { ApiKey, ConflictSnapshot, CreateItemInput, CreateKeyInput, Item, ItemState, MergePolicy, MergeStrategy, Metadata, PaginatedResult, Profile, SearchResult, TypeSchema, UpdateKeyInput, UpdateProfileInput, Version } from '@mymehq/shared';
1
+ import { MergeStrategy, ConflictSnapshot, MergePolicy, ItemState, Tier, CreateItemInput, Item, PaginatedResult, ItemWithMetadata, Version, Edge, Metadata, SearchResult, CreateEdgeInput, EdgeTypeSchema, TypeSchema, CreateKeyInput, ApiKey, UpdateKeyInput, CreateWebhookInput, Webhook, UpdateWebhookInput, WebhookDelivery, ConnectionInstallResult, ConnectionUninstallResult, PreviewEventRequest, PreviewEventResult, TenantConfig, TenantQuota, Tenant, TenantActivityEntry, TenantMetrics, Profile, UpdateProfileInput } from '@mymehq/shared';
2
+ export { ApiKey, ConflictSnapshot, CreateItemInput, CreateKeyInput, Item, ItemState, MergePolicy, MergeStrategy, Metadata, PaginatedResult, Profile, SearchResult, Tenant, TenantActivityEntry, TenantMetrics, TenantQuota, TenantStatus, TypeSchema, UpdateKeyInput, UpdateProfileInput, Version } from '@mymehq/shared';
3
3
 
4
4
  /**
5
5
  * Conflict resolution strategy for item updates.
@@ -178,6 +178,21 @@ interface SearchFilters {
178
178
  interface MetadataInput {
179
179
  tags?: string[];
180
180
  }
181
+ /**
182
+ * Compact API-key summary returned by the admin keys-list route
183
+ * (T-117). The full `ApiKey` shape carries permission maps; the
184
+ * operator surface deliberately surfaces only the identifying fields +
185
+ * timestamps needed for emergency revocation.
186
+ */
187
+ interface TenantApiKeySummary {
188
+ id: string;
189
+ label: string;
190
+ source: string;
191
+ role: string;
192
+ is_platform: boolean;
193
+ created_at: string;
194
+ last_used_at: string | null;
195
+ }
181
196
  /** One item to create or upsert in a `POST /items/bulk` call. Shape is
182
197
  * `CreateItemInput` plus compact outbound-only inline `edges`. */
183
198
  interface BulkItemInput {
@@ -199,6 +214,57 @@ interface BulkItemInput {
199
214
  edges?: Record<string, string[]>;
200
215
  }
201
216
  type BulkMode = "upsert" | "create_only";
217
+ /**
218
+ * Input to `client.items.createWithAttachments(...)` (T-100). Wraps the
219
+ * existing blob-upload + `items.bulk` pattern: upload each attachment's
220
+ * blob, then issue one atomic bulk call containing the host item and the
221
+ * `core.file.*` items with inline `attached-to` edges from each
222
+ * attachment back to the host.
223
+ */
224
+ interface CreateWithAttachmentsInput {
225
+ /** The host item — the thing the attachments are attached *to* (note,
226
+ * message, etc.). May carry an explicit `id` for client-minted UUIDs
227
+ * and arbitrary `edges` of its own (passed through unchanged into the
228
+ * bulk payload). */
229
+ item: CreateItemInput & {
230
+ id?: string;
231
+ edges?: Record<string, string[]>;
232
+ };
233
+ /** Attachments in caller-passed order. The returned `attachments`
234
+ * array preserves the same order. Empty arrays are valid: the helper
235
+ * still issues one `items.bulk` call with just the host item. */
236
+ attachments: CreateWithAttachmentsAttachment[];
237
+ /** Edge type from each attachment back to the host. Defaults to
238
+ * `"attached-to"` (the canonical attachment edge — T-108). Override
239
+ * for app-specific semantics like `"cover-image"`. */
240
+ edgeType?: string;
241
+ }
242
+ interface CreateWithAttachmentsAttachment {
243
+ /** Type id, e.g. `"core.file.image"`. */
244
+ type: string;
245
+ /** Raw blob bytes. Uploaded via `POST /blobs`. */
246
+ blob: Uint8Array | ArrayBuffer;
247
+ /** Sent as the `Content-Type` of the blob upload. Stamped onto the
248
+ * attachment item's `mime_type` property automatically. */
249
+ mimeType: string;
250
+ /** Caller-supplied properties for this attachment item (e.g. `width`
251
+ * and `height` for `core.file.image`). The helper auto-fills
252
+ * `blob_ref` (= upload hash) and `mime_type` after upload; do not
253
+ * pre-populate them. */
254
+ properties?: Record<string, unknown>;
255
+ /** Optional additional edges on the attachment item. The helper
256
+ * appends its own `[edgeType]: [hostId]` entry; if you pass a value
257
+ * for the same `edgeType`, your ids are merged with the host id
258
+ * (helper-added edges are additive, never stripping). */
259
+ edges?: Record<string, string[]>;
260
+ /** Explicit attachment id (defaults to a client-minted UUIDv7). */
261
+ id?: string;
262
+ }
263
+ interface CreateWithAttachmentsResult {
264
+ host: Item;
265
+ /** Attachment items in the same order as `input.attachments`. */
266
+ attachments: Item[];
267
+ }
202
268
  interface BulkInput {
203
269
  items: BulkItemInput[];
204
270
  /** Default: `"upsert"`. */
@@ -404,6 +470,34 @@ declare class MymeClient {
404
470
  * `atomic: true` (default) rolls back the whole batch on any failure.
405
471
  */
406
472
  bulk: (input: BulkInput) => Promise<BulkResult>;
473
+ /**
474
+ * Create a host item plus a set of attached `core.file.*` items in
475
+ * one call (T-100). Wraps the existing `blobs.upload` + `items.bulk`
476
+ * primitives: each attachment's blob is uploaded concurrently, then a
477
+ * single atomic bulk call writes the host and the attachments
478
+ * together with inline `attached-to` edges from each attachment back
479
+ * to the host (matching T-108's spec direction).
480
+ *
481
+ * **Partial-failure contract.** Blob uploads run concurrently via
482
+ * `Promise.all`. On upload failure, throws with the failing
483
+ * attachment's index in the message. Successfully-uploaded blobs are
484
+ * not cleaned up; the server's CAS dedupe (`blob_ref` is the
485
+ * content hash) makes orphaned uploads cost-trivial — the same bytes
486
+ * uploaded later resolve to the same hash. Callers are expected to
487
+ * retry the whole call rather than reason about partial state.
488
+ *
489
+ * **Empty `attachments`** is valid and supported: the helper still
490
+ * issues one `items.bulk` call with just the host item, no blob
491
+ * uploads, no auto-edges. Lets callers use this method as a uniform
492
+ * entry point regardless of whether attachments are present.
493
+ *
494
+ * **Caller-provided edges co-exist with the auto-`attached-to`
495
+ * edges.** If the caller passes edges on the host item, they're
496
+ * passed through unchanged. If the caller passes edges on an
497
+ * attachment item with the same `edgeType` key, the helper's host id
498
+ * is appended to that array (additive, never strip-and-replace).
499
+ */
500
+ createWithAttachments: (input: CreateWithAttachmentsInput) => Promise<CreateWithAttachmentsResult>;
407
501
  /**
408
502
  * Apply one action to every item matching a filter. Six actions
409
503
  * discriminated on `action`. Purge is admin-only and requires
@@ -648,6 +742,54 @@ declare class MymeClient {
648
742
  }) => Promise<TenantQuota>;
649
743
  };
650
744
  };
745
+ /**
746
+ * Operator-level admin surface — the `my admin` CLI command tree's
747
+ * backing endpoints. Every method requires a platform-admin key
748
+ * (`is_platform: true`). Non-platform credentials get a `403
749
+ * forbidden`; render `"this command requires a platform-admin key"`
750
+ * in CLI / UI layers.
751
+ *
752
+ * Quota read/write is intentionally NOT duplicated here — it lives on
753
+ * `client.tenants.quotas.{getById, set}` and is already platform-
754
+ * admin-gated. The admin namespace mirrors what the CLI's `my admin`
755
+ * tree exposes; quotas are reached via the existing tenants surface.
756
+ */
757
+ readonly admin: {
758
+ tenants: {
759
+ /** List every tenant in the instance with current status. */
760
+ list: () => Promise<Tenant[]>;
761
+ /**
762
+ * Single tenant + per-tenant quota overrides + the most-recent
763
+ * `system.activity` items for the tenant (`null` quotas when no
764
+ * override is configured; quota fields then resolve to instance
765
+ * defaults).
766
+ */
767
+ show: (tenantId: string) => Promise<{
768
+ tenant: Tenant;
769
+ quotas: TenantQuota | null;
770
+ recent_activity: TenantActivityEntry[];
771
+ }>;
772
+ /**
773
+ * Flip the tenant's status to `'suspended'`. Future non-GET
774
+ * requests from credentials in the tenant return HTTP 403
775
+ * `tenant_suspended`. Reads pass through; platform-admin keys
776
+ * bypass. Idempotent.
777
+ */
778
+ suspend: (tenantId: string) => Promise<Tenant>;
779
+ /** Reverse of `suspend`. Idempotent. */
780
+ unsuspend: (tenantId: string) => Promise<Tenant>;
781
+ /**
782
+ * Per-tenant usage snapshot — item count by state, blob count
783
+ * and total bytes, custom-type count, plus recent activity.
784
+ */
785
+ metrics: (tenantId: string) => Promise<TenantMetrics>;
786
+ };
787
+ keys: {
788
+ /** Active (non-revoked) keys for the named tenant. Operator
789
+ * surface for emergency revocation — pair with `client.keys.revoke`. */
790
+ list: (tenantId: string) => Promise<TenantApiKeySummary[]>;
791
+ };
792
+ };
651
793
  /**
652
794
  * The calling user's profile. `system.profile` is a virtual type —
653
795
  * served by a dedicated endpoint over the `users` table joined to
@@ -815,4 +957,4 @@ interface VerifyWebhookSignatureInput {
815
957
  */
816
958
  declare function verifyWebhookSignature(input: VerifyWebhookSignatureInput): WebhookVerifyResult;
817
959
 
818
- export { type BulkActionErrorEntry, type BulkActionFilter, type BulkActionInput, type BulkActionResult, type BulkEdgeInput, type BulkEdgeInputItem, type BulkEdgeResult, type BulkEdgeResultEntry, type BulkInput, type BulkItemInput, type BulkMode, type BulkOutcome, type BulkResult, type BulkResultEntry, type ClientConfig, type ConflictAutoMergeListener, type ConflictAutoMergedEvent, type ConflictData, ConflictError, type ConflictResolver, type ConflictStrategy, ForbiddenError, type ItemWithExtensions, type ListFilters, type MetadataInput, MymeClient, MymeError, NotFoundError, type SearchFilters, UnauthorizedError, type UpdateOptions, ValidationError, type VerifyWebhookSignatureInput, type WebhookVerifyReason, type WebhookVerifyResult, defineType, verifyWebhookSignature };
960
+ export { type BulkActionErrorEntry, type BulkActionFilter, type BulkActionInput, type BulkActionResult, type BulkEdgeInput, type BulkEdgeInputItem, type BulkEdgeResult, type BulkEdgeResultEntry, type BulkInput, type BulkItemInput, type BulkMode, type BulkOutcome, type BulkResult, type BulkResultEntry, type ClientConfig, type ConflictAutoMergeListener, type ConflictAutoMergedEvent, type ConflictData, ConflictError, type ConflictResolver, type ConflictStrategy, type CreateWithAttachmentsAttachment, type CreateWithAttachmentsInput, type CreateWithAttachmentsResult, ForbiddenError, type ItemWithExtensions, type ListFilters, type MetadataInput, MymeClient, MymeError, NotFoundError, type SearchFilters, type TenantApiKeySummary, UnauthorizedError, type UpdateOptions, ValidationError, type VerifyWebhookSignatureInput, type WebhookVerifyReason, type WebhookVerifyResult, defineType, verifyWebhookSignature };
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ // src/client.ts
2
+ import { generateId } from "@mymehq/shared";
3
+
1
4
  // src/errors.ts
2
5
  var MymeError = class extends Error {
3
6
  code;
@@ -525,6 +528,118 @@ var MymeClient = class {
525
528
  body: input
526
529
  });
527
530
  },
531
+ /**
532
+ * Create a host item plus a set of attached `core.file.*` items in
533
+ * one call (T-100). Wraps the existing `blobs.upload` + `items.bulk`
534
+ * primitives: each attachment's blob is uploaded concurrently, then a
535
+ * single atomic bulk call writes the host and the attachments
536
+ * together with inline `attached-to` edges from each attachment back
537
+ * to the host (matching T-108's spec direction).
538
+ *
539
+ * **Partial-failure contract.** Blob uploads run concurrently via
540
+ * `Promise.all`. On upload failure, throws with the failing
541
+ * attachment's index in the message. Successfully-uploaded blobs are
542
+ * not cleaned up; the server's CAS dedupe (`blob_ref` is the
543
+ * content hash) makes orphaned uploads cost-trivial — the same bytes
544
+ * uploaded later resolve to the same hash. Callers are expected to
545
+ * retry the whole call rather than reason about partial state.
546
+ *
547
+ * **Empty `attachments`** is valid and supported: the helper still
548
+ * issues one `items.bulk` call with just the host item, no blob
549
+ * uploads, no auto-edges. Lets callers use this method as a uniform
550
+ * entry point regardless of whether attachments are present.
551
+ *
552
+ * **Caller-provided edges co-exist with the auto-`attached-to`
553
+ * edges.** If the caller passes edges on the host item, they're
554
+ * passed through unchanged. If the caller passes edges on an
555
+ * attachment item with the same `edgeType` key, the helper's host id
556
+ * is appended to that array (additive, never strip-and-replace).
557
+ */
558
+ createWithAttachments: async (input) => {
559
+ const edgeType = input.edgeType ?? "attached-to";
560
+ const hostId = input.item.id ?? generateId();
561
+ const uploadResults = await Promise.all(
562
+ input.attachments.map(async (att, idx) => {
563
+ try {
564
+ return await this.blobs.upload(att.blob, att.mimeType);
565
+ } catch (err) {
566
+ throw new MymeError(
567
+ "blob_upload_failed",
568
+ `createWithAttachments: blob upload failed for attachments[${String(idx)}] (type=${att.type}): ${err instanceof Error ? err.message : String(err)}`,
569
+ 502
570
+ );
571
+ }
572
+ })
573
+ );
574
+ const hostBulkItem = {
575
+ ...input.item,
576
+ id: hostId
577
+ };
578
+ const attachmentIds = [];
579
+ const attachmentBulkItems = input.attachments.map(
580
+ (att, idx) => {
581
+ const attachmentId = att.id ?? generateId();
582
+ attachmentIds.push(attachmentId);
583
+ const upload = uploadResults[idx];
584
+ if (!upload) {
585
+ throw new MymeError(
586
+ "internal_error",
587
+ `createWithAttachments: upload result missing for attachments[${String(idx)}]`,
588
+ 500
589
+ );
590
+ }
591
+ const callerEdges = att.edges ?? {};
592
+ const callerSameType = callerEdges[edgeType] ?? [];
593
+ const mergedEdges = {
594
+ ...callerEdges,
595
+ [edgeType]: [...callerSameType, hostId]
596
+ };
597
+ return {
598
+ id: attachmentId,
599
+ type: att.type,
600
+ properties: {
601
+ ...att.properties ?? {},
602
+ blob_ref: upload.hash,
603
+ mime_type: att.mimeType
604
+ },
605
+ edges: mergedEdges
606
+ };
607
+ }
608
+ );
609
+ const bulkResult = await this.items.bulk({
610
+ items: [hostBulkItem, ...attachmentBulkItems],
611
+ mode: "create_only",
612
+ atomic: true
613
+ });
614
+ const errored = bulkResult.results.find((r) => r.outcome === "errored");
615
+ if (errored) {
616
+ throw new MymeError(
617
+ errored.error?.code ?? "bulk_failed",
618
+ `createWithAttachments: bulk write failed at index ${String(errored.index)}: ${errored.error?.message ?? errored.reason ?? "unknown"}`,
619
+ 400
620
+ );
621
+ }
622
+ const skipped = bulkResult.results.find((r) => r.outcome === "skipped");
623
+ if (skipped) {
624
+ throw new MymeError(
625
+ "duplicate_id",
626
+ `createWithAttachments: bulk write skipped at index ${String(skipped.index)} (reason: ${skipped.reason ?? "unknown"}). The helper requires fresh ids \u2014 if you passed an explicit \`item.id\`, it must not already exist.`,
627
+ 409
628
+ );
629
+ }
630
+ const hydrated = await Promise.all(
631
+ [hostId, ...attachmentIds].map((id) => this.items.get(id))
632
+ );
633
+ const [host, ...attachments] = hydrated;
634
+ if (!host) {
635
+ throw new MymeError(
636
+ "internal_error",
637
+ "createWithAttachments: host hydration returned no item",
638
+ 500
639
+ );
640
+ }
641
+ return { host, attachments };
642
+ },
528
643
  /**
529
644
  * Apply one action to every item matching a filter. Six actions
530
645
  * discriminated on `action`. Purge is admin-only and requires
@@ -1014,6 +1129,77 @@ var MymeClient = class {
1014
1129
  }
1015
1130
  }
1016
1131
  };
1132
+ // ---- Admin (T-117) ----
1133
+ /**
1134
+ * Operator-level admin surface — the `my admin` CLI command tree's
1135
+ * backing endpoints. Every method requires a platform-admin key
1136
+ * (`is_platform: true`). Non-platform credentials get a `403
1137
+ * forbidden`; render `"this command requires a platform-admin key"`
1138
+ * in CLI / UI layers.
1139
+ *
1140
+ * Quota read/write is intentionally NOT duplicated here — it lives on
1141
+ * `client.tenants.quotas.{getById, set}` and is already platform-
1142
+ * admin-gated. The admin namespace mirrors what the CLI's `my admin`
1143
+ * tree exposes; quotas are reached via the existing tenants surface.
1144
+ */
1145
+ admin = {
1146
+ tenants: {
1147
+ /** List every tenant in the instance with current status. */
1148
+ list: async () => {
1149
+ const res = await this.transport.request(
1150
+ "GET",
1151
+ "/admin/tenants"
1152
+ );
1153
+ return res.data;
1154
+ },
1155
+ /**
1156
+ * Single tenant + per-tenant quota overrides + the most-recent
1157
+ * `system.activity` items for the tenant (`null` quotas when no
1158
+ * override is configured; quota fields then resolve to instance
1159
+ * defaults).
1160
+ */
1161
+ show: async (tenantId) => {
1162
+ return this.transport.request("GET", `/admin/tenants/${encodeURIComponent(tenantId)}`);
1163
+ },
1164
+ /**
1165
+ * Flip the tenant's status to `'suspended'`. Future non-GET
1166
+ * requests from credentials in the tenant return HTTP 403
1167
+ * `tenant_suspended`. Reads pass through; platform-admin keys
1168
+ * bypass. Idempotent.
1169
+ */
1170
+ suspend: async (tenantId) => {
1171
+ return this.transport.request(
1172
+ "POST",
1173
+ `/admin/tenants/${encodeURIComponent(tenantId)}/suspend`
1174
+ );
1175
+ },
1176
+ /** Reverse of `suspend`. Idempotent. */
1177
+ unsuspend: async (tenantId) => {
1178
+ return this.transport.request(
1179
+ "POST",
1180
+ `/admin/tenants/${encodeURIComponent(tenantId)}/unsuspend`
1181
+ );
1182
+ },
1183
+ /**
1184
+ * Per-tenant usage snapshot — item count by state, blob count
1185
+ * and total bytes, custom-type count, plus recent activity.
1186
+ */
1187
+ metrics: async (tenantId) => {
1188
+ return this.transport.request(
1189
+ "GET",
1190
+ `/admin/tenants/${encodeURIComponent(tenantId)}/metrics`
1191
+ );
1192
+ }
1193
+ },
1194
+ keys: {
1195
+ /** Active (non-revoked) keys for the named tenant. Operator
1196
+ * surface for emergency revocation — pair with `client.keys.revoke`. */
1197
+ list: async (tenantId) => {
1198
+ const res = await this.transport.request("GET", `/admin/tenants/${encodeURIComponent(tenantId)}/keys`);
1199
+ return res.data;
1200
+ }
1201
+ }
1202
+ };
1017
1203
  // ---- Profile (T-074) ----
1018
1204
  /**
1019
1205
  * The calling user's profile. `system.profile` is a virtual type —
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mymehq/sdk",
3
- "version": "5.1.0",
3
+ "version": "5.3.0",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",
@@ -20,7 +20,7 @@
20
20
  "dist"
21
21
  ],
22
22
  "dependencies": {
23
- "@mymehq/shared": "5.1.0"
23
+ "@mymehq/shared": "5.3.0"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@types/node": "^22.0.0",