@proofchain/sdk 2.23.0 → 3.1.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.mts CHANGED
@@ -1,11 +1,39 @@
1
1
  /**
2
2
  * HTTP Client for ProofChain API
3
3
  */
4
+ /**
5
+ * Returns the per-tenant Cloudflare edge API URL for `slug`.
6
+ *
7
+ * Cloudflare can cache responses from per-tenant hosts
8
+ * (`https://{slug}.proofchain.co.za/api`) because the full URL is the cache
9
+ * key. The shared `api.proofchain.co.za` host is keyed on the `X-Tenant-ID`
10
+ * header and cannot be cached safely at the CDN layer.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { tenantApiUrl, ProofChain } from '@proofchain/sdk';
15
+ *
16
+ * const client = new ProofChain({
17
+ * apiKey: 'atst_…',
18
+ * baseUrl: tenantApiUrl('acme'), // https://acme.proofchain.co.za/api
19
+ * });
20
+ * ```
21
+ */
22
+ declare function tenantApiUrl(slug: string): string;
4
23
  interface HttpClientOptions {
5
24
  apiKey?: string;
6
25
  userToken?: string;
7
26
  tenantId?: string;
27
+ /**
28
+ * Explicit base URL — highest precedence.
29
+ * Covers custom domains (e.g. `https://fanpass.onefootball.com/api`).
30
+ */
8
31
  baseUrl?: string;
32
+ /**
33
+ * Tenant slug used to derive the per-tenant edge URL when no explicit
34
+ * `baseUrl` is provided. Precedence: baseUrl > tenant > default.
35
+ */
36
+ tenant?: string;
9
37
  timeout?: number;
10
38
  maxRetries?: number;
11
39
  }
@@ -16,6 +44,7 @@ declare class HttpClient {
16
44
  private baseUrl;
17
45
  private timeout;
18
46
  private maxRetries;
47
+ private readonly inflight;
19
48
  constructor(options: HttpClientOptions);
20
49
  private getHeaders;
21
50
  private handleResponse;
@@ -384,9 +413,51 @@ interface ProofChainOptions {
384
413
  apiKey?: string;
385
414
  /** End-user JWT for JWKS auth (alternative to apiKey). For PWA clients. */
386
415
  userToken?: string;
387
- /** Tenant slug or client_id. Required when using userToken. */
416
+ /**
417
+ * Tenant slug or client_id. Required when using userToken.
418
+ *
419
+ * When used as the sole host hint (no explicit `baseUrl`), the SDK also
420
+ * derives the base URL as `https://{tenantId}.proofchain.co.za/api` so
421
+ * requests hit the per-tenant Cloudflare edge host — enabling response
422
+ * caching that the shared `api.proofchain.co.za` origin cannot provide
423
+ * (the shared host is keyed on headers, not URL, and is therefore
424
+ * uncacheable at the CDN layer).
425
+ */
388
426
  tenantId?: string;
427
+ /**
428
+ * Override the API base URL (highest precedence).
429
+ *
430
+ * Use this for custom domains (e.g. `https://fanpass-portal.onefootball.com/api`)
431
+ * or self-hosted deployments. When omitted and `tenantId` is set, the SDK
432
+ * automatically uses `https://{tenantId}.proofchain.co.za/api`.
433
+ *
434
+ * Precedence: `baseUrl` > `tenantId`-derived URL > shared default.
435
+ */
389
436
  baseUrl?: string;
437
+ /**
438
+ * Tenant slug used to derive the per-tenant Cloudflare edge URL
439
+ * (`https://{tenant}.proofchain.co.za/api`) when no explicit `baseUrl` is
440
+ * supplied.
441
+ *
442
+ * Edge caching requires the per-tenant host: the shared `api.proofchain.co.za`
443
+ * host is keyed on the `X-Tenant-ID` header and cannot be safely cached by
444
+ * Cloudflare. Providing this option is the easiest way for integrators to opt
445
+ * into cached responses without managing a custom domain.
446
+ *
447
+ * @example
448
+ * ```typescript
449
+ * // Requests go to https://acme.proofchain.co.za/api — edge cached.
450
+ * const client = new ProofChain({ apiKey: 'atst_…', tenant: 'acme' });
451
+ *
452
+ * // Custom domain takes precedence; tenant is ignored for URL derivation.
453
+ * const client = new ProofChain({
454
+ * apiKey: 'atst_…',
455
+ * tenant: 'acme',
456
+ * baseUrl: 'https://fanpass.onefootball.com/api',
457
+ * });
458
+ * ```
459
+ */
460
+ tenant?: string;
390
461
  timeout?: number;
391
462
  maxRetries?: number;
392
463
  }
@@ -2238,7 +2309,7 @@ interface QuestStep {
2238
2309
  name: string;
2239
2310
  description?: string;
2240
2311
  order: number;
2241
- step_type: 'event' | 'manual' | 'external' | 'compound';
2312
+ step_type: 'event_count' | 'event_match' | 'unique_values' | 'cumulative' | 'streak' | 'manual' | 'threshold' | 'recency' | 'time_windowed_count' | 'wallet_holds' | 'milestone';
2242
2313
  event_type?: string;
2243
2314
  event_types?: string[];
2244
2315
  criteria?: Record<string, any>;
@@ -2354,7 +2425,7 @@ interface CreateQuestStepRequest {
2354
2425
  name: string;
2355
2426
  description?: string;
2356
2427
  order?: number;
2357
- step_type?: 'event' | 'manual' | 'external' | 'compound';
2428
+ step_type?: 'event_count' | 'event_match' | 'unique_values' | 'cumulative' | 'streak' | 'manual' | 'threshold' | 'recency' | 'time_windowed_count' | 'wallet_holds' | 'milestone';
2358
2429
  event_type?: string;
2359
2430
  event_types?: string[];
2360
2431
  criteria?: Record<string, any>;
@@ -2394,9 +2465,10 @@ declare class QuestsClient {
2394
2465
  category?: string;
2395
2466
  }): Promise<QuestWithProgress[]>;
2396
2467
  /**
2397
- * Get a quest by slug
2468
+ * REMOVED: getBySlug() the server never implemented GET /quests/slug/{slug}.
2469
+ * There is no slug-based lookup route in the API. Use list() with a status/category
2470
+ * filter and find the quest client-side, or use get() with the quest ID.
2398
2471
  */
2399
- getBySlug(slug: string): Promise<Quest>;
2400
2472
  /**
2401
2473
  * Create a quest
2402
2474
  */
@@ -2422,9 +2494,9 @@ declare class QuestsClient {
2422
2494
  */
2423
2495
  pause(questId: string): Promise<Quest>;
2424
2496
  /**
2425
- * Archive a quest
2497
+ * REMOVED: archive() — the server never implemented POST /quests/{id}/archive.
2498
+ * To archive a quest, use update(questId, { status: 'archived' }) via PUT /quests/{id}.
2426
2499
  */
2427
- archive(questId: string): Promise<Quest>;
2428
2500
  /**
2429
2501
  * Get quest with user progress
2430
2502
  * Fetches the quest and user's progress separately and combines them
@@ -2469,19 +2541,37 @@ declare class QuestsClient {
2469
2541
  */
2470
2542
  claimReward(questId: string, userId: string): Promise<ClaimRewardResult>;
2471
2543
  /**
2472
- * Add a step to a quest
2544
+ * Add a step to a quest.
2545
+ *
2546
+ * The server has no dedicated step-create endpoint — steps are managed via the
2547
+ * full-replace PUT /quests/{id}. This method fetches the quest, appends the new
2548
+ * step, and issues a full update. The returned QuestStep is synthesised from the
2549
+ * updated quest's steps array (matched by order or last position).
2473
2550
  */
2474
2551
  addStep(questId: string, step: CreateQuestStepRequest): Promise<QuestStep>;
2475
2552
  /**
2476
- * Update a step
2553
+ * Update a step by step ID.
2554
+ *
2555
+ * The server has no dedicated step-update endpoint — steps are managed via the
2556
+ * full-replace PUT /quests/{id}. This method fetches the quest, replaces the
2557
+ * matching step, and issues a full update.
2477
2558
  */
2478
2559
  updateStep(questId: string, stepId: string, data: Partial<CreateQuestStepRequest>): Promise<QuestStep>;
2479
2560
  /**
2480
- * Delete a step
2561
+ * Delete a step by step ID.
2562
+ *
2563
+ * The server has no dedicated step-delete endpoint — steps are managed via the
2564
+ * full-replace PUT /quests/{id}. This method fetches the quest, filters out the
2565
+ * target step, and issues a full update.
2481
2566
  */
2482
2567
  deleteStep(questId: string, stepId: string): Promise<void>;
2483
2568
  /**
2484
- * Reorder steps
2569
+ * Reorder steps by providing step IDs in the desired order.
2570
+ *
2571
+ * The server has no dedicated reorder endpoint — steps are managed via the
2572
+ * full-replace PUT /quests/{id}. This method fetches the quest, reassigns
2573
+ * the `order` field according to the given stepIds sequence, and issues a
2574
+ * full update.
2485
2575
  */
2486
2576
  reorderSteps(questId: string, stepIds: string[]): Promise<Quest>;
2487
2577
  }
@@ -4186,4 +4276,4 @@ declare class TimeoutError extends ProofChainError {
4186
4276
  constructor(message?: string);
4187
4277
  }
4188
4278
 
4189
- export { type Achievement, type ActivitySummaryView, type AddNFTRequest, type AnchorResult, type ApiKey, type AttestRequest, type AttestationConfig, type AttestationMode, type Attestation as AttestationRecord, type AttestationResult, type AttestationSchema, AttestationsClient, AuthenticationError, AuthorizationError, type AvailableView, type Badge, type BatchIngestRequest, type BatchIngestResponse, type BatchVerifyResult, type BlockchainProof, type BlockchainStats, type Certificate, type CertificateVerifyResult, CertificatesResource, type Channel, type ChannelState, type ChannelStatus, ChannelsResource, type ClaimNFTResult, type ClaimRewardResult, type CohortDefinition, type CohortGroupStats, CohortLeaderboardClient, type CohortLeaderboardEntry, type CohortLeaderboardOptions, type CohortLeaderboardResponse, type ComprehensiveWalletInfo, type CreateAchievementRequest, type CreateApiKeyRequest, type CreateAttestationRequest, type CreateBadgeRequest, type CreateChannelRequest, type CreateCredentialTypeRequest, type CreateDataViewRequest, type CreateDualWalletsRequest, type CreateEndUserRequest, type CreateEventRequest, type CreatePassportDefinitionRequest, type CreatePassportRequest, type CreateQuestRequest, type CreateQuestStepRequest, type CreateRewardDefinitionRequest, type CreateSchemaRequest, type CreateTemplateFieldRequest, type CreateTemplateRequest, type CreateWalletRequest as CreateUserWalletRequest, type CreateWalletRequest$1 as CreateWalletRequest, type CreateWebhookRequest, type CredentialType, type CredentialVerifyResult, CredentialsClient, type DataViewColumn, type DataViewComputation, type DataViewDetail, type DataViewExecuteResult, type DataViewInfo, type DataViewListResponse, type DataViewPreviewRequest, type DataViewPreviewResult, type DataViewSummary, DataViewsClient, type DistributeResult, DocumentsResource, type DualWallets, type EarnedReward, type EndUser, EndUserIngestionClient, type EndUserIngestionClientOptions, type EndUserListResponse, EndUsersClient, type Event, type EventBatchProof, type EventMetadata, type EventStatus, EventsResource, type ExecuteSwapRequest, type Facet, type FacetsResponse, type FanProfileView, type FanpassGroupStats, FanpassLeaderboardClient, type FanpassLeaderboardEntry, type FanpassLeaderboardOptions, type FanpassLeaderboardResponse, type FanpassUserComparisonResponse, type FieldValue, type GDPRDeletionRequest, type GDPRDeletionResponse, type GDPRPreviewResponse, type IngestEventRequest, type IngestEventResponse, IngestionClient, type IngestionClientOptions, type IssueCertificateRequest, type IssueCredentialRequest, type IssuedCredential, type LeaderboardUserProfile$1 as LeaderboardUserProfile, type LinkWalletRequest, type ListAttestationsOptions, type ListCertificatesRequest, type ListCohortsOptions, type ListEndUsersOptions, type ListEventsRequest, type ListIssuedCredentialsOptions, type ListMintsOptions, type ListMintsResponse, type ListQuestsOptions, type ListRewardsOptions, type ListSchemasOptions, type ListWalletCredentialsOptions, type ManualRewardRequest, type MergeUsersRequest, type Milestone, type MintNFTRequest, type NFT, type NFTMint, NetworkError, NftClient, NotFoundError, type NotificationCallback, type NotificationEvent, type NotificationEventType, NotificationsClient, type OTTConfigResponse, type OTTConfigUpdate, type OTTGenerateRequest, type OTTGenerateResponse, type OTTRedeemResponse, type OTTRequestResponse, PartnerKeysClient, type Passport, PassportClient, type PassportDefinition, type PassportFieldValue, type PassportHistory, type PassportTemplate, type PassportV2Data, type PassportWithFields, ProofChain, ProofChainError, type ProofChainOptions, type ProofVerifyResult, type PushSubscriptionJSON, type Quest, type QuestStep, type QuestWithProgress, QuestsClient, RateLimitError, type RegisterWalletRequest, type RevokeResult, type RewardAsset, type RewardAttestationResult, type RewardDefinition, type RewardEarned, type VerifyResult as RewardVerifyResult, RewardsClient, type Schema, type SchemaDetail, type SchemaField, type SchemaListResponse, type ValidationError$1 as SchemaValidationError, SchemasClient, type SearchFilters, type SearchQueryRequest, type SearchRequest, SearchResource, type SearchResponse, type SearchResult, type SearchStats, ServerError, type SetProfileRequest, type Settlement, type StepCompletionResult, type StepProgress, type StepStartResult, type StreamAck, type StreamEventRequest, type SubscribeOptions, type SwapQuote, type SwapQuoteRequest, type SwapResult, type TemplateField, type TenantInfo, TenantResource, type TierDefinition, TimeoutError, type TokenBalance, type TransferRequest, type TransferResult, type Unsubscribe, type UpdateConfigRequest as UpdateAttestationConfigRequest, type UpdateDataViewRequest, type UpdateEndUserRequest, type UpdatePassportRequest, type UpdateWebhookRequest, type UsageStats, type UserAchievement, type UserActivity, type UserActivityResponse, type UserBadge, type UserBreakdownResponse, type UserCohortBreakdownEntry, type UserCredentialsSummary, type UserQuestProgress, type UserReward$1 as UserReward, type UserRewardsResponse, type UserWalletSummary, type UserWithWallets, type UsersWithWalletsResponse, type ValidateDataRequest, ValidationError, type ValidationErrorDetail, type ValidationResult, type VaultFile, type VaultFolder, type VaultListResponse, VaultResource, type VaultStats, type VaultUploadRequest, type VerificationResult, VerifyResource, type ViewColumn, type ViewTemplate, type Wallet, type WalletBalance, WalletClient, type WalletCreationResult, type WalletCredential, type WalletCredentialVerifySummary, type WalletStats, type Webhook, WebhooksResource };
4279
+ export { type Achievement, type ActivitySummaryView, type AddNFTRequest, type AnchorResult, type ApiKey, type AttestRequest, type AttestationConfig, type AttestationMode, type Attestation as AttestationRecord, type AttestationResult, type AttestationSchema, AttestationsClient, AuthenticationError, AuthorizationError, type AvailableView, type Badge, type BatchIngestRequest, type BatchIngestResponse, type BatchVerifyResult, type BlockchainProof, type BlockchainStats, type Certificate, type CertificateVerifyResult, CertificatesResource, type Channel, type ChannelState, type ChannelStatus, ChannelsResource, type ClaimNFTResult, type ClaimRewardResult, type CohortDefinition, type CohortGroupStats, CohortLeaderboardClient, type CohortLeaderboardEntry, type CohortLeaderboardOptions, type CohortLeaderboardResponse, type ComprehensiveWalletInfo, type CreateAchievementRequest, type CreateApiKeyRequest, type CreateAttestationRequest, type CreateBadgeRequest, type CreateChannelRequest, type CreateCredentialTypeRequest, type CreateDataViewRequest, type CreateDualWalletsRequest, type CreateEndUserRequest, type CreateEventRequest, type CreatePassportDefinitionRequest, type CreatePassportRequest, type CreateQuestRequest, type CreateQuestStepRequest, type CreateRewardDefinitionRequest, type CreateSchemaRequest, type CreateTemplateFieldRequest, type CreateTemplateRequest, type CreateWalletRequest as CreateUserWalletRequest, type CreateWalletRequest$1 as CreateWalletRequest, type CreateWebhookRequest, type CredentialType, type CredentialVerifyResult, CredentialsClient, type DataViewColumn, type DataViewComputation, type DataViewDetail, type DataViewExecuteResult, type DataViewInfo, type DataViewListResponse, type DataViewPreviewRequest, type DataViewPreviewResult, type DataViewSummary, DataViewsClient, type DistributeResult, DocumentsResource, type DualWallets, type EarnedReward, type EndUser, EndUserIngestionClient, type EndUserIngestionClientOptions, type EndUserListResponse, EndUsersClient, type Event, type EventBatchProof, type EventMetadata, type EventStatus, EventsResource, type ExecuteSwapRequest, type Facet, type FacetsResponse, type FanProfileView, type FanpassGroupStats, FanpassLeaderboardClient, type FanpassLeaderboardEntry, type FanpassLeaderboardOptions, type FanpassLeaderboardResponse, type FanpassUserComparisonResponse, type FieldValue, type GDPRDeletionRequest, type GDPRDeletionResponse, type GDPRPreviewResponse, type IngestEventRequest, type IngestEventResponse, IngestionClient, type IngestionClientOptions, type IssueCertificateRequest, type IssueCredentialRequest, type IssuedCredential, type LeaderboardUserProfile$1 as LeaderboardUserProfile, type LinkWalletRequest, type ListAttestationsOptions, type ListCertificatesRequest, type ListCohortsOptions, type ListEndUsersOptions, type ListEventsRequest, type ListIssuedCredentialsOptions, type ListMintsOptions, type ListMintsResponse, type ListQuestsOptions, type ListRewardsOptions, type ListSchemasOptions, type ListWalletCredentialsOptions, type ManualRewardRequest, type MergeUsersRequest, type Milestone, type MintNFTRequest, type NFT, type NFTMint, NetworkError, NftClient, NotFoundError, type NotificationCallback, type NotificationEvent, type NotificationEventType, NotificationsClient, type OTTConfigResponse, type OTTConfigUpdate, type OTTGenerateRequest, type OTTGenerateResponse, type OTTRedeemResponse, type OTTRequestResponse, PartnerKeysClient, type Passport, PassportClient, type PassportDefinition, type PassportFieldValue, type PassportHistory, type PassportTemplate, type PassportV2Data, type PassportWithFields, ProofChain, ProofChainError, type ProofChainOptions, type ProofVerifyResult, type PushSubscriptionJSON, type Quest, type QuestStep, type QuestWithProgress, QuestsClient, RateLimitError, type RegisterWalletRequest, type RevokeResult, type RewardAsset, type RewardAttestationResult, type RewardDefinition, type RewardEarned, type VerifyResult as RewardVerifyResult, RewardsClient, type Schema, type SchemaDetail, type SchemaField, type SchemaListResponse, type ValidationError$1 as SchemaValidationError, SchemasClient, type SearchFilters, type SearchQueryRequest, type SearchRequest, SearchResource, type SearchResponse, type SearchResult, type SearchStats, ServerError, type SetProfileRequest, type Settlement, type StepCompletionResult, type StepProgress, type StepStartResult, type StreamAck, type StreamEventRequest, type SubscribeOptions, type SwapQuote, type SwapQuoteRequest, type SwapResult, type TemplateField, type TenantInfo, TenantResource, type TierDefinition, TimeoutError, type TokenBalance, type TransferRequest, type TransferResult, type Unsubscribe, type UpdateConfigRequest as UpdateAttestationConfigRequest, type UpdateDataViewRequest, type UpdateEndUserRequest, type UpdatePassportRequest, type UpdateWebhookRequest, type UsageStats, type UserAchievement, type UserActivity, type UserActivityResponse, type UserBadge, type UserBreakdownResponse, type UserCohortBreakdownEntry, type UserCredentialsSummary, type UserQuestProgress, type UserReward$1 as UserReward, type UserRewardsResponse, type UserWalletSummary, type UserWithWallets, type UsersWithWalletsResponse, type ValidateDataRequest, ValidationError, type ValidationErrorDetail, type ValidationResult, type VaultFile, type VaultFolder, type VaultListResponse, VaultResource, type VaultStats, type VaultUploadRequest, type VerificationResult, VerifyResource, type ViewColumn, type ViewTemplate, type Wallet, type WalletBalance, WalletClient, type WalletCreationResult, type WalletCredential, type WalletCredentialVerifySummary, type WalletStats, type Webhook, WebhooksResource, tenantApiUrl };
package/dist/index.d.ts CHANGED
@@ -1,11 +1,39 @@
1
1
  /**
2
2
  * HTTP Client for ProofChain API
3
3
  */
4
+ /**
5
+ * Returns the per-tenant Cloudflare edge API URL for `slug`.
6
+ *
7
+ * Cloudflare can cache responses from per-tenant hosts
8
+ * (`https://{slug}.proofchain.co.za/api`) because the full URL is the cache
9
+ * key. The shared `api.proofchain.co.za` host is keyed on the `X-Tenant-ID`
10
+ * header and cannot be cached safely at the CDN layer.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { tenantApiUrl, ProofChain } from '@proofchain/sdk';
15
+ *
16
+ * const client = new ProofChain({
17
+ * apiKey: 'atst_…',
18
+ * baseUrl: tenantApiUrl('acme'), // https://acme.proofchain.co.za/api
19
+ * });
20
+ * ```
21
+ */
22
+ declare function tenantApiUrl(slug: string): string;
4
23
  interface HttpClientOptions {
5
24
  apiKey?: string;
6
25
  userToken?: string;
7
26
  tenantId?: string;
27
+ /**
28
+ * Explicit base URL — highest precedence.
29
+ * Covers custom domains (e.g. `https://fanpass.onefootball.com/api`).
30
+ */
8
31
  baseUrl?: string;
32
+ /**
33
+ * Tenant slug used to derive the per-tenant edge URL when no explicit
34
+ * `baseUrl` is provided. Precedence: baseUrl > tenant > default.
35
+ */
36
+ tenant?: string;
9
37
  timeout?: number;
10
38
  maxRetries?: number;
11
39
  }
@@ -16,6 +44,7 @@ declare class HttpClient {
16
44
  private baseUrl;
17
45
  private timeout;
18
46
  private maxRetries;
47
+ private readonly inflight;
19
48
  constructor(options: HttpClientOptions);
20
49
  private getHeaders;
21
50
  private handleResponse;
@@ -384,9 +413,51 @@ interface ProofChainOptions {
384
413
  apiKey?: string;
385
414
  /** End-user JWT for JWKS auth (alternative to apiKey). For PWA clients. */
386
415
  userToken?: string;
387
- /** Tenant slug or client_id. Required when using userToken. */
416
+ /**
417
+ * Tenant slug or client_id. Required when using userToken.
418
+ *
419
+ * When used as the sole host hint (no explicit `baseUrl`), the SDK also
420
+ * derives the base URL as `https://{tenantId}.proofchain.co.za/api` so
421
+ * requests hit the per-tenant Cloudflare edge host — enabling response
422
+ * caching that the shared `api.proofchain.co.za` origin cannot provide
423
+ * (the shared host is keyed on headers, not URL, and is therefore
424
+ * uncacheable at the CDN layer).
425
+ */
388
426
  tenantId?: string;
427
+ /**
428
+ * Override the API base URL (highest precedence).
429
+ *
430
+ * Use this for custom domains (e.g. `https://fanpass-portal.onefootball.com/api`)
431
+ * or self-hosted deployments. When omitted and `tenantId` is set, the SDK
432
+ * automatically uses `https://{tenantId}.proofchain.co.za/api`.
433
+ *
434
+ * Precedence: `baseUrl` > `tenantId`-derived URL > shared default.
435
+ */
389
436
  baseUrl?: string;
437
+ /**
438
+ * Tenant slug used to derive the per-tenant Cloudflare edge URL
439
+ * (`https://{tenant}.proofchain.co.za/api`) when no explicit `baseUrl` is
440
+ * supplied.
441
+ *
442
+ * Edge caching requires the per-tenant host: the shared `api.proofchain.co.za`
443
+ * host is keyed on the `X-Tenant-ID` header and cannot be safely cached by
444
+ * Cloudflare. Providing this option is the easiest way for integrators to opt
445
+ * into cached responses without managing a custom domain.
446
+ *
447
+ * @example
448
+ * ```typescript
449
+ * // Requests go to https://acme.proofchain.co.za/api — edge cached.
450
+ * const client = new ProofChain({ apiKey: 'atst_…', tenant: 'acme' });
451
+ *
452
+ * // Custom domain takes precedence; tenant is ignored for URL derivation.
453
+ * const client = new ProofChain({
454
+ * apiKey: 'atst_…',
455
+ * tenant: 'acme',
456
+ * baseUrl: 'https://fanpass.onefootball.com/api',
457
+ * });
458
+ * ```
459
+ */
460
+ tenant?: string;
390
461
  timeout?: number;
391
462
  maxRetries?: number;
392
463
  }
@@ -2238,7 +2309,7 @@ interface QuestStep {
2238
2309
  name: string;
2239
2310
  description?: string;
2240
2311
  order: number;
2241
- step_type: 'event' | 'manual' | 'external' | 'compound';
2312
+ step_type: 'event_count' | 'event_match' | 'unique_values' | 'cumulative' | 'streak' | 'manual' | 'threshold' | 'recency' | 'time_windowed_count' | 'wallet_holds' | 'milestone';
2242
2313
  event_type?: string;
2243
2314
  event_types?: string[];
2244
2315
  criteria?: Record<string, any>;
@@ -2354,7 +2425,7 @@ interface CreateQuestStepRequest {
2354
2425
  name: string;
2355
2426
  description?: string;
2356
2427
  order?: number;
2357
- step_type?: 'event' | 'manual' | 'external' | 'compound';
2428
+ step_type?: 'event_count' | 'event_match' | 'unique_values' | 'cumulative' | 'streak' | 'manual' | 'threshold' | 'recency' | 'time_windowed_count' | 'wallet_holds' | 'milestone';
2358
2429
  event_type?: string;
2359
2430
  event_types?: string[];
2360
2431
  criteria?: Record<string, any>;
@@ -2394,9 +2465,10 @@ declare class QuestsClient {
2394
2465
  category?: string;
2395
2466
  }): Promise<QuestWithProgress[]>;
2396
2467
  /**
2397
- * Get a quest by slug
2468
+ * REMOVED: getBySlug() the server never implemented GET /quests/slug/{slug}.
2469
+ * There is no slug-based lookup route in the API. Use list() with a status/category
2470
+ * filter and find the quest client-side, or use get() with the quest ID.
2398
2471
  */
2399
- getBySlug(slug: string): Promise<Quest>;
2400
2472
  /**
2401
2473
  * Create a quest
2402
2474
  */
@@ -2422,9 +2494,9 @@ declare class QuestsClient {
2422
2494
  */
2423
2495
  pause(questId: string): Promise<Quest>;
2424
2496
  /**
2425
- * Archive a quest
2497
+ * REMOVED: archive() — the server never implemented POST /quests/{id}/archive.
2498
+ * To archive a quest, use update(questId, { status: 'archived' }) via PUT /quests/{id}.
2426
2499
  */
2427
- archive(questId: string): Promise<Quest>;
2428
2500
  /**
2429
2501
  * Get quest with user progress
2430
2502
  * Fetches the quest and user's progress separately and combines them
@@ -2469,19 +2541,37 @@ declare class QuestsClient {
2469
2541
  */
2470
2542
  claimReward(questId: string, userId: string): Promise<ClaimRewardResult>;
2471
2543
  /**
2472
- * Add a step to a quest
2544
+ * Add a step to a quest.
2545
+ *
2546
+ * The server has no dedicated step-create endpoint — steps are managed via the
2547
+ * full-replace PUT /quests/{id}. This method fetches the quest, appends the new
2548
+ * step, and issues a full update. The returned QuestStep is synthesised from the
2549
+ * updated quest's steps array (matched by order or last position).
2473
2550
  */
2474
2551
  addStep(questId: string, step: CreateQuestStepRequest): Promise<QuestStep>;
2475
2552
  /**
2476
- * Update a step
2553
+ * Update a step by step ID.
2554
+ *
2555
+ * The server has no dedicated step-update endpoint — steps are managed via the
2556
+ * full-replace PUT /quests/{id}. This method fetches the quest, replaces the
2557
+ * matching step, and issues a full update.
2477
2558
  */
2478
2559
  updateStep(questId: string, stepId: string, data: Partial<CreateQuestStepRequest>): Promise<QuestStep>;
2479
2560
  /**
2480
- * Delete a step
2561
+ * Delete a step by step ID.
2562
+ *
2563
+ * The server has no dedicated step-delete endpoint — steps are managed via the
2564
+ * full-replace PUT /quests/{id}. This method fetches the quest, filters out the
2565
+ * target step, and issues a full update.
2481
2566
  */
2482
2567
  deleteStep(questId: string, stepId: string): Promise<void>;
2483
2568
  /**
2484
- * Reorder steps
2569
+ * Reorder steps by providing step IDs in the desired order.
2570
+ *
2571
+ * The server has no dedicated reorder endpoint — steps are managed via the
2572
+ * full-replace PUT /quests/{id}. This method fetches the quest, reassigns
2573
+ * the `order` field according to the given stepIds sequence, and issues a
2574
+ * full update.
2485
2575
  */
2486
2576
  reorderSteps(questId: string, stepIds: string[]): Promise<Quest>;
2487
2577
  }
@@ -4186,4 +4276,4 @@ declare class TimeoutError extends ProofChainError {
4186
4276
  constructor(message?: string);
4187
4277
  }
4188
4278
 
4189
- export { type Achievement, type ActivitySummaryView, type AddNFTRequest, type AnchorResult, type ApiKey, type AttestRequest, type AttestationConfig, type AttestationMode, type Attestation as AttestationRecord, type AttestationResult, type AttestationSchema, AttestationsClient, AuthenticationError, AuthorizationError, type AvailableView, type Badge, type BatchIngestRequest, type BatchIngestResponse, type BatchVerifyResult, type BlockchainProof, type BlockchainStats, type Certificate, type CertificateVerifyResult, CertificatesResource, type Channel, type ChannelState, type ChannelStatus, ChannelsResource, type ClaimNFTResult, type ClaimRewardResult, type CohortDefinition, type CohortGroupStats, CohortLeaderboardClient, type CohortLeaderboardEntry, type CohortLeaderboardOptions, type CohortLeaderboardResponse, type ComprehensiveWalletInfo, type CreateAchievementRequest, type CreateApiKeyRequest, type CreateAttestationRequest, type CreateBadgeRequest, type CreateChannelRequest, type CreateCredentialTypeRequest, type CreateDataViewRequest, type CreateDualWalletsRequest, type CreateEndUserRequest, type CreateEventRequest, type CreatePassportDefinitionRequest, type CreatePassportRequest, type CreateQuestRequest, type CreateQuestStepRequest, type CreateRewardDefinitionRequest, type CreateSchemaRequest, type CreateTemplateFieldRequest, type CreateTemplateRequest, type CreateWalletRequest as CreateUserWalletRequest, type CreateWalletRequest$1 as CreateWalletRequest, type CreateWebhookRequest, type CredentialType, type CredentialVerifyResult, CredentialsClient, type DataViewColumn, type DataViewComputation, type DataViewDetail, type DataViewExecuteResult, type DataViewInfo, type DataViewListResponse, type DataViewPreviewRequest, type DataViewPreviewResult, type DataViewSummary, DataViewsClient, type DistributeResult, DocumentsResource, type DualWallets, type EarnedReward, type EndUser, EndUserIngestionClient, type EndUserIngestionClientOptions, type EndUserListResponse, EndUsersClient, type Event, type EventBatchProof, type EventMetadata, type EventStatus, EventsResource, type ExecuteSwapRequest, type Facet, type FacetsResponse, type FanProfileView, type FanpassGroupStats, FanpassLeaderboardClient, type FanpassLeaderboardEntry, type FanpassLeaderboardOptions, type FanpassLeaderboardResponse, type FanpassUserComparisonResponse, type FieldValue, type GDPRDeletionRequest, type GDPRDeletionResponse, type GDPRPreviewResponse, type IngestEventRequest, type IngestEventResponse, IngestionClient, type IngestionClientOptions, type IssueCertificateRequest, type IssueCredentialRequest, type IssuedCredential, type LeaderboardUserProfile$1 as LeaderboardUserProfile, type LinkWalletRequest, type ListAttestationsOptions, type ListCertificatesRequest, type ListCohortsOptions, type ListEndUsersOptions, type ListEventsRequest, type ListIssuedCredentialsOptions, type ListMintsOptions, type ListMintsResponse, type ListQuestsOptions, type ListRewardsOptions, type ListSchemasOptions, type ListWalletCredentialsOptions, type ManualRewardRequest, type MergeUsersRequest, type Milestone, type MintNFTRequest, type NFT, type NFTMint, NetworkError, NftClient, NotFoundError, type NotificationCallback, type NotificationEvent, type NotificationEventType, NotificationsClient, type OTTConfigResponse, type OTTConfigUpdate, type OTTGenerateRequest, type OTTGenerateResponse, type OTTRedeemResponse, type OTTRequestResponse, PartnerKeysClient, type Passport, PassportClient, type PassportDefinition, type PassportFieldValue, type PassportHistory, type PassportTemplate, type PassportV2Data, type PassportWithFields, ProofChain, ProofChainError, type ProofChainOptions, type ProofVerifyResult, type PushSubscriptionJSON, type Quest, type QuestStep, type QuestWithProgress, QuestsClient, RateLimitError, type RegisterWalletRequest, type RevokeResult, type RewardAsset, type RewardAttestationResult, type RewardDefinition, type RewardEarned, type VerifyResult as RewardVerifyResult, RewardsClient, type Schema, type SchemaDetail, type SchemaField, type SchemaListResponse, type ValidationError$1 as SchemaValidationError, SchemasClient, type SearchFilters, type SearchQueryRequest, type SearchRequest, SearchResource, type SearchResponse, type SearchResult, type SearchStats, ServerError, type SetProfileRequest, type Settlement, type StepCompletionResult, type StepProgress, type StepStartResult, type StreamAck, type StreamEventRequest, type SubscribeOptions, type SwapQuote, type SwapQuoteRequest, type SwapResult, type TemplateField, type TenantInfo, TenantResource, type TierDefinition, TimeoutError, type TokenBalance, type TransferRequest, type TransferResult, type Unsubscribe, type UpdateConfigRequest as UpdateAttestationConfigRequest, type UpdateDataViewRequest, type UpdateEndUserRequest, type UpdatePassportRequest, type UpdateWebhookRequest, type UsageStats, type UserAchievement, type UserActivity, type UserActivityResponse, type UserBadge, type UserBreakdownResponse, type UserCohortBreakdownEntry, type UserCredentialsSummary, type UserQuestProgress, type UserReward$1 as UserReward, type UserRewardsResponse, type UserWalletSummary, type UserWithWallets, type UsersWithWalletsResponse, type ValidateDataRequest, ValidationError, type ValidationErrorDetail, type ValidationResult, type VaultFile, type VaultFolder, type VaultListResponse, VaultResource, type VaultStats, type VaultUploadRequest, type VerificationResult, VerifyResource, type ViewColumn, type ViewTemplate, type Wallet, type WalletBalance, WalletClient, type WalletCreationResult, type WalletCredential, type WalletCredentialVerifySummary, type WalletStats, type Webhook, WebhooksResource };
4279
+ export { type Achievement, type ActivitySummaryView, type AddNFTRequest, type AnchorResult, type ApiKey, type AttestRequest, type AttestationConfig, type AttestationMode, type Attestation as AttestationRecord, type AttestationResult, type AttestationSchema, AttestationsClient, AuthenticationError, AuthorizationError, type AvailableView, type Badge, type BatchIngestRequest, type BatchIngestResponse, type BatchVerifyResult, type BlockchainProof, type BlockchainStats, type Certificate, type CertificateVerifyResult, CertificatesResource, type Channel, type ChannelState, type ChannelStatus, ChannelsResource, type ClaimNFTResult, type ClaimRewardResult, type CohortDefinition, type CohortGroupStats, CohortLeaderboardClient, type CohortLeaderboardEntry, type CohortLeaderboardOptions, type CohortLeaderboardResponse, type ComprehensiveWalletInfo, type CreateAchievementRequest, type CreateApiKeyRequest, type CreateAttestationRequest, type CreateBadgeRequest, type CreateChannelRequest, type CreateCredentialTypeRequest, type CreateDataViewRequest, type CreateDualWalletsRequest, type CreateEndUserRequest, type CreateEventRequest, type CreatePassportDefinitionRequest, type CreatePassportRequest, type CreateQuestRequest, type CreateQuestStepRequest, type CreateRewardDefinitionRequest, type CreateSchemaRequest, type CreateTemplateFieldRequest, type CreateTemplateRequest, type CreateWalletRequest as CreateUserWalletRequest, type CreateWalletRequest$1 as CreateWalletRequest, type CreateWebhookRequest, type CredentialType, type CredentialVerifyResult, CredentialsClient, type DataViewColumn, type DataViewComputation, type DataViewDetail, type DataViewExecuteResult, type DataViewInfo, type DataViewListResponse, type DataViewPreviewRequest, type DataViewPreviewResult, type DataViewSummary, DataViewsClient, type DistributeResult, DocumentsResource, type DualWallets, type EarnedReward, type EndUser, EndUserIngestionClient, type EndUserIngestionClientOptions, type EndUserListResponse, EndUsersClient, type Event, type EventBatchProof, type EventMetadata, type EventStatus, EventsResource, type ExecuteSwapRequest, type Facet, type FacetsResponse, type FanProfileView, type FanpassGroupStats, FanpassLeaderboardClient, type FanpassLeaderboardEntry, type FanpassLeaderboardOptions, type FanpassLeaderboardResponse, type FanpassUserComparisonResponse, type FieldValue, type GDPRDeletionRequest, type GDPRDeletionResponse, type GDPRPreviewResponse, type IngestEventRequest, type IngestEventResponse, IngestionClient, type IngestionClientOptions, type IssueCertificateRequest, type IssueCredentialRequest, type IssuedCredential, type LeaderboardUserProfile$1 as LeaderboardUserProfile, type LinkWalletRequest, type ListAttestationsOptions, type ListCertificatesRequest, type ListCohortsOptions, type ListEndUsersOptions, type ListEventsRequest, type ListIssuedCredentialsOptions, type ListMintsOptions, type ListMintsResponse, type ListQuestsOptions, type ListRewardsOptions, type ListSchemasOptions, type ListWalletCredentialsOptions, type ManualRewardRequest, type MergeUsersRequest, type Milestone, type MintNFTRequest, type NFT, type NFTMint, NetworkError, NftClient, NotFoundError, type NotificationCallback, type NotificationEvent, type NotificationEventType, NotificationsClient, type OTTConfigResponse, type OTTConfigUpdate, type OTTGenerateRequest, type OTTGenerateResponse, type OTTRedeemResponse, type OTTRequestResponse, PartnerKeysClient, type Passport, PassportClient, type PassportDefinition, type PassportFieldValue, type PassportHistory, type PassportTemplate, type PassportV2Data, type PassportWithFields, ProofChain, ProofChainError, type ProofChainOptions, type ProofVerifyResult, type PushSubscriptionJSON, type Quest, type QuestStep, type QuestWithProgress, QuestsClient, RateLimitError, type RegisterWalletRequest, type RevokeResult, type RewardAsset, type RewardAttestationResult, type RewardDefinition, type RewardEarned, type VerifyResult as RewardVerifyResult, RewardsClient, type Schema, type SchemaDetail, type SchemaField, type SchemaListResponse, type ValidationError$1 as SchemaValidationError, SchemasClient, type SearchFilters, type SearchQueryRequest, type SearchRequest, SearchResource, type SearchResponse, type SearchResult, type SearchStats, ServerError, type SetProfileRequest, type Settlement, type StepCompletionResult, type StepProgress, type StepStartResult, type StreamAck, type StreamEventRequest, type SubscribeOptions, type SwapQuote, type SwapQuoteRequest, type SwapResult, type TemplateField, type TenantInfo, TenantResource, type TierDefinition, TimeoutError, type TokenBalance, type TransferRequest, type TransferResult, type Unsubscribe, type UpdateConfigRequest as UpdateAttestationConfigRequest, type UpdateDataViewRequest, type UpdateEndUserRequest, type UpdatePassportRequest, type UpdateWebhookRequest, type UsageStats, type UserAchievement, type UserActivity, type UserActivityResponse, type UserBadge, type UserBreakdownResponse, type UserCohortBreakdownEntry, type UserCredentialsSummary, type UserQuestProgress, type UserReward$1 as UserReward, type UserRewardsResponse, type UserWalletSummary, type UserWithWallets, type UsersWithWalletsResponse, type ValidateDataRequest, ValidationError, type ValidationErrorDetail, type ValidationResult, type VaultFile, type VaultFolder, type VaultListResponse, VaultResource, type VaultStats, type VaultUploadRequest, type VerificationResult, VerifyResource, type ViewColumn, type ViewTemplate, type Wallet, type WalletBalance, WalletClient, type WalletCreationResult, type WalletCredential, type WalletCredentialVerifySummary, type WalletStats, type Webhook, WebhooksResource, tenantApiUrl };
package/dist/index.js CHANGED
@@ -54,7 +54,8 @@ __export(index_exports, {
54
54
  VaultResource: () => VaultResource,
55
55
  VerifyResource: () => VerifyResource,
56
56
  WalletClient: () => WalletClient,
57
- WebhooksResource: () => WebhooksResource
57
+ WebhooksResource: () => WebhooksResource,
58
+ tenantApiUrl: () => tenantApiUrl
58
59
  });
59
60
  module.exports = __toCommonJS(index_exports);
60
61
 
@@ -123,12 +124,19 @@ var TimeoutError = class extends ProofChainError {
123
124
  var DEFAULT_BASE_URL = "https://api.proofchain.co.za";
124
125
  var DEFAULT_TIMEOUT = 3e4;
125
126
  var USER_AGENT = "proofchain-js/0.1.0";
127
+ function tenantApiUrl(slug) {
128
+ return `https://${slug}.proofchain.co.za/api`;
129
+ }
126
130
  var HttpClient = class {
127
131
  constructor(options) {
132
+ // In-flight GET de-duplication: concurrent identical GETs share one network
133
+ // request (and its response), cutting origin load for chatty UIs/pollers.
134
+ this.inflight = /* @__PURE__ */ new Map();
128
135
  this.apiKey = options.apiKey || "";
129
136
  this.userToken = options.userToken || "";
130
137
  this.tenantId = options.tenantId || "";
131
- this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
138
+ const resolvedBase = options.baseUrl || (options.tenant ? tenantApiUrl(options.tenant) : DEFAULT_BASE_URL);
139
+ this.baseUrl = resolvedBase.replace(/\/$/, "");
132
140
  this.timeout = options.timeout || DEFAULT_TIMEOUT;
133
141
  this.maxRetries = options.maxRetries ?? 3;
134
142
  }
@@ -193,7 +201,8 @@ var HttpClient = class {
193
201
  clearTimeout(timeoutId);
194
202
  if (error instanceof ProofChainError) {
195
203
  if (error instanceof RateLimitError && retries < this.maxRetries) {
196
- const delay = Math.min((error.retryAfter || 1) * 1e3, 6e4);
204
+ const base = Math.min((error.retryAfter || 1) * 1e3, 6e4);
205
+ const delay = base + Math.floor(Math.random() * 1e3);
197
206
  await new Promise((resolve) => setTimeout(resolve, delay));
198
207
  return this.fetchWithRetry(url, options, retries + 1);
199
208
  }
@@ -204,7 +213,9 @@ var HttpClient = class {
204
213
  throw new TimeoutError();
205
214
  }
206
215
  if (retries < this.maxRetries) {
207
- await new Promise((resolve) => setTimeout(resolve, 1e3 * (retries + 1)));
216
+ const base = 1e3 * (retries + 1);
217
+ const delay = base + Math.floor(Math.random() * 500);
218
+ await new Promise((resolve) => setTimeout(resolve, delay));
208
219
  return this.fetchWithRetry(url, options, retries + 1);
209
220
  }
210
221
  throw new NetworkError(error.message, error);
@@ -233,6 +244,17 @@ var HttpClient = class {
233
244
  if (options.body) {
234
245
  fetchOptions.body = JSON.stringify(options.body);
235
246
  }
247
+ if (method === "GET") {
248
+ const existing = this.inflight.get(url);
249
+ if (existing) {
250
+ return existing;
251
+ }
252
+ const pending = this.fetchWithRetry(url, fetchOptions).finally(() => {
253
+ this.inflight.delete(url);
254
+ });
255
+ this.inflight.set(url, pending);
256
+ return pending;
257
+ }
236
258
  return this.fetchWithRetry(url, fetchOptions);
237
259
  }
238
260
  async requestMultipart(path, formData) {
@@ -1726,11 +1748,10 @@ var QuestsClient = class {
1726
1748
  return this.http.get(`/quests/available?${params.toString()}`);
1727
1749
  }
1728
1750
  /**
1729
- * Get a quest by slug
1751
+ * REMOVED: getBySlug() the server never implemented GET /quests/slug/{slug}.
1752
+ * There is no slug-based lookup route in the API. Use list() with a status/category
1753
+ * filter and find the quest client-side, or use get() with the quest ID.
1730
1754
  */
1731
- async getBySlug(slug) {
1732
- return this.http.get(`/quests/slug/${encodeURIComponent(slug)}`);
1733
- }
1734
1755
  /**
1735
1756
  * Create a quest
1736
1757
  */
@@ -1771,11 +1792,9 @@ var QuestsClient = class {
1771
1792
  return this.http.post(`/quests/${questId}/pause`, {});
1772
1793
  }
1773
1794
  /**
1774
- * Archive a quest
1795
+ * REMOVED: archive() — the server never implemented POST /quests/{id}/archive.
1796
+ * To archive a quest, use update(questId, { status: 'archived' }) via PUT /quests/{id}.
1775
1797
  */
1776
- async archive(questId) {
1777
- return this.http.post(`/quests/${questId}/archive`, {});
1778
- }
1779
1798
  // ---------------------------------------------------------------------------
1780
1799
  // User Progress
1781
1800
  // ---------------------------------------------------------------------------
@@ -1855,28 +1874,144 @@ var QuestsClient = class {
1855
1874
  // Quest Steps
1856
1875
  // ---------------------------------------------------------------------------
1857
1876
  /**
1858
- * Add a step to a quest
1877
+ * Add a step to a quest.
1878
+ *
1879
+ * The server has no dedicated step-create endpoint — steps are managed via the
1880
+ * full-replace PUT /quests/{id}. This method fetches the quest, appends the new
1881
+ * step, and issues a full update. The returned QuestStep is synthesised from the
1882
+ * updated quest's steps array (matched by order or last position).
1859
1883
  */
1860
1884
  async addStep(questId, step) {
1861
- return this.http.post(`/quests/${questId}/steps`, step);
1862
- }
1863
- /**
1864
- * Update a step
1885
+ const quest = await this.get(questId);
1886
+ const newOrder = step.order ?? (quest.steps.length > 0 ? Math.max(...quest.steps.map((s) => s.order)) + 1 : 0);
1887
+ const stepsPayload = [
1888
+ ...quest.steps.map((s) => ({
1889
+ name: s.name,
1890
+ description: s.description,
1891
+ order: s.order,
1892
+ step_type: s.step_type,
1893
+ event_type: s.event_type,
1894
+ event_types: s.event_types,
1895
+ criteria: s.criteria,
1896
+ required_data_fields: s.required_data_fields,
1897
+ step_points: s.step_points,
1898
+ cta_text: s.cta_text,
1899
+ cta_url: s.cta_url,
1900
+ icon_url: s.icon_url,
1901
+ is_optional: s.is_optional
1902
+ })),
1903
+ { ...step, order: newOrder }
1904
+ ];
1905
+ const updated = await this.update(questId, { steps: stepsPayload });
1906
+ const added = updated.steps.find((s) => s.order === newOrder) ?? updated.steps[updated.steps.length - 1];
1907
+ return added;
1908
+ }
1909
+ /**
1910
+ * Update a step by step ID.
1911
+ *
1912
+ * The server has no dedicated step-update endpoint — steps are managed via the
1913
+ * full-replace PUT /quests/{id}. This method fetches the quest, replaces the
1914
+ * matching step, and issues a full update.
1865
1915
  */
1866
1916
  async updateStep(questId, stepId, data) {
1867
- return this.http.put(`/quests/${questId}/steps/${stepId}`, data);
1917
+ const quest = await this.get(questId);
1918
+ const stepsPayload = quest.steps.map((s) => {
1919
+ if (s.id !== stepId) {
1920
+ return {
1921
+ name: s.name,
1922
+ description: s.description,
1923
+ order: s.order,
1924
+ step_type: s.step_type,
1925
+ event_type: s.event_type,
1926
+ event_types: s.event_types,
1927
+ criteria: s.criteria,
1928
+ required_data_fields: s.required_data_fields,
1929
+ step_points: s.step_points,
1930
+ cta_text: s.cta_text,
1931
+ cta_url: s.cta_url,
1932
+ icon_url: s.icon_url,
1933
+ is_optional: s.is_optional
1934
+ };
1935
+ }
1936
+ return {
1937
+ name: s.name,
1938
+ description: s.description,
1939
+ order: s.order,
1940
+ step_type: s.step_type,
1941
+ event_type: s.event_type,
1942
+ event_types: s.event_types,
1943
+ criteria: s.criteria,
1944
+ required_data_fields: s.required_data_fields,
1945
+ step_points: s.step_points,
1946
+ cta_text: s.cta_text,
1947
+ cta_url: s.cta_url,
1948
+ icon_url: s.icon_url,
1949
+ is_optional: s.is_optional,
1950
+ ...data
1951
+ };
1952
+ });
1953
+ const updated = await this.update(questId, { steps: stepsPayload });
1954
+ const found = updated.steps.find((s) => s.id === stepId);
1955
+ if (!found) throw new Error(`Step ${stepId} not found after update`);
1956
+ return found;
1868
1957
  }
1869
1958
  /**
1870
- * Delete a step
1959
+ * Delete a step by step ID.
1960
+ *
1961
+ * The server has no dedicated step-delete endpoint — steps are managed via the
1962
+ * full-replace PUT /quests/{id}. This method fetches the quest, filters out the
1963
+ * target step, and issues a full update.
1871
1964
  */
1872
1965
  async deleteStep(questId, stepId) {
1873
- await this.http.delete(`/quests/${questId}/steps/${stepId}`);
1874
- }
1875
- /**
1876
- * Reorder steps
1966
+ const quest = await this.get(questId);
1967
+ const stepsPayload = quest.steps.filter((s) => s.id !== stepId).map((s) => ({
1968
+ name: s.name,
1969
+ description: s.description,
1970
+ order: s.order,
1971
+ step_type: s.step_type,
1972
+ event_type: s.event_type,
1973
+ event_types: s.event_types,
1974
+ criteria: s.criteria,
1975
+ required_data_fields: s.required_data_fields,
1976
+ step_points: s.step_points,
1977
+ cta_text: s.cta_text,
1978
+ cta_url: s.cta_url,
1979
+ icon_url: s.icon_url,
1980
+ is_optional: s.is_optional
1981
+ }));
1982
+ await this.update(questId, { steps: stepsPayload });
1983
+ }
1984
+ /**
1985
+ * Reorder steps by providing step IDs in the desired order.
1986
+ *
1987
+ * The server has no dedicated reorder endpoint — steps are managed via the
1988
+ * full-replace PUT /quests/{id}. This method fetches the quest, reassigns
1989
+ * the `order` field according to the given stepIds sequence, and issues a
1990
+ * full update.
1877
1991
  */
1878
1992
  async reorderSteps(questId, stepIds) {
1879
- return this.http.post(`/quests/${questId}/steps/reorder`, { step_ids: stepIds });
1993
+ const quest = await this.get(questId);
1994
+ const stepMap = new Map(quest.steps.map((s) => [s.id, s]));
1995
+ const stepsPayload = stepIds.map((id, index) => {
1996
+ const s = stepMap.get(id);
1997
+ if (!s) throw new Error(`Step ${id} not found in quest ${questId}`);
1998
+ return {
1999
+ name: s.name,
2000
+ description: s.description,
2001
+ order: index,
2002
+ step_type: s.step_type,
2003
+ event_type: s.event_type,
2004
+ event_types: s.event_types,
2005
+ criteria: s.criteria,
2006
+ required_data_fields: s.required_data_fields,
2007
+ step_points: s.step_points,
2008
+ cta_text: s.cta_text,
2009
+ cta_url: s.cta_url,
2010
+ icon_url: s.icon_url,
2011
+ is_optional: s.is_optional
2012
+ };
2013
+ });
2014
+ return this.update(questId, { steps: stepsPayload });
1880
2015
  }
1881
2016
  };
1882
2017
 
@@ -3563,5 +3698,6 @@ var EndUserIngestionClient = class {
3563
3698
  VaultResource,
3564
3699
  VerifyResource,
3565
3700
  WalletClient,
3566
- WebhooksResource
3701
+ WebhooksResource,
3702
+ tenantApiUrl
3567
3703
  });
package/dist/index.mjs CHANGED
@@ -63,12 +63,19 @@ var TimeoutError = class extends ProofChainError {
63
63
  var DEFAULT_BASE_URL = "https://api.proofchain.co.za";
64
64
  var DEFAULT_TIMEOUT = 3e4;
65
65
  var USER_AGENT = "proofchain-js/0.1.0";
66
+ function tenantApiUrl(slug) {
67
+ return `https://${slug}.proofchain.co.za/api`;
68
+ }
66
69
  var HttpClient = class {
67
70
  constructor(options) {
71
+ // In-flight GET de-duplication: concurrent identical GETs share one network
72
+ // request (and its response), cutting origin load for chatty UIs/pollers.
73
+ this.inflight = /* @__PURE__ */ new Map();
68
74
  this.apiKey = options.apiKey || "";
69
75
  this.userToken = options.userToken || "";
70
76
  this.tenantId = options.tenantId || "";
71
- this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
77
+ const resolvedBase = options.baseUrl || (options.tenant ? tenantApiUrl(options.tenant) : DEFAULT_BASE_URL);
78
+ this.baseUrl = resolvedBase.replace(/\/$/, "");
72
79
  this.timeout = options.timeout || DEFAULT_TIMEOUT;
73
80
  this.maxRetries = options.maxRetries ?? 3;
74
81
  }
@@ -133,7 +140,8 @@ var HttpClient = class {
133
140
  clearTimeout(timeoutId);
134
141
  if (error instanceof ProofChainError) {
135
142
  if (error instanceof RateLimitError && retries < this.maxRetries) {
136
- const delay = Math.min((error.retryAfter || 1) * 1e3, 6e4);
143
+ const base = Math.min((error.retryAfter || 1) * 1e3, 6e4);
144
+ const delay = base + Math.floor(Math.random() * 1e3);
137
145
  await new Promise((resolve) => setTimeout(resolve, delay));
138
146
  return this.fetchWithRetry(url, options, retries + 1);
139
147
  }
@@ -144,7 +152,9 @@ var HttpClient = class {
144
152
  throw new TimeoutError();
145
153
  }
146
154
  if (retries < this.maxRetries) {
147
- await new Promise((resolve) => setTimeout(resolve, 1e3 * (retries + 1)));
155
+ const base = 1e3 * (retries + 1);
156
+ const delay = base + Math.floor(Math.random() * 500);
157
+ await new Promise((resolve) => setTimeout(resolve, delay));
148
158
  return this.fetchWithRetry(url, options, retries + 1);
149
159
  }
150
160
  throw new NetworkError(error.message, error);
@@ -173,6 +183,17 @@ var HttpClient = class {
173
183
  if (options.body) {
174
184
  fetchOptions.body = JSON.stringify(options.body);
175
185
  }
186
+ if (method === "GET") {
187
+ const existing = this.inflight.get(url);
188
+ if (existing) {
189
+ return existing;
190
+ }
191
+ const pending = this.fetchWithRetry(url, fetchOptions).finally(() => {
192
+ this.inflight.delete(url);
193
+ });
194
+ this.inflight.set(url, pending);
195
+ return pending;
196
+ }
176
197
  return this.fetchWithRetry(url, fetchOptions);
177
198
  }
178
199
  async requestMultipart(path, formData) {
@@ -1666,11 +1687,10 @@ var QuestsClient = class {
1666
1687
  return this.http.get(`/quests/available?${params.toString()}`);
1667
1688
  }
1668
1689
  /**
1669
- * Get a quest by slug
1690
+ * REMOVED: getBySlug() the server never implemented GET /quests/slug/{slug}.
1691
+ * There is no slug-based lookup route in the API. Use list() with a status/category
1692
+ * filter and find the quest client-side, or use get() with the quest ID.
1670
1693
  */
1671
- async getBySlug(slug) {
1672
- return this.http.get(`/quests/slug/${encodeURIComponent(slug)}`);
1673
- }
1674
1694
  /**
1675
1695
  * Create a quest
1676
1696
  */
@@ -1711,11 +1731,9 @@ var QuestsClient = class {
1711
1731
  return this.http.post(`/quests/${questId}/pause`, {});
1712
1732
  }
1713
1733
  /**
1714
- * Archive a quest
1734
+ * REMOVED: archive() — the server never implemented POST /quests/{id}/archive.
1735
+ * To archive a quest, use update(questId, { status: 'archived' }) via PUT /quests/{id}.
1715
1736
  */
1716
- async archive(questId) {
1717
- return this.http.post(`/quests/${questId}/archive`, {});
1718
- }
1719
1737
  // ---------------------------------------------------------------------------
1720
1738
  // User Progress
1721
1739
  // ---------------------------------------------------------------------------
@@ -1795,28 +1813,144 @@ var QuestsClient = class {
1795
1813
  // Quest Steps
1796
1814
  // ---------------------------------------------------------------------------
1797
1815
  /**
1798
- * Add a step to a quest
1816
+ * Add a step to a quest.
1817
+ *
1818
+ * The server has no dedicated step-create endpoint — steps are managed via the
1819
+ * full-replace PUT /quests/{id}. This method fetches the quest, appends the new
1820
+ * step, and issues a full update. The returned QuestStep is synthesised from the
1821
+ * updated quest's steps array (matched by order or last position).
1799
1822
  */
1800
1823
  async addStep(questId, step) {
1801
- return this.http.post(`/quests/${questId}/steps`, step);
1802
- }
1803
- /**
1804
- * Update a step
1824
+ const quest = await this.get(questId);
1825
+ const newOrder = step.order ?? (quest.steps.length > 0 ? Math.max(...quest.steps.map((s) => s.order)) + 1 : 0);
1826
+ const stepsPayload = [
1827
+ ...quest.steps.map((s) => ({
1828
+ name: s.name,
1829
+ description: s.description,
1830
+ order: s.order,
1831
+ step_type: s.step_type,
1832
+ event_type: s.event_type,
1833
+ event_types: s.event_types,
1834
+ criteria: s.criteria,
1835
+ required_data_fields: s.required_data_fields,
1836
+ step_points: s.step_points,
1837
+ cta_text: s.cta_text,
1838
+ cta_url: s.cta_url,
1839
+ icon_url: s.icon_url,
1840
+ is_optional: s.is_optional
1841
+ })),
1842
+ { ...step, order: newOrder }
1843
+ ];
1844
+ const updated = await this.update(questId, { steps: stepsPayload });
1845
+ const added = updated.steps.find((s) => s.order === newOrder) ?? updated.steps[updated.steps.length - 1];
1846
+ return added;
1847
+ }
1848
+ /**
1849
+ * Update a step by step ID.
1850
+ *
1851
+ * The server has no dedicated step-update endpoint — steps are managed via the
1852
+ * full-replace PUT /quests/{id}. This method fetches the quest, replaces the
1853
+ * matching step, and issues a full update.
1805
1854
  */
1806
1855
  async updateStep(questId, stepId, data) {
1807
- return this.http.put(`/quests/${questId}/steps/${stepId}`, data);
1856
+ const quest = await this.get(questId);
1857
+ const stepsPayload = quest.steps.map((s) => {
1858
+ if (s.id !== stepId) {
1859
+ return {
1860
+ name: s.name,
1861
+ description: s.description,
1862
+ order: s.order,
1863
+ step_type: s.step_type,
1864
+ event_type: s.event_type,
1865
+ event_types: s.event_types,
1866
+ criteria: s.criteria,
1867
+ required_data_fields: s.required_data_fields,
1868
+ step_points: s.step_points,
1869
+ cta_text: s.cta_text,
1870
+ cta_url: s.cta_url,
1871
+ icon_url: s.icon_url,
1872
+ is_optional: s.is_optional
1873
+ };
1874
+ }
1875
+ return {
1876
+ name: s.name,
1877
+ description: s.description,
1878
+ order: s.order,
1879
+ step_type: s.step_type,
1880
+ event_type: s.event_type,
1881
+ event_types: s.event_types,
1882
+ criteria: s.criteria,
1883
+ required_data_fields: s.required_data_fields,
1884
+ step_points: s.step_points,
1885
+ cta_text: s.cta_text,
1886
+ cta_url: s.cta_url,
1887
+ icon_url: s.icon_url,
1888
+ is_optional: s.is_optional,
1889
+ ...data
1890
+ };
1891
+ });
1892
+ const updated = await this.update(questId, { steps: stepsPayload });
1893
+ const found = updated.steps.find((s) => s.id === stepId);
1894
+ if (!found) throw new Error(`Step ${stepId} not found after update`);
1895
+ return found;
1808
1896
  }
1809
1897
  /**
1810
- * Delete a step
1898
+ * Delete a step by step ID.
1899
+ *
1900
+ * The server has no dedicated step-delete endpoint — steps are managed via the
1901
+ * full-replace PUT /quests/{id}. This method fetches the quest, filters out the
1902
+ * target step, and issues a full update.
1811
1903
  */
1812
1904
  async deleteStep(questId, stepId) {
1813
- await this.http.delete(`/quests/${questId}/steps/${stepId}`);
1814
- }
1815
- /**
1816
- * Reorder steps
1905
+ const quest = await this.get(questId);
1906
+ const stepsPayload = quest.steps.filter((s) => s.id !== stepId).map((s) => ({
1907
+ name: s.name,
1908
+ description: s.description,
1909
+ order: s.order,
1910
+ step_type: s.step_type,
1911
+ event_type: s.event_type,
1912
+ event_types: s.event_types,
1913
+ criteria: s.criteria,
1914
+ required_data_fields: s.required_data_fields,
1915
+ step_points: s.step_points,
1916
+ cta_text: s.cta_text,
1917
+ cta_url: s.cta_url,
1918
+ icon_url: s.icon_url,
1919
+ is_optional: s.is_optional
1920
+ }));
1921
+ await this.update(questId, { steps: stepsPayload });
1922
+ }
1923
+ /**
1924
+ * Reorder steps by providing step IDs in the desired order.
1925
+ *
1926
+ * The server has no dedicated reorder endpoint — steps are managed via the
1927
+ * full-replace PUT /quests/{id}. This method fetches the quest, reassigns
1928
+ * the `order` field according to the given stepIds sequence, and issues a
1929
+ * full update.
1817
1930
  */
1818
1931
  async reorderSteps(questId, stepIds) {
1819
- return this.http.post(`/quests/${questId}/steps/reorder`, { step_ids: stepIds });
1932
+ const quest = await this.get(questId);
1933
+ const stepMap = new Map(quest.steps.map((s) => [s.id, s]));
1934
+ const stepsPayload = stepIds.map((id, index) => {
1935
+ const s = stepMap.get(id);
1936
+ if (!s) throw new Error(`Step ${id} not found in quest ${questId}`);
1937
+ return {
1938
+ name: s.name,
1939
+ description: s.description,
1940
+ order: index,
1941
+ step_type: s.step_type,
1942
+ event_type: s.event_type,
1943
+ event_types: s.event_types,
1944
+ criteria: s.criteria,
1945
+ required_data_fields: s.required_data_fields,
1946
+ step_points: s.step_points,
1947
+ cta_text: s.cta_text,
1948
+ cta_url: s.cta_url,
1949
+ icon_url: s.icon_url,
1950
+ is_optional: s.is_optional
1951
+ };
1952
+ });
1953
+ return this.update(questId, { steps: stepsPayload });
1820
1954
  }
1821
1955
  };
1822
1956
 
@@ -3502,5 +3636,6 @@ export {
3502
3636
  VaultResource,
3503
3637
  VerifyResource,
3504
3638
  WalletClient,
3505
- WebhooksResource
3639
+ WebhooksResource,
3640
+ tenantApiUrl
3506
3641
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proofchain/sdk",
3
- "version": "2.23.0",
3
+ "version": "3.1.0",
4
4
  "description": "Official JavaScript/TypeScript SDK for ProofChain - blockchain-anchored document attestation",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",