@optizio/merchant-identity 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/dist/index.d.ts +157 -0
- package/dist/index.js +73 -0
- package/package.json +30 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared merchant-identity contract for the merchant-hub portfolio.
|
|
3
|
+
*
|
|
4
|
+
* Consumed by:
|
|
5
|
+
* - the `merchant-identity` service Worker, which implements `IdentityClient`
|
|
6
|
+
* against the shared D1, and
|
|
7
|
+
* - portfolio apps (DiscountKit, CodeBulk, Stackable), which call the service
|
|
8
|
+
* over an RPC service binding through the `identity()` seam below.
|
|
9
|
+
*
|
|
10
|
+
* This package holds only types + pure helpers, so it has no Cloudflare or
|
|
11
|
+
* runtime dependencies and is safe to import anywhere.
|
|
12
|
+
*/
|
|
13
|
+
export interface MerchantRecord {
|
|
14
|
+
shopDomain: string;
|
|
15
|
+
shopGid?: string | null;
|
|
16
|
+
name?: string | null;
|
|
17
|
+
email?: string | null;
|
|
18
|
+
planName?: string | null;
|
|
19
|
+
shopifyPlus?: boolean | null;
|
|
20
|
+
partnerDevelopment?: boolean | null;
|
|
21
|
+
currencyCode?: string | null;
|
|
22
|
+
ianaTimezone?: string | null;
|
|
23
|
+
primaryLocale?: string | null;
|
|
24
|
+
subscribedPlan?: string | null;
|
|
25
|
+
installedAt?: string | null;
|
|
26
|
+
createdAt?: string | null;
|
|
27
|
+
lastIdentifiedAt?: string | null;
|
|
28
|
+
}
|
|
29
|
+
export interface MerchantAppRecord {
|
|
30
|
+
shopDomain: string;
|
|
31
|
+
appId: string;
|
|
32
|
+
appHandle?: string | null;
|
|
33
|
+
installedAt?: string | null;
|
|
34
|
+
uninstalledAt?: string | null;
|
|
35
|
+
firstSeenAt?: string | null;
|
|
36
|
+
lastSeenAt?: string | null;
|
|
37
|
+
}
|
|
38
|
+
export interface MerchantUserRecord {
|
|
39
|
+
shopDomain: string;
|
|
40
|
+
userId: string;
|
|
41
|
+
firstName?: string | null;
|
|
42
|
+
lastName?: string | null;
|
|
43
|
+
email?: string | null;
|
|
44
|
+
emailVerified?: boolean | null;
|
|
45
|
+
accountOwner?: boolean | null;
|
|
46
|
+
locale?: string | null;
|
|
47
|
+
firstSeenAt?: string | null;
|
|
48
|
+
lastSeenAt?: string | null;
|
|
49
|
+
}
|
|
50
|
+
export interface MerchantSubscriptionRecord {
|
|
51
|
+
shopDomain: string;
|
|
52
|
+
appId?: string;
|
|
53
|
+
topic?: string;
|
|
54
|
+
userId?: string;
|
|
55
|
+
subscribed: boolean;
|
|
56
|
+
source?: string | null;
|
|
57
|
+
}
|
|
58
|
+
export interface StoredMerchant {
|
|
59
|
+
shopDomain: string;
|
|
60
|
+
shopGid: string | null;
|
|
61
|
+
name: string | null;
|
|
62
|
+
email: string | null;
|
|
63
|
+
planName: string | null;
|
|
64
|
+
shopifyPlus: boolean | null;
|
|
65
|
+
partnerDevelopment: boolean | null;
|
|
66
|
+
currencyCode: string | null;
|
|
67
|
+
ianaTimezone: string | null;
|
|
68
|
+
primaryLocale: string | null;
|
|
69
|
+
subscribedPlan: string | null;
|
|
70
|
+
installedAt: string | null;
|
|
71
|
+
lastIdentifiedAt: string | null;
|
|
72
|
+
createdAt: string | null;
|
|
73
|
+
updatedAt: string | null;
|
|
74
|
+
}
|
|
75
|
+
export interface StoredMerchantUser {
|
|
76
|
+
shopDomain: string;
|
|
77
|
+
userId: string;
|
|
78
|
+
firstName: string | null;
|
|
79
|
+
lastName: string | null;
|
|
80
|
+
email: string | null;
|
|
81
|
+
emailVerified: boolean | null;
|
|
82
|
+
accountOwner: boolean | null;
|
|
83
|
+
locale: string | null;
|
|
84
|
+
firstSeenAt: string | null;
|
|
85
|
+
lastSeenAt: string | null;
|
|
86
|
+
}
|
|
87
|
+
export interface StoredMerchantApp {
|
|
88
|
+
shopDomain: string;
|
|
89
|
+
appId: string;
|
|
90
|
+
appHandle: string | null;
|
|
91
|
+
installedAt: string | null;
|
|
92
|
+
uninstalledAt: string | null;
|
|
93
|
+
firstSeenAt: string | null;
|
|
94
|
+
lastSeenAt: string | null;
|
|
95
|
+
}
|
|
96
|
+
export interface StoredMerchantSubscription {
|
|
97
|
+
shopDomain: string;
|
|
98
|
+
appId: string;
|
|
99
|
+
topic: string;
|
|
100
|
+
userId: string;
|
|
101
|
+
subscribed: boolean;
|
|
102
|
+
source: string | null;
|
|
103
|
+
createdAt: string | null;
|
|
104
|
+
updatedAt: string | null;
|
|
105
|
+
}
|
|
106
|
+
export interface StoreDetails {
|
|
107
|
+
merchant: StoredMerchant | null;
|
|
108
|
+
users: StoredMerchantUser[];
|
|
109
|
+
apps: StoredMerchantApp[];
|
|
110
|
+
subscriptions: StoredMerchantSubscription[];
|
|
111
|
+
}
|
|
112
|
+
/** Sentinel stored for the `app_id` / `user_id` columns when a row applies to
|
|
113
|
+
* every app / every user of a store. */
|
|
114
|
+
export declare const SUBSCRIPTION_ALL_SCOPES = "*";
|
|
115
|
+
/** Sentinel stored for the `topic` column when a row applies to all topics. */
|
|
116
|
+
export declare const SUBSCRIPTION_ALL_TOPICS = "all";
|
|
117
|
+
export interface SubscriptionScope {
|
|
118
|
+
appId?: string;
|
|
119
|
+
topic?: string;
|
|
120
|
+
userId?: string;
|
|
121
|
+
/** Fallback returned by `isSubscribed` when no row matches (opt-out default). */
|
|
122
|
+
default?: boolean;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Resolve the effective subscribed state for a scope from a store's rows.
|
|
126
|
+
* Most-specific match wins (user > app > topic); returns `null` when nothing
|
|
127
|
+
* matches so callers can apply their own default.
|
|
128
|
+
*/
|
|
129
|
+
export declare function resolveSubscription(rows: StoredMerchantSubscription[], scope?: SubscriptionScope): boolean | null;
|
|
130
|
+
/**
|
|
131
|
+
* The method surface exposed by the `merchant-identity` service Worker and
|
|
132
|
+
* consumed by apps over an RPC service binding (`env.IDENTITY`). All arguments
|
|
133
|
+
* and return values are structured-cloneable so they cross the RPC boundary.
|
|
134
|
+
*/
|
|
135
|
+
export interface IdentityClient {
|
|
136
|
+
captureMerchant(input: MerchantRecord): Promise<void>;
|
|
137
|
+
captureMerchantUser(input: MerchantUserRecord): Promise<void>;
|
|
138
|
+
touchMerchantApp(input: MerchantAppRecord): Promise<void>;
|
|
139
|
+
markAppUninstalled(shopDomain: string, appId: string): Promise<void>;
|
|
140
|
+
setSubscription(input: MerchantSubscriptionRecord): Promise<void>;
|
|
141
|
+
isSubscribed(shopDomain: string, scope?: SubscriptionScope): Promise<boolean>;
|
|
142
|
+
getStoreDetails(shopDomain: string): Promise<StoreDetails>;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* No-op client used when the service is unavailable (local dev) or explicitly
|
|
146
|
+
* disabled via `IDENTITY_DISABLED`. Reads return empty/opt-out defaults so the
|
|
147
|
+
* host app behaves identically minus the capture side effects.
|
|
148
|
+
*/
|
|
149
|
+
export declare const NOOP_IDENTITY: IdentityClient;
|
|
150
|
+
/**
|
|
151
|
+
* Single seam every app uses instead of touching `env.IDENTITY` directly.
|
|
152
|
+
* Returns the real binding when present and enabled, otherwise a no-op client
|
|
153
|
+
* so apps spin up and run without the service (local dev). This is the point at
|
|
154
|
+
* which a future local direct-D1 fallback could be swapped in without changing
|
|
155
|
+
* any call sites.
|
|
156
|
+
*/
|
|
157
|
+
export declare function identity(binding: IdentityClient | null | undefined, disabled?: boolean | string): IdentityClient;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared merchant-identity contract for the merchant-hub portfolio.
|
|
3
|
+
*
|
|
4
|
+
* Consumed by:
|
|
5
|
+
* - the `merchant-identity` service Worker, which implements `IdentityClient`
|
|
6
|
+
* against the shared D1, and
|
|
7
|
+
* - portfolio apps (DiscountKit, CodeBulk, Stackable), which call the service
|
|
8
|
+
* over an RPC service binding through the `identity()` seam below.
|
|
9
|
+
*
|
|
10
|
+
* This package holds only types + pure helpers, so it has no Cloudflare or
|
|
11
|
+
* runtime dependencies and is safe to import anywhere.
|
|
12
|
+
*/
|
|
13
|
+
/* -------------------------------------------------------------------------- */
|
|
14
|
+
/* Subscriptions */
|
|
15
|
+
/* -------------------------------------------------------------------------- */
|
|
16
|
+
/** Sentinel stored for the `app_id` / `user_id` columns when a row applies to
|
|
17
|
+
* every app / every user of a store. */
|
|
18
|
+
export const SUBSCRIPTION_ALL_SCOPES = '*';
|
|
19
|
+
/** Sentinel stored for the `topic` column when a row applies to all topics. */
|
|
20
|
+
export const SUBSCRIPTION_ALL_TOPICS = 'all';
|
|
21
|
+
/**
|
|
22
|
+
* Resolve the effective subscribed state for a scope from a store's rows.
|
|
23
|
+
* Most-specific match wins (user > app > topic); returns `null` when nothing
|
|
24
|
+
* matches so callers can apply their own default.
|
|
25
|
+
*/
|
|
26
|
+
export function resolveSubscription(rows, scope = {}) {
|
|
27
|
+
const appId = scope.appId ?? SUBSCRIPTION_ALL_SCOPES;
|
|
28
|
+
const topic = scope.topic ?? SUBSCRIPTION_ALL_TOPICS;
|
|
29
|
+
const userId = scope.userId ?? SUBSCRIPTION_ALL_SCOPES;
|
|
30
|
+
let best = null;
|
|
31
|
+
for (const row of rows) {
|
|
32
|
+
const appScore = row.appId === appId ? 2 : row.appId === SUBSCRIPTION_ALL_SCOPES ? 0 : -1;
|
|
33
|
+
const topicScore = row.topic === topic ? 1 : row.topic === SUBSCRIPTION_ALL_TOPICS ? 0 : -1;
|
|
34
|
+
const userScore = row.userId === userId ? 4 : row.userId === SUBSCRIPTION_ALL_SCOPES ? 0 : -1;
|
|
35
|
+
if (appScore < 0 || topicScore < 0 || userScore < 0)
|
|
36
|
+
continue;
|
|
37
|
+
const score = appScore + topicScore + userScore;
|
|
38
|
+
if (!best || score > best.score)
|
|
39
|
+
best = { score, subscribed: row.subscribed };
|
|
40
|
+
}
|
|
41
|
+
return best ? best.subscribed : null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* No-op client used when the service is unavailable (local dev) or explicitly
|
|
45
|
+
* disabled via `IDENTITY_DISABLED`. Reads return empty/opt-out defaults so the
|
|
46
|
+
* host app behaves identically minus the capture side effects.
|
|
47
|
+
*/
|
|
48
|
+
export const NOOP_IDENTITY = {
|
|
49
|
+
async captureMerchant() { },
|
|
50
|
+
async captureMerchantUser() { },
|
|
51
|
+
async touchMerchantApp() { },
|
|
52
|
+
async markAppUninstalled() { },
|
|
53
|
+
async setSubscription() { },
|
|
54
|
+
async isSubscribed() {
|
|
55
|
+
return true;
|
|
56
|
+
},
|
|
57
|
+
async getStoreDetails() {
|
|
58
|
+
return { merchant: null, users: [], apps: [], subscriptions: [] };
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Single seam every app uses instead of touching `env.IDENTITY` directly.
|
|
63
|
+
* Returns the real binding when present and enabled, otherwise a no-op client
|
|
64
|
+
* so apps spin up and run without the service (local dev). This is the point at
|
|
65
|
+
* which a future local direct-D1 fallback could be swapped in without changing
|
|
66
|
+
* any call sites.
|
|
67
|
+
*/
|
|
68
|
+
export function identity(binding, disabled) {
|
|
69
|
+
if (!binding || disabled === true || disabled === 'true') {
|
|
70
|
+
return NOOP_IDENTITY;
|
|
71
|
+
}
|
|
72
|
+
return binding;
|
|
73
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@optizio/merchant-identity",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared merchant/user identity types + RPC client contract for the merchant-hub portfolio.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.build.json",
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
21
|
+
"prepare": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"registry": "https://registry.npmjs.org",
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"typescript": "^5.4.5"
|
|
29
|
+
}
|
|
30
|
+
}
|