@propertysimple/client 0.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/API.md +79 -0
- package/README.md +348 -0
- package/client/src/directory-sync.d.ts +123 -0
- package/client/src/directory-sync.js +404 -0
- package/client/src/index.d.ts +5123 -0
- package/client/src/index.js +285 -0
- package/client/src/managed-sync.d.ts +129 -0
- package/client/src/managed-sync.js +410 -0
- package/contracts/src/catalog-derived.d.ts +7 -0
- package/contracts/src/catalog-derived.js +78 -0
- package/contracts/src/catalog-fields.d.ts +231 -0
- package/contracts/src/catalog-fields.js +194 -0
- package/contracts/src/catalog-inventory.d.ts +14 -0
- package/contracts/src/catalog-inventory.js +10 -0
- package/contracts/src/directory-execution.d.ts +320 -0
- package/contracts/src/directory-execution.js +122 -0
- package/contracts/src/directory-identity.d.ts +1255 -0
- package/contracts/src/directory-identity.js +129 -0
- package/contracts/src/directory-sales.d.ts +333 -0
- package/contracts/src/directory-sales.js +102 -0
- package/contracts/src/directory.d.ts +1088 -0
- package/contracts/src/directory.js +179 -0
- package/contracts/src/index.d.ts +18 -0
- package/contracts/src/index.js +18 -0
- package/contracts/src/intelligence.d.ts +366 -0
- package/contracts/src/intelligence.js +157 -0
- package/contracts/src/location-serving.d.ts +13 -0
- package/contracts/src/location-serving.js +89 -0
- package/contracts/src/location.d.ts +73 -0
- package/contracts/src/location.js +75 -0
- package/contracts/src/openapi.d.ts +22 -0
- package/contracts/src/openapi.js +109 -0
- package/contracts/src/operations.d.ts +8316 -0
- package/contracts/src/operations.js +56 -0
- package/contracts/src/opportunity-events.d.ts +38 -0
- package/contracts/src/opportunity-events.js +69 -0
- package/contracts/src/platform-api.d.ts +12 -0
- package/contracts/src/platform-api.js +1 -0
- package/contracts/src/professional-intelligence.d.ts +256 -0
- package/contracts/src/professional-intelligence.js +90 -0
- package/contracts/src/property-features.d.ts +94 -0
- package/contracts/src/property-features.js +398 -0
- package/contracts/src/publication.d.ts +44 -0
- package/contracts/src/publication.js +42 -0
- package/contracts/src/routes.d.ts +437 -0
- package/contracts/src/routes.js +496 -0
- package/contracts/src/schemas.d.ts +3198 -0
- package/contracts/src/schemas.js +674 -0
- package/contracts/src/search.d.ts +2630 -0
- package/contracts/src/search.js +223 -0
- package/contracts/src/smart-feed.d.ts +3731 -0
- package/contracts/src/smart-feed.js +109 -0
- package/openapi.json +43542 -0
- package/package.json +60 -0
package/API.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# PropertySimple client API reference
|
|
2
|
+
|
|
3
|
+
Generated from the shared route registry. Run `npm run sdk:docs` in the platform repository to update it.
|
|
4
|
+
|
|
5
|
+
Every operation below is available through `ps.call(operation, input, options)`, with inferred input and response types. The [quickstart](README.md) documents convenient namespace helpers such as `ps.listings.get(id)` and `ps.search.listings(query)`. Request fields use the API's snake_case names. Supply `{ idempotencyKey: 'stable-request-id' }` in options for mutating POST requests; search POSTs are read-only.
|
|
6
|
+
|
|
7
|
+
This reference describes the SDK contract, not a claim that every feature is enabled or accepted in production. Credential scopes, dataset grants, source freshness and index readiness still govern access. Legacy `/api/*`, admin pages, public health/OpenAPI routes and internal jobs are outside this versioned SDK registry. The package also includes `openapi.json` with full input/output schemas.
|
|
8
|
+
|
|
9
|
+
| Operation for `ps.call` | HTTP endpoint | Required scope | Purpose |
|
|
10
|
+
| ---------------------------------- | ---------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------- |
|
|
11
|
+
| `searchFeatures` | `GET /v1/search/features` | `catalog:read` | Read typed feature definitions and supported search operators. |
|
|
12
|
+
| `searchListings` | `POST /v1/search/listings` | `catalog:read` | Search current inventory with typed hard constraints and evidence. |
|
|
13
|
+
| `searchFacets` | `POST /v1/search/facets` | `catalog:read` | Read authorized refinement counts for the same normalized search. |
|
|
14
|
+
| `searchMap` | `POST /v1/search/map` | `catalog:read` | Read bounded map points and clusters with matching search semantics. |
|
|
15
|
+
| `searchSuggest` | `GET /v1/search/suggest` | `catalog:read` | Suggest registered features and permitted listing locations. |
|
|
16
|
+
| `searchInterpret` | `POST /v1/search/interpret` | `catalog:read` | Interpret a bounded search vocabulary into editable filters and unresolved text. |
|
|
17
|
+
| `directoryGetCanonicalIdentity` | `GET /v1/directory/canonical-identities/{id}` | `directory:read` | Resolve reviewed identity links using only permitted source members. |
|
|
18
|
+
| `directoryMergeIdentities` | `POST /v1/directory/identity-merges` | `directory:identity:write` | Record an audited identity merge with expected source bindings. |
|
|
19
|
+
| `directorySplitIdentity` | `POST /v1/directory/identity-splits` | `directory:identity:write` | Split a reviewed identity without moving account or action history. |
|
|
20
|
+
| `directoryListInventory` | `GET /v1/directory/entities/{id}/inventory` | `directory:read` | Read represented or current-roster inventory with optional verified corporate rollup. |
|
|
21
|
+
| `discoverDirectorySalesCandidates` | `GET /v1/directory/sales-candidates` | `directory:sales` | Discover a bounded authorized page of subjects for sales-plan evaluation. |
|
|
22
|
+
| `claimDirectoryActions` | `POST /v1/directory/action-claims` | `directory:execute` | Reserve directory recommendations under a separately reviewed execution policy. |
|
|
23
|
+
| `startDirectoryAttempt` | `POST /v1/directory/actions/{id}/attempts` | `directory:execute` | Recheck authority and start one attempt, or return reconciliation-only replay. |
|
|
24
|
+
| `getDirectoryAction` | `GET /v1/directory/actions/{id}` | `directory:execute` | Read a product-scoped directory action and its execution state. |
|
|
25
|
+
| `recordDirectoryActionOutcomes` | `POST /v1/directory/action-outcomes` | `directory:execute` | Record authenticated provider receipts without retrying ambiguous effects. |
|
|
26
|
+
| `directoryRecordProfile` | `POST /v1/directory/profile-observations` | `directory:write` | Record a trusted producer’s scoped profile evidence. |
|
|
27
|
+
| `directoryRecordRelationship` | `POST /v1/directory/relationship-observations` | `directory:write` | Record a trusted producer’s evidenced relationship. |
|
|
28
|
+
| `directoryListProfessionals` | `GET /v1/directory/professionals` | `directory:read` | Read permitted source-backed professionals. |
|
|
29
|
+
| `directoryListOffices` | `GET /v1/directory/offices` | `directory:read` | Read permitted source offices. |
|
|
30
|
+
| `directoryListOrganizations` | `GET /v1/directory/organizations` | `directory:read` | Read evidenced business and brand observations. |
|
|
31
|
+
| `directoryListTeams` | `GET /v1/directory/teams` | `directory:read` | Read evidenced teams. |
|
|
32
|
+
| `directoryGetEntity` | `GET /v1/directory/entities/{id}` | `directory:read` | Read a scoped professional, office, organization or team. |
|
|
33
|
+
| `directoryResolveAgent` | `GET /v1/directory/legacy-agents/{id}` | `directory:read` | Resolve a listing agent ID to its permitted public directory identity. |
|
|
34
|
+
| `directoryResolveIdentity` | `GET /v1/directory/identities` | `directory:read` | Resolve a typed directory source identity. |
|
|
35
|
+
| `directoryListRelationships` | `GET /v1/directory/relationships` | `directory:read` | Read bounded evidenced relationships. |
|
|
36
|
+
| `directoryGetContacts` | `GET /v1/directory/entities/{id}/contacts` | `directory:contacts` | Read purpose-permitted directory contact endpoints. |
|
|
37
|
+
| `directoryGetMetrics` | `GET /v1/directory/entities/{id}/metrics` | `directory:read` | Read authorized observed inventory metrics. |
|
|
38
|
+
| `createDirectorySnapshot` | `POST /v1/directory/snapshots` | `directory:sync` | Capture a directory or combined feed boundary. |
|
|
39
|
+
| `getDirectorySnapshot` | `GET /v1/directory/snapshots/{id}` | `directory:sync` | Read snapshot scope, boundary and expiry. |
|
|
40
|
+
| `listDirectorySnapshotItems` | `GET /v1/directory/snapshots/{id}/items` | `directory:sync` | Page immutable typed snapshot resources. |
|
|
41
|
+
| `listDirectoryChanges` | `GET /v1/directory/changes` | `directory:sync` | Replay typed changes with complete commit-group boundaries. |
|
|
42
|
+
| `createDirectorySalesPlan` | `POST /v1/directory/sales-plans` | `directory:sales` | Prepare bounded professional or organization sales recommendations. |
|
|
43
|
+
| `getDirectorySalesPlan` | `GET /v1/directory/sales-plans/{id}` | `directory:sales` | Read a scoped recommendation-only directory sales plan. |
|
|
44
|
+
| `requestDirectoryAssessment` | `POST /v1/directory/assessments` | `intelligence:judge` | Queue a canonical-evidence directory assessment. |
|
|
45
|
+
| `latestDirectoryAssessment` | `GET /v1/directory/assessments/latest` | `intelligence:read` | Read prepared directory intelligence with current evidence checks. |
|
|
46
|
+
| `getDirectoryAssessment` | `GET /v1/directory/assessments/{id}` | `intelligence:read` | Read a purpose-scoped directory assessment. |
|
|
47
|
+
| `reportPublication` | `POST /v1/publications` | `publications:write` | Acknowledge this product’s current listing publication. |
|
|
48
|
+
| `resolveDestination` | `GET /v1/listings/{id}/destination` | `catalog:read` | Resolve a current product-reported destination. |
|
|
49
|
+
| `evaluateJudgment` | `POST /v1/judgments` | `intelligence:judge` | Evaluate a bounded versioned task against caller-supplied evidence. |
|
|
50
|
+
| `getJudgment` | `GET /v1/judgments/{id}` | `intelligence:read` | Read this credential subject’s durable judgment audit. |
|
|
51
|
+
| `intelligenceCapabilities` | `GET /v1/intelligence-capabilities` | `intelligence:read` | Read implemented judgment tasks and explicit unsupported capabilities. |
|
|
52
|
+
| `capabilities` | `GET /v1/capabilities` | Authenticated caller | Read this caller’s supported capabilities. |
|
|
53
|
+
| `getCatalogHealth` | `GET /v1/catalog-health` | `catalog:read` | Read authorized source freshness independently of catalog changes. |
|
|
54
|
+
| `listListings` | `GET /v1/listings` | `catalog:read` | Search the current authorized catalog. |
|
|
55
|
+
| `listListingMap` | `GET /v1/listings/map` | `catalog:read` | Read bounded map markers within a required geographic box. |
|
|
56
|
+
| `listSimilarListings` | `GET /v1/listings/{id}/similar` | `catalog:read` | Rank a bounded local shortlist using transparent fact similarity. |
|
|
57
|
+
| `getListing` | `GET /v1/listings/{id}` | `catalog:read` | Read a current listing. |
|
|
58
|
+
| `getListingContacts` | `GET /v1/listings/{id}/contacts` | `contacts:read` | Read protected source contact routing for a current active listing. |
|
|
59
|
+
| `resolveListingIdentity` | `GET /v1/listing-identities` | `catalog:read` | Resolve a namespaced source alias. |
|
|
60
|
+
| `resolveAgentIdentity` | `GET /v1/agent-identities` | `agents:read` | Resolve a namespaced agent source alias. |
|
|
61
|
+
| `getAgent` | `GET /v1/agents/{id}` | `agents:read` | Read permitted agent attribution. |
|
|
62
|
+
| `listAgentListings` | `GET /v1/agents/{id}/listings` | `catalog:read` | Read an agent’s authorized inventory. |
|
|
63
|
+
| `classificationSchema` | `GET /v1/classification-schema` | `catalog:read` | Read the current classification vocabulary. |
|
|
64
|
+
| `createSnapshot` | `POST /v1/catalog-snapshots` | `catalog:sync` | Create a consistent snapshot with its replay cursor. |
|
|
65
|
+
| `getSnapshot` | `GET /v1/catalog-snapshots/{id}` | `catalog:sync` | Read snapshot readiness and expiry. |
|
|
66
|
+
| `listSnapshotItems` | `GET /v1/catalog-snapshots/{id}/items` | `catalog:sync` | Page an immutable snapshot. |
|
|
67
|
+
| `listCatalogChanges` | `GET /v1/catalog-changes` | `catalog:sync` | Replay authorized projection changes and removals. |
|
|
68
|
+
| `listEvents` | `GET /v1/events` | `catalog:sync` | Replay committed domain occurrences. |
|
|
69
|
+
| `listActionProfiles` | `GET /v1/action-profiles` | `opportunities:read` | Read published action profiles. |
|
|
70
|
+
| `listOpportunities` | `GET /v1/opportunities` | `opportunities:read` | Read current candidates without reserving actions. |
|
|
71
|
+
| `createPlan` | `POST /v1/opportunity-plans` | `plans:create` | Create or reuse an actual persisted selection. |
|
|
72
|
+
| `getPlan` | `GET /v1/opportunity-plans/{id}` | `opportunities:read` | Read plan progress, coverage and execution counts. |
|
|
73
|
+
| `listPlanItems` | `GET /v1/opportunity-plans/{id}/items` | `opportunities:read` | Read stable selection pages. |
|
|
74
|
+
| `claimDispatchBatch` | `POST /v1/dispatch-batches` | `actions:dispatch` | Reserve executable actions after critical revalidation. |
|
|
75
|
+
| `startAttempt` | `POST /v1/actions/{id}/attempts` | `actions:dispatch` | Fence and record dispatch before an external effect. |
|
|
76
|
+
| `recordActionOutcomes` | `POST /v1/action-outcomes` | `actions:dispatch` | Record idempotent sender receipts with per-item results. |
|
|
77
|
+
| `recordProductContextEvents` | `POST /v1/product-context-events` | `product-events:write` | Ingest owned product context snapshots and deltas. |
|
|
78
|
+
| `createWebhookSubscription` | `POST /v1/webhook-subscriptions` | `webhooks:manage` | Register a subscription only where delivery support is configured. |
|
|
79
|
+
| `readiness` | `GET /v1/operations/readiness` | `operations:read` | Inspect configured operational dependencies. |
|
package/README.md
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# @propertysimple/client
|
|
2
|
+
|
|
3
|
+
The typed **server-side PropertySimple SDK** for listing details, property search,
|
|
4
|
+
agents, catalog synchronization, directory data and the platform's other public
|
|
5
|
+
API operations. Configure one client in your backend, then use `ps.listings`,
|
|
6
|
+
`ps.search`, `ps.agents` and `ps.catalog` throughout your application.
|
|
7
|
+
|
|
8
|
+
This package is distributed publicly for **authenticated PropertySimple API
|
|
9
|
+
integrations**. An API key is required to access data; no credentials or listing
|
|
10
|
+
data are included in the package. The existing `@propertysimple/listings` package
|
|
11
|
+
is separate and unchanged.
|
|
12
|
+
|
|
13
|
+
**Service readiness:** SDK availability and API feature readiness are separate.
|
|
14
|
+
Start direct catalog integrations with `ps.listings` and `ps.agents`. Managed
|
|
15
|
+
search uses `ps.search` and requires a ready, authorized index on the API server.
|
|
16
|
+
At the initial 0.1.0 release, full production search acceptance is still pending;
|
|
17
|
+
do not treat this package release as approval to switch search traffic. Installing
|
|
18
|
+
the SDK does not activate imports, indexing, synchronization schedules, Jev,
|
|
19
|
+
geocoding or outbound actions.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
Requires **Node.js 24** and ESM (`"type": "module"` in your application's
|
|
24
|
+
`package.json`). JavaScript and TypeScript are supported; declarations are
|
|
25
|
+
included. TypeScript applications should use their normal `@types/node` setup.
|
|
26
|
+
CommonJS and browser clients are not supported by this release.
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npm install @propertysimple/client
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The archive contains compiled JavaScript, TypeScript declarations, the shared
|
|
33
|
+
contracts, this guide, [the complete operation reference](API.md) and
|
|
34
|
+
`openapi.json`. Its only external runtime dependency is Zod. Consumers do not need
|
|
35
|
+
`@ps/client`, `@ps/contracts`, the platform source tree or a TypeScript runtime loader.
|
|
36
|
+
Commit your application's lockfile so deployments use the version you tested.
|
|
37
|
+
|
|
38
|
+
## Configure once
|
|
39
|
+
|
|
40
|
+
Create a backend-only module such as `src/lib/ps.ts`. The environment variable
|
|
41
|
+
names below are application conventions; the SDK reads only the supplied options.
|
|
42
|
+
Do not use browser-exposed environment variables for service credentials.
|
|
43
|
+
|
|
44
|
+
<!-- sdk-example: ps.ts -->
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import PropertySimple from '@propertysimple/client';
|
|
48
|
+
|
|
49
|
+
const apiKey = process.env.PROPERTYSIMPLE_API_KEY;
|
|
50
|
+
if (!apiKey) throw new Error('Set PROPERTYSIMPLE_API_KEY on the server.');
|
|
51
|
+
|
|
52
|
+
const ps = new PropertySimple({
|
|
53
|
+
apiKey,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
export default ps;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The default base URL is `https://api.propertysimple.ai`. Supply a scoped service
|
|
60
|
+
key issued for your application; you do not need Typesense, ListHub or database
|
|
61
|
+
credentials. For staging or local development, explicitly pass `baseUrl` when
|
|
62
|
+
constructing the client, for example `baseUrl: 'http://127.0.0.1:3100'`. Each instance owns its credentials; multiple
|
|
63
|
+
instances can coexist without changing another tenant's configuration.
|
|
64
|
+
|
|
65
|
+
Request a service key and dataset ID from your PropertySimple platform
|
|
66
|
+
administrator. Keys grant specific datasets and operations: catalog reads use
|
|
67
|
+
`catalog:read`, agent profiles use `agents:read`, and protected contacts need their
|
|
68
|
+
own scope. Choose the minimum permissions your integration needs. A dataset ID is
|
|
69
|
+
an authorization boundary, not an MLS number or a value to guess.
|
|
70
|
+
|
|
71
|
+
Keep the key in your backend's secret manager or an ignored local `.env` file.
|
|
72
|
+
Never put it in a browser bundle, mobile app, public repository, URL, or a
|
|
73
|
+
`NEXT_PUBLIC_`/`VITE_` environment variable. The SDK sends it as an HTTPS Bearer
|
|
74
|
+
credential. Browser and mobile clients should call your own authenticated backend;
|
|
75
|
+
that backend owns the `ps` instance and validates end-user access.
|
|
76
|
+
|
|
77
|
+
In application code, import this configured instance. Adjust the relative path
|
|
78
|
+
or use your application's `@/lib/ps` alias:
|
|
79
|
+
|
|
80
|
+
<!-- sdk-example: listings.ts -->
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import ps from './ps.js';
|
|
84
|
+
|
|
85
|
+
const datasetId = 'your-authorized-dataset';
|
|
86
|
+
|
|
87
|
+
const page = await ps.listings.list({
|
|
88
|
+
dataset_id: datasetId,
|
|
89
|
+
mls_number: 'EXAMPLE-123',
|
|
90
|
+
limit: 25,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const listing = await ps.listings.get('your-listing-id', {
|
|
94
|
+
dataset_id: datasetId,
|
|
95
|
+
profile: 'portal_active',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const identity = await ps.listings.resolveIdentity({
|
|
99
|
+
source: 'listhub',
|
|
100
|
+
key: 'your-source-listing-key',
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const agent = await ps.agents.get('your-agent-id');
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`listings.get` includes the full gallery. `profile: 'portal_active'` requires
|
|
107
|
+
currently eligible active portal inventory; generic catalog reads can retain
|
|
108
|
+
meaningful pending/sold status. A missing or removed listing is not evidence that
|
|
109
|
+
it sold. A source key and a canonical listing ID are different identities: resolve
|
|
110
|
+
existing source keys instead of treating addresses as unique IDs.
|
|
111
|
+
|
|
112
|
+
For a first connectivity check, use `await ps.call('capabilities', {})`, then
|
|
113
|
+
`await ps.catalog.health(datasetId)` and a small `ps.listings.list` request.
|
|
114
|
+
Capabilities describe the caller's API support; health describes source freshness.
|
|
115
|
+
Neither response establishes that a Typesense generation is ready.
|
|
116
|
+
|
|
117
|
+
The examples use ESM imports. Run plain JavaScript in a `.mjs` file, or set
|
|
118
|
+
`"type": "module"`; for local environment files Node 24 supports
|
|
119
|
+
`node --env-file=.env your-script.mjs`. TypeScript examples should be compiled by
|
|
120
|
+
your application's normal build. In a server-rendered framework, keep this module
|
|
121
|
+
in server routes/services and use a Node.js runtime.
|
|
122
|
+
|
|
123
|
+
## Search
|
|
124
|
+
|
|
125
|
+
<!-- sdk-example: search.ts -->
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import ps from './ps.js';
|
|
129
|
+
|
|
130
|
+
const results = await ps.search.listings({
|
|
131
|
+
dataset_id: 'your-authorized-dataset',
|
|
132
|
+
q: '123 Main Street',
|
|
133
|
+
filters: {
|
|
134
|
+
transaction_type: 'for_sale',
|
|
135
|
+
currency: 'USD',
|
|
136
|
+
price: { max: '900000.00' },
|
|
137
|
+
bedrooms: { min: 3 },
|
|
138
|
+
},
|
|
139
|
+
sort: 'relevance',
|
|
140
|
+
limit: 24,
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Search responses include compact listing cards with a cover photo, `photo_count`,
|
|
145
|
+
match reasons and index/freshness metadata. Use `ps.listings.get(id, options)` for
|
|
146
|
+
complete details. The SDK calls the shared API; it never exposes Typesense keys.
|
|
147
|
+
Source facts can be searched without Jev; interpreted features require their own
|
|
148
|
+
current evidence. Missing evidence is not a negative match.
|
|
149
|
+
|
|
150
|
+
The map helper takes `{ query, viewport, zoom, limit }`. Use the same applied
|
|
151
|
+
query for listings, facets and maps, and honor generation/checkpoint, coverage,
|
|
152
|
+
truncation and expiry metadata. Search readiness or stale-index errors must not
|
|
153
|
+
silently become empty inventory or a broader query with filters removed.
|
|
154
|
+
|
|
155
|
+
## Methods and complete API coverage
|
|
156
|
+
|
|
157
|
+
| Namespace | Common methods |
|
|
158
|
+
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
|
|
159
|
+
| `ps.listings` | `list`, `get`, `resolveIdentity`, `contacts`, `map`, `similar` |
|
|
160
|
+
| `ps.search` | `listings`, `suggest`, `facets`, `map`, `features`, `interpret` |
|
|
161
|
+
| `ps.agents` | `get`, `resolveIdentity`, `listings` |
|
|
162
|
+
| `ps.catalog` | `health`, `createSnapshot`, `snapshot`, `snapshotItems`, `changes`, `events`, `opportunityObservations` |
|
|
163
|
+
| `ps.directory` | `professionals`, `offices`, `organizations`, `teams`, `get`, `inventory`, identity, contact and synchronization helpers |
|
|
164
|
+
| `ps.publications` | `report`, `destination` |
|
|
165
|
+
| `ps.judgments` | `capabilities`, `evaluate`, `get` |
|
|
166
|
+
| `ps.opportunityPlans` | `create`, `get`, `items`, `claimDispatchBatch`, `waitUntilReady` |
|
|
167
|
+
| `ps.actions` / `ps.directoryActions` | Explicit action claims, attempts and outcomes |
|
|
168
|
+
|
|
169
|
+
All 69 registered versioned public operations are available through
|
|
170
|
+
`ps.call(operation, input, options)`, including operations without a shortcut.
|
|
171
|
+
Input and output types are inferred from the operation name. Input fields use the
|
|
172
|
+
API's snake_case names; helpers use camelCase. For example:
|
|
173
|
+
|
|
174
|
+
<!-- sdk-example: operations.ts -->
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import ps from './ps.js';
|
|
178
|
+
|
|
179
|
+
const capabilities = await ps.call('capabilities', {});
|
|
180
|
+
const health = await ps.catalog.health('your-authorized-dataset');
|
|
181
|
+
const listing = await ps.call('getListing', {
|
|
182
|
+
id: 'your-listing-id',
|
|
183
|
+
dataset_id: 'your-authorized-dataset',
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
See [API.md](API.md) for every operation, HTTP route and required scope. Legacy
|
|
188
|
+
`/api/*` adapters, admin pages, `/health/live`, `/openapi.json` and internal worker
|
|
189
|
+
routes are not versioned SDK operations. Having a method does not grant its scope
|
|
190
|
+
or enable an optional feature in production.
|
|
191
|
+
The reference and OpenAPI document also ship at
|
|
192
|
+
`node_modules/@propertysimple/client/API.md` and
|
|
193
|
+
`node_modules/@propertysimple/client/openapi.json`, so integrations can inspect
|
|
194
|
+
them without repository access. The repository guides linked below require
|
|
195
|
+
access to the PropertySimple source repository.
|
|
196
|
+
|
|
197
|
+
## Pagination and synchronization
|
|
198
|
+
|
|
199
|
+
Continue a listing page with its `next_cursor` and the original query. Honor
|
|
200
|
+
`has_more`, including when an authorized feed page is empty. Do not increase an
|
|
201
|
+
offset to page a large dataset. `ps.iterateListings(query)` is convenient for live
|
|
202
|
+
browsing, but concurrent updates can move records between pages.
|
|
203
|
+
|
|
204
|
+
<!-- sdk-example: pagination.ts -->
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
import ps from './ps.js';
|
|
208
|
+
|
|
209
|
+
const query = { dataset_id: 'your-authorized-dataset', limit: 25 };
|
|
210
|
+
const firstPage = await ps.listings.list(query);
|
|
211
|
+
|
|
212
|
+
if (firstPage.has_more && firstPage.next_cursor) {
|
|
213
|
+
const nextPage = await ps.listings.list({
|
|
214
|
+
...query,
|
|
215
|
+
cursor: firstPage.next_cursor,
|
|
216
|
+
});
|
|
217
|
+
console.log(nextPage.data.map((listing) => listing.id));
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
For a consistent replica, create a catalog snapshot, read its immutable pages,
|
|
222
|
+
then replay `ps.catalog.changes` from the supplied cursor. Persist the checkpoint
|
|
223
|
+
atomically with applied updates/removals. An expired cursor requires a new
|
|
224
|
+
snapshot; resuming from an invented timestamp loses removals.
|
|
225
|
+
|
|
226
|
+
The optional `@propertysimple/client/managed-sync` export supplies
|
|
227
|
+
`CatalogSyncWorker` and `PostgresCatalogTarget`. The
|
|
228
|
+
`@propertysimple/client/directory-sync` export supplies `DirectorySyncWorker`
|
|
229
|
+
and `PostgresDirectoryTarget` for directory or combined feeds. They use an
|
|
230
|
+
explicitly supplied database adapter and a separate consumer-owned schema;
|
|
231
|
+
installation does not create tables, overwrite product data or install a schedule.
|
|
232
|
+
Keep the database wrappers, checkpoints and cache invalidation handling required
|
|
233
|
+
by the [managed synchronization guide](https://github.com/PropertySimple/ps-listings-platform/blob/main/docs/managed-sync.md).
|
|
234
|
+
|
|
235
|
+
## Errors, requests and retries
|
|
236
|
+
|
|
237
|
+
`ApiError` contains the HTTP `status`, stable `code` and validated `problem`
|
|
238
|
+
(including the request ID when provided). `ContractError` means an input or a
|
|
239
|
+
successful response failed the shared contract; never treat it as empty inventory.
|
|
240
|
+
|
|
241
|
+
<!-- sdk-example: errors.ts -->
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
import { ApiError, ContractError } from '@propertysimple/client';
|
|
245
|
+
import ps from './ps.js';
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
await ps.listings.get('your-listing-id');
|
|
249
|
+
} catch (error) {
|
|
250
|
+
if (error instanceof ApiError) {
|
|
251
|
+
console.error(error.status, error.code, error.problem?.request_id);
|
|
252
|
+
} else if (error instanceof ContractError) {
|
|
253
|
+
console.error('API contract mismatch', error.operation);
|
|
254
|
+
}
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Client options include the optional `baseUrl` override, `timeoutMs` (default 15,000 milliseconds per attempt),
|
|
260
|
+
`maxRetries` (default two; allowed zero through five) and an optional `fetch`
|
|
261
|
+
implementation. Requests accept an abort `signal`. Network failures and HTTP
|
|
262
|
+
429/503 can retry within those limits; authorization errors do not retry.
|
|
263
|
+
Use an abort signal for a total deadline across attempts. Redirects are rejected;
|
|
264
|
+
HTTPS is required except for loopback development.
|
|
265
|
+
|
|
266
|
+
For an integration timeout spanning retries, pass
|
|
267
|
+
`{ signal: AbortSignal.timeout(30_000) }` as the request options. Log the operation,
|
|
268
|
+
HTTP status, problem code and request ID; exclude API keys, contact details and
|
|
269
|
+
complete response bodies from ordinary logs. In particular:
|
|
270
|
+
|
|
271
|
+
| Result | Integration behavior |
|
|
272
|
+
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
273
|
+
| HTTP 401/403 | Check the service key, dataset grant and operation scope; do not retry with broader credentials automatically. |
|
|
274
|
+
| HTTP 404 | Handle the unavailable resource explicitly; do not infer that a listing sold. |
|
|
275
|
+
| HTTP 429/503 | Honor the SDK's bounded retry behavior and surface persistent unavailability. |
|
|
276
|
+
| `SEARCH_INDEX_NOT_READY` / `SEARCH_SNAPSHOT_TOO_OLD` | Keep search unavailable until a valid generation is ready; do not show an empty inventory as a successful response. |
|
|
277
|
+
| `ContractError` | Check API/SDK compatibility and report the contract mismatch. |
|
|
278
|
+
|
|
279
|
+
Mutating POST operations require a caller-supplied
|
|
280
|
+
`{ idempotencyKey: 'stable-request-id' }` request option. Retries keep the same key
|
|
281
|
+
and body. Search POSTs are read-only and need no key. An action attempt with
|
|
282
|
+
`replayed: true` is an existing attempt to reconcile, not permission to send again.
|
|
283
|
+
The SDK does not call email, advertising or payment providers. A plan does not
|
|
284
|
+
constitute permission to contact someone or make a purchase.
|
|
285
|
+
|
|
286
|
+
## Types, validation and compatibility
|
|
287
|
+
|
|
288
|
+
The default export, named `PropertySimple` and named `ListingsClient` refer to the
|
|
289
|
+
same class. Existing instance-based code can retain `ListingsClient`; there is no
|
|
290
|
+
process-global `Listings.init()` configuration.
|
|
291
|
+
|
|
292
|
+
The root exports the `DEFAULT_BASE_URL` constant and `Listing`, `InputRequest`, `Output`, `Operation`, `Principal`,
|
|
293
|
+
`ClientOptions` and `RequestOptions` types. Full schemas and types are available
|
|
294
|
+
from `@propertysimple/client/contracts`; location expiry/privacy projection helpers
|
|
295
|
+
are available from `@propertysimple/client/location-serving`.
|
|
296
|
+
|
|
297
|
+
Keep API and SDK contract versions compatible. Responses are validated strictly;
|
|
298
|
+
coordinate additive schema changes before deployment when older consumers would
|
|
299
|
+
reject new fields. Source freshness, dataset grants, contacts permissions and
|
|
300
|
+
current address privacy still apply. Private-address listings can remain in
|
|
301
|
+
nonspatial results, but their street/unit/subdivision and coordinates must be
|
|
302
|
+
cleared from consumer copies and caches. Do not restore an old geocode or contact.
|
|
303
|
+
|
|
304
|
+
## Migrating existing applications
|
|
305
|
+
|
|
306
|
+
This package is separate from `@propertysimple/listings`, not a drop-in package
|
|
307
|
+
rename. Start one configured `ps` instance, map legacy source IDs to canonical IDs,
|
|
308
|
+
use cursors, and verify actual fields, status/privacy behavior and application
|
|
309
|
+
relationships before switching traffic. Keep the old integration available during
|
|
310
|
+
a reviewed migration. The server's authenticated `/api/*` compatibility adapters
|
|
311
|
+
remain available for existing retrieval callers; new code should use `/v1` methods.
|
|
312
|
+
Those adapters are not the public-portal serving contract.
|
|
313
|
+
|
|
314
|
+
Read the [retrieval replacement guide](https://github.com/PropertySimple/ps-listings-platform/blob/main/docs/listings-api-replacement.md)
|
|
315
|
+
and [product integration guide](https://github.com/PropertySimple/ps-listings-platform/blob/main/docs/product-integration.md)
|
|
316
|
+
for the five existing retrieval mappings, source freshness and cutover checks.
|
|
317
|
+
Installing this package does not modify or switch an existing consumer application.
|
|
318
|
+
|
|
319
|
+
## Maintainer checks
|
|
320
|
+
|
|
321
|
+
The source workspace retains the internal `@ps/client` name so existing platform
|
|
322
|
+
imports continue working. `package.release.json` defines the distribution named
|
|
323
|
+
`@propertysimple/client`; `sdk:build` compiles the SDK and its reachable shared
|
|
324
|
+
contracts together, then replaces private workspace imports with local imports.
|
|
325
|
+
The version comes from this workspace's `package.json`.
|
|
326
|
+
|
|
327
|
+
```sh
|
|
328
|
+
npm run sdk:docs -- --check
|
|
329
|
+
npm run sdk:test
|
|
330
|
+
npm run sdk:pack
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
For local integration testing, install the archive from
|
|
334
|
+
`packages/client/dist/propertysimple-client-0.1.0.tgz` into a separate application.
|
|
335
|
+
Publish the exact inspected archive to avoid including workspace configuration:
|
|
336
|
+
|
|
337
|
+
```sh
|
|
338
|
+
npm publish ./packages/client/dist/propertysimple-client-0.1.0.tgz --access public --registry https://registry.npmjs.org/ --ignore-scripts
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
`npm run sdk:test` builds and packs the release, installs it in a temporary project
|
|
342
|
+
with local dependency archives and network access disabled for npm, checks every
|
|
343
|
+
public export and the README examples' TypeScript types, and exercises synthetic
|
|
344
|
+
requests. It performs no live API/provider work. Build output and archives stay
|
|
345
|
+
ignored under `packages/client/dist`. Inspect the archive's file list and scan it
|
|
346
|
+
for credentials before publication. Publishing requires npm organization access
|
|
347
|
+
and any two-factor authentication required by that account. Registry versions are
|
|
348
|
+
immutable; bump the client workspace version and rebuild for later releases.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { SmartFeedSelectorSchema, type SmartFeedResource, type Output } from '../../contracts/src/index.js';
|
|
2
|
+
import { type ListingsClient } from './index.js';
|
|
3
|
+
import { type TargetDatabase, type TargetSql, type ProjectionReadOptions } from './managed-sync.js';
|
|
4
|
+
type ResourceType = SmartFeedResource['resource_type'];
|
|
5
|
+
type Selector = ReturnType<typeof SmartFeedSelectorSchema.parse>;
|
|
6
|
+
type Group = {
|
|
7
|
+
id: string;
|
|
8
|
+
end: string;
|
|
9
|
+
size: number;
|
|
10
|
+
next: number;
|
|
11
|
+
start: string;
|
|
12
|
+
};
|
|
13
|
+
interface Checkpoint {
|
|
14
|
+
phase: 'create' | 'snapshot' | 'replay' | 'blocked';
|
|
15
|
+
generation: string;
|
|
16
|
+
snapshotId?: string;
|
|
17
|
+
snapshotCursor?: string;
|
|
18
|
+
replayCursor?: string;
|
|
19
|
+
lastSequence: string;
|
|
20
|
+
visibleSequence: string;
|
|
21
|
+
snapshotSequence?: string;
|
|
22
|
+
group?: Group;
|
|
23
|
+
sourceFreshness?: Output<'listDirectoryChanges'>['meta']['source_freshness'];
|
|
24
|
+
}
|
|
25
|
+
export interface DirectorySyncState {
|
|
26
|
+
version: string;
|
|
27
|
+
checkpoint: Checkpoint;
|
|
28
|
+
active_generation: string | null;
|
|
29
|
+
visible_seq: string;
|
|
30
|
+
synchronized_at: string | null;
|
|
31
|
+
}
|
|
32
|
+
export interface DirectorySyncConfiguration {
|
|
33
|
+
streamId: string;
|
|
34
|
+
datasetId: string;
|
|
35
|
+
/** Nonsecret stable identity for environment + credential scope. Change streams on scope changes. */
|
|
36
|
+
sourceNamespace: string;
|
|
37
|
+
selector?: {
|
|
38
|
+
mode?: 'directory' | 'combined';
|
|
39
|
+
party_id?: string;
|
|
40
|
+
inventory_basis?: 'listing_representation';
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export interface DirectoryInvalidation {
|
|
44
|
+
id: string;
|
|
45
|
+
kind: 'reset' | 'group';
|
|
46
|
+
generation: string;
|
|
47
|
+
from_sequence: string;
|
|
48
|
+
through_sequence: string;
|
|
49
|
+
lease_token: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A consumer-owned typed projection. Versions received for an incomplete group are durable but
|
|
53
|
+
* invisible. Completing a group changes one visibility pointer rather than applying an unbounded
|
|
54
|
+
* mutation transaction. Product-owned users, leads, slugs and billing never enter this schema.
|
|
55
|
+
*/
|
|
56
|
+
export declare class PostgresDirectoryTarget {
|
|
57
|
+
private readonly db;
|
|
58
|
+
readonly configuration: DirectorySyncConfiguration;
|
|
59
|
+
readonly selector: Selector;
|
|
60
|
+
private readonly fingerprint;
|
|
61
|
+
constructor(db: TargetDatabase, configuration: DirectorySyncConfiguration);
|
|
62
|
+
/**
|
|
63
|
+
* Explicit approved install/upgrade. v2 keeps resource_type as text, adds version metadata,
|
|
64
|
+
* and invalidates every v1 checkpoint so identity bindings enter through a complete snapshot.
|
|
65
|
+
* Stable local UUID mappings and old versions remain for product-owned recovery/pruning.
|
|
66
|
+
* Quiesce old SDK workers before invoking this; initialization is not a request-time action.
|
|
67
|
+
*/
|
|
68
|
+
initialize(): Promise<void>;
|
|
69
|
+
state(): Promise<DirectorySyncState>;
|
|
70
|
+
commit(expected: DirectorySyncState, next: Checkpoint, write: (tx: TargetSql) => Promise<void>, options?: {
|
|
71
|
+
activate?: boolean;
|
|
72
|
+
synchronized?: boolean;
|
|
73
|
+
}): Promise<boolean>;
|
|
74
|
+
/** Two set-based writes per page, independent of page size. Checkpoint commits in the caller. */
|
|
75
|
+
writePage(tx: TargetSql, generation: string, items: Array<{
|
|
76
|
+
resource_type: ResourceType;
|
|
77
|
+
resource_id: string;
|
|
78
|
+
sequence: string;
|
|
79
|
+
resource: SmartFeedResource | null;
|
|
80
|
+
}>): Promise<void>;
|
|
81
|
+
invalidate(tx: TargetSql, generation: string, kind: 'reset' | 'group', from: string, through: string): Promise<void>;
|
|
82
|
+
reset(expected: DirectorySyncState, blocked?: boolean): Promise<boolean>;
|
|
83
|
+
resume(): Promise<void>;
|
|
84
|
+
mapExistingIdentity(resourceType: ResourceType, resourceId: string, localId: string): Promise<void>;
|
|
85
|
+
private readable;
|
|
86
|
+
list(options: ProjectionReadOptions & {
|
|
87
|
+
resourceType?: ResourceType;
|
|
88
|
+
after?: string;
|
|
89
|
+
limit?: number;
|
|
90
|
+
}): Promise<Array<{
|
|
91
|
+
resource: SmartFeedResource;
|
|
92
|
+
local_id: string;
|
|
93
|
+
}>>;
|
|
94
|
+
get(resourceType: ResourceType, resourceId: string, options: ProjectionReadOptions): Promise<SmartFeedResource | null>;
|
|
95
|
+
claimInvalidations(limit?: number, leaseSeconds?: number): Promise<DirectoryInvalidation[]>;
|
|
96
|
+
/** Page every affected key before acknowledging a group; resolve current state for cache effects. */
|
|
97
|
+
invalidationItems(event: DirectoryInvalidation, after?: string, limit?: number): Promise<Array<{
|
|
98
|
+
resource_type: ResourceType;
|
|
99
|
+
resource_id: string;
|
|
100
|
+
}>>;
|
|
101
|
+
acknowledgeInvalidation(event: Pick<DirectoryInvalidation, 'id' | 'lease_token'>): Promise<boolean>;
|
|
102
|
+
/** Preserve staged groups and unacknowledged invalidations while reclaiming bounded history. */
|
|
103
|
+
pruneObsolete(limit?: number): Promise<number>;
|
|
104
|
+
}
|
|
105
|
+
type FeedSource = Pick<ListingsClient, 'directory'>;
|
|
106
|
+
export declare class DirectorySyncWorker {
|
|
107
|
+
private readonly source;
|
|
108
|
+
private readonly target;
|
|
109
|
+
private readonly pageSize;
|
|
110
|
+
constructor(source: FeedSource, target: PostgresDirectoryTarget, pageSize?: number);
|
|
111
|
+
step(options?: {
|
|
112
|
+
signal?: AbortSignal;
|
|
113
|
+
}): Promise<{
|
|
114
|
+
status: 'progress' | 'idle' | 'conflict' | 'reset' | 'blocked';
|
|
115
|
+
applied: number;
|
|
116
|
+
}>;
|
|
117
|
+
private validateChange;
|
|
118
|
+
run(options: {
|
|
119
|
+
maxSteps: number;
|
|
120
|
+
signal?: AbortSignal;
|
|
121
|
+
}): Promise<Array<Awaited<ReturnType<DirectorySyncWorker['step']>>>>;
|
|
122
|
+
}
|
|
123
|
+
export {};
|