Skip to content

Commit 307711c

Browse files
ekremneyclaude
andauthored
feat(data-access): expose postgrestClient for direct PostgREST queries (#1396)
## Summary - Exposes the underlying `PostgrestClient` instance under a `services` namespace on the object returned by `createDataAccess()`, enabling direct PostgREST queries against tables that are not modeled as entities (e.g. analytics views, brand presence tables) - Adds full TypeScript typings for all 32 entity collections on the `DataAccess` interface (previously typed as `object`) - Re-exports 5 previously missing collection types from `models/index.d.ts` (`ApiKey`, `AuditUrl`, `PageCitability`, `PageIntent`, `Project`) ## Motivation Some API endpoints need to query PostgREST tables that don't have corresponding entity models in the data-access layer (e.g. `brand_presence_executions`, `brand_metrics_weekly`). Previously, developers resorted to reaching into collection internals like `Site.postgrestService` to get the raw client. This change makes it a first-class, typed API under a `services` namespace that cleanly separates infrastructure from entity collections. ## Usage ```javascript // Entity collections stay top-level const { Site, Organization } = context.dataAccess; const site = await Site.findById(siteId); // Infrastructure services live under the services namespace const { postgrestClient } = context.dataAccess.services; // Direct queries against non-entity tables const { data, error } = await postgrestClient .from('brand_presence_executions') .select('execution_date, visibility_score, sentiment') .eq('site_id', siteId) .gte('execution_date', '2025-01-01') .order('execution_date', { ascending: false }) .limit(100); ``` Full IDE autocomplete is available for the entire PostgREST query builder chain (`.from()`, `.select()`, `.eq()`, `.gte()`, `.order()`, `.range()`, `.rpc()`, etc.). ## Test plan - [x] Existing unit tests updated to verify `services.postgrestClient` is present and is the correct instance - [x] All 1522 unit tests passing - [x] Lint passing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4e29cc5 commit 307711c

File tree

5 files changed

+101
-10
lines changed

5 files changed

+101
-10
lines changed

packages/spacecat-shared-data-access/src/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@
1313
export type * from './errors';
1414
export type * from './models';
1515
export type * from './util';
16+
export type { DataAccess } from './service';

packages/spacecat-shared-data-access/src/models/index.d.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,36 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13-
export type * from './audit';
13+
export type * from './api-key';
1414
export type * from './async-job';
15-
export type * from './configuration';
15+
export type * from './audit';
16+
export type * from './audit-url';
1617
export type * from './base';
18+
export type * from './configuration';
1719
export type * from './consumer';
20+
export type * from './entitlement';
21+
export type * from './experiment';
1822
export type * from './fix-entity';
1923
export type * from './fix-entity-suggestion';
20-
export type * from './experiment';
21-
export type * from './entitlement';
2224
export type * from './import-job';
2325
export type * from './import-url';
2426
export type * from './key-event';
2527
export type * from './latest-audit';
2628
export type * from './opportunity';
2729
export type * from './organization';
30+
export type * from './page-citability';
31+
export type * from './page-intent';
32+
export type * from './project';
33+
export type * from './report';
2834
export type * from './scrape-job';
2935
export type * from './scrape-url';
36+
export type * from './sentiment-guideline';
37+
export type * from './sentiment-topic';
3038
export type * from './site';
3139
export type * from './site-candidate';
3240
export type * from './site-enrollment';
3341
export type * from './site-top-form';
3442
export type * from './site-top-page';
3543
export type * from './suggestion';
36-
export type * from './report';
3744
export type * from './trial-user';
38-
export type * from './trial-user-activity';
39-
export type * from './sentiment-guideline';
40-
export type * from './sentiment-topic';
45+
export type * from './trial-user-activity';

packages/spacecat-shared-data-access/src/service/index.d.ts

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,41 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13+
import type PostgrestClient from '@supabase/postgrest-js/dist/cjs/PostgrestClient';
14+
15+
import type { ApiKeyCollection } from '../models/api-key';
16+
import type { AsyncJobCollection } from '../models/async-job';
17+
import type { AuditCollection } from '../models/audit';
18+
import type { AuditUrlCollection } from '../models/audit-url';
19+
import type { ConfigurationCollection } from '../models/configuration';
20+
import type { ConsumerCollection } from '../models/consumer';
21+
import type { EntitlementCollection } from '../models/entitlement';
22+
import type { ExperimentCollection } from '../models/experiment';
23+
import type { FixEntityCollection } from '../models/fix-entity';
24+
import type { FixEntitySuggestionCollection } from '../models/fix-entity-suggestion';
25+
import type { ImportJobCollection } from '../models/import-job';
26+
import type { ImportUrlCollection } from '../models/import-url';
27+
import type { KeyEventCollection } from '../models/key-event';
28+
import type { LatestAuditCollection } from '../models/latest-audit';
29+
import type { OpportunityCollection } from '../models/opportunity';
30+
import type { OrganizationCollection } from '../models/organization';
31+
import type { PageCitabilityCollection } from '../models/page-citability';
32+
import type { PageIntentCollection } from '../models/page-intent';
33+
import type { ProjectCollection } from '../models/project';
34+
import type { ReportCollection } from '../models/report';
35+
import type { ScrapeJobCollection } from '../models/scrape-job';
36+
import type { ScrapeUrlCollection } from '../models/scrape-url';
37+
import type { SentimentGuidelineCollection } from '../models/sentiment-guideline';
38+
import type { SentimentTopicCollection } from '../models/sentiment-topic';
39+
import type { SiteCollection } from '../models/site';
40+
import type { SiteCandidateCollection } from '../models/site-candidate';
41+
import type { SiteEnrollmentCollection } from '../models/site-enrollment';
42+
import type { SiteTopFormCollection } from '../models/site-top-form';
43+
import type { SiteTopPageCollection } from '../models/site-top-page';
44+
import type { SuggestionCollection } from '../models/suggestion';
45+
import type { TrialUserCollection } from '../models/trial-user';
46+
import type { TrialUserActivityCollection } from '../models/trial-user-activity';
47+
1348
interface DataAccessConfig {
1449
postgrestUrl: string;
1550
postgrestSchema?: string;
@@ -19,8 +54,48 @@ interface DataAccessConfig {
1954
region?: string;
2055
}
2156

57+
export interface DataAccessServices {
58+
postgrestClient: PostgrestClient;
59+
}
60+
61+
export interface DataAccess {
62+
services: DataAccessServices;
63+
ApiKey: ApiKeyCollection;
64+
AsyncJob: AsyncJobCollection;
65+
Audit: AuditCollection;
66+
AuditUrl: AuditUrlCollection;
67+
Configuration: ConfigurationCollection;
68+
Consumer: ConsumerCollection;
69+
Entitlement: EntitlementCollection;
70+
Experiment: ExperimentCollection;
71+
FixEntity: FixEntityCollection;
72+
FixEntitySuggestion: FixEntitySuggestionCollection;
73+
ImportJob: ImportJobCollection;
74+
ImportUrl: ImportUrlCollection;
75+
KeyEvent: KeyEventCollection;
76+
LatestAudit: LatestAuditCollection;
77+
Opportunity: OpportunityCollection;
78+
Organization: OrganizationCollection;
79+
PageCitability: PageCitabilityCollection;
80+
PageIntent: PageIntentCollection;
81+
Project: ProjectCollection;
82+
Report: ReportCollection;
83+
ScrapeJob: ScrapeJobCollection;
84+
ScrapeUrl: ScrapeUrlCollection;
85+
SentimentGuideline: SentimentGuidelineCollection;
86+
SentimentTopic: SentimentTopicCollection;
87+
Site: SiteCollection;
88+
SiteCandidate: SiteCandidateCollection;
89+
SiteEnrollment: SiteEnrollmentCollection;
90+
SiteTopForm: SiteTopFormCollection;
91+
SiteTopPage: SiteTopPageCollection;
92+
Suggestion: SuggestionCollection;
93+
TrialUser: TrialUserCollection;
94+
TrialUserActivity: TrialUserActivityCollection;
95+
}
96+
2297
export function createDataAccess(
2398
config: DataAccessConfig,
2499
logger: object,
25100
client?: object,
26-
): object;
101+
): DataAccess;

packages/spacecat-shared-data-access/src/service/index.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,5 +98,10 @@ export const createDataAccess = (config, log = console, client = undefined) => {
9898
const services = createServices(postgrestService, config);
9999
const entityRegistry = new EntityRegistry(services, config, log);
100100

101-
return entityRegistry.getCollections();
101+
return {
102+
...entityRegistry.getCollections(),
103+
services: {
104+
postgrestClient: postgrestService,
105+
},
106+
};
102107
};

packages/spacecat-shared-data-access/test/unit/service/index.test.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ describe('service/index', () => {
2222
const dataAccess = createDataAccess({}, console, client);
2323

2424
expect(dataAccess).to.be.an('object');
25+
expect(dataAccess.services).to.be.an('object');
26+
expect(dataAccess.services.postgrestClient).to.equal(client);
2527
});
2628

2729
it('throws when postgrestUrl is missing and no client is provided', () => {
@@ -40,6 +42,9 @@ describe('service/index', () => {
4042
}, console);
4143

4244
expect(dataAccess).to.be.an('object');
45+
expect(dataAccess.services).to.be.an('object');
46+
expect(dataAccess.services.postgrestClient).to.be.an('object');
47+
expect(dataAccess.services.postgrestClient).to.have.property('from').that.is.a('function');
4348
});
4449

4550
it('creates data access with optional S3 config', () => {

0 commit comments

Comments
 (0)