graphile-i18n 0.0.2 → 0.1.1
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/LICENSE +3 -1
- package/README.md +128 -34
- package/env.d.ts +5 -0
- package/env.js +17 -0
- package/esm/env.js +14 -0
- package/esm/index.js +5 -0
- package/esm/middleware.js +57 -0
- package/esm/plugin.js +147 -0
- package/index.d.ts +5 -0
- package/index.js +15 -0
- package/middleware.d.ts +26 -0
- package/middleware.js +65 -0
- package/package.json +33 -56
- package/plugin.d.ts +10 -0
- package/plugin.js +153 -0
- package/main/env.js +0 -22
- package/main/index.js +0 -37
- package/main/middleware.js +0 -95
- package/main/plugin.js +0 -206
- package/module/env.js +0 -9
- package/module/index.js +0 -4
- package/module/middleware.js +0 -60
- package/module/plugin.js +0 -173
package/module/middleware.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import DataLoader from 'dataloader';
|
|
2
|
-
import langParser from 'accept-language-parser';
|
|
3
|
-
import env from './env';
|
|
4
|
-
|
|
5
|
-
const escapeIdentifier = str => `"${str.replace(/"/g, '""')}"`;
|
|
6
|
-
|
|
7
|
-
export const makeLanguageDataLoaderForTable = _req => {
|
|
8
|
-
const cache = new Map();
|
|
9
|
-
return (props, pgClient, languageCodes, identifer, idType, sqlField, gqlField) => {
|
|
10
|
-
let dataLoader = cache.get(props);
|
|
11
|
-
|
|
12
|
-
if (!dataLoader) {
|
|
13
|
-
const {
|
|
14
|
-
table,
|
|
15
|
-
coalescedFields,
|
|
16
|
-
variationsTableName,
|
|
17
|
-
key
|
|
18
|
-
} = props;
|
|
19
|
-
const schemaName = escapeIdentifier(table.namespaceName);
|
|
20
|
-
const baseTable = escapeIdentifier(table.name);
|
|
21
|
-
const variationTable = escapeIdentifier(variationsTableName);
|
|
22
|
-
const joinKey = escapeIdentifier(key);
|
|
23
|
-
const fields = coalescedFields.join(', ');
|
|
24
|
-
const b = [schemaName, baseTable].join('.');
|
|
25
|
-
const v = [schemaName, variationTable].join('.');
|
|
26
|
-
dataLoader = new DataLoader(async ids => {
|
|
27
|
-
const {
|
|
28
|
-
rows
|
|
29
|
-
} = await pgClient.query(`
|
|
30
|
-
select *
|
|
31
|
-
from unnest($1::${idType}[]) ids(${identifer})
|
|
32
|
-
inner join lateral (
|
|
33
|
-
select b.${identifer}, v.${sqlField} as "${gqlField}", ${fields}
|
|
34
|
-
from ${b} b
|
|
35
|
-
left join ${v} v
|
|
36
|
-
on (v.${joinKey} = b.${identifer} and array_position($2, ${sqlField}) is not null)
|
|
37
|
-
where b.${identifer} = ids.${identifer}
|
|
38
|
-
order by array_position($2, ${sqlField}) asc nulls last
|
|
39
|
-
limit 1
|
|
40
|
-
) tmp on (true)
|
|
41
|
-
`, [ids, languageCodes]);
|
|
42
|
-
return ids.map(id => rows.find(r => r.id === id));
|
|
43
|
-
});
|
|
44
|
-
cache.set(props, dataLoader);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return dataLoader;
|
|
48
|
-
};
|
|
49
|
-
};
|
|
50
|
-
export const additionalGraphQLContextFromRequest = (req, res) => {
|
|
51
|
-
const language = langParser.pick(env.ACCEPTED_LANGUAGES, req.get('accept-language')); // Accept-Language: *
|
|
52
|
-
// Accept-Language: en-US,en;q=0.5
|
|
53
|
-
// Accept-Language: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
langCodes: [language],
|
|
57
|
-
// in future make fallback languages
|
|
58
|
-
getLanguageDataLoader: makeLanguageDataLoaderForTable(req)
|
|
59
|
-
};
|
|
60
|
-
};
|
package/module/plugin.js
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
const escapeIdentifier = str => `"${str.replace(/"/g, '""')}"`;
|
|
2
|
-
|
|
3
|
-
export const LangPlugin = (builder, options) => {
|
|
4
|
-
const {
|
|
5
|
-
langPluginLanguageCodeColumn = 'lang_code',
|
|
6
|
-
langPluginLanguageCodeGqlField = 'langCode',
|
|
7
|
-
langPluginAllowedTypes = ['citext', 'text'],
|
|
8
|
-
langPluginDefaultLanguages = ['en']
|
|
9
|
-
} = options;
|
|
10
|
-
builder.hook('build', build => {
|
|
11
|
-
const {
|
|
12
|
-
pgSql: sql
|
|
13
|
-
} = build;
|
|
14
|
-
/** @type {import('graphile-build-pg').PgIntrospectionResultsByKind} */
|
|
15
|
-
|
|
16
|
-
const introspection = build.pgIntrospectionResultsByKind;
|
|
17
|
-
const inflection = build.inflection;
|
|
18
|
-
const tablesWithLanguageTables = introspection.class.filter(table => table.tags.hasOwnProperty('i18n'));
|
|
19
|
-
const tablesWithLanguageTablesIdInfo = introspection.class.filter(table => table.tags.hasOwnProperty('i18n')).map(table => {
|
|
20
|
-
return {
|
|
21
|
-
identifier: table.primaryKeyConstraint.keyAttributes[0].name,
|
|
22
|
-
idType: table.primaryKeyConstraint.keyAttributes[0].type.name
|
|
23
|
-
};
|
|
24
|
-
});
|
|
25
|
-
const languageVariationTables = tablesWithLanguageTables.map(table => introspection.class.find(t => t.name === table.tags.i18n && t.namespaceName === table.namespaceName));
|
|
26
|
-
const i18nTables = {};
|
|
27
|
-
const tables = {};
|
|
28
|
-
tablesWithLanguageTables.forEach((table, i) => {
|
|
29
|
-
i18nTables[table.tags.i18n] = {
|
|
30
|
-
table: table.name,
|
|
31
|
-
key: null,
|
|
32
|
-
// action_id
|
|
33
|
-
connection: inflection.connection(inflection.tableType(table)),
|
|
34
|
-
attrs: {},
|
|
35
|
-
fields: {},
|
|
36
|
-
keyInfo: tablesWithLanguageTablesIdInfo[i]
|
|
37
|
-
};
|
|
38
|
-
tables[table.name] = table.tags.i18n;
|
|
39
|
-
});
|
|
40
|
-
languageVariationTables.forEach(table => {
|
|
41
|
-
const foreignConstraintsThatMatter = table.constraints.filter(c => c.type === 'f').filter(c => c.foreignClass.name === i18nTables[table.name].table);
|
|
42
|
-
if (foreignConstraintsThatMatter.length !== 1) throw new Error('lang table only supports one foreign key to parent table');
|
|
43
|
-
if (foreignConstraintsThatMatter[0].keyAttributes.length !== 1) throw new Error('lang table only supports one non compound foreign key to parent table');
|
|
44
|
-
i18nTables[table.name].key = foreignConstraintsThatMatter[0].keyAttributes[0].name;
|
|
45
|
-
const {
|
|
46
|
-
identifier,
|
|
47
|
-
idType
|
|
48
|
-
} = i18nTables[table.name].keyInfo;
|
|
49
|
-
table.attributes.forEach(attr => {
|
|
50
|
-
if ([langPluginLanguageCodeColumn, identifier].includes(attr.name)) return;
|
|
51
|
-
|
|
52
|
-
if (langPluginAllowedTypes.includes(attr.type.name)) {
|
|
53
|
-
i18nTables[table.name].fields[inflection.column(attr)] = {
|
|
54
|
-
type: attr.type.name,
|
|
55
|
-
attr: attr.name,
|
|
56
|
-
isNotNull: attr.isNotNull,
|
|
57
|
-
column: inflection.column(attr)
|
|
58
|
-
};
|
|
59
|
-
i18nTables[table.name].attrs[attr.name] = {
|
|
60
|
-
type: attr.type.name,
|
|
61
|
-
attr: attr.name,
|
|
62
|
-
column: inflection.column(attr)
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
return build.extend(build, {
|
|
68
|
-
i18n: {
|
|
69
|
-
i18nTables,
|
|
70
|
-
tables
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
builder.hook('GraphQLObjectType:fields', (fields, build, context) => {
|
|
75
|
-
const {
|
|
76
|
-
graphql: {
|
|
77
|
-
GraphQLString,
|
|
78
|
-
GraphQLObjectType,
|
|
79
|
-
GraphQLNonNull
|
|
80
|
-
},
|
|
81
|
-
i18n: {
|
|
82
|
-
i18nTables,
|
|
83
|
-
tables
|
|
84
|
-
},
|
|
85
|
-
pgSql: sql
|
|
86
|
-
} = build;
|
|
87
|
-
const {
|
|
88
|
-
scope: {
|
|
89
|
-
pgIntrospection: table,
|
|
90
|
-
isPgRowType
|
|
91
|
-
},
|
|
92
|
-
fieldWithHooks
|
|
93
|
-
} = context;
|
|
94
|
-
|
|
95
|
-
if (!isPgRowType || !table || table.kind !== 'class') {
|
|
96
|
-
return fields;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const variationsTableName = tables[table.name];
|
|
100
|
-
|
|
101
|
-
if (!variationsTableName) {
|
|
102
|
-
return fields;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const i18nTable = i18nTables[variationsTableName];
|
|
106
|
-
const {
|
|
107
|
-
identifier,
|
|
108
|
-
idType
|
|
109
|
-
} = i18nTable.keyInfo;
|
|
110
|
-
const {
|
|
111
|
-
key,
|
|
112
|
-
connection,
|
|
113
|
-
attrs,
|
|
114
|
-
fields: i18nFields
|
|
115
|
-
} = i18nTable;
|
|
116
|
-
const localeFieldName = 'localeStrings';
|
|
117
|
-
const localeFieldsType = new GraphQLObjectType({
|
|
118
|
-
name: `${context.Self.name}LocaleStrings`,
|
|
119
|
-
description: `Locales for ${context.Self.name}`,
|
|
120
|
-
fields: Object.keys(i18nFields).reduce((memo, field) => {
|
|
121
|
-
memo[field] = {
|
|
122
|
-
type: i18nFields[field].isNotNull ? new GraphQLNonNull(GraphQLString) : GraphQLString,
|
|
123
|
-
description: `Locale for ${field}`
|
|
124
|
-
};
|
|
125
|
-
return memo;
|
|
126
|
-
}, {
|
|
127
|
-
langCode: {
|
|
128
|
-
type: GraphQLString // MUST BE NULLABLE
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
})
|
|
132
|
-
});
|
|
133
|
-
return build.extend(fields, {
|
|
134
|
-
[localeFieldName]: fieldWithHooks(localeFieldName, fieldContext => {
|
|
135
|
-
const {
|
|
136
|
-
addDataGenerator
|
|
137
|
-
} = fieldContext;
|
|
138
|
-
addDataGenerator(parsedResolveInfoFragment => {
|
|
139
|
-
return {
|
|
140
|
-
pgQuery: queryBuilder => {
|
|
141
|
-
queryBuilder.select(sql.fragment`${queryBuilder.getTableAlias()}.${sql.identifier(identifier)}`, identifier);
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
});
|
|
145
|
-
const coalescedFields = Object.keys(i18nFields).map(field => {
|
|
146
|
-
const columnName = i18nFields[field].attr;
|
|
147
|
-
const escColumnName = escapeIdentifier(columnName);
|
|
148
|
-
const escFieldName = escapeIdentifier(field);
|
|
149
|
-
return `coalesce(v.${escColumnName}, b.${escColumnName}) as ${escFieldName}`;
|
|
150
|
-
});
|
|
151
|
-
const props = {
|
|
152
|
-
table,
|
|
153
|
-
coalescedFields,
|
|
154
|
-
variationsTableName,
|
|
155
|
-
key
|
|
156
|
-
};
|
|
157
|
-
return {
|
|
158
|
-
description: `Locales for ${context.Self.name}`,
|
|
159
|
-
type: new GraphQLNonNull(localeFieldsType),
|
|
160
|
-
|
|
161
|
-
async resolve({
|
|
162
|
-
id
|
|
163
|
-
}, args, context) {
|
|
164
|
-
const languageCodes = context.langCodes ?? langPluginDefaultLanguages;
|
|
165
|
-
const dataloader = context.getLanguageDataLoader(props, context.pgClient, languageCodes, identifier, idType, langPluginLanguageCodeColumn, langPluginLanguageCodeGqlField);
|
|
166
|
-
return dataloader.load(id);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
};
|
|
170
|
-
}, 'Adding the language code field from the lang plugin')
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
};
|